Commit Graph

11 Commits

Author SHA1 Message Date
karim ad16202c12 2D: Bezier-Hilfslinien beim Editieren + Kreis kann zur Ellipse werden
Bezier: Anker→Griff-Verbindungen werden beim Editieren (Selektion) gestrichelt
angezeigt, nicht nur während des Zeichnens — macht sichtbar, welcher Griff
welchen Kurvenabschnitt steuert (wie bei üblichen Vektor-Editoren).

Kreis: bekommt wie die Ellipse zwei Griffe (Ost/Nord). Zieht man einen davon
unabhängig, wird der Kreis zur Ellipse (rotation 0); nähern sich die
Halbachsen beim Ziehen wieder an, wird daraus wieder ein Kreis. Erstellen
bleibt unverändert ein normaler Kreis — der Formwandel passiert nur beim
Bearbeiten.
2026-08-22 01:54:27 +02:00
karim 121cb5a372 2D: Ellipse, Spline und Bezier als neue Zeichenwerkzeuge
Ellipse (zwei Bounding-Box-Ecken, achsparallel), Spline (Wegpunkte, geglättet
via Catmull-Rom, wie Polylinie mit rundem statt geradem Verlauf) und Bezier
(klassische manuelle Kontrollpunkt-Kette: Anker/Griff/Griff je Segment).

Neue Drawing2DGeom-Formen + Mathe-Helfer (tools/curves.ts). Rendering
tessellliert zu Linien-/Polygon-Primitiven (wie Polylinie/Rechteck) statt
eigenem SVG-Primitiv — dadurch funktionieren DXF-/Print-SVG-Export und
Hit-Testing ohne Änderung mit. Griff-Editieren (2D-PlanView + 3D-Viewport),
Verschieben/Skalieren, Snapping (Ellipse-Mittelpunkt, Bezier/Spline-Konturen)
und Georef-Rebase entsprechend erweitert.
2026-08-22 01:45:32 +02:00
karim c5c60d3df5 2D: nach dem ersten getippten Wert bleibt das andere Feld ebenfalls fix
Nutzer-Feedback zum vorigen Commit: das Einfrieren beim Tab-Öffnen
gefiel, aber sobald man einen Wert (z. B. Länge) eintippte und Enter
drückte, sprang der ANDERE Wert (Winkel) sofort wieder auf die Maus um
-- gewünscht war stattdessen: Enter fixiert BEIDE Werte (den getippten
UND den beim Öffnen eingefrorenen), und erst ein explizites Tab in das
andere Feld gibt die Maus für GENAU dieses Feld wieder frei.

Vereinfachung statt Zusatzmechanismus: `locks` wird beim ÖFFNEN (erstes
Tab) nicht mehr leer gelassen, sondern SOFORT mit beiden aktuellen
Werten (Länge+Winkel bzw. bei geführten Kanten nur der eine Abstands-
Wert) befüllt -- die ohnehin vorhandene Locked/Live-Mischung greift
dadurch von Anfang an vollständig, ein separater "eingefroren"-Zustand
(gripFrozenPointRef/edgeFrozenPointRef aus dem vorigen Commit) wird
dadurch überflüssig und wieder entfernt. Zykeln (weiteres Tab bei schon
offenem Feld) wechselt jetzt zum anderen Feld UND löst NUR dessen Lock
(delete locks[nextActive]) -- das verlassene Feld bleibt exakt auf
seinem letzten Wert (getippt oder eingefroren) fixiert. Getippte Werte
(submitGripEditValue/submitEdgeEditValue) sowie Escape-Verhalten
(global = ganzen Drag verwerfen, in der Befehlszeile = nur Feld
schließen) bleiben unverändert.

