[SOLVED] Trouble accessing children in a glTF-imported file (load externally)

If I want to change color on the meshinstances within an imported glTF-file, I get issues relating naming from within the blender-file.
The imported file at server-side, is logged as “Untitled”?

I am able to change color on the entire object, but - as such - not the individual parts.
How to access? (as one can see I am trying with a children-approach):


var LoadGltfExternal = pc.createScript('loadGltfExternal');
LoadGltfExternal.attributes.add('baseUrl', {type: 'string'});
LoadGltfExternal.attributes.add('gltfFilename', {type: 'string'});

// initialize code called once per entity
LoadGltfExternal.prototype.initialize = function() {
   
    var app = this.app;
    var self = this;  self.fr =0;

 app.assets.loadFromUrl(self.baseUrl + self.gltfFilename, 'json', function (err, asset) {
        var gltf = JSON.parse(asset.resource);

        loadGltf(gltf, app.graphicsDevice, function (err, res) {
            // Wrap the model as an asset and add to the asset registry
            var asset = new pc.Asset('gltf', 'model', {
                url: ''
            });

            asset.resource = res.model;
            asset.loaded = true;
            app.assets.add(asset);

            // Add the loaded scene to the hierarchy
            self.entity.addComponent('model', {
                asset: asset
            });
        }, {
            basePath: self.baseUrl
        });
    });
};

// update code called every frame
LoadGltfExternal.prototype.update = function(dt) {
    var app = this.app;
    var self = this;
    self.fr++;
    if(self.fr>100 && self.fr<110){
    

    var testMat = self.app.root.findByName('TestMat'); console.log('TestMat @ ModelLoad : ',testMat.name);
    var meshInstance = self.app.root.findByName('ModelLoad'); console.log('meshInstance children dmt: ',meshInstance.children[0].name);
    var color = meshInstance.model.meshInstances[0].material.diffuse.clone(); 
     // a parallel try:
// console.log('meshInstance.model.meshInstances[1] og [2] : '+ meshInstance.model.meshInstances[0].name + " :: "+  meshInstance.model.meshInstances[1].name );
      material = meshInstance.model.meshInstances[0].material;
      material.diffuse.set(1, 0, 0);
    material.update();
    console.log('color update: ', color.r, color.g, color.b);
        
    var color0 = meshInstance.model.meshInstances[1].material.diffuse.clone();
                 material0 = meshInstance.model.meshInstances[1].material;
                material0.diffuse.set(0, 1, 0);
            material0.update();
        console.log('color update0: ', color0.r, color0.g, color0.b);
}
};

Can’t really share the project here, as it doesn’t make sense localhost/playcanvas-host (due to CORS for the glTF-file).

The naming of the parented objects in blender:

Heirarchy as follows from below:

  1. Sp is child of Bn (blender)
  2. Server-object is called “PyraMid_SB3.gltf” (the external loaded)
  3. Editor-object is top by the name of “ModelLoad” (having the script attached):

Here is the output (“Untitled”):

Here you are setting the material asset colour so if both mesh instances are using the same material, it’s going to change both.

See: Change material diffuse tint color :slight_smile:

hmmm … I think we are “off track” in the approach. You might be right, but I am not getting the ‘steps in between’ then. If I get an output, at model-level, called “Untitled”, there must be something else going on (the usual output tend to be “undefined”, in case of bad API-syntax/strictly .js (like wrong reference at wrong dot.-level, sort of say)).
… I do have a hunch that it might be impossible with glTF within PC … as of yet (my backup-plan is to change color of the entire glTF-object, in case not)

Can you try printing out the entire hierarchy in the launch window? Paste this into the console of devtools:

function printChildren(node, indentLevel) {
    var i = 0;
    var indentStr = "";
    for (i = 0; i < indentLevel; ++i) {
        indentStr += " ";
    }

    console.log(indentStr + node.name);

    for (i = 0; i < node.children.length; ++i) {
        printChildren(node.children[i], indentLevel+1);
    }
}

printChildren(pc.app.root, 0);

Edit: Just to check, which library are you using to load the GLTF file?

1 Like

Nice one … did not know the console was so versatile:

printChildren(pc.app.root, 0);
VM988:8 Untitled
VM988:8 Root
VM988:8 Camera
VM988:8 Light
VM988:8 ModelLoad
VM988:8 Untitled
VM988:8 Bn
VM988:8 Sp
VM988:8 Lamp
VM988:8 Camera
VM988:8 TestMat
VM988:8 TestMat2
VM988:8 RootNode
VM988:8 Cube
VM988:8 Cube.001
VM988:8 RootNode
VM988:8 Cube
VM988:8 Cube.001
undefined

