mesh v2 at /v2/mesh
This commit is contained in:
parent
aee9fd6ed7
commit
6566c45f9a
85 changed files with 138198 additions and 1 deletions
227
src2/load/gbr.js
Normal file
227
src2/load/gbr.js
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import { polygons } from '../geo/polygons.js';
|
||||
import { CSG } from '../geo/csg.js';
|
||||
import * as TracespaceParser from '../ext/gerber.js';
|
||||
import { newPolygon } from '../geo/polygon.js';
|
||||
import { newBounds } from '../geo/bounds.js';
|
||||
|
||||
export 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 = polygons.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 = 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 polygons.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);
|
||||
}
|
||||
|
||||
export function parse(text) {
|
||||
let fact = 1000000;
|
||||
let tools = {};
|
||||
let tool;
|
||||
let poly;
|
||||
let polys = [];
|
||||
let circs = [];
|
||||
let rects = [];
|
||||
let last = {};
|
||||
let bounds = 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 = 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(newPolygon().centerCircle(pos, shape.xSize / 2, 20));
|
||||
} else {
|
||||
console.log('TODO', shape);
|
||||
}
|
||||
break;
|
||||
case 'circle':
|
||||
circs.push(newPolygon().centerCircle(pos, shape.diameter / 2, 20));
|
||||
break;
|
||||
case 'rectangle':
|
||||
rects.push(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);
|
||||
}
|
||||
}
|
||||
return { open, closed, circs, rects };
|
||||
}
|
||||
Loading…
Reference in a new issue