5.9.2

From model to component

Why a .glb becomes a Vue single-file component, told through one team shipping one robot.

tres gltf exists because a loaded model is a black box, and a Vue component is not. This page follows one small team through the difference. If you want the reference instead, go to tres gltf.

The team

Two people ship a product configurator. An artist owns robot.glb in Blender and re-exports it several times a week. A Vue developer owns the app and has never opened Blender. Both want the same thing: change the model without breaking the app, and change the app without touching the model.

Day one: the black box

The developer loads the model the usual way and drops the scene into the canvas:

App.vue
<script setup lang="ts">
import { useGLTF } from '@tresjs/cientos'

const { scene, nodes } = await useGLTF('/models/robot.glb')
</script>

<template>
  <primitive :object="scene" />
</template>

It renders. Then the first ticket arrives: the head should glow when hovered. The developer now needs one mesh out of the scene graph, and the only handle is a string:

const head = nodes.Head // Object3D, maybe. Is it a Mesh? Does it exist?
;(head as Mesh).material = hologram

Three things are true about this line, and none of them are visible in the editor:

  • nodes.Head is typed as a generic Object3D. The cast to Mesh is a guess.
  • The name Head came from a Blender outliner the developer cannot see.
  • Nothing checks the string. If it is wrong, head is undefined at runtime and the page throws.

The developer gets it working by logging nodes to the console and copying names out of it. The app ships.

Day two: the re-export

The artist splits the head into Helmet and Face and re-exports. No code changed, so no test fails and no type error appears. The build is green. In the browser, the head no longer glows, and in production it throws on undefined.material.

This is the failure tres gltf is built around. The contract between the model and the app lived in a string, and a string cannot tell you when it goes stale.

Running the command

The developer generates a component from the model instead:

tres gltf public/models/robot.glb
# ▲ ■ ● Tres  gltf  robot.glb
#
# ✔ Parse       12 named nodes · 3 meshes · 2 materials    34ms
# ✔ Emit        3 slots                                     1ms
#
# ✔ src/models/Robot.gen.vue
#   slots  Helmet, Face, Body, Base
#
# Done in 41ms

The output is a .vue file. That single fact changes what the team can do with the model:

  • Read it. Every node is a <TresMesh> or <TresGroup> with its transform written out. The developer sees the structure of the model without opening Blender.
  • Diff it. The next re-export produces a new file, and the pull request shows exactly which nodes moved, appeared or disappeared. The artist's change is reviewable like any other change.
  • Type-check it. The component declares the nodes, materials and clips the model actually has. The rest of the app compiles against that declaration.

Overriding without owning

The generated file is disposable. The head glow does not go in it. It goes in the parent, as a slot override, and the generated markup stays as the fallback:

App.vue
<script setup lang="ts">
import Robot from '@/models/Robot.gen.vue'
import { hologram } from './materials'
</script>

<template>
  <Robot>
    <template #Helmet="{ node }">
      <TresMesh :geometry="node.geometry" :material="hologram" @pointerenter="glow" />
    </template>
  </Robot>
</template>

node here is a Mesh, not an Object3D, because the generator saw a mesh at that node and said so. The cast is gone. The console logging is gone. And the next re-export overwrites Robot.gen.vue without touching App.vue, so the override survives every export the artist makes.

The rename becomes a type error

On day two the artist renamed a node and the app broke in production. With the component, the same rename fails in the editor:

App.vue:9:15 - error TS2339: Property 'Head' does not exist on type
  '{ Helmet: (props: { node: Mesh }) => any; Face: …; Body: …; Base: … }'.

The developer reads the new slot names off the error, updates the override, and the failure never reaches a browser. The contract now lives in a type, and types go stale loudly.

Animations with names

The artist adds an idle loop. The generated component picks up the clip and hands the bound actions to @ready, keyed by a union of the clip names:

App.vue
<Robot @ready="({ actions }) => actions.Idle?.play()" />

actions.Idle autocompletes. actions.Idel is a compile error. When the artist ships the clips in separate files, as Mixamo and most asset packs do, --animations merges them and the CLI checks each clip's tracks against the model's node names, which is the one animation failure that is otherwise silent at runtime.

Physics without a spreadsheet

The configurator grows a showroom floor the robot can fall onto. Instead of the developer measuring boxes and typing collider sizes, the artist names the meshes in Blender: Floor-col, Crate-rigid, Trigger-sensor. The command reads the names:

tres gltf public/models/showroom.glb --physics rapier

Each suffixed mesh becomes a RigidBody sized from its own geometry when the model loads. The artist reshapes the floor, the collider follows, and no number in the codebase has to be updated.

Shipping smaller, shipping many

Two flags close the loop for production:

  • --transform runs the model through glTF-Transform and points the component at a robot-transformed.glb that is typically 70 to 90 percent smaller. The original stays untouched.
  • --instance batches repeated meshes into an InstancedMesh, so the showroom can render twenty robots for the draw calls of one.

Both are decisions the developer makes once, in the command, and the artist never sees them.

What changed for the team

BeforeAfter
Where a node's name livesIn a string the developer typedIn a slot the compiler checks
What a renamed node doesThrows in productionFails to compile
Where an override livesInside a traverse() over the sceneIn the parent, as a slot
What a re-export costsRe-test every override by handRe-run one command, read the diff
Who authors physicsThe developer, with numbersThe artist, with names
What a reviewer seesA new binary in the PRA readable .vue diff

The artist still exports from Blender. The developer still writes Vue. The command sits between them and turns the handoff into a type.

Every flag, the generated file in full, and the edge cases are in the tres gltf reference.