Or by scrdmp:

so something like “meshInstance.children[0].children.name”, should give me the level of “Bn”?

Then again:


// update code called every frame
LoadGltfExternal.prototype.update = function(dt) {
    var app = this.app;
    var self = this;
    self.fr++;
    if(self.fr>100 && self.fr<110){
    

    var testMat = self.app.root.findByName('TestMat'); console.log('TestMat @ ModelLoad : ',testMat.name);
    var meshInstance = self.app.root.findByName('ModelLoad'); 
       console.log('meshInstance child Bn: '+  meshInstance.children[0].children[0].node);
    console.log('meshInstance grandchild Sp: '+  meshInstance.children[0].children[0].children[0].material); 
        

        
    var meshInstance_glTFlevel_Bn =  meshInstance.children[0].children[0];
    //var color = meshInstance_glTFlevel_Bn.material;
    //model.meshInstances[0].material.diffuse.clone(); 
   
    material = meshInstance_glTFlevel_Bn.model.meshInstances[0].material;
    material.diffuse.set(1, 0, 0);
    material.update();
    console.log('color update: ', color.r, color.g, color.b);
   
    var meshInstance_glTFlevel_Sp =  meshInstance.children[0].children[0].children[0];
    var color0 = meshInstance_glTFlevel_Sp.model.meshInstances[0].material.diffuse.clone();
    material0 = meshInstance_glTFlevel_Sp.model.meshInstances[0].material;
    material0.diffuse.set(0, 1, 0);
    material0.update();
    console.log('color update0: ', color0.r, color0.g, color0.b);
}
};
  • doesn’t work. And the logs gives “undefined” as output.

Can you share the glTF file please?

{
    "accessors" : [
        {
            "bufferView" : 0,
            "componentType" : 5123,
            "count" : 60,
            "max" : [
                38
            ],
            "min" : [
                0
            ],
            "type" : "SCALAR"
        },
        {
            "bufferView" : 1,
            "componentType" : 5126,
            "count" : 39,
            "max" : [
                1.0,
                0.0,
                1.0
            ],
            "min" : [
                -1.0000001192092896,
                -1.0,
                -1.0000003576278687
            ],
            "type" : "VEC3"
        },
        {
            "bufferView" : 2,
            "componentType" : 5126,
            "count" : 39,
            "max" : [
                0.8944272398948669,
                0.44721370935440063,
                0.8944272994995117
            ],
            "min" : [
                -0.8944272994995117,
                -1.0,
                -0.8944271206855774
            ],
            "type" : "VEC3"
        },
        {
            "bufferView" : 3,
            "componentType" : 5123,
            "count" : 12,
            "max" : [
                11
            ],
            "min" : [
                0
            ],
            "type" : "SCALAR"
        },
        {
            "bufferView" : 4,
            "componentType" : 5126,
            "count" : 12,
            "max" : [
                0.4999999403953552,
                1.0,
                0.5000001192092896
            ],
            "min" : [
                -0.5000001192092896,
                0.0,
                -0.5000001192092896
            ],
            "type" : "VEC3"
        },
        {
            "bufferView" : 5,
            "componentType" : 5126,
            "count" : 12,
            "max" : [
                0.8944271802902222,
                0.4472137689590454,
                0.8944272994995117
            ],
            "min" : [
                -0.8944271802902222,
                0.44721347093582153,
                -0.8944271206855774
            ],
            "type" : "VEC3"
        }
    ],
    "asset" : {
        "generator" : "Khronos Blender glTF 2.0 exporter",
        "version" : "2.0"
    },
    "bufferViews" : [
        {
            "buffer" : 0,
            "byteLength" : 120,
            "byteOffset" : 0,
            "target" : 34963
        },
        {
            "buffer" : 0,
            "byteLength" : 468,
            "byteOffset" : 120,
            "target" : 34962
        },
        {
            "buffer" : 0,
            "byteLength" : 468,
            "byteOffset" : 588,
            "target" : 34962
        },
        {
            "buffer" : 0,
            "byteLength" : 24,
            "byteOffset" : 1056,
            "target" : 34963
        },
        {
            "buffer" : 0,
            "byteLength" : 144,
            "byteOffset" : 1080,
            "target" : 34962
        },
        {
            "buffer" : 0,
            "byteLength" : 144,
            "byteOffset" : 1224,
            "target" : 34962
        }
    ],
    "buffers" : [
        {
            "byteLength" : 1368,
            "uri" : "PyraMid_SB3.bin"
        }
    ],
    "materials" : [
        {
            "name" : "Forneden",
            "pbrMetallicRoughness" : {
                "baseColorFactor" : [
                    0.640000066757203,
                    0.012529936619549042,
                    0.0009863978398799572,
                    1.0
                ],
                "metallicFactor" : 0.0
            }
        },
        {
            "name" : "Top",
            "pbrMetallicRoughness" : {
                "baseColorFactor" : [
                    0.022967736765261537,
                    0.05866455523169867,
                    0.640000066757203,
                    1.0
                ],
                "metallicFactor" : 0.0
            }
        }
    ],
    "meshes" : [
        {
            "name" : "Ud",
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 2,
                        "POSITION" : 1
                    },
                    "indices" : 0,
                    "material" : 0
                }
            ]
        },
        {
            "name" : "ids",
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 5,
                        "POSITION" : 4
                    },
                    "indices" : 3,
                    "material" : 1
                }
            ]
        }
    ],
    "nodes" : [
        {
            "children" : [
                3
            ],
            "mesh" : 0,
            "name" : "Bn"
        },
        {
            "name" : "Camera",
            "rotation" : [
                0.483536034822464,
                0.33687159419059753,
                -0.20870360732078552,
                0.7804827094078064
            ],
            "translation" : [
                7.481131553649902,
                5.34366512298584,
                6.5076398849487305
            ]
        },
        {
            "name" : "Lamp",
            "rotation" : [
                0.16907575726509094,
                0.7558802962303162,
                -0.27217137813568115,
                0.570947527885437
            ],
            "scale" : [
                1.0,
                1.0,
                0.9999999403953552
            ],
            "translation" : [
                4.076245307922363,
                5.903861999511719,
                -1.0054539442062378
            ]
        },
        {
            "mesh" : 1,
            "name" : "Sp",
            "translation" : [
                0.0,
                0.5661911964416504,
                -0.0
            ]
        }
    ],
    "scene" : 0,
    "scenes" : [
        {
            "name" : "Scene",
            "nodes" : [
                0,
                2,
                1
            ]
        }
    ]
}

