glTF model added to Object3D but after loading, properties can't be accessed by .chlidren[0]

I can’t access the glTF model properties after “adding” a clone of the model to the Object3D instance.

Steps to reproduce the behavior:

  1. Load the glTF model using the GLTF() loader.
  2. Create an Object3D instance.
  3. Add the loaded model to the Object3D instance.
  4. Try to access the Object3D child[0] (added model) to change its color.

Code

// code goes here
class ModelCreator {
    constructor(model_path, model_containers,
        model_positions, model_rotation, model_color, model_names, scene) {
        this.model_path = model_path;
        this.model_containers = model_containers;
        this.model_positions = model_positions;
        this.model_rotation = model_rotation;
        this.model_color = model_color;
        this.model_names = model_names;
        this.scene = scene;
        this.loader = new GLTFLoader();
    }
    create = () => { 
        this.loadModel();
        this.duplicateModel();
    }
    loadModel =()=> {
        const onLoad = (result, model_containers) => {
            const model = result.scene.children[0];
            model.position.set(0.0, 0.0, 0.0);
            model.scale.set(0.05, 0.05, 0.05);
            model.material.color.set(this.model_color)
            for (let i = 0; i < model_containers.length; i++) {
                model_containers[i].add(model.clone());
            }
        };
        this.loader.load(
            this.model_path,
            gltf => onLoad(gltf, this.model_containers),
        ), undefined, function (error) {
            console.log('An error happened');
        };
    }

    duplicateModel = () => {
        for (let i = 0; i < this.model_containers.length; i++) {

            this.model_containers[i].translateX(this.model_positions[i].x);
            this.model_containers[i].translateY(this.model_positions[i].y);
            this.model_containers[i].translateZ(this.model_positions[i].z);

            if (this.model_rotation) {
                this.model_containers[i].rotateY(Math.PI/2);
            }
            
            /* Supplementary Information */
            this.model_containers[i].userData.color = this.model_color;
            this.model_containers[i].name = this.model_names[i];
            
            this.scene.add(this.model_containers[i]);
        }
    }
}

export function createLego(position, name, scene, size=2, rot=false) {
    let lego = [new Object3D()];
    let lego_color = new Color(0x4D4D4D);
    let model_path = lego_2x2_path;

    const legoCreator_ = new ModelCreator(model_path, lego, [position],
        rot, lego_color, [name], scene);
    
    legoCreator_.create();
}
...
/* This function has no access to the Object3D child model, it generates an error! */
function changeColorLego(scene, name, color) {
    // This part works well and I can see my object in the console
    console.log(scene.getObjectByName(name).children);
    // children[0] is not accessible!
    scene.getObjectByName(name).children[0].material.color.set(color);
}

ERROR Message:

TypeError: scene.getObjectByName(...).children[0] is undefined

I can access console.log(scene.getObjectByName(...).children) and see that my model is inside the object which means that my model is already loaded, but I have no access to the children[0] to change the properties, and console.log(scene.getObjectByName(...).children) yields 0, but my model is already loaded as I can get an output from console.log(scene.getObjectByName(...).children) which is the object.

Expected behavior

To be able to access the Object3D children and change its properties.

Platform:

  • Device: [Desktop]
  • OS: [Windows]
  • Browser: [Firefox]
  • Three.js version: [r1440]

Is it possible for you do demonstrate the issue with a live example (fiddle, codepen, codesandbox)?

The link to your glTF asset is broken.

clone doesn’t account for children by default, clone(true) will, this could already blow it depending on the gltf data, but either way, all of this looks … really fragile. traversal is evil, scene.getObjectByName(name).children[0].material.color.set is evil. these are all anti-patterns, race conditions. i can guarantee you that if you make this code into an app, it will eventually scale enough to fail beyond repair.

perhaps it would be easier to ask you what it is that you want to do because there is no fixing the above. the first thing i’d suggest is that you start with async/await instead of callbacks. your models are async, most of the complexity arises if you don’t treat them as such.

@Mugen87 Thanks for your note, I have fixed the link to the glTF, I wasn’t able to load my glTF model from GitHub to JSFiddle, I have tried with Collada also, but the same problem.

What I want to do is to load the glTF model once, and then duplicate it with different positions/colors/opacity, I was able to change the position/color, but not the opacity to make the LEGO cube blink, can you pleas tell me what is the right way to access the opacity after loading the model once and duplicating it using clone(true) or any other approach that you recommend, thanks in advance.

When you clone a 3D object, the geometry and material are shared. So updating the opacity of one mesh will influence all clones. That means you have to clone the materials explicitly if you want to animate material properties in an independent fashion.

1 Like

that is so, so easy with a framework: Re-using GLTFs - CodeSandbox which would solve all the issues you are trying to solve, especially cloning and traversal, through GitHub - pmndrs/gltfjsx: 🎮 Turns GLTFs into JSX components

in vanilla you’ll pretty much have to create a framework yourself, if you want to do that i would start by removing all callbacks in favour of async/await, that alone will remove so much pain:

async function app() {
  const fontLoader = new FontLoader()
  const font = await new Promise(res => fontLoader.load("/font.json", res))
  const geometry = new TextGeometry('Scroll to Start', { font })
  ...

if you have multiple assets avoid waterfalls like this:

const [font, image, model] = await Promise.all([
  new Promise(res => fontLoader.load("/font.json", res)),
  new Promise(res => textureLoader.load("/image.jpg", res)),
  new Promise(res => gltfLoader.load("/model.glb", res)),
])
...

all your assets are guaranteed to be there without you hoping they are. next step is cloning, you’d have to know what you want out of it. you can have shallow clones and deep clones. as for the traversal and reaching through children[index], that is not good. i’m not even sure that’s possible at all, wasn’t GLTFLoader async and order can change? like every time you load [0] is something else? but even if not, i’d rely on names if i had to pluck stuff out of its original context.

1 Like

thanks for your advice, I ended up using the ‘Mesh()’ as it has the material property, cloning the model material with my_mesh.material = loaded_gltf_model.material.clone(true);, and trying to change its color like this:

my_mesh.add(model.clone(true));
my_mesh.material = model.material.clone(true);

export function changeColorLego(scene, name, color_) {
    const obj = scene.getObjectByName(name);
    obj.material.color.set(color_);
    obj.material.opacity = 0.5;
    console.log("color changed");
    console.log(obj);
}

I get no errors, and the message “color changed” is printed on the console as you can see in this codesandbox, but the color is not changed in the scene!, can you please tell me how to change the color properly so I can see the new mesh color on the scene? thanks.