Tag Manager

When to use it

The tag manager is responsible for altering node and material data, such as the node visibility, transformation and color information. This functionality might sound very similar to the Parameter system, however the tag manager comes with some advantages.

  • Easy configuration without the Spec
  • Interacting directly with Babylon.js API instead of additional variant/element system layer
  • Possibility to group nodes and materials using tags
  • Automatic application of stored tag values when nodes and materials get added to the scene
  • Possibility to add custom observers before bootstrapping

The tag manager is the successor of the legacy parameter system. Since node grouping is a main task of the tag manager it makes the "variant/element" system obsolete as well. Technically the Spec with it's "variant/element" and parameter system is still available, but it is recommended to use the tag manager instead, whenever possible. Have a look at Auto Spec docs to find out how to avoid using the Spec.

How to use it

NOTE: The tag manager works for NODES and MATERIALS alike, but for the sake of simplicity the main part of this documentation is about NODES, as this will be the main use case of the tag manager. MATERIALS are covered in a dedicated chapter at the end of this page.

Preparation

The tag manager of course only works, if tags are set on the desired nodes. Have a read in the Babylon.js docs to get familiar with this feature in general.

Most common 3d tools are able to export tags along with it's nodes. If the viewer is used in a Combeenation configurator, you can also adapt the tags in the babylon asset editor.

Basic functionality

Altering certain node data is as easy as calling setTagParameterValue. The viewer will extract all nodes that share the desired tag value and alters these nodes according to the given parameter and value.

import { Parameter } from '@combeenation/3d-viewer';

// shows all nodes that have the tag "wheel" set
viewer.tagManager.setTagParameterValue('wheel', Parameter.VISIBLE, true);

It's also possible to set multiple tag values simultaniously, using setParameterValues

// show nodes with tags "seat" and "wheel" and also move "wheel" nodes to a different position
viewer.tagManager.setParameterValues([
{ tagName: 'seat', parameterName: Parameter.VISIBLE, value: true },
{ tagName: 'wheel', parameterName: Parameter.VISIBLE, value: true },
{ tagName: 'wheel', parameterName: Parameter.POSITION, value: '(1, 0, 0)' },
]);

Built-in parameters

Similar to the legacy parameter system, there are so-called "built-in parameters" that are already implemented by the system. These are:

For example the parameter handler for the visibility will just call the Babylon.js function setEnabled for each node that is affected by the given tag value.

Transformation parameters (position, rotation & scaling) will also just set the input value on the corresponding node property. Therefore the final outcome strongly depends on the parent nodes transformation and the nodes pivot point.

It's also worth noting that the parameters will not be applied to sub childs of tagged nodes. In this way the user has full control over the node states and can use the node tree structure to its advantage. If a sub child needs special treatment just set an according tag.

Custom parameters

Since some scenarios can not be handled with built-in parameters, the user has the possibility to create custom parameter observers as well. This can be done by using the setParameterObserver function.

import { Texture } from '@combeenation/3d-viewer';
import { setMaterialTexture } from '@combeenation/3d-viewer/dist/lib-cjs/api/util/babylonHelper';

viewer.tagManager.setParameterObserver('CustomTextureParam', async ({ nodes, newValue }) => {
// create the texture from the input value
const texture = new Texture(/** @type {string} */ (newValue));

// go through each node and set the created texture
nodes.forEach(node => setMaterialTexture(node, texture));

return true;
});

The parameter observer will be triggered with setTagParameterValue or setParameterValues, just like it can be done with built-in parameters.

viewer.tagManager.setTagParameterValue('wheel', 'CustomTextureParam', 'https://some-texture-url...');

A parameter can only have one observer! If setParameterObserver is called a second time for the same parameter, the first observer will be removed. In this way you can also overwrite built-in parameter observers.

Set initial tag values

In most cases you want to startup the viewer with a certain parameter set, so that the viewer can already take the values into account when bootstrapping. Therefore the bootstrap function can now be enhanced with an initial parameter set on the tagManagerParameterValues input.

