UNPKG

scadr

Version:

Render multi-part OpenSCAD files to STL

151 lines (150 loc) 6.14 kB
#!/usr/bin/env node "use strict"; 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]; }