From b470ac33469e617ca72579f4ae13b12eb2480044 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Thu, 27 Aug 2026 20:03:14 +0200 Subject: [PATCH] task(D-ext3): join/explode/align tools for v2 --- .../src/plugins/builtin/core-modify/index.ts | 139 +++++++++++++++++- 1 file changed, 136 insertions(+), 3 deletions(-) diff --git a/frontend/src/plugins/builtin/core-modify/index.ts b/frontend/src/plugins/builtin/core-modify/index.ts index ad5d68a..19d5710 100644 --- a/frontend/src/plugins/builtin/core-modify/index.ts +++ b/frontend/src/plugins/builtin/core-modify/index.ts @@ -439,6 +439,139 @@ export const rotateTool: ToolExtensionV2 = makeTransformTool('rotate'); export const scaleTool: ToolExtensionV2 = makeTransformTool('scale'); export const mirrorTool: ToolExtensionV2 = makeTransformTool('mirror'); +// ───────────────────────────────────────────────────────────── +// Join / Explode / Align (Task D-Extensions): +// - join: 2+ ausgewählte Linien → eine Polyline (Transaktion) +// - explode: Polyline → einzelne Liniensegmente (Transaktion) +// - align: bewegt ein Element so, dass sein Anker (Zentrum) auf das +// Ziel-Element-Zentrum ausgerichtet wird (3 Klicks: Quelle, Ziel, Commit) +// ───────────────────────────────────────────────────────────── +let alignSourceId: string | null = null; + +function makeJoinExplodeAlignTool(mode: 'join' | 'explode' | 'align'): ToolExtensionV2 { + const labels = { join: 'Join', explode: 'Explode', align: 'Align' } as const; + return { + manifest: { + id: mode, + label: labels[mode], + icon: mode === 'join' ? '\u2795' : mode === 'explode' ? '\u2737' : '\u21c4', + ribbonTab: 'canvas', + tags: ['pro'], + description: `${labels[mode]}: siehe Statuszeile`, + }, + optionsSchema: [], + handlers: { + down(e, ctx) { + const c = ctx as FullToolContext; + if (!c.doc || !c.selection) return; + const ids = c.selection.getIds(); + + if (mode === 'join') { + // Alle selektierten Linien → eine Polyline: + const lines = ids + .map((id) => c.doc!.getElement(id)) + .filter((el): el is CADElement => !!el && el.type === 'line'); + if (lines.length < 2) { + ctx.setStatus('Join: mindestens 2 Linien auswählen'); + return; + } + // Punkte der Linien in Reihenfolge sammeln: + const allPts: Pt[] = []; + for (const ln of lines) { + const props = ln.properties as Record; + if (props.x1 === undefined) continue; + allPts.push({ x: props.x1 as number, y: props.y1 as number }); + allPts.push({ x: props.x2 as number, y: props.y2 as number }); + } + if (allPts.length < 4) return; + // Erste und letzte Koordinate als Start/Ende nehmen: + const first = allPts[0]; + const last = allPts[allPts.length - 1]; + const joined: CADElement = { + ...lines[0], + id: uid('pl'), + type: 'polyline', + x: (first.x + last.x) / 2, + y: (first.y + last.y) / 2, + width: Math.abs(last.x - first.x), + height: Math.abs(last.y - first.y), + properties: { points: allPts, stroke: '#e0e0e0', strokeWidth: 1 }, + }; + c.doc.transact(() => { + for (const ln of lines) c.doc!.deleteElements([ln.id]); + c.doc!.addElement(joined); + }); + ctx.setStatus(`Join: ${lines.length} Linien → 1 Polyline`); + return; + } + + if (mode === 'explode') { + // Polyline → einzelne Liniensegmente: + const polys = ids + .map((id) => c.doc!.getElement(id)) + .filter((el): el is CADElement => !!el && el.type === 'polyline'); + if (polys.length === 0) { + ctx.setStatus('Explode: mindestens eine Polyline auswählen'); + return; + } + c.doc.transact(() => { + for (const poly of polys) { + const props = poly.properties as Record; + const pts = Array.isArray(props.points) + ? (props.points as unknown[]).map((p) => p as Pt) + : []; + for (let i = 0; i < pts.length - 1; i++) { + c.doc!.addElement({ + ...poly, + id: uid('ln'), + type: 'line', + x: (pts[i].x + pts[i + 1].x) / 2, + y: (pts[i].y + pts[i + 1].y) / 2, + width: Math.abs(pts[i + 1].x - pts[i].x), + height: Math.abs(pts[i + 1].y - pts[i].y), + properties: { x1: pts[i].x, y1: pts[i].y, x2: pts[i + 1].x, y2: pts[i + 1].y, stroke: '#e0e0e0', strokeWidth: 1 }, + }); + } + c.doc!.deleteElements([poly.id]); + } + }); + ctx.setStatus(`Explode: ${polys.length} Polyline(n) in Segmente zerlegt`); + return; + } + + // align: Klick 1 = Quelle (Element), Klick 2 = Ziel-Position + if (mode === 'align') { + if (ids.length === 0) { + const hit = c.selection.hitTest(e.world.x, e.world.y, 5); + if (!hit) return; + c.selection.setIds([hit.id]); + alignSourceId = hit.id; + ctx.setStatus('Align: Ziel-Position klicken'); + return; + } + const el = c.doc.getElement(ids[0]); + if (!el) return; + const cx = el.x; + const cy = el.y; + c.doc.transact(() => { + c.doc!.updateElement(el.id, { x: e.world.x, y: e.world.y }); + }); + ctx.setStatus(`Align: ${el.id} nach (${e.world.x.toFixed(0)}, ${e.world.y.toFixed(0)}) verschoben`); + alignSourceId = null; + return; + } + }, + cancel(ctx) { + alignSourceId = null; + ctx.setStatus(`${labels[mode]} abgebrochen`); + }, + }, + }; +} +export const joinTool: ToolExtensionV2 = makeJoinExplodeAlignTool('join'); +export const explodeTool: ToolExtensionV2 = makeJoinExplodeAlignTool('explode'); +export const alignTool: ToolExtensionV2 = makeJoinExplodeAlignTool('align'); + // ───────────────────────────────────────────────────────────── // Array rect / polar (Task D-Extensions): // Vervielfältigt die aktuelle Selektion in einem Raster bzw. im Kreis. @@ -950,11 +1083,11 @@ export const coreModifyPlugin: PluginV2 = { manifest: { id: 'core-modify', name: 'Bearbeiten (Kern)', - version: '1.7.0', + version: '1.8.0', author: 'web-cad team', - description: 'Auswahl, Schraffur, Move/Copy/Rotate/Scale/Mirror/Delete/Offset/Trim/Extend/Fillet/Arrays/Chamfer/Lengthen (V2).', + description: 'Auswahl, Schraffur, Move/Copy/Rotate/Scale/Mirror/Delete/Offset/Trim/Extend/Fillet/Arrays/Chamfer/Lengthen/Join/Explode/Align (V2).', category: 'tools', enabledByDefault: true, }, - tools: [selectTool, hatchTool, moveTool, copyTool, rotateTool, scaleTool, mirrorTool, deleteTool, offsetTool, trimTool, extendTool, filletTool, arrayRectTool, arrayPolarTool, chamferTool, lengthenTool], + tools: [selectTool, hatchTool, moveTool, copyTool, rotateTool, scaleTool, mirrorTool, deleteTool, offsetTool, trimTool, extendTool, filletTool, arrayRectTool, arrayPolarTool, chamferTool, lengthenTool, joinTool, explodeTool, alignTool], };