task(CAD-1): associative dimensioning - DimStyles (ISO-25/50/Arch/Engineering), 5 dimension types (linear H/V, aligned, angular, radius, diameter), computed values, full primitives with arrows + extension lines

This commit is contained in:
Agent Zero
2026-08-29 09:58:45 +02:00
parent 17b62ee36a
commit 44a60fa718
5 changed files with 709 additions and 16 deletions
+84 -10
View File
@@ -15,6 +15,7 @@
* - arc-Winkel in Grad, 0° = 3 Uhr, gegen den Uhrzeigersinn
*/
import type { CADElement } from '../../types/cad.types';
import { getDimStyle } from '../../services/dimStyleService';
import type { Pt } from '../../tools/modification/geometry';
/** Zeichenprimitiv — kleinste darstellbare Einheit. */
@@ -42,6 +43,29 @@ function fillOrNull(raw: unknown): string | null {
* Gibt ein leeres Array zurück für unbekannte Typen bzw. Elemente,
* die nichts Sichtbares beitragen (z.B. leerer Text).
*/
/**
* Pfeilspitze als geschlossenes Polygon (Task CAD-1).
* Spitze bei (x,y), Richtung ang (rad), Laenge size.
*/
function arrowPrimitives(x: number, y: number, ang: number, size: number, color: string): ShapePrimitive[] {
const halfW = size * 0.35;
const bx = x - size * Math.cos(ang);
const by = y - size * Math.sin(ang);
const px = -Math.sin(ang) * halfW;
const py = Math.cos(ang) * halfW;
return [{
kind: 'polyline',
points: [
{ x, y },
{ x: bx + px, y: by + py },
{ x: bx - px, y: by - py },
],
close: true,
stroke: color,
strokeWidth: 1,
}];
}
export function elementToPrimitives(el: CADElement, attrDefs?: Array<{ tag: string; prompt: string; defaultValue?: string }>): ShapePrimitive[] {
const props = (el.properties ?? {}) as Record<string, unknown>;
const stroke = (props.stroke as string | undefined) ?? FALLBACK_STROKE;
@@ -133,25 +157,75 @@ export function elementToPrimitives(el: CADElement, attrDefs?: Array<{ tag: stri
// ── 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+)
// Task CAD-1: komplette Bemassung mit Extension lines, Masslinie,
// Pfeilspitzen (geschlossene Polygone) und Wert-Text nach DimStyle.
const dimType = (props.dimType as string | undefined) ?? 'linear';
const style = getDimStyle(props.dimStyle as string | undefined);
const dimColor = (props.dimColor as string | undefined) ?? (props.color as string | undefined) ?? '#aaaaaa';
const label = String(props.value ?? props.text ?? '');
if (dimType === 'radius' || dimType === 'diameter') {
const cx = (props.centerX as number | undefined) ?? el.x;
const cy = (props.centerY as number | undefined) ?? el.y;
const ax = (props.arrowX as number | undefined) ?? cx + el.width / 2;
const ay = (props.arrowY as number | undefined) ?? cy;
const out: ShapePrimitive[] = [
{ kind: 'line', from: { x: cx, y: cy }, to: { x: ax, y: ay }, stroke: dimColor, strokeWidth: 1 },
];
out.push(...arrowPrimitives(ax, ay, Math.atan2(ay - cy, ax - cx), style.arrowSize, dimColor));
if (label) {
out.push({ kind: 'text', at: { x: (cx + ax) / 2, y: (cy + ay) / 2 - style.textHeight * 0.6 }, text: label, fontSize: style.textHeight * 4, color: dimColor });
}
return out;
}
if (dimType === 'angular') {
const vx = (props.vertexX as number | undefined) ?? el.x;
const vy = (props.vertexY as number | undefined) ?? el.y;
const l1x = (props.leg1X as number | undefined) ?? vx + 100;
const l1y = (props.leg1Y as number | undefined) ?? vy;
const l2x = (props.leg2X as number | undefined) ?? vx;
const l2y = (props.leg2Y as number | undefined) ?? vy + 100;
const out: ShapePrimitive[] = [
{ kind: 'line', from: { x: vx, y: vy }, to: { x: l1x, y: l1y }, stroke: dimColor, strokeWidth: 1 },
{ kind: 'line', from: { x: vx, y: vy }, to: { x: l2x, y: l2y }, stroke: dimColor, strokeWidth: 1 },
];
if (label) {
out.push({ kind: 'text', at: { x: vx + 30, y: vy - 10 }, text: label, fontSize: style.textHeight * 4, color: dimColor });
}
return out;
}
// linear / aligned
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 subtype = (props.dimSubtype as string | undefined) ?? 'horizontal';
// Masslinie: bei horizontal bei y = min(y1,y2) - offset; vertical bei x analog
const off = 15;
let mx1 = x1, my1 = y1, mx2 = x2, my2 = y2;
if (subtype === 'horizontal') { my1 = Math.min(y1, y2) - off; my2 = my1; }
else if (subtype === 'vertical') { mx1 = Math.min(x1, x2) - off; mx2 = mx1; }
else { /* aligned: Masslinie = Punkte-Verbindung */ }
const out: ShapePrimitive[] = [
{ kind: 'line', from: { x: x1, y: y1 }, to: { x: x2, y: y2 }, stroke: dimColor, strokeWidth: 1 },
// Extension lines
{ kind: 'line', from: { x: x1, y: y1 }, to: { x: x1, y: my1 }, stroke: '#888888', strokeWidth: 0.5 },
{ kind: 'line', from: { x: x2, y: y2 }, to: { x: x2, y: my2 }, stroke: '#888888', strokeWidth: 0.5 },
// Masslinie
{ kind: 'line', from: { x: mx1, y: my1 }, to: { x: mx2, y: my2 }, stroke: dimColor, strokeWidth: 1 },
];
// Pfeilspitzen an beiden Enden der Masslinie
const ang1 = Math.atan2(my2 - my1, mx2 - mx1);
out.push(...arrowPrimitives(mx1, my1, ang1, style.arrowSize, dimColor));
out.push(...arrowPrimitives(mx2, my2, ang1 + Math.PI, style.arrowSize, dimColor));
if (label) {
out.push({
kind: 'text',
at: { x: (x1 + x2) / 2, y: (y1 + y2) / 2 - 12 },
text: label,
fontSize: 10,
color: dimColor,
at: { x: (mx1 + mx2) / 2, y: (my1 + my2) / 2 - style.textHeight * 0.6 },
text: label, fontSize: style.textHeight * 4, color: dimColor,
});
}
return out;