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:
@@ -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],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user