2D: Bemassung — Massketten (Reihenbemassung) in einer Geste

Das Bemassungs-Werkzeug sammelt jetzt beliebig viele Punkte wie eine
Polylinie (Doppelklick/Enter beendet, "Zurück" nimmt den letzten Punkt
zurück), statt nur zwei. EIN abschliessender Klick für die Lage der
Masslinie gilt für die ganze Reihe (SIA 400 Figur 14) und erzeugt je
Punktpaar ein eigenes, unabhängig editierbares Bemassungs-Element.
This commit is contained in:
2026-08-22 02:22:47 +02:00
parent 6fd5a50d77
commit 4c7fc99eb8
4 changed files with 205 additions and 91 deletions
+65 -7
View File
@@ -1,6 +1,8 @@
/** /**
* `dimensionCommand` — zwei Punkte + Lage der Masslinie → `shape:"dimension"`. * `dimensionCommand` — Punktreihe (wie Polylinie) + EIN Offset-Klick →
* Die Masszahl wird NICHT gespeichert (aus dist(a,b) beim Rendern abgeleitet). * eine oder mehrere `shape:"dimension"`-Elemente (Reihenbemassung, SIA 400
* Figur 14). Die Masszahl wird NICHT gespeichert (aus dist(a,b) beim Rendern
* abgeleitet).
*/ */
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
@@ -43,7 +45,7 @@ function makeCtx(p: Project): CommandContext {
} }
describe("dimensionCommand", () => { describe("dimensionCommand", () => {
it("zwei Punkte + Offset-Klick (links positiv) → shape:dimension mit korrektem Vorzeichen", () => { it("zwei Punkte + Enter + Offset-Klick (links positiv) → ein shape:dimension mit korrektem Vorzeichen", () => {
const p = project(); const p = project();
const ctx = makeCtx(p); const ctx = makeCtx(p);
let [state] = dimensionCommand.onInput( let [state] = dimensionCommand.onInput(
@@ -52,6 +54,7 @@ describe("dimensionCommand", () => {
ctx, ctx,
); );
[state] = dimensionCommand.onInput(state, { kind: "point", point: { x: 10, y: 0 } }, ctx); [state] = dimensionCommand.onInput(state, { kind: "point", point: { x: 10, y: 0 } }, ctx);
[state] = dimensionCommand.onConfirm(state, ctx);
// Cursor bei y=2 (oberhalb von a→b, entlang +x) → Links-Normale zeigt nach +y. // Cursor bei y=2 (oberhalb von a→b, entlang +x) → Links-Normale zeigt nach +y.
const [, result] = dimensionCommand.onInput(state, { kind: "point", point: { x: 5, y: 2 } }, ctx); const [, result] = dimensionCommand.onInput(state, { kind: "point", point: { x: 5, y: 2 } }, ctx);
expect(result.commit).toBeDefined(); expect(result.commit).toBeDefined();
@@ -66,7 +69,7 @@ describe("dimensionCommand", () => {
} }
}); });
it("getippte Offset-Zahl committet direkt (kein Punkt-Klick nötig)", () => { it("Fertig-Option (statt Enter) beendet die Punktreihe identisch", () => {
const p = project(); const p = project();
const ctx = makeCtx(p); const ctx = makeCtx(p);
let [state] = dimensionCommand.onInput( let [state] = dimensionCommand.onInput(
@@ -74,14 +77,54 @@ describe("dimensionCommand", () => {
{ kind: "point", point: { x: 0, y: 0 } }, { kind: "point", point: { x: 0, y: 0 } },
ctx, ctx,
); );
[state] = dimensionCommand.onInput(state, { kind: "point", point: { x: 0, y: 5 } }, ctx); [state] = dimensionCommand.onInput(state, { kind: "point", point: { x: 4, y: 0 } }, ctx);
[state] = dimensionCommand.onInput(state, { kind: "option", id: "finish" }, ctx);
const [, result] = dimensionCommand.onInput(state, { kind: "number", value: 1.5 }, ctx); const [, result] = dimensionCommand.onInput(state, { kind: "number", value: 1.5 }, ctx);
const next = result.commit!(p); const next = result.commit!(p);
const g = next.drawings2d[0].geom; const g = next.drawings2d[0].geom;
if (g.shape === "dimension") expect(g.offset).toBe(1.5); if (g.shape === "dimension") expect(g.offset).toBe(1.5);
}); });
it("Null-Strecke (a===b) committet nichts", () => { it("Kette aus drei Punkten (Reihenbemassung) → zwei Segmente mit demselben Offset", () => {
const p = project();
const ctx = makeCtx(p);
let [state] = dimensionCommand.onInput(
dimensionCommand.init(),
{ kind: "point", point: { x: 0, y: 0 } },
ctx,
);
[state] = dimensionCommand.onInput(state, { kind: "point", point: { x: 4, y: 0 } }, ctx);
[state] = dimensionCommand.onInput(state, { kind: "point", point: { x: 10, y: 0 } }, ctx);
[state] = dimensionCommand.onConfirm(state, ctx);
const [, result] = dimensionCommand.onInput(state, { kind: "number", value: 2 }, ctx);
const next = result.commit!(p);
expect(next.drawings2d.length).toBe(2);
const segs = next.drawings2d.map((d) => d.geom).filter((g) => g.shape === "dimension");
expect(segs[0]).toMatchObject({ a: { x: 0, y: 0 }, b: { x: 4, y: 0 }, offset: 2 });
expect(segs[1]).toMatchObject({ a: { x: 4, y: 0 }, b: { x: 10, y: 0 }, offset: 2 });
});
it("Zurück-Option entfernt den letzten Punkt", () => {
const p = project();
const ctx = makeCtx(p);
let [state] = dimensionCommand.onInput(
dimensionCommand.init(),
{ kind: "point", point: { x: 0, y: 0 } },
ctx,
);
[state] = dimensionCommand.onInput(state, { kind: "point", point: { x: 4, y: 0 } }, ctx);
[state] = dimensionCommand.onInput(state, { kind: "point", point: { x: 9, y: 0 } }, ctx);
[state] = dimensionCommand.onInput(state, { kind: "option", id: "undo" }, ctx);
[state] = dimensionCommand.onConfirm(state, ctx);
const [, result] = dimensionCommand.onInput(state, { kind: "number", value: 1 }, ctx);
const next = result.commit!(p);
// Nach Zurück nur noch [0,0]→[4,0] übrig → ein Segment.
expect(next.drawings2d.length).toBe(1);
const g = next.drawings2d[0].geom;
if (g.shape === "dimension") expect(g.b).toEqual({ x: 4, y: 0 });
});
it("entartetes Segment (identische Punkte) innerhalb der Kette wird übersprungen", () => {
const p = project(); const p = project();
const ctx = makeCtx(p); const ctx = makeCtx(p);
let [state] = dimensionCommand.onInput( let [state] = dimensionCommand.onInput(
@@ -89,7 +132,22 @@ describe("dimensionCommand", () => {
{ kind: "point", point: { x: 3, y: 3 } }, { kind: "point", point: { x: 3, y: 3 } },
ctx, ctx,
); );
const [, result] = dimensionCommand.onInput(state, { kind: "point", point: { x: 3, y: 3 } }, ctx); [state] = dimensionCommand.onInput(state, { kind: "point", point: { x: 3, y: 3 } }, ctx);
[state] = dimensionCommand.onConfirm(state, ctx);
const [, result] = dimensionCommand.onInput(state, { kind: "number", value: 1 }, ctx);
const next = result.commit!(p);
expect(next.drawings2d.length).toBe(0);
});
it("Enter mit nur einem Punkt bricht ohne commit ab", () => {
const p = project();
const ctx = makeCtx(p);
const [state] = dimensionCommand.onInput(
dimensionCommand.init(),
{ kind: "point", point: { x: 0, y: 0 } },
ctx,
);
const [, result] = dimensionCommand.onConfirm(state, ctx);
expect(result.commit).toBeUndefined(); expect(result.commit).toBeUndefined();
expect(result.done).toBe(true); expect(result.done).toBe(true);
}); });
+110 -58
View File
@@ -1,21 +1,27 @@
// Bemassung (lineare Masslinie, SIA 400 B.5.3) — misst den Abstand zweier // Bemassung (lineare Masslinie(n), SIA 400 B.5.3, Figur 14 „Reihenbemassung").
// Punkte. Schritte: // Wie eine Polylinie: beliebig viele Punkte, dann EIN weiterer Klick für die
// 1) „Erster Punkt:" → Punkt (a) // Lage der Masslinie(n) — erzeugt eine ganze Massketten in einer Geste.
// 2) „Zweiter Punkt:" → Punkt (b) — zeigt die Länge live // Schritte:
// 3) „Lage der Masslinie:" → Punkt/Zahl (Normalabstand zu a-b, // 1) „Erster Punkt:" → Punkt
// links von a→b positiv) → commit (shape:"dimension") // 2) „Nächster Punkt ( Fertig Zurück ):" → Punkt … (Doppelklick/Enter
// beendet die Punktreihe, sobald ≥2 Punkte gesetzt sind)
// 3) „Lage der Masslinie:" → Punkt/Zahl (EIN Normalabstand
// für ALLE Segmente) → commit: je aufeinanderfolgendem Punktpaar EIN
// `shape:"dimension"`-Element (unabhängig editierbar, teilt nur den
// beim Zeichnen gewählten Offset).
// //
// Die Masszahl wird beim Rendern aus dist(a,b) abgeleitet (nicht gespeichert) // Die Masszahl wird beim Rendern aus dist(a,b) abgeleitet (nicht gespeichert)
// — bleibt beim späteren Griff-Ziehen automatisch korrekt. // — bleibt beim späteren Griff-Ziehen automatisch korrekt.
import type { Drawing2D } from "../../model/types"; import type { Drawing2D } from "../../model/types";
import { uniqueId } from "../../tools/types"; import { segmentHud, uniqueId } from "../../tools/types";
import type { import type {
Command, Command,
CommandContext, CommandContext,
CommandField, CommandField,
CommandResult, CommandResult,
CommandState, CommandState,
CmdOption,
DraftShape, DraftShape,
Project, Project,
ToolDraft, ToolDraft,
@@ -34,27 +40,47 @@ function signedOffset(a: Vec2, b: Vec2, pt: Vec2): number {
return (pt.x - a.x) * nx + (pt.y - a.y) * ny; return (pt.x - a.x) * nx + (pt.y - a.y) * ny;
} }
/** Endpunkte der um `offset` parallel verschobenen Masslinie (Strecke a-b). */
function offsetPoints(a: Vec2, b: Vec2, offset: number): { a: Vec2; b: Vec2 } {
const dx = b.x - a.x, dy = b.y - a.y;
const len = Math.hypot(dx, dy) || 1;
const nx = (-dy / len) * offset, ny = (dx / len) * offset;
return { a: { x: a.x + nx, y: a.y + ny }, b: { x: b.x + nx, y: b.y + ny } };
}
interface DimStart extends CommandState { interface DimStart extends CommandState {
phase: "start"; phase: "start";
} }
interface DimEnd extends CommandState { interface DimNext extends CommandState {
phase: "end"; phase: "next";
a: Vec2; pts: Vec2[];
cursor: Vec2 | null; cursor: Vec2 | null;
} }
interface DimOffset extends CommandState { interface DimOffset extends CommandState {
phase: "offset"; phase: "offset";
a: Vec2; pts: Vec2[];
b: Vec2;
cursor: Vec2 | null; cursor: Vec2 | null;
} }
type DimState = DimStart | DimEnd | DimOffset; type DimState = DimStart | DimNext | DimOffset;
const UNDO: CmdOption = { id: "undo", labelKey: "cmd.dimension.undo" };
const FINISH: CmdOption = { id: "finish", labelKey: "cmd.dimension.finish" };
const OFFSET_FIELDS: CommandField[] = [{ id: "offset", labelKey: "cmd.field.offset" }]; const OFFSET_FIELDS: CommandField[] = [{ id: "offset", labelKey: "cmd.field.offset" }];
/** Vorschau: Masshilfslinien + Masslinie (grob, ohne Massstriche/Text — Feinheiten rendert generatePlan). */ /** Vorschau: Punktreihe (Gummiband) während des Sammelns. */
function dimDraft(a: Vec2, b: Vec2, offset: number, at: Vec2 | null): ToolDraft { function chainDraft(pts: Vec2[], cursor: Vec2 | null): ToolDraft {
const shown = cursor ? [...pts, cursor] : pts;
const draft: ToolDraft = { preview: [{ kind: "poly", pts: shown, closed: false }], vertices: pts };
const last = pts[pts.length - 1];
if (cursor && last && dist(last, cursor) >= EPS) draft.hud = segmentHud(last, cursor);
return draft;
}
/** Vorschau: alle Masslinien-Segmente bei gegebenem Offset (grob — Massstriche/Text rendert generatePlan). */
function offsetDraft(pts: Vec2[], offset: number, at: Vec2 | null): ToolDraft {
const preview: DraftShape[] = []; const preview: DraftShape[] = [];
for (let i = 0; i < pts.length - 1; i++) {
const a = pts[i], b = pts[i + 1];
if (Math.abs(offset) > EPS) { if (Math.abs(offset) > EPS) {
const off = offsetPoints(a, b, offset); const off = offsetPoints(a, b, offset);
preview.push({ kind: "line", a, b: off.a }); preview.push({ kind: "line", a, b: off.a });
@@ -63,29 +89,30 @@ function dimDraft(a: Vec2, b: Vec2, offset: number, at: Vec2 | null): ToolDraft
} else { } else {
preview.push({ kind: "line", a, b }); preview.push({ kind: "line", a, b });
} }
const draft: ToolDraft = { preview, vertices: [a, b] }; }
if (at) draft.hud = { at, text: `${dist(a, b).toFixed(3)}m · Δ${offset.toFixed(3)}m` }; const draft: ToolDraft = { preview, vertices: pts };
if (at) {
const total = pts.reduce((sum, p, i) => (i === 0 ? 0 : sum + dist(pts[i - 1], p)), 0);
draft.hud = { at, text: `Σ${total.toFixed(3)}m · Δ${offset.toFixed(3)}m` };
}
return draft; return draft;
} }
/** Endpunkte der um `offset` parallel verschobenen Masslinie. */ function appendDimensionChain(p: Project, pts: Vec2[], offset: number, ctx: CommandContext): Project {
function offsetPoints(a: Vec2, b: Vec2, offset: number): { a: Vec2; b: Vec2 } { const added: Drawing2D[] = [];
const dx = b.x - a.x, dy = b.y - a.y; for (let i = 0; i < pts.length - 1; i++) {
const len = Math.hypot(dx, dy) || 1; const a = pts[i], b = pts[i + 1];
const nx = (-dy / len) * offset, ny = (dx / len) * offset; if (dist(a, b) < EPS) continue;
return { a: { x: a.x + nx, y: a.y + ny }, b: { x: b.x + nx, y: b.y + ny } }; added.push({
}
function appendDimension(p: Project, a: Vec2, b: Vec2, offset: number, ctx: CommandContext): Project {
if (dist(a, b) < EPS) return p;
const d: Drawing2D = {
id: uniqueId("dr2d"), id: uniqueId("dr2d"),
type: "drawing2d", type: "drawing2d",
levelId: ctx.level.id, levelId: ctx.level.id,
categoryCode: ctx.defaultCategoryCode, categoryCode: ctx.defaultCategoryCode,
geom: { shape: "dimension", a, b, offset }, geom: { shape: "dimension", a, b, offset },
}; });
return { ...p, drawings2d: [...p.drawings2d, d] }; }
if (added.length === 0) return p;
return { ...p, drawings2d: [...p.drawings2d, ...added] };
} }
const idle = (): [CommandState, CommandResult] => [ const idle = (): [CommandState, CommandResult] => [
@@ -98,70 +125,95 @@ export const dimensionCommand: Command = {
labelKey: "cmd.dimension.label", labelKey: "cmd.dimension.label",
prompt: (s) => { prompt: (s) => {
const phase = (s as DimState).phase; const phase = (s as DimState).phase;
return phase === "end" ? "cmd.dimension.end" : phase === "offset" ? "cmd.dimension.offset" : "cmd.dimension.start"; return phase === "next" ? "cmd.dimension.next" : phase === "offset" ? "cmd.dimension.offset" : "cmd.dimension.start";
},
accepts: (s) => ((s as DimState).phase === "offset" ? ["point", "number"] : ["point", "option"]),
options: (s) => {
const ds = s as DimState;
if (ds.phase !== "next") return [];
return ds.pts.length >= 2 ? [FINISH, UNDO] : [UNDO];
}, },
accepts: (s) => ((s as DimState).phase === "offset" ? ["point", "number"] : ["point"]),
options: () => [],
init: (): DimStart => ({ phase: "start", lastPoint: null }), init: (): DimStart => ({ phase: "start", lastPoint: null }),
onInput: (state, input, ctx): [CommandState, CommandResult] => { onInput: (state, input, ctx): [CommandState, CommandResult] => {
const s = state as DimState; const s = state as DimState;
if (input.kind === "option" && s.phase === "next") {
if (input.id === "undo") {
const pts = s.pts.slice(0, -1);
if (pts.length === 0) return [{ phase: "start", lastPoint: null }, { draft: null }];
const ns: DimNext = { phase: "next", pts, cursor: s.cursor, lastPoint: pts[pts.length - 1] };
return [ns, { draft: chainDraft(pts, s.cursor) }];
}
if (input.id === "finish" && s.pts.length >= 2) {
const ns: DimOffset = { phase: "offset", pts: s.pts, cursor: s.cursor, lastPoint: s.lastPoint };
return [ns, { draft: offsetDraft(s.pts, 0, null) }];
}
return [s, { draft: chainDraft(s.pts, s.cursor) }];
}
if (s.phase !== "offset") { if (s.phase !== "offset") {
if (input.kind !== "point") return [s, { draft: null }]; if (input.kind !== "point") return [s, { draft: null }];
const pt = input.point; const pt = input.point;
if (s.phase !== "end") { if (s.phase !== "next") {
const ns: DimEnd = { phase: "end", a: pt, cursor: pt, lastPoint: pt }; const ns: DimNext = { phase: "next", pts: [pt], cursor: pt, lastPoint: pt };
return [ns, { draft: { preview: [], vertices: [pt] } }]; return [ns, { draft: chainDraft([pt], pt) }];
} }
if (dist(s.a, pt) < EPS) return idle(); const pts = [...s.pts, pt];
const ns: DimOffset = { phase: "offset", a: s.a, b: pt, cursor: pt, lastPoint: pt }; const ns: DimNext = { phase: "next", pts, cursor: pt, lastPoint: pt };
return [ns, { draft: dimDraft(s.a, pt, 0, null) }]; return [ns, { draft: chainDraft(pts, pt) }];
} }
// Offset-Schritt: Zahl = direkter Abstand, Punkt = Abstand zum Cursor. // Offset-Schritt: Zahl = direkter Abstand, Punkt = Abstand zum Cursor.
let offset: number; let offset: number;
if (input.kind === "number") offset = input.value; if (input.kind === "number") offset = input.value;
else if (input.kind === "point") offset = signedOffset(s.a, s.b, input.point); else if (input.kind === "point") offset = signedOffset(s.pts[0], s.pts[1], input.point);
else return [s, { draft: dimDraft(s.a, s.b, 0, null) }]; else return [s, { draft: offsetDraft(s.pts, 0, null) }];
const { a, b } = s; const pts = s.pts;
return [ return [
{ phase: "start", lastPoint: null }, { phase: "start", lastPoint: null },
{ draft: null, done: true, commit: (p) => appendDimension(p, a, b, offset, ctx) }, { draft: null, done: true, commit: (p) => appendDimensionChain(p, pts, offset, ctx) },
]; ];
}, },
onMove: (state, point): [CommandState, CommandResult] => { onMove: (state, point): [CommandState, CommandResult] => {
const s = state as DimState; const s = state as DimState;
if (s.phase === "end") { if (s.phase === "next") {
const ns: DimEnd = { ...s, cursor: point }; const ns: DimNext = { ...s, cursor: point };
if (dist(s.a, point) < EPS) return [ns, { draft: { preview: [], vertices: [s.a] } }]; return [ns, { draft: chainDraft(s.pts, point) }];
const draft: ToolDraft = {
preview: [{ kind: "line", a: s.a, b: point }],
vertices: [s.a],
hud: { at: point, text: `${dist(s.a, point).toFixed(3)}m` },
};
return [ns, { draft }];
} }
if (s.phase === "offset") { if (s.phase === "offset") {
const off = signedOffset(s.a, s.b, point); // Offset relativ zum ERSTEN Segment (repräsentativ für die ganze Kette).
const off = signedOffset(s.pts[0], s.pts[1], point);
const ns: DimOffset = { ...s, cursor: point }; const ns: DimOffset = { ...s, cursor: point };
return [ns, { draft: dimDraft(s.a, s.b, off, point) }]; return [ns, { draft: offsetDraft(s.pts, off, point) }];
} }
return [s, { draft: null }]; return [s, { draft: null }];
}, },
onConfirm: (): [CommandState, CommandResult] => idle(), // Enter/Space/Rechtsklick: im "next"-Schritt (≥2 Punkte) wie Doppelklick →
// Punktreihe abschliessen; im "offset"-Schritt bricht es ab (kein sinnvoller
// Default-Abstand ohne Zeigegeste).
onConfirm: (state): [CommandState, CommandResult] => {
const s = state as DimState;
if (s.phase === "next" && s.pts.length >= 2) {
const ns: DimOffset = { phase: "offset", pts: s.pts, cursor: s.cursor, lastPoint: s.lastPoint };
return [ns, { draft: offsetDraft(s.pts, 0, null) }];
}
return idle();
},
onCancel: (): [CommandState, CommandResult] => idle(), onCancel: (): [CommandState, CommandResult] => idle(),
fields: (state) => ((state as DimState).phase === "offset" ? OFFSET_FIELDS : []), fields: (state) => ((state as DimState).phase === "offset" ? OFFSET_FIELDS : []),
fieldValues: (state, _locks, cursor): Record<string, number> => { fieldValues: (state, _locks, cursor): Record<string, number> => {
const s = state as DimState; const s = state as DimState;
if (s.phase !== "offset" || !cursor) return {}; if (s.phase !== "offset" || !cursor) return {};
return { offset: signedOffset(s.a, s.b, cursor) }; return { offset: signedOffset(s.pts[0], s.pts[1], cursor) };
}, },
pointFromFields: (state, locks, cursor) => { pointFromFields: (state, locks, cursor) => {
const s = state as DimState; const s = state as DimState;
if (s.phase !== "offset") return null; if (s.phase !== "offset") return null;
const offset = "offset" in locks ? locks.offset : cursor ? signedOffset(s.a, s.b, cursor) : 0; const offset = "offset" in locks ? locks.offset : cursor ? signedOffset(s.pts[0], s.pts[1], cursor) : 0;
return offsetPoints(s.a, s.b, offset).a; return offsetPoints(s.pts[0], s.pts[1], offset).a;
}, },
}; };
+4 -2
View File
@@ -185,7 +185,7 @@ export const de = {
"tool.bezier": "Bezier", "tool.bezier": "Bezier",
"tool.bezier.hint": "Bezier: Anker, Griff 1, Griff 2, Endpunkt — je Segment; Enter beendet", "tool.bezier.hint": "Bezier: Anker, Griff 1, Griff 2, Endpunkt — je Segment; Enter beendet",
"tool.dimension": "Bemassung", "tool.dimension": "Bemassung",
"tool.dimension.hint": "Bemassung: Erster Punkt, zweiter Punkt, dann Lage der Masslinie", "tool.dimension.hint": "Bemassung: Punkte wie Polylinie klicken, Doppelklick/Enter beendet, dann Lage der Masslinie (Massketten in einer Geste)",
"tool.text": "Text", "tool.text": "Text",
"tool.text.hint": "Text: Ankerpunkt setzen, dann Text eintippen", "tool.text.hint": "Text: Ankerpunkt setzen, dann Text eintippen",
"tool.textbox": "Textspalte", "tool.textbox": "Textspalte",
@@ -1164,8 +1164,10 @@ export const de = {
"cmd.bezier.finish": "Fertig", "cmd.bezier.finish": "Fertig",
"cmd.dimension.label": "Bemassung", "cmd.dimension.label": "Bemassung",
"cmd.dimension.start": "Erster Punkt:", "cmd.dimension.start": "Erster Punkt:",
"cmd.dimension.end": "Zweiter Punkt:", "cmd.dimension.next": "Nächster Punkt ( Fertig Zurück ):",
"cmd.dimension.offset": "Lage der Masslinie:", "cmd.dimension.offset": "Lage der Masslinie:",
"cmd.dimension.undo": "Zurück",
"cmd.dimension.finish": "Fertig",
"cmd.field.offset": "Abstand", "cmd.field.offset": "Abstand",
"cmd.text.label": "Text", "cmd.text.label": "Text",
"cmd.text.point": "Ankerpunkt:", "cmd.text.point": "Ankerpunkt:",
+4 -2
View File
@@ -183,7 +183,7 @@ export const en: Record<TranslationKey, string> = {
"tool.bezier": "Bezier", "tool.bezier": "Bezier",
"tool.bezier.hint": "Bezier: anchor, handle 1, handle 2, end point — per segment; Enter finishes", "tool.bezier.hint": "Bezier: anchor, handle 1, handle 2, end point — per segment; Enter finishes",
"tool.dimension": "Dimension", "tool.dimension": "Dimension",
"tool.dimension.hint": "Dimension: first point, second point, then dimension line position", "tool.dimension.hint": "Dimension: click points like a polyline, double-click/Enter finishes, then set the dimension line position (chains in one gesture)",
"tool.text": "Text", "tool.text": "Text",
"tool.text.hint": "Text: set anchor point, then type the text", "tool.text.hint": "Text: set anchor point, then type the text",
"tool.textbox": "Text column", "tool.textbox": "Text column",
@@ -1153,8 +1153,10 @@ export const en: Record<TranslationKey, string> = {
"cmd.bezier.finish": "Finish", "cmd.bezier.finish": "Finish",
"cmd.dimension.label": "Dimension", "cmd.dimension.label": "Dimension",
"cmd.dimension.start": "First point:", "cmd.dimension.start": "First point:",
"cmd.dimension.end": "Second point:", "cmd.dimension.next": "Next point ( Finish Undo ):",
"cmd.dimension.offset": "Dimension line position:", "cmd.dimension.offset": "Dimension line position:",
"cmd.dimension.undo": "Undo",
"cmd.dimension.finish": "Finish",
"cmd.field.offset": "Offset", "cmd.field.offset": "Offset",
"cmd.text.label": "Text", "cmd.text.label": "Text",
"cmd.text.point": "Anchor point:", "cmd.text.point": "Anchor point:",