grid-apps-cmms/src.old/load/gbr.js

241 lines
7.3 KiB
JavaScript

/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
// use: geo.bounds
// use: geo.polygon
// use: geo.polygons
// use: ext.gerber
// use: geo.csg
gapp.register("load.gbr", (root, exports) => {
const { load } = root;
const POLYS = base.polygons;
const CSG = base.CSG;
load.GBR = {
parse,
toMesh
};
function toMesh(text, opt = {}) {
const progress = opt.progress || function(){};
const debug = opt.debug ? console.log : function(){};
// for now ignore open trace lines
// future exports should allow a way to represent them for cnc
const { open, closed, circs, rects } = parse(text);
progress(5);
// for the copper layer:
// should produce one top level poly which is the board outline
// next level down should be the remaining copper trace which we
// subtract from the board to produce paths that can be milled
const nest = POLYS.nest([...closed, ...circs, ...rects], true);
debug({ nest });
if (nest.length === 0) return [].toFloat32();
const flat = [];
for (let n of nest) n.flattenTo(flat, [], true);
progress(8);
debug({ flat });
const z0 = opt.z || opt.z0 || 1;
const z1 = opt.z1 || 0.25;
// encapsulate all polys in one enclosing rect with an offset
if (nest.length > 1) {
const offset = (opt.offset || 2) * 2;
const bounds = nest[0].bounds;
for (let p of nest) {
bounds.merge(p.bounds);
}
const rect = base.newPolygon().centerRectangle(bounds.center(0), bounds.width() + offset, bounds.height() + offset);
for (let n of nest) {
n.depth++;
}
flat.push(rect);
debug({ bounds, flat });
opt.raise = z1;
}
// board outline
const top = [];
for (let poly of flat.filter(p => p.depth === 0)) {
top.push(poly.extrude(z0));
}
progress(15);
debug({ top });
// nothing to render
if (top.length === 0) {
return [].toFloat32();
}
// copper pads
const mid = [];
for (let poly of flat.filter(p => p.depth === 1)) {
mid.push(poly.extrude(z1, { zadd: z0 - z1 }));
}
progress(20);
debug({ mid });
// if no traces, return board
if (mid.length === 0) {
return top[0];
}
// copper traces (expand first since open)
const open_exp = open.map(poly => {
const diam = poly.tool?.shape?.diameter;
return diam ? poly.offset_open(diam / 2, 'round') : null;
}).filter(p => p).flat();
// union open trace expansions
const opn = [];
for (let poly of POLYS.union(open_exp, 0, true)) {
const tool = poly.tool;
opn.push(poly.extrude(z0 + (opt.raise || 0)));
}
progress(25);
debug({ opn });
// inner polys for traces which we add back to the top
// and (optionally) remove from the bottom for making vias
const low = [];
for (let poly of flat.filter(p => p.depth === 2)) {
low.push(poly.extrude(z1, { zadd: z0 - z1 }));
}
progress(30);
// convert vertex arrays to csg mesh definitions
const topMesh = CSG.fromPositionArray(top[0]);
const midMesh = mid.map(v => CSG.fromPositionArray(v));
progress(35);
// subtract mid from top
let meshOut = CSG.subtract(topMesh, ...midMesh);
progress(50);
if (opn.length) {
// add back raised traces (before removing vias)
const opnMesh = opn.map(v => CSG.fromPositionArray(v))
meshOut = CSG.union(meshOut, ...opnMesh);
}
progress(70);
if (low.length) {
// subtract low (vias) from remaining
const lowMesh = low.map(v => CSG.fromPositionArray(v))
meshOut = CSG.union(meshOut, ...lowMesh);
}
progress(90);
return CSG.toPositionArray(meshOut);
}
function parse(text) {
let fact = 1000000;
let tools = {};
let tool;
let poly;
let polys = [];
let circs = [];
let rects = [];
let last = {};
let bounds = base.newBounds();
let p = TracespaceParser.createParser();
p.feed(text);
let r = p.results();
let c = r.children
.filter(c => {
switch (c.type) {
case 'toolDefinition':
tools[c.code] = c.shape;
break;
case 'toolChange':
tool = { code: c.code, shape: tools[c.code] };
break;
case 'graphic':
c.tool = tool;
return true;
}
return false;
})
.map(r => {
return {
type: r.graphic,
tool: r.tool,
x: parseInt(r.coordinates.x),
y: parseInt(r.coordinates.y)
};
})
.map(r => {
const { x, y, tool, type } = r;
const pos = { x:x/fact, y:y/fact, z: 0};
bounds.update(pos);
switch (type) {
case 'move':
if (true && poly && last.x === x && last.y === y && last.tool === tool) {
// console.log('continuation');
} else {
poly = base.newPolygon().setOpen().add(pos.x, pos.y, 0);
poly.tool = tool;
polys.push(poly);
}
break;
case 'segment':
poly.add(pos.x, pos.y, 0);
break;
case 'shape':
poly = undefined;
let { shape } = tool;
switch (shape.type) {
case 'obround':
if (shape.xSize === shape.ySize) {
circs.push(base.newPolygon().centerCircle(pos, shape.xSize / 2, 20));
} else {
console.log('TODO', shape);
}
break;
case 'circle':
circs.push(base.newPolygon().centerCircle(pos, shape.diameter / 2, 20));
break;
case 'rectangle':
rects.push(base.newPolygon().centerRectangle(pos, shape.xSize, shape.ySize));
break;
default:
console.log('TODO', shape);
break;
}
break;
}
last = { x, y, tool };
return r;
});
// center board on origin
const { minx, maxx, miny, maxy } = bounds;
for (let poly of [...polys, ...circs, ...rects]) {
poly.move({
x: -((maxx-minx)/2 + minx),
y: -((maxy-miny)/2 + miny),
z: 0
});
}
// separate open and closed polys (traces vs areas)
const open = [];
const closed = [];
for (let poly of polys) {
if (poly.appearsClosed()) {
// clean up gnarly overlapping lines
// make contiguous, simpler enclosed areas
closed.push(...poly.simplify());
} else {
open.push(poly);
}
}
// const nest = base.polygons.nest([...closed, ...circs, ...rects], true);
// console.log({ nest });
return { open, closed, circs, rects };
}
});