Skip to content

Commit

Permalink
feat(cli): added options to output designs as individual parts, and c…
Browse files Browse the repository at this point in the history
…reate zip archive of parts
  • Loading branch information
tsdexter authored Jun 27, 2023
1 parent 7f5f8e1 commit a20b9cf
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 12 deletions.
10 changes: 10 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ Examples:

```jscad mydesign.js -of amf # -- convert mydesign.js into mydesign.amf```

For multi-part models, you can pass the `generateParts` flag `-gp` to output each part as a separate, numbered file:

```jscad mydesign.js -gp # -- convert mydesign.js into mydesign-part-1-of-2.stl and mydesign-part-2-of-2.stl```

You may also pass the `zip` flag `-z` to zip generated files into one .zip file:

```jscad mydesign.js -z # -- convert mydesign.js into mydesign.zip which contains: mydesign.stl```

```jscad mydesign.js -gp -z # -- convert mydesign.js into mydesign.zip which contains: mydesign-part-1-of-2.stl and mydesign-part-2-of-2.stl```

The '-o' option can be used to control where the output will be placed.
While, the '-of' option can be used to control the format of the output.

Expand Down
61 changes: 54 additions & 7 deletions packages/cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
// jscad name_plate.jscad --name "Just Me" --title "CEO" -o amf test.amf
//
const fs = require('fs')
const JSZip = require('jszip')

const { formats } = require('@jscad/io/formats')

Expand All @@ -33,7 +34,7 @@ const parseArgs = require('./src/parseArgs')

// handle arguments (inputs, outputs, etc)
const args = process.argv.splice(2)
let { inputFile, inputFormat, outputFile, outputFormat, params, addMetaData, inputIsDirectory } = parseArgs(args)
let { inputFile, inputFormat, outputFile, outputFormat, generateParts, zip, params, addMetaData, inputIsDirectory } = parseArgs(args)

// outputs
const output = determineOutputNameAndFormat(outputFormat, outputFile, inputFile)
Expand All @@ -48,18 +49,64 @@ const clicolors = {
black: '\u{1b}[0m'
}

console.log(`${clicolors.blue}JSCAD: generating output ${clicolors.red}
from: ${clicolors.green} ${inputFile} ${clicolors.red}
to: ${clicolors.green} ${outputFile} ${clicolors.yellow}(${formats[outputFormat].description}) ${clicolors.black}
`)
const logFileOutput = (outputFile) => {
console.log(`${clicolors.blue}JSCAD: generating output ${clicolors.red}
from: ${clicolors.green} ${inputFile} ${clicolors.red}
to: ${clicolors.green} ${outputFile} ${clicolors.yellow}(${formats[outputFormat].description}) ${clicolors.black}
`)
}

// read input data
const src = fs.readFileSync(inputFile, inputFile.match(/\.stl$/i) ? 'binary' : 'UTF8')

