task(D-ext): array-rect and array-polar tools for v2
This commit is contained in:
@@ -277,6 +277,117 @@ 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');
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// Array rect / polar (Task D-Extensions):
|
||||||
|
// Vervielfältigt die aktuelle Selektion in einem Raster bzw. im Kreis.
|
||||||
|
// Klick 1: Basis-/Zentrumspunkt. Klick 2: Commit.
|
||||||
|
// rect: Options rows, cols, dx, dy (Abstände).
|
||||||
|
// polar: Options count, optional angleSum (Default 360).
|
||||||
|
// Jede Aktion = EINE Transaktion → 1 Undo-Schritt.
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
let arrayBase: Pt | null = null;
|
||||||
|
|
||||||
|
function makeArrayTool(mode: 'array-rect' | 'array-polar'): ToolExtensionV2 {
|
||||||
|
const labels = { 'array-rect': 'Array Rect', 'array-polar': 'Array Polar' } as const;
|
||||||
|
return {
|
||||||
|
manifest: {
|
||||||
|
id: mode,
|
||||||
|
label: labels[mode],
|
||||||
|
icon: mode === 'array-rect' ? '\u25a6' : '\u273b',
|
||||||
|
ribbonTab: 'canvas',
|
||||||
|
tags: ['pro'],
|
||||||
|
description: `${labels[mode]}: Auswahl treffen, dann Basis\u2192Ziel klicken`,
|
||||||
|
},
|
||||||
|
optionsSchema:
|
||||||
|
mode === 'array-rect'
|
||||||
|
? [
|
||||||
|
{ key: 'rows', label: 'Zeilen', type: 'number', min: 1, max: 50 },
|
||||||
|
{ key: 'cols', label: 'Spalten', type: 'number', min: 1, max: 50 },
|
||||||
|
{ key: 'dx', label: 'Abstand X', type: 'number', min: 1 },
|
||||||
|
{ key: 'dy', label: 'Abstand Y', type: 'number', min: 1 },
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{ 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 (!arrayBase) {
|
||||||
|
if (c.selection.getIds().length === 0) {
|
||||||
|
const hit = c.selection.hitTest(e.world.x, e.world.y, 5);
|
||||||
|
if (!hit) {
|
||||||
|
ctx.setStatus(`${labels[mode]}: zuerst Element(e) auswählen`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c.selection.setIds([hit.id]);
|
||||||
|
}
|
||||||
|
arrayBase = e.world;
|
||||||
|
ctx.setStatus(`${labels[mode]}: Zielklick zum Anwenden (${c.selection.getIds().length} Elemente)`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Zweiter Klick → Commit
|
||||||
|
const ids = c.selection.getIds();
|
||||||
|
const base = arrayBase;
|
||||||
|
c.doc.transact(() => {
|
||||||
|
if (mode === 'array-rect') {
|
||||||
|
const rows = (c.options.rows as number | undefined) ?? 3;
|
||||||
|
const cols = (c.options.cols as number | undefined) ?? 3;
|
||||||
|
const dx = (c.options.dx as number | undefined) ?? 50;
|
||||||
|
const dy = (c.options.dy as number | undefined) ?? 50;
|
||||||
|
for (const id of ids) {
|
||||||
|
const el = c.doc!.getElement(id);
|
||||||
|
if (!el) continue;
|
||||||
|
for (let r = 0; r < rows; r++) {
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
if (r === 0 && col === 0) continue; // Original überspringen
|
||||||
|
c.doc!.addElement({
|
||||||
|
...el,
|
||||||
|
id: uid('arr'),
|
||||||
|
x: el.x + col * dx,
|
||||||
|
y: el.y + r * dy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const count = (c.options.count as number | undefined) ?? 6;
|
||||||
|
// Zentrum = Basispunkt
|
||||||
|
for (const id of ids) {
|
||||||
|
const el = c.doc!.getElement(id);
|
||||||
|
if (!el) continue;
|
||||||
|
for (let i = 1; i < count; i++) {
|
||||||
|
const angle = (2 * Math.PI * i) / count;
|
||||||
|
const cx = base.x;
|
||||||
|
const cy = base.y;
|
||||||
|
const cos = Math.cos(angle);
|
||||||
|
const sin = Math.sin(angle);
|
||||||
|
// Position rotieren um Zentrum:
|
||||||
|
const relX = el.x - cx;
|
||||||
|
const relY = el.y - cy;
|
||||||
|
c.doc!.addElement({
|
||||||
|
...el,
|
||||||
|
id: uid('arr'),
|
||||||
|
x: cx + relX * cos - relY * sin,
|
||||||
|
y: cy + relX * sin + relY * cos,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.setStatus(`${labels[mode]}: ${ids.length} Element(e) vervielfältigt`);
|
||||||
|
arrayBase = null;
|
||||||
|
},
|
||||||
|
cancel(ctx) {
|
||||||
|
arrayBase = null;
|
||||||
|
ctx.setStatus(`${labels[mode]} abgebrochen`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export const arrayRectTool: ToolExtensionV2 = makeArrayTool('array-rect');
|
||||||
|
export const arrayPolarTool: ToolExtensionV2 = makeArrayTool('array-polar');
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// Trim / Extend / Fillet (Task A4.10) — baut auf BUG-2-reparierter
|
// Trim / Extend / Fillet (Task A4.10) — baut auf BUG-2-reparierter
|
||||||
// findIntersection-Geometrie (B2). Legacy-Ablauf:
|
// findIntersection-Geometrie (B2). Legacy-Ablauf:
|
||||||
@@ -470,11 +581,11 @@ export const coreModifyPlugin: PluginV2 = {
|
|||||||
manifest: {
|
manifest: {
|
||||||
id: 'core-modify',
|
id: 'core-modify',
|
||||||
name: 'Bearbeiten (Kern)',
|
name: 'Bearbeiten (Kern)',
|
||||||
version: '1.5.0',
|
version: '1.6.0',
|
||||||
author: 'web-cad team',
|
author: 'web-cad team',
|
||||||
description: 'Auswahl, Schraffur, Move/Copy/Rotate/Scale/Mirror/Delete/Offset/Trim/Extend/Fillet (V2).',
|
description: 'Auswahl, Schraffur, Move/Copy/Rotate/Scale/Mirror/Delete/Offset/Trim/Extend/Fillet/Arrays (V2).',
|
||||||
category: 'tools',
|
category: 'tools',
|
||||||
enabledByDefault: true,
|
enabledByDefault: true,
|
||||||
},
|
},
|
||||||
tools: [selectTool, hatchTool, moveTool, copyTool, rotateTool, scaleTool, mirrorTool, deleteTool, offsetTool, trimTool, extendTool, filletTool],
|
tools: [selectTool, hatchTool, moveTool, copyTool, rotateTool, scaleTool, mirrorTool, deleteTool, offsetTool, trimTool, extendTool, filletTool, arrayRectTool, arrayPolarTool],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -810,3 +810,53 @@ describe('A3 pilot: select tool', () => {
|
|||||||
expect(sel?.manifest.tags).toContain('basic');
|
expect(sel?.manifest.tags).toContain('basic');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('D-Ext: array-rect tool', () => {
|
||||||
|
it('vervielfaeltigt Selektion rows×cols in EINER Transaktion', () => {
|
||||||
|
const { ydoc } = createInMemoryDoc();
|
||||||
|
const doc = new CADDocument(ydoc);
|
||||||
|
doc.addElement(makeEl('a1'));
|
||||||
|
let selIds: string[] = ['a1'];
|
||||||
|
const bridge = {
|
||||||
|
getIds: () => selIds,
|
||||||
|
setIds: (ids: string[]) => { selIds = ids; },
|
||||||
|
hitTest: () => null,
|
||||||
|
};
|
||||||
|
const ctx = makeCtx({ doc, selection: bridge, options: { rows: 2, cols: 2, dx: 60, dy: 60 } });
|
||||||
|
const arr = coreModifyPlugin.tools!.find((t) => t.manifest.id === 'array-rect')!;
|
||||||
|
|
||||||
|
arr.handlers.down!(pe(0, 0), ctx);
|
||||||
|
arr.handlers.down!(pe(0, 0), ctx);
|
||||||
|
|
||||||
|
expect(doc.getAllElements().length).toBe(4);
|
||||||
|
expect(doc.undo()).toBe(true);
|
||||||
|
expect(doc.getAllElements().length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('D-Ext: array-polar tool', () => {
|
||||||
|
it('verteilt count Kopien gleichmaessig um das Zentrum', () => {
|
||||||
|
const { ydoc } = createInMemoryDoc();
|
||||||
|
const doc = new CADDocument(ydoc);
|
||||||
|
const elem = makeEl('p1');
|
||||||
|
elem.x = 100;
|
||||||
|
elem.y = 0;
|
||||||
|
doc.addElement(elem);
|
||||||
|
let selIds: string[] = ['p1'];
|
||||||
|
const bridge = {
|
||||||
|
getIds: () => selIds,
|
||||||
|
setIds: (ids: string[]) => { selIds = ids; },
|
||||||
|
hitTest: () => null,
|
||||||
|
};
|
||||||
|
const ctx = makeCtx({ doc, selection: bridge, options: { count: 4 } });
|
||||||
|
const polar = coreModifyPlugin.tools!.find((t) => t.manifest.id === 'array-polar')!;
|
||||||
|
|
||||||
|
polar.handlers.down!(pe(0, 0), ctx);
|
||||||
|
polar.handlers.down!(pe(0, 0), ctx);
|
||||||
|
|
||||||
|
const all = doc.getAllElements();
|
||||||
|
expect(all.length).toBe(4);
|
||||||
|
expect(doc.undo()).toBe(true);
|
||||||
|
expect(doc.getAllElements().length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user