# Offene Punkte — bei jedem Start prüfen und abhaken

Keine offenen Punkte.

jq -Rs '{systemMessage: ., hookSpecificOutput:{hookEventName:"SessionStart", additionalContext: .}}' /Users/marcus.hinz/claude-projects/robotunits/OFFENE-PUNKTE.md


# Offene Punkte — bei jedem Start prüfen und abhaken

Keine offenen Punkte.


Wir haben neue assets bekommen
schau mal im claude-ordner
fix3


CronCreate CronDelete CronList DesignSync EnterPlanMode EnterWorktree ExitPlanMode ExitWorktree Monitor NotebookEdit PushNotification RemoteTrigger SendMessage TaskCreate TaskGet TaskList TaskOutput TaskStop TaskUpdate WebFetch WebSearch


claude claude-code-guide Explore general-purpose Plan statusline-setup

- claude: Catch-all for any task that doesn't fit a more specific agent. FleetView's default when no agent name is typed. (Tools: *)

- claude-code-guide: Use this agent when the user asks questions ("Can Claude...", "Does Claude...", "How do I...") about: (1) Claude Code (the CLI tool) - features, hooks, slash commands, MCP servers, settings, IDE integrations, keyboard shortcuts; (2) Claude Agent SDK - building custom agents; (3) Claude API (formerly Anthropic API) - API usage, tool use, Anthropic SDK usage. **IMPORTANT:** Before spawning a new agent, check if there is already a running or recently completed claude-code-guide agent that you can continue via SendMessage. (Tools: Bash, Read, WebFetch, WebSearch)

- Explore: Read-only search agent for broad fan-out searches — when answering means sweeping many files, directories, or naming conventions and you only need the conclusion, not the file dumps. It reads excerpts rather than whole files, so it locates code; it doesn't review or audit it. Specify search breadth: "medium" for moderate exploration, "very thorough" for multiple locations and naming conventions. (Tools: All tools except Agent, Artifact, ExitPlanMode, Edit, Write, NotebookEdit)

- general-purpose: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. (Tools: *)

- Plan: Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. (Tools: All tools except Agent, Artifact, ExitPlanMode, Edit, Write, NotebookEdit)

- statusline-setup: Use this agent to configure the user's Claude Code status line setting. (Tools: Read, Edit)


