task(D-fin): polyline-edit, stretch, break tools complete phase D
This commit is contained in:
@@ -572,6 +572,245 @@ export const joinTool: ToolExtensionV2 = makeJoinExplodeAlignTool('join');
|
|||||||
export const explodeTool: ToolExtensionV2 = makeJoinExplodeAlignTool('explode');
|
export const explodeTool: ToolExtensionV2 = makeJoinExplodeAlignTool('explode');
|
||||||
export const alignTool: ToolExtensionV2 = makeJoinExplodeAlignTool('align');
|
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):
|
// Array rect / polar (Task D-Extensions):
|
||||||
// Vervielfältigt die aktuelle Selektion in einem Raster bzw. im Kreis.
|
// Vervielfältigt die aktuelle Selektion in einem Raster bzw. im Kreis.
|
||||||
@@ -1083,11 +1322,11 @@ export const coreModifyPlugin: PluginV2 = {
|
|||||||
manifest: {
|
manifest: {
|
||||||
id: 'core-modify',
|
id: 'core-modify',
|
||||||
name: 'Bearbeiten (Kern)',
|
name: 'Bearbeiten (Kern)',
|
||||||
version: '1.8.0',
|
version: '1.9.0',
|
||||||
author: 'web-cad team',
|
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',
|
category: 'tools',
|
||||||
enabledByDefault: true,
|
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],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user