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
@@ -6,6 +6,9 @@
* (2-Punkt Pfeil+Label).
*/
import type { PluginV2, ToolExtensionV2, FullToolContext } from '../../types';
import {
DEFAULT_DIM_STYLE, getDimStyle, computeDimValue, formatDimValue,
} from '../../../services/dimStyleService';
import type { CADElement } from '../../../types/cad.types';
import type { Pt } from '../../../tools/modification/geometry';
@@ -193,6 +196,197 @@ export const mtextTool: ToolExtensionV2 = {
},
};
// ─────────────────────────────────────────────────────────────
// Task CAD-1: Assoziative Bemassung — 5 Typen mit DimStyles.
// linear (H/V), aligned, angular (3 Klicks), radius, diameter.
// Alle berechnen den Masswert aus der Geometrie (assoziativ).
// ─────────────────────────────────────────────────────────────
let dimStart: Pt | null = null;
let dimVertex: Pt | null = null; // angular: Scheitel
function dimStyleOf(ctx: FullToolContext) {
const name = (ctx.options.dimStyle as string | undefined) ?? 'ISO-25';
return getDimStyle(name);
}
function commitDim(el: CADElement, ctx: FullToolContext, noun: string): void {
const c = ctx as FullToolContext;
if (!c.doc) return;
c.doc.transact(() => c.doc!.addElement(el));
ctx.setPreview?.(null);
ctx.setStatus(`${noun} erstellt: ${String(el.properties.value)}`);
}
export const dimLinearTool: ToolExtensionV2 = {
manifest: {
id: 'dim-linear', label: 'Linear-Bemassung', icon: '\u2194',
ribbonTab: 'format', tags: ['basic'],
description: 'Lineare Bemassung: 2 Punkte, horizontal/vertikal (Option)',
},
optionsSchema: [
{ key: 'dimStyle', label: 'Stil', type: 'select',
options: [{ value: 'ISO-25', label: 'ISO-25' }, { value: 'ISO-50', label: 'ISO-50' },
{ value: 'Architectural', label: 'Architektur' }, { value: 'Engineering', label: 'Engineering' }] },
{ key: 'dimSubtype', label: 'Ausrichtung', type: 'select',
options: [{ value: 'horizontal', label: 'Horizontal' }, { value: 'vertical', label: 'Vertikal' }] },
],
handlers: {
down(e, ctx) {
const c = ctx as FullToolContext;
if (!c.doc) return;
if (!dimStart) { dimStart = e.world; ctx.setStatus('Bemassung: zweiten Punkt wählen'); return; }
const subtype = (c.options.dimSubtype as string | undefined) ?? 'horizontal';
const style = dimStyleOf(c);
const raw = computeDimValue('linear', dimStart, e.world, undefined, subtype);
const el: CADElement = {
id: uid('dim'), type: 'dimension', layerId: (c.options.layerId as string | undefined) ?? 'layer-0',
x: (dimStart.x + e.world.x) / 2, y: (dimStart.y + e.world.y) / 2,
width: Math.abs(e.world.x - dimStart.x), height: Math.abs(e.world.y - dimStart.y),
properties: {
x1: dimStart.x, y1: dimStart.y, x2: e.world.x, y2: e.world.y,
dimType: 'linear', dimSubtype: subtype,
value: formatDimValue(raw, style), dimStyle: style.name,
},
} as unknown as CADElement;
dimStart = null;
commitDim(el, ctx, 'Linearbemassung');
},
move(e, ctx) {
const c = ctx as FullToolContext;
if (!dimStart) return;
const subtype = (c.options.dimSubtype as string | undefined) ?? 'horizontal';
const style = dimStyleOf(c);
const raw = computeDimValue('linear', dimStart, e.world, undefined, subtype);
ctx.setPreview?.({
id: '__preview_dim__', type: 'dimension', layerId: 'layer-0',
x: (dimStart.x + e.world.x) / 2, y: (dimStart.y + e.world.y) / 2,
width: Math.abs(e.world.x - dimStart.x), height: Math.abs(e.world.y - dimStart.y),
properties: {
x1: dimStart.x, y1: dimStart.y, x2: e.world.x, y2: e.world.y,
dimType: 'linear', dimSubtype: subtype,
value: formatDimValue(raw, style), dimStyle: style.name,
},
} as unknown as CADElement);
},
cancel(ctx) { dimStart = null; ctx.setPreview?.(null); ctx.setStatus('Bemassung abgebrochen'); },
},
};
export const dimAlignedTool: ToolExtensionV2 = {
manifest: {
id: 'dim-aligned', label: 'Ausgerichtet', icon: '\u2197',
ribbonTab: 'format', tags: ['basic'],
description: 'Bemassung entlang der Punkte-Verbindung',
},
optionsSchema: [
{ key: 'dimStyle', label: 'Stil', type: 'select',
options: [{ value: 'ISO-25', label: 'ISO-25' }, { value: 'ISO-50', label: 'ISO-50' }] },
],
handlers: {
down(e, ctx) {
const c = ctx as FullToolContext;
if (!c.doc) return;
if (!dimStart) { dimStart = e.world; ctx.setStatus('Ausgerichtet: zweiten Punkt wählen'); return; }
const style = dimStyleOf(c);
const raw = computeDimValue('aligned', dimStart, e.world);
const el: CADElement = {
id: uid('dim'), type: 'dimension', layerId: (c.options.layerId as string | undefined) ?? 'layer-0',
x: (dimStart.x + e.world.x) / 2, y: (dimStart.y + e.world.y) / 2,
width: Math.abs(e.world.x - dimStart.x), height: Math.abs(e.world.y - dimStart.y),
properties: {
x1: dimStart.x, y1: dimStart.y, x2: e.world.x, y2: e.world.y,
dimType: 'aligned', value: formatDimValue(raw, style), dimStyle: style.name,
},
} as unknown as CADElement;
dimStart = null;
commitDim(el, ctx, 'Ausgerichtete Bemassung');
},
cancel(ctx) { dimStart = null; ctx.setPreview?.(null); ctx.setStatus('Abgebrochen'); },
},
};
export const dimAngularTool: ToolExtensionV2 = {
manifest: {
id: 'dim-angular', label: 'Winkel', icon: '\u2220',
ribbonTab: 'format', tags: ['pro'],
description: 'Winkelbemassung: Scheitel, Schenkel 1, Schenkel 2',
},
optionsSchema: [
{ key: 'dimStyle', label: 'Stil', type: 'select',
options: [{ value: 'ISO-25', label: 'ISO-25' }] },
],
handlers: {
down(e, ctx) {
const c = ctx as FullToolContext;
if (!c.doc) return;
if (!dimVertex) { dimVertex = e.world; ctx.setStatus('Winkel: Schenkel 1 klicken'); return; }
if (!dimStart) { dimStart = e.world; ctx.setStatus('Winkel: Schenkel 2 klicken'); return; }
const style = dimStyleOf(c);
const leg1 = { x: dimStart.x - dimVertex.x, y: dimStart.y - dimVertex.y };
const leg2 = { x: e.world.x - dimVertex.x, y: e.world.y - dimVertex.y };
const raw = computeDimValue('angular', leg1, leg2);
const el: CADElement = {
id: uid('dim'), type: 'dimension', layerId: (c.options.layerId as string | undefined) ?? 'layer-0',
x: dimVertex.x, y: dimVertex.y, width: 0, height: 0,
properties: {
dimType: 'angular', value: formatDimValue(raw, style, 'angular'),
vertexX: dimVertex.x, vertexY: dimVertex.y,
leg1X: dimStart.x, leg1Y: dimStart.y, leg2X: e.world.x, leg2Y: e.world.y,
dimStyle: style.name,
},
} as unknown as CADElement;
dimVertex = null; dimStart = null;
commitDim(el, ctx, 'Winkelbemassung');
},
cancel(ctx) { dimVertex = null; dimStart = null; ctx.setPreview?.(null); ctx.setStatus('Abgebrochen'); },
},
};
function makeRadialDim(mode: 'radius' | 'diameter'): ToolExtensionV2 {
return {
manifest: {
id: mode === 'radius' ? 'dim-radius' : 'dim-diameter',
label: mode === 'radius' ? 'Radius' : 'Durchmesser',
icon: mode === 'radius' ? '\u2199' : '\u2300',
ribbonTab: 'format', tags: ['basic'],
description: mode === 'radius' ? 'Radius bemassen (Kreis anklicken)' : 'Durchmesser bemassen (Kreis anklicken)',
},
optionsSchema: [
{ key: 'dimStyle', label: 'Stil', type: 'select',
options: [{ value: 'ISO-25', label: 'ISO-25' }] },
],
handlers: {
down(e, ctx) {
const c = ctx as FullToolContext;
if (!c.doc || !c.selection) return;
const hit = c.selection.hitTest(e.world.x, e.world.y, 8);
if (!hit || hit.type !== 'circle') {
ctx.setStatus(`${mode === 'radius' ? 'Radius' : 'Durchmesser'}: Kreis anklicken`);
return;
}
const style = dimStyleOf(c);
const raw = computeDimValue(mode, undefined, undefined, hit);
const el: CADElement = {
id: uid('dim'), type: 'dimension', layerId: hit.layerId,
x: hit.x, y: hit.y, width: hit.width, height: hit.height,
properties: {
dimType: mode, value: formatDimValue(raw, style, mode),
centerX: hit.x, centerY: hit.y,
arrowX: hit.x + ((hit.properties as Record<string, unknown>).radius as number ?? hit.width / 2),
arrowY: hit.y,
dimStyle: style.name,
},
} as unknown as CADElement;
commitDim(el, ctx, mode === 'radius' ? 'Radiusbemassung' : 'Durchmesserbemassung');
},
},
};
}
export const dimRadiusTool: ToolExtensionV2 = makeRadialDim('radius');
export const dimDiameterTool: ToolExtensionV2 = makeRadialDim('diameter');
export const coreAnnotatePlugin: PluginV2 = {
manifest: {
id: 'core-annotate',
@@ -203,5 +397,5 @@ export const coreAnnotatePlugin: PluginV2 = {
category: 'tools',
enabledByDefault: true,
},
tools: [textTool, dimensionTool, leaderTool, mtextTool],
tools: [textTool, dimensionTool, leaderTool, mtextTool, dimLinearTool, dimAlignedTool, dimAngularTool, dimRadiusTool, dimDiameterTool],
};