Zeichen-Feedback: Δx/Δy beim Rechteck, lange Führungslinien, Winkelbogen
VW-Feinschliff (Nutzer-Screenshots): - Rechteck (2-Punkt + Zentrum) zeigt Δx/Δy vorzeichenbehaftet statt Diagonale+Winkel (deltaHud; Zentrum = volle Rechteckmasse). Die width/height-Tab-Felder erscheinen im HUD als Δx/Δy. - Führungslinie des Winkelrasters läuft jetzt weit über den Cursor hinaus (quer über den Ausschnitt statt 40 px). - Winkelbogen ergänzt: gestrichelter Bogen von der horizontalen 0°-Referenz (dezentes Lachsrot, VW-Anlehnung) zur Strahlrichtung, Badge sitzt am halben Winkel aussen am Bogen. 737/737 grün; headless verifiziert (Linie 45° + Rechteck-Δ).
This commit is contained in:
@@ -17,7 +17,7 @@
|
|||||||
// werden (die Engine löst `r…` relativ zu lastPoint auf).
|
// werden (die Engine löst `r…` relativ zu lastPoint auf).
|
||||||
|
|
||||||
import type { Drawing2D } from "../../model/types";
|
import type { Drawing2D } from "../../model/types";
|
||||||
import { segmentHud, uniqueId } from "../../tools/types";
|
import { deltaHud, segmentHud, uniqueId } from "../../tools/types";
|
||||||
import type {
|
import type {
|
||||||
Command,
|
Command,
|
||||||
CommandContext,
|
CommandContext,
|
||||||
@@ -208,7 +208,7 @@ function cornerDraft(a: Vec2, cursor: Vec2 | null): ToolDraft {
|
|||||||
if (!cursor) return { preview: [], vertices: [a] };
|
if (!cursor) return { preview: [], vertices: [a] };
|
||||||
const g = rectGeom(a, cursor);
|
const g = rectGeom(a, cursor);
|
||||||
const pts: Vec2[] = [g.min, { x: g.max.x, y: g.min.y }, g.max, { x: g.min.x, y: g.max.y }];
|
const pts: Vec2[] = [g.min, { x: g.max.x, y: g.min.y }, g.max, { x: g.min.x, y: g.max.y }];
|
||||||
return corners4Draft(pts, segLen(a, cursor) >= EPS ? segmentHud(a, cursor) : null);
|
return corners4Draft(pts, segLen(a, cursor) >= EPS ? deltaHud(a, cursor) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Vorschau des Zentrum-Rechtecks (achsparallel) — HUD zeigt die Diagonale center→cursor. */
|
/** Vorschau des Zentrum-Rechtecks (achsparallel) — HUD zeigt die Diagonale center→cursor. */
|
||||||
@@ -216,7 +216,12 @@ function centerDraft(center: Vec2, cursor: Vec2 | null): ToolDraft {
|
|||||||
if (!cursor) return { preview: [], vertices: [center] };
|
if (!cursor) return { preview: [], vertices: [center] };
|
||||||
const g = centerGeom(center, cursor);
|
const g = centerGeom(center, cursor);
|
||||||
const pts: Vec2[] = [g.min, { x: g.max.x, y: g.min.y }, g.max, { x: g.min.x, y: g.max.y }];
|
const pts: Vec2[] = [g.min, { x: g.max.x, y: g.min.y }, g.max, { x: g.min.x, y: g.max.y }];
|
||||||
return corners4Draft(pts, segLen(center, cursor) >= EPS ? segmentHud(center, cursor) : null);
|
return corners4Draft(
|
||||||
|
pts,
|
||||||
|
segLen(center, cursor) >= EPS
|
||||||
|
? { at: cursor, dx: 2 * (cursor.x - center.x), dy: 2 * (cursor.y - center.y) }
|
||||||
|
: null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Vorschau der Basiskante (3-Punkt, erster Teil). */
|
/** Vorschau der Basiskante (3-Punkt, erster Teil). */
|
||||||
|
|||||||
+47
-7
@@ -2447,11 +2447,22 @@ function DraftHud({
|
|||||||
locked: boolean;
|
locked: boolean;
|
||||||
}
|
}
|
||||||
const liveFor = (id: string): number | null =>
|
const liveFor = (id: string): number | null =>
|
||||||
id === "length" ? (hud.length ?? null) : id === "angle" ? (hud.angleDeg ?? null) : null;
|
id === "length"
|
||||||
|
? (hud.length ?? null)
|
||||||
|
: id === "angle"
|
||||||
|
? (hud.angleDeg ?? null)
|
||||||
|
: id === "width"
|
||||||
|
? (hud.dx ?? null)
|
||||||
|
: id === "height"
|
||||||
|
? (hud.dy ?? null)
|
||||||
|
: null;
|
||||||
|
// Feld-Beschriftung: Länge/Winkel als L/W, Rechteck-Masse als Δx/Δy (VW).
|
||||||
|
const labelFor = (id: string): string =>
|
||||||
|
id === "length" ? "L" : id === "angle" ? "W" : id === "width" ? "Δx" : id === "height" ? "Δy" : id;
|
||||||
const segs: Seg[] = [];
|
const segs: Seg[] = [];
|
||||||
if (hudFields && hudFields.fields.length > 0) {
|
if (hudFields && hudFields.fields.length > 0) {
|
||||||
for (const f of hudFields.fields) {
|
for (const f of hudFields.fields) {
|
||||||
const label = f.id === "length" ? "L" : f.id === "angle" ? "W" : f.id;
|
const label = labelFor(f.id);
|
||||||
const unit = f.id === "angle" ? "°" : "m";
|
const unit = f.id === "angle" ? "°" : "m";
|
||||||
const typedHere = f.active && hudFields.typed.trim() !== "";
|
const typedHere = f.active && hudFields.typed.trim() !== "";
|
||||||
const value = typedHere
|
const value = typedHere
|
||||||
@@ -2461,6 +2472,9 @@ function DraftHud({
|
|||||||
: (liveFor(f.id)?.toFixed(3) ?? "—");
|
: (liveFor(f.id)?.toFixed(3) ?? "—");
|
||||||
segs.push({ text: `${label}: ${value}${unit}`, active: f.active, locked: f.locked });
|
segs.push({ text: `${label}: ${value}${unit}`, active: f.active, locked: f.locked });
|
||||||
}
|
}
|
||||||
|
} else if (hud.dx != null && hud.dy != null) {
|
||||||
|
segs.push({ text: `Δx: ${hud.dx.toFixed(3)}m`, active: false, locked: false });
|
||||||
|
segs.push({ text: `Δy: ${hud.dy.toFixed(3)}m`, active: false, locked: false });
|
||||||
} else if (hud.length != null && hud.angleDeg != null) {
|
} else if (hud.length != null && hud.angleDeg != null) {
|
||||||
segs.push({ text: `L: ${hud.length.toFixed(3)}m`, active: false, locked: false });
|
segs.push({ text: `L: ${hud.length.toFixed(3)}m`, active: false, locked: false });
|
||||||
segs.push({ text: `W: ${hud.angleDeg.toFixed(3)}°`, active: false, locked: false });
|
segs.push({ text: `W: ${hud.angleDeg.toFixed(3)}°`, active: false, locked: false });
|
||||||
@@ -2541,12 +2555,27 @@ function AngleGuide({
|
|||||||
const dy = b.y - a.y;
|
const dy = b.y - a.y;
|
||||||
const len = Math.hypot(dx, dy);
|
const len = Math.hypot(dx, dy);
|
||||||
const dir = len > 1e-6 ? { x: dx / len, y: dy / len } : { x: 1, y: 0 };
|
const dir = len > 1e-6 ? { x: dx / len, y: dy / len } : { x: 1, y: 0 };
|
||||||
const extend = 40 * vbPerPx; // Bildschirm-px, die die Führungslinie über den Cursor hinausragt
|
// Führungslinie WEIT über den Cursor hinaus (VW-Verhalten: die Referenz läuft
|
||||||
|
// quer über den sichtbaren Ausschnitt; der SVG-Clip schneidet den Rest ab).
|
||||||
|
const extend = 4000 * vbPerPx;
|
||||||
const end = { x: b.x + dir.x * extend, y: b.y + dir.y * extend };
|
const end = { x: b.x + dir.x * extend, y: b.y + dir.y * extend };
|
||||||
const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
// Winkelbogen (VW): gestrichelter Bogen von der HORIZONTALEN Referenz (0°) zur
|
||||||
// Senkrecht zur Linie versetzt, damit das Badge die Führungslinie nicht verdeckt.
|
// Strahlrichtung, Radius knapp innerhalb des Cursors. Screen-y läuft nach
|
||||||
const perp = { x: -dir.y, y: dir.x };
|
// unten → der Screen-Winkel ist der negierte Modellwinkel; für den Bogenpfad
|
||||||
const badgeAt = { x: mid.x + perp.x * 10 * vbPerPx, y: mid.y + perp.y * 10 * vbPerPx };
|
// rechnen wir direkt mit Screen-Vektoren.
|
||||||
|
const screenAng = Math.atan2(dy, dx); // (−π, π], y-down
|
||||||
|
const r = Math.min(len * 0.85, 160 * vbPerPx);
|
||||||
|
const arcStart = { x: a.x + r, y: a.y };
|
||||||
|
const arcEnd = { x: a.x + r * Math.cos(screenAng), y: a.y + r * Math.sin(screenAng) };
|
||||||
|
const sweep = screenAng > 0 ? 1 : 0; // y-down: positiver Screen-Winkel = im Uhrzeigersinn
|
||||||
|
const arcPath = `M ${arcStart.x} ${arcStart.y} A ${r} ${r} 0 0 ${sweep} ${arcEnd.x} ${arcEnd.y}`;
|
||||||
|
// Horizontale Referenz-Strichellinie (0°-Basis des Bogens), etwas über den
|
||||||
|
// Bogen hinaus.
|
||||||
|
const refEnd = { x: a.x + r + 60 * vbPerPx, y: a.y };
|
||||||
|
// Badge aussen am Bogen bei halbem Winkel (VW-Position).
|
||||||
|
const halfAng = screenAng / 2;
|
||||||
|
const badgeR = r + 22 * vbPerPx;
|
||||||
|
const badgeAt = { x: a.x + badgeR * Math.cos(halfAng), y: a.y + badgeR * Math.sin(halfAng) };
|
||||||
const fontSize = 11 * vbPerPx;
|
const fontSize = 11 * vbPerPx;
|
||||||
const padX = 5 * vbPerPx;
|
const padX = 5 * vbPerPx;
|
||||||
const padY = 3 * vbPerPx;
|
const padY = 3 * vbPerPx;
|
||||||
@@ -2563,6 +2592,17 @@ function AngleGuide({
|
|||||||
y2={end.y}
|
y2={end.y}
|
||||||
vectorEffect="non-scaling-stroke"
|
vectorEffect="non-scaling-stroke"
|
||||||
/>
|
/>
|
||||||
|
<line
|
||||||
|
className="plan-angle-ref-line"
|
||||||
|
x1={a.x}
|
||||||
|
y1={a.y}
|
||||||
|
x2={refEnd.x}
|
||||||
|
y2={refEnd.y}
|
||||||
|
vectorEffect="non-scaling-stroke"
|
||||||
|
/>
|
||||||
|
{len > 24 * vbPerPx && Math.abs(screenAng) > 0.02 && (
|
||||||
|
<path className="plan-angle-arc" d={arcPath} vectorEffect="non-scaling-stroke" />
|
||||||
|
)}
|
||||||
<rect
|
<rect
|
||||||
className="plan-angle-badge-box"
|
className="plan-angle-badge-box"
|
||||||
x={badgeAt.x - w / 2}
|
x={badgeAt.x - w / 2}
|
||||||
|
|||||||
@@ -2066,6 +2066,21 @@ body {
|
|||||||
stroke-dasharray: 5 4;
|
stroke-dasharray: 5 4;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
/* Horizontale 0°-Referenz des Winkelbogens (dezentes Lachsrot, VW-Anlehnung). */
|
||||||
|
.plan-svg .plan-angle-ref-line {
|
||||||
|
stroke: #cf8d7f;
|
||||||
|
stroke-width: 1;
|
||||||
|
stroke-dasharray: 7 5;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
/* Gestrichelter Winkelbogen von der Referenz zur Strahlrichtung. */
|
||||||
|
.plan-svg .plan-angle-arc {
|
||||||
|
fill: none;
|
||||||
|
stroke: #8a8a8a;
|
||||||
|
stroke-width: 1;
|
||||||
|
stroke-dasharray: 5 4;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
.plan-svg .plan-angle-badge-box {
|
.plan-svg .plan-angle-badge-box {
|
||||||
fill: #2c2c2c;
|
fill: #2c2c2c;
|
||||||
fill-opacity: 0.94;
|
fill-opacity: 0.94;
|
||||||
|
|||||||
+10
-1
@@ -164,7 +164,7 @@ export interface ToolDraft {
|
|||||||
* einfaches Text-Label. Beide Formen sind additiv kombinierbar (text kann als
|
* einfaches Text-Label. Beide Formen sind additiv kombinierbar (text kann als
|
||||||
* Zusatzzeile neben length/angleDeg stehen).
|
* Zusatzzeile neben length/angleDeg stehen).
|
||||||
*/
|
*/
|
||||||
hud?: { at: Vec2; text?: string; length?: number; angleDeg?: number };
|
hud?: { at: Vec2; text?: string; length?: number; angleDeg?: number; dx?: number; dy?: number };
|
||||||
/** Live-Messwerte (nur beim Mess-Werkzeug gesetzt) fürs Objekt-Info-Panel. */
|
/** Live-Messwerte (nur beim Mess-Werkzeug gesetzt) fürs Objekt-Info-Panel. */
|
||||||
measure?: MeasurementReadout;
|
measure?: MeasurementReadout;
|
||||||
}
|
}
|
||||||
@@ -253,6 +253,15 @@ export function segmentHud(from: Vec2, to: Vec2): { at: Vec2; length: number; an
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HUD für achsparallele Masse (Rechtecke, VW-Verhalten): Δx/Δy VORZEICHENBEHAFTET
|
||||||
|
* statt Diagonale+Winkel — die Diagonale eines Rechtecks interessiert beim
|
||||||
|
* Aufziehen nicht, die Kantenmasse schon.
|
||||||
|
*/
|
||||||
|
export function deltaHud(from: Vec2, to: Vec2): { at: Vec2; dx: number; dy: number } {
|
||||||
|
return { at: to, dx: to.x - from.x, dy: to.y - from.y };
|
||||||
|
}
|
||||||
|
|
||||||
// ── ID-Vergabe ───────────────────────────────────────────────────────────────
|
// ── ID-Vergabe ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let idCounter = 0;
|
let idCounter = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user