@thi.ng/iges
Version:
IGES 5.3 serializer for (currently only) polygonal geometry, both open & closed
323 lines (322 loc) • 8.02 kB
JavaScript
import { isArray } from "@thi.ng/checks/is-array";
import { defmulti } from "@thi.ng/defmulti/defmulti";
import { float } from "@thi.ng/strings/float";
import { hstr } from "@thi.ng/strings/hollerith";
import { padLeft } from "@thi.ng/strings/pad-left";
import { padRight } from "@thi.ng/strings/pad-right";
import { comp } from "@thi.ng/transducers/comp";
import { map } from "@thi.ng/transducers/map";
import { mapIndexed } from "@thi.ng/transducers/map-indexed";
import { mapcat } from "@thi.ng/transducers/mapcat";
import { partition } from "@thi.ng/transducers/partition";
import { push } from "@thi.ng/transducers/push";
import { transduce } from "@thi.ng/transducers/transduce";
import { wordWrap } from "@thi.ng/transducers/word-wrap";
import { wrapSides } from "@thi.ng/transducers/wrap-sides";
import {
DEFAULT_GLOBALS,
EntityType,
PolylineMode,
Type,
Unit
} from "./api.js";
const LINEWIDTH_GLOBALS = 72;
const LINEWIDTH_PARAMS = 64;
const $Z2 = padLeft(2, "0");
const $Z7 = padLeft(7, "0");
const $F8 = padLeft(8, " ");
const $SEQ = padLeft(7, " ");
const $BODY = padRight(72, " ");
const $PBODY = padRight(64, " ");
const $DATE = (d) => hstr(
[
d.getUTCFullYear(),
$Z2(d.getUTCMonth() + 1),
$Z2(d.getUTCDate()),
".",
$Z2(d.getUTCHours()),
$Z2(d.getUTCMinutes()),
$Z2(d.getUTCSeconds())
].join("")
);
const newDocument = (g, start) => {
const globals = { ...DEFAULT_GLOBALS, ...g };
const $FF = float(globals.precision);
const $PARAM = defmulti(
(x) => x[1],
{},
{
[Type.INT]: (x) => x[0].toString(),
[Type.POINTER]: (x) => (-x[0]).toString(),
[Type.FLOAT]: (x) => $FF(x[0]),
[Type.STR]: (x) => x[0],
[Type.HSTR]: (x) => hstr(x[0]),
[Type.DATE]: (x) => $DATE(x[0])
}
);
return {
globals,
start: start || [],
dict: [],
param: [],
offsets: {
S: 0,
G: 0,
P: 1,
D: 0
},
$FF,
$PARAM
};
};
const serialize = (doc) => [
...__formatStart(doc),
...__formatGlobals(doc),
...doc.dict,
...doc.param,
__formatTerminate(doc)
].join("\n");
const __formatLine = (body, type, i) => `${$BODY(body)}${type}${$SEQ(i + 1)}`;
const __formatStart = (doc) => {
const res = [
...mapIndexed((i, x) => __formatLine(x, "S", i), doc.start)
];
doc.offsets.S += res.length;
return res;
};
const __formatGlobals = (doc) => {
const g = doc.globals;
const res = __formatParams(
doc,
[
[g.delimParam, Type.HSTR],
[g.delimRecord, Type.HSTR],
[g.senderProductID, Type.HSTR],
[g.fileName, Type.HSTR],
[g.generator, Type.HSTR],
[g.generatorVersion, Type.HSTR],
[g.intBits, Type.INT],
[g.singleMaxPow, Type.INT],
[g.singleDigits, Type.INT],
[g.doubleMaxPow, Type.INT],
[g.doubleDigits, Type.INT],
[g.receiverProductID, Type.HSTR],
[g.modelScale, Type.FLOAT],
[g.units, Type.INT],
[Unit[g.units], Type.HSTR],
[g.numLineWeights, Type.INT],
[g.maxLineWeight, Type.FLOAT],
[g.created || /* @__PURE__ */ new Date(), Type.DATE],
[1 / Math.pow(10, g.precision), Type.FLOAT],
[g.maxCoord || 1e3, Type.FLOAT],
[g.author, Type.HSTR],
[g.authorOrg, Type.HSTR],
[g.specVersion, Type.INT],
[g.draftVersion, Type.INT],
[g.modified || /* @__PURE__ */ new Date(), Type.DATE]
],
(body, i) => `${$BODY(body)}G${$SEQ(i + 1)}`,
LINEWIDTH_GLOBALS
);
doc.offsets.G += res.length;
return res;
};
const __formatTerminate = (doc) => __formatLine(
`S${$Z7(doc.offsets.S)}G${$Z7(doc.offsets.G)}D${$Z7(
doc.offsets.D
)}P${$Z7(doc.offsets.P - 1)}`,
"T",
0
);
const __formatStatus = (s) => [s.blank || 0, s.subord || 0, s.usage || 0, s.hierarchy || 0].map((x) => $Z2(x)).join("");
const __formatDictEntry = (e) => transduce(
comp(
map((x) => $F8(x)),
partition(9),
mapIndexed(
(i, x) => __formatLine(x.join(""), "D", i),
e.index
)
),
push(),
[
e.type,
e.param || 0,
e.struct || 0,
e.pattern || 0,
e.level || 0,
e.view || 0,
e.matrix || 0,
e.labelAssoc || 0,
__formatStatus(e.status || {}),
//
e.type,
e.lineWeight || 0,
e.color || 0,
e.lineCount || 1,
e.form || 0,
0,
0,
e.label || "",
e.subscript || 0
]
);
const __formatParam = (did, pid) => (body, i) => `${$PBODY(body)} ${$Z7(did + 1)}P${$SEQ(i + pid)}`;
const __formatParams = (doc, params, fmtBody, bodyWidth = LINEWIDTH_PARAMS) => {
const lines = transduce(
comp(
map(doc.$PARAM),
wordWrap(bodyWidth, { delim: 1, always: true }),
map((p) => p.join(doc.globals.delimParam))
),
push(),
params
);
const n = lines.length - 1;
return lines.map((line, i) => {
const d = i < n ? doc.globals.delimParam : doc.globals.delimRecord;
return fmtBody(line + d, i);
});
};
const __addEntity = (doc, type, entry, params, opts = {}) => {
const did = doc.offsets.D;
const pid = doc.offsets.P;
const fparams = __formatParams(
doc,
[[type, Type.INT]].concat(params),
__formatParam(did, pid)
);
doc.offsets.P += fparams.length;
doc.offsets.D += 2;
doc.dict.push(
...__formatDictEntry({
type,
pattern: opts.pattern || 0,
color: opts.color || 0,
param: pid,
index: did,
lineCount: fparams.length,
...entry
})
);
doc.param.push(...fparams);
return did + 1;
};
const addPolyline = (doc, pts, form = PolylineMode.OPEN, opts) => {
const is2D = pts[0].length == 2;
const params = [
[is2D ? 1 : 2, Type.INT],
[pts.length + (form === PolylineMode.CLOSED ? 1 : 0), Type.INT]
];
is2D && params.push([0, Type.FLOAT]);
return __addEntity(
doc,
EntityType.POLYLINE,
{
form: form === PolylineMode.FILLED ? 63 : 11
},
[
...params,
...mapcat(
(p) => map((x) => [x, Type.FLOAT], p),
form === PolylineMode.CLOSED ? wrapSides(pts, 0, 1) : pts
)
],
opts
);
};
const addPolygon = (doc, pts, opts) => addPolyline(doc, pts, PolylineMode.FILLED, opts);
const addPoint = (doc, p, opts) => __addEntity(
doc,
EntityType.POINT,
null,
[
[p[0], Type.FLOAT],
[p[1], Type.FLOAT],
[p[2] || 0, Type.FLOAT],
[0, Type.POINTER]
],
opts
);
const addLine = (doc, a, b, opts) => __addEntity(
doc,
EntityType.LINE,
null,
[
[a[0], Type.FLOAT],
[a[1], Type.FLOAT],
[a[2] || 0, Type.FLOAT],
[b[0], Type.FLOAT],
[b[1], Type.FLOAT],
[b[2] || 0, Type.FLOAT]
],
opts
);
const addBooleanTree = (doc, tree, opts) => {
const params = __postOrder([], tree);
return __addEntity(
doc,
EntityType.BOOLEAN_TREE,
null,
[[params.length, Type.INT], ...params],
opts
);
};
const __postOrder = (acc, tree) => {
if (isArray(tree)) {
__postOrder(acc, tree[1]);
__postOrder(acc, tree[2]);
acc.push([tree[0], Type.INT]);
} else {
acc.push([tree, Type.POINTER]);
}
return acc;
};
const addCSGBox = (doc, pos, size, xaxis = [1, 0, 0], zaxis = [0, 0, 1], opts) => __addEntity(
doc,
EntityType.CSG_BOX,
null,
[
[size[0], Type.FLOAT],
[size[1], Type.FLOAT],
[size[2], Type.FLOAT],
[pos[0], Type.FLOAT],
[pos[1], Type.FLOAT],
[pos[2], Type.FLOAT],
[xaxis[0], Type.FLOAT],
[xaxis[1], Type.FLOAT],
[xaxis[2], Type.FLOAT],
[zaxis[0], Type.FLOAT],
[zaxis[1], Type.FLOAT],
[zaxis[2], Type.FLOAT]
],
opts
);
const addCSGCylinder = (doc, pos, normal, radius, height, opts) => __addEntity(
doc,
EntityType.CSG_CYLINDER,
null,
[
[pos[0], Type.FLOAT],
[pos[1], Type.FLOAT],
[pos[2], Type.FLOAT],
[normal[0], Type.FLOAT],
[normal[1], Type.FLOAT],
[normal[2], Type.FLOAT],
[radius, Type.FLOAT],
[height, Type.FLOAT]
],
opts
);
export * from "./api.js";
export {
addBooleanTree,
addCSGBox,
addCSGCylinder,
addLine,
addPoint,
addPolygon,
addPolyline,
newDocument,
serialize
};