tsc/vitest 922/922 grün.
2026-08-22 00:24:51 +02:00
karim c43573859f 2D: Tab in ein Griff-/Kanten-Feld friert die Maus ein, bis der erste Wert getippt ist
Nutzer-Wunsch: "wenn ich mit Tab in die Werte springe, soll die Maus
dann nicht mehr steuern, bis man den ersten Wert eingegeben hat -- das
kann man aber mit Esc lösen." Bisher folgte der Punkt/die Kante nach
Tab weiter der Maus, solange noch kein Wert gelockt war -- ein
Zittern der Hand beim Greifen zur Tastatur konnte die Position also
noch verändern, bevor überhaupt getippt wurde.

Neue gripFrozenPointRef/edgeFrozenPointRef (useGripEditing.ts): beim
ÖFFNEN des Feld-Controllers (erstes Tab, noch kein vorheriges Feld
offen) wird die AKTUELLE Position eingefroren; onGripMove/onEdgeMove
halten den Punkt/die Kante bei aktivem Feld-Controller OHNE Lock exakt
dort fest, unabhängig von der Mausposition. Der erste getippte Wert
(submitGripEditValue/submitEdgeEditValue) löst das Einfrieren -- ab da
gilt wieder die bisherige Locked/Live-Mischung (gelockte Felder fest,
ungelockte folgen der Maus).

Neue closeGripEditField/closeEdgeEditField-Funktionen lösen das
Einfrieren explizit auf und schließen NUR den Feld-Prompt (Punkt/Kante
bleibt bewaffnet, Maus steuert wieder normal) -- ersetzen in App.tsx
die bisherigen direkten setGripEdit(null)/setEdgeEdit(null)-Aufrufe im
CommandLine-onCancel (Escape MIT Fokus in der Befehlszeile). Das
bestehende, GLOBALE Escape (ohne Fokus in einem Eingabefeld) bleibt
unverändert: es verwirft den ganzen Griff-/Kanten-Drag komplett auf
die Ursprungsposition (PlanView.tsx, voriger Commit) -- zwei bewusst
verschiedene Eskalationsstufen.

tsc/vitest 922/922 grün.
2026-08-22 00:16:29 +02:00
karim 0c69162ba6 2D: Shift beim Punkt-Griff-Ziehen snappt jetzt auch auf die Flucht
anliegender schräger Kanten, nicht nur Welt-H/V

Nutzer-Wunsch: "wenn ein Punkt aus zwei Linien besteht, die nicht in
[0°/90°] liegen, sollte auch in diese Richtung mit Shift gesnappt
werden können" -- bisher zwang Shift beim Ziehen eines Vielecks-
Eckpunkts (Drawing2D-Polylinie/rect, Raum-/Deckenumriss) IMMER auf
Welt-Horizontal/Vertikal (applyAngleConstraint mit 90°), unabhängig
davon, in welchem Winkel die beiden an diesem Punkt anliegenden Kanten
tatsächlich standen.

Neue Kandidaten-Achsen-Logik (useGripEditing.ts): Shift wählt jetzt die
NÄCHSTLIEGENDE aus mehreren Achsen (kleinster senkrechter Abstand des
Cursors zur Achse) -- Welt-H/V bleiben als Kandidaten erhalten, dazu
kommen die Richtungen der beiden FIXEN Nachbarkanten (deren jeweils
ANDERER Endpunkt bewegt sich ja nicht), gemessen an der Position des
gezogenen Punkts VOR dem Drag (neuer gripOriginRef, sonst würde die
Flucht-Richtung während des Ziehens mitdriften). resolvePolygonNeighbors
kennt geschlossene Ringe (rect/geschlossene Polylinie/Raum/Decke,
Nachbar 0/n-1 wickelt um) vs. offene Ketten (offene Polylinie/Linie);
Kreis/Bogen/Wand/Öffnung/Treppe haben keine Vielecks-Nachbarn und
bleiben unverändert bei reinem Welt-H/V.

Bewusst NICHT angefasst: Kanten-/Körper-Verschieben (die "M"-Bewegung
und die Kanten-Dreieck-Griffe) -- der Nutzer bezog sich explizit auf
"einen einzelnen Punkt", diese behalten ihr bisheriges Welt-H/V-Ortho.