I am trying to get a filestructure overview by:

  1. https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_002_BasicGltfStructure.md
  2. https://www.khronos.org/files/gltf20-reference-guide.pdf

… as well as “consol’ing” by:

printChildren(pc.app.root, 0);function printChildren(node, indentLevel) {
var i = 0;
var indentStr = “”;
for (i = 0; i < indentLevel; ++i) {
indentStr += " ";
}

console.log(indentStr + mesh.name);

for (i = 0; i < mesh.primitives.length; ++i) {
    printChildren(mesh.primitives[i], indentLevel+1);
}

}

printChildren(pc.app.root, 0);

Sorry, can you also upload the bin file too please?

Should be this one: https://docs.google.com/uc?export=download&id=1CrhDqrF1BraNZ1y8Weu0GSI3SGk8ihNP

Example project: https://playcanvas.com/editor/scene/849692
Code: https://playcanvas.com/editor/code/655732?tabs=30691726

// initialize code called once per entity
AccessNodes.prototype.initialize = function() {
    // The model root entity
    console.log("Model root entity: " + this.entity.name);
    
    // Bn
    console.log("Bn graph node: " + this.entity.children[0].children[0].name);
    
    // Sp
    console.log("Sp graph node: " + this.entity.children[0].children[0].children[0].name);
    
    // If you want to access the mesh instance, that's a bit trickier as the are not attached the graph node
    // You would need to iterate through all the mesh instances and check the graph node it's attached to
    var i, mi;
    var meshInstances = this.entity.model.meshInstances;
    for (i = 0; i < meshInstances.length; ++i) {
        mi = meshInstances[i];
        if (mi.node.name == "Bn") {
            console.log(mi.material);
        }
    }
};

Well … looked promising and I do appreciate the effort regarding your last post :slight_smile:

still - I had the audacity to rename the topic headline. Otherwise ‘the snake seems to bite its own tale’
Cf the first post as well (just below half down):

  1. Sp is child of Bn (blender)
  2. Server-object is called “PyraMid_SB3.gltf” (the external loaded)
  3. Editor-object is top by the name of “ModelLoad” (having the script attached):

So the trick was to get further down within the total hierarchy. Your first answer got us some of the way, as we could see what was below “Untitled” in the externally loaded file - so that was great.