Keep in mind to register your custom parameter observers before the bootstrap function, so that the initial tag values can be considered immediatly.

const viewer = new Viewer(canvas, createSpec());

viewer.tagManager.setParameterObserver('CustomTextureParam', async ({ nodes, newValue }) => {
const texture = new Texture(/** @type {string} */ (newValue));
nodes.forEach(node => setMaterialTexture(node, texture));
return true;
});

// provide initial tag values in the same format as for `setParameterValues` function
await viewer.bootstrap([
{ parameterName: 'CustomTextureParam', value: 'https://some-texture-url...', tagName: 'wheel' }
]);

Variant cloning

Per default, tags are cloned along with all nodes inside the variant when calling the clone function. This may be the desired behaviour in some cases, if you want to treat original and cloned nodes as a combined group.

In other cases you may want to have dedicated groups for each clone, which require the tags of the clone to be renamed. This can be done with the new tagMapping input of the clone function. This input requires an object with the original tag name as key and the new tag name as value.

await viewer.variantInstances.clone(
'WheelOrig',
'WheelClone',
// leave legacy parameters empty when cloning the variant
{},
// rename `wheel` tag to `wheelCloned` for nodes inside the cloned instance
{
wheel: 'wheelCloned',
}
);

// only the cloned wheel should be moved to (5, 0, 0)
await viewer.tagManager.setTagParameterValue('wheelCloned', Parameter.POSITION, '(5, 0, 0)');

Synchronization of stored tag states

Whenever tag values are set (eg: setTagParameterValue, setParameterValues), the tag values are persisted in a dedicated store. If new nodes are loaded into the scene, the values from the tag store are applied on the nodes immediatly, so that node data is synced with the already existing nodes in the scene.

Let's make an example:

  • Node with tag wheel is available and visible in the scene
  • Call setTagParameterValue('wheel', Parameter.VISIBLE, false) to hide the node
  • Import new node into scene (eg: by showing a new variant instance), which also has the tag wheel set
  • => The new node gets hidden immediatly, as the visibility state of the wheel tag is false!

Accessing nodes without tags

In some cases you will probably set a tag only on one node, for example when handling the visibility of a whole node tree branch by controlling the parent node. In this case the tag doesn't really make sense, because nothing has to be grouped. However the tag is required to be able to use the tag manager though.

For this use case parameter values can be set on nodes directly, using the nodeName instead of a tagName.

viewer.tagManager.setTagParameterValue('tagNameWheel', Parameter.VISIBLE, true);
// vs
viewer.tagManager.setNodeParameterValue('nodeNameWheel', Parameter.VISIBLE, true);

viewer.tagManager.setParameterValues([
{ tagName: 'tagNameWheel', parameterName: Parameter.VISIBLE, value: true },
// vs
{ nodeName: 'nodeNameWheel', parameterName: Parameter.VISIBLE, value: true },
]);

In general node parameters are treated exactly the same way as tag parameters. That means features like custom parameters, setting initial parameters before bootstrapping and synchronization of stored node tag states are implemented as well.

Nodes vs tags

Accessing nodes directly instead of working with corresponding tags has the advantage, that the 3d models don't have to be extended with tags. In this case most 3d models can be used directly, without having to be modified by 3d artists or with the tag editor on the Combeenation platform.

Working with tags is certainly preferred when parameters should be applied on multiple nodes. In this way the parameter has to be set just once instead of having to go through all affected nodes.

Furthermore tags can be forwarded to cloned variants. If you want to set the same parameter values on the clone and the original variant, you can do this by accessing the dedicated tag of the original variant. This however is not possible for nodes, as node names always have to be unique and therefore the cloned variant will have different node names than the original one.

Uniqueness of node and tag names

Whilst it is no hard restriction of Babylon.js, we strive for unique naming of nodes, in order to avoid confusion and undesired side effects. Therefore each time a node is created in the scene by the viewer, a certain naming strategy is used. Nodes are created by the viewer in the following scenarios:

  • Creating elements from the Spec
  • Cloning existing variant instances

