gerber-plotter
Version:
Streaming Gerber / NC drill layer image plotter
1,509 lines (1,347 loc) • 77.5 kB
JavaScript
/* eslint-env mocha */
// test suite for plotter
'use strict'
var expect = require('chai').expect
var plotter = require('..')
var boundingBox = require('../lib/_box')
var EPSILON = 0.000001
describe('gerber plotter', function() {
var p
beforeEach(function() {
p = plotter()
})
it('should be an object stream', function() {
expect(function() {
p.write({})
}).to.not.throw()
})
describe('format options', function() {
it('should allow user to set units', function() {
p = plotter({units: 'mm'})
expect(p.format.units).to.equal('mm')
p = plotter({units: 'in'})
expect(p.format.units).to.equal('in')
expect(function() {
p = plotter({units: 'foo'})
}).to.throw(/units/)
})
it('should allow user to set backupUnits', function() {
p = plotter({backupUnits: 'mm'})
expect(p.format.backupUnits).to.equal('mm')
p = plotter({units: 'in'})
expect(p.format.units).to.equal('in')
expect(function() {
p = plotter({backupUnits: 'foo'})
}).to.throw(/units must be/)
})
it('should allow user to set notation', function() {
p = plotter({nota: 'A'})
expect(p.format.nota).to.equal('A')
p = plotter({nota: 'I'})
expect(p.format.nota).to.equal('I')
expect(function() {
p = plotter({nota: 'foo'})
}).to.throw(/notation/)
})
it('should allow user to set backup notation', function() {
p = plotter({backupNota: 'A'})
expect(p.format.backupNota).to.equal('A')
p = plotter({backupNota: 'I'})
expect(p.format.backupNota).to.equal('I')
expect(function() {
p = plotter({backupNota: 'foo'})
}).to.throw(/notation must be/)
})
it('should default backup units and notation to inches and abs', function() {
expect(p.format.backupUnits).to.equal('in')
expect(p.format.backupNota).to.equal('A')
})
it('should not throw with null/undefined options', function() {
var p
expect(function() {
p = plotter({units: null})
}).to.not.throw()
expect(p.format.units === null).to.equal(true)
expect(function() {
p = plotter({backupUnits: undefined})
}).to.not.throw()
expect(p.format.backupUnits).to.equal('in')
expect(function() {
p = plotter({nota: undefined})
}).to.not.throw()
expect(p.format.nota === null).to.equal(true)
expect(function() {
p = plotter({backupNota: null})
}).to.not.throw()
expect(p.format.backupNota).to.equal('A')
})
})
describe('plotting options', function() {
it('should have an optimize paths option that defaults to falsey', function() {
expect(!p._optimizePaths).to.equal(true)
p = plotter({optimizePaths: true})
expect(p._optimizePaths).to.equal(true)
})
it('should have an outline mode option that defaults to falsey', function() {
expect(!p._plotAsOutline).to.equal(true)
p = plotter({plotAsOutline: true, units: 'mm'})
expect(p._plotAsOutline).to.equal(0.00011)
})
it('should convert default outline gap fill to inches', function() {
expect(!p._plotAsOutline).to.equal(true)
p = plotter({plotAsOutline: true, units: 'in'})
expect(p._plotAsOutline).to.be.closeTo(0.00011 / 25.4, EPSILON)
})
it('should convert given outline gap fill to inches', function() {
expect(!p._plotAsOutline).to.equal(true)
p = plotter({plotAsOutline: 0.1, units: 'in'})
expect(p._plotAsOutline).to.be.closeTo(0.1 / 25.4, EPSILON)
})
it('should force optimize paths to true if plot as outline is true', function() {
p = plotter({plotAsOutline: true, optimizePaths: false, units: 'mm'})
expect(p._plotAsOutline).to.equal(0.00011)
expect(p._optimizePaths).to.equal(true)
})
})
describe('handling set commands', function() {
describe('format', function() {
it('should set units', function() {
p.write({type: 'set', prop: 'units', value: 'mm'})
expect(p.format.units).to.equal('mm')
p = plotter()
p.write({type: 'set', prop: 'units', value: 'in'})
expect(p.format.units).to.equal('in')
})
it('should not redefine units', function() {
p = plotter({units: 'in'})
p.write({type: 'set', prop: 'units', value: 'mm'})
expect(p.format.units).to.equal('in')
})
it('should set the notation', function() {
p.write({type: 'set', prop: 'nota', value: 'A'})
expect(p.format.nota).to.equal('A')
p = plotter()
p.write({type: 'set', prop: 'nota', value: 'I'})
expect(p.format.nota).to.equal('I')
})
it('should not redefine notation', function() {
p = plotter({nota: 'A'})
p.write({type: 'set', prop: 'nota', value: 'I'})
expect(p.format.nota).to.equal('A')
})
it('should set the backup units', function() {
p.write({type: 'set', prop: 'backupUnits', value: 'mm'})
expect(p.format.backupUnits).to.equal('mm')
p.write({type: 'set', prop: 'backupUnits', value: 'in'})
expect(p.format.backupUnits).to.equal('in')
})
it('should not redefine the backupUnits set by user', function() {
p = plotter({backupUnits: 'in'})
p.write({type: 'set', prop: 'backupUnits', value: 'mm'})
expect(p.format.backupUnits).to.equal('in')
})
it('should not redefine the backupNotation set by user', function() {
p = plotter({backupNota: 'A'})
p.write({type: 'set', prop: 'backupNota', value: 'I'})
expect(p.format.backupNota).to.equal('A')
})
})
describe('plotter state', function() {
it('should change the tool', function() {
var tool = {}
p._tools['10'] = tool
p.write({type: 'set', prop: 'tool', value: '10'})
expect(p._tool).to.equal(tool)
})
it('should warn if the tool does not exist', function(done) {
p.once('warning', function(w) {
expect(w.line).to.equal(10)
expect(w.message).to.match(/tool 10/)
expect(p._tool === null).to.equal(true)
done()
})
p.write({type: 'set', line: 10, prop: 'tool', value: '10'})
})
it('should set the region mode', function() {
p.write({type: 'set', line: 10, prop: 'region', value: true})
expect(p._region).to.equal(true)
p.write({type: 'set', line: 10, prop: 'region', value: false})
expect(p._region).to.equal(false)
})
it('should warn and ignore tool changes if region mode is on', function(done) {
p.once('warning', function(w) {
expect(w.line).to.equal(11)
expect(w.message).to.match(/region/)
expect(p._tool === null).to.equal(true)
done()
})
p._tools['10'] = {}
p.write({type: 'set', line: 10, prop: 'region', value: true})
p.write({type: 'set', line: 11, prop: 'tool', value: '10'})
})
it('should set the interpolation mode', function() {
p.write({type: 'set', prop: 'mode', value: 'i'})
expect(p._mode).to.equal('i')
p.write({type: 'set', prop: 'mode', value: 'cw'})
expect(p._mode).to.equal('cw')
p.write({type: 'set', prop: 'mode', value: 'ccw'})
expect(p._mode).to.equal('ccw')
})
it('should set the arc quadrant mode', function() {
p.write({type: 'set', prop: 'quad', value: 's'})
expect(p._quad).to.equal('s')
p.write({type: 'set', prop: 'quad', value: 'm'})
expect(p._quad).to.equal('m')
})
})
})
describe('handling done command', function() {
it('should set the done flag', function() {
p.write({type: 'done'})
expect(p._done).to.equal(true)
})
it('should warn if other commands come in after a done', function(done) {
p.once('warning', function(w) {
expect(w.message).to.match(/done/)
done()
})
p.write({type: 'done'})
p.write({type: 'set', prop: 'mode', value: 'i'})
})
})
describe('handling new tool commands', function() {
it('should set current tool to newly defined tool', function() {
var circle = {shape: 'circle', params: [4], hole: []}
p.write({type: 'tool', code: '10', tool: circle})
expect(p._tools['10']).to.equal(p._tool)
p.write({type: 'tool', code: '15', tool: circle})
expect(p._tools['15']).to.equal(p._tool)
})
it('should set trace width for circle and rectangle tools', function() {
var circle = {shape: 'circle', params: [4], hole: []}
var rect = {shape: 'rect', params: [2, 3], hole: []}
p.write({type: 'tool', code: '10', tool: circle})
expect(p._tool.trace).to.eql([4])
p.write({type: 'tool', code: '11', tool: rect})
expect(p._tool.trace).to.eql([2, 3])
})
it('should warn if the tool has already been set', function() {
var circle1 = {shape: 'circle', params: [1], hole: []}
var circle2 = {shape: 'circle', params: [2], hole: []}
var circle3 = {shape: 'circle', params: [3], hole: []}
var capturedWarning = null
p.once('warning', function(w) {
capturedWarning = w
})
p.write({type: 'tool', code: '11', tool: circle1, line: 8})
p.write({type: 'tool', code: '12', tool: circle2, line: 9})
p.write({type: 'tool', code: '11', tool: circle3, line: 10})
expect(capturedWarning.message).to.match(/already defined/)
expect(capturedWarning.line).to.equal(10)
expect(p._tool).to.eql(p._tools['11'])
})
it('should not set trace for untraceable tools', function() {
var circle = {shape: 'circle', params: [4], hole: [1, 1]}
var rect = {shape: 'rect', params: [2, 3], hole: [1]}
var obround = {shape: 'obround', params: [2, 3], hole: []}
var poly = {shape: 'poly', params: [2, 3, 4], hole: []}
var macro = {shape: 'SOME_MACRO', params: [], hole: []}
p.write({type: 'tool', code: '10', tool: circle})
expect(p._tool.trace).to.eql([])
p.write({type: 'tool', code: '11', tool: rect})
expect(p._tool.trace).to.eql([])
p.write({type: 'tool', code: '12', tool: obround})
expect(p._tool.trace).to.eql([])
p.write({type: 'tool', code: '13', tool: poly})
expect(p._tool.trace).to.eql([])
p.write({type: 'tool', code: '14', tool: macro})
expect(p._tool.trace).to.eql([])
})
describe('standard tool pad shapes', function() {
it('should create pad shapes for standard circles', function() {
var circle0 = {shape: 'circle', params: [1], hole: []}
var circle1 = {shape: 'circle', params: [2], hole: [1]}
var circle2 = {shape: 'circle', params: [3], hole: [1, 1]}
p.write({type: 'tool', code: '10', tool: circle0})
expect(p._tool.pad).to.eql([{type: 'circle', cx: 0, cy: 0, r: 0.5}])
p.write({type: 'tool', code: '11', tool: circle1})
expect(p._tool.pad).to.eql([
{type: 'circle', cx: 0, cy: 0, r: 1},
{type: 'layer', polarity: 'clear', box: [-1, -1, 1, 1]},
{type: 'circle', cx: 0, cy: 0, r: 0.5},
])
p.write({type: 'tool', code: '12', tool: circle2})
expect(p._tool.pad).to.eql([
{type: 'circle', cx: 0, cy: 0, r: 1.5},
{type: 'layer', polarity: 'clear', box: [-1.5, -1.5, 1.5, 1.5]},
{type: 'rect', cx: 0, cy: 0, r: 0, width: 1, height: 1},
])
})
it('should create pad shapes for standard rectangles', function() {
var rect0 = {shape: 'rect', params: [1, 2], hole: []}
var rect1 = {shape: 'rect', params: [3, 4], hole: [1]}
var rect2 = {shape: 'rect', params: [5, 6], hole: [1, 1]}
p.write({type: 'tool', code: '10', tool: rect0})
expect(p._tool.pad).to.eql([
{type: 'rect', cx: 0, cy: 0, r: 0, width: 1, height: 2},
])
p.write({type: 'tool', code: '11', tool: rect1})
expect(p._tool.pad).to.eql([
{type: 'rect', cx: 0, cy: 0, r: 0, width: 3, height: 4},
{type: 'layer', polarity: 'clear', box: [-1.5, -2, 1.5, 2]},
{type: 'circle', cx: 0, cy: 0, r: 0.5},
])
p.write({type: 'tool', code: '12', tool: rect2})
expect(p._tool.pad).to.eql([
{type: 'rect', cx: 0, cy: 0, r: 0, width: 5, height: 6},
{type: 'layer', polarity: 'clear', box: [-2.5, -3, 2.5, 3]},
{type: 'rect', cx: 0, cy: 0, r: 0, width: 1, height: 1},
])
})
it('should create pad shapes for standard obrounds', function() {
var obround0 = {shape: 'obround', params: [1, 2], hole: []}
var obround1 = {shape: 'obround', params: [4, 3], hole: [1]}
var obround2 = {shape: 'obround', params: [5, 6], hole: [1, 1]}
p.write({type: 'tool', code: '10', tool: obround0})
expect(p._tool.pad).to.eql([
{type: 'rect', cx: 0, cy: 0, r: 0.5, width: 1, height: 2},
])
p.write({type: 'tool', code: '11', tool: obround1})
expect(p._tool.pad).to.eql([
{type: 'rect', cx: 0, cy: 0, r: 1.5, width: 4, height: 3},
{type: 'layer', polarity: 'clear', box: [-2, -1.5, 2, 1.5]},
{type: 'circle', cx: 0, cy: 0, r: 0.5},
])
p.write({type: 'tool', code: '12', tool: obround2})
expect(p._tool.pad).to.eql([
{type: 'rect', cx: 0, cy: 0, r: 2.5, width: 5, height: 6},
{type: 'layer', polarity: 'clear', box: [-2.5, -3, 2.5, 3]},
{type: 'rect', cx: 0, cy: 0, r: 0, width: 1, height: 1},
])
})
it('should create pad shapes for standard polygons', function() {
var poly0 = {shape: 'poly', params: [2, 3, 0], hole: []}
var poly1 = {shape: 'poly', params: [2, 6, 45], hole: [1]}
var poly2 = {shape: 'poly', params: [2, 12, 140], hole: [1, 1]}
p.write({type: 'tool', code: '10', tool: poly0})
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[1, 0],
[-0.5, 0.8660254],
[-0.5, -0.8660254],
],
},
])
p.write({type: 'tool', code: '11', tool: poly1})
var poly = p._tool.pad[0]
var box = [-0.96592583, -0.96592583, 0.96592583, 0.96592583]
expect(p._tool.pad).to.have.length(3)
expect(poly).to.have.all.keys(['type', 'points'])
expect(poly.type).to.equal('poly')
expect(poly.points).to.eql([
[0.70710678, 0.70710678],
[-0.25881905, 0.96592583],
[-0.96592583, 0.25881905],
[-0.70710678, -0.70710678],
[0.25881905, -0.96592583],
[0.96592583, -0.25881905],
])
expect(p._tool.pad.slice(1)).to.eql([
{type: 'layer', polarity: 'clear', box: box},
{type: 'circle', cx: 0, cy: 0, r: 0.5},
])
p.write({type: 'tool', code: '12', tool: poly2})
poly = p._tool.pad[0]
box = [-0.98480775, -0.98480775, 0.98480775, 0.98480775]
expect(p._tool.pad).to.have.length(3)
expect(poly).to.have.all.keys(['type', 'points'])
expect(poly.type).to.equal('poly')
expect(poly.points).to.eql([
[-0.76604444, 0.64278761],
[-0.98480775, 0.17364818],
[-0.93969262, -0.34202014],
[-0.64278761, -0.76604444],
[-0.17364818, -0.98480775],
[0.34202014, -0.93969262],
[0.76604444, -0.64278761],
[0.98480775, -0.17364818],
[0.93969262, 0.34202014],
[0.64278761, 0.76604444],
[0.17364818, 0.98480775],
[-0.34202014, 0.93969262],
])
expect(p._tool.pad.slice(1)).to.eql([
{type: 'layer', polarity: 'clear', box: box},
{type: 'rect', cx: 0, cy: 0, r: 0, width: 1, height: 1},
])
})
})
describe('standard tool bounding boxes', function() {
it('should calculate a bounding box for a circle', function() {
var circle0 = {shape: 'circle', params: [1], hole: []}
var circle1 = {shape: 'circle', params: [7], hole: [1]}
var circle2 = {shape: 'circle', params: [4], hole: [1, 1]}
p.write({type: 'tool', code: '10', tool: circle0})
expect(p._tool.box).to.eql([-0.5, -0.5, 0.5, 0.5])
p.write({type: 'tool', code: '11', tool: circle1})
expect(p._tool.box).to.eql([-3.5, -3.5, 3.5, 3.5])
p.write({type: 'tool', code: '12', tool: circle2})
expect(p._tool.box).to.eql([-2, -2, 2, 2])
})
it('should calculate a bounding box for a rects and obrounds', function() {
var rect0 = {shape: 'rect', params: [1, 2], hole: []}
var rect1 = {shape: 'rect', params: [7, 4], hole: [1]}
var obround0 = {shape: 'obround', params: [9, 8], hole: [1, 1]}
var obround1 = {shape: 'obround', params: [4, 1], hole: []}
p.write({type: 'tool', code: '10', tool: rect0})
expect(p._tool.box).to.eql([-0.5, -1, 0.5, 1])
p.write({type: 'tool', code: '11', tool: rect1})
expect(p._tool.box).to.eql([-3.5, -2, 3.5, 2])
p.write({type: 'tool', code: '12', tool: obround0})
expect(p._tool.box).to.eql([-4.5, -4, 4.5, 4])
p.write({type: 'tool', code: '13', tool: obround1})
expect(p._tool.box).to.eql([-2, -0.5, 2, 0.5])
})
it('should calculate a bounding box for a standard polygon', function() {
var poly0 = {shape: 'poly', params: [5, 4, 0], hole: []}
var poly1 = {shape: 'poly', params: [6, 8, 0], hole: [1]}
var poly2 = {
shape: 'poly',
params: [4 * Math.sqrt(2), 4, 45],
hole: [1, 1],
}
p.write({type: 'tool', code: '10', tool: poly0})
expect(p._tool.box).to.eql([-2.5, -2.5, 2.5, 2.5])
p.write({type: 'tool', code: '11', tool: poly1})
expect(p._tool.box).to.eql([-3, -3, 3, 3])
p.write({type: 'tool', code: '12', tool: poly2})
expect(p._tool.box).to.eql([-2, -2, 2, 2], 10)
})
})
describe('macro tool pads', function() {
describe('primitives without rotation', function() {
it('should ignore comment primitives', function() {
var macro = {
type: 'macro',
name: 'EMPTY',
blocks: [{type: 'comment'}],
}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'EMPTY', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([])
expect(p._tool.box).to.eql([Infinity, Infinity, -Infinity, -Infinity])
})
it('should be able to handle shape and box for circle primitives', function() {
var blocks = [{type: 'circle', exp: 1, dia: 4, cx: 3, cy: 4, rot: 0}]
var macro = {type: 'macro', name: 'CIRC', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'CIRC', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([{type: 'circle', cx: 3, cy: 4, r: 2}])
expect(p._tool.box).to.eql([1, 2, 5, 6])
})
it('should be able to handle shape and box for vect primitives', function() {
var blocks = [
{
type: 'vect',
exp: 1,
width: 2,
x1: 0,
y1: 0,
x2: 5,
y2: 0,
rot: 0,
},
{
type: 'vect',
exp: 1,
width: 1,
x1: 0,
y1: 0,
x2: 0,
y2: 5,
rot: 0,
},
]
var macro = {type: 'macro', name: 'VECT', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'VECT', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[0, -1],
[5, -1],
[5, 1],
[0, 1],
],
},
{
type: 'poly',
points: [
[0.5, 0],
[0.5, 5],
[-0.5, 5],
[-0.5, 0],
],
},
])
expect(p._tool.box).to.eql([-0.5, -1, 5, 5])
})
it('should be able to handle rectangle primitives', function() {
var blocks = [
{type: 'rect', exp: 1, width: 4, height: 2, cx: 3, cy: 4, rot: 0},
]
var macro = {type: 'macro', name: 'RECT', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'RECT', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{type: 'rect', cx: 3, cy: 4, width: 4, height: 2, r: 0},
])
expect(p._tool.box).to.eql([1, 3, 5, 5])
})
it('should be able to handle lower-left rects', function() {
var blocks = [
{type: 'rectLL', exp: 1, width: 4, height: 2, x: 1, y: 3, rot: 0},
]
var macro = {type: 'macro', name: 'LRECT', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'LRECT', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{type: 'rect', cx: 3, cy: 4, width: 4, height: 2, r: 0},
])
expect(p._tool.box).to.eql([1, 3, 5, 5])
})
it('should be able to handle an outline primitive', function() {
var blocks = [
{type: 'outline', exp: 1, points: [0, 0, 1, 0, 1, 1, 0, 0], rot: 0},
]
var macro = {type: 'macro', name: 'OPOLY', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'OPOLY', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[0, 0],
[1, 0],
[1, 1],
],
},
])
expect(p._tool.box).to.eql([0, 0, 1, 1])
})
it('should handle a regular polygon primitive', function() {
var blocks = [
{type: 'poly', exp: 1, vertices: 4, cx: 3, cy: 2, dia: 2, rot: 0},
]
var macro = {type: 'macro', name: 'POLY', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'POLY', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[4, 2],
[3, 3],
[2, 2],
[3, 1],
],
},
])
expect(p._tool.box).to.eql([2, 1, 4, 3])
})
it('should handle moiré primitives with only rings', function() {
var blocks = [
{
type: 'moire',
exp: 1,
cx: 2,
cy: 3,
dia: 4,
ringThx: 0.4,
ringGap: 0.2,
maxRings: 2,
crossThx: 0.1,
crossLen: 5,
rot: 0,
},
]
var macro = {type: 'macro', name: 'TARG', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'TARG', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{type: 'ring', cx: 2, cy: 3, r: 1.8, width: 0.4},
{type: 'ring', cx: 2, cy: 3, r: 1.2, width: 0.4},
{type: 'rect', cx: 2, cy: 3, width: 5, height: 0.1, r: 0},
{type: 'rect', cx: 2, cy: 3, width: 0.1, height: 5, r: 0},
])
expect(p._tool.box).to.eql([-0.5, 0.5, 4.5, 5.5])
})
it('should handle moirés with circle centers', function() {
var blocks = [
{
type: 'moire',
exp: 1,
cx: 5,
cy: 5,
dia: 2.8,
ringThx: 0.5,
ringGap: 0.5,
maxRings: 2,
crossThx: 0.2,
crossLen: 2.5,
rot: 0,
},
]
var macro = {type: 'macro', name: 'TARG', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'TARG', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{type: 'ring', cx: 5, cy: 5, r: 1.15, width: 0.5},
{type: 'circle', cx: 5, cy: 5, r: 0.4},
{type: 'rect', cx: 5, cy: 5, width: 2.5, height: 0.2, r: 0},
{type: 'rect', cx: 5, cy: 5, width: 0.2, height: 2.5, r: 0},
])
expect(p._tool.box).to.eql([3.6, 3.6, 6.4, 6.4])
})
it('should handle thermals', function() {
var blocks = [
{
type: 'thermal',
exp: 1,
cx: 1,
cy: 1,
outerDia: 7,
innerDia: 5,
gap: 1,
rot: 0,
},
]
var macro = {type: 'macro', name: 'THRM', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'THRM', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'clip',
shape: [
{type: 'rect', cx: 3, cy: 3, width: 3, height: 3, r: 0},
{type: 'rect', cx: -1, cy: 3, width: 3, height: 3, r: 0},
{type: 'rect', cx: -1, cy: -1, width: 3, height: 3, r: 0},
{type: 'rect', cx: 3, cy: -1, width: 3, height: 3, r: 0},
],
clip: {type: 'ring', cx: 1, cy: 1, r: 3, width: 1},
},
])
expect(p._tool.box).to.eql([-2.5, -2.5, 4.5, 4.5])
})
})
describe('rotated primitives', function() {
it('should handle rotated circles', function() {
var blocks = [{type: 'circle', exp: 1, dia: 4, cx: 0, cy: 4, rot: 90}]
var macro = {type: 'macro', name: 'RCIRC', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'RCIRC', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([{type: 'circle', cx: -4, cy: 0, r: 2}])
expect(p._tool.box).to.eql([-6, -2, -2, 2])
})
it('should handle rotated vects', function() {
var blocks = [
{
type: 'vect',
exp: 1,
width: 1,
x1: 1,
y1: 1,
x2: 5,
y2: 5,
rot: 45,
},
]
var macro = {type: 'macro', name: 'RVECT', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'RVECT', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[0.5, 1.41421356],
[0.5, 7.07106781],
[-0.5, 7.07106781],
[-0.5, 1.41421356],
],
},
])
expect(p._tool.box).to.eql([-0.5, 1.41421356, 0.5, 7.07106781])
})
it('should handle rotated rects', function() {
var blocks = [
{type: 'rect', exp: 1, width: 4, height: 2, cx: 3, cy: 4, rot: -30},
]
var macro = {type: 'macro', name: 'RRECT', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'RRECT', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[2.3660254, 2.09807622],
[5.83012702, 0.09807622],
[6.83012702, 1.83012702],
[3.3660254, 3.83012702],
],
},
])
expect(p._tool.box).to.eql([
2.3660254,
0.09807622,
6.83012702,
3.83012702,
])
})
it('should handle rotated lower-left rects', function() {
var blocks = [
{type: 'rectLL', exp: 1, width: 4, height: 2, x: 1, y: 3, rot: -30},
]
var macro = {type: 'macro', name: 'LRECT', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'LRECT', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[2.3660254, 2.09807622],
[5.83012702, 0.09807622],
[6.83012702, 1.83012702],
[3.3660254, 3.83012702],
],
},
])
expect(p._tool.box).to.eql([
2.3660254,
0.09807622,
6.83012702,
3.83012702,
])
})
it('should handle rotated outline polygons', function() {
var blocks = [
{
type: 'outline',
exp: 1,
points: [0, 0, 1, 0, 1, 1, 0, 0],
rot: 150,
},
]
var macro = {type: 'macro', name: 'LRECT', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'LRECT', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[0, 0],
[-0.8660254, 0.5],
[-1.3660254, -0.3660254],
],
},
])
expect(p._tool.box).to.eql([-1.3660254, -0.3660254, 0, 0.5])
})
it('should handle rotated regular polygons', function() {
var dia = 2 * Math.sqrt(2)
var blocks = [
{
type: 'poly',
exp: 1,
vertices: 4,
cx: 0,
cy: 0,
dia: dia,
rot: 45,
},
]
var macro = {type: 'macro', name: 'POLY', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'POLY', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[1, 1],
[-1, 1],
[-1, -1],
[1, -1],
],
},
])
expect(p._tool.box).to.eql([-1, -1, 1, 1])
})
it('should handle rotated moires', function() {
var blocks = [
{
type: 'moire',
exp: 1,
cx: 0,
cy: 0,
dia: 4,
ringThx: 0.4,
ringGap: 0.2,
maxRings: 2,
crossThx: 0.1,
crossLen: 5,
rot: -150,
},
]
var macro = {type: 'macro', name: 'TARG', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'TARG', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{type: 'ring', cx: 0, cy: 0, r: 1.8, width: 0.4},
{type: 'ring', cx: 0, cy: 0, r: 1.2, width: 0.4},
{
type: 'poly',
points: [
[2.19006351, 1.20669873],
[-2.14006351, -1.29330127],
[-2.19006351, -1.20669873],
[2.14006351, 1.29330127],
],
},
{
type: 'poly',
points: [
[1.29330127, -2.14006351],
[1.20669873, -2.19006351],
[-1.29330127, 2.14006351],
[-1.20669873, 2.19006351],
],
},
])
expect(p._tool.box).to.eql([
-2.19006351,
-2.19006351,
2.19006351,
2.19006351,
])
})
it('should handle rotated thermals', function() {
var blocks = [
{
type: 'thermal',
exp: 1,
cx: 0,
cy: 0,
outerDia: 4,
innerDia: 3,
gap: 0.2,
rot: 45,
},
]
var macro = {type: 'macro', name: 'THRM', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'THRM', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'clip',
shape: [
{
type: 'poly',
points: [
[0, 0.14142136],
[1.34350288, 1.48492424],
[0, 2.82842712],
[-1.34350288, 1.48492424],
],
},
{
type: 'poly',
points: [
[-1.48492424, -1.34350288],
[-0.14142136, 0],
[-1.48492424, 1.34350288],
[-2.82842712, 0],
],
},
{
type: 'poly',
points: [
[0, -2.82842712],
[1.34350288, -1.48492424],
[0, -0.14142136],
[-1.34350288, -1.48492424],
],
},
{
type: 'poly',
points: [
[1.48492424, -1.34350288],
[2.82842712, 0],
[1.48492424, 1.34350288],
[0.14142136, 0],
],
},
],
clip: {type: 'ring', cx: 0, cy: 0, r: 1.75, width: 0.5},
},
])
expect(p._tool.box).to.eql([-2, -2, 2, 2])
})
})
it('should handle modifiers and functional args', function() {
var blocks = [
{
type: 'circle',
exp: 1,
dia: function(mods) {
return mods.$1
},
cx: function(mods) {
return mods.$2
},
cy: function(mods) {
return mods.$3
},
rot: function(mods) {
return mods.$4
},
},
]
var mods = [4, 3, 2, 0]
var macro = {type: 'macro', name: 'CIRC', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'CIRC', params: mods, hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([{type: 'circle', cx: 3, cy: 2, r: 2}])
})
it('should handle variable sets', function() {
var blocks = [
{
type: 'variable',
set: function(mods) {
return {$1: 4, $2: 3, $3: mods.$2 - 1}
},
},
{
type: 'circle',
exp: 1,
dia: function(mods) {
return mods.$1
},
cx: function(mods) {
return mods.$2
},
cy: function(mods) {
return mods.$3
},
rot: 0,
},
]
var mods = [4, 3]
var macro = {type: 'macro', name: 'CIRC', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'CIRC', params: mods, hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([{type: 'circle', cx: 3, cy: 2, r: 2}])
})
it('should handle modifiers and functional array args', function() {
var blocks = [
{
type: 'outline',
exp: 1,
points: [
function(mods) {
return mods.$1
},
function(mods) {
return mods.$2
},
function(mods) {
return mods.$3
},
function(mods) {
return mods.$4
},
function(mods) {
return mods.$5
},
function(mods) {
return mods.$6
},
function(mods) {
return mods.$7
},
function(mods) {
return mods.$8
},
],
rot: function(mods) {
return mods.$9
},
},
]
var mods = [0, 0, 1, 0, 1, 1, 0]
var macro = {type: 'macro', name: 'OUT', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'OUT', params: mods, hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{
type: 'poly',
points: [
[0, 0],
[1, 0],
[1, 1],
],
},
])
})
it('should handle multiple primitives and exposure', function() {
var blocks = [
{type: 'circle', exp: 1, dia: 4, cx: -2, cy: 0, rot: 0},
{type: 'rect', exp: 0, width: 1, height: 1, cx: -1, cy: 0, rot: 0},
{type: 'rect', exp: 0, width: 1, height: 1, cx: 1, cy: 0, rot: 0},
{type: 'circle', exp: 1, dia: 4, cx: 2, cy: 0, rot: 0},
]
var macro = {type: 'macro', name: 'MAC', blocks: blocks}
var tool = {
type: 'tool',
code: '10',
tool: {shape: 'MAC', params: [], hole: []},
}
p.write(macro)
p.write(tool)
expect(p._tool.pad).to.eql([
{type: 'circle', cx: -2, cy: 0, r: 2},
{type: 'layer', polarity: 'clear', box: [-4, -2, 0, 2]},
{type: 'rect', width: 1, height: 1, cx: -1, cy: 0, r: 0},
{type: 'rect', width: 1, height: 1, cx: 1, cy: 0, r: 0},
{type: 'layer', polarity: 'dark', box: [-4, -2, 0, 2]},
{type: 'circle', cx: 2, cy: 0, r: 2},
])
expect(p._tool.box).to.eql([-4, -2, 4, 2])
})
})
})
describe('handling operation commands', function() {
beforeEach(function() {
var tool = {shape: 'circle', params: [2], hole: []}
p.write({type: 'set', prop: 'epsilon', value: 0.00000001})
p.write({type: 'set', prop: 'units', value: 'in'})
p.write({type: 'set', prop: 'nota', value: 'A'})
p.write({type: 'set', prop: 'mode', value: 'i'})
p.write({type: 'tool', code: '10', tool: tool})
})
it('should move the plotter', function() {
p.write({type: 'op', op: 'int', coord: {x: 4, y: -3}})
expect(p._pos).to.eql([4, -3])
p.write({type: 'op', op: 'move', coord: {y: 0}})
expect(p._pos).to.eql([4, 0])
p.write({type: 'op', op: 'flash', coord: {x: -7}})
expect(p._pos).to.eql([-7, 0])
})
it('should move the plotter with incremental notation', function() {
p.nota = 'I'
p.write({type: 'op', op: 'int', coord: {x: 4, y: -3, i: 1, j: 4}})
expect(p._pos).to.eql([4, -3])
p.write({type: 'op', op: 'move', coord: {y: 1}})
expect(p._pos).to.eql([4, -2])
p.write({type: 'op', op: 'flash', coord: {x: -7}})
expect(p._pos).to.eql([-3, -2])
})
describe('flashing pads', function() {
it('should emit a shape if first flash for tool', function(done) {
p.once('readable', function() {
var result = p.read()
expect(result).to.eql({
type: 'shape',
tool: '10',
shape: p._tool.pad,
})
done()
})
p.write({type: 'op', op: 'flash', coord: {x: 1, y: 1}})
})
it('should emit pad objects after the shape object', function(done) {
p.once('data', function(result) {
expect(result.type).to.equal('shape')
p.once('data', function(result) {
expect(result).to.eql({type: 'pad', tool: '10', x: 1, y: 1})
done()
})
})
p.write({type: 'op', op: 'flash', coord: {x: 1, y: 1}})
})
it('should not emit the pad shape more than once', function(done) {
var results = 0
var expected = ['shape', 'pad', 'pad']
var handleData = function(data) {
expect(data.type).to.eql(expected[results])
if (++results >= expected.length) {
p.removeListener('data', handleData)
return done()
}
}
p.on('data', handleData)
p.write({type: 'op', op: 'flash', coord: {x: 1, y: 1}})
p.write({type: 'op', op: 'flash', coord: {x: 5, y: 5}})
})
it('should update the bounding box', function() {
p.write({type: 'op', op: 'flash', coord: {x: 1, y: 1}})
expect(p._box).to.eql([0, 0, 2, 2])
})
})
describe('interpolating to create strokes', function() {
it('should create a path graph with linear strokes', function() {
p.write({type: 'op', op: 'int', coord: {x: 1, y: 1}})
p.write({type: 'op', op: 'int', coord: {x: 1, y: 3}})
p.write({type: 'op', op: 'int', coord: {x: 3, y: 3}})
expect(p._path.traverse()).to.eql([
{type: 'line', start: [0, 0], end: [1, 1]},
{type: 'line', start: [1, 1], end: [1, 3]},
{type: 'line', start: [1, 3], end: [3, 3]},
])
})
it('should handle moves in between strokes when optimizing paths', function() {
var tool = {shape: 'circle', params: [2], hole: []}
p = plotter({optimizePaths: true})
p.write({type: 'set', prop: 'epsilon', value: 0.00000001})
p.write({type: 'set', prop: 'units', value: 'in'})
p.write({type: 'set', prop: 'nota', value: 'A'})
p.write({type: 'set', prop: 'mode', value: 'i'})
p.write({type: 'tool', code: '10', tool: tool})
p.write({type: 'op', op: 'int', coord: {x: 1, y: 1}})
p.write({type: 'op', op: 'move', coord: {x: 1, y: 3}})
p.write({type: 'op', op: 'int', coord: {x: 1, y: 1}})
p.write({type: 'op', op: 'move', coord: {x: 3, y: 3}})
p.write({type: 'op', op: 'int', coord: {x: 1, y: 3}})
expect(p._path.traverse()).to.eql([
{type: 'line', start: [0, 0], end: [1, 1]},
{type: 'line', start: [1, 1], end: [1, 3]},
{type: 'line', start: [1, 3], end: [3, 3]},
])
})
it('should handle moves in between strokes when not optimizing paths', function() {
var tool = {shape: 'circle', params: [2], hole: []}
p = plotter({optimizePaths: false})
p.write({type: 'set', prop: 'epsilon', value: 0.00000001})
p.write({type: 'set', prop: 'units', value: 'in'})
p.write({type: 'set', prop: 'nota', value: 'A'})
p.write({type: 'set', prop: 'mode', value: 'i'})
p.write({type: 'tool', code: '10', tool: tool})
p.write({type: 'op', op: 'int', coord: {x: 1, y: 1}})
p.write({type: 'op', op: 'move', coord: {x: 1, y: 3}})
p.write({type: 'op', op: 'int', coord: {x: 1, y: 1}})
p.write({type: 'op', op: 'move', coord: {x: 3, y: 3}})
p.write({type: 'op', op: 'int', coord: {x: 1, y: 3}})
expect(p._path.traverse()).to.eql([
{type: 'line', start: [0, 0], end: [1, 1]},
{type: 'line', start: [1, 3], end: [1, 1]},
{type: 'line', start: [3, 3], end: [1, 3]},
])
})
it('should update the box in non-region mode', function() {
p.write({type: 'op', op: 'int', coord: {x: 1, y: 3}})
p.write({type: 'op', op: 'int', coord: {x: 3, y: 3}})
p.write({type: 'op', op: 'int', coord: {x: 0, y: 0}})
expect(p._box).to.eql([-1, -1, 4, 4])
})
it('should update the bounding box in region mode', function() {
p.write({type: 'set', prop: 'region', value: true})
p.write({type: 'op', op: 'int', coord: {x: 1, y: 3}})
p.write({type: 'op', op: 'int', coord: {x: 3, y: 3}})
p.write({type: 'op', op: 'int', coord: {x: 0, y: 0}})
expect(p._box).to.eql([0, 0, 3, 3])
})
describe('arc strokes', function() {
it('should determine the center and radius in single quadrant mode', function() {
p.write({type: 'set', prop: 'arc', value: 's'})
p.write({type: 'set', prop: 'mode', value: 'cw'})
p.write({type: 'op', op: 'int', coord: {x: 2, y: 0, i: 1, j: 1.5}})
p.write({type: 'set', prop: 'mode', value: 'ccw'})
p.write({type: 'op', op: 'int', coord: {x: 4, y: 0, i: 1, j: 1.5}})
p.write({type: 'set', prop: 'mode', value: 'cw'})
p.write({type: 'op', op: 'int', coord: {x: 4, y: -2, i: 1.5, j: 1}})
p.write({type: 'set', prop: 'mode', value: 'ccw'})
p.write({type: 'op', op: 'int', coord: {x: 4, y: 0, i: 1.5, j: 1}})
var R = Math.sqrt(Math.pow(1.5, 2) + 1)
var arcs = p._path.traverse()
// first arc
expect(arcs[0]).to.deep.include({
type: 'arc',
dir: 'cw',
center: [1, -1.5],
})
expect(arcs[0].start.slice(0, 2)).to.eql([0, 0])
expect(arcs[0].start[2]).to.be.closeTo(2.158799, EPSILON)
expect(arcs[0].end.slice(0, 2)).to.eql([2, 0])
expect(arcs[0].end[2]).to.be.closeTo(0.982794, EPSILON)
expect(arcs[0].sweep).to.be.closeTo(1.176005, EPSILON)
expect(arcs[0].radius).to.be.closeTo(R, EPSILON)
// second arc
expect(arcs[1]).to.deep.include({
type: 'arc',
dir: 'ccw',
center: [3, 1.5],
})
expect(arcs[1].start.slice(0, 2)).to.eql([2, 0])
expect(arcs[1].start[2]).to.be.closeTo(4.124386, EPSILON)
expect(arcs[1].end.slice(0, 2)).to.eql([4, 0])
expect(arcs[1].end[2]).to.be.closeTo(5.300391, EPSILON)
expect(arcs[1].sweep).to.be.closeTo(1.176005, EPSILON)
expect(arcs[1].radius).to.be.closeTo(R, EPSILON)
// third arc
expect(arcs[2]).to.deep.include({
type: 'arc',
dir: 'cw',
center: [2.5, -1],
})
expect(arcs[2].start.slice(0, 2)).to.eql([4, 0])
expect(arcs[2].start[2]).to.be.closeTo(0.588002, EPSILON)
expect(arcs[2].end.slice(0, 2)).to.eql([4, -2])
expect(arcs[2].end[2]).to.be.closeTo(5.695182, EPSILON)
expect(arcs[2].sweep).to.be.closeTo(1.176005, EPSILON)
expect(arcs[2].radius).to.be.closeTo(R, EPSILON)
// fourth arc
expect(arcs[3]).to.deep.include({
type: 'arc',
dir: 'ccw',
center: [2.5, -1],
})
expect(arcs[3].start.slice(0, 2)).to.eql([4, -2])
expect(arcs[3].start[2]).to.be.closeTo(5.695183, EPSILON)
expect(arcs[3].end.slice(0, 2)).to.eql([4, 0])
expect(arcs[3].end[2]).to.be.closeTo(0.588003, EPSILON)
expect(arcs[3].sweep).to.be.closeTo(1.176005, EPSILON)
expect(arcs[3].radius).to.be.closeTo(R, EPSILON)
})
it('should use the actual offsets to get the center in multi-quadrant mode', function() {
p.write({type: 'set', prop: 'arc', value: 'm'})
p.write({type: 'set', prop: 'mode', value: 'cw'})
p.write({type: 'op', op: 'int', coord: {x: 2, y: 0, i: 1, j: -1.5}})
p.write({type: 'set', prop: 'mode', value: 'ccw'})
p.write({type: 'op', op: 'int', coord: {x: 4, y: 0, i: 1, j: 1.5}})
var R = Math.sqrt(Math.pow(1.5, 2) + 1)
var arcs = p._path.traverse()
// first arc
expect(arcs[0]).to.deep.include({
type: 'arc',
dir: 'cw',
center: [1, -1.5],
})
expect(arcs[0].start.slice(0, 2)).to.eql([0, 0])
expect(arcs[0].start[2]).to.be.closeTo(2.158799, EPSILON)
expect(arcs[0].end.slice(0, 2)).to.eql([2, 0])
expect(arcs[0].end[2]).to.be.closeTo(0.982794, EPSILON)
expect(arcs[0].sweep).to.be.closeTo(1.176005, EPSILON)
expect(arcs[0].radius).to.be.closeTo(R, EPSILON)
// second arc
expect(arcs[1]).to.deep.include({
type: 'arc',
dir: 'ccw',
center: [3, 1.5],
})
expect(arcs[1].sta