task(C3): extend pixi element types - dimension/leader/revcloud/chair/table/stage

This commit is contained in:
Agent Zero
2026-08-27 10:07:35 +02:00
parent 8eb5a06816
commit 124b924457
2 changed files with 165 additions and 0 deletions
+106
View File
@@ -130,6 +130,112 @@ export function elementToPrimitives(el: CADElement): ShapePrimitive[] {
}]; }];
} }
// ── Task C3: bisher im Pixi-Pfad fehlende Typen ──
case 'dimension': {
// Legacy-Konvention: x1..y2 in properties, BBox-Zentrum als x/y,
// Extension lines + Hauptlinie; Pfeilspitzen sind Detail (C3+)
const x1 = (props.x1 as number | undefined) ?? el.x - el.width / 2;
const y1 = (props.y1 as number | undefined) ?? el.y;
const x2 = (props.x2 as number | undefined) ?? el.x + el.width / 2;
const y2 = (props.y2 as number | undefined) ?? el.y;
const color = (props.color as string | undefined) ?? '#aaaaaa';
const dimColor = (props.dimColor as string | undefined) ?? color;
const label = String(props.value ?? props.text ?? '');
const out: ShapePrimitive[] = [
{ kind: 'line', from: { x: x1, y: y1 }, to: { x: x2, y: y2 }, stroke: dimColor, strokeWidth: 1 },
];
if (label) {
out.push({
kind: 'text',
at: { x: (x1 + x2) / 2, y: (y1 + y2) / 2 - 12 },
text: label,
fontSize: 10,
color: dimColor,
});
}
return out;
}
case 'leader': {
// Legacy-Konvention: Linie p1→p2 + optionaler Text am Ende
const lx1 = (props.x1 as number | undefined) ?? el.x;
const ly1 = (props.y1 as number | undefined) ?? el.y;
const lx2 = (props.x2 as number | undefined) ?? el.x + el.width;
const ly2 = (props.y2 as number | undefined) ?? el.y + el.height;
const out: ShapePrimitive[] = [
{ kind: 'line', from: { x: lx1, y: ly1 }, to: { x: lx2, y: ly2 }, stroke, strokeWidth: sw },
];
const label = String(props.text ?? '');
if (label) {
out.push({
kind: 'text',
at: { x: lx2 + 4, y: ly2 - 4 },
text: label,
fontSize: (props.fontSize as number | undefined) ?? 12,
color: stroke,
});
}
return out;
}
case 'revcloud': {
// Revisionswolke: Punkte-Zug (Bogen-Detail folgt in C3+)
const pts = Array.isArray(props.points)
? (props.points as unknown[]).map(asPoint)
: [];
if (pts.length < 2) return [];
return [{
kind: 'polyline',
points: pts,
close: true,
stroke,
strokeWidth: sw,
}];
}
case 'chair': {
// Stuhl: gefülltes Quadrat in Element-Farbe + Umrandung
const fill = (props.fill as string | undefined) ?? '#4a90d9';
return [{
kind: 'rect',
x: el.x - el.width / 2,
y: el.y - el.height / 2,
w: el.width,
h: el.height,
fill,
stroke: (props.outlineColor as string | undefined) ?? '#2a5a99',
strokeWidth: 1,
}];
}
case 'table': {
// Tisch: Kreis in Element-Größe mit Füllung
const fill = (props.fill as string | undefined) ?? '#8b7355';
return [{
kind: 'circle',
center: { x: el.x, y: el.y },
radius: Math.max(el.width, el.height) / 2,
fill,
stroke: '#5a4a35',
strokeWidth: 1,
}];
}
case 'stage': {
// Bühne: Rechteck mit dunkler Füllung
return [{
kind: 'rect',
x: el.x - el.width / 2,
y: el.y - el.height / 2,
w: el.width,
h: el.height,
fill: (props.fill as string | undefined) ?? '#3a3a4a',
stroke,
strokeWidth: 1.5,
}];
}
default: default:
return []; return [];
} }
+59
View File
@@ -112,4 +112,63 @@ describe('elementToPrimitives', () => {
it('unbekannter Typ ergibt leere Liste (renderer-sicher)', () => { it('unbekannter Typ ergibt leere Liste (renderer-sicher)', () => {
expect(elementToPrimitives(el('x', 'definitely-unknown'))).toEqual([]); expect(elementToPrimitives(el('x', 'definitely-unknown'))).toEqual([]);
}); });
it('dimension: Linie + Label aus value/text', () => {
const prims = elementToPrimitives(el('d', 'dimension', {
x1: 0, y1: 0, x2: 100, y2: 0, value: '250 cm',
}, { x: 50, y: 0 }));
expect(prims.length).toBe(2);
expect(prims[0].kind).toBe('line');
const txt = prims[1];
if (txt.kind !== 'text') throw new Error('erwartete text');
expect(txt.text).toBe('250 cm');
});
it('leader: Linie + Label am Ende', () => {
const prims = elementToPrimitives(el('ld', 'leader', {
x1: 10, y1: 10, x2: 80, y2: 60, text: 'A1', fontSize: 14,
}));
expect(prims.length).toBe(2);
expect(prims[0].kind).toBe('line');
const txt = prims[1];
if (txt.kind !== 'text') throw new Error('erwartete text');
expect(txt.text).toBe('A1');
expect(txt.fontSize).toBe(14);
});
it('revcloud: geschlossener Punktzug', () => {
const prims = elementToPrimitives(el('rc', 'revcloud', {
points: [{ x: 0, y: 0 }, { x: 50, y: 0 }, { x: 50, y: 30 }],
}));
expect(prims.length).toBe(1);
const p = prims[0];
if (p.kind !== 'polyline') throw new Error('erwartete polyline');
expect(p.close).toBe(true);
});
it('chair: gefuelltes Rechteck mit Fallback-Farbe', () => {
const prims = elementToPrimitives(el('ch', 'chair', {}, { x: 50, y: 50, width: 20, height: 20 }));
const p = prims[0];
if (p.kind !== 'rect') throw new Error('erwartete rect');
expect(p.fill).toBe('#4a90d9');
expect(p.x).toBe(40);
expect(p.y).toBe(40);
});
it('table: Kreis mit Fallback-Farbe und Radius aus width', () => {
const prims = elementToPrimitives(el('tb', 'table', {}, { x: 0, y: 0, width: 60, height: 60 }));
const p = prims[0];
if (p.kind !== 'circle') throw new Error('erwartete circle');
expect(p.radius).toBe(30);
expect(p.fill).toBe('#8b7355');
});
it('stage: dunkles Rechteck', () => {
const prims = elementToPrimitives(el('st', 'stage', {}, { x: 0, y: 0, width: 200, height: 100 }));
const p = prims[0];
if (p.kind !== 'rect') throw new Error('erwartete rect');
expect(p.fill).toBe('#3a3a4a');
expect(p.w).toBe(200);
expect(p.h).toBe(100);
});
}); });