Runtime material swap for any
SceneMesh — pick a procedural painter
from a curated catalog and the palette repaints the mesh's
material in place.
The materials module wraps every applicable
model!procgen.paintMaterials | procgen/paintMaterials painter into a
lookup table the host can drive from a context menu or button bar.
Per-(SceneModel, painter) SceneMaterials
are created on first use and shared across every mesh that adopts
them — applying the same painter to a thousand objects produces
one material, not a thousand.
Material swapping uses the SDK's supported mutation pattern:
snapshot the mesh's params (id, geometryId, matrix, opacity,
parentTransform), detach + destroy the old mesh, then create a
fresh mesh with the same id bound to the palette's
SceneMaterial and re-attach to the
source SceneObject. The object keeps
its identity; only the mesh's material binding changes.
Shape
The palette sits over a small cast of types: a MaterialsPalette
owns a list of PainterCatalogEntry entries, delegates the actual
texture work to the procgen painters in
procgen!materials.paintMaterials | paintMaterials, and reuses each
resulting SceneMaterial through an
internal per-SceneModel cache.
%%{init:{"theme":"dark"}}%%
classDiagram
direction TB
class MaterialsPalette {
+catalog : PainterCatalogEntry[]
+textureSize : number
+uvScale : number
+painterIds() string[]
+getEntry(id) PainterCatalogEntry
+paintMaterial(mesh, id) SDKResult
}
class PainterCatalogEntry {
+id : string
+label : string
+category : string
+paint(size) MaterialMaps
+material : MaterialOverrides
}
class MatCache {
<<internal>>
+get(model, id) SceneMaterial
}
class SceneMesh {
<<scene>>
}
class SceneMaterial {
<<scene>>
}
class paintMaterials {
<<procgen>>
}
MaterialsPalette "1" *-- "*" PainterCatalogEntry : catalog
MaterialsPalette "1" *-- "1" MatCache : reuses materials
MaterialsPalette ..> paintMaterials : invokes painters
MaterialsPalette ..> SceneMesh : repaints
MatCache ..> SceneMaterial : shared per SceneModel + painterId
%%{init:{"theme":"default"}}%%
classDiagram
direction TB
class MaterialsPalette {
+catalog : PainterCatalogEntry[]
+textureSize : number
+uvScale : number
+painterIds() string[]
+getEntry(id) PainterCatalogEntry
+paintMaterial(mesh, id) SDKResult
}
class PainterCatalogEntry {
+id : string
+label : string
+category : string
+paint(size) MaterialMaps
+material : MaterialOverrides
}
class MatCache {
<<internal>>
+get(model, id) SceneMaterial
}
class SceneMesh {
<<scene>>
}
class SceneMaterial {
<<scene>>
}
class paintMaterials {
<<procgen>>
}
MaterialsPalette "1" *-- "*" PainterCatalogEntry : catalog
MaterialsPalette "1" *-- "1" MatCache : reuses materials
MaterialsPalette ..> paintMaterials : invokes painters
MaterialsPalette ..> SceneMesh : repaints
MatCache ..> SceneMaterial : shared per SceneModel + painterId
classDiagram
direction TB
class MaterialsPalette {
+catalog : PainterCatalogEntry[]
+textureSize : number
+uvScale : number
+painterIds() string[]
+getEntry(id) PainterCatalogEntry
+paintMaterial(mesh, id) SDKResult
}
class PainterCatalogEntry {
+id : string
+label : string
+category : string
+paint(size) MaterialMaps
+material : MaterialOverrides
}
class MatCache {
<<internal>>
+get(model, id) SceneMaterial
}
class SceneMesh {
<<scene>>
}
class SceneMaterial {
<<scene>>
}
class paintMaterials {
<<procgen>>
}
MaterialsPalette "1" *-- "*" PainterCatalogEntry : catalog
MaterialsPalette "1" *-- "1" MatCache : reuses materials
MaterialsPalette ..> paintMaterials : invokes painters
MaterialsPalette ..> SceneMesh : repaints
MatCache ..> SceneMaterial : shared per SceneModel + painterId
Mesh repaint sequence
A repaint call routes through a catalog lookup, an optional first-time
material build (painter → textures → material, cached), and the SDK's
supported mutation pattern for swapping a mesh's material binding —
snapshot the mesh's params, destroy the old mesh, then recreate it
with the same id and re-attach it to its SceneObject.
%%{init:{"theme":"dark"}}%%
flowchart TD
A[paintMaterial mesh painterId] --> B[lookup entry by id]
B --> C{material cached for SceneModel?}
C -- no --> D[call painter → MaterialMaps]
D --> E[create SceneTextures]
E --> F[create SceneMaterial — cache as materialId]
C -- yes --> G[reuse cached materialId]
F --> H[snapshot mesh params]
G --> H
H --> I[detach + destroy old mesh]
I --> J[create new mesh — same id, new materialId]
J --> K[re-attach to SceneObject]
%%{init:{"theme":"default"}}%%
flowchart TD
A[paintMaterial mesh painterId] --> B[lookup entry by id]
B --> C{material cached for SceneModel?}
C -- no --> D[call painter → MaterialMaps]
D --> E[create SceneTextures]
E --> F[create SceneMaterial — cache as materialId]
C -- yes --> G[reuse cached materialId]
F --> H[snapshot mesh params]
G --> H
H --> I[detach + destroy old mesh]
I --> J[create new mesh — same id, new materialId]
J --> K[re-attach to SceneObject]
flowchart TD
A[paintMaterial mesh painterId] --> B[lookup entry by id]
B --> C{material cached for SceneModel?}
C -- no --> D[call painter → MaterialMaps]
D --> E[create SceneTextures]
E --> F[create SceneMaterial — cache as materialId]
C -- yes --> G[reuse cached materialId]
F --> H[snapshot mesh params]
G --> H
H --> I[detach + destroy old mesh]
I --> J[create new mesh — same id, new materialId]
J --> K[re-attach to SceneObject]
Features
Curated default catalog — every applicable
model!procgen.paintMaterials | procgen/paintMaterials painter is
pre-registered in a default catalog (brick, concrete, marble,
oak, polished steel, copper, gold, glass, …), grouped into four
coarse categories ("Masonry", "Interior", "Metal",
"Glass") so menus can group entries naturally.
Per-(SceneModel, painter) sharing — repainting a thousand
meshes with the same painter creates one
SceneMaterial, not a thousand.
Materials are cached in a WeakMap keyed by SceneModel, so a
destroyed SceneModel drops its entries without explicit
cleanup.
Hatch passthrough — each catalog entry carries the painter's
engineering hatch pattern (ANSI 32 steel, ANSI 36 brick, ISO
concrete, etc.); the painted material binds the hatch into
hatchPattern, so section caps and Detailed-mode body shading
pick it up automatically.
Transparent dielectrics — glass + other transparent
painters carry opacity / alphaMode: "BLEND" in their entry,
so swapping to glass produces a translucent material without
per-call overrides.
Triplanar by default — painted materials forward
uvScale as
triplanarScale, so painters apply correctly to UV-less BIM
geometry via the renderer's triplanar fallback.
In-place repaint — paintMaterial keeps the source
SceneObject intact; only the mesh's
material binding changes, via the SDK's supported
detach + destroy + recreate + reattach pattern.
Custom catalogs — pass catalog to the constructor to
replace the default with a host-defined set of painter entries
(e.g. a project-specific palette of branded materials).
With no params, the palette ships with the full default catalog.
constpalette = newMaterialsPalette();
Or override texture size and UV scale:
constpalette = newMaterialsPalette({ textureSize:512, // sharper close-up texturing uvScale:0.5// tile half as often (larger pattern) });
3) Repaint one mesh
Pass a SceneMesh and a painter id. The
returned SDKResult carries the new (replacement) SceneMesh on
success.
constresult = palette.paintMaterial(mesh, "brick"); if (!result.ok) console.warn(result.error);
4) Repaint every mesh under a SceneObject
paintMaterial returns a fresh SceneMesh each call; iterate the
SceneObject's meshes array once via a snapshot so the loop
isn't disturbed by the in-flight detach + reattach.
for (constmof [...sceneObject.meshes]) { constr = palette.paintMaterial(m, "polSteel"); if (!r.ok) console.warn(r.error); }
5) Drive from a UI menu
painterIds returns the
registered ids in catalog order; getEntry
looks up an entry for label / category metadata. Together they're
enough to build a context-menu submenu grouped by category.
The palette is stateless across SceneModels — its material cache
is a WeakMap, so destroying a SceneModel automatically drops its
cached materials. The palette itself has no destroy method;
drop the reference when you're done.
xeokit Materials Palette
Runtime material swap for any SceneMesh — pick a procedural painter from a curated catalog and the palette repaints the mesh's material in place.
The
materialsmodule wraps every applicable model!procgen.paintMaterials | procgen/paintMaterials painter into a lookup table the host can drive from a context menu or button bar. Per-(SceneModel, painter) SceneMaterials are created on first use and shared across every mesh that adopts them — applying the same painter to a thousand objects produces one material, not a thousand.Material swapping uses the SDK's supported mutation pattern: snapshot the mesh's params (id, geometryId, matrix, opacity, parentTransform), detach + destroy the old mesh, then create a fresh mesh with the same id bound to the palette's SceneMaterial and re-attach to the source SceneObject. The object keeps its identity; only the mesh's material binding changes.
Shape
The palette sits over a small cast of types: a MaterialsPalette owns a list of PainterCatalogEntry entries, delegates the actual texture work to the procgen painters in procgen!materials.paintMaterials | paintMaterials, and reuses each resulting SceneMaterial through an internal per-
SceneModelcache.Mesh repaint sequence
A repaint call routes through a catalog lookup, an optional first-time material build (painter → textures → material, cached), and the SDK's supported mutation pattern for swapping a mesh's material binding — snapshot the mesh's params, destroy the old mesh, then recreate it with the same id and re-attach it to its SceneObject.
Features
"Masonry","Interior","Metal","Glass") so menus can group entries naturally.hatchPattern, so section caps and Detailed-mode body shading pick it up automatically.opacity/alphaMode: "BLEND"in their entry, so swapping to glass produces a translucent material without per-call overrides.triplanarScale, so painters apply correctly to UV-less BIM geometry via the renderer's triplanar fallback.paintMaterialkeeps the source SceneObject intact; only the mesh's material binding changes, via the SDK's supported detach + destroy + recreate + reattach pattern.catalogto the constructor to replace the default with a host-defined set of painter entries (e.g. a project-specific palette of branded materials).Installation
Quick Start
1) Import the entry points
2) Construct the palette
With no params, the palette ships with the full default catalog.
Or override texture size and UV scale:
3) Repaint one mesh
Pass a SceneMesh and a painter id. The returned
SDKResultcarries the new (replacement) SceneMesh on success.4) Repaint every mesh under a SceneObject
paintMaterialreturns a fresh SceneMesh each call; iterate the SceneObject'smeshesarray once via a snapshot so the loop isn't disturbed by the in-flight detach + reattach.5) Drive from a UI menu
painterIds returns the registered ids in catalog order; getEntry looks up an entry for label / category metadata. Together they're enough to build a context-menu submenu grouped by category.
6) Custom catalog
Pass a catalog to bypass the default — useful for project-specific brand materials or stripped-down palettes.
7) Lifetime
The palette is stateless across SceneModels — its material cache is a WeakMap, so destroying a SceneModel automatically drops its cached materials. The palette itself has no
destroymethod; drop the reference when you're done.