task(D10): array-path tool - distribute element copies evenly along polyline path in one undo transaction, count option
This commit is contained in:
@@ -1077,6 +1077,110 @@ function makeArrayTool(mode: 'array-rect' | 'array-polar'): ToolExtensionV2 {
|
||||
export const arrayRectTool: ToolExtensionV2 = makeArrayTool('array-rect');
|
||||
export const arrayPolarTool: ToolExtensionV2 = makeArrayTool('array-polar');
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Task D10-Rest: array-path — Elemente entlang einer Polyline
|
||||
// (Pfad) verteilen. Klick1: Quelle (Auto-HitSelect), Klick2: auf
|
||||
// die Pfad-Polyline klicken. count Kopien gleichmäßig über die
|
||||
// Gesamtlänge (s = i/(count-1)·total), ALLES in 1 Transaktion.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
let arrayPathBase: Pt | null = null;
|
||||
|
||||
/** Punkt auf einer Polyline bei kumulierter Länge s (0..total). */
|
||||
function pointAlongPolyline(pts: Pt[], s: number): Pt {
|
||||
if (pts.length === 0) return { x: 0, y: 0 };
|
||||
if (pts.length === 1 || s <= 0) return { ...pts[0] };
|
||||
let remaining = s;
|
||||
for (let i = 0; i < pts.length - 1; i++) {
|
||||
const dx = pts[i + 1].x - pts[i].x;
|
||||
const dy = pts[i + 1].y - pts[i].y;
|
||||
const segLen = Math.hypot(dx, dy);
|
||||
if (segLen <= 0) continue;
|
||||
if (remaining <= segLen) {
|
||||
const t = remaining / segLen;
|
||||
return { x: pts[i].x + dx * t, y: pts[i].y + dy * t };
|
||||
}
|
||||
remaining -= segLen;
|
||||
}
|
||||
return { ...pts[pts.length - 1] };
|
||||
}
|
||||
|
||||
export const arrayPathTool: ToolExtensionV2 = {
|
||||
manifest: {
|
||||
id: 'array-path',
|
||||
label: 'Array Pfad',
|
||||
icon: '∿',
|
||||
ribbonTab: 'canvas',
|
||||
tags: ['pro'],
|
||||
description: 'Array entlang Pfad: Auswahl treffen, dann auf Pfad-Polyline klicken (Task D10)',
|
||||
},
|
||||
optionsSchema: [
|
||||
{ key: 'count', label: 'Anzahl', type: 'number', min: 2, max: 100 },
|
||||
],
|
||||
handlers: {
|
||||
down(e, ctx) {
|
||||
const c = ctx as FullToolContext;
|
||||
if (!c.doc || !c.selection) return;
|
||||
if (!arrayPathBase) {
|
||||
if (c.selection.getIds().length === 0) {
|
||||
const hit = c.selection.hitTest(e.world.x, e.world.y, 5);
|
||||
if (!hit) {
|
||||
ctx.setStatus('Array Pfad: zuerst Element(e) auswählen');
|
||||
return;
|
||||
}
|
||||
c.selection.setIds([hit.id]);
|
||||
}
|
||||
arrayPathBase = e.world;
|
||||
ctx.setStatus(`Array Pfad: auf Pfad-Polyline klicken (${c.selection.getIds().length} Element(e))`);
|
||||
return;
|
||||
}
|
||||
// Zweiter Klick: muss eine Polyline als Pfad treffen
|
||||
const pathHit = c.selection.hitTest(e.world.x, e.world.y, 8);
|
||||
if (!pathHit || (pathHit.type !== 'polyline' && pathHit.type !== 'polygon')) {
|
||||
ctx.setStatus('Array Pfad: zweiter Klick muss eine Polyline als Pfad treffen');
|
||||
arrayPathBase = null;
|
||||
return;
|
||||
}
|
||||
const pathPts = (pathHit.properties as Record<string, unknown>).points as Pt[] | undefined;
|
||||
if (!pathPts || pathPts.length < 2) {
|
||||
ctx.setStatus('Array Pfad: Pfad-Polyline hat zu wenige Punkte');
|
||||
arrayPathBase = null;
|
||||
return;
|
||||
}
|
||||
const count = Math.max(2, (c.options.count as number | undefined) ?? 5);
|
||||
const ids = c.selection.getIds().filter((id) => id !== pathHit.id);
|
||||
// Gesamtlänge des Pfads
|
||||
let total = 0;
|
||||
for (let i = 0; i < pathPts.length - 1; i++) {
|
||||
total += Math.hypot(pathPts[i + 1].x - pathPts[i].x, pathPts[i + 1].y - pathPts[i].y);
|
||||
}
|
||||
c.doc.transact(() => {
|
||||
for (const id of ids) {
|
||||
const el = c.doc!.getElement(id);
|
||||
if (!el) continue;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const s = total * (i / Math.max(1, count - 1));
|
||||
const pos = pointAlongPolyline(pathPts, s);
|
||||
c.doc!.addElement({
|
||||
...el,
|
||||
id: uid('arrp'),
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
} as CADElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
arrayPathBase = null;
|
||||
ctx.setStatus(`Array Pfad: ${ids.length * count} Kopien entlang Pfad verteilt`);
|
||||
},
|
||||
cancel(ctx) {
|
||||
arrayPathBase = null;
|
||||
ctx.setPreview?.(null);
|
||||
ctx.setStatus('Array Pfad abgebrochen');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Chamfer (Task D-Ext): schneidet zwei sich schneidende Linien an einer
|
||||
// festen Distanz vom Schnittpunkt und verbindet die Enden mit einer
|
||||
@@ -1483,5 +1587,5 @@ export const coreModifyPlugin: PluginV2 = {
|
||||
category: 'tools',
|
||||
enabledByDefault: true,
|
||||
},
|
||||
tools: [selectTool, hatchTool, moveTool, copyTool, rotateTool, scaleTool, mirrorTool, deleteTool, offsetTool, trimTool, extendTool, filletTool, arrayRectTool, arrayPolarTool, chamferTool, lengthenTool, joinTool, explodeTool, alignTool, polylineEditTool, stretchTool, breakTool],
|
||||
tools: [selectTool, hatchTool, moveTool, copyTool, rotateTool, scaleTool, mirrorTool, deleteTool, offsetTool, trimTool, extendTool, filletTool, arrayRectTool, arrayPolarTool, chamferTool, lengthenTool, joinTool, explodeTool, alignTool, polylineEditTool, stretchTool, breakTool, arrayPathTool],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user