diff --git a/packages/cli/cli.conversions.test.js b/packages/cli/cli.conversions.test.js index dfcc4b3c2..271882d74 100644 --- a/packages/cli/cli.conversions.test.js +++ b/packages/cli/cli.conversions.test.js @@ -8,19 +8,23 @@ test.afterEach.always((t) => { // remove files try { if (t.context.file1Path) fs.unlinkSync(t.context.file1Path) - } catch (err) {} + } catch (err) { } try { if (t.context.file2Path) fs.unlinkSync(t.context.file2Path) - } catch (err) {} + } catch (err) { } try { if (t.context.file3Path) fs.unlinkSync(t.context.file3Path) - } catch (err) {} + } catch (err) { } try { if (t.context.file4Path) fs.unlinkSync(t.context.file4Path) - } catch (err) {} + } catch (err) { } + + try { + if (t.context.file5Path) fs.unlinkSync(t.context.file5Path) + } catch (err) { } }) test.beforeEach((t) => { @@ -68,6 +72,57 @@ module.exports = { main, getParameterDefinitions } return filePath } +const createImportJscad = (id, extension) => { + const jscadScript = `// test script ${id} -- import + +const main = () => { + return require('./test${id}.${extension}') +} + +module.exports = { main } +` + + const fileName = `./test${id}-import.jscad`; + const filePath = path.resolve(__dirname, fileName); + fs.writeFileSync(filePath, jscadScript); + return filePath; +} + +const testBackImport = (t, testID, extension) => { + const cliPath = t.context.cliPath; + + const file4Path = createImportJscad(testID, extension); + t.context.file4Path = file4Path; + t.true(fs.existsSync(file4Path)); + + const file5Name = `./test${testID}-import.stl`; + const file5Path = path.resolve(__dirname, file5Name); + t.context.file5Path = file5Path; + t.false(fs.existsSync(file5Path)); + + cmd = `node ${cliPath} ${file4Path}` + execSync(cmd, { stdio: [0, 1, 2] }) + t.true(fs.existsSync(file5Path)); +} + +const runOnFixture = (t, fixtureName) => { + const inputFile = path.resolve( + __dirname, + path.join('test_fixtures', fixtureName, 'index.js')); + + const outputFile = inputFile.replace('.js', '.stl'); + t.context.file1Path = outputFile; + + t.false(fs.existsSync(outputFile)); + + const cmd = `node ${t.context.cliPath} ${inputFile}` + execSync(cmd, { stdio: [0, 1, 2] }); + t.true(fs.existsSync(outputFile)) + + return outputFile; +} + + test('cli (conversions STL)', (t) => { const testID = 11 @@ -87,18 +142,20 @@ test('cli (conversions STL)', (t) => { let cmd = `node ${cliPath} ${file1Path}` execSync(cmd, { stdio: [0, 1, 2] }) - t.true(fs.existsSync(file2Path)) + t.true(fs.existsSync(file2Path)); // convert from STL to JSCAD script const file3Name = `./test${testID}.js` const file3Path = path.resolve(__dirname, file3Name) - t.false(fs.existsSync(file3Path)) + t.false(fs.existsSync(file3Path)); t.context.file3Path = file3Path cmd = `node ${cliPath} ${file2Path} -o ${file3Path} -v -add-metadata false` execSync(cmd, { stdio: [0, 1, 2] }) - t.true(fs.existsSync(file3Path)) + t.true(fs.existsSync(file3Path)); + + testBackImport(t, testID, 'stl'); }) test('cli (conversions DXF)', (t) => { @@ -132,6 +189,8 @@ test('cli (conversions DXF)', (t) => { cmd = `node ${cliPath} ${file2Path} -of js` execSync(cmd, { stdio: [0, 1, 2] }) t.true(fs.existsSync(file3Path)) + + testBackImport(t, testID, 'dxf'); }) test('cli (conversions AMF)', (t) => { @@ -165,6 +224,8 @@ test('cli (conversions AMF)', (t) => { cmd = `node ${cliPath} ${file2Path} -of js` execSync(cmd, { stdio: [0, 1, 2] }) t.true(fs.existsSync(file3Path)) + + testBackImport(t, testID, 'amf'); }) test('cli (conversions JSON)', (t) => { @@ -198,6 +259,8 @@ test('cli (conversions JSON)', (t) => { cmd = `node ${cliPath} ${file2Path} -of js` execSync(cmd, { stdio: [0, 1, 2] }) t.true(fs.existsSync(file3Path)) + + testBackImport(t, testID, 'json'); }) test('cli (conversions SVG)', (t) => { @@ -264,4 +327,12 @@ test('cli (conversions X3D)', (t) => { cmd = `node ${cliPath} ${file2Path} -of js` execSync(cmd, { stdio: [0, 1, 2] }) t.true(fs.existsSync(file3Path)) + + testBackImport(t, testID, 'x3d'); +}) + +test('cli (import STL)', (t) => { + const testID = 17 + + runOnFixture(t, 'stl_import') }) diff --git a/packages/cli/test_fixtures/stl_import/binary_stl.stl b/packages/cli/test_fixtures/stl_import/binary_stl.stl new file mode 100644 index 000000000..62cb2f2a3 Binary files /dev/null and b/packages/cli/test_fixtures/stl_import/binary_stl.stl differ diff --git a/packages/cli/test_fixtures/stl_import/index.js b/packages/cli/test_fixtures/stl_import/index.js new file mode 100644 index 000000000..457a15ddc --- /dev/null +++ b/packages/cli/test_fixtures/stl_import/index.js @@ -0,0 +1,6 @@ + +function main() { + return require('./binary_stl.stl'); +} + +module.exports = { main } \ No newline at end of file diff --git a/packages/core/src/io/registerExtensions.js b/packages/core/src/io/registerExtensions.js index f6993ccad..301fa3c8e 100644 --- a/packages/core/src/io/registerExtensions.js +++ b/packages/core/src/io/registerExtensions.js @@ -16,8 +16,17 @@ const registerDeserializer = (extension, fs, _require) => { const deserializer = deserializers[extension] const fileExtension = '.' + extension _require.extensions[fileExtension] = (module, filename) => { - const content = fs.readFileSync(filename, 'utf8') - const parsed = deserializer({ filename, output: 'geometry' }, content) + const fileReadResult = fs.readFileSync(filename); + + // https://nodejs.org/api/buffer.html#bufbuffer: Buffer.buffer is not + // guaranteed to correspond exactly to the original Buffer. + const fileContent = fileReadResult.buffer + ? fileReadResult.buffer.slice( + fileReadResult.byteOffset, + fileReadResult.byteOffset + fileReadResult.length) + : fileReadResult; + + const parsed = deserializer({ filename, output: 'geometry' }, fileContent) module.exports = parsed } } diff --git a/packages/io/amf-deserializer/package.json b/packages/io/amf-deserializer/package.json index 98630c3dd..fe75c41b5 100644 --- a/packages/io/amf-deserializer/package.json +++ b/packages/io/amf-deserializer/package.json @@ -32,6 +32,7 @@ "license": "MIT", "dependencies": { "@jscad/modeling": "2.12.3", + "@jscad/io-utils": "2.0.28", "saxes": "5.0.1" }, "devDependencies": { diff --git a/packages/io/amf-deserializer/src/index.js b/packages/io/amf-deserializer/src/index.js index 351be483a..3db063927 100644 --- a/packages/io/amf-deserializer/src/index.js +++ b/packages/io/amf-deserializer/src/index.js @@ -22,6 +22,8 @@ All code released under MIT license * const { deserializer, extension } = require('@jscad/amf-serializer') */ +const { ensureString } = require('@jscad/io-utils') + const version = require('../package.json').version const translate = require('./translate') const instantiate = require('./deserialize') @@ -48,6 +50,7 @@ const deserialize = (options, input) => { } options = Object.assign({}, defaults, options) + input = ensureString(input); return options.output === 'script' ? translate(options, input) : instantiate(options, input) } diff --git a/packages/io/dxf-deserializer/index.js b/packages/io/dxf-deserializer/index.js index 31d98e040..28be06f5d 100644 --- a/packages/io/dxf-deserializer/index.js +++ b/packages/io/dxf-deserializer/index.js @@ -7,7 +7,10 @@ All code released under MIT license */ +const { ensureString } = require('@jscad/io-utils') + const version = require('./package.json').version + const { BYLAYER, getTLA } = require('./autocad') const colorIndex = require('./colorindex2017') const dxf = require('./DxfReader') @@ -275,7 +278,7 @@ const handleXcoord = (reader, group, value) => { const obj = reader.objstack.pop() if ('type' in obj) { if (obj.type === 'lwpolyline') { - // special handling to build a list of vertices + // special handling to build a list of vertices if (obj.pptxs === undefined) { obj.pptxs = [] obj.bulgs = [] @@ -284,7 +287,7 @@ const handleXcoord = (reader, group, value) => { obj.bulgs.push(0) } else { if (obj.type === 'mesh') { - // special handling to build a list of vertices + // special handling to build a list of vertices if (obj.pptxs === undefined) { obj.pptxs = [] } @@ -307,7 +310,7 @@ const handleYcoord = (reader, group, value) => { const obj = reader.objstack.pop() if ('type' in obj) { if (obj.type === 'lwpolyline' || obj.type === 'mesh') { - // special handling to build a list of vertices + // special handling to build a list of vertices if (obj.pptys === undefined) { obj.pptys = [] } @@ -329,7 +332,7 @@ const handleZcoord = (reader, group, value) => { const obj = reader.objstack.pop() if ('type' in obj) { if (obj.type === 'mesh') { - // special handling to build a list of vertices + // special handling to build a list of vertices if (obj.pptzs === undefined) { obj.pptzs = [] } @@ -351,7 +354,7 @@ const handleBulge = (reader, group, value) => { const obj = reader.objstack.pop() if ('type' in obj) { if (obj.type === 'lwpolyline') { - // special handling to build a list of vertices + // special handling to build a list of vertices const bulgs = obj.bulgs if (bulgs !== undefined) { const pptxs = obj.pptxs @@ -376,7 +379,7 @@ const handleLen = (reader, group, value) => { const obj = reader.objstack.pop() if ('type' in obj) { if (obj.type === 'mesh') { - // mesh has an order of lengths + // mesh has an order of lengths const state = obj.state // console.log('mesh len: '+group+','+value+','+state) switch (group) { @@ -600,6 +603,8 @@ const deserialize = (options, src) => { } } options = Object.assign({}, defaults, options) + + src = ensureString(src); return options.output === 'script' ? translate(src, options) : instantiate(src, options) } diff --git a/packages/io/dxf-deserializer/package.json b/packages/io/dxf-deserializer/package.json index 48c490489..4f1e516bb 100644 --- a/packages/io/dxf-deserializer/package.json +++ b/packages/io/dxf-deserializer/package.json @@ -28,7 +28,8 @@ ], "license": "MIT", "dependencies": { - "@jscad/modeling": "2.12.3" + "@jscad/modeling": "2.12.3", + "@jscad/io-utils": "2.0.28" }, "devDependencies": { "ava": "3.15.0", diff --git a/packages/io/io-utils/ensureString.js b/packages/io/io-utils/ensureString.js new file mode 100644 index 000000000..f15eae94e --- /dev/null +++ b/packages/io/io-utils/ensureString.js @@ -0,0 +1,9 @@ +const ensureString = (stringOrArrayBuffer, defaultBinaryEncoding = 'utf-8') => { + if (typeof (stringOrArrayBuffer) === 'string') { + return stringOrArrayBuffer; + } + + return new TextDecoder(defaultBinaryEncoding).decode(new Uint8Array(stringOrArrayBuffer)); +} + +module.exports = ensureString; \ No newline at end of file diff --git a/packages/io/io-utils/index.js b/packages/io/io-utils/index.js index 13dee02d0..d307c5762 100644 --- a/packages/io/io-utils/index.js +++ b/packages/io/io-utils/index.js @@ -8,5 +8,6 @@ module.exports = { convertToBlob: require('./convertToBlob'), makeBlob: require('./makeBlob'), BinaryReader: require('./BinaryReader'), - Blob: require('./Blob') + Blob: require('./Blob'), + ensureString: require('./ensureString') } diff --git a/packages/io/json-deserializer/index.js b/packages/io/json-deserializer/index.js index 7ac3676b6..dd6f20ff8 100644 --- a/packages/io/json-deserializer/index.js +++ b/packages/io/json-deserializer/index.js @@ -21,6 +21,7 @@ All code released under MIT license */ const { flatten, toArray } = require('@jscad/array-utils') +const { ensureString } = require('@jscad/io-utils') const version = require('./package.json').version @@ -45,6 +46,7 @@ const deserialize = (options, input) => { options = Object.assign({}, defaults, options) // convert the JSON notation into anonymous object(s) + input = ensureString(input); let objects = JSON.parse(input) // cleanup the objects @@ -69,7 +71,7 @@ const translate = (options, objects) => { : '' script += -` + ` const { geometries } = require('@jscad/modeling') const main = () => { diff --git a/packages/io/json-deserializer/package.json b/packages/io/json-deserializer/package.json index 7a8cd0c54..4c1048a5f 100644 --- a/packages/io/json-deserializer/package.json +++ b/packages/io/json-deserializer/package.json @@ -28,7 +28,8 @@ ], "license": "MIT", "dependencies": { - "@jscad/array-utils": "2.1.4" + "@jscad/array-utils": "2.1.4", + "@jscad/io-utils": "2.0.28" }, "devDependencies": { "@jscad/modeling": "2.12.3", diff --git a/packages/io/obj-deserializer/index.js b/packages/io/obj-deserializer/index.js index a0e455b75..11ab47cee 100644 --- a/packages/io/obj-deserializer/index.js +++ b/packages/io/obj-deserializer/index.js @@ -1,4 +1,5 @@ const { colors, primitives } = require('@jscad/modeling') +const { ensureString } = require('@jscad/io-utils') const version = require('./package.json').version @@ -32,6 +33,8 @@ const deserialize = (options, input) => { options = Object.assign({}, defaults, options) const { output } = options + input = ensureString(input); + options && options.statusCallback && options.statusCallback({ progress: 0 }) const { positions, groups } = getGroups(input, options) diff --git a/packages/io/obj-deserializer/package.json b/packages/io/obj-deserializer/package.json index 92d3cb18b..957875fda 100644 --- a/packages/io/obj-deserializer/package.json +++ b/packages/io/obj-deserializer/package.json @@ -32,7 +32,8 @@ ], "license": "MIT", "dependencies": { - "@jscad/modeling": "2.12.3" + "@jscad/modeling": "2.12.3", + "@jscad/io-utils": "2.0.28" }, "devDependencies": { "ava": "3.15.0", diff --git a/packages/io/svg-deserializer/package.json b/packages/io/svg-deserializer/package.json index f8da9af66..c0a0eec85 100644 --- a/packages/io/svg-deserializer/package.json +++ b/packages/io/svg-deserializer/package.json @@ -34,6 +34,7 @@ "dependencies": { "@jscad/array-utils": "2.1.4", "@jscad/modeling": "2.12.3", + "@jscad/io-utils": "2.0.28", "saxes": "5.0.1" }, "devDependencies": { diff --git a/packages/io/svg-deserializer/src/index.js b/packages/io/svg-deserializer/src/index.js index 2161044e5..879d6f6f8 100644 --- a/packages/io/svg-deserializer/src/index.js +++ b/packages/io/svg-deserializer/src/index.js @@ -14,6 +14,7 @@ const saxes = require('saxes') const { colors, transforms } = require('@jscad/modeling') const { toArray } = require('@jscad/array-utils') +const { ensureString } = require('@jscad/io-utils') const version = require('../package.json').version @@ -22,6 +23,7 @@ const { svgSvg, svgRect, svgCircle, svgGroup, svgLine, svgPath, svgEllipse, svgP const shapesMapGeometry = require('./shapesMapGeometry') const shapesMapJscad = require('./shapesMapJscad') + /** * Deserializer of SVG source data to JSCAD geometries. * @see {@link https://github.com/jscad/OpenJSCAD.org/blob/master/packages/io/svg-deserializer/README.md|README} for supported conversion of SVG elements. @@ -58,6 +60,7 @@ const deserialize = (options, input) => { version } options = Object.assign({}, defaults, options) + input = ensureString(input); return options.output === 'script' ? translate(input, options) : instantiate(input, options) } @@ -395,7 +398,7 @@ const createSvgParser = (src, pxPmm) => { if (svgGroups.length > 0) { const group = svgGroups.pop() if ('objects' in group) { - // TBD apply presentation attributes from the group + // TBD apply presentation attributes from the group group.objects.push(obj) } svgGroups.push(group) @@ -422,7 +425,7 @@ const createSvgParser = (src, pxPmm) => { DEFS: () => { svgInDefs = false }, USE: popGroup, G: popGroup, - undefined: () => {} + undefined: () => { } } const elementName = node.name.toUpperCase() const obj = objMap[elementName] ? objMap[elementName]() : undefined diff --git a/packages/io/x3d-deserializer/package.json b/packages/io/x3d-deserializer/package.json index 09188d5cd..1f5afe9ea 100644 --- a/packages/io/x3d-deserializer/package.json +++ b/packages/io/x3d-deserializer/package.json @@ -28,6 +28,7 @@ "dependencies": { "@jscad/array-utils": "2.1.4", "@jscad/modeling": "2.12.3", + "@jscad/io-utils": "2.0.28", "saxes": "5.0.1" }, "devDependencies": { diff --git a/packages/io/x3d-deserializer/src/index.js b/packages/io/x3d-deserializer/src/index.js index 622c7d195..4413cf749 100644 --- a/packages/io/x3d-deserializer/src/index.js +++ b/packages/io/x3d-deserializer/src/index.js @@ -20,12 +20,14 @@ All code released under MIT license * @example * const { deserializer, extension } = require('@jscad/x3d-deserializer') */ +const { ensureString } = require('@jscad/io-utils') const version = require('../package.json').version const translate = require('./translate') const instantiate = require('./instantiate') + /** * Deserialize the given X3D source (XML Encoding) into either a script or an array of geometry * @see {@link https://www.web3d.org/documents/specifications/19776-1/V3.3/index.html|X3D File Format} @@ -48,7 +50,7 @@ const deserialize = (options, input) => { addMetaData: true } options = Object.assign({}, defaults, options) - + input = ensureString(input); return options.output === 'script' ? translate(options, input) : instantiate(options, input) }