saxi
Version:
Drive the AxiDraw pen plotter
288 lines • 11.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cli = cli;
const yargs_1 = __importDefault(require("yargs"));
const server_1 = require("./server");
const massager_1 = require("./massager");
const svgdom_1 = require("svgdom");
const fs = __importStar(require("node:fs"));
const flatten_svg_1 = require("flatten-svg");
const util_1 = require("./util");
const planning_1 = require("./planning");
const paper_size_1 = require("./paper-size");
function parseSvg(svg) {
const window = new svgdom_1.Window;
window.document.documentElement.innerHTML = svg;
return window.document.documentElement;
}
function cli(argv) {
yargs_1.default
.strict()
.option('hardware', {
describe: 'select hardware type',
choices: ['v3', 'brushless'],
default: 'v3',
coerce: value => value
})
.option('device', {
alias: 'd',
describe: 'device to connect to',
type: 'string'
})
.command('plot <file>', 'plot an svg, then exit', yargs => yargs
.positional("file", {
type: "string",
description: "File to plot",
})
.option("paper-size", {
alias: "s",
describe: "Paper size to use",
coerce: (value) => {
if (Object.prototype.hasOwnProperty.call(paper_size_1.PaperSize.standard, value)) {
return paper_size_1.PaperSize.standard[value];
}
else {
const m = /^([0-9]*(?:\.[0-9]+)?)\s*x\s*([0-9]*(?:\.[0-9]+)?)\s*(cm|mm|in)$/i.exec(String(value).trim());
if (m) {
return new paper_size_1.PaperSize({ x: Number(m[1]), y: Number(m[2]) });
}
}
throw new Error(`Paper size should be a standard size (${Object.keys(paper_size_1.PaperSize.standard).join(", ")}) or a custom size such as "100x100mm" or "16x10in"`);
},
required: true
})
.option("landscape", {
type: "boolean",
description: "Place the long side of the paper on the x-axis"
})
.option("portrait", {
type: "boolean",
description: "Place the short side of the paper on the x-axis"
})
.option("margin", {
describe: "Margin (in mm)",
type: "number",
default: planning_1.defaultPlanOptions.marginMm,
required: false
})
.option("pen-down-height", {
describe: "Pen down height (%)",
type: "number",
default: planning_1.defaultPlanOptions.penDownHeight,
required: false
})
.option("pen-up-height", {
describe: "Pen up height (%)",
type: "number",
default: planning_1.defaultPlanOptions.penUpHeight,
required: false
})
.option("pen-down-acceleration", {
describe: "Acceleration when the pen is down (in mm/s^2)",
type: "number",
default: planning_1.defaultPlanOptions.penDownAcceleration,
required: false
})
.option("pen-down-max-velocity", {
describe: "Maximum velocity when the pen is down (in mm/s)",
type: "number",
default: planning_1.defaultPlanOptions.penDownMaxVelocity,
required: false
})
.option("pen-down-cornering-factor", {
describe: "Cornering factor when the pen is down",
type: "number",
default: planning_1.defaultPlanOptions.penDownCorneringFactor,
required: false
})
.option("pen-up-acceleration", {
describe: "Acceleration when the pen is up (in mm/s^2)",
type: "number",
default: planning_1.defaultPlanOptions.penUpAcceleration,
required: false
})
.option("pen-up-max-velocity", {
describe: "Maximum velocity when the pen is up (in mm/s)",
type: "number",
default: planning_1.defaultPlanOptions.penUpMaxVelocity,
required: false
})
.option("pen-drop-duration", {
describe: "How long the pen takes to drop (in seconds)",
type: "number",
default: planning_1.defaultPlanOptions.penDropDuration,
required: false
})
.option("pen-lift-duration", {
describe: "How long the pen takes to lift (in seconds)",
type: "number",
default: planning_1.defaultPlanOptions.penLiftDuration,
required: false
})
.option("sort-paths", {
describe: "Re-order paths to minimize pen-up travel time",
type: "boolean",
default: true,
})
.option("fit-page", {
describe: "Re-scale and position the image to fit on the page",
type: "boolean",
default: true,
})
.option("crop-to-margins", {
describe: "Remove lines that fall outside the margins",
type: "boolean",
default: true,
})
.option("minimum-path-length", {
describe: "Remove paths that are shorter than this length (in mm)",
type: "number",
default: planning_1.defaultPlanOptions.minimumPathLength
})
.option("point-join-radius", {
describe: "Point-joining radius (in mm)",
type: "number",
default: planning_1.defaultPlanOptions.pointJoinRadius
})
.option("path-join-radius", {
describe: "Path-joining radius (in mm)",
type: "number",
default: planning_1.defaultPlanOptions.pathJoinRadius
})
.option("rotate-drawing", {
describe: "Rotate drawing (in degrees)",
type: "number",
default: planning_1.defaultPlanOptions.rotateDrawing
})
.check((args) => {
if (args.landscape && args.portrait) {
throw new Error("Only one of --portrait and --landscape may be specified");
}
return true;
}), (args) => __awaiter(this, void 0, void 0, function* () {
console.log("reading svg...");
const svg = fs.readFileSync(args.file, 'utf8');
console.log("parsing svg...");
const parsed = parseSvg(svg);
console.log("flattening svg...");
const lines = (0, flatten_svg_1.flattenSVG)(parsed, {});
console.log("generating motion plan...");
const paperSize = args.landscape ? args['paper-size'].landscape
: args.portrait ? args['paper-size'].portrait
: args['paper-size'];
const planOptions = {
paperSize,
marginMm: args.margin,
hardware: args.hardware,
selectedGroupLayers: new Set([]), // TODO
selectedStrokeLayers: new Set([]), // TODO
layerMode: "all", // TODO
penUpHeight: args["pen-up-height"],
penDownHeight: args["pen-down-height"],
penDownAcceleration: args["pen-down-acceleration"],
penDownMaxVelocity: args["pen-down-max-velocity"],
penDownCorneringFactor: args["pen-down-cornering-factor"],
penUpAcceleration: args["pen-up-acceleration"],
penUpMaxVelocity: args["pen-up-max-velocity"],
penDropDuration: args["pen-drop-duration"],
penLiftDuration: args["pen-lift-duration"],
sortPaths: args["sort-paths"],
fitPage: args["fit-page"],
cropToMargins: args["crop-to-margins"],
rotateDrawing: args["rotate-drawing"],
minimumPathLength: args["minimum-path-length"],
pathJoinRadius: args["path-join-radius"],
pointJoinRadius: args["point-join-radius"],
};
const p = (0, massager_1.replan)(linesToVecs(lines), planOptions);
console.log(`${p.motions.length} motions, estimated duration: ${(0, util_1.formatDuration)(p.duration())}`);
console.log("connecting to plotter...");
const ebb = yield (0, server_1.connectEBB)(args.hardware, args.device);
if (!ebb) {
console.error("Couldn't connect to device!");
process.exit(1);
}
console.log("plotting...");
const startTime = +new Date;
yield ebb.executePlan(p);
console.log(`done! took ${(0, util_1.formatDuration)((+new Date - startTime) / 1000)}`);
yield ebb.close();
}))
.command('pen [percent]', 'put the pen to [percent]', yargs => yargs
.positional('percent', { type: 'number', description: 'percent height between 0 and 100', required: true })
.check(args => args.percent >= 0 && args.percent <= 100), (args) => __awaiter(this, void 0, void 0, function* () {
console.log('connecting to plotter...');
const ebb = yield (0, server_1.connectEBB)(args.hardware, args.device);
if (!ebb) {
console.error("Couldn't connect to device!");
process.exit(1);
}
const device = (0, planning_1.Device)(ebb.hardware);
yield ebb.setPenHeight(device.penPctToPos(args.percent), 1000);
console.log(`moving to ${args.percent}%...`);
yield ebb.close();
}))
.command('$0', 'run the saxi web server', args => args
.option('port', {
alias: 'p',
describe: 'TCP port on which to listen',
default: 9080,
type: 'number',
})
.option('enable-cors', {
describe: 'enable cross-origin resource sharing (CORS)',
default: false,
type: 'boolean',
})
.option('max-payload-size', {
describe: 'maximum payload size to accept',
default: '200mb',
}), args => {
(0, server_1.startServer)(args.port, args.hardware, args.device, args['enable-cors'], args['max-payload-size']);
})
.parse(argv);
}
function linesToVecs(lines) {
return lines.map((line) => {
const a = line.points.map(([x, y]) => ({ x, y }));
a.stroke = line.stroke;
a.groupId = line.groupId;
return a;
});
}
//# sourceMappingURL=cli.js.map