/** * Event-Tools Plugin – Built-in example plugin * Adds custom element types: stage-curtain, spotlight, barrier * Adds command: EVENT_SEATING (generates seating rows) */ import type { Plugin, PluginContext, ElementTypeExtension, CommandExtension } from '../types'; import type { CADElement } from '../../types/cad.types'; function uid(prefix: string): string { return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 10000)}`; } // ─── Element Type: Stage Curtain ──────────────────────── const stageCurtain: ElementTypeExtension = { typeName: 'stage-curtain', displayName: 'Bühnenvorhang', defaultWidth: 6, defaultHeight: 0.3, defaultProperties: { fill: '#8B0000', stroke: '#5C0000', strokeWidth: 2, curtainStyle: 'pleated', }, render(ctx, element, scale) { const { x, y, width, height, properties } = element; ctx.save(); ctx.fillStyle = (properties.fill as string) || '#8B0000'; ctx.strokeStyle = (properties.stroke as string) || '#5C0000'; ctx.lineWidth = (properties.strokeWidth as number) || 2; // Draw pleated curtain const pleats = Math.max(6, Math.floor(width / 0.5)); const pleatWidth = width / pleats; ctx.beginPath(); ctx.moveTo(x, y); for (let i = 0; i <= pleats; i++) { const px = x + i * pleatWidth; const py = y + (i % 2 === 0 ? 0 : height * 0.15); ctx.lineTo(px, py); } ctx.lineTo(x + width, y + height); ctx.lineTo(x, y + height); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.restore(); return true; }, hitTest(element, hx, hy, tolerance) { const { x, y, width, height } = element; return hx >= x - tolerance && hx <= x + width + tolerance && hy >= y - tolerance && hy <= y + height + tolerance; }, propertyFields: [ { key: 'fill', label: 'Farbe', type: 'color' }, { key: 'curtainStyle', label: 'Stil', type: 'select', options: [ { value: 'pleated', label: 'Gefaltet' }, { value: 'flat', label: 'Glatt' }, ]}, ], }; // ─── Element Type: Spotlight ──────────────────────────── const spotlight: ElementTypeExtension = { typeName: 'spotlight', displayName: 'Scheinwerfer', defaultWidth: 2, defaultHeight: 2, defaultProperties: { fill: 'rgba(255, 220, 100, 0.3)', stroke: '#FFD700', strokeWidth: 1.5, beamAngle: 45, }, render(ctx, element, scale) { const { x, y, width, height, properties } = element; ctx.save(); const cx = x + width / 2; const cy = y + height / 2; const radius = Math.max(width, height) / 2; // Draw beam cone const beamAngle = ((properties.beamAngle as number) || 45) * Math.PI / 180; const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius); gradient.addColorStop(0, 'rgba(255, 220, 100, 0.5)'); gradient.addColorStop(1, 'rgba(255, 220, 100, 0.05)'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, radius, -Math.PI / 2 - beamAngle / 2, -Math.PI / 2 + beamAngle / 2); ctx.closePath(); ctx.fill(); // Draw fixture circle ctx.fillStyle = '#333'; ctx.strokeStyle = (properties.stroke as string) || '#FFD700'; ctx.lineWidth = (properties.strokeWidth as number) || 1.5; ctx.beginPath(); ctx.arc(cx, cy, 0.15 * scale, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.restore(); return true; }, hitTest(element, hx, hy, tolerance) { const cx = element.x + element.width / 2; const cy = element.y + element.height / 2; const radius = Math.max(element.width, element.height) / 2 + tolerance; const dx = hx - cx; const dy = hy - cy; return dx * dx + dy * dy <= radius * radius; }, propertyFields: [ { key: 'beamAngle', label: 'Strahlwinkel°', type: 'number', min: 10, max: 180, step: 5 }, { key: 'stroke', label: 'Farbe', type: 'color' }, ], }; // ─── Element Type: Barrier ────────────────────────────── const barrier: ElementTypeExtension = { typeName: 'barrier', displayName: 'Absperrung', defaultWidth: 3, defaultHeight: 0.1, defaultProperties: { fill: '#FFA500', stroke: '#CC8400', strokeWidth: 1.5, pattern: 'striped', }, render(ctx, element, scale) { const { x, y, width, height, properties } = element; ctx.save(); ctx.fillStyle = (properties.fill as string) || '#FFA500'; ctx.strokeStyle = (properties.stroke as string) || '#CC8400'; ctx.lineWidth = (properties.strokeWidth as number) || 1.5; // Draw striped barrier const stripeWidth = 0.3; const stripes = Math.floor(width / stripeWidth); for (let i = 0; i < stripes; i++) { ctx.fillStyle = i % 2 === 0 ? '#FFA500' : '#000000'; ctx.fillRect(x + i * stripeWidth, y, stripeWidth, height); } ctx.strokeStyle = (properties.stroke as string) || '#CC8400'; ctx.strokeRect(x, y, width, height); ctx.restore(); return true; }, hitTest(element, hx, hy, tolerance) { const { x, y, width, height } = element; return hx >= x - tolerance && hx <= x + width + tolerance && hy >= y - tolerance && hy <= y + height + tolerance; }, propertyFields: [ { key: 'fill', label: 'Farbe', type: 'color' }, { key: 'pattern', label: 'Muster', type: 'select', options: [ { value: 'striped', label: 'Gestreift' }, { value: 'solid', label: 'Einfarbig' }, ]}, ], }; // ─── Command: EVENT_SEATING ───────────────────────────── const eventSeatingCommand: CommandExtension = { name: 'EVENT_SEATING', description: 'Erzeugt Bestuhlung in Reihen', usage: 'EVENT_SEATING [gap] [rowGap]', execute(args, context) { const rows = parseInt(args[0] || '5', 10); const cols = parseInt(args[1] || '10', 10); const gap = parseFloat(args[2] || '0.6'); const rowGap = parseFloat(args[3] || '1.0'); const layerId = context.getActiveLayerId(); const chairWidth = 0.5; const chairHeight = 0.5; const startX = 2; const startY = 2; let count = 0; for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { const el: CADElement = { id: uid('chair'), type: 'chair', layerId, x: startX + c * (chairWidth + gap), y: startY + r * (chairHeight + rowGap), width: chairWidth, height: chairHeight, properties: { fill: '#4A90D9', stroke: '#2A70B9', strokeWidth: 1, rotation: 0 }, }; context.addElement(el); count++; } } context.showToast(`${count} Stühle in ${rows} Reihen erstellt`, 'success'); }, }; // ─── Plugin Definition ────────────────────────────────── export const eventToolsPlugin: Plugin = { manifest: { id: 'event-tools', name: 'Event-Tools', version: '1.0.0', author: 'Web CAD Team', description: 'Erweitert Web CAD um Bühnenvorhänge, Scheinwerfer, Absperrungen und Bestuhlungs-Befehle.', category: 'elements', enabledByDefault: true, }, elementTypes: [stageCurtain, spotlight, barrier], commands: [eventSeatingCommand], onInit(context) { context.log('Event-Tools Plugin initialisiert'); }, onActivate(context) { context.log('Event-Tools Plugin aktiviert'); }, };