Zum Hauptinhalt springen

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:

MechanismusWasWie nutzen
parentGeometry-PropKomplette Konstruktions-Geometriewird jedem Slot-Modell injiziert
slotAnchor-PropFertig berechnete Einbauposition des SlotsPosition, Rotation, Bauraum
autoAnchorAutomatische Platzierung im Slot-FreiraumautoAnchor: "1" in defaultProps
useSlotSpace()Lückenlose, kollisionsfreie Höhenzuweisung im Slotüber window.__VERANDA_SDK__.useSlotSpace
window.__VERANDA_SDK__Context, Helfer, Slot-IDsfür fortgeschrittene Fälle

Slot-IDs​

Veranda (Pultdach) und Qubus (Flachdach) teilen dieselben Slot-IDs mit dem Präfix konstruktion-slot-.

Slot-KeySlot-IDZweckDefault-Modell
pfostenkonstruktion-slot-pfostenStützstrukturveranda-pfosten-eckig
eindeckungkonstruktion-slot-eindeckungÜberdachungveranda-glas-eindeckung
rinnekonstruktion-slot-rinneRegenrinneveranda-regenrinne-kasten
beschattungkonstruktion-slot-beschattungBeschattungveranda-markise
wandLinks / Rechts / Vorne / Hintenkonstruktion-slot-wand-*Wändeveranda-wand-keil bzw. -rahmenwand
ledkonstruktion-slot-ledLED-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, innerDepth
  • dachneigung in Grad — beim Qubus 0, die echte Neigung steht in eindeckungDachneigung
  • pfostenBreite, pfostenTiefe, pfostenAnzahlVorne, pfostenAnzahlHinten
  • sparrenAnzahl, sparrenBreite, sparrenHoehe, sparrenAuflage
  • schwelle, schwelleBreite, schwelleHoehe, pfette, pfettenBreite, pfettenHoehe
  • rinnenHoehe, rinnenBreite
  • isQubus, 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:

Slotpositionrotationmeta
rinneRinnen-Unterkante vor der Schwelle; beim Qubus ersetzt sie den Vorderbalken180° um YschwelleOK, innenliegend, isQubus
eindeckungAuflage-VorderkanteDachneigung um XhoeheVorne, hoeheHinten, neigung, dachneigung
beschattung, ledUnterdach-Vorderkante—hoeheVorne, hoeheHinten, sparrenHoehe
wand, aufbauSeitenmitte am Boden; lokale +x entlang der Wand, +z nach außen0, ±90 oder 180° um YhoeheVorne, hoeheHinten
pfostenKonstruktions-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:

priorityBedeutung bei WändenModus
0Sichtschutzwand (Boden)fixed — exakte Höhe
1Aufbau (Rahmenwand, Schiebetür, Shutters)fill — füllt den Rest
2Keil (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:

  • useSlotSpace gehö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, desiredHeight wird ignoriert.
  • Mehrere fill-Modelle teilen sich den verbleibenden Raum gleichmäßig. Der letzte Eintrag bekommt den exakten Rest, sodass keine Lücke bleibt.
  • Ohne SlotLayoutProvider im Baum liefert der Hook null. 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 kann useWandSeite nicht direkt importieren, über das SDK aber schon.
  • slotAnchor — segmentgenau berechnet über calcWandSlotAnchor. Berücksichtigt den Freiraum aus Dachvorsprung, Pfosten, Pfette und Qubus-Rahmen, und bei gesetztem segmentIndex bzw. segmentAnzahl die Position zwischen Pfosten i und i+1. size.breite ist die nutzbare Wandbreite, meta.hoeheVorne und meta.hoeheHinten die 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:

SlotIDZweck
Aufbaub2f4e8a1-1234-4abc-9def-000000000001Wand-Element oben auf dem Sichtschutz
Füllung35abe01f-0247-4eb2-ae2a-d62104227bd4Fü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) und wandSeite
  • aufbauBasis — die Y-Startposition, also die Oberkante der Sichtschutzwand
  • aufbauMaxHoehe — der verfügbare Platz bis zur Unterkante der Sparren
  • wandBreite — die Breite der Trägerwand
  • autoAnchor: "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;
}
ExportTypBeschreibung
VerandaGeometryContextContextKonstruktionsmaße
useVerandaGeometryHookKurzform für den Context
calcVerandaGeometryFunktionDachwerte berechnen: Neigung, Höhe vorne und hinten
calcSlotAnchorsFunktionAlle Slot-Anker aus der Geometrie berechnen
calcWandSlotAnchorFunktionSegmentgenauer Wand-Anker
WandSeiteContext, useWandSeiteContext, HookAktuelle Wandseite
WandInfoContext, useWandInfoContext, HookSichtschutz-Höhen
SlotLayoutContextContextSlot-Layout-Context
useSlotSpaceHookGarantierte Slot-Raumzuweisung
KONSTRUKTION_SLOTSKonstanteSlot-ID-Definitionen
SICHTSCHUTZWAND_AUFBAU_SLOTSKonstanteAufbau-Slot-IDs der Sichtschutzwand
SICHTSCHUTZWAND_FUELLUNG_SLOT_IDstringFü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, slotAnchor und autoAnchor sind ausgenommen: Geometrie-Änderungen lösen kein Remount aus.
  • Belegter Rinnen-Slot blendet den Vorderbalken aus. Beim Qubus greift hideFront automatisch, sobald im Rinnen-Slot mindestens eine Instanz sitzt.
  • Props-Vertrag der Rinne. Bieten Sie rinnenBreite und rinnenHoehe als 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 mit Number(...) und einem Fallback.