import type { CADElement, CADProperties } from '../types/cad.types'; /** * Seating Service — creates chairs, rows, blocks, tables, stages with configurable parameters. * Also provides seat counting and template presets. */ export interface ChairConfig { width: number; height: number; fill: string; backrestColor: string; outlineColor: string; } export const DEFAULT_CHAIR: ChairConfig = { width: 40, height: 40, fill: '#4a90d9', backrestColor: '#3a7ac9', outlineColor: '#2a5a99', }; export interface SeatingRowConfig { count: number; spacing: number; rotation: number; chairWidth: number; chairHeight: number; fill: string; } export const DEFAULT_ROW: SeatingRowConfig = { count: 10, spacing: 50, rotation: 0, chairWidth: 40, chairHeight: 40, fill: '#4a90d9', }; export interface SeatingBlockConfig { rows: number; cols: number; rowSpacing: number; colSpacing: number; rowOffset: number; rotation: number; chairWidth: number; chairHeight: number; fill: string; } export const DEFAULT_BLOCK: SeatingBlockConfig = { rows: 5, cols: 10, rowSpacing: 50, colSpacing: 50, rowOffset: 0, rotation: 0, chairWidth: 40, chairHeight: 40, fill: '#4a90d9', }; export interface TableConfig { width: number; height: number; shape: 'rect' | 'round'; fill: string; rotation: number; } export const DEFAULT_TABLE: TableConfig = { width: 80, height: 40, shape: 'rect', fill: '#8b6f47', rotation: 0, }; export interface StageConfig { width: number; height: number; fill: string; rotation: number; label: string; } export const DEFAULT_STAGE: StageConfig = { width: 200, height: 60, fill: '#2c3e50', rotation: 0, label: 'Bühne', }; export interface SeatingTemplate { name: string; description: string; type: 'row' | 'block' | 'mixed'; config: Record; } export const SEATING_TEMPLATES: SeatingTemplate[] = [ { name: 'Kleine Reihe', description: '5 Stühle in einer Reihe', type: 'row', config: { count: 5, spacing: 50, rotation: 0 }, }, { name: 'Mittlere Reihe', description: '10 Stühle in einer Reihe', type: 'row', config: { count: 10, spacing: 50, rotation: 0 }, }, { name: 'Große Reihe', description: '20 Stühle in einer Reihe', type: 'row', config: { count: 20, spacing: 50, rotation: 0 }, }, { name: 'Kleiner Block', description: '3×5 Stühle', type: 'block', config: { rows: 3, cols: 5, rowSpacing: 50, colSpacing: 50, rowOffset: 0 }, }, { name: 'Mittlerer Block', description: '5×10 Stühle', type: 'block', config: { rows: 5, cols: 10, rowSpacing: 50, colSpacing: 50, rowOffset: 0 }, }, { name: 'Großer Block', description: '8×15 Stühle', type: 'block', config: { rows: 8, cols: 15, rowSpacing: 50, colSpacing: 50, rowOffset: 0 }, }, { name: 'Konzertsaal', description: '3 Blöcke mit Gängen: 5×8, 5×12, 5×8', type: 'mixed', config: { blocks: [ { rows: 5, cols: 8, offsetX: 0, rowSpacing: 50, colSpacing: 50 }, { rows: 5, cols: 12, offsetX: 500, rowSpacing: 50, colSpacing: 50 }, { rows: 5, cols: 8, offsetX: 1200, rowSpacing: 50, colSpacing: 50 }, ], }, }, { name: 'Theater', description: '10 Reihen mit 15 Stühlen, versetzt', type: 'block', config: { rows: 10, cols: 15, rowSpacing: 55, colSpacing: 50, rowOffset: 25 }, }, ]; export class SeatingService { private generateId(): string { return `el_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; } /** Create a single chair element */ createChair(x: number, y: number, layerId: string, config: Partial = {}): CADElement { const cfg = { ...DEFAULT_CHAIR, ...config }; return { id: this.generateId(), type: 'chair', layerId, x, y, width: cfg.width, height: cfg.height, properties: { rotation: 0, fill: cfg.fill, backrestColor: cfg.backrestColor, outlineColor: cfg.outlineColor, seatType: 'standard', }, }; } /** Create a seating row (multiple chairs in a line) */ createSeatingRow(x: number, y: number, layerId: string, config: Partial = {}): CADElement[] { const cfg = { ...DEFAULT_ROW, ...config }; const elements: CADElement[] = []; const totalWidth = (cfg.count - 1) * cfg.spacing + cfg.chairWidth; const startX = x - totalWidth / 2 + cfg.chairWidth / 2; for (let i = 0; i < cfg.count; i++) { const cx = startX + i * cfg.spacing; const cy = y; // Apply rotation around center const rot = cfg.rotation * Math.PI / 180; const dx = cx - x; const dy = cy - y; const rx = dx * Math.cos(rot) - dy * Math.sin(rot) + x; const ry = dx * Math.sin(rot) + dy * Math.cos(rot) + y; elements.push({ id: this.generateId(), type: 'chair', layerId, x: rx, y: ry, width: cfg.chairWidth, height: cfg.chairHeight, properties: { rotation: cfg.rotation, fill: cfg.fill, backrestColor: '#3a7ac9', outlineColor: '#2a5a99', seatType: 'standard', rowIndex: i, rowId: `row_${Date.now()}`, }, }); } return elements; } /** Create a seating block (rows × cols of chairs) */ createSeatingBlock(x: number, y: number, layerId: string, config: Partial = {}): CADElement[] { const cfg = { ...DEFAULT_BLOCK, ...config }; const elements: CADElement[] = []; const totalW = (cfg.cols - 1) * cfg.colSpacing + cfg.chairWidth; const totalH = (cfg.rows - 1) * cfg.rowSpacing + cfg.chairHeight; const startX = x - totalW / 2 + cfg.chairWidth / 2; const startY = y - totalH / 2 + cfg.chairHeight / 2; const rot = cfg.rotation * Math.PI / 180; const blockId = `block_${Date.now()}`; for (let row = 0; row < cfg.rows; row++) { const offset = cfg.rowOffset * (row % 2); for (let col = 0; col < cfg.cols; col++) { const lx = startX + col * cfg.colSpacing + offset; const ly = startY + row * cfg.rowSpacing; // Rotate around center const dx = lx - x; const dy = ly - y; const rx = dx * Math.cos(rot) - dy * Math.sin(rot) + x; const ry = dx * Math.sin(rot) + dy * Math.cos(rot) + y; elements.push({ id: this.generateId(), type: 'chair', layerId, x: rx, y: ry, width: cfg.chairWidth, height: cfg.chairHeight, properties: { rotation: cfg.rotation, fill: cfg.fill, backrestColor: '#3a7ac9', outlineColor: '#2a5a99', seatType: 'standard', rowIndex: row, colIndex: col, blockId, }, }); } } return elements; } /** Create a table element */ createTable(x: number, y: number, layerId: string, config: Partial = {}): CADElement { const cfg = { ...DEFAULT_TABLE, ...config }; return { id: this.generateId(), type: 'table', layerId, x, y, width: cfg.width, height: cfg.height, properties: { rotation: cfg.rotation, fill: cfg.fill, shape: cfg.shape, stroke: '#5a4a37', strokeWidth: 1.5, }, }; } /** Create a stage element */ createStage(x: number, y: number, layerId: string, config: Partial = {}): CADElement { const cfg = { ...DEFAULT_STAGE, ...config }; return { id: this.generateId(), type: 'stage', layerId, x, y, width: cfg.width, height: cfg.height, properties: { rotation: cfg.rotation, fill: cfg.fill, stroke: '#1a2e3f', strokeWidth: 2, label: cfg.label, }, }; } /** Create elements from a template */ createFromTemplate(templateName: string, x: number, y: number, layerId: string): CADElement[] { const template = SEATING_TEMPLATES.find(t => t.name === templateName); if (!template) return []; if (template.type === 'row') { return this.createSeatingRow(x, y, layerId, template.config as Partial); } if (template.type === 'block') { return this.createSeatingBlock(x, y, layerId, template.config as Partial); } if (template.type === 'mixed') { const blocks = (template.config as { blocks: Array> }).blocks; const elements: CADElement[] = []; for (const blk of blocks) { const bx = x + (blk.offsetX || 0); const cfg: Partial = { rows: blk.rows, cols: blk.cols, rowSpacing: blk.rowSpacing || 50, colSpacing: blk.colSpacing || 50, rowOffset: 0, }; elements.push(...this.createSeatingBlock(bx, y, layerId, cfg)); } return elements; } return []; } /** Count seats in a list of elements */ countSeats(elements: CADElement[]): { total: number; byRow: Record; byBlock: Record } { let total = 0; const byRow: Record = {}; const byBlock: Record = {}; for (const el of elements) { if (el.type === 'chair') { total++; const rowId = el.properties.rowId as string | undefined; const blockId = el.properties.blockId as string | undefined; if (rowId) { byRow[rowId] = (byRow[rowId] || 0) + 1; } if (blockId) { byBlock[blockId] = (byBlock[blockId] || 0) + 1; } } } return { total, byRow, byBlock }; } /** Get all chairs belonging to a specific row */ getRowElements(elements: CADElement[], rowId: string): CADElement[] { return elements.filter(el => el.type === 'chair' && el.properties.rowId === rowId); } /** Get all chairs belonging to a specific block */ getBlockElements(elements: CADElement[], blockId: string): CADElement[] { return elements.filter(el => el.type === 'chair' && el.properties.blockId === blockId); } /** Add a chair to an existing row at the end */ addChairToRow(elements: CADElement[], rowId: string, layerId: string): CADElement | null { const rowChairs = this.getRowElements(elements, rowId); if (rowChairs.length === 0) return null; const last = rowChairs[rowChairs.length - 1]; const spacing = 50; const rot = (last.properties.rotation || 0) * Math.PI / 180; const dx = spacing; const dy = 0; const rx = dx * Math.cos(rot) - dy * Math.sin(rot) + last.x; const ry = dx * Math.sin(rot) + dy * Math.cos(rot) + last.y; return this.createChair(rx, ry, layerId, { width: last.width, height: last.height, fill: last.properties.fill as string, }); } /** Remove a chair from a row by index */ removeChairFromRow(elements: CADElement[], rowId: string, index: number): string[] { const rowChairs = this.getRowElements(elements, rowId); if (index < 0 || index >= rowChairs.length) return []; const sorted = rowChairs.sort((a, b) => (a.properties.rowIndex as number) - (b.properties.rowIndex as number)); return [sorted[index].id]; } }