From 2a043de6faf272f3907f34cb2ea719c085fa041e Mon Sep 17 00:00:00 2001
From: Bernhard Reiter
Date: Fri, 4 Sep 2026 09:22:14 +0200
Subject: [PATCH 1/5] =?UTF-8?q?feat(werkstoff):=20der=20Auftrag=20bestimmt?=
=?UTF-8?q?=20die=20Bretter=20=E2=80=94=20Bundle=20aus=20dem=20BuildPlan,?=
=?UTF-8?q?=20Loader=20pr=C3=BCft=20in=20beide=20Richtungen?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bisher stand die Teileliste im Code (zwei Katalog-Beispiele) und das Bundle
kannte Bretter derselben Sorte. Jetzt kommt das Bundle aus dem BuildPlan-
Modus von cody-cad (#66, Demo-Plan = Beispielmöbel ohne Rückwand) und trägt
auftrag.json: moebel_id, buildplan_sha256, revision, teile[{schluessel,
werkstueck_id}] — dasselbe Brett wie in der Zeichnung.
- lib/werkstoff/auftrag.ts: ladeAuftrag (strikt, mit Grund: fehlende Felder,
doppelte Schlüssel/Karten, revision < 1, Hash kein Hex64), glbKnoten
(liest aus einem GLB nur Wurzel + Kindernamen — ohne WebGL prüfbar),
pruefeSzene (Wurzel = moebel_id; jeder Knoten hat eine Karte UND jede
Karte einen Knoten; ALLE Abweichungen, nicht nur die erste), karteFuer.
Vertrag mit Cody #2: GLB-Knotennamen = schluessel, Wurzel = moebel_id.
- Page: Teile aus auftrag.teile statt aus einer Konstante; Kopf zeigt
Möbel, Revision, Plan-Hash; fehlt der Auftrag → »Kein Auftrag im
Bundle« und KEIN Brett (kein Katalog-Beispiel als Ersatz). Karte nennt
die Werkstück-ID des Plan-Teils.
- Fixture demo-mini.glb (280 Bytes: Wurzel + 4 benannte Kinder, keine
Meshes) — genug für den Vertrag; das echte Modell kommt in die Szene.
- Bundle-Tausch: Karten/Manifeste heißen nach werkstueck_id des Plans;
werkstoffe/werkstuecke/ bleibt das Datenpaket des Katalogstands.
Red-First: 14 neue Tests (11 Lib, 3 Panel), vorher rot. 42/42 in den vier
Werkstoff-Dateien, Suite 403/403, tsc + eslint grün. Saboteure einzeln,
zurückgebaut: Richtung Karte→Knoten aus → 2 rot · Wurzelvergleich aus → 2
rot · Doppelschlüssel erlaubt → 1 rot · 404 → leerer Auftrag → 2 rot
(Lib UND Panel).
Co-Authored-By: Claude Fable 5.1
---
apps/tischler/app/[locale]/werkstoff/page.tsx | 41 +-
.../app/[locale]/werkstoff/panel.test.tsx | 40 +
apps/tischler/lib/werkstoff/auftrag.test.ts | 160 +++
apps/tischler/lib/werkstoff/auftrag.ts | 145 +++
.../lib/werkstoff/fixtures/demo-mini.glb | Bin 0 -> 280 bytes
apps/tischler/lib/werkstoff/karte.test.ts | 14 +-
.../public/werkstoff-bundle/auftrag.json | 23 +
...562x555.json => teil_beispielbo0oben.json} | 38 +-
.../karten/teil_beispielbo0unten.json | 1048 +++++++++++++++++
...90x555.json => teil_beispielse0links.json} | 45 +-
.../karten/teil_beispielse0rechts.json | 1048 +++++++++++++++++
...562x555.json => teil_beispielbo0oben.json} | 32 +-
.../manifeste/teil_beispielbo0unten.json | 414 +++++++
...90x555.json => teil_beispielse0links.json} | 38 +-
.../manifeste/teil_beispielse0rechts.json | 414 +++++++
15 files changed, 3394 insertions(+), 106 deletions(-)
create mode 100644 apps/tischler/lib/werkstoff/auftrag.test.ts
create mode 100644 apps/tischler/lib/werkstoff/auftrag.ts
create mode 100644 apps/tischler/lib/werkstoff/fixtures/demo-mini.glb
create mode 100644 apps/tischler/public/werkstoff-bundle/auftrag.json
rename apps/tischler/public/werkstoff-bundle/karten/{boden-562x555.json => teil_beispielbo0oben.json} (98%)
create mode 100644 apps/tischler/public/werkstoff-bundle/karten/teil_beispielbo0unten.json
rename apps/tischler/public/werkstoff-bundle/karten/{seite-links-890x555.json => teil_beispielse0links.json} (97%)
create mode 100644 apps/tischler/public/werkstoff-bundle/karten/teil_beispielse0rechts.json
rename apps/tischler/public/werkstoff-bundle/manifeste/{boden-562x555.json => teil_beispielbo0oben.json} (96%)
create mode 100644 apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielbo0unten.json
rename apps/tischler/public/werkstoff-bundle/manifeste/{seite-links-890x555.json => teil_beispielse0links.json} (96%)
create mode 100644 apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielse0rechts.json
diff --git a/apps/tischler/app/[locale]/werkstoff/page.tsx b/apps/tischler/app/[locale]/werkstoff/page.tsx
index 3881f2f..2853cc3 100644
--- a/apps/tischler/app/[locale]/werkstoff/page.tsx
+++ b/apps/tischler/app/[locale]/werkstoff/page.tsx
@@ -31,11 +31,7 @@ import {
type Komponentenzeile,
type Materialkarte,
} from "../../../lib/werkstoff/karte";
-
-const WERKSTUECKE = [
- { id: "seite-links-890x555", kurz: "Seite links", laenge: 890, breite: 555 },
- { id: "boden-562x555", kurz: "Boden", laenge: 562, breite: 555 },
-];
+import { ladeAuftrag, schluesselKurz, type Auftrag } from "../../../lib/werkstoff/auftrag";
function Ampel({ freigabe }: { freigabe: Materialkarte["freigabe"] }) {
return (
@@ -120,6 +116,10 @@ function Zeile({ karte, z }: { karte: Materialkarte; z: Komponentenzeile }) {
}
export default function WerkstoffPage() {
+ // Der Auftrag bestimmt, welche Bretter es gibt — nicht eine Liste im Code. Fehlt er, gibt es
+ // kein Brett und den Satz dazu; ein Katalog-Beispiel an seiner Stelle wäre das falsche Material.
+ const [auftrag, setAuftrag] = useState(null);
+ const [auftragFehler, setAuftragFehler] = useState(null);
const [gewaehlt, setGewaehlt] = useState(null);
const [karte, setKarte] = useState(null);
const [stand, setStand] = useState(null);
@@ -131,6 +131,9 @@ export default function WerkstoffPage() {
// Fehlt die Grenzaussage, bleibt es still — kein Fehler, aber auch kein Satz, den
// niemand belegen kann.
ladeDatengrenze().then(setGrenze);
+ ladeAuftrag()
+ .then(setAuftrag)
+ .catch((e) => setAuftragFehler(String(e.message ?? e)));
}, []);
useEffect(() => {
@@ -154,7 +157,6 @@ export default function WerkstoffPage() {
const gruppen = useMemo(() => (karte ? gruppiere(karte.komponenten) : []), [karte]);
const schritte = useMemo(() => (karte ? arbeitsfolge(karte) : []), [karte]);
- const massstab = 0.28;
return (
@@ -165,6 +167,13 @@ export default function WerkstoffPage() {
? `Katalogstand ${stand.stand} · ${stand.dateien} Datendateien · ${stand.sha256.slice(0, 12)}…`
: "Katalogstand wird gelesen …"}
+ {auftrag && (
+
+ Möbel {auftrag.moebel_id} · Revision {auftrag.revision} · Plan{" "}
+ {auftrag.buildplan_sha256.slice(0, 12)}…
+
+ )}
+ {auftragFehler && ⚠ Kein Auftrag im Bundle: {auftragFehler}
}
Offline-Bundle aus dem Auftrag — Datenblätter sind verlinkt, nicht kopiert.
@@ -179,19 +188,16 @@ export default function WerkstoffPage() {
- {WERKSTUECKE.map((w) => (
+ {(auftrag?.teile ?? []).map((w) => (
setGewaehlt(gewaehlt === w.id ? null : w.id)}
- aria-pressed={gewaehlt === w.id}
+ className={`teil ${gewaehlt === w.werkstueck_id ? "aktiv" : ""}`}
+ onClick={() => setGewaehlt(gewaehlt === w.werkstueck_id ? null : w.werkstueck_id)}
+ aria-pressed={gewaehlt === w.werkstueck_id}
>
- {w.kurz}
-
- {w.laenge} × {w.breite}
-
+ {schluesselKurz(w.schluessel)}
+ {w.werkstueck_id}
))}
@@ -204,7 +210,8 @@ export default function WerkstoffPage() {
{karte.werkstueck.bezeichnung}
- Aufbau {karte.aufbau.bezeichnung} ({karte.aufbau.id}@{karte.aufbau.revision})
+ Teil {karte.werkstueck.id} · Aufbau {karte.aufbau.bezeichnung} ({karte.aufbau.id}@
+ {karte.aufbau.revision})
diff --git a/apps/tischler/app/[locale]/werkstoff/panel.test.tsx b/apps/tischler/app/[locale]/werkstoff/panel.test.tsx
index 42f7a4b..940be7e 100644
--- a/apps/tischler/app/[locale]/werkstoff/panel.test.tsx
+++ b/apps/tischler/app/[locale]/werkstoff/panel.test.tsx
@@ -57,3 +57,43 @@ describe("Werkstoff-Panel: die Grenzaussage am Werkstück", () => {
expect(document.body.textContent ?? "").not.toMatch(/Feldname/i);
});
});
+
+describe("Werkstoff-Panel: der Auftrag bestimmt, welche Bretter es gibt", () => {
+ it("zeigt Möbel und Revision aus dem Auftrag — und die vier Teile des Plans als Schaltflächen", async () => {
+ vi.stubGlobal("fetch", stub());
+ render( );
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: /Se:links/ })).toBeTruthy();
+ });
+ const text = document.body.textContent ?? "";
+ expect(text).toMatch(/moebel_beispiel0001/);
+ expect(text).toMatch(/Revision 3/);
+ expect(screen.getAllByRole("button", { name: /^(Bo|Se):/ })).toHaveLength(4);
+ });
+
+ it("tippt der Handwerker auf »Se:links«, kommt die Karte des Plan-Teils, nicht eines Katalog-Bretts", async () => {
+ vi.stubGlobal("fetch", stub());
+ render( );
+ const knopf = await waitFor(() => screen.getByRole("button", { name: /Se:links/ }));
+ knopf.click();
+ await waitFor(() => {
+ expect(document.body.textContent).toMatch(/Teil teil_beispielse0links/);
+ });
+ // Die Bezeichnung kommt aus der Karte des Plan-Teils — der Test liest sie dort, statt sie
+ // zu raten; ein Katalog-Brett hieße anders.
+ const karte = JSON.parse(
+ readFileSync(join(BUNDLE, "karten/teil_beispielse0links.json"), "utf8"),
+ ) as { werkstueck: { bezeichnung: string } };
+ expect(karte.werkstueck.bezeichnung.length).toBeGreaterThan(0);
+ expect(document.body.textContent).toContain(karte.werkstueck.bezeichnung);
+ });
+
+ it("ohne Auftrag im Bundle: kein Brett, sondern der Satz, dass der Auftrag fehlt", async () => {
+ vi.stubGlobal("fetch", stub((url, roh) => (url.endsWith("auftrag.json") ? null : roh)));
+ render( );
+ await waitFor(() => {
+ expect(document.body.textContent).toMatch(/Kein Auftrag/);
+ });
+ expect(screen.queryAllByRole("button", { name: /^(Bo|Se):/ })).toHaveLength(0);
+ });
+});
diff --git a/apps/tischler/lib/werkstoff/auftrag.test.ts b/apps/tischler/lib/werkstoff/auftrag.test.ts
new file mode 100644
index 0000000..3c2ff2a
--- /dev/null
+++ b/apps/tischler/lib/werkstoff/auftrag.test.ts
@@ -0,0 +1,160 @@
+/**
+ * Der Auftrag im Bundle ist die Klammer zwischen Konstruktion und Karte: welches Möbel, welcher
+ * Plan, welche Revision — und welcher Knoten im 3D-Modell welche Karte trägt. Red-First: diese
+ * Datei stand, bevor `lib/werkstoff/auftrag.ts` existierte.
+ *
+ * Vertrag mit cody-cad (#66, Cody #2 04.09.): GLB-Knotennamen = `schluessel`, Wurzel = `moebel_id`.
+ * Der Loader prüft in BEIDE Richtungen — ein Knoten ohne Karte ist so falsch wie eine Karte
+ * ohne Knoten; das eine zeigt ein Brett ohne Material, das andere Material ohne Brett.
+ */
+import { describe, it, expect, vi, afterEach } from "vitest";
+import { readFileSync, existsSync } from "node:fs";
+import { join } from "node:path";
+import { glbKnoten, karteFuer, ladeAuftrag, pruefeSzene, type Auftrag } from "./auftrag";
+
+const BUNDLE = join(__dirname, "../../public/werkstoff-bundle");
+const MINI_GLB = join(__dirname, "fixtures/demo-mini.glb");
+const AUFTRAG = JSON.parse(readFileSync(join(BUNDLE, "auftrag.json"), "utf8")) as Auftrag;
+
+function stub(aenderung?: (url: string, roh: string) => string | null) {
+ return vi.fn(async (url: string) => {
+ const pfad = join(BUNDLE, String(url).replace("/werkstoff-bundle/", ""));
+ if (!existsSync(pfad)) return { ok: false, status: 404 } as Response;
+ const roh = readFileSync(pfad, "utf8");
+ const inhalt = aenderung ? aenderung(String(url), roh) : roh;
+ if (inhalt === null) return { ok: false, status: 404 } as Response;
+ return { ok: true, status: 200, json: async () => JSON.parse(inhalt) } as Response;
+ });
+}
+const verbiegen = (f: (a: Record) => void) =>
+ stub((url, roh) => {
+ if (!url.endsWith("auftrag.json")) return roh;
+ const o = JSON.parse(roh);
+ f(o);
+ return JSON.stringify(o);
+ });
+
+afterEach(() => vi.unstubAllGlobals());
+
+describe("ladeAuftrag — die Klammer aus dem Bundle", () => {
+ it("liest Möbel, Plan-Hash, Revision und die vier Teile des Demo-Plans", async () => {
+ vi.stubGlobal("fetch", stub());
+ const a = await ladeAuftrag();
+ expect(a.moebel_id).toBe("moebel_beispiel0001");
+ expect(a.buildplan_sha256).toMatch(/^[0-9a-f]{64}$/);
+ expect(a.revision).toBe(3);
+ expect(a.teile.map((t) => t.schluessel)).toEqual([
+ "teil:Bo:oben",
+ "teil:Bo:unten",
+ "teil:Se:links",
+ "teil:Se:rechts",
+ ]);
+ expect(a.teile.every((t) => /^teil_/.test(t.werkstueck_id))).toBe(true);
+ });
+
+ it("fehlt der Auftrag, ist das ein Fehler — kein leeres Möbel", async () => {
+ vi.stubGlobal("fetch", stub((url, roh) => (url.endsWith("auftrag.json") ? null : roh)));
+ await expect(ladeAuftrag()).rejects.toThrow(/Kein Auftrag/);
+ });
+
+ it("lehnt jeden verbogenen Auftrag ab — mit dem Grund", async () => {
+ const faelle: Array<[RegExp, (a: Record) => void]> = [
+ [/teile/, (a) => (a.teile = [])],
+ [/doppelt/i, (a) => ((a.teile as Array<{ schluessel: string }>)[1]!.schluessel = "teil:Bo:oben")],
+ [/doppelt/i, (a) => ((a.teile as Array<{ werkstueck_id: string }>)[1]!.werkstueck_id = "teil_beispielbo0oben")],
+ [/revision/i, (a) => (a.revision = 0)],
+ [/revision/i, (a) => (a.revision = "3")],
+ [/moebel_id/, (a) => delete a.moebel_id],
+ [/buildplan_sha256/, (a) => (a.buildplan_sha256 = "abc")],
+ [/schluessel/, (a) => delete (a.teile as Array<{ schluessel?: string }>)[0]!.schluessel],
+ [/werkstueck_id/, (a) => ((a.teile as Array<{ werkstueck_id: string }>)[0]!.werkstueck_id = "")],
+ ];
+ for (const [grund, f] of faelle) {
+ vi.stubGlobal("fetch", verbiegen(f));
+ await expect(ladeAuftrag(), String(grund)).rejects.toThrow(grund);
+ }
+ });
+});
+
+describe("glbKnoten — was das 3D-Modell an Namen trägt", () => {
+ const glb = () => new Uint8Array(readFileSync(MINI_GLB)).buffer;
+
+ it("liest Wurzel und Teile aus dem Fixture (Wurzel = Möbel, Kinder = Schlüssel)", () => {
+ const k = glbKnoten(glb());
+ expect(k.wurzel).toBe("moebel_beispiel0001");
+ expect([...k.teile].sort()).toEqual(["teil:Bo:oben", "teil:Bo:unten", "teil:Se:links", "teil:Se:rechts"]);
+ });
+
+ it("lehnt ab, was kein glTF-Binary ist", () => {
+ const kaputt = new Uint8Array(glb());
+ kaputt[0] = 0x58;
+ expect(() => glbKnoten(kaputt.buffer)).toThrow(/glTF/);
+ });
+
+ it("verlangt genau eine benannte Wurzel und benannte Kinder", () => {
+ const umbauen = (f: (j: Record) => void) => {
+ const b = new Uint8Array(glb());
+ const len = new DataView(b.buffer).getUint32(12, true);
+ const j = JSON.parse(new TextDecoder().decode(b.subarray(20, 20 + len)));
+ f(j);
+ const txt = new TextEncoder().encode(JSON.stringify(j));
+ const pad = (4 - (txt.length % 4)) % 4;
+ const out = new Uint8Array(20 + txt.length + pad);
+ const dv = new DataView(out.buffer);
+ out.set([0x67, 0x6c, 0x54, 0x46], 0);
+ dv.setUint32(4, 2, true);
+ dv.setUint32(8, out.length, true);
+ dv.setUint32(12, txt.length + pad, true);
+ dv.setUint32(16, 0x4e4f534a, true);
+ out.set(txt, 20);
+ for (let i = 0; i < pad; i++) out[20 + txt.length + i] = 0x20;
+ return out.buffer;
+ };
+ expect(() => glbKnoten(umbauen((j) => ((j.scenes as Array<{ nodes: number[] }>)[0]!.nodes = [0, 1])))).toThrow(/eine Wurzel/);
+ expect(() => glbKnoten(umbauen((j) => delete (j.nodes as Array<{ name?: string }>)[0]!.name))).toThrow(/Wurzel.*Name/);
+ expect(() => glbKnoten(umbauen((j) => delete (j.nodes as Array<{ name?: string }>)[2]!.name))).toThrow(/ohne Namen/);
+ });
+});
+
+describe("pruefeSzene — Modell und Auftrag müssen sich decken, in beide Richtungen", () => {
+ const knoten = () => glbKnoten(new Uint8Array(readFileSync(MINI_GLB)).buffer);
+
+ it("passt: Demo-Fixture gegen den Auftrag des Bundles", () => {
+ expect(pruefeSzene(AUFTRAG, knoten())).toEqual({ ok: true, fehler: [] });
+ });
+
+ it("ein Knoten ohne Karte wird genannt — die Rückwand, die es im Katalog nicht gibt", () => {
+ const k = knoten();
+ const r = pruefeSzene(AUFTRAG, { ...k, teile: [...k.teile, "teil:Rw"] });
+ expect(r.ok).toBe(false);
+ expect(r.fehler.join("\n")).toMatch(/teil:Rw.*ohne Karte/);
+ });
+
+ it("eine Karte ohne Knoten wird genannt — Material ohne Brett", () => {
+ const k = knoten();
+ const r = pruefeSzene(AUFTRAG, { ...k, teile: k.teile.filter((t) => t !== "teil:Se:rechts") });
+ expect(r.ok).toBe(false);
+ expect(r.fehler.join("\n")).toMatch(/teil:Se:rechts.*nicht in der Szene/);
+ });
+
+ it("die falsche Wurzel ist das falsche Möbel — beide Namen stehen im Fehler", () => {
+ const r = pruefeSzene(AUFTRAG, { ...knoten(), wurzel: "moebel_anderes0002" });
+ expect(r.ok).toBe(false);
+ expect(r.fehler.join("\n")).toMatch(/moebel_anderes0002/);
+ expect(r.fehler.join("\n")).toMatch(/moebel_beispiel0001/);
+ });
+
+ it("meldet ALLE Abweichungen, nicht nur die erste", () => {
+ const k = knoten();
+ const r = pruefeSzene(AUFTRAG, {
+ wurzel: "moebel_anderes0002",
+ teile: [...k.teile.filter((t) => t !== "teil:Se:rechts"), "teil:Rw"],
+ });
+ expect(r.fehler).toHaveLength(3);
+ });
+
+ it("karteFuer: Schlüssel → Karte, unbekannter Schlüssel → undefined (kein Raten)", () => {
+ expect(karteFuer(AUFTRAG, "teil:Se:links")).toBe("teil_beispielse0links");
+ expect(karteFuer(AUFTRAG, "teil:Rw")).toBeUndefined();
+ });
+});
diff --git a/apps/tischler/lib/werkstoff/auftrag.ts b/apps/tischler/lib/werkstoff/auftrag.ts
new file mode 100644
index 0000000..d3b3fd0
--- /dev/null
+++ b/apps/tischler/lib/werkstoff/auftrag.ts
@@ -0,0 +1,145 @@
+/**
+ * Der Auftrag im Offline-Bundle — die Klammer zwischen Konstruktion und Karte.
+ *
+ * cody-cad schreibt ihn im BuildPlan-Modus (`bauen --buildplan --revision`, #66): welches Möbel
+ * (`moebel_id`), welcher Plan (`buildplan_sha256`), welche Revision — und für jedes Teil die
+ * Brücke `schluessel` (Knotenname im 3D-Modell) → `werkstueck_id` (Karte). Ohne diese Klammer
+ * zeigt die Szene Bretter derselben Sorte; mit ihr dasselbe Brett wie in der Zeichnung.
+ *
+ * Vertrag (Cody #2, 04.09.): GLB-Knotennamen = `schluessel`, Wurzel = `moebel_id`. Geprüft wird in
+ * BEIDE Richtungen — ein Knoten ohne Karte ist ein Brett ohne Material, eine Karte ohne Knoten ist
+ * Material ohne Brett. Beides ist ein Fehler, keiner davon still.
+ */
+
+export interface AuftragTeil {
+ schluessel: string;
+ werkstueck_id: string;
+}
+
+export interface Auftrag {
+ moebel_id: string;
+ buildplan_sha256: string;
+ revision: number;
+ teile: AuftragTeil[];
+}
+
+const HEX64 = /^[0-9a-f]{64}$/;
+
+/** Strikt: was nicht die Form des Vertrags hat, ist kein Auftrag — mit dem Grund. */
+function pruefeAuftrag(d: unknown): Auftrag {
+ if (!d || typeof d !== "object") throw new Error("Auftrag ist kein Objekt");
+ const o = d as Record;
+ if (typeof o.moebel_id !== "string" || !o.moebel_id) throw new Error("Auftrag ohne moebel_id");
+ if (typeof o.buildplan_sha256 !== "string" || !HEX64.test(o.buildplan_sha256)) {
+ throw new Error("Auftrag ohne gültigen buildplan_sha256 (64 Hex-Zeichen)");
+ }
+ if (typeof o.revision !== "number" || !Number.isInteger(o.revision) || o.revision < 1) {
+ throw new Error("Auftrag ohne gültige revision (ganze Zahl ≥ 1)");
+ }
+ if (!Array.isArray(o.teile) || o.teile.length === 0) throw new Error("Auftrag ohne teile");
+ const schluessel = new Set();
+ const karten = new Set();
+ const teile: AuftragTeil[] = [];
+ for (const t of o.teile as unknown[]) {
+ const x = (t ?? {}) as Record;
+ if (typeof x.schluessel !== "string" || !x.schluessel) throw new Error("Teil ohne schluessel");
+ if (typeof x.werkstueck_id !== "string" || !x.werkstueck_id) {
+ throw new Error(`Teil ${x.schluessel} ohne werkstueck_id`);
+ }
+ if (schluessel.has(x.schluessel)) throw new Error(`Schlüssel ${x.schluessel} doppelt im Auftrag`);
+ if (karten.has(x.werkstueck_id)) throw new Error(`Karte ${x.werkstueck_id} doppelt im Auftrag`);
+ schluessel.add(x.schluessel);
+ karten.add(x.werkstueck_id);
+ teile.push({ schluessel: x.schluessel, werkstueck_id: x.werkstueck_id });
+ }
+ return { moebel_id: o.moebel_id, buildplan_sha256: o.buildplan_sha256, revision: o.revision, teile };
+}
+
+export async function ladeAuftrag(basis = "/werkstoff-bundle"): Promise {
+ const r = await fetch(`${basis}/auftrag.json`);
+ if (!r.ok) {
+ throw new Error(
+ `Kein Auftrag im Bundle (${r.status}) — das Bundle kennt Bretter derselben Sorte, ` +
+ "aber nicht dieses Möbel",
+ );
+ }
+ return pruefeAuftrag(await r.json());
+}
+
+export interface SzenenKnoten {
+ wurzel: string;
+ teile: string[];
+}
+
+/**
+ * Liest aus einem glTF-Binary (GLB) nur die Namen: die eine Wurzel der Szene und ihre Kinder.
+ * Kein Mesh, keine Geometrie — genau das, was der Vertrag verlangt, und ohne WebGL prüfbar.
+ */
+export function glbKnoten(buf: ArrayBuffer): SzenenKnoten {
+ const dv = new DataView(buf);
+ if (buf.byteLength < 20 || dv.getUint32(0, true) !== 0x46546c67) {
+ throw new Error("Kein glTF-Binary (Magic »glTF« fehlt)");
+ }
+ const jsonLen = dv.getUint32(12, true);
+ if (dv.getUint32(16, true) !== 0x4e4f534a) throw new Error("glTF-Binary ohne JSON-Chunk");
+ const j = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 20, jsonLen))) as {
+ scenes?: Array<{ nodes?: number[] }>;
+ nodes?: Array<{ name?: string; children?: number[] }>;
+ scene?: number;
+ };
+ const nodes = j.nodes ?? [];
+ const szene = j.scenes?.[j.scene ?? 0];
+ const wurzeln = szene?.nodes ?? [];
+ if (wurzeln.length !== 1) {
+ throw new Error(
+ `Szene hat ${wurzeln.length} Wurzeln — der Vertrag verlangt genau eine Wurzel (das Möbel)`,
+ );
+ }
+ const wurzelIdx = wurzeln[0];
+ const wurzel = wurzelIdx === undefined ? undefined : nodes[wurzelIdx];
+ if (!wurzel || !wurzel.name) {
+ throw new Error("Wurzel ohne Name — das Möbel ist nicht identifizierbar");
+ }
+ const kinder: number[] = wurzel.children ?? [];
+ const teile = kinder.map((i) => {
+ const n = nodes[i]?.name;
+ if (!n) throw new Error(`Knoten ${i} ohne Namen — er kann keine Karte tragen`);
+ return n;
+ });
+ return { wurzel: wurzel.name, teile };
+}
+
+export interface SzenenPruefung {
+ ok: boolean;
+ fehler: string[];
+}
+
+/** Beide Richtungen, alle Abweichungen — nicht nur die erste. */
+export function pruefeSzene(auftrag: Auftrag, knoten: SzenenKnoten): SzenenPruefung {
+ const fehler: string[] = [];
+ if (knoten.wurzel !== auftrag.moebel_id) {
+ fehler.push(
+ `Falsches Möbel: die Szene heißt ${knoten.wurzel}, der Auftrag gilt für ${auftrag.moebel_id}`,
+ );
+ }
+ const imAuftrag = new Set(auftrag.teile.map((t) => t.schluessel));
+ const inSzene = new Set(knoten.teile);
+ for (const s of knoten.teile) {
+ if (!imAuftrag.has(s)) fehler.push(`Knoten ${s} ohne Karte im Auftrag — Brett ohne Material`);
+ }
+ for (const t of auftrag.teile) {
+ if (!inSzene.has(t.schluessel)) {
+ fehler.push(`Karte ${t.schluessel} nicht in der Szene — Material ohne Brett`);
+ }
+ }
+ return { ok: fehler.length === 0, fehler };
+}
+
+export function karteFuer(auftrag: Auftrag, schluessel: string): string | undefined {
+ return auftrag.teile.find((t) => t.schluessel === schluessel)?.werkstueck_id;
+}
+
+/** »teil:Se:links« → »Se:links« — die Schaltfläche trägt den Schlüssel, nicht ein Wunschwort. */
+export function schluesselKurz(schluessel: string): string {
+ return schluessel.replace(/^teil:/, "");
+}
diff --git a/apps/tischler/lib/werkstoff/fixtures/demo-mini.glb b/apps/tischler/lib/werkstoff/fixtures/demo-mini.glb
new file mode 100644
index 0000000000000000000000000000000000000000..65fcb90540e1f54378eefcfa6c88b5ff687f3dde
GIT binary patch
literal 280
zcmZvVy$S*`5QL-n5SFral=xGnw6L(yLOThP%dL08m`D;uIr8qVAU0Yo%ziV&G;Y7@
zGscEJ#s+lj-FDM~P6;!}4(=F}4IU)SCIUD#=rQUXA`uhn=h*0alriPe$v*NeA`}kE
zPzt2@s08xRfe$sll~6_T??+r;C<~W>6}n@EHeD?`A;gsCOlw^ok@gQ$KI3z~P=$Xx
g8LgAcAVWooKm6wNA7h8oS$|GH4KgB{Hj|^u3&kW^P5=M^
literal 0
HcmV?d00001
diff --git a/apps/tischler/lib/werkstoff/karte.test.ts b/apps/tischler/lib/werkstoff/karte.test.ts
index 0ea2a73..e02f403 100644
--- a/apps/tischler/lib/werkstoff/karte.test.ts
+++ b/apps/tischler/lib/werkstoff/karte.test.ts
@@ -19,7 +19,7 @@ import {
const KARTE = JSON.parse(
readFileSync(
- join(__dirname, "../../public/werkstoff-bundle/karten/seite-links-890x555.json"),
+ join(__dirname, "../../public/werkstoff-bundle/karten/teil_beispielse0links.json"),
"utf8",
),
) as Materialkarte;
@@ -105,8 +105,8 @@ describe("ladeKarte", () => {
it("liefert die Karte, wenn Karte und Manifest zusammengehören", async () => {
vi.stubGlobal("fetch", stub());
- const k = await ladeKarte("boden-562x555");
- expect(k.werkstueck.id).toBe("boden-562x555");
+ const k = await ladeKarte("teil_beispielbo0unten");
+ expect(k.werkstueck.id).toBe("teil_beispielbo0unten");
vi.unstubAllGlobals();
});
@@ -120,7 +120,7 @@ describe("ladeKarte", () => {
// Ohne Manifest hieße "bekanntes Werkstück" nur: es steht in der Datei, die ich
// gerade gelesen habe. Die Karte belegte sich selbst.
vi.stubGlobal("fetch", stub((url, roh) => (url.includes("/manifeste/") ? null : roh)));
- await expect(ladeKarte("boden-562x555")).rejects.toThrow(/ohne Resolve-Manifest/);
+ await expect(ladeKarte("teil_beispielbo0unten")).rejects.toThrow(/ohne Resolve-Manifest/);
vi.unstubAllGlobals();
});
@@ -138,7 +138,7 @@ describe("ladeKarte", () => {
return JSON.stringify(o);
}),
);
- await expect(ladeKarte("boden-562x555")).rejects.toThrow(
+ await expect(ladeKarte("teil_beispielbo0unten")).rejects.toThrow(
/ohne gültigen Manifest-Hash/,
);
vi.unstubAllGlobals();
@@ -156,7 +156,7 @@ describe("ladeKarte", () => {
: roh,
),
);
- await expect(ladeKarte("boden-562x555")).rejects.toThrow(/Manifest gehört zu ein-anderes/);
+ await expect(ladeKarte("teil_beispielbo0unten")).rejects.toThrow(/Manifest gehört zu ein-anderes/);
vi.unstubAllGlobals();
});
@@ -169,7 +169,7 @@ describe("ladeKarte", () => {
: roh,
),
);
- await expect(ladeKarte("boden-562x555")).rejects.toThrow(/gehören nicht zusammen/);
+ await expect(ladeKarte("teil_beispielbo0unten")).rejects.toThrow(/gehören nicht zusammen/);
vi.unstubAllGlobals();
});
});
diff --git a/apps/tischler/public/werkstoff-bundle/auftrag.json b/apps/tischler/public/werkstoff-bundle/auftrag.json
new file mode 100644
index 0000000..ca9ddbe
--- /dev/null
+++ b/apps/tischler/public/werkstoff-bundle/auftrag.json
@@ -0,0 +1,23 @@
+{
+ "buildplan_sha256": "dbaca54603baaf7b83e9dc0b23dcf7812148d8b20b53a6e4c38e137adc2938ef",
+ "moebel_id": "moebel_beispiel0001",
+ "revision": 3,
+ "teile": [
+ {
+ "schluessel": "teil:Bo:oben",
+ "werkstueck_id": "teil_beispielbo0oben"
+ },
+ {
+ "schluessel": "teil:Bo:unten",
+ "werkstueck_id": "teil_beispielbo0unten"
+ },
+ {
+ "schluessel": "teil:Se:links",
+ "werkstueck_id": "teil_beispielse0links"
+ },
+ {
+ "schluessel": "teil:Se:rechts",
+ "werkstueck_id": "teil_beispielse0rechts"
+ }
+ ]
+}
diff --git a/apps/tischler/public/werkstoff-bundle/karten/boden-562x555.json b/apps/tischler/public/werkstoff-bundle/karten/teil_beispielbo0oben.json
similarity index 98%
rename from apps/tischler/public/werkstoff-bundle/karten/boden-562x555.json
rename to apps/tischler/public/werkstoff-bundle/karten/teil_beispielbo0oben.json
index b1b2bec..e518384 100644
--- a/apps/tischler/public/werkstoff-bundle/karten/boden-562x555.json
+++ b/apps/tischler/public/werkstoff-bundle/karten/teil_beispielbo0oben.json
@@ -339,7 +339,7 @@
],
"wert": 14.352
},
- "karte_sha256": "7f863d472a15d7cf1a4676d6c1e934120aa115efa713e51b5ac03fd962b2cc2e",
+ "karte_sha256": "4fd87039da1d7b107f9db11dbbb29628574e0d7db8a5e10bb97a63d04374b664",
"karteversion": 1,
"komponenten": [
{
@@ -878,7 +878,7 @@
]
}
],
- "resolve_manifest_sha256": "2539300205de77f8ecec3f5a40d525829039f7309c53d9002b0ea34784fabc49",
+ "resolve_manifest_sha256": "129f81aa35dc2ea0802a44cbf7e8671235b24fc0fb620772dee94f5a44d1f37c",
"schema": "werkstoff/karte.schema.json",
"unterlagen_ohne_verweis": [
{
@@ -926,7 +926,7 @@
],
"verbrauch": {
"ausgeschlossen": [],
- "flaeche_m2": 0.6238,
+ "flaeche_m2": 0.3119,
"freigabe": "annahme",
"positionen": [
{
@@ -936,7 +936,7 @@
"komponente": "egger-spanplatte-p2-19",
"revision": 1,
"rolle": "traeger",
- "wert": 0.6238
+ "wert": 0.3119
},
{
"bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
@@ -945,7 +945,7 @@
"komponente": "furnier-eiche-0-9-gemessert",
"revision": 1,
"rolle": "belag/oben",
- "wert": 0.6238
+ "wert": 0.3119
},
{
"bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
@@ -954,7 +954,7 @@
"komponente": "ponal-flaechenleim-pvac",
"revision": 1,
"rolle": "leim/oben",
- "wert": 74.9
+ "wert": 37.4
},
{
"bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
@@ -963,7 +963,7 @@
"komponente": "furnier-eiche-0-9-gemessert",
"revision": 1,
"rolle": "belag/unten",
- "wert": 0.6238
+ "wert": 0.3119
},
{
"bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
@@ -972,7 +972,7 @@
"komponente": "ponal-flaechenleim-pvac",
"revision": 1,
"rolle": "leim/unten",
- "wert": 74.9
+ "wert": 37.4
},
{
"bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
@@ -981,7 +981,7 @@
"komponente": "adler-aduro-legnopur-g10",
"revision": 1,
"rolle": "oberflaeche/oben/1-klarlack/netto",
- "wert": 124.8
+ "wert": 62.4
},
{
"bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
@@ -990,7 +990,7 @@
"komponente": "adler-aduro-legnopur-g10",
"revision": 1,
"rolle": "oberflaeche/oben/1-klarlack/brutto",
- "wert": 191.9
+ "wert": 96.0
},
{
"bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
@@ -999,7 +999,7 @@
"komponente": "adler-aduro-legnopur-g10",
"revision": 1,
"rolle": "oberflaeche/unten/1-klarlack/netto",
- "wert": 124.8
+ "wert": 62.4
},
{
"bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
@@ -1008,7 +1008,7 @@
"komponente": "adler-aduro-legnopur-g10",
"revision": 1,
"rolle": "oberflaeche/unten/1-klarlack/brutto",
- "wert": 191.9
+ "wert": 96.0
},
{
"bezeichnung": "Ostermann Furnierkante Eiche europäisch, geschliffen K180, 1,0 × 24 mm (Art. 070.4200)",
@@ -1017,7 +1017,7 @@
"komponente": "ostermann-furnierkante-eiche-1-0",
"revision": 1,
"rolle": "kante/vorne",
- "wert": 1.132
+ "wert": 0.566
},
{
"bezeichnung": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
@@ -1026,18 +1026,18 @@
"komponente": "jowatherm-280-30-eva",
"revision": 1,
"rolle": "kleber/vorne",
- "wert": 6.8
+ "wert": 3.4
}
],
- "werkstueck": "boden-562x555"
+ "werkstueck": "teil_beispielbo0oben"
},
"videos": [],
"werkstueck": {
- "anzahl": 2,
- "bezeichnung": "Boden (Korpus 600 breit, Seiten 19)",
+ "anzahl": 1,
+ "bezeichnung": "Boden Oben",
"faserrichtung": "laengs",
- "flaeche_m2": 0.6238,
- "id": "boden-562x555",
+ "flaeche_m2": 0.3119,
+ "id": "teil_beispielbo0oben",
"kanten": {},
"kontur": {
"breite": 555.0,
diff --git a/apps/tischler/public/werkstoff-bundle/karten/teil_beispielbo0unten.json b/apps/tischler/public/werkstoff-bundle/karten/teil_beispielbo0unten.json
new file mode 100644
index 0000000..0af7fe6
--- /dev/null
+++ b/apps/tischler/public/werkstoff-bundle/karten/teil_beispielbo0unten.json
@@ -0,0 +1,1048 @@
+{
+ "arbeitsfolge": [
+ {
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "nr": 1,
+ "parameter": {
+ "auftrag_g_m2": 120.0,
+ "beanspruchungsgruppe": "D1/D2 (EN 204)",
+ "faserrichtung": {
+ "oben": "laengs",
+ "unten": "laengs"
+ },
+ "holzfeuchte_max_prozent": "12 %",
+ "holzfeuchte_min_prozent": "8 %",
+ "klebstoff": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "klebstoff_quelle": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "leim": "Arbeitsannahme PVAc (Ponal Flächenleim, Kaltpresse) — Alternative UF/Pressal (Heißpresse): Bernhard entscheidet",
+ "offene_zeit_min": 15.0,
+ "pressdruck_n_cm2": 20.0,
+ "presstemperatur_c": 60.0,
+ "presszeit_min": 4.0
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "furnieren",
+ "werkzeuge": [
+ "Furnierpresse (Kalt- oder Heißpresse)",
+ "Leimauftragswalze — Eiche + blankes Metall = Blauverfärbung (Gerbsäure)",
+ "Leimauftragswalze (kein blankes Metall bei Eiche — Blauverfärbung)",
+ "Kaltpresse / Furnierpresse ≤ 60 °C"
+ ]
+ },
+ {
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "nr": 2,
+ "parameter": {
+ "dicke_mm": 1.0,
+ "kantenart": "furnierkante",
+ "kleber": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
+ "temperatur_c": 190.0
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann Technisches Merkblatt Furnierkanten (01/2020)",
+ "stand": "2020-01-01",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ "regel": "kante_vor_nach_belag",
+ "seiten": [
+ "vorne"
+ ],
+ "sicherheit": [],
+ "typ": "kante",
+ "werkzeuge": [
+ "Kantenanleimmaschine / BAZ (EVA 190–210 °C)",
+ "Kappsäge, Bündigfräser, Ziehklinge, Schwabbelscheibe"
+ ]
+ },
+ {
+ "nr": 3,
+ "parameter": {
+ "korn_max": 180.0,
+ "vor": "beschichtung"
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "regel": "schleifgrenze_belag",
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "schleifen",
+ "werkzeuge": []
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 4,
+ "parameter": {
+ "auftrag": 1,
+ "auftragsmenge_g_m2": 100.0,
+ "glanzgrad": "10 GU",
+ "mischung.haerter": "10:1 PUR-Hardener 8419",
+ "mischung.topfzeit_h": "8 h",
+ "mischung.verduennung_prozent": "10 %",
+ "von": 2
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "rolle": "klarlack",
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [
+ "Atemschutz A2/P2",
+ "nur gewerbliche Verarbeitung"
+ ],
+ "typ": "auftragen",
+ "verfahren": "spritzen",
+ "werkzeuge": [
+ "Becherpistole Düse 1,8 mm, 2–3 bar",
+ "Airless Düse 0,23–0,33 mm, 100–120 bar"
+ ]
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 5,
+ "parameter": {
+ "endhaerte_min": "2880 min",
+ "schleifbar_min": "120 min",
+ "stapelbar_min": "720 min"
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "trocknen",
+ "werkzeuge": []
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 6,
+ "parameter": {
+ "korn": 320.0
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "regel": "zwischenschliff_pflicht",
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "zwischenschliff",
+ "werkzeuge": []
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 7,
+ "parameter": {
+ "auftrag": 2,
+ "auftragsmenge_g_m2": 100.0,
+ "glanzgrad": "10 GU",
+ "mischung.haerter": "10:1 PUR-Hardener 8419",
+ "mischung.topfzeit_h": "8 h",
+ "mischung.verduennung_prozent": "10 %",
+ "von": 2
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "rolle": "klarlack",
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [
+ "Atemschutz A2/P2",
+ "nur gewerbliche Verarbeitung"
+ ],
+ "typ": "auftragen",
+ "verfahren": "spritzen",
+ "werkzeuge": [
+ "Becherpistole Düse 1,8 mm, 2–3 bar",
+ "Airless Düse 0,23–0,33 mm, 100–120 bar"
+ ]
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 8,
+ "parameter": {
+ "endhaerte_min": "2880 min",
+ "schleifbar_min": "120 min",
+ "stapelbar_min": "720 min"
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "trocknen",
+ "werkzeuge": []
+ }
+ ],
+ "aufbau": {
+ "bezeichnung": "EGGER Spanplatte P2 19 + Furnier Eiche 0,9 beidseitig + 2× ADLER G10 beidseitig + Furnierkante Eiche vorne",
+ "id": "eiche-furniert-p2-19-g10",
+ "revision": 1
+ },
+ "brand": {
+ "fehlend": [
+ "Prüfzeugnis (EN 13501-1) für den Aufbau eiche-furniert-p2-19-g10: Träger + Belag + Oberflächensystem als geprüftes System"
+ ],
+ "hinweise": [
+ {
+ "dok_ref": "EGGER TD-181 / Brandverhalten-Merkblatt",
+ "freigabe": "belegt",
+ "klasse": "D-s2,d0",
+ "komponente": "egger-spanplatte-p2-19",
+ "nachweistyp": "einzelbaustoff"
+ },
+ {
+ "freigabe": "annahme",
+ "klasse": "E",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "nachweistyp": "einzelbaustoff"
+ }
+ ],
+ "status": "nicht_nachgewiesen"
+ },
+ "dicke_mm": {
+ "ausgeschlossen": [
+ {
+ "grund": "Trockenschichtdicke nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/oben/1-klarlack"
+ },
+ {
+ "grund": "Trockenschichtdicke nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/unten/1-klarlack"
+ }
+ ],
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "maximum": 21.5,
+ "minimum": 20.1,
+ "nominal": 20.8,
+ "positionen": [
+ {
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "komponente": "egger-spanplatte-p2-19",
+ "revision": 1,
+ "rolle": "traeger",
+ "wert": 19.0
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "mm",
+ "freigabe": "meister",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/oben",
+ "wert": 0.9
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "mm",
+ "freigabe": "meister",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/unten",
+ "wert": 0.9
+ }
+ ]
+ },
+ "dokumente_hinweis": "Unterlagen sind Verweise (URL, Seite, Hash) auf die Dokumente des Herstellers — nie deren Inhalt. Speichern, Durchsuchen oder Offline-Kopien erst nach Zustimmung des Herstellers.",
+ "freigabe": "annahme",
+ "gewicht_kg_m2": {
+ "ausgeschlossen": [
+ {
+ "grund": "Trockenmasse der Beschichtung nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche"
+ },
+ {
+ "grund": "Kante ist je lfm — braucht das Werkstück (siehe verbrauch)",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "revision": 1,
+ "rolle": "kante/vorne"
+ }
+ ],
+ "einheit": "kg/m2",
+ "freigabe": "annahme",
+ "positionen": [
+ {
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "einheit": "kg/m2",
+ "freigabe": "annahme",
+ "komponente": "egger-spanplatte-p2-19",
+ "revision": 1,
+ "rolle": "traeger",
+ "wert": 13.11
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "kg/m2",
+ "freigabe": "annahme",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/oben",
+ "wert": 0.621
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "kg/m2",
+ "freigabe": "annahme",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/unten",
+ "wert": 0.621
+ }
+ ],
+ "wert": 14.352
+ },
+ "karte_sha256": "5935fc020b748d8222fb65aa6ef77edc58b3ceb05b4f6f17e102862b25d309d9",
+ "karteversion": 1,
+ "komponenten": [
+ {
+ "art": "traeger",
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "freigabe": "annahme",
+ "hersteller": "EGGER",
+ "id": "egger-spanplatte-p2-19",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Technisches Datenblatt TD-181 (E1E05 TSCA P2 CE), Rev. 08.04.2026",
+ "seite": "S. 1",
+ "stand": "2026-04-08",
+ "url": "https://binary.egger.link/pis/978585f4-777c-45bf-96bb-3cfa40df2bae/Technical_datasheet_E1E05_TSCA_P2_CE_TD-181_Deutsch"
+ },
+ "revision": 1,
+ "rolle": "traeger",
+ "typ": "spanplatte",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER TD-181 Technisches Datenblatt E1E05 TSCA P2 CE",
+ "seiten": "S. 1–2",
+ "sha256": "1b0d66099daba0c8159b0100bb5a5cd5015cb5d0240ebc2bf266fb4ff12d82e0",
+ "stand": "2026-04-08",
+ "typ": "datenblatt",
+ "url": "https://binary.egger.link/pis/978585f4-777c-45bf-96bb-3cfa40df2bae/Technical_datasheet_E1E05_TSCA_P2_CE_TD-181_Deutsch"
+ },
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Verarbeitungshinweise Dekorative Platten und Trägerplatten",
+ "seiten": "S. 1–20",
+ "sha256": "e9fdcbea5e943a199f94178d89ffa83d20224b1cf25758fd229b6c8fe195a574",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://binary.egger.link/pis/4a389dc5-b778-426f-ada5-03d2c6e7eee3/Verarbeitungshinweise_Dekorative_Platten_und_Traegerplatten_Deutsch"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "Platten wie Kantenmaterial im Raumklima konditionieren (EGGER Verarbeitungshinweise S. 10)"
+ ],
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Verarbeitungshinweise Dekorative Platten und Trägerplatten",
+ "url": "https://binary.egger.link/pis/4a389dc5-b778-426f-ada5-03d2c6e7eee3/Verarbeitungshinweise_Dekorative_Platten_und_Traegerplatten_Deutsch"
+ },
+ "schritt": "lagern"
+ },
+ {
+ "hinweise": [
+ "EGGER-Werkzeugempfehlung ist ein separates PDF — noch nicht beschafft (Bernhards Kundenkonto)"
+ ],
+ "schritt": "saegen"
+ }
+ ]
+ },
+ {
+ "art": "belag",
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "freigabe": "annahme",
+ "id": "furnier-eiche-0-9-gemessert",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "revision": 1,
+ "rolle": "belag/oben",
+ "typ": "furnier",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN — Furnier im Innenausbau (Verarbeitung)",
+ "seiten": "S. 1–62",
+ "sha256": "3ab3f0fd735dae1a5a5b543e503f93b73f41effb273d08b65948f54b7be6578c",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "kühl, nicht in geheizten Räumen, möglichst wenig Tageslicht (ÜK4 S. 9)",
+ "Gegenzug: Rückseite gleiche Dicke (ÜK4 S. 9)"
+ ],
+ "parameter": {
+ "rf_max_prozent": {
+ "einheit": "%",
+ "freigabe": "belegt",
+ "wert": 70
+ },
+ "rf_min_prozent": {
+ "einheit": "%",
+ "freigabe": "belegt",
+ "wert": 60
+ }
+ },
+ "schritt": "lagern"
+ },
+ {
+ "hinweise": [
+ "Eiche: Probefurnierung mit PVAc (Ponal); sonst Pressal (UF)",
+ "max. 24 h zwischen Schliff und Lack"
+ ],
+ "schritt": "pressen",
+ "werkzeuge": [
+ "Furnierpresse (Kalt- oder Heißpresse)",
+ "Leimauftragswalze — Eiche + blankes Metall = Blauverfärbung (Gerbsäure)"
+ ]
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "freigabe": "annahme",
+ "hersteller": "Henkel (Ponal)",
+ "id": "ponal-flaechenleim-pvac",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "seite": "S. 1–2"
+ },
+ "revision": 1,
+ "rolle": "klebstoff/belag/oben",
+ "sicherheit": {
+ "hinweise": [
+ "Sicherheitsdatenblatt Henkel — beschaffen (nicht recherchiert)"
+ ]
+ },
+ "typ": "pvac",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "typ": "datenblatt"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "einseitig auf die Trägerplatte auftragen",
+ "Einlege-/Schließzeit kurz halten"
+ ],
+ "schritt": "pressen",
+ "werkzeuge": [
+ "Leimauftragswalze (kein blankes Metall bei Eiche — Blauverfärbung)",
+ "Kaltpresse / Furnierpresse ≤ 60 °C"
+ ]
+ },
+ {
+ "hinweise": [
+ "frostfrei lagern (Dispersionsleim)"
+ ],
+ "schritt": "lagern"
+ }
+ ]
+ },
+ {
+ "art": "belag",
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "freigabe": "annahme",
+ "id": "furnier-eiche-0-9-gemessert",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "revision": 1,
+ "rolle": "belag/unten",
+ "typ": "furnier",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN — Furnier im Innenausbau (Verarbeitung)",
+ "seiten": "S. 1–62",
+ "sha256": "3ab3f0fd735dae1a5a5b543e503f93b73f41effb273d08b65948f54b7be6578c",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "kühl, nicht in geheizten Räumen, möglichst wenig Tageslicht (ÜK4 S. 9)",
+ "Gegenzug: Rückseite gleiche Dicke (ÜK4 S. 9)"
+ ],
+ "parameter": {
+ "rf_max_prozent": {
+ "einheit": "%",
+ "freigabe": "belegt",
+ "wert": 70
+ },
+ "rf_min_prozent": {
+ "einheit": "%",
+ "freigabe": "belegt",
+ "wert": 60
+ }
+ },
+ "schritt": "lagern"
+ },
+ {
+ "hinweise": [
+ "Eiche: Probefurnierung mit PVAc (Ponal); sonst Pressal (UF)",
+ "max. 24 h zwischen Schliff und Lack"
+ ],
+ "schritt": "pressen",
+ "werkzeuge": [
+ "Furnierpresse (Kalt- oder Heißpresse)",
+ "Leimauftragswalze — Eiche + blankes Metall = Blauverfärbung (Gerbsäure)"
+ ]
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "freigabe": "annahme",
+ "hersteller": "Henkel (Ponal)",
+ "id": "ponal-flaechenleim-pvac",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "seite": "S. 1–2"
+ },
+ "revision": 1,
+ "rolle": "klebstoff/belag/unten",
+ "sicherheit": {
+ "hinweise": [
+ "Sicherheitsdatenblatt Henkel — beschaffen (nicht recherchiert)"
+ ]
+ },
+ "typ": "pvac",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "typ": "datenblatt"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "einseitig auf die Trägerplatte auftragen",
+ "Einlege-/Schließzeit kurz halten"
+ ],
+ "schritt": "pressen",
+ "werkzeuge": [
+ "Leimauftragswalze (kein blankes Metall bei Eiche — Blauverfärbung)",
+ "Kaltpresse / Furnierpresse ≤ 60 °C"
+ ]
+ },
+ {
+ "hinweise": [
+ "frostfrei lagern (Dispersionsleim)"
+ ],
+ "schritt": "lagern"
+ }
+ ]
+ },
+ {
+ "art": "system",
+ "bezeichnung": "2× ADLER G10 Klarlack (1× Grund + Zwischenschliff + 1× Deck, gleiches Produkt) — Produktwahl offen: Legnopur (Standard) / Antiscratch / Bluefin Unistar",
+ "freigabe": "annahme",
+ "id": "adler-g10-2x-klarlack",
+ "revision": 1,
+ "rolle": "system/oben",
+ "schichten": [
+ {
+ "anzahl": 2,
+ "komponente": {
+ "id": "adler-aduro-legnopur-g10",
+ "revision": 1
+ },
+ "rolle": "klarlack",
+ "verfahren": "spritzen"
+ }
+ ],
+ "unterlagen": []
+ },
+ {
+ "art": "system",
+ "bezeichnung": "2× ADLER G10 Klarlack (1× Grund + Zwischenschliff + 1× Deck, gleiches Produkt) — Produktwahl offen: Legnopur (Standard) / Antiscratch / Bluefin Unistar",
+ "freigabe": "annahme",
+ "id": "adler-g10-2x-klarlack",
+ "revision": 1,
+ "rolle": "system/unten",
+ "schichten": [
+ {
+ "anzahl": 2,
+ "komponente": {
+ "id": "adler-aduro-legnopur-g10",
+ "revision": 1
+ },
+ "rolle": "klarlack",
+ "verfahren": "spritzen"
+ }
+ ],
+ "unterlagen": []
+ },
+ {
+ "art": "beschichtung",
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "freigabe": "annahme",
+ "hersteller": "ADLER-Werk Lackfabrik, Schwaz",
+ "id": "adler-aduro-legnopur-g10",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "revision": 1,
+ "rolle": "oberflaeche/oben/1-klarlack",
+ "sicherheit": {
+ "hinweise": [
+ "Sicherheitsdatenblatt auf adler-lacke.com nur eingeloggt — beschaffen"
+ ],
+ "schutz": [
+ "Atemschutz A2/P2",
+ "nur gewerbliche Verarbeitung"
+ ]
+ },
+ "typ": "lack",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER TMB 2513 ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "seiten": "S. 1–4",
+ "sha256": "3802082c34744eb165c12d8df2a6d01afca051fc6930961a6b2401edf7152dcc",
+ "typ": "datenblatt",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "sdb",
+ "url": "https://www.adler-lacke.com/at/produkte"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://www.adler-lacke.com/de/service-ratgeber/arbeitsrichtlinien"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "Verarbeitungstemperatur ≥ +10 °C (Aduro) bzw. ≥ +15 °C (Bluefin)",
+ "hohe Luftfeuchte verlängert die Trocknung"
+ ],
+ "schritt": "spritzen",
+ "werkzeuge": [
+ "Becherpistole Düse 1,8 mm, 2–3 bar",
+ "Airless Düse 0,23–0,33 mm, 100–120 bar"
+ ]
+ },
+ {
+ "hinweise": [
+ "≥ 1 Jahr, frostfrei, < 30 °C"
+ ],
+ "schritt": "lagern"
+ }
+ ]
+ },
+ {
+ "art": "beschichtung",
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "freigabe": "annahme",
+ "hersteller": "ADLER-Werk Lackfabrik, Schwaz",
+ "id": "adler-aduro-legnopur-g10",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "revision": 1,
+ "rolle": "oberflaeche/unten/1-klarlack",
+ "sicherheit": {
+ "hinweise": [
+ "Sicherheitsdatenblatt auf adler-lacke.com nur eingeloggt — beschaffen"
+ ],
+ "schutz": [
+ "Atemschutz A2/P2",
+ "nur gewerbliche Verarbeitung"
+ ]
+ },
+ "typ": "lack",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER TMB 2513 ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "seiten": "S. 1–4",
+ "sha256": "3802082c34744eb165c12d8df2a6d01afca051fc6930961a6b2401edf7152dcc",
+ "typ": "datenblatt",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "sdb",
+ "url": "https://www.adler-lacke.com/at/produkte"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://www.adler-lacke.com/de/service-ratgeber/arbeitsrichtlinien"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "Verarbeitungstemperatur ≥ +10 °C (Aduro) bzw. ≥ +15 °C (Bluefin)",
+ "hohe Luftfeuchte verlängert die Trocknung"
+ ],
+ "schritt": "spritzen",
+ "werkzeuge": [
+ "Becherpistole Düse 1,8 mm, 2–3 bar",
+ "Airless Düse 0,23–0,33 mm, 100–120 bar"
+ ]
+ },
+ {
+ "hinweise": [
+ "≥ 1 Jahr, frostfrei, < 30 °C"
+ ],
+ "schritt": "lagern"
+ }
+ ]
+ },
+ {
+ "art": "kante",
+ "artikelnr": "070.4200",
+ "bezeichnung": "Ostermann Furnierkante Eiche europäisch, geschliffen K180, 1,0 × 24 mm (Art. 070.4200)",
+ "freigabe": "annahme",
+ "hersteller": "Ostermann",
+ "id": "ostermann-furnierkante-eiche-1-0",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann Technisches Merkblatt Furnierkanten (01/2020)",
+ "stand": "2020-01-01",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ "revision": 1,
+ "rolle": "kante/vorne",
+ "typ": "furnierkante",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann TM Furnierkanten",
+ "seiten": "S. 1–2",
+ "sha256": "50860b11af6ba61b68d593991c01b5a9ce148dda628a20acf292036eb4d2f250",
+ "typ": "datenblatt",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Kleiberit Infoblatt Kantenverklebung",
+ "seiten": "S. 1–4",
+ "sha256": "45597e449c7307a5c900d6399d0466bf6c983d24176c64ac7af51d79c42834fb",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://legacy.kleiberit.com/fileadmin/Content_new/Infoblaetter/deutsch/Kantenverklebung_D.pdf"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "Kante wie Trägerplatte im Raumklima konditionieren (EGGER)",
+ "Rückseite Primer — verbessert Verankerung, verhindert Austritt von Holzinhaltsstoffen"
+ ],
+ "schritt": "kanten",
+ "werkzeuge": [
+ "Kantenanleimmaschine / BAZ (EVA 190–210 °C)",
+ "Kappsäge, Bündigfräser, Ziehklinge, Schwabbelscheibe"
+ ]
+ },
+ {
+ "hinweise": [
+ "vollflächig waagerecht, 20 °C, 50–60 % rF, kein UV"
+ ],
+ "schritt": "lagern"
+ },
+ {
+ "hinweise": [
+ "lackierfähig geschliffen K180 — kann sofort lackiert werden"
+ ],
+ "schritt": "schleifen"
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "bezeichnung": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
+ "freigabe": "annahme",
+ "hersteller": "Jowat",
+ "id": "jowatherm-280-30-eva",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Jowat Produktinformation Jowatherm 280.30"
+ },
+ "revision": 1,
+ "rolle": "klebstoff/kante/vorne",
+ "sicherheit": {
+ "hinweise": [
+ "heißer Schmelzkleber: Verbrennungsgefahr; SDB Jowat beschaffen"
+ ]
+ },
+ "typ": "eva_schmelz",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Jowat TDS Jowatherm 280.30/31 (Kante, Stand 12/25)",
+ "seiten": "S. 1–3",
+ "sha256": "68aafb838580eb574888ffa160f3d38e0b3d495b80241ae1d06b55a5399be45c",
+ "stand": "2025-12-01",
+ "typ": "datenblatt",
+ "url": "https://d3files.jowat-cloud.com/technical-data-sheets-v2/280.30/31/TD28030-28031-Kante_German.PDF"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "erste Andruckrolle regelmäßig prüfen",
+ "Kante und Platte im Raumklima konditionieren"
+ ],
+ "schritt": "kanten",
+ "werkzeuge": [
+ "Kantenanleimmaschine / BAZ (Durchlauf, Stationär, Softforming)"
+ ]
+ }
+ ]
+ }
+ ],
+ "resolve_manifest_sha256": "bc29b2392ef8e7e67c9c30da8de8f1fa380828b1036a45579db8fa3ffbd3d3fe",
+ "schema": "werkstoff/karte.schema.json",
+ "unterlagen_ohne_verweis": [
+ {
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "grund": "kein Link hinterlegt",
+ "id": "ponal-flaechenleim-pvac",
+ "rolle": "klebstoff/belag/oben",
+ "typ": "datenblatt"
+ },
+ {
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "grund": "kein Link hinterlegt",
+ "id": "ponal-flaechenleim-pvac",
+ "rolle": "klebstoff/belag/unten",
+ "typ": "datenblatt"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "grund": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "id": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack",
+ "typ": "sdb"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "grund": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "id": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack",
+ "typ": "verarbeitungsrichtlinie"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "grund": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "id": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack",
+ "typ": "sdb"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "grund": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "id": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack",
+ "typ": "verarbeitungsrichtlinie"
+ }
+ ],
+ "verbrauch": {
+ "ausgeschlossen": [],
+ "flaeche_m2": 0.3119,
+ "freigabe": "annahme",
+ "positionen": [
+ {
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "egger-spanplatte-p2-19",
+ "revision": 1,
+ "rolle": "traeger",
+ "wert": 0.3119
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/oben",
+ "wert": 0.3119
+ },
+ {
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "ponal-flaechenleim-pvac",
+ "revision": 1,
+ "rolle": "leim/oben",
+ "wert": 37.4
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/unten",
+ "wert": 0.3119
+ },
+ {
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "ponal-flaechenleim-pvac",
+ "revision": 1,
+ "rolle": "leim/unten",
+ "wert": 37.4
+ },
+ {
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "einheit": "g",
+ "freigabe": "belegt",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/oben/1-klarlack/netto",
+ "wert": 62.4
+ },
+ {
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/oben/1-klarlack/brutto",
+ "wert": 96.0
+ },
+ {
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "einheit": "g",
+ "freigabe": "belegt",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/unten/1-klarlack/netto",
+ "wert": 62.4
+ },
+ {
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/unten/1-klarlack/brutto",
+ "wert": 96.0
+ },
+ {
+ "bezeichnung": "Ostermann Furnierkante Eiche europäisch, geschliffen K180, 1,0 × 24 mm (Art. 070.4200)",
+ "einheit": "lfm",
+ "freigabe": "annahme",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "revision": 1,
+ "rolle": "kante/vorne",
+ "wert": 0.566
+ },
+ {
+ "bezeichnung": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "jowatherm-280-30-eva",
+ "revision": 1,
+ "rolle": "kleber/vorne",
+ "wert": 3.4
+ }
+ ],
+ "werkstueck": "teil_beispielbo0unten"
+ },
+ "videos": [],
+ "werkstueck": {
+ "anzahl": 1,
+ "bezeichnung": "Boden Unten",
+ "faserrichtung": "laengs",
+ "flaeche_m2": 0.3119,
+ "id": "teil_beispielbo0unten",
+ "kanten": {},
+ "kontur": {
+ "breite": 555.0,
+ "laenge": 562.0,
+ "typ": "rechteck"
+ }
+ }
+}
diff --git a/apps/tischler/public/werkstoff-bundle/karten/seite-links-890x555.json b/apps/tischler/public/werkstoff-bundle/karten/teil_beispielse0links.json
similarity index 97%
rename from apps/tischler/public/werkstoff-bundle/karten/seite-links-890x555.json
rename to apps/tischler/public/werkstoff-bundle/karten/teil_beispielse0links.json
index aa95e50..c5bba10 100644
--- a/apps/tischler/public/werkstoff-bundle/karten/seite-links-890x555.json
+++ b/apps/tischler/public/werkstoff-bundle/karten/teil_beispielse0links.json
@@ -339,7 +339,7 @@
],
"wert": 14.352
},
- "karte_sha256": "6be589ab4182b84dc6e340848520ed52ef2d6f2240055290834174f04b745a34",
+ "karte_sha256": "81ec973b54fc8d99450aa4a286fc8833ca73a63fdd26d0e7478d42ef5c2352e4",
"karteversion": 1,
"komponenten": [
{
@@ -878,7 +878,7 @@
]
}
],
- "resolve_manifest_sha256": "803aa37c60bfdb9aed0f79ba309c42c59c5fea4a5a8f94edb3e7e02463d89dee",
+ "resolve_manifest_sha256": "e7ed33f6c8b864ead3d8d2406075ace94154298b08835872ab5da71705f5c7c8",
"schema": "werkstoff/karte.schema.json",
"unterlagen_ohne_verweis": [
{
@@ -926,7 +926,7 @@
],
"verbrauch": {
"ausgeschlossen": [],
- "flaeche_m2": 0.9879,
+ "flaeche_m2": 0.494,
"freigabe": "annahme",
"positionen": [
{
@@ -936,7 +936,7 @@
"komponente": "egger-spanplatte-p2-19",
"revision": 1,
"rolle": "traeger",
- "wert": 0.9879
+ "wert": 0.494
},
{
"bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
@@ -945,7 +945,7 @@
"komponente": "furnier-eiche-0-9-gemessert",
"revision": 1,
"rolle": "belag/oben",
- "wert": 0.9879
+ "wert": 0.494
},
{
"bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
@@ -954,7 +954,7 @@
"komponente": "ponal-flaechenleim-pvac",
"revision": 1,
"rolle": "leim/oben",
- "wert": 118.5
+ "wert": 59.3
},
{
"bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
@@ -963,7 +963,7 @@
"komponente": "furnier-eiche-0-9-gemessert",
"revision": 1,
"rolle": "belag/unten",
- "wert": 0.9879
+ "wert": 0.494
},
{
"bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
@@ -972,7 +972,7 @@
"komponente": "ponal-flaechenleim-pvac",
"revision": 1,
"rolle": "leim/unten",
- "wert": 118.5
+ "wert": 59.3
},
{
"bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
@@ -981,7 +981,7 @@
"komponente": "adler-aduro-legnopur-g10",
"revision": 1,
"rolle": "oberflaeche/oben/1-klarlack/netto",
- "wert": 197.6
+ "wert": 98.8
},
{
"bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
@@ -990,7 +990,7 @@
"komponente": "adler-aduro-legnopur-g10",
"revision": 1,
"rolle": "oberflaeche/oben/1-klarlack/brutto",
- "wert": 304.0
+ "wert": 152.0
},
{
"bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
@@ -999,7 +999,7 @@
"komponente": "adler-aduro-legnopur-g10",
"revision": 1,
"rolle": "oberflaeche/unten/1-klarlack/netto",
- "wert": 197.6
+ "wert": 98.8
},
{
"bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
@@ -1008,7 +1008,7 @@
"komponente": "adler-aduro-legnopur-g10",
"revision": 1,
"rolle": "oberflaeche/unten/1-klarlack/brutto",
- "wert": 304.0
+ "wert": 152.0
},
{
"bezeichnung": "Ostermann Furnierkante Eiche europäisch, geschliffen K180, 1,0 × 24 mm (Art. 070.4200)",
@@ -1017,7 +1017,7 @@
"komponente": "ostermann-furnierkante-eiche-1-0",
"revision": 1,
"rolle": "kante/vorne",
- "wert": 1.788
+ "wert": 0.894
},
{
"bezeichnung": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
@@ -1026,24 +1026,19 @@
"komponente": "jowatherm-280-30-eva",
"revision": 1,
"rolle": "kleber/vorne",
- "wert": 10.7
+ "wert": 5.4
}
],
- "werkstueck": "seite-links-890x555"
+ "werkstueck": "teil_beispielse0links"
},
"videos": [],
"werkstueck": {
- "anzahl": 2,
- "bezeichnung": "Seite links (Bernhards Korpus 890 × 555)",
+ "anzahl": 1,
+ "bezeichnung": "Seite Links",
"faserrichtung": "laengs",
- "flaeche_m2": 0.9879,
- "id": "seite-links-890x555",
- "kanten": {
- "vorne": {
- "id": "ostermann-furnierkante-eiche-1-0",
- "revision": 1
- }
- },
+ "flaeche_m2": 0.494,
+ "id": "teil_beispielse0links",
+ "kanten": {},
"kontur": {
"breite": 555.0,
"laenge": 890.0,
diff --git a/apps/tischler/public/werkstoff-bundle/karten/teil_beispielse0rechts.json b/apps/tischler/public/werkstoff-bundle/karten/teil_beispielse0rechts.json
new file mode 100644
index 0000000..9e19512
--- /dev/null
+++ b/apps/tischler/public/werkstoff-bundle/karten/teil_beispielse0rechts.json
@@ -0,0 +1,1048 @@
+{
+ "arbeitsfolge": [
+ {
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "nr": 1,
+ "parameter": {
+ "auftrag_g_m2": 120.0,
+ "beanspruchungsgruppe": "D1/D2 (EN 204)",
+ "faserrichtung": {
+ "oben": "laengs",
+ "unten": "laengs"
+ },
+ "holzfeuchte_max_prozent": "12 %",
+ "holzfeuchte_min_prozent": "8 %",
+ "klebstoff": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "klebstoff_quelle": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "leim": "Arbeitsannahme PVAc (Ponal Flächenleim, Kaltpresse) — Alternative UF/Pressal (Heißpresse): Bernhard entscheidet",
+ "offene_zeit_min": 15.0,
+ "pressdruck_n_cm2": 20.0,
+ "presstemperatur_c": 60.0,
+ "presszeit_min": 4.0
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "furnieren",
+ "werkzeuge": [
+ "Furnierpresse (Kalt- oder Heißpresse)",
+ "Leimauftragswalze — Eiche + blankes Metall = Blauverfärbung (Gerbsäure)",
+ "Leimauftragswalze (kein blankes Metall bei Eiche — Blauverfärbung)",
+ "Kaltpresse / Furnierpresse ≤ 60 °C"
+ ]
+ },
+ {
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "nr": 2,
+ "parameter": {
+ "dicke_mm": 1.0,
+ "kantenart": "furnierkante",
+ "kleber": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
+ "temperatur_c": 190.0
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann Technisches Merkblatt Furnierkanten (01/2020)",
+ "stand": "2020-01-01",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ "regel": "kante_vor_nach_belag",
+ "seiten": [
+ "vorne"
+ ],
+ "sicherheit": [],
+ "typ": "kante",
+ "werkzeuge": [
+ "Kantenanleimmaschine / BAZ (EVA 190–210 °C)",
+ "Kappsäge, Bündigfräser, Ziehklinge, Schwabbelscheibe"
+ ]
+ },
+ {
+ "nr": 3,
+ "parameter": {
+ "korn_max": 180.0,
+ "vor": "beschichtung"
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "regel": "schleifgrenze_belag",
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "schleifen",
+ "werkzeuge": []
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 4,
+ "parameter": {
+ "auftrag": 1,
+ "auftragsmenge_g_m2": 100.0,
+ "glanzgrad": "10 GU",
+ "mischung.haerter": "10:1 PUR-Hardener 8419",
+ "mischung.topfzeit_h": "8 h",
+ "mischung.verduennung_prozent": "10 %",
+ "von": 2
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "rolle": "klarlack",
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [
+ "Atemschutz A2/P2",
+ "nur gewerbliche Verarbeitung"
+ ],
+ "typ": "auftragen",
+ "verfahren": "spritzen",
+ "werkzeuge": [
+ "Becherpistole Düse 1,8 mm, 2–3 bar",
+ "Airless Düse 0,23–0,33 mm, 100–120 bar"
+ ]
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 5,
+ "parameter": {
+ "endhaerte_min": "2880 min",
+ "schleifbar_min": "120 min",
+ "stapelbar_min": "720 min"
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "trocknen",
+ "werkzeuge": []
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 6,
+ "parameter": {
+ "korn": 320.0
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "regel": "zwischenschliff_pflicht",
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "zwischenschliff",
+ "werkzeuge": []
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 7,
+ "parameter": {
+ "auftrag": 2,
+ "auftragsmenge_g_m2": 100.0,
+ "glanzgrad": "10 GU",
+ "mischung.haerter": "10:1 PUR-Hardener 8419",
+ "mischung.topfzeit_h": "8 h",
+ "mischung.verduennung_prozent": "10 %",
+ "von": 2
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "rolle": "klarlack",
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [
+ "Atemschutz A2/P2",
+ "nur gewerbliche Verarbeitung"
+ ],
+ "typ": "auftragen",
+ "verfahren": "spritzen",
+ "werkzeuge": [
+ "Becherpistole Düse 1,8 mm, 2–3 bar",
+ "Airless Düse 0,23–0,33 mm, 100–120 bar"
+ ]
+ },
+ {
+ "komponente": "adler-aduro-legnopur-g10",
+ "nr": 8,
+ "parameter": {
+ "endhaerte_min": "2880 min",
+ "schleifbar_min": "120 min",
+ "stapelbar_min": "720 min"
+ },
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "seiten": [
+ "oben",
+ "unten"
+ ],
+ "sicherheit": [],
+ "typ": "trocknen",
+ "werkzeuge": []
+ }
+ ],
+ "aufbau": {
+ "bezeichnung": "EGGER Spanplatte P2 19 + Furnier Eiche 0,9 beidseitig + 2× ADLER G10 beidseitig + Furnierkante Eiche vorne",
+ "id": "eiche-furniert-p2-19-g10",
+ "revision": 1
+ },
+ "brand": {
+ "fehlend": [
+ "Prüfzeugnis (EN 13501-1) für den Aufbau eiche-furniert-p2-19-g10: Träger + Belag + Oberflächensystem als geprüftes System"
+ ],
+ "hinweise": [
+ {
+ "dok_ref": "EGGER TD-181 / Brandverhalten-Merkblatt",
+ "freigabe": "belegt",
+ "klasse": "D-s2,d0",
+ "komponente": "egger-spanplatte-p2-19",
+ "nachweistyp": "einzelbaustoff"
+ },
+ {
+ "freigabe": "annahme",
+ "klasse": "E",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "nachweistyp": "einzelbaustoff"
+ }
+ ],
+ "status": "nicht_nachgewiesen"
+ },
+ "dicke_mm": {
+ "ausgeschlossen": [
+ {
+ "grund": "Trockenschichtdicke nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/oben/1-klarlack"
+ },
+ {
+ "grund": "Trockenschichtdicke nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/unten/1-klarlack"
+ }
+ ],
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "maximum": 21.5,
+ "minimum": 20.1,
+ "nominal": 20.8,
+ "positionen": [
+ {
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "komponente": "egger-spanplatte-p2-19",
+ "revision": 1,
+ "rolle": "traeger",
+ "wert": 19.0
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "mm",
+ "freigabe": "meister",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/oben",
+ "wert": 0.9
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "mm",
+ "freigabe": "meister",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/unten",
+ "wert": 0.9
+ }
+ ]
+ },
+ "dokumente_hinweis": "Unterlagen sind Verweise (URL, Seite, Hash) auf die Dokumente des Herstellers — nie deren Inhalt. Speichern, Durchsuchen oder Offline-Kopien erst nach Zustimmung des Herstellers.",
+ "freigabe": "annahme",
+ "gewicht_kg_m2": {
+ "ausgeschlossen": [
+ {
+ "grund": "Trockenmasse der Beschichtung nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche"
+ },
+ {
+ "grund": "Kante ist je lfm — braucht das Werkstück (siehe verbrauch)",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "revision": 1,
+ "rolle": "kante/vorne"
+ }
+ ],
+ "einheit": "kg/m2",
+ "freigabe": "annahme",
+ "positionen": [
+ {
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "einheit": "kg/m2",
+ "freigabe": "annahme",
+ "komponente": "egger-spanplatte-p2-19",
+ "revision": 1,
+ "rolle": "traeger",
+ "wert": 13.11
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "kg/m2",
+ "freigabe": "annahme",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/oben",
+ "wert": 0.621
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "kg/m2",
+ "freigabe": "annahme",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/unten",
+ "wert": 0.621
+ }
+ ],
+ "wert": 14.352
+ },
+ "karte_sha256": "09d7fb7407987a229fce61d97146b8ff2dad2b316711b04029cd327ccaf61518",
+ "karteversion": 1,
+ "komponenten": [
+ {
+ "art": "traeger",
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "freigabe": "annahme",
+ "hersteller": "EGGER",
+ "id": "egger-spanplatte-p2-19",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Technisches Datenblatt TD-181 (E1E05 TSCA P2 CE), Rev. 08.04.2026",
+ "seite": "S. 1",
+ "stand": "2026-04-08",
+ "url": "https://binary.egger.link/pis/978585f4-777c-45bf-96bb-3cfa40df2bae/Technical_datasheet_E1E05_TSCA_P2_CE_TD-181_Deutsch"
+ },
+ "revision": 1,
+ "rolle": "traeger",
+ "typ": "spanplatte",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER TD-181 Technisches Datenblatt E1E05 TSCA P2 CE",
+ "seiten": "S. 1–2",
+ "sha256": "1b0d66099daba0c8159b0100bb5a5cd5015cb5d0240ebc2bf266fb4ff12d82e0",
+ "stand": "2026-04-08",
+ "typ": "datenblatt",
+ "url": "https://binary.egger.link/pis/978585f4-777c-45bf-96bb-3cfa40df2bae/Technical_datasheet_E1E05_TSCA_P2_CE_TD-181_Deutsch"
+ },
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Verarbeitungshinweise Dekorative Platten und Trägerplatten",
+ "seiten": "S. 1–20",
+ "sha256": "e9fdcbea5e943a199f94178d89ffa83d20224b1cf25758fd229b6c8fe195a574",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://binary.egger.link/pis/4a389dc5-b778-426f-ada5-03d2c6e7eee3/Verarbeitungshinweise_Dekorative_Platten_und_Traegerplatten_Deutsch"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "Platten wie Kantenmaterial im Raumklima konditionieren (EGGER Verarbeitungshinweise S. 10)"
+ ],
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Verarbeitungshinweise Dekorative Platten und Trägerplatten",
+ "url": "https://binary.egger.link/pis/4a389dc5-b778-426f-ada5-03d2c6e7eee3/Verarbeitungshinweise_Dekorative_Platten_und_Traegerplatten_Deutsch"
+ },
+ "schritt": "lagern"
+ },
+ {
+ "hinweise": [
+ "EGGER-Werkzeugempfehlung ist ein separates PDF — noch nicht beschafft (Bernhards Kundenkonto)"
+ ],
+ "schritt": "saegen"
+ }
+ ]
+ },
+ {
+ "art": "belag",
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "freigabe": "annahme",
+ "id": "furnier-eiche-0-9-gemessert",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "revision": 1,
+ "rolle": "belag/oben",
+ "typ": "furnier",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN — Furnier im Innenausbau (Verarbeitung)",
+ "seiten": "S. 1–62",
+ "sha256": "3ab3f0fd735dae1a5a5b543e503f93b73f41effb273d08b65948f54b7be6578c",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "kühl, nicht in geheizten Räumen, möglichst wenig Tageslicht (ÜK4 S. 9)",
+ "Gegenzug: Rückseite gleiche Dicke (ÜK4 S. 9)"
+ ],
+ "parameter": {
+ "rf_max_prozent": {
+ "einheit": "%",
+ "freigabe": "belegt",
+ "wert": 70
+ },
+ "rf_min_prozent": {
+ "einheit": "%",
+ "freigabe": "belegt",
+ "wert": 60
+ }
+ },
+ "schritt": "lagern"
+ },
+ {
+ "hinweise": [
+ "Eiche: Probefurnierung mit PVAc (Ponal); sonst Pressal (UF)",
+ "max. 24 h zwischen Schliff und Lack"
+ ],
+ "schritt": "pressen",
+ "werkzeuge": [
+ "Furnierpresse (Kalt- oder Heißpresse)",
+ "Leimauftragswalze — Eiche + blankes Metall = Blauverfärbung (Gerbsäure)"
+ ]
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "freigabe": "annahme",
+ "hersteller": "Henkel (Ponal)",
+ "id": "ponal-flaechenleim-pvac",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "seite": "S. 1–2"
+ },
+ "revision": 1,
+ "rolle": "klebstoff/belag/oben",
+ "sicherheit": {
+ "hinweise": [
+ "Sicherheitsdatenblatt Henkel — beschaffen (nicht recherchiert)"
+ ]
+ },
+ "typ": "pvac",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "typ": "datenblatt"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "einseitig auf die Trägerplatte auftragen",
+ "Einlege-/Schließzeit kurz halten"
+ ],
+ "schritt": "pressen",
+ "werkzeuge": [
+ "Leimauftragswalze (kein blankes Metall bei Eiche — Blauverfärbung)",
+ "Kaltpresse / Furnierpresse ≤ 60 °C"
+ ]
+ },
+ {
+ "hinweise": [
+ "frostfrei lagern (Dispersionsleim)"
+ ],
+ "schritt": "lagern"
+ }
+ ]
+ },
+ {
+ "art": "belag",
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "freigabe": "annahme",
+ "id": "furnier-eiche-0-9-gemessert",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "revision": 1,
+ "rolle": "belag/unten",
+ "typ": "furnier",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN — Furnier im Innenausbau (Verarbeitung)",
+ "seiten": "S. 1–62",
+ "sha256": "3ab3f0fd735dae1a5a5b543e503f93b73f41effb273d08b65948f54b7be6578c",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "kühl, nicht in geheizten Räumen, möglichst wenig Tageslicht (ÜK4 S. 9)",
+ "Gegenzug: Rückseite gleiche Dicke (ÜK4 S. 9)"
+ ],
+ "parameter": {
+ "rf_max_prozent": {
+ "einheit": "%",
+ "freigabe": "belegt",
+ "wert": 70
+ },
+ "rf_min_prozent": {
+ "einheit": "%",
+ "freigabe": "belegt",
+ "wert": 60
+ }
+ },
+ "schritt": "lagern"
+ },
+ {
+ "hinweise": [
+ "Eiche: Probefurnierung mit PVAc (Ponal); sonst Pressal (UF)",
+ "max. 24 h zwischen Schliff und Lack"
+ ],
+ "schritt": "pressen",
+ "werkzeuge": [
+ "Furnierpresse (Kalt- oder Heißpresse)",
+ "Leimauftragswalze — Eiche + blankes Metall = Blauverfärbung (Gerbsäure)"
+ ]
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "freigabe": "annahme",
+ "hersteller": "Henkel (Ponal)",
+ "id": "ponal-flaechenleim-pvac",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "seite": "S. 1–2"
+ },
+ "revision": 1,
+ "rolle": "klebstoff/belag/unten",
+ "sicherheit": {
+ "hinweise": [
+ "Sicherheitsdatenblatt Henkel — beschaffen (nicht recherchiert)"
+ ]
+ },
+ "typ": "pvac",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "typ": "datenblatt"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "einseitig auf die Trägerplatte auftragen",
+ "Einlege-/Schließzeit kurz halten"
+ ],
+ "schritt": "pressen",
+ "werkzeuge": [
+ "Leimauftragswalze (kein blankes Metall bei Eiche — Blauverfärbung)",
+ "Kaltpresse / Furnierpresse ≤ 60 °C"
+ ]
+ },
+ {
+ "hinweise": [
+ "frostfrei lagern (Dispersionsleim)"
+ ],
+ "schritt": "lagern"
+ }
+ ]
+ },
+ {
+ "art": "system",
+ "bezeichnung": "2× ADLER G10 Klarlack (1× Grund + Zwischenschliff + 1× Deck, gleiches Produkt) — Produktwahl offen: Legnopur (Standard) / Antiscratch / Bluefin Unistar",
+ "freigabe": "annahme",
+ "id": "adler-g10-2x-klarlack",
+ "revision": 1,
+ "rolle": "system/oben",
+ "schichten": [
+ {
+ "anzahl": 2,
+ "komponente": {
+ "id": "adler-aduro-legnopur-g10",
+ "revision": 1
+ },
+ "rolle": "klarlack",
+ "verfahren": "spritzen"
+ }
+ ],
+ "unterlagen": []
+ },
+ {
+ "art": "system",
+ "bezeichnung": "2× ADLER G10 Klarlack (1× Grund + Zwischenschliff + 1× Deck, gleiches Produkt) — Produktwahl offen: Legnopur (Standard) / Antiscratch / Bluefin Unistar",
+ "freigabe": "annahme",
+ "id": "adler-g10-2x-klarlack",
+ "revision": 1,
+ "rolle": "system/unten",
+ "schichten": [
+ {
+ "anzahl": 2,
+ "komponente": {
+ "id": "adler-aduro-legnopur-g10",
+ "revision": 1
+ },
+ "rolle": "klarlack",
+ "verfahren": "spritzen"
+ }
+ ],
+ "unterlagen": []
+ },
+ {
+ "art": "beschichtung",
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "freigabe": "annahme",
+ "hersteller": "ADLER-Werk Lackfabrik, Schwaz",
+ "id": "adler-aduro-legnopur-g10",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "revision": 1,
+ "rolle": "oberflaeche/oben/1-klarlack",
+ "sicherheit": {
+ "hinweise": [
+ "Sicherheitsdatenblatt auf adler-lacke.com nur eingeloggt — beschaffen"
+ ],
+ "schutz": [
+ "Atemschutz A2/P2",
+ "nur gewerbliche Verarbeitung"
+ ]
+ },
+ "typ": "lack",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER TMB 2513 ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "seiten": "S. 1–4",
+ "sha256": "3802082c34744eb165c12d8df2a6d01afca051fc6930961a6b2401edf7152dcc",
+ "typ": "datenblatt",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "sdb",
+ "url": "https://www.adler-lacke.com/at/produkte"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://www.adler-lacke.com/de/service-ratgeber/arbeitsrichtlinien"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "Verarbeitungstemperatur ≥ +10 °C (Aduro) bzw. ≥ +15 °C (Bluefin)",
+ "hohe Luftfeuchte verlängert die Trocknung"
+ ],
+ "schritt": "spritzen",
+ "werkzeuge": [
+ "Becherpistole Düse 1,8 mm, 2–3 bar",
+ "Airless Düse 0,23–0,33 mm, 100–120 bar"
+ ]
+ },
+ {
+ "hinweise": [
+ "≥ 1 Jahr, frostfrei, < 30 °C"
+ ],
+ "schritt": "lagern"
+ }
+ ]
+ },
+ {
+ "art": "beschichtung",
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "freigabe": "annahme",
+ "hersteller": "ADLER-Werk Lackfabrik, Schwaz",
+ "id": "adler-aduro-legnopur-g10",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "revision": 1,
+ "rolle": "oberflaeche/unten/1-klarlack",
+ "sicherheit": {
+ "hinweise": [
+ "Sicherheitsdatenblatt auf adler-lacke.com nur eingeloggt — beschaffen"
+ ],
+ "schutz": [
+ "Atemschutz A2/P2",
+ "nur gewerbliche Verarbeitung"
+ ]
+ },
+ "typ": "lack",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER TMB 2513 ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "seiten": "S. 1–4",
+ "sha256": "3802082c34744eb165c12d8df2a6d01afca051fc6930961a6b2401edf7152dcc",
+ "typ": "datenblatt",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "sdb",
+ "url": "https://www.adler-lacke.com/at/produkte"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://www.adler-lacke.com/de/service-ratgeber/arbeitsrichtlinien"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "Verarbeitungstemperatur ≥ +10 °C (Aduro) bzw. ≥ +15 °C (Bluefin)",
+ "hohe Luftfeuchte verlängert die Trocknung"
+ ],
+ "schritt": "spritzen",
+ "werkzeuge": [
+ "Becherpistole Düse 1,8 mm, 2–3 bar",
+ "Airless Düse 0,23–0,33 mm, 100–120 bar"
+ ]
+ },
+ {
+ "hinweise": [
+ "≥ 1 Jahr, frostfrei, < 30 °C"
+ ],
+ "schritt": "lagern"
+ }
+ ]
+ },
+ {
+ "art": "kante",
+ "artikelnr": "070.4200",
+ "bezeichnung": "Ostermann Furnierkante Eiche europäisch, geschliffen K180, 1,0 × 24 mm (Art. 070.4200)",
+ "freigabe": "annahme",
+ "hersteller": "Ostermann",
+ "id": "ostermann-furnierkante-eiche-1-0",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann Technisches Merkblatt Furnierkanten (01/2020)",
+ "stand": "2020-01-01",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ "revision": 1,
+ "rolle": "kante/vorne",
+ "typ": "furnierkante",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann TM Furnierkanten",
+ "seiten": "S. 1–2",
+ "sha256": "50860b11af6ba61b68d593991c01b5a9ce148dda628a20acf292036eb4d2f250",
+ "typ": "datenblatt",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Kleiberit Infoblatt Kantenverklebung",
+ "seiten": "S. 1–4",
+ "sha256": "45597e449c7307a5c900d6399d0466bf6c983d24176c64ac7af51d79c42834fb",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://legacy.kleiberit.com/fileadmin/Content_new/Infoblaetter/deutsch/Kantenverklebung_D.pdf"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "Kante wie Trägerplatte im Raumklima konditionieren (EGGER)",
+ "Rückseite Primer — verbessert Verankerung, verhindert Austritt von Holzinhaltsstoffen"
+ ],
+ "schritt": "kanten",
+ "werkzeuge": [
+ "Kantenanleimmaschine / BAZ (EVA 190–210 °C)",
+ "Kappsäge, Bündigfräser, Ziehklinge, Schwabbelscheibe"
+ ]
+ },
+ {
+ "hinweise": [
+ "vollflächig waagerecht, 20 °C, 50–60 % rF, kein UV"
+ ],
+ "schritt": "lagern"
+ },
+ {
+ "hinweise": [
+ "lackierfähig geschliffen K180 — kann sofort lackiert werden"
+ ],
+ "schritt": "schleifen"
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "bezeichnung": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
+ "freigabe": "annahme",
+ "hersteller": "Jowat",
+ "id": "jowatherm-280-30-eva",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Jowat Produktinformation Jowatherm 280.30"
+ },
+ "revision": 1,
+ "rolle": "klebstoff/kante/vorne",
+ "sicherheit": {
+ "hinweise": [
+ "heißer Schmelzkleber: Verbrennungsgefahr; SDB Jowat beschaffen"
+ ]
+ },
+ "typ": "eva_schmelz",
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Jowat TDS Jowatherm 280.30/31 (Kante, Stand 12/25)",
+ "seiten": "S. 1–3",
+ "sha256": "68aafb838580eb574888ffa160f3d38e0b3d495b80241ae1d06b55a5399be45c",
+ "stand": "2025-12-01",
+ "typ": "datenblatt",
+ "url": "https://d3files.jowat-cloud.com/technical-data-sheets-v2/280.30/31/TD28030-28031-Kante_German.PDF"
+ }
+ ],
+ "verarbeitung": [
+ {
+ "hinweise": [
+ "erste Andruckrolle regelmäßig prüfen",
+ "Kante und Platte im Raumklima konditionieren"
+ ],
+ "schritt": "kanten",
+ "werkzeuge": [
+ "Kantenanleimmaschine / BAZ (Durchlauf, Stationär, Softforming)"
+ ]
+ }
+ ]
+ }
+ ],
+ "resolve_manifest_sha256": "7113369780cade95bc8252b687e072666321ac133f22d528498055612728e9a6",
+ "schema": "werkstoff/karte.schema.json",
+ "unterlagen_ohne_verweis": [
+ {
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "grund": "kein Link hinterlegt",
+ "id": "ponal-flaechenleim-pvac",
+ "rolle": "klebstoff/belag/oben",
+ "typ": "datenblatt"
+ },
+ {
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "grund": "kein Link hinterlegt",
+ "id": "ponal-flaechenleim-pvac",
+ "rolle": "klebstoff/belag/unten",
+ "typ": "datenblatt"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "grund": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "id": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack",
+ "typ": "sdb"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "grund": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "id": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack",
+ "typ": "verarbeitungsrichtlinie"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "grund": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "id": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack",
+ "typ": "sdb"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "grund": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "id": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack",
+ "typ": "verarbeitungsrichtlinie"
+ }
+ ],
+ "verbrauch": {
+ "ausgeschlossen": [],
+ "flaeche_m2": 0.494,
+ "freigabe": "annahme",
+ "positionen": [
+ {
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "egger-spanplatte-p2-19",
+ "revision": 1,
+ "rolle": "traeger",
+ "wert": 0.494
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/oben",
+ "wert": 0.494
+ },
+ {
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "ponal-flaechenleim-pvac",
+ "revision": 1,
+ "rolle": "leim/oben",
+ "wert": 59.3
+ },
+ {
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "revision": 1,
+ "rolle": "belag/unten",
+ "wert": 0.494
+ },
+ {
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "ponal-flaechenleim-pvac",
+ "revision": 1,
+ "rolle": "leim/unten",
+ "wert": 59.3
+ },
+ {
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "einheit": "g",
+ "freigabe": "belegt",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/oben/1-klarlack/netto",
+ "wert": 98.8
+ },
+ {
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/oben/1-klarlack/brutto",
+ "wert": 152.0
+ },
+ {
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "einheit": "g",
+ "freigabe": "belegt",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/unten/1-klarlack/netto",
+ "wert": 98.8
+ },
+ {
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "adler-aduro-legnopur-g10",
+ "revision": 1,
+ "rolle": "oberflaeche/unten/1-klarlack/brutto",
+ "wert": 152.0
+ },
+ {
+ "bezeichnung": "Ostermann Furnierkante Eiche europäisch, geschliffen K180, 1,0 × 24 mm (Art. 070.4200)",
+ "einheit": "lfm",
+ "freigabe": "annahme",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "revision": 1,
+ "rolle": "kante/vorne",
+ "wert": 0.894
+ },
+ {
+ "bezeichnung": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "jowatherm-280-30-eva",
+ "revision": 1,
+ "rolle": "kleber/vorne",
+ "wert": 5.4
+ }
+ ],
+ "werkstueck": "teil_beispielse0rechts"
+ },
+ "videos": [],
+ "werkstueck": {
+ "anzahl": 1,
+ "bezeichnung": "Seite Rechts",
+ "faserrichtung": "laengs",
+ "flaeche_m2": 0.494,
+ "id": "teil_beispielse0rechts",
+ "kanten": {},
+ "kontur": {
+ "breite": 555.0,
+ "laenge": 890.0,
+ "typ": "rechteck"
+ }
+ }
+}
diff --git a/apps/tischler/public/werkstoff-bundle/manifeste/boden-562x555.json b/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielbo0oben.json
similarity index 96%
rename from apps/tischler/public/werkstoff-bundle/manifeste/boden-562x555.json
rename to apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielbo0oben.json
index 176fc5c..e697c46 100644
--- a/apps/tischler/public/werkstoff-bundle/manifeste/boden-562x555.json
+++ b/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielbo0oben.json
@@ -33,7 +33,7 @@
"min": 20.1,
"nominal": 20.8
},
- "flaeche_m2": 0.6238,
+ "flaeche_m2": 0.3119,
"gewicht_kg_m2": {
"freigabe": "annahme",
"wert": 14.352
@@ -44,77 +44,77 @@
"freigabe": "belegt",
"komponente": "egger-spanplatte-p2-19",
"rolle": "traeger",
- "wert": 0.6238
+ "wert": 0.3119
},
{
"einheit": "m2",
"freigabe": "belegt",
"komponente": "furnier-eiche-0-9-gemessert",
"rolle": "belag/oben",
- "wert": 0.6238
+ "wert": 0.3119
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "ponal-flaechenleim-pvac",
"rolle": "leim/oben",
- "wert": 74.9
+ "wert": 37.4
},
{
"einheit": "m2",
"freigabe": "belegt",
"komponente": "furnier-eiche-0-9-gemessert",
"rolle": "belag/unten",
- "wert": 0.6238
+ "wert": 0.3119
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "ponal-flaechenleim-pvac",
"rolle": "leim/unten",
- "wert": 74.9
+ "wert": 37.4
},
{
"einheit": "g",
"freigabe": "belegt",
"komponente": "adler-aduro-legnopur-g10",
"rolle": "oberflaeche/oben/1-klarlack/netto",
- "wert": 124.8
+ "wert": 62.4
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "adler-aduro-legnopur-g10",
"rolle": "oberflaeche/oben/1-klarlack/brutto",
- "wert": 191.9
+ "wert": 96.0
},
{
"einheit": "g",
"freigabe": "belegt",
"komponente": "adler-aduro-legnopur-g10",
"rolle": "oberflaeche/unten/1-klarlack/netto",
- "wert": 124.8
+ "wert": 62.4
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "adler-aduro-legnopur-g10",
"rolle": "oberflaeche/unten/1-klarlack/brutto",
- "wert": 191.9
+ "wert": 96.0
},
{
"einheit": "lfm",
"freigabe": "annahme",
"komponente": "ostermann-furnierkante-eiche-1-0",
"rolle": "kante/vorne",
- "wert": 1.132
+ "wert": 0.566
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "jowatherm-280-30-eva",
"rolle": "kleber/vorne",
- "wert": 6.8
+ "wert": 3.4
}
]
},
@@ -385,7 +385,7 @@
]
}
],
- "resolve_manifest_sha256": "2539300205de77f8ecec3f5a40d525829039f7309c53d9002b0ea34784fabc49",
+ "resolve_manifest_sha256": "129f81aa35dc2ea0802a44cbf7e8671235b24fc0fb620772dee94f5a44d1f37c",
"systeme": {
"oben": {
"id": "adler-g10-2x-klarlack",
@@ -397,14 +397,14 @@
}
},
"werkstueck": {
- "anzahl": 2,
+ "anzahl": 1,
"aufbau": {
"id": "eiche-furniert-p2-19-g10",
"revision": 1
},
- "bezeichnung": "Boden (Korpus 600 breit, Seiten 19)",
+ "bezeichnung": "Boden Oben",
"faserrichtung": "laengs",
- "id": "boden-562x555",
+ "id": "teil_beispielbo0oben",
"kontur": {
"breite": 555.0,
"laenge": 562.0,
diff --git a/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielbo0unten.json b/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielbo0unten.json
new file mode 100644
index 0000000..d33d5ea
--- /dev/null
+++ b/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielbo0unten.json
@@ -0,0 +1,414 @@
+{
+ "aufbau": {
+ "bezeichnung": "EGGER Spanplatte P2 19 + Furnier Eiche 0,9 beidseitig + 2× ADLER G10 beidseitig + Furnierkante Eiche vorne",
+ "id": "eiche-furniert-p2-19-g10",
+ "revision": 1
+ },
+ "ergebnisse": {
+ "ausgeschlossen": [
+ {
+ "grund": "Trockenschichtdicke nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack"
+ },
+ {
+ "grund": "Trockenschichtdicke nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack"
+ },
+ {
+ "grund": "Trockenmasse der Beschichtung nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche"
+ },
+ {
+ "grund": "Kante ist je lfm — braucht das Werkstück (siehe verbrauch)",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "rolle": "kante/vorne"
+ }
+ ],
+ "dicke_mm": {
+ "freigabe": "annahme",
+ "max": 21.5,
+ "min": 20.1,
+ "nominal": 20.8
+ },
+ "flaeche_m2": 0.3119,
+ "gewicht_kg_m2": {
+ "freigabe": "annahme",
+ "wert": 14.352
+ },
+ "verbrauch": [
+ {
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "egger-spanplatte-p2-19",
+ "rolle": "traeger",
+ "wert": 0.3119
+ },
+ {
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "rolle": "belag/oben",
+ "wert": 0.3119
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "ponal-flaechenleim-pvac",
+ "rolle": "leim/oben",
+ "wert": 37.4
+ },
+ {
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "rolle": "belag/unten",
+ "wert": 0.3119
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "ponal-flaechenleim-pvac",
+ "rolle": "leim/unten",
+ "wert": 37.4
+ },
+ {
+ "einheit": "g",
+ "freigabe": "belegt",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack/netto",
+ "wert": 62.4
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack/brutto",
+ "wert": 96.0
+ },
+ {
+ "einheit": "g",
+ "freigabe": "belegt",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack/netto",
+ "wert": 62.4
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack/brutto",
+ "wert": 96.0
+ },
+ {
+ "einheit": "lfm",
+ "freigabe": "annahme",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "rolle": "kante/vorne",
+ "wert": 0.566
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "jowatherm-280-30-eva",
+ "rolle": "kleber/vorne",
+ "wert": 3.4
+ }
+ ]
+ },
+ "formelversion": "werkstoff-ableitung/1",
+ "komponenten": [
+ {
+ "art": "traeger",
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "dicke": {
+ "begruendung": "Dicke im Auftrag nicht genannt; 19 mm = Handelsdicke (Frischeis) und übliche Korpusplatte im Betrieb — Bernhard bestätigen",
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "wert": 19
+ },
+ "id": "egger-spanplatte-p2-19",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Technisches Datenblatt TD-181 (E1E05 TSCA P2 CE), Rev. 08.04.2026",
+ "seite": "S. 1",
+ "stand": "2026-04-08",
+ "url": "https://binary.egger.link/pis/978585f4-777c-45bf-96bb-3cfa40df2bae/Technical_datasheet_E1E05_TSCA_P2_CE_TD-181_Deutsch"
+ },
+ "revision": 1,
+ "rohdichte": {
+ "begruendung": "TD-181 sagt 'werkspezifisch' (±10 %); 690 aus Frischeis-Plattengewicht 75,982 kg / (2,8 × 2,07 m² × 0,019 m) berechnet",
+ "einheit": "kg/m3",
+ "freigabe": "annahme",
+ "wert": 690
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER TD-181 Technisches Datenblatt E1E05 TSCA P2 CE",
+ "seiten": "S. 1–2",
+ "sha256": "1b0d66099daba0c8159b0100bb5a5cd5015cb5d0240ebc2bf266fb4ff12d82e0",
+ "stand": "2026-04-08",
+ "typ": "datenblatt",
+ "url": "https://binary.egger.link/pis/978585f4-777c-45bf-96bb-3cfa40df2bae/Technical_datasheet_E1E05_TSCA_P2_CE_TD-181_Deutsch"
+ },
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Verarbeitungshinweise Dekorative Platten und Trägerplatten",
+ "seiten": "S. 1–20",
+ "sha256": "e9fdcbea5e943a199f94178d89ffa83d20224b1cf25758fd229b6c8fe195a574",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://binary.egger.link/pis/4a389dc5-b778-426f-ada5-03d2c6e7eee3/Verarbeitungshinweise_Dekorative_Platten_und_Traegerplatten_Deutsch"
+ }
+ ]
+ },
+ {
+ "art": "belag",
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "dicke": {
+ "autor": "Bernhard Reiter",
+ "bemerkung": "Auftrag 02.09. 07:55 (Spanplatte + Furnier Eiche 0,9 + 2x ADLER G10 + Kante)",
+ "datum": "2026-09-02",
+ "einheit": "mm",
+ "freigabe": "meister",
+ "wert": 0.9
+ },
+ "id": "furnier-eiche-0-9-gemessert",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "revision": 1,
+ "rohdichte": {
+ "begruendung": "Rohdichte Eiche nicht recherchiert; Literaturbereich 650–700 kg/m³",
+ "einheit": "kg/m3",
+ "freigabe": "annahme",
+ "wert": 690
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN — Furnier im Innenausbau (Verarbeitung)",
+ "seiten": "S. 1–62",
+ "sha256": "3ab3f0fd735dae1a5a5b543e503f93b73f41effb273d08b65948f54b7be6578c",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ }
+ ]
+ },
+ {
+ "art": "belag",
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "dicke": {
+ "autor": "Bernhard Reiter",
+ "bemerkung": "Auftrag 02.09. 07:55 (Spanplatte + Furnier Eiche 0,9 + 2x ADLER G10 + Kante)",
+ "datum": "2026-09-02",
+ "einheit": "mm",
+ "freigabe": "meister",
+ "wert": 0.9
+ },
+ "id": "furnier-eiche-0-9-gemessert",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "revision": 1,
+ "rohdichte": {
+ "begruendung": "Rohdichte Eiche nicht recherchiert; Literaturbereich 650–700 kg/m³",
+ "einheit": "kg/m3",
+ "freigabe": "annahme",
+ "wert": 690
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN — Furnier im Innenausbau (Verarbeitung)",
+ "seiten": "S. 1–62",
+ "sha256": "3ab3f0fd735dae1a5a5b543e503f93b73f41effb273d08b65948f54b7be6578c",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "auftragsmenge": {
+ "begruendung": "Untergrenze aus »120–150 g/m² je nach Saugfähigkeit« (Ponal TM) — Betriebswert bis zur Messung",
+ "bemerkung": "120–150 g/m² je nach Saugfähigkeit; höherer Auftrag = längere Presszeit + Durchschlaggefahr",
+ "einheit": "g/m2",
+ "freigabe": "annahme",
+ "wert": 120
+ },
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "id": "ponal-flaechenleim-pvac",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "seite": "S. 1–2"
+ },
+ "revision": 1,
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "typ": "datenblatt"
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "auftragsmenge": {
+ "begruendung": "Mittelwert aus »Echtholzkanten 200–300 g/m²« (Ostermann-Verarbeitungshinweise); Jowat beziffert den Walzenauftrag nicht — Betriebswert bis zur Messung an der Maschine",
+ "bemerkung": "Echtholzkanten 200–300 g/m² (Ostermann: mehr Eigenspannung → mehr Leimauftrag); Jowat beziffert nicht",
+ "einheit": "g/m2",
+ "freigabe": "annahme",
+ "wert": 250
+ },
+ "bezeichnung": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
+ "id": "jowatherm-280-30-eva",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Jowat Produktinformation Jowatherm 280.30"
+ },
+ "revision": 1,
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Jowat TDS Jowatherm 280.30/31 (Kante, Stand 12/25)",
+ "seiten": "S. 1–3",
+ "sha256": "68aafb838580eb574888ffa160f3d38e0b3d495b80241ae1d06b55a5399be45c",
+ "stand": "2025-12-01",
+ "typ": "datenblatt",
+ "url": "https://d3files.jowat-cloud.com/technical-data-sheets-v2/280.30/31/TD28030-28031-Kante_German.PDF"
+ }
+ ]
+ },
+ {
+ "art": "beschichtung",
+ "auftragsmenge": {
+ "bemerkung": "TMB: 100–150 g/m² je Schicht, max. 450 g/m² gesamt",
+ "einheit": "g/m2",
+ "freigabe": "belegt",
+ "wert": 100
+ },
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "id": "adler-aduro-legnopur-g10",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "revision": 1,
+ "uebertragung": {
+ "spritzen": {
+ "begruendung": "Übertragungsgrad Becherpistole ~65 % (Erfahrungswert); Airless/Airmix höher — Betriebsmessung",
+ "einheit": "1",
+ "freigabe": "annahme",
+ "wert": 0.65
+ }
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER TMB 2513 ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "seiten": "S. 1–4",
+ "sha256": "3802082c34744eb165c12d8df2a6d01afca051fc6930961a6b2401edf7152dcc",
+ "typ": "datenblatt",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "sdb",
+ "url": "https://www.adler-lacke.com/at/produkte"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://www.adler-lacke.com/de/service-ratgeber/arbeitsrichtlinien"
+ }
+ ],
+ "zwischenschliff_korn": {
+ "bemerkung": "TMB: Zwischenschliff K240–320",
+ "einheit": "korn",
+ "freigabe": "belegt",
+ "wert": 320
+ }
+ },
+ {
+ "art": "kante",
+ "bezeichnung": "Ostermann Furnierkante Eiche europäisch, geschliffen K180, 1,0 × 24 mm (Art. 070.4200)",
+ "dicke": {
+ "begruendung": "Kantenart und Dicke offen (Furnierkante 0,5/1/2/3 oder Massivholz-Anleimer) — Bernhard",
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "wert": 1.0
+ },
+ "id": "ostermann-furnierkante-eiche-1-0",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann Technisches Merkblatt Furnierkanten (01/2020)",
+ "stand": "2020-01-01",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ "revision": 1,
+ "ueberstand": {
+ "begruendung": "Zuschnittzugabe je Kantenende — Zahlenwert in keiner Quelle belegt",
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "wert": 2
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann TM Furnierkanten",
+ "seiten": "S. 1–2",
+ "sha256": "50860b11af6ba61b68d593991c01b5a9ce148dda628a20acf292036eb4d2f250",
+ "typ": "datenblatt",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Kleiberit Infoblatt Kantenverklebung",
+ "seiten": "S. 1–4",
+ "sha256": "45597e449c7307a5c900d6399d0466bf6c983d24176c64ac7af51d79c42834fb",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://legacy.kleiberit.com/fileadmin/Content_new/Infoblaetter/deutsch/Kantenverklebung_D.pdf"
+ }
+ ]
+ }
+ ],
+ "resolve_manifest_sha256": "bc29b2392ef8e7e67c9c30da8de8f1fa380828b1036a45579db8fa3ffbd3d3fe",
+ "systeme": {
+ "oben": {
+ "id": "adler-g10-2x-klarlack",
+ "revision": 1
+ },
+ "unten": {
+ "id": "adler-g10-2x-klarlack",
+ "revision": 1
+ }
+ },
+ "werkstueck": {
+ "anzahl": 1,
+ "aufbau": {
+ "id": "eiche-furniert-p2-19-g10",
+ "revision": 1
+ },
+ "bezeichnung": "Boden Unten",
+ "faserrichtung": "laengs",
+ "id": "teil_beispielbo0unten",
+ "kontur": {
+ "breite": 555.0,
+ "laenge": 562.0,
+ "typ": "rechteck"
+ }
+ }
+}
diff --git a/apps/tischler/public/werkstoff-bundle/manifeste/seite-links-890x555.json b/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielse0links.json
similarity index 96%
rename from apps/tischler/public/werkstoff-bundle/manifeste/seite-links-890x555.json
rename to apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielse0links.json
index 3f4f4e2..9c54336 100644
--- a/apps/tischler/public/werkstoff-bundle/manifeste/seite-links-890x555.json
+++ b/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielse0links.json
@@ -33,7 +33,7 @@
"min": 20.1,
"nominal": 20.8
},
- "flaeche_m2": 0.9879,
+ "flaeche_m2": 0.494,
"gewicht_kg_m2": {
"freigabe": "annahme",
"wert": 14.352
@@ -44,77 +44,77 @@
"freigabe": "belegt",
"komponente": "egger-spanplatte-p2-19",
"rolle": "traeger",
- "wert": 0.9879
+ "wert": 0.494
},
{
"einheit": "m2",
"freigabe": "belegt",
"komponente": "furnier-eiche-0-9-gemessert",
"rolle": "belag/oben",
- "wert": 0.9879
+ "wert": 0.494
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "ponal-flaechenleim-pvac",
"rolle": "leim/oben",
- "wert": 118.5
+ "wert": 59.3
},
{
"einheit": "m2",
"freigabe": "belegt",
"komponente": "furnier-eiche-0-9-gemessert",
"rolle": "belag/unten",
- "wert": 0.9879
+ "wert": 0.494
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "ponal-flaechenleim-pvac",
"rolle": "leim/unten",
- "wert": 118.5
+ "wert": 59.3
},
{
"einheit": "g",
"freigabe": "belegt",
"komponente": "adler-aduro-legnopur-g10",
"rolle": "oberflaeche/oben/1-klarlack/netto",
- "wert": 197.6
+ "wert": 98.8
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "adler-aduro-legnopur-g10",
"rolle": "oberflaeche/oben/1-klarlack/brutto",
- "wert": 304.0
+ "wert": 152.0
},
{
"einheit": "g",
"freigabe": "belegt",
"komponente": "adler-aduro-legnopur-g10",
"rolle": "oberflaeche/unten/1-klarlack/netto",
- "wert": 197.6
+ "wert": 98.8
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "adler-aduro-legnopur-g10",
"rolle": "oberflaeche/unten/1-klarlack/brutto",
- "wert": 304.0
+ "wert": 152.0
},
{
"einheit": "lfm",
"freigabe": "annahme",
"komponente": "ostermann-furnierkante-eiche-1-0",
"rolle": "kante/vorne",
- "wert": 1.788
+ "wert": 0.894
},
{
"einheit": "g",
"freigabe": "annahme",
"komponente": "jowatherm-280-30-eva",
"rolle": "kleber/vorne",
- "wert": 10.7
+ "wert": 5.4
}
]
},
@@ -385,7 +385,7 @@
]
}
],
- "resolve_manifest_sha256": "803aa37c60bfdb9aed0f79ba309c42c59c5fea4a5a8f94edb3e7e02463d89dee",
+ "resolve_manifest_sha256": "e7ed33f6c8b864ead3d8d2406075ace94154298b08835872ab5da71705f5c7c8",
"systeme": {
"oben": {
"id": "adler-g10-2x-klarlack",
@@ -397,20 +397,14 @@
}
},
"werkstueck": {
- "anzahl": 2,
+ "anzahl": 1,
"aufbau": {
"id": "eiche-furniert-p2-19-g10",
"revision": 1
},
- "bezeichnung": "Seite links (Bernhards Korpus 890 × 555)",
+ "bezeichnung": "Seite Links",
"faserrichtung": "laengs",
- "id": "seite-links-890x555",
- "kanten": {
- "vorne": {
- "id": "ostermann-furnierkante-eiche-1-0",
- "revision": 1
- }
- },
+ "id": "teil_beispielse0links",
"kontur": {
"breite": 555.0,
"laenge": 890.0,
diff --git a/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielse0rechts.json b/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielse0rechts.json
new file mode 100644
index 0000000..0418921
--- /dev/null
+++ b/apps/tischler/public/werkstoff-bundle/manifeste/teil_beispielse0rechts.json
@@ -0,0 +1,414 @@
+{
+ "aufbau": {
+ "bezeichnung": "EGGER Spanplatte P2 19 + Furnier Eiche 0,9 beidseitig + 2× ADLER G10 beidseitig + Furnierkante Eiche vorne",
+ "id": "eiche-furniert-p2-19-g10",
+ "revision": 1
+ },
+ "ergebnisse": {
+ "ausgeschlossen": [
+ {
+ "grund": "Trockenschichtdicke nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack"
+ },
+ {
+ "grund": "Trockenschichtdicke nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack"
+ },
+ {
+ "grund": "Trockenmasse der Beschichtung nicht hinterlegt — nicht addiert",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche"
+ },
+ {
+ "grund": "Kante ist je lfm — braucht das Werkstück (siehe verbrauch)",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "rolle": "kante/vorne"
+ }
+ ],
+ "dicke_mm": {
+ "freigabe": "annahme",
+ "max": 21.5,
+ "min": 20.1,
+ "nominal": 20.8
+ },
+ "flaeche_m2": 0.494,
+ "gewicht_kg_m2": {
+ "freigabe": "annahme",
+ "wert": 14.352
+ },
+ "verbrauch": [
+ {
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "egger-spanplatte-p2-19",
+ "rolle": "traeger",
+ "wert": 0.494
+ },
+ {
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "rolle": "belag/oben",
+ "wert": 0.494
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "ponal-flaechenleim-pvac",
+ "rolle": "leim/oben",
+ "wert": 59.3
+ },
+ {
+ "einheit": "m2",
+ "freigabe": "belegt",
+ "komponente": "furnier-eiche-0-9-gemessert",
+ "rolle": "belag/unten",
+ "wert": 0.494
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "ponal-flaechenleim-pvac",
+ "rolle": "leim/unten",
+ "wert": 59.3
+ },
+ {
+ "einheit": "g",
+ "freigabe": "belegt",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack/netto",
+ "wert": 98.8
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/oben/1-klarlack/brutto",
+ "wert": 152.0
+ },
+ {
+ "einheit": "g",
+ "freigabe": "belegt",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack/netto",
+ "wert": 98.8
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "adler-aduro-legnopur-g10",
+ "rolle": "oberflaeche/unten/1-klarlack/brutto",
+ "wert": 152.0
+ },
+ {
+ "einheit": "lfm",
+ "freigabe": "annahme",
+ "komponente": "ostermann-furnierkante-eiche-1-0",
+ "rolle": "kante/vorne",
+ "wert": 0.894
+ },
+ {
+ "einheit": "g",
+ "freigabe": "annahme",
+ "komponente": "jowatherm-280-30-eva",
+ "rolle": "kleber/vorne",
+ "wert": 5.4
+ }
+ ]
+ },
+ "formelversion": "werkstoff-ableitung/1",
+ "komponenten": [
+ {
+ "art": "traeger",
+ "bezeichnung": "EGGER Spanplatte roh E1E05 TSCA P2 CE (Eurospan), 19 mm",
+ "dicke": {
+ "begruendung": "Dicke im Auftrag nicht genannt; 19 mm = Handelsdicke (Frischeis) und übliche Korpusplatte im Betrieb — Bernhard bestätigen",
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "wert": 19
+ },
+ "id": "egger-spanplatte-p2-19",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Technisches Datenblatt TD-181 (E1E05 TSCA P2 CE), Rev. 08.04.2026",
+ "seite": "S. 1",
+ "stand": "2026-04-08",
+ "url": "https://binary.egger.link/pis/978585f4-777c-45bf-96bb-3cfa40df2bae/Technical_datasheet_E1E05_TSCA_P2_CE_TD-181_Deutsch"
+ },
+ "revision": 1,
+ "rohdichte": {
+ "begruendung": "TD-181 sagt 'werkspezifisch' (±10 %); 690 aus Frischeis-Plattengewicht 75,982 kg / (2,8 × 2,07 m² × 0,019 m) berechnet",
+ "einheit": "kg/m3",
+ "freigabe": "annahme",
+ "wert": 690
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER TD-181 Technisches Datenblatt E1E05 TSCA P2 CE",
+ "seiten": "S. 1–2",
+ "sha256": "1b0d66099daba0c8159b0100bb5a5cd5015cb5d0240ebc2bf266fb4ff12d82e0",
+ "stand": "2026-04-08",
+ "typ": "datenblatt",
+ "url": "https://binary.egger.link/pis/978585f4-777c-45bf-96bb-3cfa40df2bae/Technical_datasheet_E1E05_TSCA_P2_CE_TD-181_Deutsch"
+ },
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "EGGER Verarbeitungshinweise Dekorative Platten und Trägerplatten",
+ "seiten": "S. 1–20",
+ "sha256": "e9fdcbea5e943a199f94178d89ffa83d20224b1cf25758fd229b6c8fe195a574",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://binary.egger.link/pis/4a389dc5-b778-426f-ada5-03d2c6e7eee3/Verarbeitungshinweise_Dekorative_Platten_und_Traegerplatten_Deutsch"
+ }
+ ]
+ },
+ {
+ "art": "belag",
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "dicke": {
+ "autor": "Bernhard Reiter",
+ "bemerkung": "Auftrag 02.09. 07:55 (Spanplatte + Furnier Eiche 0,9 + 2x ADLER G10 + Kante)",
+ "datum": "2026-09-02",
+ "einheit": "mm",
+ "freigabe": "meister",
+ "wert": 0.9
+ },
+ "id": "furnier-eiche-0-9-gemessert",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "revision": 1,
+ "rohdichte": {
+ "begruendung": "Rohdichte Eiche nicht recherchiert; Literaturbereich 650–700 kg/m³",
+ "einheit": "kg/m3",
+ "freigabe": "annahme",
+ "wert": 690
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN — Furnier im Innenausbau (Verarbeitung)",
+ "seiten": "S. 1–62",
+ "sha256": "3ab3f0fd735dae1a5a5b543e503f93b73f41effb273d08b65948f54b7be6578c",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ }
+ ]
+ },
+ {
+ "art": "belag",
+ "bezeichnung": "Furnier Eiche europäisch, gemessert (Starkfurnier), 0,9 mm",
+ "dicke": {
+ "autor": "Bernhard Reiter",
+ "bemerkung": "Auftrag 02.09. 07:55 (Spanplatte + Furnier Eiche 0,9 + 2x ADLER G10 + Kante)",
+ "datum": "2026-09-02",
+ "einheit": "mm",
+ "freigabe": "meister",
+ "wert": 0.9
+ },
+ "id": "furnier-eiche-0-9-gemessert",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN 'Furnier im Innenausbau' (Initiative Furnier + Natur) / ÜK-Unterlage Furnieren",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ },
+ "revision": 1,
+ "rohdichte": {
+ "begruendung": "Rohdichte Eiche nicht recherchiert; Literaturbereich 650–700 kg/m³",
+ "einheit": "kg/m3",
+ "freigabe": "annahme",
+ "wert": 690
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "IFN — Furnier im Innenausbau (Verarbeitung)",
+ "seiten": "S. 1–62",
+ "sha256": "3ab3f0fd735dae1a5a5b543e503f93b73f41effb273d08b65948f54b7be6578c",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://tu-dresden.de/ing/maschinenwesen/int/ressourcen/dateien/holztechnik-und-faserwerkstofftechnik/download/wissenschaftliche-publikationen/Furnier.pdf"
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "auftragsmenge": {
+ "begruendung": "Untergrenze aus »120–150 g/m² je nach Saugfähigkeit« (Ponal TM) — Betriebswert bis zur Messung",
+ "bemerkung": "120–150 g/m² je nach Saugfähigkeit; höherer Auftrag = längere Presszeit + Durchschlaggefahr",
+ "einheit": "g/m2",
+ "freigabe": "annahme",
+ "wert": 120
+ },
+ "bezeichnung": "Ponal Flächenleim (PVAc-Dispersionsleim, D1/D2) — Furnier-/Flächenverleimung, Kaltpresse",
+ "id": "ponal-flaechenleim-pvac",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "seite": "S. 1–2"
+ },
+ "revision": 1,
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ponal Flächenleim — Technisches Merkblatt (Henkel)",
+ "typ": "datenblatt"
+ }
+ ]
+ },
+ {
+ "art": "klebstoff",
+ "auftragsmenge": {
+ "begruendung": "Mittelwert aus »Echtholzkanten 200–300 g/m²« (Ostermann-Verarbeitungshinweise); Jowat beziffert den Walzenauftrag nicht — Betriebswert bis zur Messung an der Maschine",
+ "bemerkung": "Echtholzkanten 200–300 g/m² (Ostermann: mehr Eigenspannung → mehr Leimauftrag); Jowat beziffert nicht",
+ "einheit": "g/m2",
+ "freigabe": "annahme",
+ "wert": 250
+ },
+ "bezeichnung": "Jowatherm 280.30 (EVA-Schmelzkleber, ungefüllt, Nullfuge) — Kantenanleimen stationär",
+ "id": "jowatherm-280-30-eva",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Jowat Produktinformation Jowatherm 280.30"
+ },
+ "revision": 1,
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Jowat TDS Jowatherm 280.30/31 (Kante, Stand 12/25)",
+ "seiten": "S. 1–3",
+ "sha256": "68aafb838580eb574888ffa160f3d38e0b3d495b80241ae1d06b55a5399be45c",
+ "stand": "2025-12-01",
+ "typ": "datenblatt",
+ "url": "https://d3files.jowat-cloud.com/technical-data-sheets-v2/280.30/31/TD28030-28031-Kante_German.PDF"
+ }
+ ]
+ },
+ {
+ "art": "beschichtung",
+ "auftragsmenge": {
+ "bemerkung": "TMB: 100–150 g/m² je Schicht, max. 450 g/m² gesamt",
+ "einheit": "g/m2",
+ "freigabe": "belegt",
+ "wert": 100
+ },
+ "bezeichnung": "ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "id": "adler-aduro-legnopur-g10",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER Technisches Merkblatt 2513 (ADLER Aduro Legnopur G10, 2K-PUR-Klarlack)",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ "revision": 1,
+ "uebertragung": {
+ "spritzen": {
+ "begruendung": "Übertragungsgrad Becherpistole ~65 % (Erfahrungswert); Airless/Airmix höher — Betriebsmessung",
+ "einheit": "1",
+ "freigabe": "annahme",
+ "wert": 0.65
+ }
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "ADLER TMB 2513 ADLER Aduro Legnopur G10, 2K-PUR-Klarlack",
+ "seiten": "S. 1–4",
+ "sha256": "3802082c34744eb165c12d8df2a6d01afca051fc6930961a6b2401edf7152dcc",
+ "typ": "datenblatt",
+ "url": "https://www.adler-lacke.com/Canto/tmb/aduro-legnopur_tmb_2513_de.pdf"
+ },
+ {
+ "dokument": "ADLER Sicherheitsdatenblatt 2513 (nur mit Kundenkonto)",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "sdb",
+ "url": "https://www.adler-lacke.com/at/produkte"
+ },
+ {
+ "dokument": "ADLER Arbeitsrichtlinie ARL 131 (PUR-Möbellacke) — auf Anfrage",
+ "fingerabdruck_offen": "URL führt auf eine Übersichtsseite, nicht auf das Dokument — das Dokument selbst gibt es nur mit Kundenkonto (ADLER); Fingerabdruck erst mit dem echten Dokument",
+ "seiten": "gesamtes Dokument",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://www.adler-lacke.com/de/service-ratgeber/arbeitsrichtlinien"
+ }
+ ],
+ "zwischenschliff_korn": {
+ "bemerkung": "TMB: Zwischenschliff K240–320",
+ "einheit": "korn",
+ "freigabe": "belegt",
+ "wert": 320
+ }
+ },
+ {
+ "art": "kante",
+ "bezeichnung": "Ostermann Furnierkante Eiche europäisch, geschliffen K180, 1,0 × 24 mm (Art. 070.4200)",
+ "dicke": {
+ "begruendung": "Kantenart und Dicke offen (Furnierkante 0,5/1/2/3 oder Massivholz-Anleimer) — Bernhard",
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "wert": 1.0
+ },
+ "id": "ostermann-furnierkante-eiche-1-0",
+ "quelle": {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann Technisches Merkblatt Furnierkanten (01/2020)",
+ "stand": "2020-01-01",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ "revision": 1,
+ "ueberstand": {
+ "begruendung": "Zuschnittzugabe je Kantenende — Zahlenwert in keiner Quelle belegt",
+ "einheit": "mm",
+ "freigabe": "annahme",
+ "wert": 2
+ },
+ "unterlagen": [
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Ostermann TM Furnierkanten",
+ "seiten": "S. 1–2",
+ "sha256": "50860b11af6ba61b68d593991c01b5a9ce148dda628a20acf292036eb4d2f250",
+ "typ": "datenblatt",
+ "url": "https://web.ostermann.eu/mamdata/PDF_Daten/Technische_Merkblaetter/Kanten/Echtholz/Furnierkante_DE_ipdf_dase.pdf"
+ },
+ {
+ "abgerufen": "2026-09-02",
+ "dokument": "Kleiberit Infoblatt Kantenverklebung",
+ "seiten": "S. 1–4",
+ "sha256": "45597e449c7307a5c900d6399d0466bf6c983d24176c64ac7af51d79c42834fb",
+ "typ": "verarbeitungsrichtlinie",
+ "url": "https://legacy.kleiberit.com/fileadmin/Content_new/Infoblaetter/deutsch/Kantenverklebung_D.pdf"
+ }
+ ]
+ }
+ ],
+ "resolve_manifest_sha256": "7113369780cade95bc8252b687e072666321ac133f22d528498055612728e9a6",
+ "systeme": {
+ "oben": {
+ "id": "adler-g10-2x-klarlack",
+ "revision": 1
+ },
+ "unten": {
+ "id": "adler-g10-2x-klarlack",
+ "revision": 1
+ }
+ },
+ "werkstueck": {
+ "anzahl": 1,
+ "aufbau": {
+ "id": "eiche-furniert-p2-19-g10",
+ "revision": 1
+ },
+ "bezeichnung": "Seite Rechts",
+ "faserrichtung": "laengs",
+ "id": "teil_beispielse0rechts",
+ "kontur": {
+ "breite": 555.0,
+ "laenge": 890.0,
+ "typ": "rechteck"
+ }
+ }
+}
From 9bf7138f3c15f22c9a7ea4259c9031b4bdf581e3 Mon Sep 17 00:00:00 2001
From: Bernhard Reiter
Date: Fri, 4 Sep 2026 09:58:11 +0200
Subject: [PATCH 2/5] =?UTF-8?q?fix(werkstoff):=20die=20L=C3=BCcke=20wird?=
=?UTF-8?q?=20getragen=20=E2=80=94=20teile=5Fohne=5Fkarte,=20Duplikate=20u?=
=?UTF-8?q?nd=20Enkel=20sind=20Fehler,=20Header=20wird=20gepr=C3=BCft?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Review craft#47 (Cody #2), zwei Blocker und der Vertrag, der aus cody-cad#66
nachkommt:
- Doppelte Knotennamen waren ein Fail-Open (['teil:A','teil:A'] gegen {A}
→ ok). glbKnoten lehnt Duplikate ab, pruefeSzene zählt statt Mengen zu
vergleichen — beide Seiten, damit wir nicht die lockerere sind.
- buildplan_sha256 wird gegen nichts gehalten — jetzt steht es am Typ:
was das beweist (Form, kanonischer Hash von cody-cad) und was nicht (dass
das Modell aus demselben Plan kommt; die Wurzel unterscheidet Pläne
nicht). Die Bindung schließt auftrag.modell.glb_sha256 (eigener PR).
- Vertrag (04.09., beidseitig): teile = nur Teile MIT Karte;
teile_ohne_karte = [{schluessel, werkstueck_id, aufbau, grund}], grund
Pflicht, disjunkt zu teile (Schlüssel in beiden Listen = Fehler), fehlt
das Feld (älteres Bundle) = keine Lücke. pruefeSzene: erlaubt in der Szene
ist genau teile ∪ teile_ohne_karte, und jedes davon muss da sein — in
beide Richtungen. Genau ZWEI Ebenen festgeschrieben: ein Kind mit eigenen
Kindern ist ein Vertragsbruch, keine Gruppe. Header-Bereichsprüfung:
ein lügender JSON-Chunk ist Klartext, kein RangeError.
- Page: die Rückwand steht als fünftes Brett da — gestrichelt, »Material
noch offen«; ein Tap zeigt Aufbau und Grund statt einer Karte. Das Bundle
ist jetzt der Referenzplan mit --ohne-karte teil:Rw (Revision 3), die
Fixture demo-mini.glb trägt fünf Kinder und die Wurzel NICHT an Index 0
(Reihenfolge ≠ Identität).
Red-First: 13 Tests vorher rot. 51/51 in den fünf Werkstoff-Dateien,
Suite 412/412, tsc 0, eslint grün. Saboteure einzeln, zurückgebaut:
GLB-Duplikate erlaubt · Enkel erlaubt · Szene-Duplikate im Mengenvergleich ·
Lücke ohne Grund · Schlüssel in beiden Listen · Lücke nicht in der Szene ·
Header-Prüfung aus · Tap auf Lücke zeigt nichts — jeder genau 1 rot, am
richtigen Test.
Co-Authored-By: Claude Fable 5.1
---
apps/tischler/app/[locale]/werkstoff/page.tsx | 78 ++++++++++--
.../app/[locale]/werkstoff/panel.test.tsx | 14 +++
apps/tischler/app/globals.css | 6 +
apps/tischler/lib/werkstoff/auftrag.test.ts | 119 ++++++++++++++++--
apps/tischler/lib/werkstoff/auftrag.ts | 105 +++++++++++++++-
.../lib/werkstoff/fixtures/demo-mini.glb | Bin 280 -> 400 bytes
.../public/werkstoff-bundle/auftrag.json | 10 +-
.../public/werkstoff-bundle/modell.glb | Bin 0 -> 1685260 bytes
8 files changed, 303 insertions(+), 29 deletions(-)
create mode 100644 apps/tischler/public/werkstoff-bundle/modell.glb
diff --git a/apps/tischler/app/[locale]/werkstoff/page.tsx b/apps/tischler/app/[locale]/werkstoff/page.tsx
index 2853cc3..b431708 100644
--- a/apps/tischler/app/[locale]/werkstoff/page.tsx
+++ b/apps/tischler/app/[locale]/werkstoff/page.tsx
@@ -31,7 +31,14 @@ import {
type Komponentenzeile,
type Materialkarte,
} from "../../../lib/werkstoff/karte";
-import { ladeAuftrag, schluesselKurz, type Auftrag } from "../../../lib/werkstoff/auftrag";
+import {
+ karteFuer,
+ ladeAuftrag,
+ luecke,
+ schluesselKurz,
+ type Auftrag,
+ type AuftragLuecke,
+} from "../../../lib/werkstoff/auftrag";
function Ampel({ freigabe }: { freigabe: Materialkarte["freigabe"] }) {
return (
@@ -121,6 +128,8 @@ export default function WerkstoffPage() {
const [auftrag, setAuftrag] = useState(null);
const [auftragFehler, setAuftragFehler] = useState(null);
const [gewaehlt, setGewaehlt] = useState(null);
+ // Ein angetipptes Brett ohne Karte: kein Fehler, sondern der Grund — Material noch offen.
+ const [lueckeGewaehlt, setLueckeGewaehlt] = useState(null);
const [karte, setKarte] = useState(null);
const [stand, setStand] = useState(null);
const [grenze, setGrenze] = useState(null);
@@ -156,6 +165,31 @@ export default function WerkstoffPage() {
}, [gewaehlt]);
const gruppen = useMemo(() => (karte ? gruppiere(karte.komponenten) : []), [karte]);
+ // Ein Tap führt über den Schlüssel: zur Karte, wenn es eine gibt — sonst zur benannten Lücke.
+ const tippeTeil = (schluessel: string) => {
+ if (!auftrag) return;
+ const id = karteFuer(auftrag, schluessel);
+ if (id) {
+ setLueckeGewaehlt(null);
+ setGewaehlt(gewaehlt === id ? null : id);
+ return;
+ }
+ const l = auftrag.teile_ohne_karte.find((t) => t.schluessel === schluessel);
+ if (l) {
+ setGewaehlt(null);
+ setLueckeGewaehlt(lueckeGewaehlt?.schluessel === l.schluessel ? null : l);
+ }
+ };
+ const bretter = useMemo(
+ () =>
+ auftrag
+ ? [
+ ...auftrag.teile.map((t) => ({ ...t, luecke: undefined as AuftragLuecke | undefined })),
+ ...auftrag.teile_ohne_karte.map((t) => ({ ...t, luecke: t })),
+ ].sort((a, b) => a.schluessel.localeCompare(b.schluessel))
+ : [],
+ [auftrag],
+ );
const schritte = useMemo(() => (karte ? arbeitsfolge(karte) : []), [karte]);
return (
@@ -188,20 +222,38 @@ export default function WerkstoffPage() {
- {(auftrag?.teile ?? []).map((w) => (
- setGewaehlt(gewaehlt === w.werkstueck_id ? null : w.werkstueck_id)}
- aria-pressed={gewaehlt === w.werkstueck_id}
- >
- {schluesselKurz(w.schluessel)}
- {w.werkstueck_id}
-
- ))}
+ {bretter.map((w) => {
+ const aktiv = w.luecke
+ ? lueckeGewaehlt?.schluessel === w.schluessel
+ : gewaehlt === w.werkstueck_id;
+ return (
+ tippeTeil(w.schluessel)}
+ aria-pressed={aktiv}
+ >
+ {schluesselKurz(w.schluessel)}
+ {w.luecke ? "Material noch offen" : w.werkstueck_id}
+
+ );
+ })}
+ {auftrag && lueckeGewaehlt && (
+
+ {schluesselKurz(lueckeGewaehlt.schluessel)} — Material noch offen
+
+ Aufbau {lueckeGewaehlt.aufbau}: {luecke(auftrag, lueckeGewaehlt.schluessel)?.grund}
+
+
+ Der Plan verlangt dieses Brett, der Katalog kennt seinen Aufbau nicht. Welches Material
+ verbaut wird, entscheidet der Betrieb — nicht das Werkzeug.
+
+
+ )}
+
{fehler && ⚠ {fehler}
}
{karte && (
diff --git a/apps/tischler/app/[locale]/werkstoff/panel.test.tsx b/apps/tischler/app/[locale]/werkstoff/panel.test.tsx
index 940be7e..f4e3068 100644
--- a/apps/tischler/app/[locale]/werkstoff/panel.test.tsx
+++ b/apps/tischler/app/[locale]/werkstoff/panel.test.tsx
@@ -69,6 +69,20 @@ describe("Werkstoff-Panel: der Auftrag bestimmt, welche Bretter es gibt", () =>
expect(text).toMatch(/moebel_beispiel0001/);
expect(text).toMatch(/Revision 3/);
expect(screen.getAllByRole("button", { name: /^(Bo|Se):/ })).toHaveLength(4);
+ // Die Rückwand steht als fünftes Brett da — als benannte Lücke, nicht weggeschnitten.
+ expect(screen.getByRole("button", { name: /^Rw/ })).toBeTruthy();
+ });
+
+ it("tippt der Handwerker auf die Rückwand, kommt keine Karte, sondern der Grund — Material noch offen", async () => {
+ vi.stubGlobal("fetch", stub());
+ render( );
+ const rw = await waitFor(() => screen.getByRole("button", { name: /^Rw/ }));
+ rw.click();
+ await waitFor(() => {
+ expect(document.body.textContent).toMatch(/Material noch offen/);
+ });
+ expect(document.body.textContent).toMatch(/rw-8@1/);
+ expect(document.querySelector(".karte")).toBeNull();
});
it("tippt der Handwerker auf »Se:links«, kommt die Karte des Plan-Teils, nicht eines Katalog-Bretts", async () => {
diff --git a/apps/tischler/app/globals.css b/apps/tischler/app/globals.css
index fdfe40d..e579f38 100644
--- a/apps/tischler/app/globals.css
+++ b/apps/tischler/app/globals.css
@@ -1002,3 +1002,9 @@ body.cc-kiosk .cc-workbench {
border-top: 1px dashed currentColor;
border-color: color-mix(in srgb, currentColor 25%, transparent);
}
+
+/* Werkstoff: ein Brett ohne Karte — die benannte Lücke */
+.werkstoff .teil.luecke { border-style: dashed; color: #64748b; background: #f8fafc; }
+.werkstoff .luecke-hinweis { border: 1px dashed #94a3b8; border-radius: 8px; padding: 1rem 1.25rem; margin: 1rem 0; background: #f8fafc; }
+.werkstoff .luecke-hinweis h2 { margin: 0 0 0.5rem; font-size: 1.1rem; }
+.werkstoff .luecke-hinweis p { margin: 0.25rem 0; }
diff --git a/apps/tischler/lib/werkstoff/auftrag.test.ts b/apps/tischler/lib/werkstoff/auftrag.test.ts
index 3c2ff2a..dd10681 100644
--- a/apps/tischler/lib/werkstoff/auftrag.test.ts
+++ b/apps/tischler/lib/werkstoff/auftrag.test.ts
@@ -10,7 +10,7 @@
import { describe, it, expect, vi, afterEach } from "vitest";
import { readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
-import { glbKnoten, karteFuer, ladeAuftrag, pruefeSzene, type Auftrag } from "./auftrag";
+import { glbKnoten, karteFuer, ladeAuftrag, luecke, pruefeSzene, type Auftrag } from "./auftrag";
const BUNDLE = join(__dirname, "../../public/werkstoff-bundle");
const MINI_GLB = join(__dirname, "fixtures/demo-mini.glb");
@@ -50,6 +50,35 @@ describe("ladeAuftrag — die Klammer aus dem Bundle", () => {
"teil:Se:rechts",
]);
expect(a.teile.every((t) => /^teil_/.test(t.werkstueck_id))).toBe(true);
+ // Die Lücke wird getragen: die Rückwand hat keinen Katalog-Aufbau, und der Auftrag sagt es.
+ expect(a.teile_ohne_karte).toEqual([
+ {
+ schluessel: "teil:Rw",
+ werkstueck_id: "teil_beispielrw000001",
+ aufbau: "rw-8@1",
+ grund: "kein Aufbau rw-8@1 im Katalogstand 2026-09-03 — Material noch offen",
+ },
+ ]);
+ });
+
+ it("ein Bundle ohne das Feld teile_ohne_karte (älterer Stand) hat keine Lücke — kein Fehler", async () => {
+ vi.stubGlobal("fetch", verbiegen((a) => delete a.teile_ohne_karte));
+ expect((await ladeAuftrag()).teile_ohne_karte).toEqual([]);
+ });
+
+ it("die Lücke braucht Grund und Aufbau, und ein Schlüssel steht nie in beiden Listen", async () => {
+ type L = Array<{ schluessel: string; werkstueck_id: string; aufbau: string; grund: string }>;
+ const faelle: Array<[RegExp, (a: Record) => void]> = [
+ [/grund/, (a) => ((a.teile_ohne_karte as L)[0]!.grund = "")],
+ [/grund/, (a) => delete (a.teile_ohne_karte as Partial[])[0]!.grund],
+ [/aufbau/, (a) => ((a.teile_ohne_karte as L)[0]!.aufbau = "")],
+ [/beiden Listen/, (a) => ((a.teile_ohne_karte as L)[0]!.schluessel = "teil:Bo:oben")],
+ [/doppelt/i, (a) => ((a.teile_ohne_karte as L)[0]!.werkstueck_id = "teil_beispielbo0oben")],
+ ];
+ for (const [grund, f] of faelle) {
+ vi.stubGlobal("fetch", verbiegen(f));
+ await expect(ladeAuftrag(), String(grund)).rejects.toThrow(grund);
+ }
});
it("fehlt der Auftrag, ist das ein Fehler — kein leeres Möbel", async () => {
@@ -78,11 +107,40 @@ describe("ladeAuftrag — die Klammer aus dem Bundle", () => {
describe("glbKnoten — was das 3D-Modell an Namen trägt", () => {
const glb = () => new Uint8Array(readFileSync(MINI_GLB)).buffer;
+ const umbauenAllg = (f: (j: Record) => void) => {
+ const b = new Uint8Array(glb());
+ const len = new DataView(b.buffer).getUint32(12, true);
+ const j = JSON.parse(new TextDecoder().decode(b.subarray(20, 20 + len)));
+ f(j);
+ const txt = new TextEncoder().encode(JSON.stringify(j));
+ const pad = (4 - (txt.length % 4)) % 4;
+ const out = new Uint8Array(20 + txt.length + pad);
+ const dv = new DataView(out.buffer);
+ out.set([0x67, 0x6c, 0x54, 0x46], 0);
+ dv.setUint32(4, 2, true);
+ dv.setUint32(8, out.length, true);
+ dv.setUint32(12, txt.length + pad, true);
+ dv.setUint32(16, 0x4e4f534a, true);
+ out.set(txt, 20);
+ for (let i = 0; i < pad; i++) out[20 + txt.length + i] = 0x20;
+ return out.buffer;
+ };
- it("liest Wurzel und Teile aus dem Fixture (Wurzel = Möbel, Kinder = Schlüssel)", () => {
+ it("liest Wurzel und Teile aus dem Fixture — die Wurzel ist die der Szene, nicht nodes[0]", () => {
const k = glbKnoten(glb());
expect(k.wurzel).toBe("moebel_beispiel0001");
- expect([...k.teile].sort()).toEqual(["teil:Bo:oben", "teil:Bo:unten", "teil:Se:links", "teil:Se:rechts"]);
+ expect([...k.teile].sort()).toEqual([
+ "teil:Bo:oben",
+ "teil:Bo:unten",
+ "teil:Rw",
+ "teil:Se:links",
+ "teil:Se:rechts",
+ ]);
+ // Reihenfolge ≠ Identität: im Fixture steht das Möbel absichtlich nicht an Index 0.
+ const b = new Uint8Array(glb());
+ const len = new DataView(b.buffer).getUint32(12, true);
+ const j = JSON.parse(new TextDecoder().decode(b.subarray(20, 20 + len))) as { scenes: Array<{ nodes: number[] }> };
+ expect(j.scenes[0]!.nodes[0]).not.toBe(0);
});
it("lehnt ab, was kein glTF-Binary ist", () => {
@@ -110,9 +168,29 @@ describe("glbKnoten — was das 3D-Modell an Namen trägt", () => {
for (let i = 0; i < pad; i++) out[20 + txt.length + i] = 0x20;
return out.buffer;
};
+ type N = Array<{ name?: string; children?: number[] }>;
+ const wurzelIdx = (j: Record) => (j.scenes as Array<{ nodes: number[] }>)[0]!.nodes[0]!;
expect(() => glbKnoten(umbauen((j) => ((j.scenes as Array<{ nodes: number[] }>)[0]!.nodes = [0, 1])))).toThrow(/eine Wurzel/);
- expect(() => glbKnoten(umbauen((j) => delete (j.nodes as Array<{ name?: string }>)[0]!.name))).toThrow(/Wurzel.*Name/);
- expect(() => glbKnoten(umbauen((j) => delete (j.nodes as Array<{ name?: string }>)[2]!.name))).toThrow(/ohne Namen/);
+ expect(() => glbKnoten(umbauen((j) => delete (j.nodes as N)[wurzelIdx(j)]!.name))).toThrow(/Wurzel.*Name/);
+ expect(() => glbKnoten(umbauen((j) => delete (j.nodes as N)[0]!.name))).toThrow(/ohne Namen/);
+ });
+
+ it("doppelte Knotennamen sind ein Fehler — zwei Bretter mit einem Namen sind kein Vertrag", () => {
+ type N = Array<{ name?: string; children?: number[] }>;
+ const dup = umbauenAllg((j) => ((j.nodes as N)[0]!.name = (j.nodes as N)[1]!.name));
+ expect(() => glbKnoten(dup)).toThrow(/doppelt/);
+ });
+
+ it("genau zwei Ebenen: ein Kind mit eigenen Kindern ist ein Vertragsbruch, keine Gruppe", () => {
+ type N = Array<{ name?: string; children?: number[] }>;
+ const enkel = umbauenAllg((j) => ((j.nodes as N)[0]!.children = [1]));
+ expect(() => glbKnoten(enkel)).toThrow(/zwei Ebenen/);
+ });
+
+ it("ein Header, der über das Dateiende hinaus zeigt, ist ein Klartext-Fehler, kein RangeError", () => {
+ const b = new Uint8Array(glb());
+ new DataView(b.buffer).setUint32(12, 10_000_000, true);
+ expect(() => glbKnoten(b.buffer)).toThrow(/Header|Chunk/);
});
});
@@ -123,11 +201,34 @@ describe("pruefeSzene — Modell und Auftrag müssen sich decken, in beide Richt
expect(pruefeSzene(AUFTRAG, knoten())).toEqual({ ok: true, fehler: [] });
});
- it("ein Knoten ohne Karte wird genannt — die Rückwand, die es im Katalog nicht gibt", () => {
+ it("die benannte Lücke ist erlaubt: teil:Rw hat keine Karte, aber einen Grund — kein Fehler", () => {
+ expect(pruefeSzene(AUFTRAG, knoten()).ok).toBe(true);
+ expect(luecke(AUFTRAG, "teil:Rw")).toEqual({
+ aufbau: "rw-8@1",
+ grund: "kein Aufbau rw-8@1 im Katalogstand 2026-09-03 — Material noch offen",
+ });
+ expect(luecke(AUFTRAG, "teil:Se:links")).toBeUndefined();
+ });
+
+ it("ein Knoten, der weder Karte noch benannte Lücke hat, wird genannt", () => {
+ const k = knoten();
+ const r = pruefeSzene(AUFTRAG, { ...k, teile: [...k.teile, "teil:Xy"] });
+ expect(r.ok).toBe(false);
+ expect(r.fehler.join("\n")).toMatch(/teil:Xy.*ohne Karte/);
+ });
+
+ it("fehlt die Lücke in der Szene, fehlt ein Brett — auch das wird genannt", () => {
+ const k = knoten();
+ const r = pruefeSzene(AUFTRAG, { ...k, teile: k.teile.filter((t) => t !== "teil:Rw") });
+ expect(r.ok).toBe(false);
+ expect(r.fehler.join("\n")).toMatch(/teil:Rw.*nicht in der Szene/);
+ });
+
+ it("doppelte Knoten in der Szene sind ein Fehler, kein Mengenvergleich (Review craft#47)", () => {
const k = knoten();
- const r = pruefeSzene(AUFTRAG, { ...k, teile: [...k.teile, "teil:Rw"] });
+ const r = pruefeSzene(AUFTRAG, { ...k, teile: [...k.teile, "teil:Se:links"] });
expect(r.ok).toBe(false);
- expect(r.fehler.join("\n")).toMatch(/teil:Rw.*ohne Karte/);
+ expect(r.fehler.join("\n")).toMatch(/teil:Se:links.*doppelt/);
});
it("eine Karte ohne Knoten wird genannt — Material ohne Brett", () => {
@@ -148,7 +249,7 @@ describe("pruefeSzene — Modell und Auftrag müssen sich decken, in beide Richt
const k = knoten();
const r = pruefeSzene(AUFTRAG, {
wurzel: "moebel_anderes0002",
- teile: [...k.teile.filter((t) => t !== "teil:Se:rechts"), "teil:Rw"],
+ teile: [...k.teile.filter((t) => t !== "teil:Se:rechts"), "teil:Xy"],
});
expect(r.fehler).toHaveLength(3);
});
diff --git a/apps/tischler/lib/werkstoff/auftrag.ts b/apps/tischler/lib/werkstoff/auftrag.ts
index d3b3fd0..afcf248 100644
--- a/apps/tischler/lib/werkstoff/auftrag.ts
+++ b/apps/tischler/lib/werkstoff/auftrag.ts
@@ -16,11 +16,31 @@ export interface AuftragTeil {
werkstueck_id: string;
}
+/**
+ * Ein Teil des Plans, für das der Katalog keinen Aufbau kennt — benannt, mit Grund. Die Lücke
+ * wird getragen, nicht weggeschnitten (cody-cad `--ohne-karte`): der Plan bleibt ein
+ * konsistentes Möbel, das Brett steht in der Szene, und die Brille sagt, warum es keine Karte hat.
+ */
+export interface AuftragLuecke extends AuftragTeil {
+ aufbau: string;
+ grund: string;
+}
+
export interface Auftrag {
moebel_id: string;
+ /**
+ * Was das beweist: die Form (64 Hex) — und dass cody-cad den kanonischen Hash des Plans
+ * geschrieben hat, aus dem die Karten stammen. Was es NICHT beweist: dass das Modell in
+ * der Szene aus demselben Plan kommt. Referenz- und Demo-Plan trugen dieselbe `moebel_id`
+ * (Review craft#47, Cody #2) — die Wurzel unterscheidet Pläne nicht. Die Bindung Plan →
+ * Modell schließt erst `auftrag.modell.glb_sha256` (kommt mit `bauen --modell`).
+ */
buildplan_sha256: string;
revision: number;
+ /** Nur Teile MIT Karte — die Brücke Knotenname → Karte. */
teile: AuftragTeil[];
+ /** Teile ohne Karte, jedes mit Grund. Disjunkt zu `teile`; zusammen = Knoten des Modells. */
+ teile_ohne_karte: AuftragLuecke[];
}
const HEX64 = /^[0-9a-f]{64}$/;
@@ -52,7 +72,42 @@ function pruefeAuftrag(d: unknown): Auftrag {
karten.add(x.werkstueck_id);
teile.push({ schluessel: x.schluessel, werkstueck_id: x.werkstueck_id });
}
- return { moebel_id: o.moebel_id, buildplan_sha256: o.buildplan_sha256, revision: o.revision, teile };
+ // Ein älteres Bundle trägt das Feld nicht: keine Lücke. Trägt es das Feld, gilt der Vertrag
+ // hart — Grund und Aufbau Pflicht, kein Schlüssel in beiden Listen.
+ const teile_ohne_karte: AuftragLuecke[] = [];
+ if (o.teile_ohne_karte !== undefined) {
+ if (!Array.isArray(o.teile_ohne_karte)) throw new Error("teile_ohne_karte ist keine Liste");
+ for (const l of o.teile_ohne_karte as unknown[]) {
+ const x = (l ?? {}) as Record;
+ if (typeof x.schluessel !== "string" || !x.schluessel) throw new Error("Lücke ohne schluessel");
+ if (typeof x.werkstueck_id !== "string" || !x.werkstueck_id) {
+ throw new Error(`Lücke ${x.schluessel} ohne werkstueck_id`);
+ }
+ if (typeof x.aufbau !== "string" || !x.aufbau) throw new Error(`Lücke ${x.schluessel} ohne aufbau`);
+ if (typeof x.grund !== "string" || !x.grund) {
+ throw new Error(`Lücke ${x.schluessel} ohne grund — eine Lücke ohne Grund ist eine weggeschnittene`);
+ }
+ if (schluessel.has(x.schluessel)) {
+ throw new Error(`Schlüssel ${x.schluessel} steht in beiden Listen — Karte oder Lücke, nicht beides`);
+ }
+ if (karten.has(x.werkstueck_id)) throw new Error(`Werkstück ${x.werkstueck_id} doppelt im Auftrag`);
+ schluessel.add(x.schluessel);
+ karten.add(x.werkstueck_id);
+ teile_ohne_karte.push({
+ schluessel: x.schluessel,
+ werkstueck_id: x.werkstueck_id,
+ aufbau: x.aufbau,
+ grund: x.grund,
+ });
+ }
+ }
+ return {
+ moebel_id: o.moebel_id,
+ buildplan_sha256: o.buildplan_sha256,
+ revision: o.revision,
+ teile,
+ teile_ohne_karte,
+ };
}
export async function ladeAuftrag(basis = "/werkstoff-bundle"): Promise {
@@ -74,6 +129,10 @@ export interface SzenenKnoten {
/**
* Liest aus einem glTF-Binary (GLB) nur die Namen: die eine Wurzel der Szene und ihre Kinder.
* Kein Mesh, keine Geometrie — genau das, was der Vertrag verlangt, und ohne WebGL prüfbar.
+ *
+ * Vertrag (cody-cad `exportiere_glb_verifiziert`, festgeschrieben 04.09. mit Cody #2): GENAU
+ * zwei Ebenen — Wurzel = Möbel, Kinder = Teile. Ein Kind mit eigenen Kindern ist kein Teil und
+ * keine Gruppe, sondern ein Vertragsbruch; Rekursion würde Gruppen als Teile lesen.
*/
export function glbKnoten(buf: ArrayBuffer): SzenenKnoten {
const dv = new DataView(buf);
@@ -82,6 +141,11 @@ export function glbKnoten(buf: ArrayBuffer): SzenenKnoten {
}
const jsonLen = dv.getUint32(12, true);
if (dv.getUint32(16, true) !== 0x4e4f534a) throw new Error("glTF-Binary ohne JSON-Chunk");
+ if (20 + jsonLen > buf.byteLength) {
+ throw new Error(
+ `glTF-Header lügt: JSON-Chunk mit ${jsonLen} Bytes, Datei hat nur ${buf.byteLength}`,
+ );
+ }
const j = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 20, jsonLen))) as {
scenes?: Array<{ nodes?: number[] }>;
nodes?: Array<{ name?: string; children?: number[] }>;
@@ -101,9 +165,18 @@ export function glbKnoten(buf: ArrayBuffer): SzenenKnoten {
throw new Error("Wurzel ohne Name — das Möbel ist nicht identifizierbar");
}
const kinder: number[] = wurzel.children ?? [];
+ const gesehen = new Set();
const teile = kinder.map((i) => {
- const n = nodes[i]?.name;
+ const k = nodes[i];
+ const n = k?.name;
if (!n) throw new Error(`Knoten ${i} ohne Namen — er kann keine Karte tragen`);
+ if ((k?.children ?? []).length > 0) {
+ throw new Error(
+ `Knoten ${n} hat eigene Kinder — der Vertrag verlangt genau zwei Ebenen (Möbel → Teile)`,
+ );
+ }
+ if (gesehen.has(n)) throw new Error(`Knotenname ${n} doppelt — zwei Bretter mit einem Namen`);
+ gesehen.add(n);
return n;
});
return { wurzel: wurzel.name, teile };
@@ -114,7 +187,11 @@ export interface SzenenPruefung {
fehler: string[];
}
-/** Beide Richtungen, alle Abweichungen — nicht nur die erste. */
+/**
+ * Beide Richtungen, alle Abweichungen — nicht nur die erste. Erlaubt in der Szene sind genau
+ * `teile ∪ teile_ohne_karte`; jedes davon muss auch da sein. Duplikate sind ein Fehler, kein
+ * Mengenvergleich (Review craft#47: `['teil:A','teil:A']` gegen `{A}` war ok — Fail-Open).
+ */
export function pruefeSzene(auftrag: Auftrag, knoten: SzenenKnoten): SzenenPruefung {
const fehler: string[] = [];
if (knoten.wurzel !== auftrag.moebel_id) {
@@ -122,16 +199,26 @@ export function pruefeSzene(auftrag: Auftrag, knoten: SzenenKnoten): SzenenPruef
`Falsches Möbel: die Szene heißt ${knoten.wurzel}, der Auftrag gilt für ${auftrag.moebel_id}`,
);
}
- const imAuftrag = new Set(auftrag.teile.map((t) => t.schluessel));
- const inSzene = new Set(knoten.teile);
+ const erlaubt = new Set([
+ ...auftrag.teile.map((t) => t.schluessel),
+ ...auftrag.teile_ohne_karte.map((t) => t.schluessel),
+ ]);
+ const inSzene = new Set();
for (const s of knoten.teile) {
- if (!imAuftrag.has(s)) fehler.push(`Knoten ${s} ohne Karte im Auftrag — Brett ohne Material`);
+ if (inSzene.has(s)) fehler.push(`Knoten ${s} doppelt in der Szene — zwei Bretter mit einem Namen`);
+ inSzene.add(s);
+ if (!erlaubt.has(s)) fehler.push(`Knoten ${s} ohne Karte im Auftrag — Brett ohne Material`);
}
for (const t of auftrag.teile) {
if (!inSzene.has(t.schluessel)) {
fehler.push(`Karte ${t.schluessel} nicht in der Szene — Material ohne Brett`);
}
}
+ for (const t of auftrag.teile_ohne_karte) {
+ if (!inSzene.has(t.schluessel)) {
+ fehler.push(`Lücke ${t.schluessel} nicht in der Szene — das Brett fehlt, nicht nur die Karte`);
+ }
+ }
return { ok: fehler.length === 0, fehler };
}
@@ -139,6 +226,12 @@ export function karteFuer(auftrag: Auftrag, schluessel: string): string | undefi
return auftrag.teile.find((t) => t.schluessel === schluessel)?.werkstueck_id;
}
+/** Die benannte Lücke zu einem Schlüssel — Aufbau und Grund, oder nichts. */
+export function luecke(auftrag: Auftrag, schluessel: string): { aufbau: string; grund: string } | undefined {
+ const l = auftrag.teile_ohne_karte.find((t) => t.schluessel === schluessel);
+ return l ? { aufbau: l.aufbau, grund: l.grund } : undefined;
+}
+
/** »teil:Se:links« → »Se:links« — die Schaltfläche trägt den Schlüssel, nicht ein Wunschwort. */
export function schluesselKurz(schluessel: string): string {
return schluessel.replace(/^teil:/, "");
diff --git a/apps/tischler/lib/werkstoff/fixtures/demo-mini.glb b/apps/tischler/lib/werkstoff/fixtures/demo-mini.glb
index 65fcb90540e1f54378eefcfa6c88b5ff687f3dde..f96593b19365353f4858c5c232a90960ea50980b 100644
GIT binary patch
delta 177
zcmbQiG=Z5nJtxGCiGhJ(0wV)M%|u@3WQ9i!QxyCXb5rva5=)B}3QCi5GL!YoQ;V{T
zOY+mwbdyT+QgTxD6HC)diW1ZHvWoNb6f{awGjmcE9(Alz0JGxrGxAd7vlEL-QZ;oH
z!b^*)Qgaj%^AtSuQc^1vj3x#s@)^a}#_A~L<)@?;D_KQP+-x`76Caub0OTJ$rT_o{
delta 61
zcmbQhJcEfhJtxGCiGhJZf{}rNWg@Th#LbF)2C=oVI!bx@DXGOuR?(A%8MU|!b&Pb3
RbxdMwbtXQtnw-k04FJ5C51;@5
diff --git a/apps/tischler/public/werkstoff-bundle/auftrag.json b/apps/tischler/public/werkstoff-bundle/auftrag.json
index ca9ddbe..418840e 100644
--- a/apps/tischler/public/werkstoff-bundle/auftrag.json
+++ b/apps/tischler/public/werkstoff-bundle/auftrag.json
@@ -1,5 +1,5 @@
{
- "buildplan_sha256": "dbaca54603baaf7b83e9dc0b23dcf7812148d8b20b53a6e4c38e137adc2938ef",
+ "buildplan_sha256": "49499816f78a60c40d35268e2fb68e92a67f3bbf480834f3189a063e18c17cf8",
"moebel_id": "moebel_beispiel0001",
"revision": 3,
"teile": [
@@ -19,5 +19,13 @@
"schluessel": "teil:Se:rechts",
"werkstueck_id": "teil_beispielse0rechts"
}
+ ],
+ "teile_ohne_karte": [
+ {
+ "aufbau": "rw-8@1",
+ "grund": "kein Aufbau rw-8@1 im Katalogstand 2026-09-03 — Material noch offen",
+ "schluessel": "teil:Rw",
+ "werkstueck_id": "teil_beispielrw000001"
+ }
]
}
diff --git a/apps/tischler/public/werkstoff-bundle/modell.glb b/apps/tischler/public/werkstoff-bundle/modell.glb
new file mode 100644
index 0000000000000000000000000000000000000000..b190df9d0d6be7658c417fa9db418ee29e2ac2e9
GIT binary patch
literal 1685260
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