Might be; thinking (in all humbleness) that we should try and show this to the API-guys (team that codes/optimizes functionality … what do I know? :-j ), in case it all strands [PlayCanvas should see great advantages in this … if glTF is the JPEG of 3D objects, “we need to make progressive web-loading of jpeg possible” {analogy … hehe }]

I’m still not sure what the actual issue/problem is here? The graphnode hierarchy is the same as the shown in blender. All graphnodes are accessible if by name or by the children order. Mesh instances are accessible as an array and can be checked against their graphnode.

Basically, what do you need to progress with your project?

During the total debugging-route within this topic, I did actually try something in regards to accessing those external nodes - as seen below:


var LoadGltfExternal = pc.createScript('loadGltfExternal');
LoadGltfExternal.attributes.add('baseUrl', {type: 'string'});
LoadGltfExternal.attributes.add('gltfFilename', {type: 'string'});

// initialize code called once per entity
LoadGltfExternal.prototype.initialize = function() {
   
    var app = this.app;
    var self = this;  self.fr =0;

 app.assets.loadFromUrl(self.baseUrl + self.gltfFilename, 'json', function (err, asset) {
        var gltf = JSON.parse(asset.resource);

        loadGltf(gltf, app.graphicsDevice, function (err, res) {
            // Wrap the model as an asset and add to the asset registry
            var asset = new pc.Asset('gltf', 'model', {
                url: ''
            });

            asset.resource = res.model;
            asset.loaded = true;
            app.assets.add(asset);

            // Add the loaded scene to the hierarchy
            self.entity.addComponent('model', {
                asset: asset
            });
        }, {
            basePath: self.baseUrl
        });
    });
};

// update code called every frame
LoadGltfExternal.prototype.update = function(dt) {
    var app = this.app;
    var self = this;
    self.fr++;
    if(self.fr>100 && self.fr<110){
    

    var testMat = self.app.root.findByName('TestMat'); console.log('TestMat @ ModelLoad : ',testMat.name);
    var meshInstance = self.app.root.findByName('ModelLoad'); 
       try{ console.log('meshInstance child Bn: '+  meshInstance.children[0].children[0].node); }catch(err){}
      try{ console.log('meshInstance grandchild Sp: '+  meshInstance.children[0].children[0].children[0].material); }catch(err){}
       try{ console.log('1: '+  meshInstance.meshes[0].name);}catch(err){}
             try{  console.log('2: '+  meshInstance.children[0].meshes[0].material);}catch(err){}
        try{   console.log('3: '+  meshInstance.children[0].children[0].meshes[0].name);}catch(err){}
      try{   console.log('4: '+  meshInstance.children[0].children[0].meshes[1].name);}catch(err){}
           try{   console.log('5: '+  meshInstance.children[0].meshes[0].name);}catch(err){}
      try{   console.log('6: '+  meshInstance.children[0].meshes[1].name);}catch(err){}
        
       try{   console.log('M1: '+  meshInstance.materials[0].name);}catch(err){}      try{   console.log('M2: '+  meshInstance.materials[1].name);}catch(err){}
        try{   console.log('MLD1: '+  meshInstance.children[0].materials[0].name);}catch(err){}      try{   console.log('MLD2: '+  meshInstance.children[0].materials[1].name);}catch(err){}

     }      
};

(the reason I didn’t write was an answer from you, and then I did not get back to it)
From all the try{}catch(){}'es, the first two worked only - the rest fails [so I would argue that I don’t have access to all the nodes :-/ ]
Between the lines (and cf the blender-scrdmp above), I (we all want) to access the two mesh’es called:
“Ud” and “Ids”, that lies below the blender objects “Bn” and “Sp”.

At ‘post 4’; “Bn” and “Sp” are shown within the output list, yes. “Ud” and “Ids”; not.

Within the PyraMid_SB3.glTF-file one will find/read:

"meshes" : [
        {
            "name" : "Ud",
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 2,
                        "POSITION" : 1
                    },
                    "indices" : 0,
                    "material" : 0
                }
            ]
        },
        {
            "name" : "ids",
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 5,
                        "POSITION" : 4
                    },
                    "indices" : 3,
                    "material" : 1
                }
            ]
        }

As seen above they both have a material-reference in their ‘childrens’-list/sub-node list.
Is it possible to reach them and their material-reference?
In essence: [Did the API-responsible within the PlayCanvas-organization think of such functionality, and if so; do you have the method? :slight_smile: ]

