task(D-ext3): join/explode/align tools for v2
This commit is contained in:
@@ -439,6 +439,139 @@ export const rotateTool: ToolExtensionV2 = makeTransformTool('rotate');
|
|||||||
export const scaleTool: ToolExtensionV2 = makeTransformTool('scale');
|
export const scaleTool: ToolExtensionV2 = makeTransformTool('scale');
|
||||||
export const mirrorTool: ToolExtensionV2 = makeTransformTool('mirror');
|
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<string, unknown>;
|
||||||
|
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<string, unknown>;
|
||||||
|
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):
|
// 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.
|
||||||
@@ -950,11 +1083,11 @@ export const coreModifyPlugin: PluginV2 = {
|
|||||||
manifest: {
|
manifest: {
|
||||||
id: 'core-modify',
|
id: 'core-modify',
|
||||||
name: 'Bearbeiten (Kern)',
|
name: 'Bearbeiten (Kern)',
|
||||||
version: '1.7.0',
|
version: '1.8.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 (V2).',
|
description: 'Auswahl, Schraffur, Move/Copy/Rotate/Scale/Mirror/Delete/Offset/Trim/Extend/Fillet/Arrays/Chamfer/Lengthen/Join/Explode/Align (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],
|
tools: [selectTool, hatchTool, moveTool, copyTool, rotateTool, scaleTool, mirrorTool, deleteTool, offsetTool, trimTool, extendTool, filletTool, arrayRectTool, arrayPolarTool, chamferTool, lengthenTool, joinTool, explodeTool, alignTool],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user