One CAD object as points and bulges
64-bitThe run or outline that stands for a line, arc, circle, polyline, ellipse, spline or 2D solid.
arc: p = c + r·(cos a, sin a), b = tan(sweep ÷ 4); ellipse: n = max(24, ⌈1440·sweep ÷ 2π⌉) chords
An arc is stored as center + r·(cos, sin) at its two angles with bulge tan(sweep ÷ 4); the radius is not carried on, so the length is later rebuilt from the chord. A circle is two points at x ± r with bulge 1. An ellipse is sampled at n = max(24, ceil(1440·sweep ÷ 2π)) parameter steps and flagged approximate; a spline's points come from the drawing compiler and are flagged approximate.
/** One entity → { pts, closed, holes, approx } in the entity's own coordinates (before the sheet transform), or null / a reason. */
function shapeOf(e) {
const bulgeVs = (vs, closed) => ({ pts: vs.filter(isPt).map((v) => ({ x: v.x, y: v.y, ...(fin(v.bulge) && v.bulge ? { b: v.bulge } : {}) })), closed: !!closed });
switch (e.type) {
case 'LINE': return isPt(e.start) && isPt(e.end) ? { pts: [{ x: e.start.x, y: e.start.y }, { x: e.end.x, y: e.end.y }], closed: false } : null;
case 'ARC': {
if (!isPt(e.center) || !(e.radius > 0) || !fin(e.startAngle) || !fin(e.endAngle)) return null;
let sweep = e.endAngle - e.startAngle; while (sweep <= 0) sweep += TAU;
const a = e.startAngle, b = e.endAngle, r = e.radius;
return { pts: [{ x: e.center.x + r * Math.cos(a), y: e.center.y + r * Math.sin(a), b: Math.tan(sweep / 4) }, { x: e.center.x + r * Math.cos(b), y: e.center.y + r * Math.sin(b) }], closed: false };
}
case 'CIRCLE': {
if (!isPt(e.center) || !(e.radius > 0)) return null;
const { x, y } = e.center, r = e.radius;
return { pts: [{ x: x + r, y, b: 1 }, { x: x - r, y, b: 1 }], closed: true }; // two half circles: exact length and area
}
case 'LWPOLYLINE': { const s = bulgeVs(e.vertices || [], e.closed); return s.pts.length >= 2 ? s : null; }
case 'POLYLINE_2D': {
const s = bulgeVs((e.vertices || []).filter((v) => !(v.flags & 16)), e.closed);
return s.pts.length >= 2 ? { ...s, approx: !!(e.flags & 6) || (e.curveType || 0) > 4 } : null; // a curve- or spline-fit polyline is only its frame
}
case 'ELLIPSE': {
if (!isPt(e.center) || !isPt(e.majorAxis) || !(e.ratio > 0)) return null;
let s0 = fin(e.startParam) ? e.startParam : 0, s1 = fin(e.endParam) ? e.endParam : TAU;
let sweep = s1 - s0; while (sweep <= 0) sweep += TAU;
const full = Math.abs(sweep - TAU) < 1e-9;
const n = Math.max(24, Math.ceil(1440 * sweep / TAU)), mx = e.majorAxis.x, my = e.majorAxis.y, len = Math.hypot(mx, my), rot = Math.atan2(my, mx), cr = Math.cos(rot), sr = Math.sin(rot);
const pts = [];
for (let i = 0; i < (full ? n : n + 1); i++) {
const t = s0 + sweep * i / n, x = len * Math.cos(t), y = len * e.ratio * Math.sin(t);
pts.push({ x: e.center.x + x * cr - y * sr, y: e.center.y + x * sr + y * cr });
}
return { pts, closed: full, approx: true };
}
case 'SPLINE': {
const flat = splinePoints(e);
const pts = []; for (let i = 0; i + 1 < flat.length; i += 2) if (fin(flat[i]) && fin(flat[i + 1])) pts.push({ x: flat[i], y: flat[i + 1] });
return pts.length >= 2 ? { pts, closed: !!(e.flags & 1) || !!e.closed, approx: true } : null;
}
case 'SOLID': case 'TRACE': {
const p = (e.points || []).filter(isPt);
if (p.length < 3) return null;
const q = p.length > 3 ? [p[0], p[1], p[3], p[2]] : [p[0], p[1], p[2]]; // DXF order 1, 2, 4, 3 is the outline
return { pts: q.map((v) => ({ x: v.x, y: v.y })), closed: true };
}
default: return null;
}
}