# Model

> Reference for @apricot/react/model: the Model component and every prop, defineModelKit, part selectors, JointPart and InlineJoint, ClipInput, PartPressEvent, useModel, useTexture, useTextureState, TextureOptions, loadModelBytes and the model and texture caches.

```ts
import { defineModelKit, Model, useModel, useTexture, useTextureState } from "@apricot/react/model";
import type { ClipInput, JointInput, JointPart, KitPart, ModelKit, PartPressEvent } from "@apricot/react/model";
```

Everything on this page comes from the **`@apricot/react/model`** entry point, not `@apricot/react`. It carries three's glTF loader (about 146 KB), which apps without models do not need.

## `Model`

Shows a `.glb` file. Renders **nothing, children included, until it has loaded**.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` | `number \| string` | | A bundled asset (`require("./car.glb")`) or a URL. Or give `kit` |
| `kit` | `ModelKit` | | A model plus names for its parts (`defineModelKit`). Its names autocomplete in `materials`, `joints` and `onPartPress` |
| `materials` | `{ [part or selector]: MaterialInput }` | | Material per part: a preset name, a preset with options, or a three.js material. Colour changes animate. A key matching nothing logs a warning |
| `joints` | `{ [joint or selector]: JointInput }` | | Moving parts, 0 (rest) to 1 (open). Plain numbers animate; shared values are followed exactly |
| `animations` | `{ [clip name]: ClipInput }` | | Blender animation clips: progress 0–1, `"once"` or `"loop"` |
| `morphs` | `{ [shape key]: number or shared value }` | | Shape keys, 0 (off) to 1 (full). Plain numbers animate over 0.4 s |
| `onPartPress` | `(event: PartPressEvent) => void` | | A part was tapped |
| `inspect` | boolean | `false` | Development aid: tap parts to highlight them and see their names and materials |
| `ground` | boolean | `false` | Stands the model on the floor: its lowest point at y = 0, measured once at rest |
| `castShadow`, `receiveShadow` | boolean | automatic | Overrides for every mesh, with `<Scene shadows>` |
| `ref` | `Ref<SceneObject>` | | The model's root group |
| `onLoad` | `(object: THREE.Group) => void` | | Loaded and shown |
| `onError` | `(error: Error) => void` | logs | Failed to load |
| `children` | `ReactNode` | | Placed in the model's space; shown once it has loaded (a `<CameraRig>`, a `<Label>`) |

Plus transform props (`position`, `rotation`, `scale`; plain or shared values).

A model used by several components loads once and is shared. Each `<Model>` gets its own copy of the scene graph, so materials, joints and animations apply per copy. Copies of rigged models get their own skeleton. Unmounting restores the shared file's original materials.

## `defineModelKit(src, parts)`

```ts
const sedan = defineModelKit(require("./sedan.glb"), {
  paint: "material:vernice",
  wheels: "ruota_*",
  doorLeft: { select: "porta_ant_sx", hinge: "front", axis: "y", range: [0, -65] },
});
```

Returns a `ModelKit<P>` (`{ src, parts }`) where `P` is the union of your part names. A part (`KitPart`) is a selector or a `JointPart`.

### Selectors (`PartSelector`)

| Selector | Picks |
| --- | --- |
| `"wheel_*"` | Parts (nodes) **or** materials whose name matches. `*` any characters, `?` one. Case-insensitive |
| `"node:Lid"` | Nodes with that name, and everything under them |
| `"material:car_paint*"` | Only material slots with that name (the name in the file, even after a preset replaced it) |
| `"tag:door"` | Nodes with the Blender custom property `apricot` = `door` (glTF `extras`, so `userData.apricot`) |
| `["glass", "window_*"]` | Any of several |

Find names with `npx apricot-models inspect file.glb` or `<Model inspect>`.

### `JointPart`

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `select` | `PartSelector` | required | The part(s) that move together |
| `hinge` | words or `Vec3` | `"center"` | Where it turns: an edge or corner of the part's bounds at rest (`front`, `back`, `top`, `bottom`, `left`, `right`, `center`, combined as `"bottom-back"`), or an exact point in the part's parent space |
| `axis` | `"x" \| "y" \| "z" \| Vec3` | `"y"` | Rotation or slide axis, in the parent's space |
| `range` | `[at0, at1]` degrees | `[0, 90]` | Angle at value 0 and at value 1 |
| `slide` | `[at0, at1]` units | | Slide along the axis instead of turning |
| `transition` | seconds | `0.6` | How long a plain value change takes |

The model faces +z: `front` is +z, `top` is +y, `left` is +x.

### `JointInput`

A value (`number` or shared value), or an `InlineJoint` that describes the joint where it is used: `{ value, select?, hinge?, axis?, range?, slide?, transition? }`. `select` defaults to the key.

```tsx
<Model src={box} joints={{ "node:Lid": { value: open ? 1 : 0, hinge: "bottom-back", axis: "x", range: [0, -110] } }} />
```

## `ClipInput`

`number | SharedValue<number> | "once" | "loop"`. A number shows that point of the clip (plain numbers animate there over 0.6 s; shared values scrub exactly).

## `PartPressEvent`

| Field | Type | Description |
| --- | --- | --- |
| `part` | `P \| undefined` | The kit name: a joint that includes the mesh, else the most specific kit name. `undefined` without a kit or for unnamed parts |
| `parts` | `P[]` | Every kit name whose selector includes the mesh |
| `name` | `string` | The tapped mesh's name in the file |
| `material` | `string` | The tapped material's name in the file |
| `wearing` | `string \| undefined` | The preset it wears now, if replaced |
| `mesh` | `THREE.Mesh` | |
| `point` | `THREE.Vector3` | World-space point hit |

## `useModel(src)`

Loads a model without showing it. Returns `{ object, animations, error }`: `object` is a `THREE.Group` (or `null` while loading), `animations` its clips. The file loads once per app; `object` is this component's own copy of it (geometries and materials stay shared, so do not dispose them). The model is freed when its last user unmounts. Pass `object` to `<Instances object>` for many copies, or to `<Primitive object>`.

## `useTexture(src, options?)` and `useTextureState(src, options?)`

`useTexture` loads a PNG or JPEG (a `require` or a URL) and returns a `THREE.Texture`, or `null` while it loads (and on error, which it logs). `useTextureState` returns `{ texture, error }`. Textures are shared between components and freed when the last user unmounts.

### `TextureOptions`

| Option | Default | Description |
| --- | --- | --- |
| `colorSpace` | `SRGBColorSpace` | `NoColorSpace` (from three) for data such as masks and normal maps |
| `flipY` | `true` | Image rows top to bottom map to v = 1 … 0, as three expects |
| `repeat` | `false` | Tile instead of clamping at the edges |
| `premultiplyAlpha` | `true` | Keep colours premultiplied (what phone decoders produce). Pass `false` only for your own three.js materials that expect straight colour |

## Lower level

| Export | Description |
| --- | --- |
| `loadModelBytes(src)` | `Promise<ArrayBuffer>` of a bundled asset or URL |
| `modelCache` | The shared `ModelCache` behind `useModel` and `<Model>` |
| `textureCache` | The shared cache behind `useTexture` |
| `ModelSource` | `number \| string` |

## Related

- [Loading models](https://matthiasbrandolin.dev/apricot/guides/models).
- [Textures](https://matthiasbrandolin.dev/apricot/guides/textures).
- [Tooling](https://matthiasbrandolin.dev/apricot/reference/tooling): `apricot-models`.
