Replace frontend with real working CAD editor source from /data/web-cad-neu (TS, React 18, full ribbon bar, KI, layers, tools)
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* 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');
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user