diff --git a/frontend/src/plugins/builtin/event-tools/index.ts b/frontend/src/plugins/builtin/event-tools/index.ts index b0e67a7..afb5e20 100644 --- a/frontend/src/plugins/builtin/event-tools/index.ts +++ b/frontend/src/plugins/builtin/event-tools/index.ts @@ -186,6 +186,61 @@ export const tableTool = makePlaceTool('table', 'Tisch', '\u25cb', export const stageTool = makePlaceTool('stage', 'Bühne', '\u25ad', (x, y, layerId) => seating.createStage(x, y, layerId), 'Bühne'); +// ───────────────────────────────────────────────────────────── +// Arc-Row (Task E2): Reihen entlang Kreisbogen um Bühnenpunkt. +// Klick 1: Zentrum (Bühne). Drag: Radius + Startwinkel. +// Klick 2: Endwinkel → Commit. +// Phasen: 0=Zentrum setzen, 1=Radius+Start, 2=Endwinkel → commit +// ───────────────────────────────────────────────────────────── +let arcCenter: Pt | null = null; +let arcStartPt: Pt | null = null; +let arcPhase = 0; + +export const arcRowTool: ToolExtensionV2 = { + manifest: { + id: 'arc-row', + label: 'Arc-Row', + icon: '\u2312', + ribbonTab: 'insert', + tags: ['basic'], + description: 'Bogenreihe: Zentrum, Startpunkt, Endpunkt klicken', + }, + optionsSchema: [ + { key: 'arcSpacing', label: 'Abstand (cm)', type: 'number', min: 20, max: 200 }, + ], + handlers: { + down(e, ctx) { + const c = ctx as FullToolContext; + const { layerId } = optsOf(c); + const spacing = (c.options.arcSpacing as number | undefined) ?? 50; + if (arcPhase === 0) { + arcCenter = e.world; + arcPhase = 1; + ctx.setStatus('Arc-Row: Radius ziehen (Startpunkt)'); + } else if (arcPhase === 1 && arcCenter) { + arcStartPt = e.world; + arcPhase = 2; + ctx.setStatus('Arc-Row: Endwinkel klicken'); + } else if (arcPhase === 2 && arcCenter && arcStartPt && c.doc) { + const els = seating.createArcRow(arcCenter.x, arcCenter.y, Math.hypot(arcStartPt.x - arcCenter.x, arcStartPt.y - arcCenter.y), + (Math.atan2(arcStartPt.y - arcCenter.y, arcStartPt.x - arcCenter.x) * 180) / Math.PI, + (Math.atan2(e.world.y - arcCenter.y, e.world.x - arcCenter.x) * 180) / Math.PI, + layerId, spacing); + c.doc.transact(() => { + for (const el of els) c.doc!.addElement(el); + }); + ctx.setStatus(`Arc-Row: ${els.length} Stühle platziert`); + arcPhase = 0; arcCenter = null; arcStartPt = null; + } + }, + cancel(ctx) { + arcPhase = 0; arcCenter = null; arcStartPt = null; + ctx.setPreview?.(null); + ctx.setStatus('Arc-Row abgebrochen'); + }, + }, +}; + /** V2-Plugin: Event-Domain (Tasks A4.9 + E1). */ export const eventToolsV2Plugin: PluginV2 = { manifest: { @@ -197,5 +252,5 @@ export const eventToolsV2Plugin: PluginV2 = { category: 'elements', enabledByDefault: true, }, - tools: [chairTool, seatingRowToolDrag, seatingBlockToolDrag, tableTool, stageTool], + tools: [chairTool, seatingRowToolDrag, seatingBlockToolDrag, tableTool, stageTool, arcRowTool], }; diff --git a/frontend/src/services/seatingService.ts b/frontend/src/services/seatingService.ts index 13af8d7..413834a 100644 --- a/frontend/src/services/seatingService.ts +++ b/frontend/src/services/seatingService.ts @@ -280,6 +280,47 @@ export class SeatingService { return elements; } + /** Create an arc row (curved seating around a center point) — Task E2 */ + createArcRow( + centerX: number, + centerY: number, + radius: number, + startAngleDeg: number, + endAngleDeg: number, + layerId: string, + spacing = 50, + ): CADElement[] { + const arcLen = Math.abs(endAngleDeg - startAngleDeg) * Math.PI / 180 * radius; + const count = Math.max(2, Math.floor(arcLen / spacing)); + const rowId = `arcrow_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`; + const elements: CADElement[] = []; + for (let i = 0; i <= count; i++) { + const angle = startAngleDeg + (endAngleDeg - startAngleDeg) * (i / count); + const rad = angle * Math.PI / 180; + const px = centerX + radius * Math.cos(rad); + const py = centerY + radius * Math.sin(rad); + elements.push({ + id: this.generateId(), + type: 'chair', + layerId, + x: px, + y: py, + width: 45, + height: 45, + properties: { + rotation: angle + 90, + fill: '#4a90d9', + backrestColor: '#3a7ac9', + outlineColor: '#2a5a99', + seatType: 'standard', + rowIndex: i, + rowId, + }, + }); + } + return elements; + } + /** Create a table element */ createTable(x: number, y: number, layerId: string, config: Partial = {}): CADElement { const cfg = { ...DEFAULT_TABLE, ...config };