Default naming strategy

The first time a node gets added, the original name will not be altered! In this way it's easier for the user to recognize the original node structure from the 3d model.

However if a node is added multiple times by creating additional Spec elements and variant instances or by using the clone function, a name clash would occur and therefore the new name is optionally suffixed with the element and variant instance name.

Custom naming strategy

If required, you can also adapt this strategy by overwriting the nodeNamingStrategyHandler. You'll receive the variant, variant instance and variantParameterizable (aka element) as payload and can build your desired node name strategy accordingly.

The node renaming handling will only be triggered if nodes are created by the viewer API. If you add nodes manually, for example by calling Babylon.js clone function, the renaming strategy will not be applied and duplicate node names may occur!

Tags of variant instance clones

In some cases you want the cloned instance to have the exact same tags as the original one. In this way the nodes of the original and cloned instance can be treated as one group. In other cases you want the cloned instance to have different tags, so that the clone can be adjusted seperately.

However you can achieve both results by using the tagMapping parameter of the clone function. If the parameter is left empty, the tags from the old instance are copied onto the clone. If you want to have renamed tags, you can set up the tagMapping object in the following way.

{
wheelTag: 'clonedWheelTag',
seatTag: 'clonedSeatTag',
}

In this way all nodes that had the tags wheelTag and seatTag will have new tags clonedWheelTag and clonedSeatTag after cloning.

It's also worth mentioning that the tag manager state of the original tags will also be copied. In this case the clone looks exactly the same than the original instance initially. However the two instances will be altered differently, depending on the dedicated node and tag parameters, that will be applied later on.

Materials

The tag manager also works for materials, since tags can also be set on material objects in Babylon.js. Simliar to nodes, there are 3 ways to set a material parameter:

// set single material parameter
viewer.tagManager.setMaterialParameterValue('matWindow', Parameter.COLOR, '#DD0060');
// set material parameter via associated tag
// => tag 'windowTag' has to be available on a material
viewer.tagManager.setTagParameterValue('windowTag', Parameter.COLOR, '#DD0060');
// set parameter in bulk call
viewer.tagManager.setParameterValues([
{ materialName: 'matWindow', parameterName: Parameter.COLOR, value: '#DD0060' },
{ tagName: 'seat', parameterName: Parameter.VISIBLE, value: true },
{ nodeName: 'wheel', parameterName: Parameter.VISIBLE, value: false },
]);

As shown in the example above, it is possible to mix different "subjects" in the setParameterValues bulk call. The tag manager automatically applies the parameter value to the dedicated subject (material, node or tag).

Furthermore it is also possible to add the same tag to a node and a material. Whilst this sounds a bit theoretic at first glance, it can be used to advantage in combination with custom observers, where you can apply different logic to node tags and material tags as shown in the example below.

Built-in parameters

The following material parameters are supported:

The tag manager will set the corresponding properties on the material object. Parameter values will also be stored if the target material is not available in the scene yet. If the material gets added, the tag manager state will be synced onto this material.

Custom parameters

Materials can also be targeted in custom observers, as shown in the following example.

viewer.tagManager.setParameterObserver('CustomTextureParam', async ({ materials, newValue }) => {
const texture = new Texture(/** @type {string} */ (newValue));
materials.forEach(material => material.albedoTexture = texture);
return true;
});

If the parameter change is triggered by a tag, the observer may contain nodes and materials at the same time.

viewer.tagManager.setParameterObserver('CustomTextureParam', async ({ nodes, materials, newValue }) => {
const texture = new Texture(/** @type {string} */ (newValue));

// set texture on targeted nodes
nodes.forEach(node => setMaterialTexture(node, texture));
// set texture on targeted material directly
materials.forEach(material => material.albedoTexture = texture);

return true;
});

// called by
viewer.tagManager.setTagParameterValue('textureTag', 'CustomTextureParam', 'someTextureUrl');

Generated using TypeDoc