Can't use textures (jpg) for mtlLoader uploaded as files inputs (by users) creatObjectUrl and setURLModifier don't work

Hi there,

I’m trying to create a tool where users can upload their obj file, an mtl file and the necessary jpeg for textures (required by the mtl file). The loadmodel function is triggered by a multiple file input button in the html.

Since I can’t use the options mtlloader.setpath or mtlloader.setbaseurl to give it the directory where the textures would be because i want the users to load their own obj mtl and textures is there a way to adapt the mtlLoader (or use another solution)?

it’s returning error when i try to upload an obj with its mtl and jpg textures:

Uncaught TypeError: Failed to execute ‘createObjectURL’ on 'url: No function was found that matched the signature provided.
at eval (mainmodules.js:391)
at LoadingManager.resolveURL (three.module.js:36377)
at ImageLoader.load (three.module.js:37089)
at TextureLoader.load (three.module.js:37236)
at MTLLoader.MaterialCreator.loadTexture (MTLLoader.js:528)
at setMapForType (MTLLoader.js:331)
at MTLLoader.MaterialCreator.createMaterial_ (MTLLoader.js:380)
at MTLLoader.MaterialCreator.create (MTLLoader.js:293)
at MTLLoader.MaterialCreator.preload (MTLLoader.js:261)
at eval (mainmodules.js:422)

not sure if the problem is the assets take time to load and are not available yet when the rest of the can run? isnt createobjecturl instantaneous istead of reasAsDataUrl?
any help would be great Thanks!

function loadModel(e){

var mesh = undefined;
const files = document.getElementById("uploadedModel").files;

var extension = undefined;
var gltf = undefined;
var obj = undefined;
var mtl = undefined;
var formatAndTextureFiles = {};


for(const file of files) {

    extension = file.name.split('.')[1];
    
    console.log("checking extension " + file.name );

    if (extension === "gltf"){
        gltf = file.name;
        var gltfUrl = file.name;
        formatAndTextureFiles[file.name] = file;

    }else if (extension === "obj"){
        obj = file.name;
        var objUrl = file.name;
        formatAndTextureFiles[file.name] = file;

    }else if (extension === "mtl"){
        mtl = file.name;
        var mtlUrl = file.name;
        formatAndTextureFiles[file.name] = file;

    }else if (extension === "jpg" || extension === "jpeg" || extension === "png" || extension === "bmp" ){
        formatAndTextureFiles[file.name] = file;
    }
} 

var manager = new THREE.LoadingManager();
var objectURLs = [];

manager.setURLModifier( ( url ) => {
    url = URL.createObjectURL( formatAndTextureFiles[url]);
    objectURLs.push( url );
    return url;
} );

if (gltf !== undefined){
    console.log("there is a gltf");
    var gltfLoader = new GLTFLoader(manager);
    gltfLoader.load( gltf, (model) => {

        mesh = model.scene.children[0];
        mesh.traverse(function(o) {
            if (o.isMesh) {
            o.castShadow = true;
            //o.receiveShadow = true;
            }
        });
        scene.add(mesh);
        console.log("gltf loaded");
        console.log(mesh);
    
    });

    //objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) );

}else if (obj !== undefined && mtl !== undefined){
    console.log("there is a obj and mtl");
    var mtlLoader = new MTLLoader(manager);
    var objLoader = new OBJLoader(manager);

    mtlLoader.load( mtl, function (materials) {
        materials.preload();
        objLoader.setMaterials(materials);
        objLoader.load( obj, (model) => {
            mesh = model;
            scene.add(mesh);
            console.log("obj with mtl loaded");
            console.log(mesh);
        });
    });
    //objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) );   
}

else if (obj !== undefined && mtl === undefined){
    console.log("there is just an obj");
    var objLoader = new OBJLoader(manager);
    objLoader.load( obj, (model) => {
        mesh = model;
        scene.add(mesh);
        console.log("obj loaded");
        console.log(mesh);
    });
}

}

if i choose to go with overriding textureloader how can i doo it for the mtlloader?
i imagine something like this:

let texture = new THREE.Texture(
url //URL = a base 64 JPEG string in this case
);

If I understood you correctly, it should be as easy as duplicating the base loader and overriding MTLLoader.loadTexture - you can throw the entire body away and, instead of using TextureLoader to fetch textures, return the uploaded texture.

loadTexture takes an url argument - so you can return an uploaded texture depending on this argument (it always will be the same, unless you change it in the model. So whenever in the model file you use material called "baseTexture1.jpg", in loadTexture return uploadedImages['baseTexture1'] (ie. uploadedImages[url]).

The approach you sent will probably also work, it seems to be basically a cleaner version of what I suggested - but I haven’t had a chance to play with url modifiers in loaders though, sorry. Maybe someone else here can help out with these.

Hi mjurczyk

I made some changesto the code.

it will work if i just try to load an obj
when I try to load an obj and mtl or an obj and mtl and the jpeg I get:

    mainmodules.js:401 Uncaught TypeError: Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided.
        at eval (mainmodules.js:401)
        at LoadingManager.resolveURL (three.module.js:36377)
        at ImageLoader.load (three.module.js:37089)
        at TextureLoader.load (three.module.js:37236)
        at MTLLoader.MaterialCreator.loadTexture (MTLLoader.js:528)
        at setMapForType (MTLLoader.js:331)
        at MTLLoader.MaterialCreator.createMaterial_ (MTLLoader.js:380)
        at MTLLoader.MaterialCreator.create (MTLLoader.js:293)
        at MTLLoader.MaterialCreator.preload (MTLLoader.js:261)
        at eval (mainmodules.js:438)

Here is the loading code:

function loadModel(e){

    var mesh = undefined;
    const files = document.getElementById("uploadedModel").files;
 
    var extension = undefined;
    var gltf = undefined;
    var obj = undefined;
    var mtl = undefined;
    var formatAndTextureFiles = {};
 

    for(const file of files) {

        extension = file.name.split('.')[1];
        
        if (extension === "obj"){
            obj = file.name;
            formatAndTextureFiles[file.name] = file;
            
        }else if (extension === "mtl"){
            mtl = file.name;
            formatAndTextureFiles[file.name] = file;
          
        }else if (extension === "jpg" || extension === "jpeg" || extension === "png" || extension === "bmp" ){
            formatAndTextureFiles[file.name] = file;
        }
    } 

    var manager = new THREE.LoadingManager();
    var objectURLs = [];

    manager.setURLModifier( ( url ) => {

        url = window.URL.createObjectURL( formatAndTextureFiles[url]);
        objectURLs.push( url );
        return url; 

    } );
    
    if (obj !== undefined && mtl !== undefined){
        var mtlLoader = new MTLLoader(manager);
        var objLoader = new OBJLoader(manager);

        mtlLoader.load( mtl, function (materials) {
            materials.preload();
            objLoader.setMaterials(materials);
            objLoader.load( obj, (model) => {
                mesh = model;
                scene.add(mesh);
            });
        });
        //objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) );   
    }

    else if (obj !== undefined && mtl === undefined){
        var objLoader = new OBJLoader(manager);
        objLoader.load( obj, (model) => {
            mesh = model;
            scene.add(mesh);
        });
    }
}


seems to be something related to creatobjecturl from a blob not working if the file is an image

Can you explain how do i make the imageloader that will be used by the objloader and mtlloader have a behaviour of modifying the urls?

Any help would really be appreciated!
Thanks