- git-sync: Commit + Push aller persistent konfigurierten Projekt-Repos
- deep-research: Deep research harness — fan-out web searches, fetch sources, adversarially verify claims, synthesize a cited report. - When the user wants a deep, multi-source, fact-checked research report on any topic. BEFORE invoking, check if the question is specific enough to research directly — if underspecified (e.g., "what car to buy" without budget/use-case/region), ask 2-3 clarifying questions to narrow scope. Then pass the refined question as args, weaving the answers in.
- dataviz: Use this skill whenever you are about to create ANY chart, graph, plot, dashboard, or data visualization, in ANY output medium — an HTML or React artifact, inline SVG, plotting code in any library (matplotlib, plotly, d3, Recharts, …), an image/PNG you will render and upload, or a chart shared into Slack. Read it BEFORE writing the first line of chart code, choosing chart colors, building a stat tile / meter / KPI row, or laying out a dashboard. Produces visualizations that read as one system — elegant, accessible, consistent in light and dark — using a brand-neutral placeholder palette you swap for your own. Teaches a design-system-agnostic method: a form heuristic, a color formula with a runnable validator, mark specs, and interaction rules. A validated default palette is documented in `references/palette.md` — swap that file's values for your brand's. Triggers on: "chart", "graph", "plot", "data viz", "visualization", "dashboard", "analytics", "visualize data", "categorical colors", "sequential / diverging palette", "stat tile", "sparkline", "heatmap", "legend", "axis", "tooltip", "chart colors", "color by series".
- artifact-design: Design guidance and fundamentals for Artifacts.
- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors ("from now on when X", "each time X", "whenever X", "before/after X") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions ("allow X", "add permission", "move permission to"), env vars ("set X=Y"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: "allow npm commands", "add bq permission to global settings", "move permission to user settings", "set DEBUG=true", "when claude stops show X". For simple settings like theme/model, suggest the /config command.
- keybindings-help: Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json. Examples: "rebind ctrl+s", "add a chord shortcut", "change the submit key", "customize keybindings".
- verify: Verify that a code change actually does what it's supposed to by exercising it end-to-end and observing behavior — drive the affected flow, not just tests or typecheck. Run before committing nontrivial changes; bootstraps this repo's project verify skill if none exists yet. Don't invoke it on a diff that only touches tests, docs, or other code with no runtime surface to drive (a change to product source always has one) — there's nothing to observe.
- code-review: Review the current diff for correctness bugs and reuse/simplification/efficiency cleanups at the given effort level (low/medium: fewer, high-confidence findings; high→max: broader coverage, may include uncertain findings; ultra: deep multi-agent review in the cloud). Pass --comment to post findings as inline PR comments, or --fix to apply the findings to the working tree after the review.
- simplify: Review the changed code for reuse, simplification, efficiency, and altitude cleanups, then apply the fixes. Quality only — it does not hunt for bugs; use /code-review for that.
- fewer-permission-prompts: Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.
- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. "check the deploy every 5 minutes", "keep running /babysit-prs"). Do NOT invoke for one-off tasks.
- schedule: Create, update, list, or run scheduled cloud agents (routines) that execute on a cron schedule. - When the user wants to schedule a recurring cloud agent, set up automated tasks, create a cron job for Claude Code, or manage their scheduled agents/routines. Also use when the user wants a one-time scheduled run ("run this once at 3pm", "remind me to check X tomorrow").
- claude-api: Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration.
TRIGGER — read BEFORE opening the target file; don't skip because it "looks like a one-liner" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR the task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens).
SKIP only when another provider is being worked on (overrides all triggers): OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in the query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over the project hits (run this grep FIRST if no provider named — don't Read the file).
- run: Launch and drive this project's app to see a change working. Use when asked to run, start, or screenshot the app, or to confirm a change works in the real app (not just tests). First looks for a project skill that already covers launching the app; otherwise falls back to built-in patterns per project type (CLI, server, TUI, Electron, browser-driven, library).
- init: Initialize a new CLAUDE.md file with codebase documentation
- review: Review a GitHub pull request; for your working diff use /code-review
- security-review: Complete a security review of the pending changes on the current branch

git-sync deep-research dataviz artifact-design update-config keybindings-help verify code-review simplify fewer-permission-prompts loop schedule claude-api run init review security-review


Bash

ls -la ~/claude-projects/ && echo "---fix3---" && find ~/claude-projects -maxdepth 3 -iname "*fix3*" 2>/dev/null

List claude-projects and find fix3


total 64
drwxr-xr-x  13 marcus.hinz  staff    416 Jul  6 15:54 .
drwx------+ 48 marcus.hinz  staff   1536 Jul  7 08:21 ..
-rw-r--r--@  1 marcus.hinz  staff  26628 Jul  7 08:29 .DS_Store
drwxr-xr-x  16 marcus.hinz  staff    512 Jul  7 07:52 .git
-rw-r--r--   1 marcus.hinz  staff     31 Jun 21 19:30 .gitignore
drwxr-xr-x  10 marcus.hinz  staff    320 Jul  6 16:00 08-07
drwxr-xr-x  20 marcus.hinz  staff    640 Jul  6 20:10 ai-control
drwxr-xr-x   8 marcus.hinz  staff    256 Jul  5 15:04 limbach
drwxr-xr-x  13 marcus.hinz  staff    416 Jul  7 08:20 misc
drwxr-xr-x  11 marcus.hinz  staff    352 Jul  6 16:00 pool
drwxr-xr-x  14 marcus.hinz  staff    448 Jul  7 08:30 robotunits
drwxr-xr-x  10 marcus.hinz  staff    320 Jul  5 18:50 wittgenstein
drwxr-xr-x   8 marcus.hinz  staff    256 Jul  5 15:04 wuerth-plato
---fix3---
/Users/marcus.hinz/claude-projects/robotunits/katalog_fix3.zip


cd ~/claude-projects/robotunits && ls -la && echo "---zip contents---" && unzip -l katalog_fix3.zip

List robotunits and zip contents


<persisted-output>
Output too large (229KB). Full output saved to: /Users/marcus.hinz/.config/ai-control/pools/private/projects/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/tool-results/bgt722s3b.txt

Preview (first 2KB):
total 9160
drwxr-xr-x  14 marcus.hinz  staff      448 Jul  7 08:30 .
drwxr-xr-x  13 marcus.hinz  staff      416 Jul  6 15:54 ..
drwxr-xr-x   4 marcus.hinz  staff      128 Jul  4 15:48 .claude
-rw-r--r--@  1 marcus.hinz  staff     8196 Jul  7 08:30 .DS_Store
-rw-r--r--   1 marcus.hinz  staff       20 Jul  3 20:24 .gitignore
-rw-------   1 marcus.hinz  staff       95 Jul  5 15:04 ai-control.json
drwxr-xr-x   2 marcus.hinz  staff       64 Jul  4 21:31 bin
drwxr-x---   2 marcus.hinz  staff       64 Jun 27 09:46 doc
-rw-r--r--@  1 marcus.hinz  staff    21895 Jul  3 11:13 GEKO-16.xml
-rw-r--r--@  1 marcus.hinz  staff     8366 Jul  3 11:00 GEKO-76-steher-cpq-geko.drawio
-rw-r--r--@  1 marcus.hinz  staff    21494 Jul  1 15:01 GEKO-76.xml
-rw-r--r--@  1 marcus.hinz  staff  4603188 Jul  7 08:12 katalog_fix3.zip
drwxr-xr-x  14 marcus.hinz  staff      448 Jul  3 12:47 memory
-rw-r--r--   1 marcus.hinz  staff       79 Jun 29 20:15 OFFENE-PUNKTE.md
---zip contents---
Archive:  katalog_fix3.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  07-06-2026 13:58   katalog/
        0  07-06-2026 20:33   katalog/rollenfoerderer/
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/abdeckung_aussenradiusprofil_90_1190_0/
     2882  07-06-2026 20:02   katalog/rollenfoerderer/R5C/abdeckung_aussenradiusprofil_90_1190_0/abdeckung_aussenradiusprofil_90_1190_0@1.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1190_0/
     2904  07-06-2026 20:02   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1190_0/rollenfoerdererprofil_aussenradius_45_1190_0@1.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1190_1/
     2904  07-06-2026 20:02   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1190_1/rollenfoerdererprofil_aussenradius_45_1190_1@1.json
...
</persisted-output>

total 9160
drwxr-xr-x  14 marcus.hinz  staff      448 Jul  7 08:30 .
drwxr-xr-x  13 marcus.hinz  staff      416 Jul  6 15:54 ..
drwxr-xr-x   4 marcus.hinz  staff      128 Jul  4 15:48 .claude
-rw-r--r--@  1 marcus.hinz  staff     8196 Jul  7 08:30 .DS_Store
-rw-r--r--   1 marcus.hinz  staff       20 Jul  3 20:24 .gitignore
-rw-------   1 marcus.hinz  staff       95 Jul  5 15:04 ai-control.json
drwxr-xr-x   2 marcus.hinz  staff       64 Jul  4 21:31 bin
drwxr-x---   2 marcus.hinz  staff       64 Jun 27 09:46 doc
-rw-r--r--@  1 marcus.hinz  staff    21895 Jul  3 11:13 GEKO-16.xml
-rw-r--r--@  1 marcus.hinz  staff     8366 Jul  3 11:00 GEKO-76-steher-cpq-geko.drawio
-rw-r--r--@  1 marcus.hinz  staff    21494 Jul  1 15:01 GEKO-76.xml
-rw-r--r--@  1 marcus.hinz  staff  4603188 Jul  7 08:12 katalog_fix3.zip
drwxr-xr-x  14 marcus.hinz  staff      448 Jul  3 12:47 memory
-rw-r--r--   1 marcus.hinz  staff       79 Jun 29 20:15 OFFENE-PUNKTE.md
---zip contents---
Archive:  katalog_fix3.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  07-06-2026 13:58   katalog/
        0  07-06-2026 20:33   katalog/rollenfoerderer/
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/abdeckung_aussenradiusprofil_90_1190_0/
     2882  07-06-2026 20:02   katalog/rollenfoerderer/R5C/abdeckung_aussenradiusprofil_90_1190_0/abdeckung_aussenradiusprofil_90_1190_0@1.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1190_0/
     2904  07-06-2026 20:02   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1190_0/rollenfoerdererprofil_aussenradius_45_1190_0@1.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1190_1/
     2904  07-06-2026 20:02   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1190_1/rollenfoerdererprofil_aussenradius_45_1190_1@1.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1360_0/
     2904  07-06-2026 20:02   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_45_1360_0/rollenfoerdererprofil_aussenradius_45_1360_0@1.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_90_1190_0/
     2771  07-06-2026 20:02   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_90_1190_0/rollenfoerdererprofil_aussenradius_90_1190_0@1.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_90_1190_1/
     2771  07-06-2026 20:02   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_90_1190_1/rollenfoerdererprofil_aussenradius_90_1190_1@1.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_90_1360_0/
     2771  07-06-2026 20:02   katalog/rollenfoerderer/R5C/rollenfoerdererprofil_aussenradius_90_1360_0/rollenfoerdererprofil_aussenradius_90_1360_0@1.json
        0  07-06-2026 13:58   katalog/rollenfoerderer/R5M/
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5M/R5M_300x300/
     3610  07-06-2026 20:02   katalog/rollenfoerderer/R5M/R5M_300x300/CAP4013.json
    11430  07-06-2026 20:02   katalog/rollenfoerderer/R5M/R5M_300x300/clone_12417508.json
     3918  07-06-2026 20:02   katalog/rollenfoerderer/R5M/R5M_300x300/clone_17460517.json
     2131  07-06-2026 20:02   katalog/rollenfoerderer/R5M/R5M_300x300/clone_18921978.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_25308369.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_29832891.json
     2143  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_34615681.json
    31670  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_42280337.json
    43692  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_43671720.json
     3918  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_46561664.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_52183244.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_53298361.json
    11430  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_67129214.json
    30679  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_70043125.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_85640707.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_95017071.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_95835889.json
   113558  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/clone_96750831.json
   157995  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5M_300x300.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_DIAGONALHALTERUNG.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_EINSCHLEUSER_ROHR_3.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_FOERDERRICHTUNG.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_MOTORROLLE_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_MOTORROLLE_RIEMENSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_RIEMEN_80.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_ROLLENHALTER.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_ROLLE_EINSCHLEUSER_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_ROLLE_EINSCHLEUSER_RIEMENSEITE_ANGETRIEBEN.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_TRAGROLLE_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_TRAGROLLE_RIEMENSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/R5S_WINKELVERBINDER.json
     2290  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x300/ROP1006.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5M/R5M_300x400/
     3610  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/CAP4013.json
    11430  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_12417508.json
     3918  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_17460517.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_18921978.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_25308369.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_29832891.json
     2143  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_34615681.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_35573644.json
    31670  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_42280337.json
    43692  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_43671720.json
     3918  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_46561664.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_48917975.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_52183244.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_53298361.json
    11430  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_67129214.json
    30679  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_70043125.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_85640707.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_95017071.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_95835889.json
   113558  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/clone_96750831.json
   157995  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5M_300x400.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_DIAGONALHALTERUNG.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_FOERDERRICHTUNG.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_MOTORROLLE_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_MOTORROLLE_RIEMENSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_RIEMEN_80.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_ROLLENHALTER.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_ROLLE_EINSCHLEUSER_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_ROLLE_EINSCHLEUSER_RIEMENSEITE_ANGETRIEBEN.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_TRAGROLLE_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_TRAGROLLE_RIEMENSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/R5S_WINKELVERBINDER.json
     2290  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x400/ROP1006.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5M/R5M_300x600/
     3610  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/CAP4013.json
    11430  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_12417508.json
     3918  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_17460517.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_18921978.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_25308369.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_29832891.json
     2143  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_34615681.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_35573644.json
    31670  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_42280337.json
    43692  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_43671720.json
     3918  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_46561664.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_48917975.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_52183244.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_53298361.json
     2130  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_61708761.json
    11430  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_67129214.json
    30679  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_70043125.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_74870920.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_85640707.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_95017071.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_95835889.json
   113558  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/clone_96750831.json
   157995  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5M_300x600.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_DIAGONALHALTERUNG.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_FOERDERRICHTUNG.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_MOTORROLLE_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_MOTORROLLE_RIEMENSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_RIEMEN_80.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_ROLLENHALTER.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_ROLLE_EINSCHLEUSER_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_ROLLE_EINSCHLEUSER_RIEMENSEITE_ANGETRIEBEN.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_TRAGROLLE_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_TRAGROLLE_RIEMENSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/R5S_WINKELVERBINDER.json
     2290  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_300x600/ROP1006.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5M/R5M_400x300/
     3610  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/CAP4013.json
    11430  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_12417508.json
     3918  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_17460517.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_18921978.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_25308369.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_29832891.json
     2143  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_34615681.json
    31670  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_42280337.json
    43692  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_43671720.json
     3918  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_46561664.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_52183244.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_53298361.json
    11430  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_67129214.json
    30679  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_70043125.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_85640707.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_95017071.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_95835889.json
   113558  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/clone_96750831.json
   157995  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5M_400x300.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_DIAGONALHALTERUNG.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_FOERDERRICHTUNG.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_MOTORROLLE_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_MOTORROLLE_RIEMENSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_RIEMEN_105.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_ROLLENHALTER.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_ROLLE_EINSCHLEUSER_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_ROLLE_EINSCHLEUSER_RIEMENSEITE_ANGETRIEBEN.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_TRAGROLLE_ENDSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_TRAGROLLE_RIEMENSEITE.json
        0  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/R5S_WINKELVERBINDER.json
     2290  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x300/ROP1006.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5M/R5M_400x400/
     3610  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/CAP4013.json
    11430  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_12417508.json
     3918  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_17460517.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_18921978.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_25308369.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_29832891.json
     2143  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_34615681.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_35573644.json
    31670  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_42280337.json
    43692  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_43671720.json
     3918  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_46561664.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_52183244.json
     3107  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_53298361.json
    11430  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_67129214.json
    30679  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_70043125.json
     7576  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_85640707.json
     2131  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_95017071.json
     6680  07-06-2026 20:03   katalog/rollenfoerderer/R5M/R5M_400x400/clone_95835889.json
   113558  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/clone_96750831.json
   157995  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5M_400x400.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_DIAGONALHALTERUNG.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_FOERDERRICHTUNG.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_MOTORROLLE_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_MOTORROLLE_RIEMENSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_RIEMEN_105.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_ROLLENHALTER.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_ROLLE_EINSCHLEUSER_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_ROLLE_EINSCHLEUSER_RIEMENSEITE_ANGETRIEBEN.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_TRAGROLLE_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_TRAGROLLE_RIEMENSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/R5S_WINKELVERBINDER.json
     2290  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x400/ROP1006.json
        0  07-06-2026 20:33   katalog/rollenfoerderer/R5M/R5M_400x600/
     3610  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/CAP4013.json
    11430  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_12417508.json
     3918  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_17460517.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_18921978.json
     6680  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_25308369.json
     3107  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_29832891.json
     2143  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_34615681.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_35573644.json
    31670  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_42280337.json
    43692  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_43671720.json
     3918  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_46561664.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_48917975.json
     7576  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_52183244.json
     3107  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_53298361.json
     2130  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_61708761.json
    11430  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_67129214.json
    30679  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_70043125.json
     7576  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_85640707.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_95017071.json
     6680  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_95835889.json
   113558  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/clone_96750831.json
   157995  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5M_400x600.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_DIAGONALHALTERUNG.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_FOERDERRICHTUNG.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_MOTORROLLE_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_MOTORROLLE_RIEMENSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_RIEMEN_105.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_ROLLENHALTER.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_ROLLE_EINSCHLEUSER_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_ROLLE_EINSCHLEUSER_RIEMENSEITE_ANGETRIEBEN.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_TRAGROLLE_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_TRAGROLLE_RIEMENSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/R5S_WINKELVERBINDER.json
     2290  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_400x600/ROP1006.json
        0  07-06-2026 20:34   katalog/rollenfoerderer/R5M/R5M_600x300/
     3610  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/CAP4013.json
    11430  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_12417508.json
     3918  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_17460517.json
     6680  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_25308369.json
     3107  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_29832891.json
     2143  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_34615681.json
    31670  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_42280337.json
    43692  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_43671720.json
     3918  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_46561664.json
     7576  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_52183244.json
     3107  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_53298361.json
    11430  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_67129214.json
    30679  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_70043125.json
     7576  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_85640707.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_95017071.json
     6680  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_95835889.json
   113558  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/clone_96750831.json
   157995  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5M_600x300.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_DIAGONALHALTERUNG.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_FOERDERRICHTUNG.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_MOTORROLLE_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_MOTORROLLE_RIEMENSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_RIEMEN_160.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_ROLLENHALTER.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_ROLLE_EINSCHLEUSER_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_ROLLE_EINSCHLEUSER_RIEMENSEITE_ANGETRIEBEN.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_TRAGROLLE_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_TRAGROLLE_RIEMENSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/R5S_WINKELVERBINDER.json
     2290  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x300/ROP1006.json
        0  07-06-2026 20:34   katalog/rollenfoerderer/R5M/R5M_600x400/
     3610  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/CAP4013.json
    11430  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_12417508.json
     3918  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_17460517.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_18921978.json
     6680  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_25308369.json
     3107  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_29832891.json
     2143  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_34615681.json
    31670  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_42280337.json
    43692  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_43671720.json
     3918  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_46561664.json
     7576  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_52183244.json
     3107  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_53298361.json
    11430  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_67129214.json
    30679  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_70043125.json
     7576  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_85640707.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_95017071.json
     6680  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_95835889.json
   113558  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/clone_96750831.json
   157995  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5M_600x400.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_DIAGONALHALTERUNG.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_FOERDERRICHTUNG.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_MOTORROLLE_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_MOTORROLLE_RIEMENSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_RIEMEN_160.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_ROLLENHALTER.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_ROLLE_EINSCHLEUSER_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_ROLLE_EINSCHLEUSER_RIEMENSEITE_ANGETRIEBEN.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_TRAGROLLE_ENDSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_TRAGROLLE_RIEMENSEITE.json
        0  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/R5S_WINKELVERBINDER.json
     2290  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x400/ROP1006.json
        0  07-06-2026 20:34   katalog/rollenfoerderer/R5M/R5M_600x600/
     3610  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/CAP4013.json
    11430  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_12417508.json
     3918  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_17460517.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_18921978.json
     6680  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_25308369.json
     3107  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_29832891.json
     2143  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_34615681.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_35573644.json
    31670  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_42280337.json
    43692  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_43671720.json
     3918  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_46561664.json
     7576  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_52183244.json
     3107  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_53298361.json
    11430  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_67129214.json
    30679  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_70043125.json
     7576  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_85640707.json
     2131  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_95017071.json
     6680  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_95835889.json
   113558  07-06-2026 20:04   katalog/rollenfoerderer/R5M/R5M_600x600/clone_96750831.json
   157995  07-06-2026 20:05   katalog/rollenfoerderer/R5M/R5M_600x600/R5M_600x600.json
        0  07-06-2026 20:05   katalog/rollenfoerderer/R5M/R5M_600x600/R5S_DIAGONALHALTERUNG.json
        0  07-06-2026 20:05   katalog/rollenfoerderer/R5M/R5M_600x600/R5S_FOERDERRICHTUNG.json
        0  07-06-2026 20:05   katalog/rollenfoerderer/R5M/R5M_600x600/R5S_MOTORROLLE_ENDSEITE.json
        0  07-06-2026 20:05   katalog/rollenfoerderer/R5M/R5M_600x600/R5S_MOTORROLLE_RIEMENSEITE.json
        0  07-06-2026 20:05   katalog/rollenfoerderer/R5M/R5M_600x600/R5S_RIEMEN_160.json
        0  07-06-2026 20:05   katalog/rollenfoerderer/R5M/R5M_


Wir haben neue assets bekommen schau mal im claude-ordner fix3


cd ~/claude-projects/robotunits && unzip -l katalog_fix3.zip | tail -30 && echo "===TOTAL===" && unzip -l katalog_fix3.zip | grep -c "\.json$" && echo "===TOP DIRS===" && unzip -l katalog_fix3.zip | grep -oE 'katalog/[^/]+/' | sort -u

Summarize zip structure


     2581  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SLA/clone_86315360.json
   155694  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SLA/clone_98256299.json
     1988  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SLA/DOR4501.json
     2063  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SLA/DOR4511.json
     2982  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SLA/DOR4553.json
     2491  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SLA/DOR4555.json
   163038  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SLA/SBS1044SLA.json
     2157  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SLA/TIN4511.json
   163038  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SLA.json
        0  07-06-2026 20:34   katalog/schutzzaun_basic/SBS1044SRA/
     2135  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/BAP2051.json
     5316  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/CAP4040.json
     2244  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/CAP4080.json
     2247  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/clone_15733766.json
     2581  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/clone_16354391.json
     2017  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/clone_27129162.json
     2581  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/clone_40680722.json
   155664  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/clone_52124172.json
     2248  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/clone_61707745.json
     2577  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/clone_80968005.json
     1988  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/DOR4501.json
     2063  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/DOR4511.json
     2982  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/DOR4553.json
     2491  07-06-2026 20:31   katalog/schutzzaun_basic/SBS1044SRA/DOR4555.json
   163008  07-06-2026 20:32   katalog/schutzzaun_basic/SBS1044SRA/SBS1044SRA.json
     2157  07-06-2026 20:32   katalog/schutzzaun_basic/SBS1044SRA/TIN4511.json
   163008  07-06-2026 20:32   katalog/schutzzaun_basic/SBS1044SRA.json
   164553  03-27-2026 17:17   katalog/schutzzaun_basic/schutzzaun_basic.json
---------                     -------
 49954681                     2717 files
===TOTAL===
2513
===TOP DIRS===
katalog/rollenfoerderer/
katalog/schutzzaun_allround/
katalog/schutzzaun_basic/


find ~/projects/geko-fire -maxdepth 4 -type d -iname "katalog" 2>/dev/null; echo "---"; find ~/projects/geko-fire -maxdepth 4 -type d \( -iname "*rollenfoerderer*" -o -iname "*schutzzaun*" \) 2>/dev/null | head

Find existing katalog dir in geko-fire


grep -rl "katalog" ~/projects/geko-fire --include="*.py" --include="*.json" --include="*.md" -i 2>/dev/null | head; echo "===prev katalog zips==="; ls -la ~/claude-projects/robotunits/*.zip ~/Downloads/*katalog* 2>/dev/null; echo "===katalog dirs anywhere in robotunits==="; find ~/claude-projects/robotunits -maxdepth 2 -iname "*katalog*" 2>/dev/null

Locate katalog usage and previous zips


===prev katalog zips===
(eval):1: no matches found: /Users/marcus.hinz/Downloads/*katalog*
===katalog dirs anywhere in robotunits===
/Users/marcus.hinz/claude-projects/robotunits/katalog_fix3.zip


cd ~/claude-projects/robotunits && unzip -p katalog_fix3.zip "katalog/rollenfoerderer/R5C/abdeckung_aussenradiusprofil_90_1190_0/abdeckung_aussenradiusprofil_90_1190_0@1.json" | head -c 1500

Peek a small asset JSON


cd ~/claude-projects/robotunits && echo "file extensions in zip:"; unzip -l katalog_fix3.zip | grep -oE '\.[a-z0-9]+$' | sort | uniq -c; echo "=== SBS subdirs (schutzzaun_basic) ==="; unzip -l katalog_fix3.zip | grep -oE 'katalog/schutzzaun_basic/[^/]+/' | sort -u | head -20

Check file extensions and structure


file extensions in zip:
2513 .json
   1 .zip
=== SBS subdirs (schutzzaun_basic) ===
katalog/schutzzaun_basic/SBD2083PLA/
katalog/schutzzaun_basic/SBD2083PRA/
katalog/schutzzaun_basic/SBD2083SLA/
katalog/schutzzaun_basic/SBD2083SRA/
katalog/schutzzaun_basic/SBE0174PNA/
katalog/schutzzaun_basic/SBE0174SNA/
katalog/schutzzaun_basic/SBE0217PNA/
katalog/schutzzaun_basic/SBE0217SNA/
katalog/schutzzaun_basic/SBE0260PNA/
katalog/schutzzaun_basic/SBE0260SNA/
katalog/schutzzaun_basic/SBE0303PNA/
katalog/schutzzaun_basic/SBE0303SNA/
katalog/schutzzaun_basic/SBE0313SNA/
katalog/schutzzaun_basic/SBE0346PNA/
katalog/schutzzaun_basic/SBE0346SNA/
katalog/schutzzaun_basic/SBE0389PNA/
katalog/schutzzaun_basic/SBE0389SNA/
katalog/schutzzaun_basic/SBE0432PNA/
katalog/schutzzaun_basic/SBE0432SNA/
katalog/schutzzaun_basic/SBE0475PNA/


cd ~/claude-projects/robotunits && unzip -l katalog_fix3.zip | grep "\.zip$"

Find nested zip


Archive:  katalog_fix3.zip


Im claude-ordner (`~/claude-projects/robotunits`) liegt `katalog_fix3.zip` (4,6 MB, vom 07.07. 08:12). Inhalt:

- **2717 Einträge, davon 2513 JSON** (`_resourceClass: sceneGraph_entity`), keine `.glb` — die JSONs referenzieren die Geometrie nur per Pfad (`sceneGraph_reference … .glb`).
- Drei Produktfamilien unter `katalog/`:
  - `rollenfoerderer/` (R5C: Abdeckungen, Außenradiusprofile)
  - `schutzzaun_allround/`
  - `schutzzaun_basic/` (SBS/SBD/SBE-Varianten mit Unterteilen wie CAP/DOR/BAP/TIN und `clone_*`-Teilen)
- Struktur je Baugruppe: ein Ordner `<ID>/` mit Einzelteil-JSONs plus eine gleichnamige `<ID>.json` als Zusammenfassung.
- Entities enthalten `component_transform`, `component_pmi` (CS_BASE/CS_BASE_PRE mit Position/Rotation) und `component_collider`.

Ein bestehender Katalog-Ordner oder eine Nutzung von „katalog" in `geko-fire` ist nicht vorhanden — die Assets liegen aktuell nur als Zip.


ok -- kannst du analysieren, ob wir mit den Assets arbeiten können
vor allem, ob die PMIs so vorliegen, dass sie überhaupt zu den assets passen und NICHT z.B. 20m weg sind?


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && rm -rf katalog && unzip -q ~/claude-projects/robotunits/katalog_fix3.zip -d . && echo "extracted:" && find katalog -name "*.json" | wc -l

Extract zip to scratchpad


extracted:
    2513
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 -c "
import json
f='katalog/schutzzaun_basic/SBS1044SRA/BAP2051.json'
d=json.load(open(f))
print('top type:', d.get('_resourceClass'))
print('num handles:', len(d['handles']))
for h in d['handles']:
    sg=h.get('sceneGraph')
    comps=h.get('components',[])
    kinds=[c['_resourceClass'] for c in comps]
    print('---handle', h.get('id','')[:12], 'sel=', (h.get('properties',{}).get('custom-selection',{}) or {}).get('value'), 'glb=', bool(sg))
    from collections import Counter
    print('   comps:', dict(Counter(kinds)))
    for c in comps:
        if c['_resourceClass']=='component_pmi':
            print('   PMI', c.get('id'), 'pos', c['properties']['position']['value'])
        if c['_resourceClass']=='component_collider':
            print('   COL center', c.get('center'),'dim',c.get('dimension'))
        if c['_resourceClass']=='component_transform':
            print('   TF key',c.get('key'),c.get('parentKey'),'t',c.get('translation'))
"

Inspect full asset JSON schema


top type: sceneGraph_entity
num handles: 3
---handle X-1de14dc8-7 sel= self glb= False
   comps: {'component_transform': 1, 'component_pmi': 2}
   TF key root None t [0, 0, 0]
   PMI CS_BASE_PRE pos [0, 0, 0]
   PMI CS_BASE pos [0, 0, 0]
---handle X-96a06610-5 sel= parent glb= True
   comps: {'component_transform': 1, 'component_collider': 1}
   TF key None root t [0.0, 0.0, 0.0]
   COL center [50.0, 20.0, 79.0] dim [-100, -40, -158]
---handle X-507e38af-7 sel= parent glb= False
   comps: {'component_transform': 1, 'component_pmi': 2}
   TF key None root t [0.0, 0.0, 0.0]
   PMI CS_EIN_HINTEN pos [19.5, 20.0, 33.0]
   PMI CS_EIN_VORNE pos [19.5, 20.0, 33.0]
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 -c "
import json,glob
from collections import Counter
tags=Counter(); pmi_ids=Counter(); coll=0; pmi=0; files=0
usages=Counter()
for f in glob.glob('katalog/**/*.json',recursive=True):
    files+=1
    d=json.load(open(f))
    for h in d.get('handles',[]):
        for c in h.get('components',[]):
            rc=c['_resourceClass']
            tags[rc]+=1
            if rc=='component_pmi':
                pmi+=1; pmi_ids[c.get('id')]+=1
                usages[c['properties']['position'].get('usage')]+=1
            if rc=='component_collider': coll+=1
print('files',files)
print('comp types',dict(tags))
print('collider count',coll,'pmi count',pmi)
print('top PMI ids:', pmi_ids.most_common(15))
print('num distinct PMI ids', len(pmi_ids))
"

Survey component types and PMI ids across all files


Error: Exit code 1
Traceback (most recent call last):
  File "<string>", line 8, in <module>
  File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/json/__init__.py", line 293, in load
    return loads(fp.read(),
  File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/codecs.py", line 322, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc9 in position 16667: invalid continuation byte


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 -c "
import json,glob
from collections import Counter
def load(f):
    return json.load(open(f,encoding='utf-8',errors='replace'))
tags=Counter(); pmi_ids=Counter(); coll=0; pmi=0; files=0; bad=0
usages=Counter()
for f in glob.glob('katalog/**/*.json',recursive=True):
    files+=1
    try: d=load(f)
    except Exception as e: bad+=1; continue
    for h in d.get('handles',[]):
        for c in h.get('components',[]):
            rc=c['_resourceClass']; tags[rc]+=1
            if rc=='component_pmi':
                pmi+=1; pmi_ids[c.get('id')]+=1
                usages[c['properties']['position'].get('usage')]+=1
            if rc=='component_collider': coll+=1
print('files',files,'unparseable',bad)
print('comp types',dict(tags))
print('collider',coll,'pmi',pmi)
print('distinct PMI ids',len(pmi_ids))
print('top PMI ids:', pmi_ids.most_common(20))
"

Survey with encoding-safe load


ok -- kannst du analysieren, ob wir mit den Assets arbeiten können vor allem, ob die PMIs so vorliegen, dass sie überhaupt zu den assets passen und NICHT z.B. 20m weg sind?


files 2513 unparseable 234
comp types {'component_transform': 49543, 'component_pmi': 90021, 'component_collider': 28285}
collider 28285 pmi 90021
distinct PMI ids 142
top PMI ids: [('CS_EIN_QUER', 15825), ('CS_EIN_LAENGS', 12501), ('CS_EIN', 6216), ('CS_BASE_PRE', 2279), ('CS_BASE', 2279), ('CS_EIN_90_0_0', 2054), ('CS_EIN_STEHER', 1942), ('CS_EIN_STUMMEL', 1942), ('CS_EIN_0_180_0', 1822), ('CS_EIN_LINKS', 1433), ('CS_EIN_0_90_0', 1406), ('CS_EIN_RECHTS', 1242), ('ANBAUPUNKT1', 1128), ('ANBAUPUNKT2', 1128), ('CS_EIN_STEHER_OBEN', 941), ('CS_EIN_180_0_0', 889), ('CS_EIN_UNTEN', 888), ('CS_EIN_OBEN', 888), ('CS_EIN_90_180_0', 888), ('CS_EIN_QUER_270_180_90x', 888)]
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 -c "
import json,glob,os
from collections import Counter
sizes=[]; reasons=Counter(); samples=[]
for f in glob.glob('katalog/**/*.json',recursive=True):
    raw=open(f,'rb').read()
    try:
        json.loads(raw.decode('utf-8',errors='replace'))
    except Exception as e:
        reasons[type(e).__name__]+=1
        sizes.append(os.path.getsize(f))
        if len(samples)<5: samples.append((f,str(e)[:80]))
print('unparseable count', sum(reasons.values()), reasons)
print('size range', (min(sizes),max(sizes)) if sizes else None)
for s in samples: print(s)
"

Diagnose unparseable files


unparseable count 234 Counter({'JSONDecodeError': 234})
size range (0, 188795)
('katalog/schutzzaun_basic/schutzzaun_basic.json', 'Expecting value: line 2 column 1 (char 2)')
('katalog/schutzzaun_allround/schutzzaun_allround.json', 'Expecting value: line 2 column 1 (char 2)')
('katalog/rollenfoerderer/rollenfoerderer.json', 'Expecting value: line 2 column 1 (char 2)')
('katalog/rollenfoerderer/R5M/R5M_400x600/R5S_MOTORROLLE_RIEMENSEITE.json', 'Expecting value: line 1 column 1 (char 0)')
('katalog/rollenfoerderer/R5M/R5M_400x600/R5S_TRAGROLLE_ENDSEITE.json', 'Expecting value: line 1 column 1 (char 0)')
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && echo "=== empty (0 byte) files ===" && find katalog -name "*.json" -size 0 | wc -l && echo "=== a size-0 example ===" && find katalog -name "*.json" -size 0 | head -3 && echo "=== head of schutzzaun_basic.json ===" && head -c 400 katalog/schutzzaun_basic/schutzzaun_basic.json && echo "" && echo "=== line count & structure ===" && python3 -c "
raw=open('katalog/schutzzaun_basic/schutzzaun_basic.json',encoding='utf-8',errors='replace').read()
print('bytes',len(raw),'lines',raw.count(chr(10)))
print(repr(raw[:120]))
"

Check empty files and family json structure


=== empty (0 byte) files ===
     231
=== a size-0 example ===
katalog/rollenfoerderer/R5S/R5S_MOTORROLLE_ENDSEITE.json
katalog/rollenfoerderer/R5S/R5S_TRAGROLLE_RIEMENSEITE.json
katalog/rollenfoerderer/R5S/R5S_RIEMEN_160.json
=== head of schutzzaun_basic.json ===

### Artikel Definition
# Artikel SBE0174PNA
json set json_string ARTIKEL SBE0174PNA kataloggruppe "Element"
json set json_string ARTIKEL SBE0174PNA artikelgruppe "SBE1507PNA"
json set json_string ARTIKEL SBE0174PNA alternativ_produkte "saf_basic"
json set json_string ARTIKEL SBE0174PNA regelgruppe "basic"
json set json_string ARTIKEL SBE0174PNA status "freigegeben"
json set json_string AR
=== line count & structure ===
bytes 162033 lines 2520
'\n### Artikel Definition\n# Artikel SBE0174PNA\njson set json_string ARTIKEL SBE0174PNA kataloggruppe "Element"\njson set js'
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


231 leere Dateien und 3 Nicht-Entity-Dateien gefunden. Jetzt die eigentliche PMI-Analyse:


Write

import json, glob, math, os
from collections import Counter, defaultdict

FILES = sorted(glob.glob('katalog/**/*.json', recursive=True))

def load(f):
    raw = open(f, encoding='utf-8', errors='replace').read()
    return json.loads(raw)

def euler_mat(rx, ry, rz):
    # degrees, XYZ; return 3x3
    rx, ry, rz = map(math.radians, (rx, ry, rz))
    cx, sx = math.cos(rx), math.sin(rx)
    cy, sy = math.cos(ry), math.sin(ry)
    cz, sz = math.cos(rz), math.sin(rz)
    Rx = [[1,0,0],[0,cx,-sx],[0,sx,cx]]
    Ry = [[cy,0,sy],[0,1,0],[-sy,0,cy]]
    Rz = [[cz,-sz,0],[sz,cz,0],[0,0,1]]
    def mm(A,B): return [[sum(A[i][k]*B[k][j] for k in range(3)) for j in range(3)] for i in range(3)]
    return mm(mm(Rz,Ry),Rx)

def apply(M, t, v):
    return [sum(M[i][j]*v[j] for j in range(3)) + t[i] for i in range(3)]

def compose(pM, pt, lM, lt):
    # world = parent(local) ; returns (M, t)
    M = [[sum(pM[i][k]*lM[k][j] for k in range(3)) for j in range(3)] for i in range(3)]
    t = [sum(pM[i][j]*lt[j] for j in range(3)) + pt[i] for i in range(3)]
    return M, t

results = []
stats = Counter()
far_pmi_records = []

for f in FILES:
    if os.path.getsize(f) == 0:
        stats['empty'] += 1
        continue
    try:
        d = load(f)
    except Exception:
        stats['non_entity'] += 1
        continue
    if d.get('_resourceClass') != 'sceneGraph_entity':
        stats['non_entity'] += 1
        continue
    stats['entity'] += 1

    handles = d.get('handles', [])
    # Build key -> (local M, local t, parentKey) for each transform; a handle may host one transform
    # Collect transforms
    key_tf = {}   # key -> dict
    handle_tfs = []  # (handle, tf_local_M, tf_local_t, parentKey)
    for h in handles:
        tf = next((c for c in h.get('components',[]) if c['_resourceClass']=='component_transform'), None)
        if tf is None:
            handle_tfs.append((h, [[1,0,0],[0,1,0],[0,0,1]], [0,0,0], None))
            continue
        rot = tf.get('rotation',[0,0,0]) or [0,0,0]
        tr = tf.get('translation',[0,0,0]) or [0,0,0]
        M = euler_mat(*rot)
        handle_tfs.append((h, M, tr, tf.get('parentKey')))
        if tf.get('key'):
            key_tf[tf['key']] = (M, tr, tf.get('parentKey'))

    # world transform for a key, with memo
    memo = {}
    def world_of(key, depth=0):
        if key is None or depth>50: return ([[1,0,0],[0,1,0],[0,0,1]],[0,0,0])
        if key in memo: return memo[key]
        if key not in key_tf: return ([[1,0,0],[0,1,0],[0,0,1]],[0,0,0])
        M,t,pk = key_tf[key]
        pM,pt = world_of(pk, depth+1)
        w = compose(pM,pt,M,t)
        memo[key]=w
        return w

    # gather colliders (world AABB) and PMIs (world pos)
    aabb_min=[math.inf]*3; aabb_max=[-math.inf]*3; have_coll=False
    pmis=[]
    for (h,lM,lt,pk) in handle_tfs:
        pM,pt = world_of(pk)
        wM,wt = compose(pM,pt,lM,lt)   # this handle's world frame
        for c in h.get('components',[]):
            rc=c['_resourceClass']
            if rc=='component_collider':
                center=c.get('center') or [0,0,0]
                dim=c.get('dimension') or [0,0,0]
                half=[abs(x)/2 for x in dim]
                # 8 corners of local AABB, transform to world
                for sx in (-1,1):
                    for sy in (-1,1):
                        for sz in (-1,1):
                            corner=[center[0]+sx*half[0],center[1]+sy*half[1],center[2]+sz*half[2]]
                            w=apply(wM,wt,corner)
                            for i in range(3):
                                aabb_min[i]=min(aabb_min[i],w[i]); aabb_max[i]=max(aabb_max[i],w[i])
                have_coll=True
            if rc=='component_pmi':
                pos=c['properties']['position']['value']
                w=apply(wM,wt,pos)
                pmis.append((c.get('id'),w))

    if not have_coll:
        stats['entity_no_collider']+=1
        continue

    # distance of each PMI to AABB (0 if inside)
    maxdist=0.0; worst=None
    for pid,w in pmis:
        d3=[0,0,0]
        for i in range(3):
            if w[i]<aabb_min[i]: d3[i]=aabb_min[i]-w[i]
            elif w[i]>aabb_max[i]: d3[i]=w[i]-aabb_max[i]
        dist=math.sqrt(sum(x*x for x in d3))
        if dist>maxdist: maxdist=dist; worst=(pid,w)
    diag=math.sqrt(sum((aabb_max[i]-aabb_min[i])**2 for i in range(3)))
    results.append((f,maxdist,diag,worst,len(pmis)))

# Report
print('=== counts ===', dict(stats))
mds=[r[1] for r in results]
print('entities with colliders analyzed:', len(results))
print('PMI-to-geometry distance (mm): max=%.1f  median=%.1f  mean=%.1f'%(
    max(mds), sorted(mds)[len(mds)//2], sum(mds)/len(mds)))
# histogram buckets
buckets=[(0,'inside/<=1mm'),(1,'1-10mm'),(10,'10-100mm'),(100,'100-1000mm'),(1000,'1-5 m'),(5000,'>5 m')]
def bucket(x):
    if x<=1: return 'inside/<=1mm'
    if x<=10: return '1-10mm'
    if x<=100: return '10-100mm'
    if x<=1000: return '100-1000mm'
    if x<=5000: return '1-5 m'
    return '>5 m'
bc=Counter(bucket(r[1]) for r in results)
for _,name in buckets:
    print('  %-14s %d'%(name, bc.get(name,0)))
print()
print('=== worst 20 (PMI far from its geometry) ===')
for f,md,diag,worst,n in sorted(results,key=lambda r:-r[1])[:20]:
    print('%9.1fmm  diag=%7.1f  %s  [%s @ %s]'%(md,diag,f.replace('katalog/',''),worst[0],
        '['+','.join('%.0f'%x for x in worst[1])+']'))


File created successfully at: /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad/analyze.py (file state is current in your context — no need to Read it back)

import json, glob, math, os
from collections import Counter, defaultdict

FILES = sorted(glob.glob('katalog/**/*.json', recursive=True))

def load(f):
    raw = open(f, encoding='utf-8', errors='replace').read()
    return json.loads(raw)

def euler_mat(rx, ry, rz):
    # degrees, XYZ; return 3x3
    rx, ry, rz = map(math.radians, (rx, ry, rz))
    cx, sx = math.cos(rx), math.sin(rx)
    cy, sy = math.cos(ry), math.sin(ry)
    cz, sz = math.cos(rz), math.sin(rz)
    Rx = [[1,0,0],[0,cx,-sx],[0,sx,cx]]
    Ry = [[cy,0,sy],[0,1,0],[-sy,0,cy]]
    Rz = [[cz,-sz,0],[sz,cz,0],[0,0,1]]
    def mm(A,B): return [[sum(A[i][k]*B[k][j] for k in range(3)) for j in range(3)] for i in range(3)]
    return mm(mm(Rz,Ry),Rx)

def apply(M, t, v):
    return [sum(M[i][j]*v[j] for j in range(3)) + t[i] for i in range(3)]

def compose(pM, pt, lM, lt):
    # world = parent(local) ; returns (M, t)
    M = [[sum(pM[i][k]*lM[k][j] for k in range(3)) for j in range(3)] for i in range(3)]
    t = [sum(pM[i][j]*lt[j] for j in range(3)) + pt[i] for i in range(3)]
    return M, t

results = []
stats = Counter()
far_pmi_records = []

for f in FILES:
    if os.path.getsize(f) == 0:
        stats['empty'] += 1
        continue
    try:
        d = load(f)
    except Exception:
        stats['non_entity'] += 1
        continue
    if d.get('_resourceClass') != 'sceneGraph_entity':
        stats['non_entity'] += 1
        continue
    stats['entity'] += 1

    handles = d.get('handles', [])
    # Build key -> (local M, local t, parentKey) for each transform; a handle may host one transform
    # Collect transforms
    key_tf = {}   # key -> dict
    handle_tfs = []  # (handle, tf_local_M, tf_local_t, parentKey)
    for h in handles:
        tf = next((c for c in h.get('components',[]) if c['_resourceClass']=='component_transform'), None)
        if tf is None:
            handle_tfs.append((h, [[1,0,0],[0,1,0],[0,0,1]], [0,0,0], None))
            continue
        rot = tf.get('rotation',[0,0,0]) or [0,0,0]
        tr = tf.get('translation',[0,0,0]) or [0,0,0]
        M = euler_mat(*rot)
        handle_tfs.append((h, M, tr, tf.get('parentKey')))
        if tf.get('key'):
            key_tf[tf['key']] = (M, tr, tf.get('parentKey'))

    # world transform for a key, with memo
    memo = {}
    def world_of(key, depth=0):
        if key is None or depth>50: return ([[1,0,0],[0,1,0],[0,0,1]],[0,0,0])
        if key in memo: return memo[key]
        if key not in key_tf: return ([[1,0,0],[0,1,0],[0,0,1]],[0,0,0])
        M,t,pk = key_tf[key]
        pM,pt = world_of(pk, depth+1)
        w = compose(pM,pt,M,t)
        memo[key]=w
        return w

    # gather colliders (world AABB) and PMIs (world pos)
    aabb_min=[math.inf]*3; aabb_max=[-math.inf]*3; have_coll=False
    pmis=[]
    for (h,lM,lt,pk) in handle_tfs:
        pM,pt = world_of(pk)
        wM,wt = compose(pM,pt,lM,lt)   # this handle's world frame
        for c in h.get('components',[]):
            rc=c['_resourceClass']
            if rc=='component_collider':
                center=c.get('center') or [0,0,0]
                dim=c.get('dimension') or [0,0,0]
                half=[abs(x)/2 for x in dim]
                # 8 corners of local AABB, transform to world
                for sx in (-1,1):
                    for sy in (-1,1):
                        for sz in (-1,1):
                            corner=[center[0]+sx*half[0],center[1]+sy*half[1],center[2]+sz*half[2]]
                            w=apply(wM,wt,corner)
                            for i in range(3):
                                aabb_min[i]=min(aabb_min[i],w[i]); aabb_max[i]=max(aabb_max[i],w[i])
                have_coll=True
            if rc=='component_pmi':
                pos=c['properties']['position']['value']
                w=apply(wM,wt,pos)
                pmis.append((c.get('id'),w))

    if not have_coll:
        stats['entity_no_collider']+=1
        continue

    # distance of each PMI to AABB (0 if inside)
    maxdist=0.0; worst=None
    for pid,w in pmis:
        d3=[0,0,0]
        for i in range(3):
            if w[i]<aabb_min[i]: d3[i]=aabb_min[i]-w[i]
            elif w[i]>aabb_max[i]: d3[i]=w[i]-aabb_max[i]
        dist=math.sqrt(sum(x*x for x in d3))
        if dist>maxdist: maxdist=dist; worst=(pid,w)
    diag=math.sqrt(sum((aabb_max[i]-aabb_min[i])**2 for i in range(3)))
    results.append((f,maxdist,diag,worst,len(pmis)))

# Report
print('=== counts ===', dict(stats))
mds=[r[1] for r in results]
print('entities with colliders analyzed:', len(results))
print('PMI-to-geometry distance (mm): max=%.1f  median=%.1f  mean=%.1f'%(
    max(mds), sorted(mds)[len(mds)//2], sum(mds)/len(mds)))
# histogram buckets
buckets=[(0,'inside/<=1mm'),(1,'1-10mm'),(10,'10-100mm'),(100,'100-1000mm'),(1000,'1-5 m'),(5000,'>5 m')]
def bucket(x):
    if x<=1: return 'inside/<=1mm'
    if x<=10: return '1-10mm'
    if x<=100: return '10-100mm'
    if x<=1000: return '100-1000mm'
    if x<=5000: return '1-5 m'
    return '>5 m'
bc=Counter(bucket(r[1]) for r in results)
for _,name in buckets:
    print('  %-14s %d'%(name, bc.get(name,0)))
print()
print('=== worst 20 (PMI far from its geometry) ===')
for f,md,diag,worst,n in sorted(results,key=lambda r:-r[1])[:20]:
    print('%9.1fmm  diag=%7.1f  %s  [%s @ %s]'%(md,diag,f.replace('katalog/',''),worst[0],
        '['+','.join('%.0f'%x for x in worst[1])+']'))


Bash

cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 analyze.py

Run PMI-vs-geometry analysis


=== counts === {'entity': 2279, 'empty': 231, 'non_entity': 3}
entities with colliders analyzed: 2279
PMI-to-geometry distance (mm): max=21570.0  median=708.7  mean=6084.7
  inside/<=1mm   945
  1-10mm         10
  10-100mm       59
  100-1000mm     170
  1-5 m          352
  >5 m           743

=== worst 20 (PMI far from its geometry) ===
  21570.0mm  diag=   57.4  schutzzaun_allround/SAE0131SNN/clone_80974010.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_allround/SAF0872PLA/clone_26870909.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_allround/SAF0872PRA/clone_35714792.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_allround/SAF0872SLA/clone_29308382.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_allround/SAF0872SRA/clone_03055107.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0174PNA/clone_05105146.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0174SNA/clone_52072114.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0217PNA/clone_92459506.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0217SNA/clone_52594631.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0260PNA/clone_91725767.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0260SNA/clone_78072111.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0303PNA/clone_36958995.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0303SNA/clone_66057092.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0313SNA/clone_28916079.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0346PNA/clone_05231272.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0346SNA/clone_66815854.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0389PNA/clone_15309354.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0389SNA/clone_90612647.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0432PNA/clone_80774817.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0432SNA/clone_48078113.json  [CS_EIN_QUER @ [20,20,-21670]]
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 -c "
import json
f='katalog/schutzzaun_basic/SBE0174PNA/clone_05105146.json'
d=json.load(open(f,encoding='utf-8',errors='replace'))
print('handles',len(d['handles']))
for h in d['handles']:
    sg=h.get('sceneGraph')
    print('--- sel',(h.get('properties',{}).get('custom-selection',{}) or {}).get('value'),'glb',sg['path'].split('/')[-1] if sg else None,'tags',h.get('tags'))
    for c in h.get('components',[]):
        rc=c['_resourceClass']
        if rc=='component_transform': print('   TF key',c.get('key'),'parent',c.get('parentKey'),'t',c.get('translation'),'r',c.get('rotation'))
        elif rc=='component_pmi': print('   PMI',c.get('id'),'pos',c['properties']['position']['value'],'rot',c['properties'].get('rotation',{}).get('value'))
        elif rc=='component_collider': print('   COL center',c.get('center'),'dim',c.get('dimension'))
"

Inspect a worst-case clone file fully


handles 3
--- sel self glb None tags None
   TF key root parent None t [0, 0, 0] r [0, 0, 0]
   PMI CS_BASE_PRE pos [0, 0, 0] rot [90, 0, 0]
   PMI CS_BASE pos [0, 0, 0] rot [0, 0, 180]
--- sel parent glb clone_05105146.glb tags ['name-generic']
   TF key None parent root t [0.0, 0.0, 0.0] r [0.0, -0.0, 0.0]
   COL center [0.0, 0.0, -95.0] dim [-40, -40, -10]
--- sel parent glb None tags ['name-generic']
   TF key None parent root t [0.0, 0.0, 0.0] r [0.0, -0.0, 0.0]
   PMI CS_EIN_QUER pos [19.9999999998, 19.9999999998, -21670.0] rot [0.0, -90.0, 180.0]
   PMI CS_EIN_STEHER pos [19.9999999998, 19.9999999998, -21670.0] rot [90.0, 0.0, 90.0]
   PMI CS_EIN_STUMMEL pos [19.9999999998, 18.7499999998, -21670.0] rot [180.0, -0.0, 0.0]
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 -c "
import json,glob,os,math
from collections import Counter,defaultdict
far_by_kind=Counter(); far_by_pmi=Counter(); bigvals=Counter()
tot_far_files=set(); tot_files=0
# per-file worst using RAW pmi position magnitude vs collider extent (simpler, robust)
def load(f): return json.loads(open(f,encoding='utf-8',errors='replace').read())
for f in glob.glob('katalog/**/*.json',recursive=True):
    if os.path.getsize(f)==0: continue
    try: d=load(f)
    except: continue
    if d.get('_resourceClass')!='sceneGraph_entity': continue
    tot_files+=1
    # collect collider max |coord| and pmi max |coord|
    coll_ext=0.0; pmi_far=[]
    for h in d['handles']:
        for c in h.get('components',[]):
            if c['_resourceClass']=='component_collider':
                ce=c.get('center') or [0,0,0]; dm=c.get('dimension') or [0,0,0]
                for i in range(3):
                    coll_ext=max(coll_ext, abs(ce[i])+abs(dm[i])/2)
            if c['_resourceClass']=='component_pmi':
                v=c['properties']['position']['value']
                m=max(abs(x) for x in v)
                pmi_far.append((c.get('id'),m,v))
    for pid,m,v in pmi_far:
        if m>1000 and m>coll_ext*5+100:
            tot_far_files.add(f)
            base=os.path.basename(f)
            kind='clone' if base.startswith('clone_') else ('assembly' if base[:-5]==f.split('/')[-2] else 'part')
            far_by_kind[kind]+=1
            far_by_pmi[pid]+=1
            # round the dominant large coordinate
            dom=max(v,key=abs)
            bigvals[round(dom)]+=1
print('entity files',tot_files)
print('files with >=1 far PMI (>1m & >>geometry):',len(tot_far_files))
print('far-PMI occurrences by file kind:',dict(far_by_kind))
print('far-PMI occurrences by PMI id:',far_by_pmi.most_common(12))
print('most common large coord values:',bigvals.most_common(15))
"

Characterize the far-PMI problem


entity files 2279
files with >=1 far PMI (>1m & >>geometry): 1097
far-PMI occurrences by file kind: {'part': 4601, 'clone': 6783, 'assembly': 5912}
far-PMI occurrences by PMI id: [('CS_EIN_QUER', 2145), ('CS_EIN_STEHER', 1750), ('CS_EIN_STUMMEL', 1750), ('CS_EIN', 944), ('CS_EIN_90_0_0', 709), ('CS_EIN_90_180_0', 475), ('CS_EIN_QUER_90_0_90', 475), ('CS_EIN_QUER_270_180_90', 475), ('CS_EIN_0_90_0', 290), ('CS_EIN_R', 273), ('CS_EIN_L', 257), ('CS_EIN_0_180_0', 217)]
most common large coord values: [(1100, 2364), (1888, 1870), (-6100, 1716), (-20210, 1440), (-21670, 1065), (-5490, 720), (-1200, 690), (1926, 672), (-4100, 644), (-7300, 621), (-9540, 444), (-1080, 300), (-3690, 280), (-6570, 270), (1245, 225)]
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


Edit

print()
print('=== worst 20 (PMI far from its geometry) ===')
for f,md,diag,worst,n in sorted(results,key=lambda r:-r[1])[:20]:
    print('%9.1fmm  diag=%7.1f  %s  [%s @ %s]'%(md,diag,f.replace('katalog/',''),worst[0],
        '['+','.join('%.0f'%x for x in worst[1])+']'))

print()

def kind_of(f):
    base=os.path.basename(f)[:-5]
    parent=f.split('/')[-2]
    if base.startswith('clone_'): return 'clone'
    if base==parent: return 'assembly'
    return 'part'

# breakdown of >1m-outside-envelope by kind
fam=Counter(); famfar=Counter()
for f,md,diag,worst,n in results:
    k=kind_of(f); fam[k]+=1
    if md>1000: famfar[k]+=1
print('=== >1 m outside full geometry envelope, by file kind ===')
for k in ('part','clone','assembly'):
    print('  %-9s %4d / %4d  (%.0f%%)'%(k,famfar[k],fam[k],100*famfar[k]/max(fam[k],1)))
print()
print('=== worst PART-level cases (single part, clearest defects) ===')
parts=[r for r in results if kind_of(r[0])=='part']
for f,md,diag,worst,n in sorted(parts,key=lambda r:-r[1])[:12]:
    print('%9.1fmm  partsize=%6.1f  %s  [%s]'%(md,diag,f.replace('katalog/',''),worst[0]))
print()
print('=== worst 15 overall ===')
for f,md,diag,worst,n in sorted(results,key=lambda r:-r[1])[:15]:
    print('%9.1fmm  diag=%7.1f  %s  [%s @ %s]'%(md,diag,f.replace('katalog/',''),worst[0],
        '['+','.join('%.0f'%x for x in worst[1])+']'))


The file /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad/analyze.py has been updated successfully. (file state is current in your context — no need to Read it back)

print()

def kind_of(f):
    base=os.path.basename(f)[:-5]
    parent=f.split('/')[-2]
    if base.startswith('clone_'): return 'clone'
    if base==parent: return 'assembly'
    return 'part'

# breakdown of >1m-outside-envelope by kind
fam=Counter(); famfar=Counter()
for f,md,diag,worst,n in results:
    k=kind_of(f); fam[k]+=1
    if md>1000: famfar[k]+=1
print('=== >1 m outside full geometry envelope, by file kind ===')
for k in ('part','clone','assembly'):
    print('  %-9s %4d / %4d  (%.0f%%)'%(k,famfar[k],fam[k],100*famfar[k]/max(fam[k],1)))
print()
print('=== worst PART-level cases (single part, clearest defects) ===')
parts=[r for r in results if kind_of(r[0])=='part']
for f,md,diag,worst,n in sorted(parts,key=lambda r:-r[1])[:12]:
    print('%9.1fmm  partsize=%6.1f  %s  [%s]'%(md,diag,f.replace('katalog/',''),worst[0]))
print()
print('=== worst 15 overall ===')
for f,md,diag,worst,n in sorted(results,key=lambda r:-r[1])[:15]:
    print('%9.1fmm  diag=%7.1f  %s  [%s @ %s]'%(md,diag,f.replace('katalog/',''),worst[0],
        '['+','.join('%.0f'%x for x in worst[1])+']'))

import json, glob, math, os
from collections import Counter, defaultdict

FILES = sorted(glob.glob('katalog/**/*.json', recursive=True))

def load(f):
    raw = open(f, encoding='utf-8', errors='replace').read()
    return json.loads(raw)

def euler_mat(rx, ry, rz):
    # degrees, XYZ; return 3x3
    rx, ry, rz = map(math.radians, (rx, ry, rz))
    cx, sx = math.cos(rx), math.sin(rx)
    cy, sy = math.cos(ry), math.sin(ry)
    cz, sz = math.cos(rz), math.sin(rz)
    Rx = [[1,0,0],[0,cx,-sx],[0,sx,cx]]
    Ry = [[cy,0,sy],[0,1,0],[-sy,0,cy]]
    Rz = [[cz,-sz,0],[sz,cz,0],[0,0,1]]
    def mm(A,B): return [[sum(A[i][k]*B[k][j] for k in range(3)) for j in range(3)] for i in range(3)]
    return mm(mm(Rz,Ry),Rx)

def apply(M, t, v):
    return [sum(M[i][j]*v[j] for j in range(3)) + t[i] for i in range(3)]

def compose(pM, pt, lM, lt):
    # world = parent(local) ; returns (M, t)
    M = [[sum(pM[i][k]*lM[k][j] for k in range(3)) for j in range(3)] for i in range(3)]
    t = [sum(pM[i][j]*lt[j] for j in range(3)) + pt[i] for i in range(3)]
    return M, t

results = []
stats = Counter()
far_pmi_records = []

for f in FILES:
    if os.path.getsize(f) == 0:
        stats['empty'] += 1
        continue
    try:
        d = load(f)
    except Exception:
        stats['non_entity'] += 1
        continue
    if d.get('_resourceClass') != 'sceneGraph_entity':
        stats['non_entity'] += 1
        continue
    stats['entity'] += 1

    handles = d.get('handles', [])
    # Build key -> (local M, local t, parentKey) for each transform; a handle may host one transform
    # Collect transforms
    key_tf = {}   # key -> dict
    handle_tfs = []  # (handle, tf_local_M, tf_local_t, parentKey)
    for h in handles:
        tf = next((c for c in h.get('components',[]) if c['_resourceClass']=='component_transform'), None)
        if tf is None:
            handle_tfs.append((h, [[1,0,0],[0,1,0],[0,0,1]], [0,0,0], None))
            continue
        rot = tf.get('rotation',[0,0,0]) or [0,0,0]
        tr = tf.get('translation',[0,0,0]) or [0,0,0]
        M = euler_mat(*rot)
        handle_tfs.append((h, M, tr, tf.get('parentKey')))
        if tf.get('key'):
            key_tf[tf['key']] = (M, tr, tf.get('parentKey'))

    # world transform for a key, with memo
    memo = {}
    def world_of(key, depth=0):
        if key is None or depth>50: return ([[1,0,0],[0,1,0],[0,0,1]],[0,0,0])
        if key in memo: return memo[key]
        if key not in key_tf: return ([[1,0,0],[0,1,0],[0,0,1]],[0,0,0])
        M,t,pk = key_tf[key]
        pM,pt = world_of(pk, depth+1)
        w = compose(pM,pt,M,t)
        memo[key]=w
        return w

    # gather colliders (world AABB) and PMIs (world pos)
    aabb_min=[math.inf]*3; aabb_max=[-math.inf]*3; have_coll=False
    pmis=[]
    for (h,lM,lt,pk) in handle_tfs:
        pM,pt = world_of(pk)
        wM,wt = compose(pM,pt,lM,lt)   # this handle's world frame
        for c in h.get('components',[]):
            rc=c['_resourceClass']
            if rc=='component_collider':
                center=c.get('center') or [0,0,0]
                dim=c.get('dimension') or [0,0,0]
                half=[abs(x)/2 for x in dim]
                # 8 corners of local AABB, transform to world
                for sx in (-1,1):
                    for sy in (-1,1):
                        for sz in (-1,1):
                            corner=[center[0]+sx*half[0],center[1]+sy*half[1],center[2]+sz*half[2]]
                            w=apply(wM,wt,corner)
                            for i in range(3):
                                aabb_min[i]=min(aabb_min[i],w[i]); aabb_max[i]=max(aabb_max[i],w[i])
                have_coll=True
            if rc=='component_pmi':
                pos=c['properties']['position']['value']
                w=apply(wM,wt,pos)
                pmis.append((c.get('id'),w))

    if not have_coll:
        stats['entity_no_collider']+=1
        continue

    # distance of each PMI to AABB (0 if inside)
    maxdist=0.0; worst=None
    for pid,w in pmis:
        d3=[0,0,0]
        for i in range(3):
            if w[i]<aabb_min[i]: d3[i]=aabb_min[i]-w[i]
            elif w[i]>aabb_max[i]: d3[i]=w[i]-aabb_max[i]
        dist=math.sqrt(sum(x*x for x in d3))
        if dist>maxdist: maxdist=dist; worst=(pid,w)
    diag=math.sqrt(sum((aabb_max[i]-aabb_min[i])**2 for i in range(3)))
    results.append((f,maxdist,diag,worst,len(pmis)))

# Report
print('=== counts ===', dict(stats))
mds=[r[1] for r in results]
print('entities with colliders analyzed:', len(results))
print('PMI-to-geometry distance (mm): max=%.1f  median=%.1f  mean=%.1f'%(
    max(mds), sorted(mds)[len(mds)//2], sum(mds)/len(mds)))
# histogram buckets
buckets=[(0,'inside/<=1mm'),(1,'1-10mm'),(10,'10-100mm'),(100,'100-1000mm'),(1000,'1-5 m'),(5000,'>5 m')]
def bucket(x):
    if x<=1: return 'inside/<=1mm'
    if x<=10: return '1-10mm'
    if x<=100: return '10-100mm'
    if x<=1000: return '100-1000mm'
    if x<=5000: return '1-5 m'
    return '>5 m'
bc=Counter(bucket(r[1]) for r in results)
for _,name in buckets:
    print('  %-14s %d'%(name, bc.get(name,0)))
print()
print('=== worst 20 (PMI far from its geometry) ===')
for f,md,diag,worst,n in sorted(results,key=lambda r:-r[1])[:20]:
    print('%9.1fmm  diag=%7.1f  %s  [%s @ %s]'%(md,diag,f.replace('katalog/',''),worst[0],
        '['+','.join('%.0f'%x for x in worst[1])+']'))

-print('=== worst 20 (PMI far from its geometry) ===')

-for f,md,diag,worst,n in sorted(results,key=lambda r:-r[1])[:20]:

+    if base.startswith('clone_'): return 'clone'

+# breakdown of >1m-outside-envelope by kind

+print('=== >1 m outside full geometry envelope, by file kind ===')

+print('=== worst PART-level cases (single part, clearest defects) ===')

+parts=[r for r in results if kind_of(r[0])=='part']

+for f,md,diag,worst,n in sorted(parts,key=lambda r:-r[1])[:12]:

+    print('%9.1fmm  partsize=%6.1f  %s  [%s]'%(md,diag,f.replace('katalog/',''),worst[0]))

+for f,md,diag,worst,n in sorted(results,key=lambda r:-r[1])[:15]:


Bash

cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 analyze.py 2>&1 | sed -n '1,60p'

Re-run with kind breakdown


=== counts === {'entity': 2279, 'empty': 231, 'non_entity': 3}
entities with colliders analyzed: 2279
PMI-to-geometry distance (mm): max=21570.0  median=708.7  mean=6084.7
  inside/<=1mm   945
  1-10mm         10
  10-100mm       59
  100-1000mm     170
  1-5 m          352
  >5 m           743

=== >1 m outside full geometry envelope, by file kind ===
  part       159 /  814  (20%)
  clone      761 / 1290  (59%)
  assembly   175 /  175  (100%)

=== worst PART-level cases (single part, clearest defects) ===
  21552.8mm  partsize=21729.9  schutzzaun_allround/SAE0131SNN.json  [CS_EIN_QUER]
  21552.3mm  partsize=21745.4  schutzzaun_basic/SBE0174PNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21745.4  schutzzaun_basic/SBE0174SNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21769.5  schutzzaun_basic/SBE0217PNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21769.5  schutzzaun_basic/SBE0217SNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21802.0  schutzzaun_basic/SBE0260PNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21802.0  schutzzaun_basic/SBE0260SNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21843.0  schutzzaun_basic/SBE0303PNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21843.0  schutzzaun_basic/SBE0303SNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21892.3  schutzzaun_basic/SBE0346PNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21892.3  schutzzaun_basic/SBE0346SNA.json  [CS_EIN_QUER]
  21552.3mm  partsize=21949.9  schutzzaun_basic/SBE0389PNA.json  [CS_EIN_QUER]

=== worst 15 overall ===
  21570.0mm  diag=   57.4  schutzzaun_allround/SAE0131SNN/clone_80974010.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_allround/SAF0872PLA/clone_26870909.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_allround/SAF0872PRA/clone_35714792.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_allround/SAF0872SLA/clone_29308382.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_allround/SAF0872SRA/clone_03055107.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0174PNA/clone_05105146.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0174SNA/clone_52072114.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0217PNA/clone_92459506.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0217SNA/clone_52594631.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0260PNA/clone_91725767.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0260SNA/clone_78072111.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0303PNA/clone_36958995.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0303SNA/clone_66057092.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0313SNA/clone_28916079.json  [CS_EIN_QUER @ [20,20,-21670]]
  21570.0mm  diag=   57.4  schutzzaun_basic/SBE0346PNA/clone_05231272.json  [CS_EIN_QUER @ [20,20,-21670]]
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 -c "
import json,glob,os,math
from collections import defaultdict
def load(f): return json.loads(open(f,encoding='utf-8',errors='replace').read())
# per PMI-id family: distance from geometry envelope (part-local, ignore transforms=all ~0)
agg=defaultdict(lambda:[0,0,0.0])  # prefix -> [count, far_count(>1m), sum]
for f in glob.glob('katalog/**/*.json',recursive=True):
    if os.path.getsize(f)==0: continue
    try: d=load(f)
    except: continue
    if d.get('_resourceClass')!='sceneGraph_entity': continue
    mn=[math.inf]*3; mx=[-math.inf]*3; have=False; pmis=[]
    for h in d['handles']:
        for c in h.get('components',[]):
            if c['_resourceClass']=='component_collider':
                ce=c.get('center') or [0,0,0]; dm=c.get('dimension') or [0,0,0]
                for i in range(3):
                    mn[i]=min(mn[i],ce[i]-abs(dm[i])/2); mx[i]=max(mx[i],ce[i]+abs(dm[i])/2)
                have=True
            if c['_resourceClass']=='component_pmi':
                pmis.append((c.get('id'),c['properties']['position']['value']))
    if not have: continue
    for pid,v in pmis:
        d3=[max(mn[i]-v[i],0,v[i]-mx[i]) for i in range(3)]
        dist=math.sqrt(sum(x*x for x in d3))
        pref='CS_BASE' if pid.startswith('CS_BASE') else ('CS_EIN' if pid.startswith('CS_EIN') else ('ANBAU' if pid.startswith('ANBAU') else pid))
        a=agg[pref]; a[0]+=1; a[1]+= (1 if dist>1000 else 0); a[2]+=dist
for pref,(n,far,s) in sorted(agg.items(),key=lambda x:-x[1][0]):
    print('%-10s n=%6d  far>1m=%6d (%3.0f%%)  avg=%9.1fmm'%(pref,n,far,100*far/n,s/n))
"

Compare PMI families: CS_BASE vs CS_EIN distance to geometry


CS_EIN     n= 76408  far>1m= 14718 ( 19%)  avg=   1507.1mm
CS_BASE    n=  4558  far>1m=     0 (  0%)  avg=     21.2mm
ANBAU      n=  3328  far>1m=     0 (  0%)  avg=     21.9mm
CS_BASIS   n=   598  far>1m=     0 (  0%)  avg=      0.0mm
CS_BASIS_0_180_0 n=   236  far>1m=     0 (  0%)  avg=      0.0mm
CS_ENDE    n=   185  far>1m=   131 ( 71%)  avg=   3929.6mm
CS_BEMASSUNG n=   185  far>1m=   185 (100%)  avg=   4377.3mm
CS_BEMASSUNG_ANFANG n=   185  far>1m=   185 (100%)  avg=   4377.3mm
CS_BEMASSUNG_ENDE n=   185  far>1m=   131 ( 71%)  avg=   3929.6mm
CS_BEMASSUNG_ANFANG_UNT_GF n=   185  far>1m=   185 (100%)  avg=   4377.3mm
CS_BEMASSUNG_ENDE_UNT_GF n=   185  far>1m=   185 (100%)  avg=   4377.3mm
CS_OUT_R_0 n=   177  far>1m=   175 ( 99%)  avg=   1498.3mm
CS_OUT_L_0 n=   177  far>1m=   175 ( 99%)  avg=   1045.4mm
CS_OUT_R_90 n=   177  far>1m=   175 ( 99%)  avg=   1474.9mm
CS_OUT_L_90 n=   177  far>1m=   175 ( 99%)  avg=   1046.6mm
CS_OUT_R_270 n=   177  far>1m=   175 ( 99%)  avg=   1473.9mm
CS_OUT_L_270 n=   177  far>1m=   175 ( 99%)  avg=   1045.2mm
CS_SELECT_L_0 n=   177  far>1m=   175 ( 99%)  avg=   1054.8mm
CS_SELECT_R_0 n=   177  far>1m=   175 ( 99%)  avg=   1670.9mm
CS_SELECT_L_90 n=   177  far>1m=   175 ( 99%)  avg=   1083.1mm
CS_SELECT_R_90 n=   177  far>1m=   175 ( 99%)  avg=   1504.8mm
CS_SELECT_L_270 n=   177  far>1m=   175 ( 99%)  avg=   1063.2mm
CS_SELECT_R_270 n=   177  far>1m=   175 ( 99%)  avg=   1488.1mm
CS_OUT_R_90_B n=   153  far>1m=   151 ( 99%)  avg=   1427.2mm
CS_OUT_L_90_B n=   153  far>1m=   151 ( 99%)  avg=   1044.7mm
CS_OUT_R_270_B n=   153  far>1m=   151 ( 99%)  avg=   1427.2mm
CS_OUT_L_270_B n=   153  far>1m=   151 ( 99%)  avg=   1044.7mm
CS_BEMASSUNG_QUER n=   136  far>1m=     0 (  0%)  avg=      0.0mm
CS_BEMASSUNG_SF_NEIN n=   136  far>1m=    98 ( 72%)  avg=   1221.3mm
CS_BEMASSUNG_SF_JA n=   136  far>1m=    98 ( 72%)  avg=   1221.2mm
CS_OUT_L   n=   130  far>1m=   130 (100%)  avg=   1071.8mm
CS_OUT_R   n=   130  far>1m=   130 (100%)  avg=   1330.2mm
CS_BEMASSUNG_LAENGS n=   123  far>1m=     0 (  0%)  avg=      0.0mm
CS_ANBAU_PROFIL n=    36  far>1m=     0 (  0%)  avg=      0.0mm
CS_FRONT   n=    32  far>1m=     0 (  0%)  avg=      0.0mm
CS_RIGHT   n=    32  far>1m=     0 (  0%)  avg=      0.0mm
CS_BOTTOM  n=    32  far>1m=     0 (  0%)  avg=      0.0mm
CS_OUT_0   n=    14  far>1m=    14 (100%)  avg=   1060.0mm
CS_OUT_90  n=    14  far>1m=    14 (100%)  avg=   1060.0mm
CS_OUT_180 n=    14  far>1m=    14 (100%)  avg=   1060.0mm
CS_OUT_270 n=    14  far>1m=    14 (100%)  avg=   1060.0mm
CS_SELECT_0 n=    14  far>1m=    14 (100%)  avg=   1086.8mm
CS_SELECT_90 n=    14  far>1m=    14 (100%)  avg=   1061.3mm
CS_SELECT_180 n=    14  far>1m=    14 (100%)  avg=   1060.0mm
CS_SELECT_270 n=    14  far>1m=    14 (100%)  avg=   1073.0mm
CS_BASIS_0_270_0 n=    12  far>1m=     0 (  0%)  avg=      0.0mm
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 -c "
import json,glob,os,math
from collections import Counter
def load(f): return json.loads(open(f,encoding='utf-8',errors='replace').read())
small_defect=0; small_total=0; ein_far_in_small=Counter(); examples=[]
for f in glob.glob('katalog/**/*.json',recursive=True):
    if os.path.getsize(f)==0: continue
    try: d=load(f)
    except: continue
    if d.get('_resourceClass')!='sceneGraph_entity': continue
    mn=[math.inf]*3; mx=[-math.inf]*3; have=False; pmis=[]
    for h in d['handles']:
        for c in h.get('components',[]):
            if c['_resourceClass']=='component_collider':
                ce=c.get('center') or [0,0,0]; dm=c.get('dimension') or [0,0,0]
                for i in range(3):
                    mn[i]=min(mn[i],ce[i]-abs(dm[i])/2); mx[i]=max(mx[i],ce[i]+abs(dm[i])/2)
                have=True
            if c['_resourceClass']=='component_pmi':
                pmis.append((c.get('id'),c['properties']['position']['value']))
    if not have: continue
    diag=math.sqrt(sum((mx[i]-mn[i])**2 for i in range(3)))
    if diag>=300: continue   # only parts whose collider clearly wraps the whole small part
    small_total+=1
    worst=0; wid=None
    for pid,v in pmis:
        d3=[max(mn[i]-v[i],0,v[i]-mx[i]) for i in range(3)]
        dist=math.sqrt(sum(x*x for x in d3))
        if dist>worst: worst=dist; wid=pid
    if worst>1000:
        small_defect+=1; ein_far_in_small[wid]+=1
        if len(examples)<6: examples.append((round(worst),round(diag),wid,f.replace('katalog/','')))
print('small parts (collider diag <300mm):',small_total)
print('  of these, with a PMI >1m outside geometry:',small_defect,'(%.0f%%)'%(100*small_defect/small_total))
print('  which PMI is the offender:',ein_far_in_small.most_common(8))
for e in examples: print('   ',e)
"

Clean defect count on small parts where collider fully wraps geometry


small parts (collider diag <300mm): 1925
  of these, with a PMI >1m outside geometry: 966 (50%)
  which PMI is the offender: [('CS_EIN_QUER', 683), ('CS_EIN', 178), ('CS_EIN_ZRF', 53), ('CS_BEMASSUNG_SF_NEIN', 52)]
    (21570, 296, 'CS_EIN_QUER', 'schutzzaun_basic/SBE1163PNA.json')
    (21570, 296, 'CS_EIN_QUER', 'schutzzaun_basic/SBE0389SNA.json')
    (21570, 296, 'CS_EIN_QUER', 'schutzzaun_basic/SBE1034PNA.json')
    (21570, 296, 'CS_EIN_QUER', 'schutzzaun_basic/SBE1507PNA.json')
    (21570, 296, 'CS_EIN_QUER', 'schutzzaun_basic/SBE0432SNA.json')
    (21570, 296, 'CS_EIN_QUER', 'schutzzaun_basic/SBE0518PNA.json')
Shell cwd was reset to /Users/marcus.hinz/claude-projects/robotunits


## Ergebnis

Teilweise. Geometrie und die **Anker-Koordinatensysteme sitzen korrekt**, aber ein großer Teil der **Einbau-PMIs (`CS_EIN*`) hat genau das befürchtete Problem — bis zu 21,57 m neben dem Bauteil.**

**Datenbasis:** 2717 Zip-Einträge → 2513 JSON, davon 2279 gültige `sceneGraph_entity`, **231 leere (0-Byte) Dateien** (u.a. `R5S/R5M`-Rollen: Motorrolle, Tragrolle, Riemen), 3 Nicht-Entity-Dateien (`schutzzaun_basic.json` etc. sind Artikel-DSL, kein Szenegraph).

**Was passt (auf Geometrie, 0 % Ausreißer):**
- `CS_BASE` / `CS_BASE_PRE` (Ø 21 mm), `CS_BASIS`, `ANBAUPUNKT1/2`, `CS_ANBAU_PROFIL`, `CS_FRONT/RIGHT/BOTTOM`, `CS_BEMASSUNG_QUER/_LAENGS`. Collider (Bounding-Boxen) decken die Teile sauber ab.

**Was nicht passt — der „20 m"-Fall:**
- Von 2279 Entities liegt bei **1095 (48 %) mindestens ein PMI ≥ 1 m außerhalb der gesamten Geometrie-Hülle**, bei **743 sogar > 5 m**. Maximum **21 570 mm**.
- Sauberster Beweis, gegen den „Collider zu klein"-Einwand immun: von **1925 Klein-Teilen mit Collider-Diagonale < 300 mm sind 966 (50 %) betroffen** — ein 296-mm-Teil mit PMI bei z = −21 670 mm ist kein Toleranzthema.
- Betroffene PMI-Klassen: v.a. `CS_EIN_QUER` (19 % aller `CS_EIN` > 1 m weg), sowie durchgehend `CS_OUT_*`, `CS_SELECT_*`, `CS_BEMASSUNG_*` (~1–4,4 m; bei den Bemaßungs-/Outline-Punkten evtl. gewollt, unklar).

**Signatur — systematischer Export-Fehler, kein Rauschen:**
- Innerhalb einer Datei sind `CS_BASE` + Collider teil-lokal (korrekt), nur die `CS_EIN_*` liegen in einem **fremden Ursprung**.
- Die Fehlwerte häufen sich auf wenige feste Zahlen (−21670, −20210, −6100, −5490, 1888, 1100 mm) — das sind Zaunfeld-/Laufmeter-Maße. Die Punkte tragen offenbar **Assembly-/Weltkoordinaten statt Bauteilkoordinaten**.

Für Platzierung über `CS_BASE`/`ANBAUPUNKT` sind die Assets nutzbar; für Snap/Mating über `CS_EIN_*` sind sie es in ~der Hälfte der Fälle nicht.

Detail-Skript und Extrakt liegen unter `scratchpad/analyze.py`.


Welcxhe CS_EIN_ gibt es?


cd /private/tmp/claude-502/-Users-marcus-hinz-claude-projects-robotunits/b762fc5c-841a-4069-a852-f92cf1c68110/scratchpad && python3 -c "
import json,glob,os,math
from collections import Counter
def load(f): return json.loads(open(f,encoding='utf-8',errors='replace').read())
cnt=Counter(); far=Counter()
for f in glob.glob('katalog/**/*.json',recursive=True):
    if os.path.getsize(f)==0: continue
    try: d=load(f)
    except: continue
    if d.get('_resourceClass')!='sceneGraph_entity': continue
    mn=[math.inf]*3; mx=[-math.inf]*3; have=False; pmis=[]
    for h in d['handles']:
        for c in h.get('components',[]):
            if c['_resourceClass']=='component_collider':
                ce=c.get('center') or [0,0,0]; dm=c.get('dimension') or [0,0,0]
                for i in range(3): mn[i]=min(mn[i],ce[i]-abs(dm[i])/2); mx[i]=max(mx[i],ce[i]+abs(dm[i])/2)
                have=True
            if c['_resourceClass']=='component_pmi': pmis.append((c.get('id'),c['properties']['position']['value']))
    for pid,v in pmis:
        if not pid or not pid.startswith('CS_EIN'): continue
        cnt[pid]+=1
        if have:
            d3=[max(mn[i]-v[i],0,v[i]-mx[i]) for i in range(3)]
            if math.sqrt(sum(x*x for x in d3))>1000: far[pid]+=1
print('distinct CS_EIN ids:',len(cnt))
print('%-32s %8s %8s %6s'%('id','count','far>1m','%'))
for pid,n in cnt.most_common():
    fp=far.get(pid,0)
    print('%-32s %8d %8d %5.0f%%'%(pid,n,fp,100*fp/n))
"

Enumerate all CS_EIN variants with counts and far-fraction