222 lines
7.4 KiB
TypeScript
222 lines
7.4 KiB
TypeScript
/**
|
||
* 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 <rows> <cols> [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');
|
||
},
|
||
};
|