tsc/vitest 922/922 grün. Manuelle Prüfung nötig (Pointer-Interaktion in
PlanView.tsx ist nicht automatisiert testbar, wie schon bei den
vorherigen Griff-Editier-Fixes dieser Session).
2026-08-22 00:09:30 +02:00
karim 31a5b64836 2D: Kanten-Griffe (Seite parallel verschieben) bekommen dieselbe
Bearbeitung wie Punkt-Griffe

Nutzer-Wunsch: "das Ganze soll auch an den Seiten bei diesen Dreiecken
gehen bei denen man eine Fläche parallel verschieben kann" -- also
dieselbe Klick-loslassen-tippen-bestätigen-Geste, dieselbe Cursor-HUD-
Anzeige und dieselbe getippte Werteingabe wie bei den Punkt-Griffen aus
den letzten Commits, jetzt auch für die Kanten-Anfasser.

useGripEditing.ts: onEdgeMove rechnet jetzt mit einem ABSOLUTEN
Zielpunkt relativ zum ORIGINALEN Kanten-Mittelpunkt (Momentaufnahme vom
Grab), nicht mehr rein inkrementell pro Frame -- eine neue
edgeAppliedDeltaRef verankert, wie viel Delta bereits angewandt wurde,
und wendet jeweils nur die Differenz zum neuen Ziel an (moveEdgeOf/
moveRoomEdge/moveCeilingEdge sind additiv). Neuer edgeEdit-Feld-
Controller (Pendant zu gripEdit): geführte Kanten (rect/geschlossene
Polylinie/Wand) haben nur EIN Feld ("Länge" = vorzeichenbehafteter
Abstand entlang der festen Normale, kein Winkel), freie Kanten (offene
Polylinie/Linie) wie ein Punkt-Griff Länge+Winkel relativ zum
Ursprung. Cursor-HUD zeigt bei geführten Kanten "Δ: …m" als Freitext
(das L/W-Schema passt nicht für ein Einzelfeld), bei freien Kanten
L/W wie beim Punkt-Griff. onEdgeCancel verwirft exakt auf den
Ursprung zurück (Ziel = Anker selbst).

PlanView.tsx: Kanten-Griff-Grab läuft jetzt ohne Pointer-Capture
(dasselbe "bewaffnet nach Loslassen"-Verhalten wie beim Punkt-Griff),
Abschluss per erneutem Klick/Enter, Verwerfen per Escape. Neue
confirmEdgeEdit()-Methode am Imperativ-Handle (Pendant zu
confirmGripEdit) fürs Enter-mit-leerem-Text aus der Befehlszeile.

App.tsx: CommandLine-Verdrahtung (active/promptKey/fields/onSubmit/
onCycleField/onCancel) um den edgeEdit-Zweig erweitert, neuer i18n-
Prompt "cmd.edit.edge" (de/en). useToolNumberShortcuts blockt
Zifferntasten jetzt auch während eines bewaffneten Kanten-Griffs
(gleicher Bug wie zuvor bei Punkt-Griffen, präventiv mitgefixt).

useCommandTabShortcut (useKeyboardShortcuts.ts) generalisiert: Tab
zykelt/öffnet jetzt sowohl den Punkt- als auch den neuen Kanten-Feld-
Controller (dieselbe Instanz, zusätzliche optionale Parameter).
2026-08-21 23:56:48 +02:00
karim f9b202ac25 2D: Zifferntasten während Punkt-Griff-Bearbeitung nicht mehr als Werkzeug-Wechsel
Bug (Nutzer-Report direkt nach dem vorigen Commit): sobald man nach Tab
einen Länge-/Winkel-Wert eintippte, wechselte die App mitten in die
Griff-Bearbeitung ins Zeichenwerkzeug (z.B. "3" -> Kreis-Werkzeug).

