scadr
Version:
Render multi-part OpenSCAD files to STL
151 lines (150 loc) • 6.14 kB
JavaScript
;
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const tmp = require("tmp");
const fs = require("node:fs/promises");
const util = require("node:util");
const path = require("node:path");
const child_process = require("node:child_process");
const commander = require("commander");
const { program } = commander;
const execFile = util.promisify(child_process.execFile);
const conventions = {
pascal: /^module ([A-Z]\w+)/gm,
all: /^module (\w+)/gm,
underscore: /^module ([^_]\w+)/gm
};
program
.name('scadr')
.option('-d, --define <value>', `variable definitions`, collect, [])
.option('-m, --module <name>', `specific module to render`, collect, [])
.addOption(new commander.Option('-c, --convention <kind>', 'top-level naming convention').choices(['auto', ...Object.keys(conventions)]).default("auto"))
.option('-l, --list', `list modules without rendering`)
.option('--dry', `dry run (show what would happen)`)
.argument("<path>", `.scad file to render`)
.action(main);
program.parse();
function main(filePath, options) {
asyncMain(filePath, options).then(() => { });
}
function asyncMain(filePath, options) {
return __awaiter(this, void 0, void 0, function* () {
const fullPath = path.resolve(filePath);
const ospath = yield findOpenScad();
const tasks = [];
if (options.list) {
console.log("Discovered modules:");
for (const p of yield getPartsToRender()) {
console.log(` * ${p}`);
}
return;
}
const parts = options.module.length ? options.module : yield getPartsToRender();
for (const p of parts) {
tasks.push(processModule(p));
}
for (const p of tasks)
yield p;
console.log(`Done!`);
function getPartsToRender() {
return __awaiter(this, void 0, void 0, function* () {
const fileContent = yield fs.readFile(filePath, { encoding: "utf-8" });
let modules;
if (options.convention === 'auto') {
const allModules = findModules(conventions.all);
const pascalModules = findModules(conventions.pascal);
const underscore = findModules(conventions.underscore);
if ((pascalModules.length > 0) && (pascalModules.length < allModules.length)) {
return pascalModules;
}
else if ((underscore.length > 0) && (underscore.length < allModules.length)) {
return underscore;
}
else {
return allModules;
}
}
return findModules(conventions[options.convention]);
function findModules(rgx) {
return [...fileContent.matchAll(rgx)].map(g => g[1]);
}
});
}
function processModule(moduleName) {
return __awaiter(this, void 0, void 0, function* () {
// Generate temp file contents
const tempPath = tmp.tmpNameSync({ postfix: ".scad" });
const tempFileContents = `// Generated by scadr\nuse <${fullPath}>\n${moduleName}();`;
// Write out the temp file
yield fs.writeFile(tempPath, tempFileContents, { encoding: "utf-8" });
const outFile = `${filePath.replace(/\.scad/i, `-${moduleName}.stl`)}`;
const args = getArgs(tempPath, outFile);
console.log(`Rendering ${moduleName} to ${outFile}...`);
if (options.dry) {
console.log(`${ospath} ${args.join(" ")}`);
}
else {
yield execFile(ospath, args);
}
yield fs.unlink(tempPath);
});
}
function getArgs(inputFile, outFile) {
const opts = [
`--o`, outFile,
`--export-format`, 'binstl'
];
for (const def of options.define) {
opts.push('--D', def);
}
opts.push(inputFile);
return opts;
}
});
}
function findOpenScad() {
return __awaiter(this, void 0, void 0, function* () {
const candidateRoots = [
process.env['OPENSCADPATH'],
// Windows
winpath('ProgramFiles'),
winpath('ProgramFiles(x86)'),
// Mac
'/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD',
// Linux [?]
'/usr/bin/openscad'
].filter(p => !!p);
for (const candidate of candidateRoots) {
try {
const stat = yield fs.stat(candidate, {});
if (yield fs.stat(candidate)) {
return candidate;
}
}
catch (_a) {
// Didn't exist; carry on
}
}
console.error("Unable to find OpenSCAD; set OPENSCADPATH environment variable");
return process.exit(-1);
});
}
function winpath(env) {
const val = process.env[env];
if (val) {
return path.join(val, "OpenSCAD", "openscad.com");
}
return undefined;
}
function collect(value, previous) {
return [...previous, value];
}