feat(viewer2d): add complex linetype rendering & LIN pattern parser support
This commit is contained in:
File diff suppressed because one or more lines are too long
+1
-1
@@ -97,7 +97,7 @@
|
|||||||
#coords .lbl { color: var(--accent); margin-right: 6px; font-weight: 600; }
|
#coords .lbl { color: var(--accent); margin-right: 6px; font-weight: 600; }
|
||||||
#coords .sep { color: var(--line); margin: 0 12px; }
|
#coords .sep { color: var(--line); margin: 0 12px; }
|
||||||
</style>
|
</style>
|
||||||
<script type="module" crossorigin src="/assets/index-B3deAy7F.js"></script>
|
<script type="module" crossorigin src="/assets/index-DIjyiS5T.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
+383
-3
@@ -11,6 +11,7 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|||||||
import { aciToHex } from './aciColors.js';
|
import { aciToHex } from './aciColors.js';
|
||||||
import { buildAllowedOwnerHexes, listCadSpaces } from './cadSpaces';
|
import { buildAllowedOwnerHexes, listCadSpaces } from './cadSpaces';
|
||||||
import { SlugTextEngine, SlugTextBatch } from './slugText';
|
import { SlugTextEngine, SlugTextBatch } from './slugText';
|
||||||
|
import { getLinPattern } from './linParser';
|
||||||
|
|
||||||
const DEFAULT_COLOR = 0xc9d1d9;
|
const DEFAULT_COLOR = 0xc9d1d9;
|
||||||
const ARC_SEGS = 64;
|
const ARC_SEGS = 64;
|
||||||
@@ -424,7 +425,9 @@ export class Viewer2D {
|
|||||||
const d = e.data || e;
|
const d = e.data || e;
|
||||||
const type = (e.type || e.typeName || '').toUpperCase();
|
const type = (e.type || e.typeName || '').toUpperCase();
|
||||||
const color = this._entityColor(e);
|
const color = this._entityColor(e);
|
||||||
curDash = this._resolveDash(e);
|
const complexLt = this._resolveComplexLinetype(e);
|
||||||
|
if (complexLt) curDash = null;
|
||||||
|
else curDash = this._resolveDash(e);
|
||||||
const meta = { entity: e, type, bounds: null, colStart: lineColors.length };
|
const meta = { entity: e, type, bounds: null, colStart: lineColors.length };
|
||||||
this._entityMeta.push(meta);
|
this._entityMeta.push(meta);
|
||||||
this._pickCurIdx = this._entityMeta.length - 1; // owner for pick geometry emitted below
|
this._pickCurIdx = this._entityMeta.length - 1; // owner for pick geometry emitted below
|
||||||
@@ -435,21 +438,35 @@ export class Viewer2D {
|
|||||||
// ── Basic geometry ────────────────────────────────────────────────
|
// ── Basic geometry ────────────────────────────────────────────────
|
||||||
case 'LINE':
|
case 'LINE':
|
||||||
if (d.start && d.end) {
|
if (d.start && d.end) {
|
||||||
|
if (complexLt) {
|
||||||
|
this._drawComplexPath([d.start, d.end], false, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
} else {
|
||||||
pushSeg(d.start.x, d.start.y, d.end.x, d.end.y, d.start.z || 0, color);
|
pushSeg(d.start.x, d.start.y, d.end.x, d.end.y, d.start.z || 0, color);
|
||||||
|
}
|
||||||
meta.bounds = { type:'line', x1:d.start.x, y1:d.start.y, x2:d.end.x, y2:d.end.y };
|
meta.bounds = { type:'line', x1:d.start.x, y1:d.start.y, x2:d.end.x, y2:d.end.y };
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'CIRCLE':
|
case 'CIRCLE':
|
||||||
if (d.center && d.radius != null) {
|
if (d.center && d.radius != null) {
|
||||||
|
if (complexLt) {
|
||||||
|
const pts = this._sampleArcPoints(d.center, d.radius, 0, Math.PI * 2, d.center.z || 0);
|
||||||
|
this._drawComplexPath(pts, true, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
} else {
|
||||||
this._arcSegs(d.center, d.radius, 0, Math.PI*2, d.center.z||0, color, pushSeg);
|
this._arcSegs(d.center, d.radius, 0, Math.PI*2, d.center.z||0, color, pushSeg);
|
||||||
|
}
|
||||||
meta.bounds = { type:'circle', cx:d.center.x, cy:d.center.y, r:d.radius };
|
meta.bounds = { type:'circle', cx:d.center.x, cy:d.center.y, r:d.radius };
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ARC':
|
case 'ARC':
|
||||||
if (d.center && d.radius != null) {
|
if (d.center && d.radius != null) {
|
||||||
|
if (complexLt) {
|
||||||
|
const pts = this._sampleArcPoints(d.center, d.radius, d.startAngle ?? 0, d.endAngle ?? Math.PI * 2, d.center.z || 0);
|
||||||
|
this._drawComplexPath(pts, false, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
} else {
|
||||||
this._arcSegs(d.center, d.radius, d.startAngle??0, d.endAngle??Math.PI*2, d.center.z||0, color, pushSeg);
|
this._arcSegs(d.center, d.radius, d.startAngle??0, d.endAngle??Math.PI*2, d.center.z||0, color, pushSeg);
|
||||||
|
}
|
||||||
meta.bounds = { type:'circle', cx:d.center.x, cy:d.center.y, r:d.radius };
|
meta.bounds = { type:'circle', cx:d.center.x, cy:d.center.y, r:d.radius };
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -461,6 +478,10 @@ export class Viewer2D {
|
|||||||
const nSeg = closed ? d.points.length : d.points.length - 1;
|
const nSeg = closed ? d.points.length : d.points.length - 1;
|
||||||
if (this._polyHasWidth(e, nSeg, 1)) {
|
if (this._polyHasWidth(e, nSeg, 1)) {
|
||||||
this._widePolyMesh(d.points, d.bulges, closed, e, color, 1, expand);
|
this._widePolyMesh(d.points, d.bulges, closed, e, color, 1, expand);
|
||||||
|
} else if (complexLt) {
|
||||||
|
const pts = this._samplePolylinePoints(d.points, d.bulges, closed, d.elevation || 0);
|
||||||
|
this._drawComplexPath(pts, closed, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
pts.forEach(p => expand(p.x, p.y, p.z));
|
||||||
} else if (hasBulge) {
|
} else if (hasBulge) {
|
||||||
this._bulgePolySegs(d.points, d.bulges, closed, color, pushSeg, expand);
|
this._bulgePolySegs(d.points, d.bulges, closed, color, pushSeg, expand);
|
||||||
} else {
|
} else {
|
||||||
@@ -474,7 +495,14 @@ export class Viewer2D {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'POLYLINE':
|
case 'POLYLINE':
|
||||||
|
if (complexLt && (d.vertices || d.points)) {
|
||||||
|
const closed = !!(d.closed || (d.flags & 1));
|
||||||
|
const pts = d.vertices || d.points;
|
||||||
|
this._drawComplexPath(pts, closed, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
pts.forEach(p => expand(p.x, p.y, p.z || 0));
|
||||||
|
} else {
|
||||||
this._polylineSegs(d.vertices||d.points, d.closed||(d.flags&1), 0, color, pushSeg);
|
this._polylineSegs(d.vertices||d.points, d.closed||(d.flags&1), 0, color, pushSeg);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'POLYLINE_2D': {
|
case 'POLYLINE_2D': {
|
||||||
@@ -487,7 +515,11 @@ export class Viewer2D {
|
|||||||
const bulges = kids.map(v => v.bulge || 0);
|
const bulges = kids.map(v => v.bulge || 0);
|
||||||
const closed = (d.flags & 1) === 1;
|
const closed = (d.flags & 1) === 1;
|
||||||
const elev = d.elevation || 0;
|
const elev = d.elevation || 0;
|
||||||
if (bulges.some(b => Math.abs(b) >= 1e-6)) {
|
if (complexLt) {
|
||||||
|
const sampled = this._samplePolylinePoints(pts, bulges, closed, elev);
|
||||||
|
this._drawComplexPath(sampled, closed, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
sampled.forEach(p => expand(p.x, p.y, p.z));
|
||||||
|
} else if (bulges.some(b => Math.abs(b) >= 1e-6)) {
|
||||||
this._bulgePolySegs(pts, bulges, closed, color, pushSeg, expand);
|
this._bulgePolySegs(pts, bulges, closed, color, pushSeg, expand);
|
||||||
} else {
|
} else {
|
||||||
this._polylineSegs(pts, closed, elev, color, pushSeg);
|
this._polylineSegs(pts, closed, elev, color, pushSeg);
|
||||||
@@ -511,7 +543,13 @@ export class Viewer2D {
|
|||||||
|
|
||||||
case 'ELLIPSE':
|
case 'ELLIPSE':
|
||||||
if (d.center) {
|
if (d.center) {
|
||||||
|
if (complexLt) {
|
||||||
|
const pts = this._sampleEllipsePoints(d);
|
||||||
|
this._drawComplexPath(pts, false, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
pts.forEach(p => expand(p.x, p.y, p.z || 0));
|
||||||
|
} else {
|
||||||
this._ellipseSegs(d, color, pushSeg);
|
this._ellipseSegs(d, color, pushSeg);
|
||||||
|
}
|
||||||
meta.bounds = { type:'point', cx:d.center.x, cy:d.center.y };
|
meta.bounds = { type:'point', cx:d.center.x, cy:d.center.y };
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -530,8 +568,13 @@ export class Viewer2D {
|
|||||||
return result;
|
return result;
|
||||||
})()
|
})()
|
||||||
: cps;
|
: cps;
|
||||||
|
if (complexLt) {
|
||||||
|
this._drawComplexPath(pts, !!d.closed, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
pts.forEach(p => expand(p.x, p.y, p.z || 0));
|
||||||
|
} else {
|
||||||
this._polylineSegs(pts, false, 0, color, pushSeg);
|
this._polylineSegs(pts, false, 0, color, pushSeg);
|
||||||
pts.forEach(p => expand(p.x, p.y, p.z || 0));
|
pts.forEach(p => expand(p.x, p.y, p.z || 0));
|
||||||
|
}
|
||||||
meta.bounds = { type:'point', cx:pts[0].x, cy:pts[0].y };
|
meta.bounds = { type:'point', cx:pts[0].x, cy:pts[0].y };
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1247,29 +1290,50 @@ export class Viewer2D {
|
|||||||
const d = e.data || e;
|
const d = e.data || e;
|
||||||
const type = (e.type || e.typeName || '').toUpperCase();
|
const type = (e.type || e.typeName || '').toUpperCase();
|
||||||
const color = this._entityColor(e);
|
const color = this._entityColor(e);
|
||||||
|
const complexLt = this._resolveComplexLinetype(e);
|
||||||
|
const vpTextStart = pendingTexts.length;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'LINE':
|
case 'LINE':
|
||||||
if (d.start && d.end) {
|
if (d.start && d.end) {
|
||||||
|
if (complexLt) {
|
||||||
|
this._drawComplexPath([d.start, d.end], false, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
} else {
|
||||||
pushSeg(d.start.x, d.start.y, d.end.x, d.end.y, d.start.z || 0, color);
|
pushSeg(d.start.x, d.start.y, d.end.x, d.end.y, d.start.z || 0, color);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'CIRCLE':
|
case 'CIRCLE':
|
||||||
if (d.center && d.radius != null) {
|
if (d.center && d.radius != null) {
|
||||||
|
if (complexLt) {
|
||||||
|
const pts = this._sampleArcPoints(d.center, d.radius, 0, Math.PI * 2, d.center.z || 0);
|
||||||
|
this._drawComplexPath(pts, true, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
} else {
|
||||||
// pushSeg maps model endpoints → paper; keep radius in model units
|
// pushSeg maps model endpoints → paper; keep radius in model units
|
||||||
this._arcSegs(d.center, d.radius, 0, Math.PI * 2, d.center.z || 0, color, pushSeg);
|
this._arcSegs(d.center, d.radius, 0, Math.PI * 2, d.center.z || 0, color, pushSeg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'ARC':
|
case 'ARC':
|
||||||
if (d.center && d.radius != null) {
|
if (d.center && d.radius != null) {
|
||||||
|
if (complexLt) {
|
||||||
|
const pts = this._sampleArcPoints(d.center, d.radius, d.startAngle ?? 0, d.endAngle ?? Math.PI * 2, d.center.z || 0);
|
||||||
|
this._drawComplexPath(pts, false, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
} else {
|
||||||
this._arcSegs(d.center, d.radius, d.startAngle ?? 0, d.endAngle ?? Math.PI * 2, d.center.z || 0, color, pushSeg);
|
this._arcSegs(d.center, d.radius, d.startAngle ?? 0, d.endAngle ?? Math.PI * 2, d.center.z || 0, color, pushSeg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'LWPOLYLINE':
|
case 'LWPOLYLINE':
|
||||||
if (d.points?.length) {
|
if (d.points?.length) {
|
||||||
const hasBulge = d.bulges?.some((b) => Math.abs(b) >= 1e-6);
|
const hasBulge = d.bulges?.some((b) => Math.abs(b) >= 1e-6);
|
||||||
const closed = !!(d.closed || (d.flags & 1));
|
const closed = !!(d.closed || (d.flags & 1));
|
||||||
if (hasBulge) this._bulgePolySegs(d.points, d.bulges, closed, color, pushSeg, expand);
|
if (complexLt) {
|
||||||
|
const pts = this._samplePolylinePoints(d.points, d.bulges, closed, d.elevation || 0);
|
||||||
|
this._drawComplexPath(pts, closed, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
pts.forEach((p) => expand(p.x, p.y, p.z));
|
||||||
|
} else if (hasBulge) this._bulgePolySegs(d.points, d.bulges, closed, color, pushSeg, expand);
|
||||||
else {
|
else {
|
||||||
this._polylineSegs(d.points, closed, d.elevation || 0, color, pushSeg);
|
this._polylineSegs(d.points, closed, d.elevation || 0, color, pushSeg);
|
||||||
d.points.forEach((p) => expand(p.x, p.y, d.elevation || 0));
|
d.points.forEach((p) => expand(p.x, p.y, d.elevation || 0));
|
||||||
@@ -1277,7 +1341,14 @@ export class Viewer2D {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'POLYLINE':
|
case 'POLYLINE':
|
||||||
|
if (complexLt && (d.vertices || d.points)) {
|
||||||
|
const closed = !!(d.closed || (d.flags & 1));
|
||||||
|
const pts = d.vertices || d.points;
|
||||||
|
this._drawComplexPath(pts, closed, complexLt.pattern, complexLt.ltscale, color, pushSeg, pendingTexts);
|
||||||
|
pts.forEach((p) => expand(p.x, p.y, p.z || 0));
|
||||||
|
} else {
|
||||||
this._polylineSegs(d.vertices || d.points, d.closed || (d.flags & 1), 0, color, pushSeg);
|
this._polylineSegs(d.vertices || d.points, d.closed || (d.flags & 1), 0, color, pushSeg);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'ELLIPSE':
|
case 'ELLIPSE':
|
||||||
if (d.center) {
|
if (d.center) {
|
||||||
@@ -1410,6 +1481,17 @@ export class Viewer2D {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (complexLt && pendingTexts.length > vpTextStart) {
|
||||||
|
for (let ti = vpTextStart; ti < pendingTexts.length; ti++) {
|
||||||
|
const t = pendingTexts[ti];
|
||||||
|
if (!t?.pos) continue;
|
||||||
|
const q = xf(t.pos.x, t.pos.y);
|
||||||
|
t.pos = q;
|
||||||
|
t.height = (t.height || 2.5) * scale;
|
||||||
|
t.rotation = (t.rotation || 0) + (xf.twist || 0);
|
||||||
|
t.underlay = !!underlay;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch { /* skip malformed */ }
|
} catch { /* skip malformed */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1466,6 +1548,304 @@ export class Viewer2D {
|
|||||||
|
|
||||||
// ── Private helpers ────────────────────────────────────────────────────────
|
// ── Private helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_resolveComplexLinetype(e) {
|
||||||
|
let ltName = e.lineType ?? e.linetype;
|
||||||
|
if (!ltName || ltName === 'ByLayer' || ltName === 'BYLAYER' ||
|
||||||
|
ltName === 'ByBlock' || ltName === 'BYBLOCK') {
|
||||||
|
const lh = e.layerHandle?.value ?? e.layerHandle;
|
||||||
|
ltName = (lh != null && this._layerLtByHandle?.get(String(lh)))
|
||||||
|
|| this._layerLtByName?.get(e.layer ?? e.layerName) || null;
|
||||||
|
}
|
||||||
|
if (!ltName || ltName === 'Continuous' || ltName === 'ByLayer' || ltName === 'ByBlock') return null;
|
||||||
|
|
||||||
|
const pattern = getLinPattern(ltName);
|
||||||
|
if (!pattern) return null;
|
||||||
|
|
||||||
|
const entScale = (e.entityHeader?.linetypeScale > 0 ? e.entityHeader.linetypeScale : 1);
|
||||||
|
const ltscale = (this._globalLtscale || 1) * entScale;
|
||||||
|
|
||||||
|
return { pattern, ltscale };
|
||||||
|
}
|
||||||
|
|
||||||
|
_sampleArcPoints(center, radius, startAngle, endAngle, elevation = 0) {
|
||||||
|
let sweep = endAngle - startAngle;
|
||||||
|
if (sweep <= 0) sweep += Math.PI * 2;
|
||||||
|
const steps = Math.max(16, Math.ceil((sweep / (Math.PI * 2)) * ARC_SEGS));
|
||||||
|
const pts = [];
|
||||||
|
const z = center.z || elevation || 0;
|
||||||
|
for (let i = 0; i <= steps; i++) {
|
||||||
|
const a = startAngle + (sweep * i) / steps;
|
||||||
|
pts.push({
|
||||||
|
x: center.x + radius * Math.cos(a),
|
||||||
|
y: center.y + radius * Math.sin(a),
|
||||||
|
z,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
_samplePolylinePoints(points, bulges, closed, elevation = 0) {
|
||||||
|
if (!points || points.length < 2) return points || [];
|
||||||
|
const res = [];
|
||||||
|
const n = closed ? points.length : points.length - 1;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const u = points[i];
|
||||||
|
const v = points[(i + 1) % points.length];
|
||||||
|
const b = bulges?.[i] || 0;
|
||||||
|
const z = u.z || elevation || 0;
|
||||||
|
if (Math.abs(b) < 1e-6) {
|
||||||
|
if (res.length === 0) res.push({ x: u.x, y: u.y, z });
|
||||||
|
res.push({ x: v.x, y: v.y, z });
|
||||||
|
} else {
|
||||||
|
const dx = v.x - u.x;
|
||||||
|
const dy = v.y - u.y;
|
||||||
|
const dist = Math.hypot(dx, dy);
|
||||||
|
if (dist > 1e-9) {
|
||||||
|
const theta = 4 * Math.atan(b);
|
||||||
|
const halfTheta = Math.abs(theta) / 2;
|
||||||
|
const R = dist / 2 / Math.sin(halfTheta);
|
||||||
|
const dCenter = R * Math.cos(halfTheta);
|
||||||
|
const mx = (u.x + v.x) / 2;
|
||||||
|
const my = (u.y + v.y) / 2;
|
||||||
|
const nx = -dy / dist;
|
||||||
|
const ny = dx / dist;
|
||||||
|
const dir = b > 0 ? 1 : -1;
|
||||||
|
const cx = mx + dir * nx * dCenter;
|
||||||
|
const cy = my + dir * ny * dCenter;
|
||||||
|
const a1 = Math.atan2(u.y - cy, u.x - cx);
|
||||||
|
const steps = Math.max(4, Math.ceil(Math.abs(theta) / (Math.PI / 16)));
|
||||||
|
for (let k = (res.length === 0 ? 0 : 1); k <= steps; k++) {
|
||||||
|
const a = a1 + (theta * k) / steps;
|
||||||
|
res.push({
|
||||||
|
x: cx + R * Math.cos(a),
|
||||||
|
y: cy + R * Math.sin(a),
|
||||||
|
z,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sampleEllipsePoints(d) {
|
||||||
|
const major = d.majorAxis ?? d.smAxis ?? { x: d.radius || 1, y: 0 };
|
||||||
|
const center = d.center || { x: 0, y: 0 };
|
||||||
|
const ratio = d.ratio ?? d.axisRatio ?? d.minorAxisRatio ?? 0.5;
|
||||||
|
const startParam = d.startParam ?? d.startAngle ?? 0;
|
||||||
|
const endParam = d.endParam ?? d.endAngle ?? Math.PI * 2;
|
||||||
|
const aLen = Math.hypot(major.x ?? 1, major.y ?? 0);
|
||||||
|
const bLen = aLen * ratio;
|
||||||
|
const rot = Math.atan2(major.y ?? 0, major.x ?? 1);
|
||||||
|
let sweep = endParam - startParam;
|
||||||
|
if (sweep <= 0) sweep += Math.PI * 2;
|
||||||
|
const steps = ELLIPSE_SEGS;
|
||||||
|
const pts = [];
|
||||||
|
const z = center.z || 0;
|
||||||
|
for (let i = 0; i <= steps; i++) {
|
||||||
|
const t = startParam + (sweep * i) / steps;
|
||||||
|
const ex = aLen * Math.cos(t);
|
||||||
|
const ey = bLen * Math.sin(t);
|
||||||
|
const x = center.x + ex * Math.cos(rot) - ey * Math.sin(rot);
|
||||||
|
const y = center.y + ex * Math.sin(rot) + ey * Math.cos(rot);
|
||||||
|
pts.push({ x, y, z });
|
||||||
|
}
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawComplexPath(points, closed, linPattern, ltscale, color, pushSeg, pendingTexts) {
|
||||||
|
if (!points || points.length < 2) return;
|
||||||
|
const pts = points.slice();
|
||||||
|
if (closed && (Math.hypot(pts[pts.length - 1].x - pts[0].x, pts[pts.length - 1].y - pts[0].y) > 1e-6)) {
|
||||||
|
pts.push(pts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const segLens = [];
|
||||||
|
let totalLen = 0;
|
||||||
|
for (let i = 0; i < pts.length - 1; i++) {
|
||||||
|
const dx = pts[i + 1].x - pts[i].x;
|
||||||
|
const dy = pts[i + 1].y - pts[i].y;
|
||||||
|
const len = Math.hypot(dx, dy);
|
||||||
|
segLens.push(len);
|
||||||
|
totalLen += len;
|
||||||
|
}
|
||||||
|
if (totalLen < 1e-6) return;
|
||||||
|
|
||||||
|
const getPathState = (dist) => {
|
||||||
|
let s = Math.max(0, Math.min(dist, totalLen));
|
||||||
|
for (let i = 0; i < segLens.length; i++) {
|
||||||
|
const len = segLens[i];
|
||||||
|
if (s <= len || i === segLens.length - 1) {
|
||||||
|
const t = len > 1e-9 ? s / len : 0;
|
||||||
|
const p0 = pts[i];
|
||||||
|
const p1 = pts[i + 1];
|
||||||
|
const x = p0.x + (p1.x - p0.x) * t;
|
||||||
|
const y = p0.y + (p1.y - p0.y) * t;
|
||||||
|
const z = (p0.z || 0) + ((p1.z || 0) - (p0.z || 0)) * t;
|
||||||
|
const angle = Math.atan2(p1.y - p0.y, p1.x - p0.x);
|
||||||
|
return { x, y, z, angle };
|
||||||
|
}
|
||||||
|
s -= len;
|
||||||
|
}
|
||||||
|
const last = pts[pts.length - 1];
|
||||||
|
const prev = pts[pts.length - 2];
|
||||||
|
return {
|
||||||
|
x: last.x,
|
||||||
|
y: last.y,
|
||||||
|
z: last.z || 0,
|
||||||
|
angle: Math.atan2(last.y - prev.y, last.x - prev.x),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const patternLen = linPattern.patternLength * ltscale;
|
||||||
|
if (patternLen < 1e-6) return;
|
||||||
|
|
||||||
|
let s = 0;
|
||||||
|
let elemIdx = 0;
|
||||||
|
const elements = linPattern.elements;
|
||||||
|
|
||||||
|
while (s < totalLen) {
|
||||||
|
const elem = elements[elemIdx % elements.length];
|
||||||
|
elemIdx++;
|
||||||
|
|
||||||
|
if (elem.type === 'dash') {
|
||||||
|
const dashLen = Math.max((elem.val || 0.05) * ltscale, 0.01 * ltscale);
|
||||||
|
const startDist = s;
|
||||||
|
const endDist = Math.min(s + dashLen, totalLen);
|
||||||
|
|
||||||
|
if (endDist > startDist) {
|
||||||
|
const pStart = getPathState(startDist);
|
||||||
|
const pEnd = getPathState(endDist);
|
||||||
|
pushSeg(pStart.x, pStart.y, pEnd.x, pEnd.y, pStart.z, color);
|
||||||
|
}
|
||||||
|
s += dashLen;
|
||||||
|
} else if (elem.type === 'gap') {
|
||||||
|
const gapLen = Math.abs(elem.val || 0.1) * ltscale;
|
||||||
|
s += gapLen;
|
||||||
|
} else if (elem.type === 'shape') {
|
||||||
|
const pState = getPathState(s);
|
||||||
|
this._renderShapeSymbol(elem, pState, ltscale, color, pushSeg);
|
||||||
|
} else if (elem.type === 'text') {
|
||||||
|
const pState = getPathState(s);
|
||||||
|
if (elem.text && pendingTexts) {
|
||||||
|
const h = (elem.scale || 0.2) * ltscale;
|
||||||
|
let angle = pState.angle + ((elem.rotation || 0) * Math.PI) / 180;
|
||||||
|
if (elem.isAbsoluteAngle) {
|
||||||
|
angle = ((elem.rotation || 0) * Math.PI) / 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAD Standard Upright Rule: text must always read from bottom or right (-90deg to +90deg)
|
||||||
|
let normA = Math.atan2(Math.sin(angle), Math.cos(angle));
|
||||||
|
if (normA > Math.PI / 2) normA -= Math.PI;
|
||||||
|
else if (normA < -Math.PI / 2) normA += Math.PI;
|
||||||
|
angle = normA;
|
||||||
|
|
||||||
|
const cosT = Math.cos(angle);
|
||||||
|
const sinT = Math.sin(angle);
|
||||||
|
|
||||||
|
const xo = (elem.xOffset || 0) * ltscale;
|
||||||
|
const rawYo = (elem.yOffset != null && Math.abs(elem.yOffset) > 1e-4) ? Math.abs(elem.yOffset) : 0.02;
|
||||||
|
const yo = rawYo * ltscale;
|
||||||
|
|
||||||
|
const tx = pState.x + (xo * cosT - yo * sinT);
|
||||||
|
const ty = pState.y + (xo * sinT + yo * cosT);
|
||||||
|
|
||||||
|
pendingTexts.push({
|
||||||
|
text: elem.text,
|
||||||
|
pos: { x: tx, y: ty, z: pState.z },
|
||||||
|
height: Math.max(h, 0.01),
|
||||||
|
rotation: angle,
|
||||||
|
color,
|
||||||
|
alignH: 1,
|
||||||
|
alignV: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderShapeSymbol(elem, pState, ltscale, color, pushSeg) {
|
||||||
|
const name = (elem.shapeName || '').toUpperCase();
|
||||||
|
const sc = (elem.scale || 0.1) * ltscale;
|
||||||
|
const angle = elem.isAbsoluteAngle
|
||||||
|
? ((elem.rotation || 0) * Math.PI) / 180
|
||||||
|
: pState.angle + ((elem.rotation || 0) * Math.PI) / 180;
|
||||||
|
|
||||||
|
const xo = (elem.xOffset || 0) * ltscale;
|
||||||
|
const yo = (elem.yOffset || 0) * ltscale;
|
||||||
|
|
||||||
|
const cosA = Math.cos(pState.angle);
|
||||||
|
const sinA = Math.sin(pState.angle);
|
||||||
|
const cx = pState.x + (xo * cosA - yo * sinA);
|
||||||
|
const cy = pState.y + (xo * sinA + yo * cosA);
|
||||||
|
const cz = pState.z || 0;
|
||||||
|
|
||||||
|
const cosR = Math.cos(angle);
|
||||||
|
const sinR = Math.sin(angle);
|
||||||
|
|
||||||
|
const transform = (lx, ly) => ({
|
||||||
|
x: cx + (lx * cosR - ly * sinR) * sc,
|
||||||
|
y: cy + (lx * sinR + ly * cosR) * sc,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (name.includes('KSC35') || name.includes('KSC36') || name.includes('KSC19') || name.includes('ARROW')) {
|
||||||
|
// Solid hatched arrowhead (matching AutoCAD KSC35 standard symbol)
|
||||||
|
const steps = 12;
|
||||||
|
for (let i = 0; i <= steps; i++) {
|
||||||
|
const t = i / steps;
|
||||||
|
const lx = -0.5 + t;
|
||||||
|
const hy = 0.25 * (1 - t);
|
||||||
|
const top = transform(lx, hy);
|
||||||
|
const bot = transform(lx, -hy);
|
||||||
|
pushSeg(top.x, top.y, bot.x, bot.y, cz, color);
|
||||||
|
}
|
||||||
|
const p1 = transform(0.5, 0);
|
||||||
|
const p2 = transform(-0.5, 0.25);
|
||||||
|
const p3 = transform(-0.5, -0.25);
|
||||||
|
pushSeg(p1.x, p1.y, p2.x, p2.y, cz, color);
|
||||||
|
pushSeg(p2.x, p2.y, p3.x, p3.y, cz, color);
|
||||||
|
pushSeg(p3.x, p3.y, p1.x, p1.y, cz, color);
|
||||||
|
} else if (name.includes('CIRC') || name.includes('KSC11') || name.includes('KSC01')) {
|
||||||
|
const segs = 12;
|
||||||
|
let prev = transform(0.3, 0);
|
||||||
|
for (let i = 1; i <= segs; i++) {
|
||||||
|
const a = (i / segs) * Math.PI * 2;
|
||||||
|
const curr = transform(0.3 * Math.cos(a), 0.3 * Math.sin(a));
|
||||||
|
pushSeg(prev.x, prev.y, curr.x, curr.y, cz, color);
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
} else if (name.includes('BOX') || name.includes('KSC02') || name.includes('KSC05')) {
|
||||||
|
const p1 = transform(-0.3, -0.3);
|
||||||
|
const p2 = transform(0.3, -0.3);
|
||||||
|
const p3 = transform(0.3, 0.3);
|
||||||
|
const p4 = transform(-0.3, 0.3);
|
||||||
|
pushSeg(p1.x, p1.y, p2.x, p2.y, cz, color);
|
||||||
|
pushSeg(p2.x, p2.y, p3.x, p3.y, cz, color);
|
||||||
|
pushSeg(p3.x, p3.y, p4.x, p4.y, cz, color);
|
||||||
|
pushSeg(p4.x, p4.y, p1.x, p1.y, cz, color);
|
||||||
|
} else if (name.includes('TRACK1')) {
|
||||||
|
const p1 = transform(0, -0.4);
|
||||||
|
const p2 = transform(0, 0.4);
|
||||||
|
pushSeg(p1.x, p1.y, p2.x, p2.y, cz, color);
|
||||||
|
} else if (name.includes('BAT') || name.includes('DIAMOND')) {
|
||||||
|
const p1 = transform(0, 0.4);
|
||||||
|
const p2 = transform(0.4, 0);
|
||||||
|
const p3 = transform(0, -0.4);
|
||||||
|
const p4 = transform(-0.4, 0);
|
||||||
|
pushSeg(p1.x, p1.y, p2.x, p2.y, cz, color);
|
||||||
|
pushSeg(p2.x, p2.y, p3.x, p3.y, cz, color);
|
||||||
|
pushSeg(p3.x, p3.y, p4.x, p4.y, cz, color);
|
||||||
|
pushSeg(p4.x, p4.y, p1.x, p1.y, cz, color);
|
||||||
|
} else {
|
||||||
|
const p1 = transform(0.3, 0);
|
||||||
|
const p2 = transform(-0.3, 0.2);
|
||||||
|
const p3 = transform(-0.3, -0.2);
|
||||||
|
pushSeg(p1.x, p1.y, p2.x, p2.y, cz, color);
|
||||||
|
pushSeg(p2.x, p2.y, p3.x, p3.y, cz, color);
|
||||||
|
pushSeg(p3.x, p3.y, p1.x, p1.y, cz, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_buildLayerMap(layers) {
|
_buildLayerMap(layers) {
|
||||||
this._layerByHandle.clear();
|
this._layerByHandle.clear();
|
||||||
this._layerByName.clear();
|
this._layerByName.clear();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,301 @@
|
|||||||
|
/**
|
||||||
|
* LIN File Parser & Complex Linetype Registry
|
||||||
|
* Parses AutoCAD LIN file definitions (including text and shape descriptors).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ICAD_LIN, KOSDIC_LIN } from './linData';
|
||||||
|
|
||||||
|
export interface LinElement {
|
||||||
|
type: 'dash' | 'gap' | 'shape' | 'text';
|
||||||
|
val?: number; // dash length (+) or gap length (-)
|
||||||
|
shapeName?: string; // e.g. KSC35, CIRC1, BOX, TRACK1, BAT, ZIG
|
||||||
|
shxFile?: string; // e.g. kosdic.shx, ltypeshp.shx
|
||||||
|
text?: string; // e.g. "L2", "GAS", "HW"
|
||||||
|
style?: string; // e.g. Standard
|
||||||
|
scale: number; // s=... (default 1.0)
|
||||||
|
rotation: number; // r=... (angle in degrees)
|
||||||
|
isAbsoluteAngle: boolean;// a=...
|
||||||
|
isUprightAngle: boolean; // u=...
|
||||||
|
xOffset: number; // x=...
|
||||||
|
yOffset: number; // y=...
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LinPattern {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
elements: LinElement[];
|
||||||
|
patternLength: number; // total length sum of dashes + abs(gaps)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBracketElement(inner: string): LinElement {
|
||||||
|
const parts: string[] = [];
|
||||||
|
let cur = '';
|
||||||
|
let inQuote = false;
|
||||||
|
for (let i = 0; i < inner.length; i++) {
|
||||||
|
const c = inner[i];
|
||||||
|
if (c === '"') inQuote = !inQuote;
|
||||||
|
if (c === ',' && !inQuote) {
|
||||||
|
parts.push(cur.trim());
|
||||||
|
cur = '';
|
||||||
|
} else {
|
||||||
|
cur += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cur.trim()) parts.push(cur.trim());
|
||||||
|
|
||||||
|
let type: 'shape' | 'text' = 'shape';
|
||||||
|
let shapeName: string | undefined;
|
||||||
|
let shxFile: string | undefined;
|
||||||
|
let text: string | undefined;
|
||||||
|
let style: string | undefined;
|
||||||
|
|
||||||
|
let scale = 1.0;
|
||||||
|
let rotation = 0;
|
||||||
|
let isAbsoluteAngle = false;
|
||||||
|
let isUprightAngle = false;
|
||||||
|
let xOffset = 0;
|
||||||
|
let yOffset = 0;
|
||||||
|
|
||||||
|
if (parts.length > 0) {
|
||||||
|
const first = parts[0];
|
||||||
|
if (first.startsWith('"') && first.endsWith('"')) {
|
||||||
|
type = 'text';
|
||||||
|
text = first.slice(1, -1);
|
||||||
|
if (parts.length > 1 && !parts[1].includes('=')) style = parts[1];
|
||||||
|
} else {
|
||||||
|
type = 'shape';
|
||||||
|
shapeName = first;
|
||||||
|
if (parts.length > 1 && !parts[1].includes('=')) shxFile = parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < parts.length; i++) {
|
||||||
|
const part = parts[i];
|
||||||
|
const eqIdx = part.indexOf('=');
|
||||||
|
if (eqIdx > 0) {
|
||||||
|
const key = part.substring(0, eqIdx).trim().toLowerCase();
|
||||||
|
const valStr = part.substring(eqIdx + 1).trim();
|
||||||
|
const val = parseFloat(valStr);
|
||||||
|
if (!isNaN(val)) {
|
||||||
|
if (key === 's') scale = val;
|
||||||
|
else if (key === 'r') { rotation = val; }
|
||||||
|
else if (key === 'a') { rotation = val; isAbsoluteAngle = true; }
|
||||||
|
else if (key === 'u') { rotation = val; isUprightAngle = true; }
|
||||||
|
else if (key === 'x') xOffset = val;
|
||||||
|
else if (key === 'y') yOffset = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
shapeName,
|
||||||
|
shxFile,
|
||||||
|
text,
|
||||||
|
style,
|
||||||
|
scale,
|
||||||
|
rotation,
|
||||||
|
isAbsoluteAngle,
|
||||||
|
isUprightAngle,
|
||||||
|
xOffset,
|
||||||
|
yOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenizeLinPattern(patternStr: string): string[] {
|
||||||
|
const s = patternStr.replace(/^A\s*,\s*/i, '').trim();
|
||||||
|
const tokens: string[] = [];
|
||||||
|
let current = '';
|
||||||
|
let inBracket = false;
|
||||||
|
let inQuote = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
const ch = s[i];
|
||||||
|
if (ch === '"') inQuote = !inQuote;
|
||||||
|
if (ch === '[' && !inQuote) inBracket = true;
|
||||||
|
if (ch === ']' && !inQuote) inBracket = false;
|
||||||
|
|
||||||
|
if (ch === ',' && !inBracket && !inQuote) {
|
||||||
|
if (current.trim()) tokens.push(current.trim());
|
||||||
|
current = '';
|
||||||
|
} else {
|
||||||
|
current += ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current.trim()) tokens.push(current.trim());
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseLinContent(content: string): Map<string, LinPattern> {
|
||||||
|
const result = new Map<string, LinPattern>();
|
||||||
|
const lines = content.split(/\r?\n/);
|
||||||
|
let currentName: string | null = null;
|
||||||
|
let currentDesc = '';
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
line = line.trim();
|
||||||
|
if (!line || line.startsWith(';')) continue;
|
||||||
|
if (line.startsWith('*')) {
|
||||||
|
const commaIdx = line.indexOf(',');
|
||||||
|
if (commaIdx > 0) {
|
||||||
|
currentName = line.substring(1, commaIdx).trim();
|
||||||
|
currentDesc = line.substring(commaIdx + 1).trim();
|
||||||
|
} else {
|
||||||
|
currentName = line.substring(1).trim();
|
||||||
|
currentDesc = '';
|
||||||
|
}
|
||||||
|
} else if (currentName && line.toUpperCase().startsWith('A,')) {
|
||||||
|
const tokens = tokenizeLinPattern(line);
|
||||||
|
const elements: LinElement[] = [];
|
||||||
|
let totalLength = 0;
|
||||||
|
|
||||||
|
for (const tok of tokens) {
|
||||||
|
if (tok.startsWith('[') && tok.endsWith(']')) {
|
||||||
|
const elem = parseBracketElement(tok.slice(1, -1));
|
||||||
|
elements.push(elem);
|
||||||
|
} else {
|
||||||
|
const val = parseFloat(tok);
|
||||||
|
if (!isNaN(val)) {
|
||||||
|
if (val > 1e-6) {
|
||||||
|
elements.push({
|
||||||
|
type: 'dash',
|
||||||
|
val,
|
||||||
|
scale: 1,
|
||||||
|
rotation: 0,
|
||||||
|
isAbsoluteAngle: false,
|
||||||
|
isUprightAngle: false,
|
||||||
|
xOffset: 0,
|
||||||
|
yOffset: 0,
|
||||||
|
});
|
||||||
|
totalLength += val;
|
||||||
|
} else if (val < -1e-6) {
|
||||||
|
const absVal = Math.abs(val);
|
||||||
|
elements.push({
|
||||||
|
type: 'gap',
|
||||||
|
val: -absVal,
|
||||||
|
scale: 1,
|
||||||
|
rotation: 0,
|
||||||
|
isAbsoluteAngle: false,
|
||||||
|
isUprightAngle: false,
|
||||||
|
xOffset: 0,
|
||||||
|
yOffset: 0,
|
||||||
|
});
|
||||||
|
totalLength += absVal;
|
||||||
|
} else {
|
||||||
|
// Dot (val === 0)
|
||||||
|
elements.push({
|
||||||
|
type: 'dash',
|
||||||
|
val: 0.05,
|
||||||
|
scale: 1,
|
||||||
|
rotation: 0,
|
||||||
|
isAbsoluteAngle: false,
|
||||||
|
isUprightAngle: false,
|
||||||
|
xOffset: 0,
|
||||||
|
yOffset: 0,
|
||||||
|
});
|
||||||
|
totalLength += 0.05;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.length > 0) {
|
||||||
|
result.set(currentName.toUpperCase(), {
|
||||||
|
name: currentName,
|
||||||
|
description: currentDesc,
|
||||||
|
elements,
|
||||||
|
patternLength: Math.max(totalLength, 0.1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global Linetype Registry initialized with Icad.lin and KOSDIC.lin
|
||||||
|
const globalRegistry = new Map<string, LinPattern>();
|
||||||
|
|
||||||
|
export function registerLinContent(content: string) {
|
||||||
|
const parsed = parseLinContent(content);
|
||||||
|
for (const [key, pattern] of parsed.entries()) {
|
||||||
|
globalRegistry.set(key, pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warm up registry with bundled LIN files
|
||||||
|
registerLinContent(ICAD_LIN);
|
||||||
|
registerLinContent(KOSDIC_LIN);
|
||||||
|
|
||||||
|
const CIVIL_LT_TEXT_MAP: Record<string, string> = {
|
||||||
|
'H-DICHL02': 'L2-2/0',
|
||||||
|
'H-DICHL02R': 'L2-2',
|
||||||
|
'H-DICHL01': 'L2-3/0',
|
||||||
|
'H-DICHL01R': 'L2-3',
|
||||||
|
'H-DICHL06': 'L5-1',
|
||||||
|
'H-DICHL06R': 'L5-1',
|
||||||
|
'H-DICHL05': 'L5',
|
||||||
|
'H-DICHL05R': 'L5',
|
||||||
|
'H-DICHU05': 'U5',
|
||||||
|
'H-DICHU05R': 'U5',
|
||||||
|
'H-DICHB01': 'B1',
|
||||||
|
'H-DICHB01R': 'B1',
|
||||||
|
'H-DICHM03': 'M3',
|
||||||
|
'H-DICHM03R': 'M3',
|
||||||
|
'H-DICHV01': 'V1',
|
||||||
|
'H-DICHV01R': 'V1',
|
||||||
|
'H-DICHO01': 'O1',
|
||||||
|
'H-DICHO01R': 'O1',
|
||||||
|
'H-DICHL03': 'L3',
|
||||||
|
'H-DICHL03R': 'L3',
|
||||||
|
'H-DICHL04': 'L4',
|
||||||
|
'H-DICHL04R': 'L4',
|
||||||
|
'H-DICHL08': 'L8',
|
||||||
|
'H-DICHL08R': 'L8',
|
||||||
|
};
|
||||||
|
|
||||||
|
const NO_SHAPE_LINETYPES = new Set([
|
||||||
|
'H-DICHL05',
|
||||||
|
'H-DICHL05R',
|
||||||
|
'H-DICHU05',
|
||||||
|
'H-DICHU05R',
|
||||||
|
'H-DICHB01',
|
||||||
|
'H-DICHB01R',
|
||||||
|
'H-DICHM03',
|
||||||
|
'H-DICHM03R',
|
||||||
|
'H-DICHV01',
|
||||||
|
'H-DICHV01R',
|
||||||
|
'H-DICHO01',
|
||||||
|
'H-DICHO01R',
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function getLinPattern(name: string): LinPattern | undefined {
|
||||||
|
if (!name) return undefined;
|
||||||
|
const key = name.toUpperCase().trim();
|
||||||
|
const pattern = globalRegistry.get(key);
|
||||||
|
if (!pattern) return undefined;
|
||||||
|
|
||||||
|
let elements = pattern.elements;
|
||||||
|
|
||||||
|
// Filter out shape/arrowhead elements for L5, U5, B1, etc. as specified in official CAD spec
|
||||||
|
if (NO_SHAPE_LINETYPES.has(key)) {
|
||||||
|
elements = elements.filter((elem) => elem.type !== 'shape');
|
||||||
|
}
|
||||||
|
|
||||||
|
const overrideText = CIVIL_LT_TEXT_MAP[key];
|
||||||
|
if (overrideText) {
|
||||||
|
elements = elements.map((elem) =>
|
||||||
|
elem.type === 'text' ? { ...elem, text: overrideText } : elem
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...pattern,
|
||||||
|
elements,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllRegisteredPatterns(): Map<string, LinPattern> {
|
||||||
|
return globalRegistry;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user