diff --git a/CHANGES.md b/CHANGES.md index 74a16fae..5b81896a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Change Log * Added ability to use the first material in the mtl file when the obj is missing `usemtl`. [#163](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/163) * Fixed loading faces that contain less than 3 vertices. [#163](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/163) * Fixed loading mtllib paths that contain spaces. [#163](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/163) +* Attempt to load missing materials and textures from within the same directory as the obj. [#163](https://github.com/AnalyticalGraphicsInc/obj2gltf/pull/163) ### 1.3.4 2018-10-16 diff --git a/lib/loadObj.js b/lib/loadObj.js index 441504cb..00036876 100644 --- a/lib/loadObj.js +++ b/lib/loadObj.js @@ -463,13 +463,11 @@ function normalizeMtlPath(mtlPath, objDirectory) { return path.normalize(path.join(objDirectory, mtlPath)); } -function outsideDirectory(filePath, objPath) { - return (path.relative(path.dirname(objPath), filePath).indexOf('..') === 0); +function outsideDirectory(file, directory) { + return (path.relative(directory, file).indexOf('..') === 0); } function loadMaterials(mtlPaths, objPath, options) { - var secure = options.secure; - var logger = options.logger; var objDirectory = path.dirname(objPath); var materials = {}; @@ -480,39 +478,69 @@ function loadMaterials(mtlPaths, objPath, options) { return Promise.map(mtlPaths, function(mtlPath) { mtlPath = normalizeMtlPath(mtlPath, objDirectory); - if (secure && outsideDirectory(mtlPath, objPath)) { - logger('Could not read mtl file at ' + mtlPath + ' because it is outside of the obj directory and the secure flag is true. Using default material instead.'); - return; + var shallowPath = path.join(objDirectory, path.basename(mtlPath)); + if (options.secure && outsideDirectory(mtlPath, objPath)) { + // Try looking for the .mtl in the same directory as the obj + optionslogger('The material file is outside of the obj directory and the secure flag is true. Attempting to read the material file from within the obj directory instead.'); + return loadMtl(shallowPath) + .then(function(materialsInMtl) { + Object.assign(materials, materialsInMtl); + }) + .catch(function(error) { + console.logger(error.message); + console.logger('Could not read material file at ' + shallowPath + '. Using default material instead.'); + }); } return loadMtl(mtlPath) + .catch(function(error) { + // Try looking for the .mtl in the same directory as the obj + options.logger(error.message); + options.logger('Could not read material file at ' + mtlPath + '. Attempting to read the material file from within the obj directory instead.'); + return loadMtl(shallowPath); + }) .then(function(materialsInMtl) { - materials = Object.assign(materials, materialsInMtl); + Object.assign(materials, materialsInMtl); }) - .catch(function() { - logger('Could not read mtl file at ' + mtlPath + '. Using default material instead.'); + .catch(function(error) { + options.logger(error.message); + options.logger('Could not read material file at ' + shallowPath + '. Using default material instead.'); }); }, {concurrency : 10}) - .thenReturn(materials); + .then(function() { + return materials; + }); } function loadImages(imagePaths, objPath, options) { - var secure = options.secure; - var logger = options.logger; var images = {}; + var objDirectory = path.dirname(objPath); return Promise.map(imagePaths, function(imagePath) { - if (secure && outsideDirectory(imagePath, objPath)) { - logger('Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.'); - return; + var shallowPath = path.join(objDirectory, path.basename(imagePath)); + if (options.secure && outsideDirectory(imagePath, objDirectory)) { + // Try looking for the image in the same directory as the obj + options.logger('Image file is outside of the obj directory and the secure flag is true. Attempting to read the image file from within the obj directory instead.'); + return loadImage(shallowPath, options) + .catch(function(error) { + options.logger(error.message); + options.logger('Could not read image file at ' + shallowPath + '. This image will be ignored'); + }); + } else { + return loadImage(imagePath, options) + .catch(function(error) { + // Try looking for the image in the same directory as the obj + options.logger(error.message); + options.logger('Could not read image file at ' + imagePath + '. Attempting to read the image file from within the obj directory instead.'); + return loadImage(shallowPath, options); + }) + .catch(function(error) { + options.logger(error.message); + options.logger('Could not read image file at ' + shallowPath + '. This image will be ignored.'); + }); } - return loadImage(imagePath, options) - .then(function(image) { - images[imagePath] = image; - }) - .catch(function() { - logger('Could not read image file at ' + imagePath + '. Material will ignore this image.'); - }); }, {concurrency : 10}) - .thenReturn(images); + .then(function(images) { + return images; + }); } function getImagePaths(materials) { diff --git a/specs/data/box-external-resources-in-root/box-external-resources-in-root.mtl b/specs/data/box-external-resources-in-root/box-external-resources-in-root.mtl new file mode 100644 index 00000000..2e2d3a50 --- /dev/null +++ b/specs/data/box-external-resources-in-root/box-external-resources-in-root.mtl @@ -0,0 +1,13 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl MaterialTextured +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 +map_Kd ../box-textured/cesium.png diff --git a/specs/data/box-external-resources-in-root/box-external-resources-in-root.obj b/specs/data/box-external-resources-in-root/box-external-resources-in-root.obj new file mode 100644 index 00000000..6b17fc59 --- /dev/null +++ b/specs/data/box-external-resources-in-root/box-external-resources-in-root.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-multiple-materials.blend' +# www.blender.org +mtllib box-external-resources-in-root.mtl +mtllib ../box/box.mtl +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 0.0000 1.0000 +usemtl MaterialTextured +f 3/1/1 7/2/1 5/3/1 1/4/1 +f 1/9/3 2/10/3 4/11/3 3/12/3 +f 3/1/5 4/6/5 8/17/5 7/18/5 +usemtl Material +f 8/5/2 4/6/2 2/7/2 6/8/2 +f 7/13/4 8/14/4 6/15/4 5/16/4 +f 5/19/6 6/20/6 2/7/6 1/4/6 diff --git a/specs/data/box-external-resources-in-root/box.mtl b/specs/data/box-external-resources-in-root/box.mtl new file mode 100644 index 00000000..4f8d1294 --- /dev/null +++ b/specs/data/box-external-resources-in-root/box.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.100000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.100000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-external-resources-in-root/cesium.png b/specs/data/box-external-resources-in-root/cesium.png new file mode 100644 index 00000000..3b8baee1 Binary files /dev/null and b/specs/data/box-external-resources-in-root/cesium.png differ diff --git a/specs/data/box-resources-in-root/box-resources-in-root.mtl b/specs/data/box-resources-in-root/box-resources-in-root.mtl new file mode 100644 index 00000000..7bc18460 --- /dev/null +++ b/specs/data/box-resources-in-root/box-resources-in-root.mtl @@ -0,0 +1,13 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 +map_Kd resources/textures/cesium.png diff --git a/specs/data/box-resources-in-root/box-resources-in-root.obj b/specs/data/box-resources-in-root/box-resources-in-root.obj new file mode 100644 index 00000000..1e9d9453 --- /dev/null +++ b/specs/data/box-resources-in-root/box-resources-in-root.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib resources/box-resources-in-root.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-resources-in-root/cesium.png b/specs/data/box-resources-in-root/cesium.png new file mode 100644 index 00000000..3b8baee1 Binary files /dev/null and b/specs/data/box-resources-in-root/cesium.png differ diff --git a/specs/lib/loadObjSpec.js b/specs/lib/loadObjSpec.js index 2e265453..b117f1f1 100644 --- a/specs/lib/loadObjSpec.js +++ b/specs/lib/loadObjSpec.js @@ -31,6 +31,8 @@ var objMtllibSpacesUrl = 'specs/data/box-mtllib-spaces/box mtllib.obj'; var objMissingMtllibUrl = 'specs/data/box-missing-mtllib/box-missing-mtllib.obj'; var objMissingUsemtlUrl = 'specs/data/box-missing-usemtl/box-missing-usemtl.obj'; var objExternalResourcesUrl = 'specs/data/box-external-resources/box-external-resources.obj'; +var objResourcesInRootUrl = 'specs/data/box-resources-in-root/box-resources-in-root.obj'; +var objExternalResourcesInRootUrl = 'specs/data/box-external-resources-in-root/box-external-resources-in-root.obj'; var objTexturedUrl = 'specs/data/box-textured/box-textured.obj'; var objMissingTextureUrl = 'specs/data/box-missing-texture/box-missing-texture.obj'; var objSubdirectoriesUrl = 'specs/data/box-subdirectories/box-textured.obj'; @@ -329,7 +331,10 @@ describe('loadObj', function() { expect(loadObj(objMissingMtllibUrl, defaultOptions) .then(function(data) { expect(data.materials).toEqual({}); - expect(console.log.calls.argsFor(0)[0].indexOf('Could not read mtl file') >= 0).toBe(true); + expect(spy.calls.argsFor(0)[0].indexOf('ENOENT') >= 0).toBe(true); + expect(spy.calls.argsFor(1)[0].indexOf('Attempting to read the material file from within the obj directory instead.') >= 0).toBe(true); + expect(spy.calls.argsFor(2)[0].indexOf('ENOENT') >= 0).toBe(true); + expect(spy.calls.argsFor(3)[0].indexOf('Could not read material file') >= 0).toBe(true); }), done).toResolve(); }); @@ -360,8 +365,27 @@ describe('loadObj', function() { expect(data.images[imagePath]).toBeUndefined(); expect(data.materials.MaterialTextured).toBeDefined(); expect(data.materials.Material).toBeUndefined(); // Not in directory, so not included - expect(console.log.calls.argsFor(0)[0].indexOf('Could not read mtl file') >= 0).toBe(true); - expect(console.log.calls.argsFor(1)[0].indexOf('Could not read image file') >= 0).toBe(true); + expect(spy.calls.argsFor(0)[0].indexOf('The material file is outside of the obj directory and the secure flag is true. Attempting to read the material file from within the obj directory instead.') >= 0).toBe(true); + expect(spy.calls.argsFor(1)[0].indexOf('ENOENT') >= 0).toBe(true); + expect(spy.calls.argsFor(2)[0].indexOf('Could not read material file') >= 0).toBe(true); + }), done).toResolve(); + }); + + it('loads resources from root directory when the .mtl path does not exist', function(done) { + expect(loadObj(objResourcesInRootUrl, options) + .then(function(data) { + expect(data.materials['Material'].diffuseTexture.source).toBeDefined(); + expect(diffuseTexture.source).toBeDefined(); + }), done).toResolve(); + }); + + it('loads resources from root directory when the .mtl path is outside of the obj directory and secure is true', function(done) { + options.secure = true; + expect(loadObj(objExternalResourcesInRootUrl, options) + .then(function(data) { + var materials = data.materials; + expect(Object.keys(materials).length).toBe(2); + expect(materials['MaterialTextured'].diffuseTexture.source).toBeDefined(); }), done).toResolve(); }); @@ -380,7 +404,10 @@ describe('loadObj', function() { var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); expect(data.images[imagePath]).toBeUndefined(); expect(data.materials.Material.diffuseTexture).toEqual(imagePath); - expect(console.log.calls.argsFor(0)[0].indexOf('Could not read image file') >= 0).toBe(true); + expect(spy.calls.argsFor(0)[0].indexOf('ENOENT') >= 0).toBe(true); + expect(spy.calls.argsFor(1)[0].indexOf('Attempting to read the image file from within the obj directory instead.') >= 0).toBe(true); + expect(spy.calls.argsFor(2)[0].indexOf('ENOENT') >= 0).toBe(true); + expect(spy.calls.argsFor(3)[0].indexOf('Could not read image file') >= 0).toBe(true); }), done).toResolve(); });