Ursache: useToolNumberShortcuts (Vectorworks-Zifferntasten fürs
Werkzeug wählen) erlaubt Zifferntasten auch bei fokussiertem, leerem
Befehlsfeld, SOFERN die Zeichen-Engine gerade keinen Befehl mit
Feldern laufen hat (`!eng.hasFields() && !eng.acceptsFreeText()`).
Genau das trifft während der Griff-Bearbeitung IMMER zu, da dort gar
keine Engine-Befehl läuft, sondern der separate Feld-Controller aus
useGripEditing.ts -- der Wächter kannte diesen zweiten Fall nicht.

Fix: gripDragInfoRef (schon vorhanden, zeigt "ein Punkt-Griff ist
bewaffnet" an) wird von useGripEditing zurückgegeben und an
useToolNumberShortcuts durchgereicht; dessen Handler bricht jetzt ganz
vorne ab, wenn ein Griff bewaffnet ist -- unabhängig davon, ob/wo
gerade Fokus liegt. Der useToolNumberShortcuts-Aufruf in App.tsx
musste dafür hinter den useGripEditing()-Aufruf wandern (Reihenfolge
war vorher umgekehrt, gripDragInfoRef existierte an der alten Stelle
noch nicht).
2026-08-21 23:13:02 +02:00
karim a6c984e517 2D: Punkt-Griff ziehen ohne gehaltene Maustaste, Cursor-HUD dabei
Nutzer-Wunsch: sobald ein Punkt-Griff angewählt ist, soll man die
Maustaste loslassen können und trotzdem weiter per Cursor UND per
getipptem Wert positionieren können -- wie beim Zeichnen (Klick setzt
Punkt, Maus bewegt sich frei, Wert tippbar, nächster Klick/Enter
bestätigt), nicht wie bisher als reine Halten-und-Ziehen-Geste.

PlanView.tsx: der Griff wird beim Klick "bewaffnet" statt per
Pointer-Capture gehalten zu werden; Loslassen der Maus beendet die
Bearbeitung nicht mehr, der Punkt folgt weiter per normalem Hover-
Pointermove. Abgeschlossen wird per erneutem Linksklick (irgendwo,
verhindert dass derselbe Klick eine andere Aktion auslöst) oder Enter,
verworfen per Escape (beide nur ausserhalb von Eingabefeldern, damit
die Befehlszeile ihr eigenes Enter behält). Neuer onGripCancel-Handler
setzt dafür exakt auf die Ausgangsposition zurück.

useGripEditing.ts: die Anker-Auflösung (Nachbar-Ecke bzw. Kreis-/Bogen-
Zentrum) ist jetzt ein gemeinsamer resolveGripAnchor()-Helper, genutzt
vom Tab-Feld-Controller UND einem neuen Cursor-HUD (Länge/Winkel neben
der Maus, dieselbe Darstellung wie beim Zeichnen) -- vorher zeigte das
Cursor-HUD beim Griff-Ziehen gar keine Werte an, nur die Befehlszeile
unten.

App.tsx: das Cursor-HUD-Feld-Objekt zeigt bei aktivem Griff-Tab-Feld
jetzt dieselben Felder wie die Befehlszeile (vorher nur bei laufendem
Zeichenbefehl).

Kanten-Griff und Körper-Verschieben (ganzes Element) bleiben bewusst
unverändert (Halten-und-Ziehen) -- nur explizit angefragt war der
Einzelpunkt-Fall.
2026-08-21 21:50:00 +02:00
karim 13619e9271 2D: Griff-Editieren funktioniert jetzt auch bei Mehrfachauswahl
Bug: grips/edgeGrips in useGripEditing.ts waren über sich gegenseitig
ausschliessende Zweige auf genau ein selektiertes Element zugeschnitten;
bei zwei oder mehr gewählten Elementen (Zeichnungen oder Wänden) traf
kein Zweig, beide Arrays wurden leer -> keine Griffe mehr sichtbar/ziehbar.

Neues GripOwner-Modell (gripOwners/gripOwnerRange parallel zum flachen
grips-Array, EdgeGrip.owner? neu im Modell): jedes gewählte Element
liefert weiterhin einen eigenen, zusammenhängenden Vertex-/Kanten-Block.
applyGrip/applyEdge/cycleGripEditField/onGripMove lösen die Zielroute
(moveGripOf/moveEdgeOf) jetzt pro Griff über dessen owner auf statt über
die singulären selectedDrawingId/selectedWallId. Einzelauswahl ist ein
Spezialfall derselben Logik, kein separater Pfad. Der Tab-Feld-Controller
(getippte Länge/Winkel beim Ziehen) funktioniert dadurch auch bei
Mehrfachauswahl korrekt pro Griff. Öffnung/Treppe/Decke/Raum bleiben
bewusst exklusiv (kein Multi-Select für diese vier).

PlanView.tsx brauchte keine Änderung, da Hit-Test und Rendering dort
bereits generisch über die flachen grips/edgeGrips-Arrays laufen.
2026-08-21 20:50:00 +02:00
karim d9cbf2cb07 2D: Tab-Feld-Controller für Kreis/Bogen-Griffe repariert (Radius/Winkel tippbar)
Nutzer-Report: Editieren bestehender Linien/Kreise fühlt sich noch
eingeschränkt an, man sollte einen Wert eingeben können.

Root Cause bei Kreis/Bogen: cycleGripEditField() ermittelt den Anker für die
Tab-Feld-Eingabe (Länge/Winkel) über eine Polylinien-Heuristik ("Nachbar-
Griff im Array") — für den Kreis-Radius-Griff (nur 1 Griff, kein Nachbar)
gab das GAR KEINEN Anker, Tab tat nichts. Beim Bogen (3 Griffe) fand die
Heuristik zwar irgendeinen Nachbar-Griff als Anker, aber semantisch falsch —
moveGrip() misst dort tatsächlich Winkel/Radius relativ zum ZENTRUM, nicht
relativ zum jeweils anderen Griff.

Fix: cycleGripEditField() erkennt jetzt Kreis/Bogen (selectedDrawing.geom)
und nimmt deren `center` als Anker — "Länge" tippen setzt damit exakt den
Radius, konsistent mit moveGrip()s eigener Distanz-vom-Zentrum-Logik.

tsc -b / vitest run (919/919) / npm run build grün. Keine Unit-Tests ergänzt
(useGripEditing ist ein React-Hook ohne renderHook-Infrastruktur im
Projekt — bestehende Testgrenze, s. andere Hooks). Nutzer prüft in der
laufenden App.
2026-08-21 20:37:15 +02:00
karim 103f704e87 2D-Griff-Editieren aus App.tsx in eigenen Hook extrahiert (state/useGripEditing.ts).
Eckpunkt-/Kanten-Griffe der aktuellen Auswahl (Wand/2D-Element/Decke/Raum/
Öffnung/Treppe), deren Zieh-Handler (Ortho/Snap/koinzidente Nachbar-Ecken)
sowie der Tab-Feld-Controller für präzise Länge/Winkel-Eingabe während eines
Vertex-Drags waren als drei separat benannte, aber tatsächlich zusammen-
hängende Abschnitte in App.tsx verstreut. Jetzt ein Hook mit explizitem
Parameter-Vertrag (Selektion/Projekt/Snap-Settings/Projekt-Mutationen rein,
grips/edgeGrips/gripHandlers/Feld-Controller raus) statt anonymer Closures
über den gesamten Komponenten-Scope.

Reine Verschiebung, keine Verhaltensänderung — einzige Korrektur beim
Verschieben: der Öffnungs-/Wand-Snap in onGripMove nutzte computeSnap direkt
(gegen ein um das gezogene Element bereinigtes Projekt), das im ersten Entwurf
versehentlich mit dem allgemeinen snapFor-Helper vertauscht worden wäre —
beim Nachvollziehen der Original-Logik korrigiert, bevor es committet wurde.

App.tsx: 5096 → 4594 Zeilen.

tsc -b sauber, vitest 891/891 grün, vite build ok.
2026-08-20 00:35:03 +02:00