@jscad/cli 2.2.26 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [2.3.0](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/cli@2.2.26...@jscad/cli@2.3.0) (2023-06-27)
7
+
8
+
9
+ ### Features
10
+
11
+ * **cli:** added options to output designs as individual parts, and create zip archive of parts ([a20b9cf](https://github.com/jscad/OpenJSCAD.org/commit/a20b9cf0edaaeca051427372d237af4c3522b04c))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [2.2.26](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/cli@2.2.25...@jscad/cli@2.2.26) (2023-04-30)
7
18
 
8
19
 
package/README.md CHANGED
@@ -73,6 +73,16 @@ Examples:
73
73
 
74
74
  ```jscad mydesign.js -of amf # -- convert mydesign.js into mydesign.amf```
75
75
 
76
+ For multi-part models, you can pass the `generateParts` flag `-gp` to output each part as a separate, numbered file:
77
+
78
+ ```jscad mydesign.js -gp # -- convert mydesign.js into mydesign-part-1-of-2.stl and mydesign-part-2-of-2.stl```
79
+
80
+ You may also pass the `zip` flag `-z` to zip generated files into one .zip file:
81
+
82
+ ```jscad mydesign.js -z # -- convert mydesign.js into mydesign.zip which contains: mydesign.stl```
83
+
84
+ ```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```
85
+
76
86
  The '-o' option can be used to control where the output will be placed.
77
87
  While, the '-of' option can be used to control the format of the output.
78
88
 
package/cli.js CHANGED
@@ -22,6 +22,7 @@
22
22
  // jscad name_plate.jscad --name "Just Me" --title "CEO" -o amf test.amf
23
23
  //
24
24
  const fs = require('fs')
25
+ const JSZip = require('jszip')
25
26
 
26
27
  const { formats } = require('@jscad/io/formats')
27
28
 
@@ -33,7 +34,7 @@ const parseArgs = require('./src/parseArgs')
33
34
 
34
35
  // handle arguments (inputs, outputs, etc)
35
36
  const args = process.argv.splice(2)
36
- let { inputFile, inputFormat, outputFile, outputFormat, params, addMetaData, inputIsDirectory } = parseArgs(args)
37
+ let { inputFile, inputFormat, outputFile, outputFormat, generateParts, zip, params, addMetaData, inputIsDirectory } = parseArgs(args)
37
38
 
38
39
  // outputs
39
40
  const output = determineOutputNameAndFormat(outputFormat, outputFile, inputFile)
@@ -48,18 +49,64 @@ const clicolors = {
48
49
  black: '\u{1b}[0m'
49
50
  }
50
51
 
51
- console.log(`${clicolors.blue}JSCAD: generating output ${clicolors.red}
52
- from: ${clicolors.green} ${inputFile} ${clicolors.red}
53
- to: ${clicolors.green} ${outputFile} ${clicolors.yellow}(${formats[outputFormat].description}) ${clicolors.black}
54
- `)
52
+ const logFileOutput = (outputFile) => {
53
+ console.log(`${clicolors.blue}JSCAD: generating output ${clicolors.red}
54
+ from: ${clicolors.green} ${inputFile} ${clicolors.red}
55
+ to: ${clicolors.green} ${outputFile} ${clicolors.yellow}(${formats[outputFormat].description}) ${clicolors.black}
56
+ `)
57
+ }
55
58
 
56
59
  // read input data
57
60
  const src = fs.readFileSync(inputFile, inputFile.match(/\.stl$/i) ? 'binary' : 'UTF8')
58
61
 
59
62
  // -- convert from JSCAD script into the desired output format
60
63
  // -- and write it to disk
61
- generateOutputData(src, params, { outputFile, outputFormat, inputFile, inputFormat, version, addMetaData, inputIsDirectory })
62
- .then((outputData) => writeOutput(outputFile, outputData))
64
+ generateOutputData(src, params, { outputFile, outputFormat, inputFile, inputFormat, generateParts, version, addMetaData, inputIsDirectory })
65
+ .then((outputData) => {
66
+ if (outputData instanceof Array) {
67
+ if (zip) {
68
+ const zip = new JSZip()
69
+ for (let i = 0; i < outputData.length; i++) {
70
+ const filename = outputFile.replace(/\.(\w+)$/, `-part-${i + 1}-of-${outputData.length}.$1`)
71
+ zip.file(filename, outputData[i].asBuffer())
72
+ }
73
+ zip.generateAsync({ type: 'nodebuffer' }).then((content) => {
74
+ const zipFilename = outputFile.replace(/\.(\w+)$/, '.zip')
75
+ fs.writeFile(zipFilename, content, (err) => {
76
+ if (err) {
77
+ console.error(err)
78
+ } else {
79
+ logFileOutput(zipFilename)
80
+ }
81
+ })
82
+ })
83
+ } else {
84
+ for (let i = 0; i < outputData.length; i++) {
85
+ const filename = outputFile.replace(/\.(\w+)$/, `-part-${i + 1}-of-${outputData.length}.$1`)
86
+ logFileOutput(filename)
87
+ writeOutput(filename, outputData[i])
88
+ }
89
+ }
90
+ } else {
91
+ if (zip) {
92
+ const zip = new JSZip()
93
+ zip.file(outputFile, outputData.asBuffer())
94
+ zip.generateAsync({ type: 'nodebuffer' }).then((content) => {
95
+ const zipFilename = outputFile.replace(/\.(\w+)$/, '.zip')
96
+ fs.writeFile(zipFilename, content, (err) => {
97
+ if (err) {
98
+ console.error(err)
99
+ } else {
100
+ logFileOutput(zipFilename)
101
+ }
102
+ })
103
+ })
104
+ } else {
105
+ logFileOutput(outputFile)
106
+ writeOutput(outputFile, outputData)
107
+ }
108
+ }
109
+ })
63
110
  .catch((error) => {
64
111
  console.error(error)
65
112
  process.exit(1)
@@ -1,4 +1,5 @@
1
1
  const test = require('ava')
2
+ const JSZip = require('jszip')
2
3
 
3
4
  const path = require('path')
4
5
  const { execSync } = require('child_process')
@@ -13,6 +14,12 @@ test.afterEach.always((t) => {
13
14
  try {
14
15
  if (t.context.outputPath) fs.unlinkSync(t.context.outputPath)
15
16
  } catch (err) {}
17
+ try {
18
+ if (t.context.outputPath2) fs.unlinkSync(t.context.outputPath2)
19
+ } catch (err) {}
20
+ try {
21
+ if (t.context.outputPath3) fs.unlinkSync(t.context.outputPath3)
22
+ } catch (err) {}
16
23
 
17
24
  try {
18
25
  if (t.context.folderPath) fs.rmdirSync(t.context.folderPath, { recursive: false })
@@ -32,7 +39,7 @@ test.beforeEach((t) => {
32
39
 
33
40
  // create a simple JSCAD script for input
34
41
  // the script should produce ALL geometry types
35
- const createJscad = (id) => {
42
+ const createJscad = (id, multipart = false) => {
36
43
  const jscadScript = `// test script ${id}
37
44
  const { primitives } = require('@jscad/modeling')
38
45
 
@@ -51,7 +58,7 @@ const main = (params) => {
51
58
  let ageom2 = primitives.ellipse()
52
59
  let ageom3 = primitives.ellipsoid()
53
60
 
54
- return [apath2, ageom2, ageom3]
61
+ ${multipart ? `return [ageom3, ageom3, ageom3]` : `return [apath2, ageom2, ageom3]`}
55
62
  }
56
63
 
57
64
  module.exports = { main, getParameterDefinitions }
@@ -207,3 +214,89 @@ test('cli (single input file, invalid jscad)', (t) => {
207
214
  execSync(cmd, { stdio: [0, 1, 2] })
208
215
  })
209
216
  })
217
+
218
+
219
+ test('cli (single input file, multiple output files)', (t) => {
220
+ const testID = 7
221
+
222
+ const inputPath = createJscad(testID, true)
223
+ t.true(fs.existsSync(inputPath))
224
+
225
+ t.context.inputPath = inputPath
226
+
227
+ const outputName = (partNum) => `./test${testID}-part-${partNum}-of-3.stl`
228
+ const outputPath1 = path.resolve(__dirname, outputName(1))
229
+ const outputPath2 = path.resolve(__dirname, outputName(2))
230
+ const outputPath3 = path.resolve(__dirname, outputName(3))
231
+ t.false(fs.existsSync(outputPath1))
232
+ t.false(fs.existsSync(outputPath2))
233
+ t.false(fs.existsSync(outputPath3))
234
+
235
+ t.context.outputPath = outputPath1
236
+ t.context.outputPath2 = outputPath2
237
+ t.context.outputPath3 = outputPath3
238
+
239
+ const cliPath = t.context.cliPath
240
+
241
+ const cmd = `node ${cliPath} ${inputPath} -gp`
242
+ execSync(cmd, { stdio: [0,1,2] })
243
+ t.true(fs.existsSync(outputPath1))
244
+ t.true(fs.existsSync(outputPath2))
245
+ t.true(fs.existsSync(outputPath3))
246
+ })
247
+
248
+ test('cli (single multipart input file, zipped output file)', async (t) => {
249
+ const testID = 8
250
+
251
+ const inputPath = createJscad(testID, true)
252
+ t.true(fs.existsSync(inputPath))
253
+
254
+ t.context.inputPath = inputPath
255
+
256
+ const outputName = `./test${testID}.zip`
257
+ const outputPath = path.resolve(__dirname, outputName)
258
+
259
+ t.false(fs.existsSync(outputPath))
260
+
261
+ t.context.outputPath = outputPath
262
+
263
+ const cliPath = t.context.cliPath
264
+
265
+ const cmd = `node ${cliPath} ${inputPath} -gp -z`
266
+ execSync(cmd, { stdio: [0,1,2] })
267
+ t.true(fs.existsSync(outputPath))
268
+
269
+ // check contents of zip file
270
+ const data = await fs.promises.readFile(outputPath)
271
+ const content = await JSZip.loadAsync(data)
272
+ t.true(content.files[path.resolve(__dirname, `./test${testID}-part-1-of-3.stl`)] !== undefined)
273
+ t.true(content.files[path.resolve(__dirname, `./test${testID}-part-2-of-3.stl`)] !== undefined)
274
+ t.true(content.files[path.resolve(__dirname, `./test${testID}-part-3-of-3.stl`)] !== undefined)
275
+ })
276
+
277
+ test('cli (single input file, zipped output file)', async (t) => {
278
+ const testID = 9
279
+
280
+ const inputPath = createJscad(testID, true)
281
+ t.true(fs.existsSync(inputPath))
282
+
283
+ t.context.inputPath = inputPath
284
+
285
+ const outputName = `./test${testID}.zip`
286
+ const outputPath = path.resolve(__dirname, outputName)
287
+
288
+ t.false(fs.existsSync(outputPath))
289
+
290
+ t.context.outputPath = outputPath
291
+
292
+ const cliPath = t.context.cliPath
293
+
294
+ const cmd = `node ${cliPath} ${inputPath} -z`
295
+ execSync(cmd, { stdio: [0,1,2] })
296
+ t.true(fs.existsSync(outputPath))
297
+
298
+ // check contents of zip file
299
+ const data = await fs.promises.readFile(outputPath)
300
+ const content = await JSZip.loadAsync(data)
301
+ t.true(content.files[path.resolve(__dirname, `./test${testID}.stl`)] !== undefined)
302
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jscad/cli",
3
- "version": "2.2.26",
3
+ "version": "2.3.0",
4
4
  "description": "Command Line Interface (CLI) for JSCAD",
5
5
  "homepage": "https://openjscad.xyz/",
6
6
  "repository": "https://github.com/jscad/OpenJSCAD.org",
@@ -36,9 +36,10 @@
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
38
  "@jscad/array-utils": "2.1.4",
39
- "@jscad/core": "2.6.6",
40
- "@jscad/io": "2.4.5",
41
- "@jscad/modeling": "2.11.1"
39
+ "@jscad/core": "2.6.7",
40
+ "@jscad/io": "2.4.6",
41
+ "@jscad/modeling": "2.12.0",
42
+ "jszip": "^3.10.1"
42
43
  },
43
44
  "devDependencies": {
44
45
  "ava": "3.15.0",
@@ -49,5 +50,5 @@
49
50
  "url": "https://opencollective.com/openjscad",
50
51
  "logo": "https://opencollective.com/openjscad/logo.txt"
51
52
  },
52
- "gitHead": "4313974b50957018d2edd010e3a251f59bea46a4"
53
+ "gitHead": "e269f212db5a00cda740d2f7ad3e5206d1eb839f"
53
54
  }
@@ -19,10 +19,11 @@ const generateOutputData = (source, params, options) => {
19
19
  outputFormat: 'stl',
20
20
  inputFile: '',
21
21
  version: '',
22
- addMetaData: true
22
+ addMetaData: true,
23
+ generateParts: false
23
24
  }
24
25
  options = Object.assign({}, defaults, options)
25
- const { outputFormat, inputFile, inputFormat } = options
26
+ const { outputFormat, inputFile, inputFormat, generateParts } = options
26
27
 
27
28
  options.filename = inputFile // for deserializers
28
29
 
@@ -62,6 +63,13 @@ const generateOutputData = (source, params, options) => {
62
63
  })
63
64
  .then((solids) => {
64
65
  const serializerOptions = Object.assign({ format: outputFormat }, params)
66
+ if (generateParts) {
67
+ let blobs = []
68
+ for (let i = 0; i < solids.length; i++) {
69
+ blobs.push(solidsAsBlob(solids[i], serializerOptions))
70
+ }
71
+ return blobs
72
+ }
65
73
  return solidsAsBlob(solids, serializerOptions)
66
74
  })
67
75
  }
package/src/parseArgs.js CHANGED
@@ -24,6 +24,8 @@ const parseArgs = (args) => {
24
24
  let inputFormat
25
25
  let outputFile
26
26
  let outputFormat
27
+ let generateParts = false
28
+ let zip = false
27
29
  const params = {} // parameters to feed the script if applicable
28
30
  let addMetaData = false // wether to add metadata to outputs or not : ie version info, timestamp etc
29
31
  let inputIsDirectory = false // did we pass in a folder or a file ?
@@ -41,6 +43,10 @@ const parseArgs = (args) => {
41
43
  for (let i = 0; i < args.length; i++) {
42
44
  if (args[i] === '-of') { // -of <format>
43
45
  outputFormat = args[++i]
46
+ } else if (args[i] === '-gp') {
47
+ generateParts = true
48
+ } else if (args[i] === '-z') {
49
+ zip = true
44
50
  } else if (args[i].match(/^-o(\S.+)/)) { // -o<output>
45
51
  outputFile = args[i]
46
52
  outputFile = outputFile.replace(/^-o(\S+)$/, '$1')
@@ -92,6 +98,8 @@ const parseArgs = (args) => {
92
98
  inputFormat,
93
99
  outputFile,
94
100
  outputFormat,
101
+ generateParts,
102
+ zip,
95
103
  params,
96
104
  addMetaData,
97
105
  inputIsDirectory