task(D-fin): polyline-edit, stretch, break tools complete phase D

This commit is contained in:
Agent Zero
2026-08-27 22:14:01 +02:00
parent 6dd4e083b0
commit 5500462bd8
@@ -572,6 +572,245 @@ export const joinTool: ToolExtensionV2 = makeJoinExplodeAlignTool('join');
export const explodeTool: ToolExtensionV2 = makeJoinExplodeAlignTool('explode');
export const alignTool: ToolExtensionV2 = makeJoinExplodeAlignTool('align');
// ─────────────────────────────────────────────────────────────
// Polyline-Editor (Task D-Finish): Vertices einer Polyline verschieben.
// Klick 1: Polyline wählen. Klick 2+: Vertex wählen (Toleranz) und ziehen.
// ESC beendet.
// ─────────────────────────────────────────────────────────────
let editPoly: CADElement | null = null;
let editVertexIdx = -1;
export const polylineEditTool: ToolExtensionV2 = {
manifest: {
id: 'polyline-edit',
label: 'Polyline-Editor',
icon: '\u2b1a',
ribbonTab: 'canvas',
tags: ['pro'],
description: 'Polyline wählen, dann Vertex ziehen',
},
optionsSchema: [],
handlers: {
down(e, ctx) {
const c = ctx as FullToolContext;
if (!c.doc || !c.selection) return;
if (!editPoly) {
const hit = c.selection.hitTest(e.world.x, e.world.y, 5);
if (!hit || hit.type !== 'polyline') {
ctx.setStatus('Polyline-Editor: erst eine Polyline anklicken');
return;
}
editPoly = hit;
ctx.setStatus('Polyline-Editor: Vertex ziehen, ESC beendet');
return;
}
// Vertex finden in der Nähe:
const props = editPoly.properties as Record<string, unknown>;
const pts = Array.isArray(props.points)
? (props.points as unknown[]).map((p) => p as Pt)
: [];
for (let i = 0; i < pts.length; i++) {
if (Math.hypot(pts[i].x - e.world.x, pts[i].y - e.world.y) < 8) {
editVertexIdx = i;
ctx.setStatus(`Vertex ${i + 1} — ziehen`);
return;
}
}
ctx.setStatus('Kein Vertex in der Nähe');
},
move(e, ctx) {
if (!editPoly || editVertexIdx < 0) return;
const c = ctx as FullToolContext;
const props = editPoly.properties as Record<string, unknown>;
const pts = Array.isArray(props.points)
? (props.points as unknown[]).map((p) => p as Pt)
: [];
pts[editVertexIdx] = e.world;
const preview: CADElement = {
...editPoly,
properties: { ...props, points: pts },
};
c.setPreview?.(preview);
},
up(e, ctx) {
if (!editPoly || editVertexIdx < 0) return;
const c = ctx as FullToolContext;
if (!c.doc) { editVertexIdx = -1; return; }
const current = c.doc.getElement(editPoly.id);
if (!current) { editVertexIdx = -1; return; }
const props = current.properties as Record<string, unknown>;
const pts = Array.isArray(props.points)
? (props.points as unknown[]).map((p) => ({ ...(p as Pt) }))
: [];
if (editVertexIdx < pts.length) {
pts[editVertexIdx] = e.world;
const xs = pts.map((p) => p.x);
const ys = pts.map((p) => p.y);
c.doc.transact(() => {
c.doc!.updateElement(editPoly!.id, {
x: (Math.min(...xs) + Math.max(...xs)) / 2,
y: (Math.min(...ys) + Math.max(...ys)) / 2,
width: Math.max(...xs) - Math.min(...xs),
height: Math.max(...ys) - Math.min(...ys),
properties: { ...props, points: pts },
});
});
ctx.setStatus(`Vertex ${editVertexIdx + 1} verschoben`);
}
editVertexIdx = -1;
},
cancel(ctx) {
editPoly = null;
editVertexIdx = -1;
ctx.setStatus('Polyline-Editor beendet');
},
},
};
// ─────────────────────────────────────────────────────────────
// Stretch (Task D-Finish): Endpunkte von Linien in einem Auswahlbereich
// verschieben. Klick 1: Basis, Klick 2: Ziel (Delta auf alle selektierten
// Endpunkte, die in der Nähe des Basispunkts liegen).
// ─────────────────────────────────────────────────────────────
let stretchBase: Pt | null = null;
export const stretchTool: ToolExtensionV2 = {
manifest: {
id: 'stretch',
label: 'Stretch',
icon: '\u2197',
ribbonTab: 'canvas',
tags: ['pro'],
description: 'Linien auswählen, dann Basis→Ziel (Endpunkte in Basisnähe werden gestreckt)',
},
optionsSchema: [],
handlers: {
down(e, ctx) {
const c = ctx as FullToolContext;
if (!c.doc || !c.selection) return;
if (!stretchBase) {
if (c.selection.getIds().length === 0) {
const hit = c.selection.hitTest(e.world.x, e.world.y, 5);
if (!hit) {
ctx.setStatus('Stretch: zuerst Linien auswählen');
return;
}
c.selection.setIds([hit.id]);
}
stretchBase = e.world;
ctx.setStatus(`Stretch: Zielklick (${c.selection.getIds().length} Linien)`);
return;
}
// Commit: alle Endpunkte, die dem Basispunkt am nächsten sind, verschieben:
const dx = e.world.x - stretchBase.x;
const dy = e.world.y - stretchBase.y;
const ids = c.selection.getIds();
c.doc.transact(() => {
for (const id of ids) {
const el = c.doc!.getElement(id);
if (!el || el.type !== 'line') continue;
const props = el.properties as { x1?: number; y1?: number; x2?: number; y2?: number };
if (props.x1 === undefined || props.y1 === undefined || props.x2 === undefined || props.y2 === undefined) continue;
// Endpunkte die dem Basispunkt am nächsten sind verschieben:
const d1 = Math.hypot(props.x1! - stretchBase!.x, props.y1! - stretchBase!.y);
const d2 = Math.hypot(props.x2! - stretchBase!.x, props.y2! - stretchBase!.y);
if (d1 < d2) {
props.x1 = props.x1! + dx;
props.y1 = props.y1! + dy;
} else {
props.x2 = props.x2! + dx;
props.y2 = props.y2! + dy;
}
const nx1 = props.x1!;
const ny1 = props.y1!;
const nx2 = props.x2!;
const ny2 = props.y2!;
c.doc!.updateElement(id, {
x: (nx1 + nx2) / 2,
y: (ny1 + ny2) / 2,
width: Math.abs(nx2 - nx1),
height: Math.abs(ny2 - ny1),
properties: props,
});
}
});
ctx.setStatus(`Stretch: ${ids.length} Linie(n) gestreckt \u0394(${dx.toFixed(0)}, ${dy.toFixed(0)})`);
stretchBase = null;
},
cancel(ctx) {
stretchBase = null;
ctx.setStatus('Stretch abgebrochen');
},
},
};
// ─────────────────────────────────────────────────────────────
// Break (Task D-Finish): Linie an einem Punkt in zwei Teile splitten.
// Klick auf Linie → zwei neue Linien, Original wird entfernt.
// ─────────────────────────────────────────────────────────────
export const breakTool: ToolExtensionV2 = {
manifest: {
id: 'break',
label: 'Break',
icon: '\u2702',
ribbonTab: 'canvas',
tags: ['pro'],
description: 'Linie anklicken — wird am Klickpunkt geteilt',
},
optionsSchema: [],
handlers: {
down(e, ctx) {
const c = ctx as FullToolContext;
if (!c.doc || !c.selection) return;
const hit = c.selection.hitTest(e.world.x, e.world.y, 5);
if (!hit || hit.type !== 'line') {
ctx.setStatus('Break: Linie anklicken');
return;
}
const props = hit.properties as { x1?: number; y1?: number; x2?: number; y2?: number };
if (props.x1 === undefined || props.y1 === undefined || props.x2 === undefined || props.y2 === undefined) return;
// Klick auf die Linie projizieren (nächster Punkt auf der Linie):
const x1 = props.x1 as number;
const y1 = props.y1 as number;
const x2 = props.x2 as number;
const y2 = props.y2 as number;
const dx = x2 - x1;
const dy = y2 - y1;
const lenSq = dx * dx + dy * dy;
if (lenSq === 0) return;
const t = Math.max(0, Math.min(1, ((e.world.x - x1) * dx + (e.world.y - y1) * dy) / lenSq));
const breakX = x1 + t * dx;
const breakY = y1 + t * dy;
c.doc.transact(() => {
// Original entfernen:
c.doc!.deleteElements([hit.id]);
// Zwei neue Linien hinzufügen:
c.doc!.addElement({
...hit,
id: uid('brk'),
x: (x1 + breakX) / 2,
y: (y1 + breakY) / 2,
width: Math.abs(breakX - x1),
height: Math.abs(breakY - y1),
properties: { ...props, x2: breakX, y2: breakY },
});
c.doc!.addElement({
...hit,
id: uid('brk'),
x: (breakX + x2) / 2,
y: (breakY + y2) / 2,
width: Math.abs(x2 - breakX),
height: Math.abs(y2 - breakY),
properties: { ...props, x1: breakX, y1: breakY },
});
});
ctx.setStatus(`Break: Linie bei (${breakX.toFixed(0)}, ${breakY.toFixed(0)}) geteilt`);
},
},
};
// ─────────────────────────────────────────────────────────────
// Array rect / polar (Task D-Extensions):
// Vervielfältigt die aktuelle Selektion in einem Raster bzw. im Kreis.
@@ -1083,11 +1322,11 @@ export const coreModifyPlugin: PluginV2 = {
manifest: {
id: 'core-modify',
name: 'Bearbeiten (Kern)',
version: '1.8.0',
version: '1.9.0',
author: 'web-cad team',
description: 'Auswahl, Schraffur, Move/Copy/Rotate/Scale/Mirror/Delete/Offset/Trim/Extend/Fillet/Arrays/Chamfer/Lengthen/Join/Explode/Align (V2).',
description: 'Auswahl, Schraffur, Move/Copy/Rotate/Scale/Mirror/Delete/Offset/Trim/Extend/Fillet/Arrays/Chamfer/Lengthen/Join/Explode/Align/Polyline-Edit/Stretch/Break (V2).',
category: 'tools',
enabledByDefault: true,
},
tools: [selectTool, hatchTool, moveTool, copyTool, rotateTool, scaleTool, mirrorTool, deleteTool, offsetTool, trimTool, extendTool, filletTool, arrayRectTool, arrayPolarTool, chamferTool, lengthenTool, joinTool, explodeTool, alignTool],
tools: [selectTool, hatchTool, moveTool, copyTool, rotateTool, scaleTool, mirrorTool, deleteTool, offsetTool, trimTool, extendTool, filletTool, arrayRectTool, arrayPolarTool, chamferTool, lengthenTool, joinTool, explodeTool, alignTool, polylineEditTool, stretchTool, breakTool],
};