| 1 | import { describe, expect, test } from 'bun:test';
|
| 2 | import { NextDraw } from '../src/nextdraw.js';
|
| 3 | import { penLiftTimes, servoSettings } from '../src/pen.js';
|
| 4 | import { calcJerk } from '../src/plan.js';
|
| 5 |
|
| 6 | function nd(configure) {
|
| 7 | const n = new NextDraw({ log: () => {} });
|
| 8 | n.interactive();
|
| 9 | configure?.(n);
|
| 10 | n.updateOptions();
|
| 11 | return n;
|
| 12 | }
|
| 13 |
|
| 14 | describe('model and handling parameters', () => {
|
| 15 | test('NextDraw 8511 defaults (Technical drawing)', () => {
|
| 16 | const n = nd();
|
| 17 | expect(n.params.modelName).toBe('Bantam Tools NextDraw™ 8511');
|
| 18 | expect(n.params.travelX).toBe(11.81);
|
| 19 | expect(n.params.travelY).toBe(8.58);
|
| 20 | expect(n.params.autoHome).toBe(true);
|
| 21 | expect(n.params.resolution).toBe(1);
|
| 22 | expect(n.params.speedLimit).toBe(8.6979);
|
| 23 | expect(n.params.speedUp).toBe(8.6979);
|
| 24 | expect(n.params.jerkPenDown).toBe(20000);
|
| 25 | expect(n.params.jerkPenUp).toBe(19000);
|
| 26 | expect(n.params.constSpeed).toBe(false);
|
| 27 | // Brushless narrow-band pen-lift servo on pin B2
|
| 28 | expect(n.params.zMotor).toBe(1);
|
| 29 | expect(n.params.servoPin).toBe(2);
|
| 30 | expect([n.params.servoMin, n.params.servoMax]).toEqual([5400, 12600]);
|
| 31 | expect(n.bounds).toEqual([[-1e-9, -1e-9], [11.81 + 1e-9, 8.58 + 1e-9]]);
|
| 32 | });
|
| 33 |
|
| 34 | test('handling modes', () => {
|
| 35 | const hw = nd((n) => { n.options.handling = 2; });
|
| 36 | expect(hw.params.resolution).toBe(2);
|
| 37 | expect(hw.params.speedLimit).toBe(7);
|
| 38 | expect(hw.params.speedUp).toBe(12);
|
| 39 | expect(hw.params.jerkPenDown).toBe(90000);
|
| 40 | expect(hw.params.jerkPenUp).toBe(13000);
|
| 41 |
|
| 42 | const sk = nd((n) => { n.options.handling = 3; });
|
| 43 | expect(sk.params.speedLimit).toBe(12);
|
| 44 |
|
| 45 | const cs = nd((n) => { n.options.handling = 4; });
|
| 46 | expect(cs.params.constSpeed).toBe(true);
|
| 47 | expect(cs.params.speedLimit).toBe(3.0);
|
| 48 | expect(cs.params.resolution).toBe(1);
|
| 49 | });
|
| 50 |
|
| 51 | test('other models and pen-lift options', () => {
|
| 52 | const a3 = nd((n) => { n.options.model = 2; });
|
| 53 | expect(a3.params.autoHome).toBe(false);
|
| 54 | expect(a3.params.zMotor).toBe(0);
|
| 55 | expect(a3.params.servoPin).toBe(1);
|
| 56 |
|
| 57 | const upgraded = nd((n) => { n.options.model = 1; n.options.penlift = 3; });
|
| 58 | expect(upgraded.params.zMotor).toBe(1);
|
| 59 |
|
| 60 | const big = nd((n) => { n.options.model = 10; n.options.handling = 1; });
|
| 61 | expect(big.params.travelX).toBe(34.02);
|
| 62 | expect(big.params.jerkPenDown).toBeCloseTo(20000 * 0.6, 9);
|
| 63 | });
|
| 64 |
|
| 65 | test('overrides are applied after model defaults', () => {
|
| 66 | const n = nd((x) => { x.params.overrides.travelX = 5; x.params.overrides.speedLimit = 4; });
|
| 67 | expect(n.params.travelX).toBe(5);
|
| 68 | expect(n.params.speedLimit).toBe(4);
|
| 69 | expect(n.bounds[1][0]).toBeCloseTo(5, 6);
|
| 70 | });
|
| 71 |
|
| 72 | test('invalid model is rejected', () => {
|
| 73 | expect(() => nd((n) => { n.options.model = 11; })).toThrow();
|
| 74 | });
|
| 75 |
|
| 76 | test('options are clamped to their documented ranges', () => {
|
| 77 | const n = nd((x) => { x.options.penPosUp = 150; x.options.speedPendown = 0; x.options.accel = -5; });
|
| 78 | expect(n.options.penPosUp).toBe(100);
|
| 79 | expect(n.options.speedPendown).toBe(1);
|
| 80 | expect(n.options.accel).toBe(1);
|
| 81 | });
|
| 82 | });
|
| 83 |
|
| 84 | describe('pen servo settings', () => {
|
| 85 | test('default positions, rates and lift times', () => {
|
| 86 | const n = nd();
|
| 87 | expect(servoSettings(n, 40)).toEqual({ rateUp: 231, rateDown: 154, posUp: 9720, posDown: 8280 });
|
| 88 | expect(penLiftTimes(n, 40)).toEqual({ raiseTime: 45, lowerTime: 47 });
|
| 89 | });
|
| 90 |
|
| 91 | test('equal up/down heights need no delay; extra delays are added', () => {
|
| 92 | const n = nd((x) => { x.options.penPosDown = 60; });
|
| 93 | expect(penLiftTimes(n, 60)).toEqual({ raiseTime: 0, lowerTime: 0 });
|
| 94 | const d = nd((x) => { x.params.penDelayUp = 100; x.params.penDelayDown = -1000; });
|
| 95 | const t = penLiftTimes(d, 40);
|
| 96 | expect(t.raiseTime).toBe(145);
|
| 97 | expect(t.lowerTime).toBe(0); // never negative
|
| 98 | });
|
| 99 |
|
| 100 | test('standard servo uses 8 PWM channels in its rate scale', () => {
|
| 101 | const n = nd((x) => { x.options.model = 2; });
|
| 102 | // range 17976 * 0.24 / 200 = 21.5712 per %
|
| 103 | expect(servoSettings(n, 40)).toEqual({
|
| 104 | rateUp: Math.round(21.5712 * 75), rateDown: Math.round(21.5712 * 50),
|
| 105 | posUp: Math.round(9855 + 179.76 * 60), posDown: Math.round(9855 + 179.76 * 40),
|
| 106 | });
|
| 107 | });
|
| 108 |
|
| 109 | test('jerk scales with the cube of accel', () => {
|
| 110 | const n = nd((x) => { x.options.accel = 50; });
|
| 111 | const [up, down] = calcJerk(n);
|
| 112 | expect(up).toBeCloseTo(19000 * 0.125, 9);
|
| 113 | expect(down).toBeCloseTo(20000 * 0.125, 9);
|
| 114 | });
|
| 115 | });
|