Eigene Modelle einhängen
Diese Seite beschreibt, wie ein eigenständiges K3-Plugin eigene 3D-Modelle in die Slots der Veranda- oder Qubus-Konstruktion einhängt — inklusive automatischer Platzierung.
Die Grundlagen zu K3-Plugins stehen unter Plugins.
Überblick
Die Konstruktion rendert pro Slot alle zugewiesenen Modell-Instanzen. Externe Modelle bekommen dabei automatisch:
| Mechanismus | Was | Wie nutzen |
|---|---|---|
parentGeometry-Prop | Komplette Konstruktions-Geometrie | wird jedem Slot-Modell injiziert |
slotAnchor-Prop | Fertig berechnete Einbauposition des Slots | Position, Rotation, Bauraum |
autoAnchor | Automatische Platzierung im Slot-Freiraum | autoAnchor: "1" in defaultProps |
useSlotSpace() | Lückenlose, kollisionsfreie Höhenzuweisung im Slot | über window.__VERANDA_SDK__.useSlotSpace |
window.__VERANDA_SDK__ | Context, Helfer, Slot-IDs | für fortgeschrittene Fälle |
Slot-IDs
Veranda (Pultdach) und Qubus (Flachdach) teilen dieselben Slot-IDs mit dem Präfix
konstruktion-slot-.
| Slot-Key | Slot-ID | Zweck | Default-Modell |
|---|---|---|---|
| pfosten | konstruktion-slot-pfosten | Stützstruktur | veranda-pfosten-eckig |
| eindeckung | konstruktion-slot-eindeckung | Überdachung | veranda-glas-eindeckung |
| rinne | konstruktion-slot-rinne | Regenrinne | veranda-regenrinne-kasten |
| beschattung | konstruktion-slot-beschattung | Beschattung | veranda-markise |
| wandLinks / Rechts / Vorne / Hinten | konstruktion-slot-wand-* | Wände | veranda-wand-keil bzw. -rahmenwand |
| led | konstruktion-slot-led | LED-Beleuchtung | — |
| aufbauLinks / … | veranda-slot-aufbau-* | Aufbau-Wände (Sub-Slot der Sichtschutzwand) | — |
Die Aufbau-Slots gehören zur Sichtschutzwand und verwenden das Präfix veranda-slot-aufbau-,
nicht konstruktion-slot-.
Minimalbeispiel: externe Rinne mit Auto-Platzierung
import type { DynamicModel } from "k3-plugin-api";
function MeineRinne(props: any) {
// Durch autoAnchor ist der Ursprung bereits die Rinnen-Einbauposition
// (X-Mitte, Y = Rinnen-Unterkante-Anker, Z = Vorderkante, 180° gedreht).
const anchor = props.slotAnchor; // SlotAnchor (siehe unten)
const geo = props.parentGeometry; // VerandaParentGeometry
const laenge = anchor?.size.breite ?? geo.width;
return (
<group>
<mesh position={[0, anchor.size.hoehe / 2, 0]}>
<boxGeometry args={[laenge, anchor.size.hoehe, anchor.size.tiefe]} />
<meshStandardMaterial color="#808080" />
</mesh>
</group>
);
}
export const meineRinneDynamicModel: DynamicModel = {
type: "mein-plugin-rinne",
label: "Meine Rinne",
defaultProps: {
autoAnchor: "1",
rinnenBreite: { expression: "0.12" },
rinnenHoehe: { expression: "0.08" },
},
component: MeineRinne,
};
Das Modell in K3 dem Slot konstruktion-slot-rinne zuweisen — fertig. Die Rinne wird automatisch
an der korrekten Position gebaut, folgt live jeder Änderung von Breite, Tiefe, Höhe und
Dachneigung, und der vordere Rahmenbalken des Qubus wird ausgeblendet, sobald der Rinnen-Slot
belegt ist.
Die injizierten Props
parentGeometry
Alle Konstruktionsmaße in Metern. Ein Auszug; die vollständige Liste steht in
src/shared/VerandaGeometryContext.tsx:
width,depth,height,innerWidth,innerDepthdachneigungin Grad — beim Qubus0, die echte Neigung steht ineindeckungDachneigungpfostenBreite,pfostenTiefe,pfostenAnzahlVorne,pfostenAnzahlHintensparrenAnzahl,sparrenBreite,sparrenHoehe,sparrenAuflageschwelle,schwelleBreite,schwelleHoehe,pfette,pfettenBreite,pfettenHoeherinnenHoehe,rinnenBreiteisQubus,qubusRahmenBreite,qubusRahmenHoehe
slotAnchor
interface SlotAnchor {
position: [number, number, number]; // Einbauposition, Ursprung: Boden-Mitte der Konstruktion
rotation: [number, number, number]; // Euler XYZ, im Bogenmaß
size: { breite: number; hoehe: number; tiefe: number }; // verfügbarer Bauraum
meta: Record<string, number | boolean>; // Slot-Extras
}
Was der Anker je Slot bedeutet:
| Slot | position | rotation | meta |
|---|---|---|---|
| rinne | Rinnen-Unterkante vor der Schwelle; beim Qubus ersetzt sie den Vorderbalken | 180° um Y | schwelleOK, innenliegend, isQubus |
| eindeckung | Auflage-Vorderkante | Dachneigung um X | hoeheVorne, hoeheHinten, neigung, dachneigung |
| beschattung, led | Unterdach-Vorderkante | — | hoeheVorne, hoeheHinten, sparrenHoehe |
| wand, aufbau | Seitenmitte am Boden; lokale +x entlang der Wand, +z nach außen | 0, ±90 oder 180° um Y | hoeheVorne, hoeheHinten |
| pfosten | Konstruktions-Ursprung | — | pfostenBreite, pfostenTiefe |
autoAnchor
Steht autoAnchor in den defaultProps auf "1" (oder { expression: "1" }), wrappt die
Konstruktion das Modell in eine Gruppe an der Ankerposition. Das Modell rendert dann nur noch am
lokalen Ursprung.
Ohne autoAnchor positioniert sich das Modell selbst — über den slotAnchor-Prop oder eigene
Logik. So arbeiten die internen Modelle.
Das Slot-Layout-System
Jeder Slot ist in einen SlotLayoutProvider eingebettet. Modelle innerhalb desselben Slots
registrieren ihren Platzbedarf und bekommen eine garantiert lückenlose, kollisionsfreie Zuteilung.
Die Raumzuweisung läuft vertikal, von unten nach oben, nach Priorität:
| priority | Bedeutung bei Wänden | Modus |
|---|---|---|
| 0 | Sichtschutzwand (Boden) | fixed — exakte Höhe |
| 1 | Aufbau (Rahmenwand, Schiebetür, Shutters) | fill — füllt den Rest |
| 2 | Keil (oberer Abschluss) | fill — füllt den obersten Rest |
Jedes Modell ruft useSlotSpace() genau einmal auf. Der Algorithmus berechnet daraus yOffset
(Unterkante der Zone) und height (zugeteilte Höhe). Die Summe aller Zonen entspricht immer genau
der Slot-Gesamthöhe.
Beispiel
import { useSlotSpace } from 'src/shared/SlotLayoutContext';
// oder über das SDK:
const { useSlotSpace } = window.__VERANDA_SDK__;
function MeinWandElement({ slotAnchor, parentGeometry }) {
const alloc = useSlotSpace({
priority: 1, // wo im Stack, 0 = unten
mode: 'fill', // 'fixed' = genau desiredHeight, 'fill' = Rest
desiredHeight: 0, // nur bei 'fixed' relevant
minHeight: 0.3, // Mindesthöhe; alloc.fits = false wenn unterschritten
});
// kein Provider im Baum -> null (kein Fehler, Fallback zur eigenen Logik)
if (!alloc) return null;
if (!alloc.fits) return null; // zu wenig Platz
const { yOffset, height } = alloc;
const { size } = slotAnchor;
return (
<group position={[0, yOffset, 0]}>
<mesh scale={[size.breite, height, 0.02]}>
<boxGeometry />
<meshStandardMaterial color="#a0c0e0" />
</mesh>
</group>
);
}
export const meinWandModell = {
type: 'mein-plugin-wand',
label: 'Meine Wand',
defaultProps: { autoAnchor: '1' },
component: MeinWandElement,
};
API
// Registrierungsparameter; die id wird intern erzeugt
interface SlotRegistration {
priority: number; // Stacking-Reihenfolge, 0 = unterste Zone
mode: 'fixed' | 'fill'; // fixed = exakt desiredHeight, fill = Restmenge
desiredHeight: number; // Wunschhöhe, nur bei mode 'fixed' wirksam
minHeight: number; // Mindesthöhe; fits = false wenn unterschritten
// Optional: abweichende Werte für die Schrägwand hinten
desiredHeightH?: number;
minHeightH?: number;
}
interface SlotAllocation {
yOffset: number; // Unterkante der Zone, vom Slot-Boden aus
height: number; // zugeteilte Höhe
fits: boolean; // false, wenn height kleiner als minHeight ist
// nur wenn totalHeightH gesetzt ist (Schrägwand):
yOffsetH?: number;
heightH?: number;
fitsH?: boolean;
}
function useSlotSpace(reg: Omit<SlotRegistration, 'id'>): SlotAllocation | null
Worauf Sie achten müssen:
useSlotSpacegehört an den Anfang des Komponenten-Körpers — es ist ein React-Hook und darf nicht in Bedingungen oder Schleifen stehen.- Bei
mode: 'fill'wird die Höhe berechnet,desiredHeightwird ignoriert. - Mehrere
fill-Modelle teilen sich den verbleibenden Raum gleichmäßig. Der letzte Eintrag bekommt den exakten Rest, sodass keine Lücke bleibt. - Ohne
SlotLayoutProviderim Baum liefert der Hooknull. Das ist kein Fehler — dann greift Ihre eigene Positionierungslogik.
Wände und Segmente
Die Seitenkonvention ist überall dieselbe: 0 = rechts, 1 = links, 2 = vorne, 3 = hinten.
Jedes Modell in einem Wand- oder Aufbau-Slot bekommt injiziert:
wandSeite— die Seite als Zahl. Externer Code kannuseWandSeitenicht direkt importieren, über das SDK aber schon.slotAnchor— segmentgenau berechnet übercalcWandSlotAnchor. Berücksichtigt den Freiraum aus Dachvorsprung, Pfosten, Pfette und Qubus-Rahmen, und bei gesetztemsegmentIndexbzw.segmentAnzahldie Position zwischen Pfosten i und i+1.size.breiteist die nutzbare Wandbreite,meta.hoeheVorneundmeta.hoeheHintendie Unterkante der Sparren.segmentIndex— wird automatisch auf den Instanz-Index gesetzt, wenn das Modell keinen eigenen liefert.
function MeineWand(props: any) {
const a = props.slotAnchor;
// Mit autoAnchor "1" ist der Ursprung bereits die Wand-Einbauposition:
// lokale +x entlang der Wand, +z nach außen.
return (
<mesh position={[0, a.size.hoehe / 2, 0]}>
<boxGeometry args={[a.size.breite, a.size.hoehe, 0.02]} />
<meshStandardMaterial color="#a0a0a0" />
</mesh>
);
}
Mit defaultProps: { autoAnchor: "1", segmentIndex: { expression: "-1" } } weisen Sie das Modell
etwa konstruktion-slot-wand-links zu und setzen Segment 2 über segmentIndex: 2 zusammen mit
segmentAnzahl: 3.
Aufbau auf der Sichtschutzwand
Die Sichtschutzwand hat zwei eigene Unter-Slots:
| Slot | ID | Zweck |
|---|---|---|
| Aufbau | b2f4e8a1-1234-4abc-9def-000000000001 | Wand-Element oben auf dem Sichtschutz |
| Füllung | 35abe01f-0247-4eb2-ae2a-d62104227bd4 | Füllung innerhalb des Rahmens |
Beide IDs liegen auch als sdk.SICHTSCHUTZWAND_AUFBAU_SLOT_ID und
sdk.SICHTSCHUTZWAND_FUELLUNG_SLOT_ID bereit.
Aufbau-Instanzen bekommen zusätzlich:
parentGeometry,slotAnchor(Anker der Trägerwand) undwandSeiteaufbauBasis— die Y-Startposition, also die Oberkante der SichtschutzwandaufbauMaxHoehe— der verfügbare Platz bis zur Unterkante der SparrenwandBreite— die Breite der TrägerwandautoAnchor: "1"gruppiert automatisch an Trägerwand-Position und Sichtschutz-Oberkante; das Modell rendert ab lokalem Y = 0 nach oben.
Füllung-Instanzen bekommen wandBreite, wandHoehe, wandHoeheHinten, material,
farbeHex, topBeamHeight und hasTopBeam. Sie rendern im lokalen Wand-Koordinatensystem, also
mittig in X und ab Y = 0.
Ein externes Modell im Wand-Slot, das als Sichtschutz dient, registriert sich mit useSlotSpace
und priority: 0, mode: 'fixed'. Aufbau-Modelle mit priority: 1 bekommen dann automatisch
den Raum darüber:
const alloc = sdk.useSlotSpace({
priority: 0,
mode: 'fixed',
desiredHeight: meineSichtschutzHoehe,
minHeight: 0.1,
});
Eine manuelle Höhenpublikation ist nicht nötig, das Layout-System koordiniert das selbst.
Das SDK
Für Modelle, die mehr brauchen als die injizierten Props:
const sdk = window.__VERANDA_SDK__;
if (sdk?.version === "1") {
// Geometrie
const geo = React.useContext(sdk.VerandaGeometryContext);
const anchors = sdk.calcSlotAnchors(geo);
// Slot-Layout — die primäre API für eigene Raumzuweisung
const alloc = sdk.useSlotSpace({ priority: 1, mode: 'fill', desiredHeight: 0, minHeight: 0.3 });
// Slot-ID-Konstanten
const slotIds = sdk.KONSTRUKTION_SLOTS;
}
| Export | Typ | Beschreibung |
|---|---|---|
VerandaGeometryContext | Context | Konstruktionsmaße |
useVerandaGeometry | Hook | Kurzform für den Context |
calcVerandaGeometry | Funktion | Dachwerte berechnen: Neigung, Höhe vorne und hinten |
calcSlotAnchors | Funktion | Alle Slot-Anker aus der Geometrie berechnen |
calcWandSlotAnchor | Funktion | Segmentgenauer Wand-Anker |
WandSeiteContext, useWandSeite | Context, Hook | Aktuelle Wandseite |
WandInfoContext, useWandInfo | Context, Hook | Sichtschutz-Höhen |
SlotLayoutContext | Context | Slot-Layout-Context |
useSlotSpace | Hook | Garantierte Slot-Raumzuweisung |
KONSTRUKTION_SLOTS | Konstante | Slot-ID-Definitionen |
SICHTSCHUTZWAND_AUFBAU_SLOTS | Konstante | Aufbau-Slot-IDs der Sichtschutzwand |
SICHTSCHUTZWAND_FUELLUNG_SLOT_ID | string | Füllungs-Slot-ID der Sichtschutzwand |
Die Typen liegen als VerandaSdk in src/sdk/types.ts.
Die Context-Nutzung setzt voraus, dass beide Plugins dieselbe React-Instanz verwenden. Die
Prop-Variante mit parentGeometry und slotAnchor funktioniert dagegen immer, auch ohne
geteiltes React.
Module Federation
Ihr Plugin muss dieselben Singletons deklarieren wie das Veranda-Plugin:
shared: {
"k3-plugin-api": { singleton: true, requiredVersion: "*" },
react: { singleton: true, requiredVersion: "*" },
"react-dom": { singleton: true, requiredVersion: "*" },
"react/jsx-runtime": { singleton: true, requiredVersion: "*" },
three: { singleton: true, requiredVersion: "*" },
"@react-three/fiber": { singleton: true, requiredVersion: "*" },
"@react-three/drei": { singleton: true, requiredVersion: "*" },
}
Liefert der K3-Host React nicht über den Shared Scope, übernehmen Sie zusätzlich den
inject-host-react-Patch aus der vite.config.js.
Verhalten, das Sie kennen sollten
- Remount bei Prop-Änderung. Die Konstruktion baut den React-Key aus allen Modell-Props.
Jede Prop-Änderung erzeugt daher ein Remount des Slot-Modells — halten Sie keinen State im
Modell, der das überleben muss.
parentGeometry,slotAnchorundautoAnchorsind ausgenommen: Geometrie-Änderungen lösen kein Remount aus. - Belegter Rinnen-Slot blendet den Vorderbalken aus. Beim Qubus greift
hideFrontautomatisch, sobald im Rinnen-Slot mindestens eine Instanz sitzt. - Props-Vertrag der Rinne. Bieten Sie
rinnenBreiteundrinnenHoeheals Expression-Props an. Die Konstruktion liest sie aus und passt Pfostenhöhe und Schwelle daran an. - Expression-Props defensiv lesen. K3 liefert Props teils als
{ expression: "0.12" }. Parsen Sie Werte immer mitNumber(...)und einem Fallback.