Blender exporter Materials to PBRMaterials Typescript type conflict

Hi

exporter V 6.2.3, Blender 2.81

While exporting mesh to .babylon file and checking option:

World Properties > Babylon.js ver 6.2.3 > Use PBR Materials

I got when Loading the scene with SceneLoader.Append a situation that I would like to modify ambientColor for all materials in mesh.materials array.

for (let index = 0; index < scene.materials.length; index++) {
const material = scene.materials[index];
material.ambientColor = new Color3(1, 1, 1);
}

And linter for typescript is throwing an error:

Property ‘ambientColor’ does not exist on type

Ok, so I cast the material to PBRMaterial:

const material = scene.materials[index] as PBRMaterial;

And this error is gone but then I got a new one:

error Do not use any type assertions @typescript-eslint/consistent-type-assertions

The solution to this is for me now:

const material = scene.materials[index] as PBRMaterial; // eslint-disable-line @typescript-eslint/consistent-type-assertions

But I think if I’m selecting Use PBR Materials my materials in the scene will all be PBRMaterials and they should have a proper meshes type so Materials[] | PBRMaterials[].

Regards

Hey and welcome!

something like that should work:

if (scene.materials[index] instanceOf PBRMaterial) {
   const material = scene.materials[index] as PBRMaterial; 
}

Hi

Unfortunately casting to a type is forbidden in TypeScript so this is not possible.

The only option is to use generics for Materials in Importing models.

I try this and i got different error form lint:

error Do not use any type assertions @typescript-eslint/consistent-type-assertions

and I needed to suppress it with:

// eslint-disable-line @typescript-eslint/consistent-type-assertions

the Idea is to not suppress it at all.

Regards
Peter

On mobile, quick answer. Cast probably fails because there could also be MultiMaterial in scene.materials.

Think @Deltakosh suggestion of an if will get Typescript to work. You replied, but did you try this?

1 Like

It fails cause I got PBRMaterials and TypeScript assume they will be Materials.

I will like to loop over materials in the mesh to change its ambientColor but I can’t cause I got errors so I need to suppress casting error to get around it.

SceneLader expects mesh have Material and not PBRMaterial. That is the problem. If he would expect one of them would be great but it doesn’t.

PBRMaterial is a Material so my code should definitely work

But it doesn’t. I know its extended but TS is calling this on error.

Check yourself.

This is the problem. This is a tooltip out of VSCode when you hover over scene word in for loop in this code::

SceneLoader.Append('/3d/', 'scene.babylon', scene, scene => {
   for (let index = 0; index < scene.materials.length; index++) {
      const pbrMaterial = scene.materials[index] as PBRMaterial;
      pbrMaterial.ambientColor = new Color3(1, 1, 1);
   }
});

Tooltop:

(property) AbstractScene.materials: Material[]

All of the materials added to this scene In the context of a Scene, it is not supposed to be modified manually. Any addition or removal should be done using the addMaterial and removeMaterial Scene methods. Note also that the order of the Material within the array is not significant and might change.

@seehttp://doc.babylonjs.com/babylon101/materials

So AbstractScene.materials: Material[] is purely expected to be Materials type and if you export PBRMaterials they are not and they are still Material type. So they do not have property ambientColor and if you cast them you got above error

Maybe you could repro it in the PG (in TS) ?

First, there is no such thing as a PBRMaterial. It is BABYLON.PBRMaterial. That is an error for sure.

Also, I have found when Typescript has a exclusionary if, it permits things that it would not otherwise.

SceneLoader.Append('/3d/', 'scene.babylon', scene, scene => {
    for (const material of scene.materials) {
        if (material instanceOf BABYLON.PBRMaterial) {
            material.ambientColor = new Color3(1, 1, 1);
        }
     } 
});

You could do a cast inside the if. Coming from Java, pbrMat = <BABYLON.PBRMaterial> material, looks better to me.

Sorry, I do not get it. This is not Java it’s JavaScript with a TypeScript which is very specific what is what.

Checking if something is instanceof is not cutting it.

The situation is very simple. You should allow having a different Material coming from mesh and not only Material but also PBRMaterials, especially cause in Blender exporter there is an option “Use PBRMaterials only” tick box. So this tick box is not supported fully in the code.

If you import Babylon in a specific way you do not need “BABYLON.” in front of any Babylon class.

Also, I report issues cause I do not have time to fix it for you.

It’s a minor change so I do not see a problem for somebody from your team to implement it.

Also, TS is a pain in the … and pleas test your solutions before you suggest them. This situation is running around in circles and there is no other solution for me wright no when suppressing one of the errors.

But you got now the solution that you could and should implement to make this all right :100:

Regards
Peter

Why being rude? We just try to help you

And the solution I offered works perfectly:
https://www.babylonjs-playground.com/#5ZBSV6#1

BABYLON.SceneLoader.Append("https://models.babylonjs.com/", "alien.glb", scene, function (newMeshes) {
            scene.createDefaultCameraOrLight(true, true);
            scene.activeCamera.attachControl(canvas, false);
            (<BABYLON.ArcRotateCamera>scene.activeCamera).alpha += Math.PI; // camera +180°

            for (const material of scene.materials) {
                if (material instanceof BABYLON.PBRMaterial) {
                    material.ambientColor = new BABYLON.Color3(1, 1, 1);
                }
            } 
        });

