Skip to main content

<pc-model>

The <pc-model> tag is used to define an entity that instantiates a 3D model from a GLB file.

For a walkthrough of the whole workflow — exporting, compressed meshes, discovering what a file contains and adjusting it — see Loading Models.

Usage
  • It must be a direct child of a <pc-scene> or a <pc-entity>.
  • It can have 0..n <pc-node> children, each binding to a node inside the instantiated hierarchy to override it, add components to it or attach new content under it.

Attributes

All attributes of <pc-entity> are also available.

AttributeTypeDefaultDescription
assetString-Container asset ID (must reference a container type asset)

Events

Listen to these events using addEventListener() or by assigning an event listener to the oneventname property of this interface.

EventDescription
loadFired each time the container asset finishes instantiating, including a re-instantiation after asset changes.
errorAn ErrorEvent fired when the container asset fails to load, with the engine's error in message.

Neither event bubbles, so listen on the element itself — or use a capture-phase listener on an ancestor to observe every model on the page.

The element becomes ready once its hierarchy has been instantiated and added to the scene, so a ready <pc-model> always has a non-null entity with valid world transforms. A failed load also settles readiness, with entity left null — readiness means the load settled, not that it succeeded, so listen for error (or check entity) to tell the two apart.

Animation

A container's animations play when you nest a <pc-anim> inside the model. One empty tag is enough to get what the file came with — every animation in the container becomes a clip, named after its track, and the first one starts playing:

<pc-entity name="robot">
<pc-model asset="robot">
<pc-anim></pc-anim>
</pc-model>
</pc-entity>

The <pc-entity> wrapper is not decoration: the component attaches to the nearest enclosing entity rather than to the instantiated root, so a <pc-model> placed directly under a <pc-scene> has nowhere to put it and warns instead. Add <pc-anim-clip> children to name the clips yourself, set a per-clip speed or looping, or take clips from other files.

To check whether a file's animations survived its export, ask the component what it found:

import { whenReady } from '@playcanvas/web-components';

const anim = await whenReady('pc-anim');
console.log(anim.clips); // ['Walk', 'Idle']

Importing by package name needs @playcanvas/web-components in your page's import map — see Programmatic Access. A container with no animations in it logs a warning naming the model, so the console answers the same question without any code.

Example

A GLB with a skeletal animation, played by the <pc-anim> nested inside the model. Drag to orbit:

Live Example
<pc-app>
<pc-asset src="https://cdn.jsdelivr.net/npm/playcanvas@2.21.4/scripts/esm/camera-controls.mjs"></pc-asset>
<pc-asset src="https://developer.playcanvas.com/assets/t-rex.glb" id="t-rex"></pc-asset>
<pc-material id="floor" diffuse="#3a3f4b"></pc-material>
<pc-scene>
<pc-entity name="camera" position="2.5 1.5 3.5">
<pc-camera clear-color="#2a2d36"></pc-camera>
<pc-scripts>
<pc-script name="cameraControls" focus-point="0 1.2 0" pitch-range="-90 0" zoom-range="1.5 10"></pc-script>
</pc-scripts>
</pc-entity>
<pc-entity name="light" rotation="45 30 0">
<pc-light cast-shadows shadow-distance="20" intensity="1.5"></pc-light>
</pc-entity>
<pc-entity name="ground" scale="30 30 30">
<pc-render type="plane" material="floor"></pc-render>
</pc-entity>
<pc-entity name="t-rex" scale="1.5 1.5 1.5">
<pc-model asset="t-rex">
<pc-anim></pc-anim>
</pc-model>
</pc-entity>
</pc-scene>
</pc-app>

To reach inside the loaded hierarchy, nest a <pc-node> for each node you want to change:

<pc-model asset="car">
<!-- Hide the ground plane the GLB was exported with -->
<pc-node name="Plane" enabled="false"></pc-node>
</pc-model>

JavaScript Interface

You can programmatically create and manipulate <pc-model> elements using the ModelElement API.

Inspecting the Hierarchy

The hierarchy() method reports the instantiated tree as it actually exists, which is the vocabulary a <pc-node> resolves against — and not necessarily what the source asset's node names suggest. Loading Models works through a real example; this is the reference. Printing it is one line:

import { whenReady } from '@playcanvas/web-components';

const model = await whenReady('pc-model');
console.log(String(model.hierarchy()));
Car
├─ FrontAxle
│ └─ Wheel [0] (render) {defaultGlbMaterial}
├─ RearAxle
│ └─ Wheel [1] (render) {defaultGlbMaterial}
├─ Wing
└─ Wing1

Each line is a node: its name, then [index] when other nodes share that name, the component types it carries in parentheses, and the materials of a render component in braces. So the two wheels above are reached with <pc-node name="Wheel" index="0"> and <pc-node name="Wheel" index="1">, and Wing1 is the engine renaming a second Wing sibling apart as it built the hierarchy.

hierarchy() returns the root node of a plain-data tree, or null while there is nothing instantiated — before the container asset has loaded, after a load failed, or once the element has left the document. Every node carries:

PropertyTypeDescription
nameStringThe node's name as instantiated, which is the name a <pc-node> looks up. It can differ from the name in the source asset: the engine synthesizes node_<index> names for unnamed nodes and renames identically named siblings apart
pathStringThe node's /-separated path below the model root, which is the path a <pc-node> bound to it reports. The root's path is its own name
indexNumberThe node's position among the nodes sharing its name, counted in depth-first order over the whole model — exactly the match a <pc-node>'s index selects
componentsString[]The types of the components attached to the node (such as render), sorted
materialsObject[]One { index, name } entry per mesh instance of the node's render component, in component order. Empty for a node without one
childrenObject[]The node's child nodes
toString()FunctionRenders the subtree rooted at this node as the printable tree above, so String(node) prints any branch

The material name values are runtime labels read as they stand, which makes them a convenient handle but not a unique one: an unnamed glTF material is called Untitled, a primitive authored without a material carries the engine's shared defaultGlbMaterial, duplicates stay duplicated, and the name is null if a script cleared the assignment. The index is the unambiguous one. Both are what <pc-node>'s material-overrides selects with, and a <pc-material> you swap in reports whatever its name attribute says — worth setting on any material you want to recognize here.

The tree is a snapshot, computed afresh on each call: it does not track later changes to the hierarchy, and mutating it changes nothing. Being plain data, it survives JSON.stringify, so it is easy to log, diff or assert against in a test.