refactor: rebrand project to HartOMat

This commit is contained in:
2026-04-06 12:45:47 +02:00
parent fa7093307a
commit b795f0e6d6
95 changed files with 608 additions and 497 deletions
+16 -16
View File
@@ -1,6 +1,6 @@
# Schaeffler Automat MCP Server
# HartOMat MCP Server
An MCP (Model Context Protocol) server that gives Claude Code direct access to the Schaeffler Automat render pipeline, product library, and database.
An MCP (Model Context Protocol) server that gives Claude Code direct access to the HartOMat render pipeline, product library, and database.
## Quick Start
@@ -17,9 +17,9 @@ The project includes `.mcp.json` which automatically registers the MCP server wh
### Setup (manual)
```bash
claude mcp add schaeffler -- uv run \
claude mcp add hartomat -- uv run \
--with "mcp[cli]" --with psycopg2-binary --with httpx \
python schaeffler_mcp_server.py
python hartomat_mcp_server.py
```
### Verify
@@ -29,7 +29,7 @@ Inside Claude Code, run:
/mcp
```
You should see `schaeffler` listed with status "connected".
You should see `hartomat` listed with status "connected".
## Available Tools
@@ -61,8 +61,8 @@ You should see `schaeffler` listed with status "connected".
| Resource URI | Description |
|---|---|
| `schaeffler://schema` | Full database schema (tables + columns) |
| `schaeffler://output-types` | All configured output types |
| `hartomat://schema` | Full database schema (tables + columns) |
| `hartomat://output-types` | All configured output types |
## Usage Examples
@@ -98,9 +98,9 @@ The server connects to your local Docker services by default. Override via envir
| Variable | Default | Description |
|---|---|---|
| `DATABASE_URL` | `postgresql://schaeffler:schaeffler@localhost:5432/schaeffler` | PostgreSQL connection string |
| `DATABASE_URL` | `postgresql://hartomat:hartomat@localhost:5432/hartomat` | PostgreSQL connection string |
| `API_URL` | `http://localhost:8888` | Backend API base URL |
| `API_EMAIL` | `admin@schaeffler.com` | API login email |
| `API_EMAIL` | `admin@hartomat.com` | API login email |
| `API_PASSWORD` | `Admin1234!` | API login password |
### Custom configuration
@@ -108,11 +108,11 @@ The server connects to your local Docker services by default. Override via envir
Edit `.mcp.json` in the project root to change defaults, or use `claude mcp add` with `--env` flags:
```bash
claude mcp add schaeffler \
claude mcp add hartomat \
--env DATABASE_URL=postgresql://user:pass@host/db \
--env API_URL=https://staging.example.com \
-- uv run --with "mcp[cli]" --with psycopg2-binary --with httpx \
python schaeffler_mcp_server.py
python hartomat_mcp_server.py
```
## Security Notes
@@ -127,7 +127,7 @@ claude mcp add schaeffler \
### Server not connecting
1. Check Docker services are running: `docker compose ps`
2. Check PostgreSQL is accessible: `psql postgresql://schaeffler:schaeffler@localhost:5432/schaeffler -c "SELECT 1"`
2. Check PostgreSQL is accessible: `psql postgresql://hartomat:hartomat@localhost:5432/hartomat -c "SELECT 1"`
3. Check backend API is up: `curl http://localhost:8888/api/auth/login`
### Dependencies missing
@@ -141,14 +141,14 @@ uv pip install "mcp[cli]" psycopg2-binary httpx
```bash
# Run manually to see errors
uv run --with "mcp[cli]" --with psycopg2-binary --with httpx \
python schaeffler_mcp_server.py
python hartomat_mcp_server.py
```
### Reset MCP connection
```bash
claude mcp remove schaeffler
claude mcp add schaeffler -- uv run \
claude mcp remove hartomat
claude mcp add hartomat -- uv run \
--with "mcp[cli]" --with psycopg2-binary --with httpx \
python schaeffler_mcp_server.py
python hartomat_mcp_server.py
```
+17 -17
View File
@@ -12,7 +12,7 @@
- [ ] `blender_render.py` decomposition is still pending; current file remains monolithic
- [ ] Legacy STL-era cleanup is still pending (`stl_quality`, STL endpoints, orphaned directories)
- [x] Decision: USD authoring library → **`usd-core` (pip)** — provides `pxr` module, no GPU tools needed, pip-installable in render-worker
- [x] Decision: seam/sharp payload encoding → **index-space primvars** (`primvars:schaeffler:seamEdgeVertexPairs`, `primvars:schaeffler:sharpEdgeVertexPairs`) — survives transforms, no KD-tree needed
- [x] Decision: seam/sharp payload encoding → **index-space primvars** (`primvars:hartomat:seamEdgeVertexPairs`, `primvars:hartomat:sharpEdgeVertexPairs`) — survives transforms, no KD-tree needed
- [x] Decision: preview GLB derivation → **co-author from same tessellation pass** during migration (avoid round-trip loss from USD→GLB export)
- [x] Decision: single-file vs override layers → **Option B: canonical geometry layer + material override layer, flattened via `UsdUtils.FlattenLayerStack()` for delivery** — preserves hierarchy AND allows instancing later (`FlattenLayerStack` keeps `instanceable` prims; `UsdStage.Flatten` would expand them). Note: Phase 1 uses no instancing (matching current GLB pipeline), but the delivery path is already instancing-safe.
@@ -63,14 +63,14 @@ Add after the `gmsh` line. `usd-core` is the Pixar-maintained pip distribution o
| Attribute | Value source |
|---|---|
| `schaeffler:partKey` | `generate_part_key(xcaf_label_path)` |
| `schaeffler:sourceName` | XCAF `TDataStd_Name` attribute |
| `schaeffler:sourceColor` | XCAF embedded color (hex string) |
| `schaeffler:rawMaterialName` | from `CadFile.part_materials` if available |
| `schaeffler:tessellation:linearDeflectionMm` | CLI arg value |
| `schaeffler:tessellation:angularDeflectionRad` | CLI arg value |
| `primvars:schaeffler:seamEdgeVertexPairs` | OCC B-rep seam edges (index pairs in mesh-local space) |
| `primvars:schaeffler:sharpEdgeVertexPairs` | sharp edges from `_extract_sharp_edge_pairs()` |
| `hartomat:partKey` | `generate_part_key(xcaf_label_path)` |
| `hartomat:sourceName` | XCAF `TDataStd_Name` attribute |
| `hartomat:sourceColor` | XCAF embedded color (hex string) |
| `hartomat:rawMaterialName` | from `CadFile.part_materials` if available |
| `hartomat:tessellation:linearDeflectionMm` | CLI arg value |
| `hartomat:tessellation:angularDeflectionRad` | CLI arg value |
| `primvars:hartomat:seamEdgeVertexPairs` | OCC B-rep seam edges (index pairs in mesh-local space) |
| `primvars:hartomat:sharpEdgeVertexPairs` | sharp edges from `_extract_sharp_edge_pairs()` |
**CLI interface:**
@@ -86,7 +86,7 @@ python3 export_step_to_usd.py \
**Acceptance gate:** `python3 export_step_to_usd.py --step_path 81113-l_cut.stp --output_path /tmp/test.usd`
- File exists, parseable
- 25 part prims with `schaeffler:partKey` attribute
- 25 part prims with `hartomat:partKey` attribute
- Part count matches `export_step_to_gltf.py` output for same file
### Task 1.2 — `usd_master` MediaAsset type
@@ -150,7 +150,7 @@ def build_scene_manifest(cad_file: CadFile, usd_asset: MediaAsset) -> dict:
"part_key": "ring_outer",
"source_name": "RingOuter_AF0",
"prim_path": "/Root/Assembly/Bearing/RingOuter",
"effective_material": "SCHAEFFLER_010102_...",
"effective_material": "HARTOMAT_010102_...",
"assignment_provenance": "manual|auto|default",
"is_unassigned": false
}
@@ -198,7 +198,7 @@ New endpoint returning `SceneManifest`. Calls `build_scene_manifest()` — reads
**File:** `backend/app/api/routers/cad.py`
Accept `{ "part_key": "ring_outer", "material": "SCHAEFFLER_010102_..." }` body (or bulk map). Write to `manual_material_overrides` column (not the old `part_materials` column).
Accept `{ "part_key": "ring_outer", "material": "HARTOMAT_010102_..." }` body (or bulk map). Write to `manual_material_overrides` column (not the old `part_materials` column).
**Acceptance gate:** PUT with `partKey` → subsequent GET `/scene-manifest` shows that part's `assignment_provenance: "manual"`.
@@ -218,10 +218,10 @@ After tessellation (OCC or GMSH), for each mesh face:
Write to USD mesh prim:
```python
mesh_prim.GetPrimvar("schaeffler:seamEdgeVertexPairs").Set(
mesh_prim.GetPrimvar("hartomat:seamEdgeVertexPairs").Set(
Vt.Vec2iArray(seam_pairs), # [(vi0, vi1), ...]
)
mesh_prim.GetPrimvar("schaeffler:sharpEdgeVertexPairs").Set(
mesh_prim.GetPrimvar("hartomat:sharpEdgeVertexPairs").Set(
Vt.Vec2iArray(sharp_pairs),
)
```
@@ -243,8 +243,8 @@ def import_usd_and_restore_topology(usd_path: str) -> list:
if obj.type != 'MESH':
continue
# Read custom attributes set by USD importer
seam_pairs = obj.get("schaeffler_seamEdgeVertexPairs") or []
sharp_pairs = obj.get("schaeffler_sharpEdgeVertexPairs") or []
seam_pairs = obj.get("hartomat_seamEdgeVertexPairs") or []
sharp_pairs = obj.get("hartomat_sharpEdgeVertexPairs") or []
_mark_seams_from_index_pairs(obj, seam_pairs)
_mark_sharp_from_index_pairs(obj, sharp_pairs)
...
@@ -262,7 +262,7 @@ def import_usd_and_restore_topology(usd_path: str) -> list:
Add `--usd_path` argument. When provided:
1. Call `import_usd.py` instead of `export_gltf.py` GLB import
2. Read `schaeffler:partKey` and `schaeffler:canonicalMaterialName` per mesh object after import
2. Read `hartomat:partKey` and `hartomat:canonicalMaterialName` per mesh object after import
3. Apply materials by `partKey → material library name` lookup instead of object-name heuristics
**Migration:** Keep `--glb_path` working in parallel; switch production task to prefer `--usd_path` when `usd_master` asset exists.
+41 -41
View File
@@ -35,12 +35,12 @@ That split introduces avoidable duplication, fragility, and impedance mismatches
The code confirms this architecture:
- Geometry export task: [backend/app/domains/pipeline/tasks/export_glb.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/domains/pipeline/tasks/export_glb.py#L16)
- Production export task: [backend/app/domains/pipeline/tasks/export_glb.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/domains/pipeline/tasks/export_glb.py#L176)
- Blender production export script: [render-worker/scripts/export_gltf.py](/home/hartmut/Documents/Copilot/schaefflerautomat/render-worker/scripts/export_gltf.py#L106)
- OCC GLB exporter with XCAF name/color preservation and sharp-edge extras: [render-worker/scripts/export_step_to_gltf.py](/home/hartmut/Documents/Copilot/schaefflerautomat/render-worker/scripts/export_step_to_gltf.py#L301)
- Media asset model: [backend/app/domains/media/models.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/domains/media/models.py#L11)
- Frontend viewer contract: [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/schaefflerautomat/frontend/src/components/cad/ThreeDViewer.tsx#L40)
- Geometry export task: [backend/app/domains/pipeline/tasks/export_glb.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/domains/pipeline/tasks/export_glb.py#L16)
- Production export task: [backend/app/domains/pipeline/tasks/export_glb.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/domains/pipeline/tasks/export_glb.py#L176)
- Blender production export script: [render-worker/scripts/export_gltf.py](/home/hartmut/Documents/Copilot/hartomat/render-worker/scripts/export_gltf.py#L106)
- OCC GLB exporter with XCAF name/color preservation and sharp-edge extras: [render-worker/scripts/export_step_to_gltf.py](/home/hartmut/Documents/Copilot/hartomat/render-worker/scripts/export_step_to_gltf.py#L301)
- Media asset model: [backend/app/domains/media/models.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/domains/media/models.py#L11)
- Frontend viewer contract: [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/hartomat/frontend/src/components/cad/ThreeDViewer.tsx#L40)
## Goals
@@ -95,18 +95,18 @@ However, this data is not represented in a durable scene data model. Some of it
Material mapping already exists conceptually in the domain model:
- product-level `cad_part_materials`
- canonical material/alias resolution in [backend/app/domains/materials/service.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/domains/materials/service.py#L32)
- canonical material/alias resolution in [backend/app/domains/materials/service.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/domains/materials/service.py#L32)
The weak point is the last mile: materials are currently assigned in Blender by matching imported object names from a GLB round-trip in [render-worker/scripts/export_gltf.py](/home/hartmut/Documents/Copilot/schaefflerautomat/render-worker/scripts/export_gltf.py#L192).
The weak point is the last mile: materials are currently assigned in Blender by matching imported object names from a GLB round-trip in [render-worker/scripts/export_gltf.py](/home/hartmut/Documents/Copilot/hartomat/render-worker/scripts/export_gltf.py#L192).
### Admin and Settings Surface
The admin/backend model still mirrors the dual-GLB architecture:
- separate preview and production tessellation settings in [backend/app/api/routers/admin.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/api/routers/admin.py#L24)
- a bulk action specifically for missing geometry GLBs in [backend/app/api/routers/admin.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/api/routers/admin.py#L536)
- an admin UI that exposes preview-vs-production GLB tessellation controls in [frontend/src/pages/Admin.tsx](/home/hartmut/Documents/Copilot/schaefflerautomat/frontend/src/pages/Admin.tsx#L1400)
- product detail logic that queries both `gltf_geometry` and `gltf_production` assets in [frontend/src/pages/ProductDetail.tsx](/home/hartmut/Documents/Copilot/schaefflerautomat/frontend/src/pages/ProductDetail.tsx#L182)
- separate preview and production tessellation settings in [backend/app/api/routers/admin.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/api/routers/admin.py#L24)
- a bulk action specifically for missing geometry GLBs in [backend/app/api/routers/admin.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/api/routers/admin.py#L536)
- an admin UI that exposes preview-vs-production GLB tessellation controls in [frontend/src/pages/Admin.tsx](/home/hartmut/Documents/Copilot/hartomat/frontend/src/pages/Admin.tsx#L1400)
- product detail logic that queries both `gltf_geometry` and `gltf_production` assets in [frontend/src/pages/ProductDetail.tsx](/home/hartmut/Documents/Copilot/hartomat/frontend/src/pages/ProductDetail.tsx#L182)
That duplication is operationally expensive and should be reduced as part of the refactor, not carried forward under new names.
@@ -216,17 +216,17 @@ Object names imported into Blender must no longer be the primary identity mechan
Each part prim should carry:
- `schaeffler:partKey`
- `schaeffler:sourceName`
- `schaeffler:sourceAssemblyPath`
- `schaeffler:sourceColor`
- `schaeffler:rawMaterialName`
- `schaeffler:canonicalMaterialName`
- `schaeffler:tessellation:linearDeflectionMm`
- `schaeffler:tessellation:angularDeflectionRad`
- `schaeffler:cadFileId`
- `schaeffler:productId` when available
- `schaeffler:mesh:topologyHash`
- `hartomat:partKey`
- `hartomat:sourceName`
- `hartomat:sourceAssemblyPath`
- `hartomat:sourceColor`
- `hartomat:rawMaterialName`
- `hartomat:canonicalMaterialName`
- `hartomat:tessellation:linearDeflectionMm`
- `hartomat:tessellation:angularDeflectionRad`
- `hartomat:cadFileId`
- `hartomat:productId` when available
- `hartomat:mesh:topologyHash`
Each mesh prim should carry:
@@ -241,10 +241,10 @@ Each mesh prim should carry:
USD does not have a first-class standard seam concept for Blender UV editing, so this RFC proposes storing authored topology support data as custom primvars or custom attributes on the mesh prim:
- `primvars:schaeffler:seamEdgeVertexPairs`
- `primvars:schaeffler:sharpEdgeVertexPairs`
- `primvars:schaeffler:faceBatchIds`
- `primvars:schaeffler:sourceUv`
- `primvars:hartomat:seamEdgeVertexPairs`
- `primvars:hartomat:sharpEdgeVertexPairs`
- `primvars:hartomat:faceBatchIds`
- `primvars:hartomat:sourceUv`
The exact encoding can evolve, but the initial implementation should optimize for deterministic Blender reconstruction rather than elegance.
@@ -321,10 +321,10 @@ The current viewer supports several behaviors that must not regress:
Those behaviors currently operate on GLB mesh objects in:
- [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/schaefflerautomat/frontend/src/components/cad/ThreeDViewer.tsx#L488)
- [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/schaefflerautomat/frontend/src/components/cad/ThreeDViewer.tsx#L553)
- [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/schaefflerautomat/frontend/src/components/cad/ThreeDViewer.tsx#L675)
- [frontend/src/components/cad/MaterialPanel.tsx](/home/hartmut/Documents/Copilot/schaefflerautomat/frontend/src/components/cad/MaterialPanel.tsx#L123)
- [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/hartomat/frontend/src/components/cad/ThreeDViewer.tsx#L488)
- [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/hartomat/frontend/src/components/cad/ThreeDViewer.tsx#L553)
- [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/hartomat/frontend/src/components/cad/ThreeDViewer.tsx#L675)
- [frontend/src/components/cad/MaterialPanel.tsx](/home/hartmut/Documents/Copilot/hartomat/frontend/src/components/cad/MaterialPanel.tsx#L123)
The USD refactor must preserve these capabilities. The replacement is not "browser renders USD directly". The replacement is:
@@ -393,10 +393,10 @@ That makes missing assignments visible instead of silently failing.
The current backend already has the right conceptual split, but the naming is misleading:
- [backend/app/domains/products/models.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/domains/products/models.py#L62) `Product.cad_part_materials` behaves like imported or product-authored source material rows
- [backend/app/domains/products/models.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/domains/products/models.py#L34) `CadFile.part_materials` behaves like viewer-side manual assignments or overrides
- [backend/app/api/routers/cad.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/api/routers/cad.py#L395) still presents those overrides as a part-name keyed map
- [backend/app/api/routers/products.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/api/routers/products.py#L433) performs Excel reconciliation directly into the product-side list
- [backend/app/domains/products/models.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/domains/products/models.py#L62) `Product.cad_part_materials` behaves like imported or product-authored source material rows
- [backend/app/domains/products/models.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/domains/products/models.py#L34) `CadFile.part_materials` behaves like viewer-side manual assignments or overrides
- [backend/app/api/routers/cad.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/api/routers/cad.py#L395) still presents those overrides as a part-name keyed map
- [backend/app/api/routers/products.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/api/routers/products.py#L433) performs Excel reconciliation directly into the product-side list
The USD refactor should formalize this into three explicit layers:
@@ -567,7 +567,7 @@ The existing `export_step_to_gltf.py` can remain temporarily for migration and f
## 2. Media Asset Model
Extend [backend/app/domains/media/models.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/domains/media/models.py#L11) with at least:
Extend [backend/app/domains/media/models.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/domains/media/models.py#L11) with at least:
- `usd_master`
@@ -580,7 +580,7 @@ The important change is that `usd_master` becomes the canonical CAD scene artifa
## 3. Pipeline Tasks
Replace the current dual-export mental model in [backend/app/domains/pipeline/tasks/export_glb.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/domains/pipeline/tasks/export_glb.py#L16) with:
Replace the current dual-export mental model in [backend/app/domains/pipeline/tasks/export_glb.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/domains/pipeline/tasks/export_glb.py#L16) with:
- `generate_usd_master_task`
- optional `generate_preview_glb_task`
@@ -590,7 +590,7 @@ The production GLB task should be retired once Blender can render from USD and b
## 4. Render Service
The render service in [backend/app/services/render_blender.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/services/render_blender.py#L18) currently converts `STEP -> GLB -> Blender render`.
The render service in [backend/app/services/render_blender.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/services/render_blender.py#L18) currently converts `STEP -> GLB -> Blender render`.
Target flow:
@@ -608,7 +608,7 @@ This removes the need for a production GLB as an intermediate render artifact.
## 5. Frontend
The current viewer API in [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/schaefflerautomat/frontend/src/components/cad/ThreeDViewer.tsx#L40) assumes:
The current viewer API in [frontend/src/components/cad/ThreeDViewer.tsx](/home/hartmut/Documents/Copilot/hartomat/frontend/src/components/cad/ThreeDViewer.tsx#L40) assumes:
- one geometry GLB
- one production GLB
@@ -633,7 +633,7 @@ The frontend should stop encoding the architectural distinction between geometry
## 6. Viewer Assignment API
The current viewer override endpoint in [backend/app/api/routers/cad.py](/home/hartmut/Documents/Copilot/schaefflerautomat/backend/app/api/routers/cad.py#L395) should evolve from a raw part-name keyed map into a canonical scene-assignment endpoint.
The current viewer override endpoint in [backend/app/api/routers/cad.py](/home/hartmut/Documents/Copilot/hartomat/backend/app/api/routers/cad.py#L395) should evolve from a raw part-name keyed map into a canonical scene-assignment endpoint.
Target behavior:
@@ -720,7 +720,7 @@ Example shape:
"part_key": "ring_outer",
"source_name": "RingOuter_AF0",
"prim_path": "/Root/Assembly/Bearing/RingOuter",
"effective_material": "SCHAEFFLER_010102_Steel-Polished",
"effective_material": "HARTOMAT_010102_Steel-Polished",
"assignment_provenance": "manual",
"is_unassigned": false
}