Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem, dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en) sowie Projektdokumentation und Probe-Harness.
25 KiB
Handoff — Rhino-artiges Befehlssystem + Modellier-Werkzeuge
Für die Instanz, die das Befehlssystem (Tab-getriggert) und die Rhino-artigen Modellierfunktionen baut. Stand: 2026-06-30. Zuerst lesen:
CONVENTIONS.md,ROADMAP.md,HANDOVER.md,docs/design/drawing-tools.md. Volle Autonomie, selbst bestätigen (Memoryproceed-autonomously,wire-dont-stub,prefer-agents).
Dieses Dokument hat drei Teile:
- Was schon steht (worauf du aufbaust — exakte Dateien/Typen/Actions).
- Rhino-Referenz (Interaktionsmodell, das nachzubilden ist).
- Konkreter Bauplan für DIESE Codebase (Architektur, Dateien, Reihenfolge).
TL;DR — die Kernidee
Es gibt kein Befehlssystem, keine Command-Line, keinen Tab-Handler. Das baust du
greenfield. Aber: Das Werkzeug-System ist bereits eine saubere Pure-Function-Registry
(src/tools/) mit generischem Controller in App.tsx, und die Mutations-Schicht
(projectSlice) ist umfassend. Ein neues Modellier-Tool steckst du durch Hinzufügen eines
Tool-Objekts ein — PlanView muss dafür nicht angefasst werden.
Das Befehlssystem ist im Kern eine State-Machine-Engine über prompt → pick/type →
options, plus eine Command-Line-UI (Statusleiste), plus ein Koordinaten-Parser.
Befehle dispatchen auf bestehende Store-Actions + setActiveTool/setProject.
Wichtigster konzeptioneller Sprung: Die heutigen Tools haben je eine eigene
ad-hoc-Phasenlogik (onClick/onMove). Rhino-Feel verlangt eine gemeinsame
Prompt/Option/Numerik-Engine, die alle Befehle teilen. Plane das als Verallgemeinerung
des bestehenden Tool-Interfaces, nicht als Parallelwelt daneben (sonst zwei Eingabe-Pfade,
die divergieren).
TEIL 1 — Was schon steht (Baufundament)
Stack: React 18 + TS + Vite, three 0.169 (nur 3D-Display). Einheiten intern Meter.
Eigener winziger Store (useSyncExternalStore, kein Redux/Zustand). Identifier englisch,
UI-Text deutsch via t(). Strict tsc (noUnusedLocals → ungenutzte Vars brechen den Build).
1.1 Werkzeug-System — src/tools/
Tool-Interfacesrc/tools/types.ts:139— reine Funktionen über internenToolState(Discriminated Union je Tool). Handler geben[nextState, ToolResult]zurück. Tools schreiben NIE Plan-Primitive; sie gebencommit(project) => projectzurück.ToolIdtypes.ts:11:"select" | "wall" | "line" | "polyline" | "rect".ToolContexttypes.ts:72:{ project, level, defaultCategoryCode, activeWallTypeId, activeLineStyleId }.ToolPointertypes.ts:85:{ raw, snap, point (=snap?.point ?? raw), shift, ctrl, alt, button }.ToolResulttypes.ts:121:{ draft, commit?, done? }.ToolDrafttypes.ts:109:{ preview: DraftShape[], vertices: Vec2[], snap?, hud?: {at,text} }.- Registry
src/tools/tools.ts:375:TOOLS: Record<ToolId,Tool>,getTool(id):384,TOOL_ORDER:389. Implementiert: select (Platzhalter), wall, line, polyline, rect. uniqueId(prefix)types.ts:188— ID-Generator.
1.2 Controller / Verdrahtung — alles in App.tsx (NICHT in den Tool-Dateien)
- Aktives Tool:
const [activeTool,setActiveTool]=useState<ToolId>("select")App.tsx:197. - Laufender Zustand in Ref
toolStateRefApp.tsx:205(kein Re-Render je Mausschritt). Live-Vorschauconst [draft,setDraft]App.tsx:206. - Controller
runToolStep(kind,raw,pxPerMeter,mods)App.tsx:350: bautToolContext(toolCtx:294), snappt viasnapFor:322(→computeSnap), bautToolPointer, rufttool.onClick/onMove, speichert State in Ref,applyToolResult:339wendet draft/commit/done an.toolHandlersApp.tsx:419verbindet PlanView↔Controller. - Tool-Tasten
App.tsx:448: Esc=Abbruch/zurück-zu-select, Enter=Commit-Geste, Backspace=Punkt zurück.
1.3 Semantisches Modell — src/model/types.ts
type Element = Wall | Door | Drawing2D:251.Vec2={x,y}. Kein Slab/Stair-Typ.Project:254:{ ..., wallTypes[], drawingLevels[], layers[], walls[], doors[], drawings2d[] }.Wall:150:{ id,type:"wall", floorId, categoryCode, start, end, wallTypeId, height, color? }(Mittellinie + mehrschichtigerWallType).- Plan-Primitive
Drawing2DGeom:200— die Geom-Typen existieren bereits ALLE:line | polyline | rect | circle | arc | text. Aber Tools erzeugen heute nur line/polyline/rect, unddrawingVertices(Grips) kennt nur diese drei. circle/arc/text sind im Typ da, aber nicht durchgängig gerendert/editierbar — Lücke, kein Neubau nötig. Drawing2D:209:{ id,type:"drawing2d", levelId, categoryCode, geom, lineStyleId?, hatchId?, color?, fillColor?, weightMm? }.
1.4 Geometrie — src/model/geometry.ts
sub,add,scale,len,normalize; leftNormal(a)={x:-a.y,y:a.x} :17 (Wand-Normale-Konvention);
cross, lineIntersect(a,da,b,db), along, wallBand, wallCorners :70, clippedBand :87.
src/model/joins.ts: computeJoins(project,walls) :44 (nur L-Ecken gehrt; T/X eckig).
Für Offset/Trim/Fillet (Rhino-Kern) gibt es NOCH KEIN 2D-Geometrie-Kernel — Kurven/Kurven-
Schnitt, Polylinien-Offset usw. musst du ergänzen (siehe Bauplan §3.4).
1.5 Store — src/state/
createStorestore.ts:51überuseSyncExternalStore. Actions leben IM State (useStore(s=>s.action), referenzstabil).RootState = Project & Selection & View & LayoutappStore.ts:21. ExportsuseStore,getState,setState.- projectSlice:
project+setProject(next|(p)=>p). Mutationen u.a.addFloor,addCategory,setElementColor/Weight/Fill,resizeElement,moveGripOf,moveElementByOf,moveEdgeOf,commitTransformOn. Es gibt keine generische „addWall/addDrawing2d"-Action — Tools committen viasetProject. (Beim Befehlssystem ggf. saubere Actions ergänzen.) - Aktive Zeichenebene + aktive Kategorie liegen im viewSlice, NICHT in selection:
activeLevelIdviewSlice.ts:46/setActiveLevelId,activeCategoryCode:42/setActiveCategoryCode. - selectionSlice:
selectedWallIds[],selectedDrawingId+ Setter/clearSelection.
1.6 Views, Eingabe, Koordinaten — src/plan/PlanView.tsx (SVG-Vektor)
- Modell →
Plan-Primitive viageneratePlansrc/plan/generatePlan.ts:203.Primitive=polygon|line|arc(polygons tragenwallId/drawingIdfür Hit-Test). - Transform (entscheidend):
PX_PER_M=90:20;toScreen(p)={x:p.x*90,y:-p.y*90}:31(fixer Welt-Ursprung 0,0; Y flippt). InversviewToModel:440. SVGviewBox=Stateview; Pan/Zoom ändern nurview, nie das Modell↔Screen-Mapping. rawModelAt(clientX,clientY):446= aktuelle Mauswelt-Position in Meter (der Eine-Aufruf, den ein Tool/Befehl braucht).currentPxPerMeter():488.- Pointer-Events alle am
<svg>:910: down:553, move:625, up:715, wheel:820, dblclick:847, contextmenu:857. Schema: Mitte=Pan, Links=Select/Marquee/Tool, Rechts=Menü. BeitoolActive:268routen Links-Events zutoolHandlers. PlanView meldet bereits(rawModelAt, currentPxPerMeter, toolMods)nach oben — neue Tools brauchen hier NICHTS. - Snapping
src/tools/snapping.ts:computeSnap(input):88— endpoint/midpoint/intersection/ onEdge/grid/ortho mit Prioritätstabelle. Wird in App (snapFor) konsumiert, nicht in PlanView.applyAngleConstraintfür Ortho.SnapSettings/DEFAULT_SNAPintools/types.ts:36/55. - 3D
src/viewport/Viewport3D.tsx(three.js, Raycaster): nur Anzeige+Auswahl, keine Zeichenwerkzeuge. 3D-Authoring = eigene spätere Phase (Raycast auf Arbeitsebene).
1.7 Tastatur / globale Eingabe — kein Dispatch-System
main.tsx:38globalercontextmenu→preventDefault;:42blockt Ctrl/Cmd+A außerhalb Inputs;isTextEntry(el):24.- App-useEffects mit
window.addEventListener("keydown"): Tool-Tasten:448, Delete:604, Transform-Shortcuts m/s/d + u/i/o/p:637. Jeder Guard wiederholt inline den INPUT/TEXTAREA/contentEditable-Check — es gibt keine geteilte Keymap. Dein Tab-Handler + Command-Input kommt als neuer globalerkeydowndazu (siehe §3.2).
1.8 UI-Shell + i18n
App.tsx(~2200 Z., enthält noch ToolController/Grips/Transform). JSX:1043: TopBar → body (Dock links, Content-View-Router, TransformBar, Dock rechts, Floating) → StatusBar → ResourceManager → ContextMenu → InlineEditor. Panel-Daten viaPanelHostContext(baseHostApp.tsx:729, Typhost.ts).StatusBar.tsx— Footer: linkshint(Tool-Hinweis), rechts X/Y, Einheit, Massstab 1:N, Zoom, aktives Geschoss, aktive Ebene. Bester Ort für die Command-Line (Rhino hat sie klassisch unten).- i18n
src/i18n/:t(key,params?)index.ts:67,useT():84. Flachesas const-Dict, Punkt-Namespaces (tool.*,snap.*,transform.*,status.*…).de.ts(Quelle, ~309 Keys) +en.ts;TranslationKey=keyof typeof deerzwingt Parität. Neue Keys IMMER in beide Dateien. Keine hartcodierten JSX-Strings.
1.9 Verifizieren
npx tsc -b·npm run build· Devnpm run dev(Vite 5173,host:true).- Screenshot
node scripts/probe.mjs→scripts/probe.png(Puppeteer headless,deviceScaleFactor:2, URL viaPROBE_URL). Viele task-Probes existieren (probe-tools.mjs,probe-line.mjs,probe-transform.mjs…) — gute Vorlagen, um Tools/Befehle programmatisch zu treiben. Screenshot ansehen + Geometrie prüfen, nicht nur „kompiliert".
TEIL 2 — Rhino-Referenz (das Interaktionsmodell)
2.1 Die Command-Line ist das Rückgrat
Alles ist ein Befehl, und die Command-Line hört immer zu: Tastenanschläge gehen an die Command-Line, wenn sie nicht von einem Feld konsumiert werden. Kein „Tool aktiv vs. Eingabe aktiv". Die Zeile hat gleichzeitig drei Rollen: Eingabe (Befehl/Wert tippen), Prompt („Start of line", „Next point"), Optionen (eckige, klickbare Inline-Optionen).
2.2 Befehl aufrufen
- Namen tippen, z. B.
Line. Präfix-Autocomplete (case-insensitiv):L→Li→Linzeigt Kandidatenliste mit Best-Match. Tab/Pfeile akzeptieren Vorschlag, Enter/Leertaste führt aus. - Aliase: nutzerdefinierte Kürzel → Makro (z. B.
L→!_Line,cp→!_Copy). Werden VOR Autocomplete gematcht. (Minimal: Einzelbuchstabe→Befehl.)
2.3 Enter / Leertaste / Rechtsklick (leicht falsch gemacht)
- Enter = Leertaste in der Command-Line. Beide: Befehl ausführen / Default akzeptieren / mehrteiligen Befehl beenden / bei leerer Zeile letzten Befehl wiederholen.
- Rechtsklick im Viewport = Enter. Also: Rechtsklick beendet Polyline UND wiederholt bei
leerer Zeile den letzten Befehl. →
lastCommandspeichern, bei Leer-Enter/Rechtsklick neu starten.
2.4 Inline-Optionen (klickbare Klammern)
Start of line ( BothSides=No Chamfer Mode=Distance ):
- Jede Option klickbar UND tippbar (genug Buchstaben zur Eindeutigkeit + Enter).
- Toggle
Name=Valueflippt beim Klick. Value-Option fragt Unterwert ab. Action-Option (ohne=) verzweigt sofort. - Optionen sind innerhalb des Befehls persistent, viele über Aufrufe hinweg (letzte Offset-Distanz, Array-Anzahl, Fillet-Radius merken). Zuletzt benutzte Optionswerte je Befehl persistieren — Nutzer erwarten das.
2.5 Sub-Prompts = State-Machine
Befehle laufen Prompts ab. Line: „Start of line:" → Punkt → „End of line:" → Punkt → fertig.
Polyline: „Start" → „Next point ( Close Undo ):" → … → Enter beendet. Prompt-Text ist
sichtbar und lehrreich („Next point. Press Enter when done") — literal nachbilden.
2.6 Transparente/verschachtelbare Befehle
Manche Befehle (Zoom/Pan, Osnap-Toggle, alles mit '-Präfix) laufen innerhalb eines anderen,
ohne ihn abzubrechen, und kehren zum Original-Prompt zurück. → Command-Runner braucht einen Stack.
2.7 Koordinaten- & Numerik-Eingabe (Herz der Präzision)
| Eingabe | Bedeutung |
|---|---|
5,3 / 5,3,2 |
absolut X,Y(,Z) |
r5,3 |
relativ zum letzten Punkt (das r-Idiom) |
<45 |
Winkel-Constraint auf 45°, dann Maus/Distanz |
5<45 |
polar: Distanz 5 unter 45° vom letzten Punkt |
| Zahl tippen während Drag | Distanz-Lock: Richtung per Maus, Länge per Zahl+Enter (meistgenutzte Geste) |
| Zahl + Tab | Lock umschalten (Länge fix → Winkel folgt Maus, oder umgekehrt) |
| Das Feld parst kontextabhängig: Befehlsname / Optionsbuchstabe / Koordinate / nackte Zahl — | |
| je nach Befehlszustand. Das Live-Tool muss einen primären Skalar (Länge/Radius/Distanz) | |
| exponieren, an den eine getippte Zahl bindet. |
2.8 Osnaps + Ortho + Gumball
- Osnaps (persistente Toggles): End, Mid, Cen, Int, Perp, Near, Quad, Tan, Point. Pro Mausschritt gegen nahe Geometrie geprüft (Pixel-Toleranz), Marker+Label am Cursor; liefert exakte Modellkoordinate (nie Roh-Maus, wenn Snap aktiv). One-Shot-Osnap überschreibt für den nächsten Pick.
- Ortho (F8): Winkelraster (90°/konfigurierbar), Shift togglet temporär. Grid Snap (F9). SmartTrack: temporäre Hilfslinien aus zuletzt gehoverten Punkten.
- Gumball: On-Object-Widget (Pfeile=Move, Bögen=Rotate, Handles=Scale); Handle klicken → Zahl tippen für exakten Transform. Direkt-Manipulations-Gegenstück zu getippten Befehlen.
Präzisionsmodell = (Snap ODER getippte Koordinate) × (Ortho/Winkel-Constraint) × (Distanz-Constraint), in EINEM Pick komponierbar.
2.9 Auswahl-Modell (links/rechts-Regel exakt)
- Klick = wählen; Shift+Klick add; Ctrl+Klick remove.
- Links→rechts = Window (nur voll umschlossene; durchgezogenes Rechteck).
- Rechts→links = Crossing (auch berührte; gestricheltes Rechteck). Richtung bestimmt Modus — starke Konvention, exakt nachbilden.
- SelLast (zuletzt erzeugte/gewählte erneut wählen) ist enorm nützlich („erzeugen, dann sofort
bewegen"). Min.
SelLast,SelAll,SelNone,Invert.
2.10 Befehls-Prompt-Sequenzen (Kurz)
2D: Line (2 Pkt) · Polyline (Close/Undo, Enter beendet) · Rectangle (Ecke+Ecke, oder Breite/Höhe tippen; 3Point/Center) · Circle (Center+Radius; 2P/3P/Tan) · Arc (Center-Start-End / 3Point) · Offset (Kurve wählen → Seite klicken/Distanz tippen; Distanz persistent) · Fillet/Chamfer (Kurve1→Kurve2, Radius/Distances persistent) · Trim (Schneider wählen→Enter→ wegzuschneidendes Stück klicken) · Split · Extend · Join · Explode · Move/Copy/Rotate/Scale/Mirror (Auswahl→Basispunkt→Ziel; Copy-Option) · ArrayRect/ArrayPolar · Group/Ungroup. 3D (braucht CSG, später): ExtrudeCrv (geschlossene Kurve→Solid, Cap) · Box · Boolean Union/Difference/Intersection · Cap · Gumball-Face-Drag = PushPull · Loft/Sweep/Revolve.
TEIL 3 — Bauplan für DIESE Codebase
Ziel: nutzbarer 2D-Architektur-Drafter mit Rhino-Feel, dann einfaches Massing. Halte das ROADMAP-Prinzip: ein semantisches Modell → Sichten abgeleitet; Extrusionshöhe ist eine Eigenschaft, nie eingebackene Geometrie.
3.0 Kuratierungs-Prinzip (WICHTIG — Nutzer-Vorgabe)
NICHT den ganzen Rhino-Katalog stumpf portieren. Wir bauen ein Wohnbau-BIM, keinen NURBS-Allzweck-Modeller. Nimm nur, was dem Wohnbau-Workflow dient; lass den Rest weg, bis er konkret gebraucht wird. Faustregel: Brauche ich das, um ein Einfamilienhaus zu zeichnen und daraus Pläne zu ziehen? Wenn nein → weglassen.
Bewusst WEGLASSEN (vorerst): Loft / Sweep1+2 / Revolve (Sonderformen, kaum Wohnbau) ·
freie NURBS-Kurven (Curve/InterpCrv Grad>1, Deformable, FromFoci) · Ellipse · Tangent/
Bisector/4Point-Linienvarianten · SmartTrack (nett, nicht kritisch) · der volle Sel*-Zoo
(nur SelLast/SelAll/SelNone/Invert) · Knot/Vertex/Tan-Osnaps. Alle leicht später additiv
nachrüstbar — kein Grund, sie jetzt mitzuschleppen.
Booleans sind KEIN „nice to have später" — sie werden gebraucht, sobald Tür/Fenster als
echte 3D-Öffnung kommen (heute schneidet Door nur eine Plan-Lücke, kein 3D-Boolean, siehe
HANDOVER). Darum: CSG/Booleans an die Tür/Fenster-Phase koppeln und dann reinnehmen — nicht
ans Ende schieben. ABER (das ist der „nicht stumpf"-Teil):
Für rechteckige Öffnungen in extrudierten Wänden braucht es keinen allgemeinen Boolean-Kernel. Eine analytische Wand-minus-Box-Subtraktion (Öffnung als parametrische Aussparung im Wand-Solid) ist einfacher, robuster und für 95 % Wohnbau ausreichend. Den allgemeinen CSG-Boolean (
rhino3dm) erst ziehen, wenn schräge/runde/verschnittene Fälle wirklich auftreten. Also: Öffnungen zuerst analytisch, allgemeine Booleans erst bei Bedarf.
3.1 Leitentscheidung: Engine verallgemeinern, nicht parallel bauen
Baue eine gemeinsame Command-Engine, die das bestehende Tool-Interface erweitert/ablöst,
sodass es einen Eingabepfad gibt (Maus + Tastatur + Command-Line speisen dieselbe Maschine).
Konkret: ein Command-Modell, das je Schritt einen Prompt (Text), erwartete Eingabearten
(Punkt | Zahl | Option | Auswahl) und Optionen beschreibt. Die heutigen Tools werden zu
Befehlen dieser Engine (wall/line/polyline/rect lassen sich 1:1 portieren — ihre Phasenlogik ist
schon eine Mini-State-Machine).
Warum nicht das alte Tool-Interface unangetastet lassen und Command-Line nur draufsetzen? Weil die Command-Line getippte Koordinaten/Optionen in denselben Schritt einspeisen muss, in dem die Maus pickt. Zwei getrennte Pfade divergieren garantiert (Snapping, Constraints, HUD doppelt).
3.2 Neue Dateien (Vorschlag)
src/commands/engine.ts— Command-Runner: aktiver Befehl, Prompt-Stack (für transparente Befehle §2.6),lastCommand-Wiederholung, Routing von Maus-Pick / getippter Eingabe / Option-Klick in den aktuellen Schritt. HältCommandState.src/commands/types.ts—Command-Interface (Verallgemeinerung vonTool): Schritte mitprompt: TranslationKey,accepts: ("point"|"number"|"option"|"selection")[],options: CmdOption[],onInput(state,input,ctx): [state, CommandResult].CommandResultwieToolResult(+commit).src/commands/parseInput.ts— Koordinaten-Parser (§2.7):5,3·r5,3·5<45·<45· nackte Zahl (Distanz-Lock) · Optionsbuchstabe. Liefert eine Discriminated Union, die die Engine in einen Modellpunkt/Constraint auflöst (mitlastPointfürr/polar).src/commands/registry.ts—COMMANDS: Record<string,Command>+ Aliase + Autocomplete (Präfix).src/ui/CommandLine.tsx— die Command-Line-UI in/über der Statusleiste (StatusBar.tsx): zeigt Prompt + klickbare Optionen + Texteingabe; Autocomplete-Dropdown. Tab fokussiert sie.- (später)
src/geometry/kernel2d.ts— 2D-Kernel für Offset/Trim/Fillet/Schnitt (§3.4). - (viel später)
src/geometry/solid3d.tso.rhino3dm-Anbindung für Massing/Booleans (§3.5).
3.3 Verdrahtung (minimal-invasiv)
- Globaler Tab-Handler: neuer
window.keydownin App (gleicher Guard wieApp.tsx:448— INPUT/TEXTAREA/contentEditable überspringen). Tab → Command-Line fokussieren/öffnen. Jeder getippte Buchstabe ohne aktives Tool startet den Befehlsmodus (Rhino „hört immer zu" — optional in Phase 2; Phase 1 reicht Tab). - Command-Line → Engine → Store: Befehle dispatchen auf
setActiveTool(für tool-artige) bzw. direkt auf Store-Actions /setProject. NutzegetState()/setState()(referenzstabil) ausappStore.ts. - Pick-Eingabe: die Engine konsumiert dieselben
(rawModelAt, currentPxPerMeter, toolMods), die PlanView schon hochmeldet (ToolHandlers).computeSnapfür Punktfang wiederverwenden. → PlanView braucht im Idealfall keine Änderung (höchstens: Window/Crossing-Marquee-Visual durchgezogen vs. gestrichelt nach Drag-Richtung, §2.9 — heute evtl. nur ein Modus). - Prompt/HUD: Prompt-Text in die Statusleiste (
StatusBarhintexistiert schon). Distanz/ Winkel-HUD am Cursor existiert inToolDraft.hud.
3.4 Reihenfolge (Tiers — strikt 2D zuerst)
Tier 0 — Substrat (VOR jedem Befehl; das ist der „Feel"):
- Command-Runner + Command-Line-UI (Prompt → pick/type → Optionen; Enter/Space/Rechtsklick =
bestätigen/beenden/wiederholen;
lastCommand). - Koordinaten-Parser (
x,y·rdx,dy·dist<angle· nackte-Zahl-Lock). - Osnaps (End/Mid/Cen/Int/Perp/Near) —
computeSnapist da, ggf. Cen/Perp/Near ergänzen. - Ortho (90°/45°, Shift-Toggle) + Grid-Snap — teils vorhanden (
applyAngleConstraint). - Auswahl: Klick, Shift/Ctrl add/remove, Window vs. Crossing (durchgezogen/gestrichelt, links/rechts-Regel).
Tier 1 — 2D-Pflicht (reines SVG/2D), grobe Baufolge: 6. Line (validiert die ganze pick/snap/constrain-Schleife) → 7. Polyline (Close/Undo) → 8. Rectangle (Ecke + Center/3Point) → 9. Circle (Center+Radius). Diese vier portieren die heutigen Tools auf die Engine + numerische Eingabe. 10. Move → 11. Copy (wiederholend) → 12. Offset (persistente Distanz — DAS Architektur- Primitiv) → 13. Trim + Split → 14. Join + Explode. Undo/Redo durchgängig annehmen (heute? — prüfen; ggf. Command-History/Undo-Stack im Store ergänzen).
Tier 2 — 2D stark nützlich: Rotate/Scale/Mirror (Copy-Option) · Fillet/Chamfer · Arc · Extend · ArrayRect/ArrayPolar · Group/Ungroup · Gumball(2D) · Sel*-Helfer (min. SelLast).
Tier 3 — Massing + Öffnungen (an Tür/Fenster-Phase gekoppelt):
- Öffnungen zuerst analytisch: Tür/Fenster als parametrische Aussparung im Wand-Solid (Wand-Extrude minus Öffnungs-Box) — KEIN allgemeiner Boolean-Kernel nötig (§3.0). Das ist der kritische, roadmap-markierte 🔴-Teil (echte 3D-Öffnung statt nur Plan-Lücke) und kommt MIT Tür/Fenster, nicht danach.
- Massing-Befehle: ExtrudeCrv (geschlossene Plan-Kurve → gecapptes Solid) → Box → Gumball-Face-Drag-PushPull.
- Allgemeine Booleans (Union/Difference/Intersection) erst bei Bedarf (schräge/runde/
verschnittene Fälle): kein eigener Kernel —
rhino3dm(WASM-openNURBS) alssrc/io/-Schicht (Roadmap-Entscheid, HANDOVER). Bis dahin reicht die analytische Subtraktion. - Weggelassen: Loft/Sweep/Revolve/OffsetSrf (§3.0 — Sonderformen, kaum Wohnbau).
3.5 Was sauber 2D ist vs. was hart ist
- Sauber SVG/2D: Line, Polyline, Rect, Circle, Arc, Move/Copy/Rotate/Scale/Mirror, Array, Group, Control-Point-Edit, Gumball(2D). Affine Transforms + Kurven-Schnitt.
- Echte Arbeit (2D-Kernel nötig): Offset, Trim, Fillet brauchen kompetenten Kurven-Schnitt
und Polylinien-Offset — dafür Zeit einplanen (
src/geometry/kernel2d.ts). - Braucht 3D/CSG: Extrude, Box, Boolean*, Cap, OffsetSrf, Loft/Sweep/Revolve, Face-Drag. Booleans
sind das Korrektheits-Zentrum →
rhino3dm.
3.6 Gotchas (aus Rhino-Verhalten + dieser Codebase)
- Command-Line hört immer zu — Tasten global routen, aber die
isTextEntry-Disziplin (main.tsx:24) + die Native-App-Regeln (kein Ctrl+A/keine Textauswahl, CONVENTIONS.md) wahren. - Zuletzt benutzte Optionswerte je Befehl persistieren (Offset-Distanz, Array-Anzahl, Fillet-Radius).
- Enter = Rechtsklick = Wiederholen/Bestätigen/Mehrteiliges-Beenden — alle drei auf EIN Signal.
- Distanz-Lock: Live-Befehl muss EINEN primären Skalar exponieren, an den eine getippte Zahl bindet.
- Window vs. Crossing über Drag-Richtung + durchgezogen/gestrichelt — nicht global ein Modus.
- Osnap liefert exakte Modellkoordinate — nie Roh-Maus, wenn Snap aktiv (
ToolPointer.point). - Strict tsc (
noUnusedLocals) — ungenutzte Vars/Parameter brechennpm run build. - i18n: jeder sichtbare String über
t('key'), Keys inde.tsUNDen.ts(Parität erzwungen). - Keine generische addWall/addDrawing2d-Action — entweder via
setProjectcommitten (wie heute) oder beim Refactor saubere Actions improjectSliceergänzen (besser für Undo/Redo). - App.tsx ist bereits ~2200 Z. (God-Component-Kritik in HANDOVER). Lege Command-Engine in
src/commands/, halte App-Verdrahtung dünn (nur Tab-Handler + CommandLine-Mount + Dispatch-Brücke).
3.7 Verifikations-Drehbuch
Pro Tier eine Probe (Vorlage: scripts/probe-tools.mjs/probe-transform.mjs): Befehl per Command-
Line tippen → Punkte/Werte tippen → Screenshot → Geometrie visuell prüfen. Tier 0 zuerst headless
treiben (Tab → „line" → „0,0" Enter → „r3,0" Enter → Linie im PNG sichtbar). npx tsc -b +
npm run build grün halten. Screenshot ansehen, nicht nur Kompilat vertrauen (Memory wire-dont-stub).
Anhang — Minimaler erster Meilenstein (konkret)
src/commands/types.ts+engine.ts+parseInput.ts(Tier 0.1/0.2).src/ui/CommandLine.tsx, inStatusBargemountet; Tab-Handler in App.Lineals erster Command (portiertlineTool), inkl. getippter0,0/r3,0/3<45.- Probe
scripts/probe-command-line.mjs: Tab→line→zwei getippte Koordinaten→PNG prüfen. - Dann Polyline/Rect/Circle, danach Move/Copy/Offset.
Damit steht der Rhino-Feel-Kern, und jeder weitere Befehl ist additiv (neues Command-Objekt in
registry.ts, keine PlanView-/App-Änderung).