// -- convert from JSCAD script into the desired output format
// -- and write it to disk
generateOutputData(src, params, { outputFile, outputFormat, inputFile, inputFormat, version, addMetaData, inputIsDirectory })
.then((outputData) => writeOutput(outputFile, outputData))
generateOutputData(src, params, { outputFile, outputFormat, inputFile, inputFormat, generateParts, version, addMetaData, inputIsDirectory })
.then((outputData) => {
if (outputData instanceof Array) {
if (zip) {
const zip = new JSZip()
for (let i = 0; i < outputData.length; i++) {
const filename = outputFile.replace(/\.(\w+)$/, `-part-${i + 1}-of-${outputData.length}.$1`)
zip.file(filename, outputData[i].asBuffer())
}
zip.generateAsync({ type: 'nodebuffer' }).then((content) => {
const zipFilename = outputFile.replace(/\.(\w+)$/, '.zip')
fs.writeFile(zipFilename, content, (err) => {
if (err) {
console.error(err)
} else {
logFileOutput(zipFilename)
}
})
})
} else {
for (let i = 0; i < outputData.length; i++) {
const filename = outputFile.replace(/\.(\w+)$/, `-part-${i + 1}-of-${outputData.length}.$1`)
logFileOutput(filename)
writeOutput(filename, outputData[i])
}
}
} else {
if (zip) {
const zip = new JSZip()
zip.file(outputFile, outputData.asBuffer())
zip.generateAsync({ type: 'nodebuffer' }).then((content) => {
const zipFilename = outputFile.replace(/\.(\w+)$/, '.zip')
fs.writeFile(zipFilename, content, (err) => {
if (err) {
console.error(err)
} else {
logFileOutput(zipFilename)
}
})
})
} else {
logFileOutput(outputFile)
writeOutput(outputFile, outputData)
}
}
})
.catch((error) => {
console.error(error)
process.exit(1)
Expand Down
97 changes: 95 additions & 2 deletions packages/cli/cli.parameters.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const test = require('ava')
const JSZip = require('jszip')

const path = require('path')
const { execSync } = require('child_process')
Expand All @@ -13,6 +14,12 @@ test.afterEach.always((t) => {
try {
if (t.context.outputPath) fs.unlinkSync(t.context.outputPath)
} catch (err) {}
try {
if (t.context.outputPath2) fs.unlinkSync(t.context.outputPath2)
} catch (err) {}
try {
if (t.context.outputPath3) fs.unlinkSync(t.context.outputPath3)
} catch (err) {}

try {
if (t.context.folderPath) fs.rmdirSync(t.context.folderPath, { recursive: false })
Expand All @@ -32,7 +39,7 @@ test.beforeEach((t) => {

// create a simple JSCAD script for input
// the script should produce ALL geometry types
const createJscad = (id) => {
const createJscad = (id, multipart = false) => {
const jscadScript = `// test script ${id}
const { primitives } = require('@jscad/modeling')
Expand All @@ -51,7 +58,7 @@ const main = (params) => {
let ageom2 = primitives.ellipse()
let ageom3 = primitives.ellipsoid()
return [apath2, ageom2, ageom3]
${multipart ? `return [ageom3, ageom3, ageom3]` : `return [apath2, ageom2, ageom3]`}
}
module.exports = { main, getParameterDefinitions }
Expand Down Expand Up @@ -207,3 +214,89 @@ test('cli (single input file, invalid jscad)', (t) => {
execSync(cmd, { stdio: [0, 1, 2] })
})
})


test('cli (single input file, multiple output files)', (t) => {
const testID = 7

const inputPath = createJscad(testID, true)
t.true(fs.existsSync(inputPath))

t.context.inputPath = inputPath

const outputName = (partNum) => `./test${testID}-part-${partNum}-of-3.stl`
const outputPath1 = path.resolve(__dirname, outputName(1))
const outputPath2 = path.resolve(__dirname, outputName(2))
const outputPath3 = path.resolve(__dirname, outputName(3))
t.false(fs.existsSync(outputPath1))
t.false(fs.existsSync(outputPath2))
t.false(fs.existsSync(outputPath3))

t.context.outputPath = outputPath1
t.context.outputPath2 = outputPath2
t.context.outputPath3 = outputPath3

const cliPath = t.context.cliPath

const cmd = `node ${cliPath} ${inputPath} -gp`
execSync(cmd, { stdio: [0,1,2] })
t.true(fs.existsSync(outputPath1))
t.true(fs.existsSync(outputPath2))
t.true(fs.existsSync(outputPath3))
})

test('cli (single multipart input file, zipped output file)', async (t) => {
const testID = 8

const inputPath = createJscad(testID, true)
t.true(fs.existsSync(inputPath))

t.context.inputPath = inputPath

const outputName = `./test${testID}.zip`
const outputPath = path.resolve(__dirname, outputName)

t.false(fs.existsSync(outputPath))

t.context.outputPath = outputPath

const cliPath = t.context.cliPath

const cmd = `node ${cliPath} ${inputPath} -gp -z`
execSync(cmd, { stdio: [0,1,2] })
t.true(fs.existsSync(outputPath))

// check contents of zip file
const data = await fs.promises.readFile(outputPath)
const content = await JSZip.loadAsync(data)
t.true(content.files[path.resolve(__dirname, `./test${testID}-part-1-of-3.stl`)] !== undefined)
t.true(content.files[path.resolve(__dirname, `./test${testID}-part-2-of-3.stl`)] !== undefined)
t.true(content.files[path.resolve(__dirname, `./test${testID}-part-3-of-3.stl`)] !== undefined)
})

test('cli (single input file, zipped output file)', async (t) => {
const testID = 9

const inputPath = createJscad(testID, true)
t.true(fs.existsSync(inputPath))

t.context.inputPath = inputPath

const outputName = `./test${testID}.zip`
const outputPath = path.resolve(__dirname, outputName)

t.false(fs.existsSync(outputPath))

t.context.outputPath = outputPath

const cliPath = t.context.cliPath

const cmd = `node ${cliPath} ${inputPath} -z`
execSync(cmd, { stdio: [0,1,2] })
t.true(fs.existsSync(outputPath))

// check contents of zip file
const data = await fs.promises.readFile(outputPath)
const content = await JSZip.loadAsync(data)
t.true(content.files[path.resolve(__dirname, `./test${testID}.stl`)] !== undefined)
})
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"@jscad/array-utils": "2.1.4",
"@jscad/core": "2.6.6",
"@jscad/io": "2.4.5",
"@jscad/modeling": "2.11.1"
"@jscad/modeling": "2.11.1",
"jszip": "^3.10.1"
},
"devDependencies": {
"ava": "3.15.0",
Expand Down
12 changes: 10 additions & 2 deletions packages/cli/src/generateOutputData.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ const generateOutputData = (source, params, options) => {
outputFormat: 'stl',
inputFile: '',
version: '',
addMetaData: true
addMetaData: true,
generateParts: false
}
options = Object.assign({}, defaults, options)
const { outputFormat, inputFile, inputFormat } = options
const { outputFormat, inputFile, inputFormat, generateParts } = options

options.filename = inputFile // for deserializers

Expand Down Expand Up @@ -62,6 +63,13 @@ const generateOutputData = (source, params, options) => {
})
.then((solids) => {
const serializerOptions = Object.assign({ format: outputFormat }, params)
if (generateParts) {
let blobs = []
for (let i = 0; i < solids.length; i++) {
blobs.push(solidsAsBlob(solids[i], serializerOptions))
}
return blobs
}
return solidsAsBlob(solids, serializerOptions)
})
}
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/parseArgs.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const parseArgs = (args) => {
let inputFormat
let outputFile
let outputFormat
let generateParts = false
let zip = false
const params = {} // parameters to feed the script if applicable
let addMetaData = false // wether to add metadata to outputs or not : ie version info, timestamp etc
let inputIsDirectory = false // did we pass in a folder or a file ?
Expand All @@ -41,6 +43,10 @@ const parseArgs = (args) => {
for (let i = 0; i < args.length; i++) {
if (args[i] === '-of') { // -of <format>
outputFormat = args[++i]
} else if (args[i] === '-gp') {
generateParts = true
} else if (args[i] === '-z') {
zip = true
} else if (args[i].match(/^-o(\S.+)/)) { // -o<output>
outputFile = args[i]
outputFile = outputFile.replace(/^-o(\S+)$/, '$1')
Expand Down Expand Up @@ -92,6 +98,8 @@ const parseArgs = (args) => {
inputFormat,
outputFile,
outputFormat,
generateParts,
zip,
params,
addMetaData,
inputIsDirectory
Expand Down

0 comments on commit a20b9cf

Please sign in to comment.