Setting an assets material using an editor script

I’m trying to use an editor script to

  1. read material names from a json file
  2. apply those materials to a mesh

The script looks like it’s working, i.e. all materials are applied in the editor window. But as soon as it’s reloaded or the project built, they disappear.

Upon inspecting the mesh instances in the editor, they look empty, even though the models have materials in the editor window.

All materials exist in the editor. Each entity has four meshInstances.

Here’s the code:

let Entity = window.editor
            .call('entities:fuzzy-search', "entity-name", 9999)
            .filter(entity => entity.json().name === "entity-name")
            .reduce((entity, v) => v || entity, null);

Entity.entity.model.model.meshInstances.forEach(mesh => {
    let theMaterial = pc.app.assets.find("material-name");
    if (theMaterial) {
        if (!theMaterial.loaded) {
            pc.app.assets.load(theMaterial);
        }
        mesh.material = theMaterial.resource;
    } else {
        console.warn(`No material named: "material-name"`);
    }
});

I also tried

let theMaterial = pc.app.assets.find("material-name");
theMaterial.ready(theMaterial => mesh.material = theMaterial.resource);
pc.app.assets.load(theMaterial);

and

let theMaterial = pc.app.assets.find("material-name");
pc.app.assets.load(theMaterial);
mesh.material = theMaterial.resource.clone()

But with the same results.

I feel like I’m taking the wrong approach. Any ideas?

Thanks,

Mei
EDIT I’ve just realised that using pc.app.assets.find("material-name"); in an editor script is probably the problem. If someone can explain how to apply a material to a meshInstance within the editor thorough code, that would be great.

I used:

this.silverPaint = this.app.assets.find('carPaintSilver');

to locate the material.
I then used:

this.fordF150Paint.model.meshInstances[1].material = this.silverPaint.resource;

to set a particulare meshInstance to the desired material. In this case, it was to set the material for the second [1] mesh instance (since the mesh instance count starts at “0”.)

Hi @Mei, your logic is sound, you just need to switch contexts.

Right now all of your changes live in the pc.app instance running inside the editor, so they get lost the next time you load your project. You have to write your changes permanently to the project.

That’s quite easy, a simple example on how you read and write properties:

// --- get the first selected item from the Hierarchy and read/write some properties
// --- Note: item here is not a pc.Entity, but an observable that is used as context for that entity in editor space
// --- To get access to the entity you just do ... e.g. item.entity.getPosition()

var item = editor.call('selector:items')[0];

// read, both lines give the same result
console.log('Name: ', item.get('name') );
console.log('Name: ', item.entity.name);

// write
item.set('name','A new name!');
item.set('components.model.castShadows', false);

As you can see getting / setting editor properties is very powerful.

How do you find the right path? Update the value in the Inspector and watch the bottom left corner of your editor to find the updated path:

image




CAUTION ADVISED: setting wrong paths can corrupt / break your project.

Here is a simple script that given a material assetID and your desired slot, it will loop through all of your selected assets and update the mesh instance material.

Indeed a handy thing to do since that batch action isn’t allowed when selecting more than one model assets.

assetModelSetMaterial(15412718, 0);

function assetModelSetMaterial(assetId, slot) {
  const type = editor.call("selector:type");

  if (type !== "asset") {
    return false;
  }

  const items = editor.call("selector:items");

  if (!items || items.length === 0) {
    return false;
  }

  items.forEach((item, index) => {
    item.set("data.mapping." + slot + ".material", assetId);
  });

  console.log("--- Finished execution ---");
}

@Leonidas Thanks!

I gave up in the end and placed the materials at run time. The plan is to build a Chrome extension to do this, so I might try and implement your solution.

Cheers,

Mei

1 Like

@Leonidas

I’ve run into a couple of issues when trying to apply a material within the editor.

const entity = window.editor
        .call('entities:fuzzy-search', "entityName", 1)
        .filter(entity => entity.json().name === "entityName")
        .reduce((entity, v) => v || entity, null);
console.log(entity.get('components.model.mapping'));

The above will return a JS object corresponding to the four meshes of the model:

{
  "0": null,
  "1": null,
  "2": null,
  "3": null
}
  1. In the models that I have, each meshInstance has a name. Is it possible to correspond the above object with the instance names of the meshes? If I just apply them to the numbers they may go on the wrong mesh.
[index, asset] = editor.call("assets:findOne", e => e.get('name') === 'BrickMaterialName');

The above gives assets as an Observable.

  1. Is it possible to get the Material from this object to apply to the entity above?

From your code above, I couldn’t reference the data attribute on the item (item.set("data.mapping." + slot + ".material", assetId);) . Was this an oversight on your part or am I getting the wrong kind of entity?

Thanks for all your help on this. Greatly appreciated.

Mei

Hi @Mei,

I’ve updated the method to:

  • Search for the material asset to assign by name.
  • To get the slot by the model node name.

To use it:

  1. Select any number of model assets.
  2. Type a valid Material Name.
  3. Type a valid Mesh Instance name.

Hope it helps.

assetModelSetMaterial("Material Character", "Character Body");

function assetModelSetMaterial(materialName, meshInstanceName) {
  const type = editor.call("selector:type");

  if (type !== "asset") {
    return false;
  }

  // --- find material by name
  let materialAsset = editor.call("assets:findOne", asset => {
    return (
      asset.get("type") === "material" && asset.get("name") === materialName
    );
  });

  if (materialAsset[1] === undefined) return;
  materialAsset = materialAsset[1];

  const items = editor.call("selector:items");

  if (!items || items.length === 0) {
    return false;
  }

  items.forEach((item, index) => {
    const slotIndex = item._nodes.indexOf(meshInstanceName);

    if (slotIndex == -1) return true;

    item.set(
      "data.mapping." + slotIndex + ".material",
      materialAsset.get("id")
    );
  });

  console.log("--- Finished execution ---");
}

1 Like