In case they did, it would help me a lot.

My ultimate goal:

  1. Load an external glTF-file with multiple objects (each having meshes).
  2. Change the color of each glTF-object at runtime/dynamically (regardless of level … if do-able at object-level, it is fine as well … cf the .glTF-file structure that contains different material-references).

My backup-plan (quite time consuming and an extra list-filling is required at ‘4’):

  1. Make a blender-project with multiple objects/meshes.
  2. Export each object/mesh as a glTF-file.
  3. Load each of these glTF-files separately.
  4. Change the color of each separate glTF-file at runtime/dynamically.

I’m not sure how GLTF meshes are related to the mesh instances in PlayCanvcas. I do wonder how the representation of multiple meshes under a node would be represented in PlayCanvas in regards to mesh instances.

For the moment, assuming that one node = one meshInstance, is this what you are looking for?

var AccessNodes = pc.createScript('accessNodes');

AccessNodes.attributes.add('greenMaterialAsset', {type: 'asset', assetType:'material'});
AccessNodes.attributes.add('redMaterialAsset', {type: 'asset', assetType:'material'});


// initialize code called once per entity
AccessNodes.prototype.initialize = function() {
    // If you want to access the mesh instance, that's a bit trickier as the are not attached the graph node
    // You would need to iterate through all the mesh instances and check the graph node it's attached to
    var i, mi;
    var meshInstances = this.entity.model.meshInstances;
    for (i = 0; i < meshInstances.length; ++i) {
        mi = meshInstances[i];
        if (mi.node.name == "Bn") {
            // This is using a referenced material but you could easily clone one and reassign
            mi.material = this.greenMaterialAsset.resource;
        }
        
        if (mi.node.name == "Sp") {
            mi.material = this.redMaterialAsset.resource;
        }
    }    
};

https://playcanvas.com/editor/scene/918604

This is from your latest example :slight_smile:

  • my project is already between 50 and 100 mb (have to use all the external sources possible … all external methods)

You can use external in the same way. Nothing really changes from it being in the Editor. Its the same gltf and bin file that is loaded via the same methods of the GLTF loader. And if it’s the splash time load you are trying to remove, you can untick preload from the assets and load them in at will later.

But for completeness, here is an example with external loaded models:
https://playcanvas.com/editor/scene/918605

Code: https://playcanvas.com/editor/code/685076?tabs=30728683

var LoadGltfExternal = pc.createScript('loadGltfExternal');
LoadGltfExternal.attributes.add('baseUrl', {type: 'string'});
LoadGltfExternal.attributes.add('gltfFilename', {type: 'string'});
LoadGltfExternal.attributes.add('greenMaterialAsset', {type: 'asset', assetType:'material'});
LoadGltfExternal.attributes.add('redMaterialAsset', {type: 'asset', assetType:'material'});

// initialize code called once per entity
LoadGltfExternal.prototype.initialize = function() {
    var app = this.app;
    var self = this;

    app.assets.loadFromUrl(self.baseUrl + self.gltfFilename, 'json', function (err, asset) {
        var gltf = JSON.parse(asset.resource);

        loadGltf(gltf, app.graphicsDevice, function (err, res) {
            // Wrap the model as an asset and add to the asset registry
            var asset = new pc.Asset('gltf', 'model', {
                url: ''
            });

            asset.resource = res.model;
            asset.loaded = true;
            app.assets.add(asset);

            // Add the loaded scene to the hierarchy
            self.entity.addComponent('model', {
                asset: asset
            });
            
            self.entity.addComponent('script', {});

            self.entity.script.create('accessNodes', { 
                attributes: {
                    greenMaterialAsset: self.greenMaterialAsset,
                    redMaterialAsset: self.redMaterialAsset
                }
            });
        }, {
            basePath: self.baseUrl
        });
    });
};

// update code called every frame
LoadGltfExternal.prototype.update = function(dt) {
    
};

// swap method called for script hot-reloading
// inherit your script state here
// LoadGltfExternal.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/

Ok, will try that.
So that’s the usage of “preload”. Had trouble understanding the exact functionality -> seemed blurry as even the smallest projects with one internal primitive adds up to an export of several mb (?)
… so was quite pessimistic of dynamic load effects (but I guess ‘all byte-size’ of the non-preloaded files lay dorment in the “files”-folder then).

Ok, works like a charm … will write a feedback to the relevant team about a “pipeline-model” etc in relation to “How things load” {might also be able to solve myths and mystique regarding the app.fire/time events}