This is wrong (See my example). It expect AT LEAST a Material and not PURELY

@peterimg, May I ask why preventing completely the use of type assertions in your project ???

This is what does not work here, not ts or js or babylon but the fact that you do not want type assertion.

The loader and scenes are designed to host any kind of materials so obviously the common type between them is Material. Now you want to filter on only one type cause you do not want to cast so if you look into typescript functionalities with type guards:

const pbrs = scene.materials.filter(
  (item): item is PBRMaterial => item.getClassName() === 'PBRMaterial',
);

But at the end it is still casting and just pass the lint rules…

Hi.

I’m not rude. I’m just pointing out that this is not working with typescrypt. I still got errors in this situation.

This issue is about typescript, not Babylon not working. So without typescript strict policies you think that It works. Cause it does but it’s giving errors.

The problem is the errors, and not code not working.

So your example it’s not having TypeScript strict rules that we have. It’s working for me in every option it’s just giving my TS errors in different places depending on the implementation.

That’s the issue.

See attachments for errors!

error1|690x253, 50%

Second error. I’m running in circles cause one error is solved but the other one pops in

First one more time

Items have Material class on them so they will not be picked up by this.

But your right I do not want casting that TS is shouting about.

But I do not see the problem cause you got Materials and PBRMaterials in babylon they should both be fine with meshes but they are not.

And it’s not a TS problem but Babylon to allow have both those types as part of the imported mesh.

So I do not like the fact that I must jump through hoops to get around missing setting in Babylon.

@peterimg, I am sorry but I think you are completely mistaken and that your attitude does not help in the slightest.

The error does not come from TS, Babylon or the tools you use but from your OWN code. Your configuration of ts eslint is preventing the use of type assertion and you need it here. In many cases it is good to be strict but it is also good to understand when it is totally safe to bypass those rules or why they would be here in the first place.

For instance how would you be able to use the DOM. Let say you go through the children of an element (they would be of type Element ) and you want to cast as a VideoElement which is totally valid as long as you check it is well a VideoElement. You will end up having the same exact behavior.

At some point, you have a list containing the base type to group all of them in e.g. Element or Material in the babylon case. This is basic programming where you would have a list of Animal containing Fish, Cat and Bird and while going through the list you want all the birds to Fly(). So you would iterate, cast to Bird and call Fly(). If you use any helper filtering on types, they would do the same internally anyway. Please keep in mind we do not have just StandardMaterial or PBRMaterial.

You could argue we should have different lists like one per kind but how would this scale when you introduce more types ?

If you know which type it is and you can test it (with instanceof or other means like getClassName() in babylon). It is totally safe to “cast”. Please note I put cast in quote cause it is not a real cast like in other programming languages, these are just helping the transpiler with types yo make your code safer and they won t appear in JS.

Searching a bit in the TS doc would lead you to Advanced Types · TypeScript where you can see the use for it in TS as well.

So you have 4 options:

  • Disable the strict rule from never to as in your lint config
  • Disable the rule on the cast line as this one would be totally safe
  • Use the filter function I shared with you previously to not disable the rule
function getPBRs(materials: Material[]): PBRMaterial[] {
  return materials.filter(
    (item): item is PBRMaterial => item.getClassName() === 'PBRMaterial',
  );
}

for (let m of getPBRs(scene.materials)) {
    m.ambientColor = null;
}
  • Create your own typeguard test to not disable the rule
function isPBR(material: Material): material is PBRMaterial {
  return material.getClassName() === "PBRMaterial";
}

for (let m of scene.materials) {
  if (isPBR(m)) {
    m.ambientColor = null;
  }
}

Hi sebavan (Witam Sebastianie w Vanie albo Wanie Sebastiana zalezy jak na to spojrzec)

I do not know what my attitude cause I do not have any.

I think you are the first person who understand the issue.

Your both code examples work but the second one In my mind is performance wiser cause it’s not using filter and any look inside.

But anyway making additional code to get around the issue that the materials is not a generic is a little bit too much in my opinion.

I wanted a fix in Babylon not hook to get around typescript rules which I can’t change.

I think this should still be reviewed in Babylon codebase cause the solution it’s good but it’s not what it’s should be.

Regards
Peter

I just checked the exported .babylon file and it’s [pointing a type of PBRMaterial:

“materials”:[{“name”:“mesh_baked”,“id”:“mesh_baked”,“customType”:“ BABYLON.PBRMaterial ”…

So I do not get it why SceneLoader should not expect the material to be one of two:

(property) AbstractScene.materials: Material[] | PBRMaterial[]

What is the problem of implementing this ??

Regards
Peter

Cause it is totally different, you do not have an array of one or the other but it could be an array of mixed materials: (Material | PBRMaterial | …)[]

And what about ShaderMaterial, GridMaterial, BackgroundMaterial and all the custom ones ? as well as the one Babylon does not provide but are provided externally ?

Also please note that even with the union type, you would need to cast at some points to specify yours is PBR.

Your issue should not be solved in the Babylon code itself as the framework needs to handle any kind of materials and the Material type here is the correct one.