feat(viewer2d): add complex linetype rendering & LIN pattern parser support
This commit is contained in:
+395
-15
@@ -11,6 +11,7 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
||||
import { aciToHex } from './aciColors.js';
|
||||
import { buildAllowedOwnerHexes, listCadSpaces } from './cadSpaces';
|
||||
import { SlugTextEngine, SlugTextBatch } from './slugText';
|
||||
import { getLinPattern } from './linParser';
|
||||
|
||||
const DEFAULT_COLOR = 0xc9d1d9;
|
||||
const ARC_SEGS = 64;
|
||||
@@ -424,7 +425,9 @@ export class Viewer2D {
|
||||
const d = e.data || e;
|
||||
const type = (e.type || e.typeName || '').toUpperCase();
|
||||
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 };
|
||||
this._entityMeta.push(meta);
|
||||
this._pickCurIdx = this._entityMeta.length - 1; // owner for pick geometry emitted below
|
||||
@@ -435,21 +438,35 @@ export class Viewer2D {
|
||||
// ── Basic geometry ────────────────────────────────────────────────
|
||||
case 'LINE':
|
||||
if (d.start && d.end) {
|
||||
pushSeg(d.start.x, d.start.y, d.end.x, d.end.y, d.start.z || 0, color);
|
||||
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);
|
||||
}
|
||||
meta.bounds = { type:'line', x1:d.start.x, y1:d.start.y, x2:d.end.x, y2:d.end.y };
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CIRCLE':
|
||||
if (d.center && d.radius != null) {
|
||||
this._arcSegs(d.center, d.radius, 0, Math.PI*2, d.center.z||0, color, pushSeg);
|
||||
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);
|
||||
}
|
||||
meta.bounds = { type:'circle', cx:d.center.x, cy:d.center.y, r:d.radius };
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ARC':
|
||||
if (d.center && d.radius != null) {
|
||||
this._arcSegs(d.center, d.radius, d.startAngle??0, d.endAngle??Math.PI*2, d.center.z||0, color, pushSeg);
|
||||
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);
|
||||
}
|
||||
meta.bounds = { type:'circle', cx:d.center.x, cy:d.center.y, r:d.radius };
|
||||
}
|
||||
break;
|
||||
@@ -461,6 +478,10 @@ export class Viewer2D {
|
||||
const nSeg = closed ? d.points.length : d.points.length - 1;
|
||||
if (this._polyHasWidth(e, nSeg, 1)) {
|
||||
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) {
|
||||
this._bulgePolySegs(d.points, d.bulges, closed, color, pushSeg, expand);
|
||||
} else {
|
||||
@@ -474,7 +495,14 @@ export class Viewer2D {
|
||||
break;
|
||||
|
||||
case 'POLYLINE':
|
||||
this._polylineSegs(d.vertices||d.points, d.closed||(d.flags&1), 0, color, pushSeg);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POLYLINE_2D': {
|
||||
@@ -487,7 +515,11 @@ export class Viewer2D {
|
||||
const bulges = kids.map(v => v.bulge || 0);
|
||||
const closed = (d.flags & 1) === 1;
|
||||
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);
|
||||
} else {
|
||||
this._polylineSegs(pts, closed, elev, color, pushSeg);
|
||||
@@ -511,7 +543,13 @@ export class Viewer2D {
|
||||
|
||||
case 'ELLIPSE':
|
||||
if (d.center) {
|
||||
this._ellipseSegs(d, color, pushSeg);
|
||||
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);
|
||||
}
|
||||
meta.bounds = { type:'point', cx:d.center.x, cy:d.center.y };
|
||||
}
|
||||
break;
|
||||
@@ -530,8 +568,13 @@ export class Viewer2D {
|
||||
return result;
|
||||
})()
|
||||
: cps;
|
||||
this._polylineSegs(pts, false, 0, color, pushSeg);
|
||||
pts.forEach(p => expand(p.x, p.y, p.z || 0));
|
||||
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);
|
||||
pts.forEach(p => expand(p.x, p.y, p.z || 0));
|
||||
}
|
||||
meta.bounds = { type:'point', cx:pts[0].x, cy:pts[0].y };
|
||||
}
|
||||
break;
|
||||
@@ -1247,29 +1290,50 @@ export class Viewer2D {
|
||||
const d = e.data || e;
|
||||
const type = (e.type || e.typeName || '').toUpperCase();
|
||||
const color = this._entityColor(e);
|
||||
const complexLt = this._resolveComplexLinetype(e);
|
||||
const vpTextStart = pendingTexts.length;
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'LINE':
|
||||
if (d.start && d.end) {
|
||||
pushSeg(d.start.x, d.start.y, d.end.x, d.end.y, d.start.z || 0, color);
|
||||
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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CIRCLE':
|
||||
if (d.center && d.radius != null) {
|
||||
// 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);
|
||||
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
|
||||
this._arcSegs(d.center, d.radius, 0, Math.PI * 2, d.center.z || 0, color, pushSeg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ARC':
|
||||
if (d.center && d.radius != null) {
|
||||
this._arcSegs(d.center, d.radius, d.startAngle ?? 0, d.endAngle ?? Math.PI * 2, d.center.z || 0, color, pushSeg);
|
||||
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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'LWPOLYLINE':
|
||||
if (d.points?.length) {
|
||||
const hasBulge = d.bulges?.some((b) => Math.abs(b) >= 1e-6);
|
||||
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 {
|
||||
this._polylineSegs(d.points, closed, d.elevation || 0, color, pushSeg);
|
||||
d.points.forEach((p) => expand(p.x, p.y, d.elevation || 0));
|
||||
@@ -1277,7 +1341,14 @@ export class Viewer2D {
|
||||
}
|
||||
break;
|
||||
case 'POLYLINE':
|
||||
this._polylineSegs(d.vertices || d.points, d.closed || (d.flags & 1), 0, color, pushSeg);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
case 'ELLIPSE':
|
||||
if (d.center) {
|
||||
@@ -1410,6 +1481,17 @@ export class Viewer2D {
|
||||
default:
|
||||
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 */ }
|
||||
}
|
||||
|
||||
@@ -1466,6 +1548,304 @@ export class Viewer2D {
|
||||
|
||||
// ── 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) {
|
||||
this._layerByHandle.clear();
|
||||
this._layerByName.clear();
|
||||
|
||||
Reference in New Issue
Block a user