Materials

Manage PBR materials with texture maps. Create realistic surface properties for your 3D models.

Surface fields are top-level properties of the material — baseColor, metallic, roughness, opacity (0–100), specular, reflectivity, reflectionStrength, transparencyMode ("Opaque" or "Transparent"), ior, emissive, doubleSided, the texture maps and the gemstone fields below. A nested pbr object is stored as sent but the viewer does not read it.

List Materials

GET/materials
const response = await fetch(BASE_URL + '/materials', {
  headers: { 'Authorization': 'Bearer ' + accessToken }
});

const data = await response.json();
// { "success": true, "materials": [{ "id": "mat_123", "name": "Brushed Metal",
//   "baseColor": "#808080", "metallic": 0.9, "roughness": 0.3, ... }] }

Create Material

POST/materials
const response = await fetch(BASE_URL + '/materials', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + accessToken,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Oak Wood',
    category: 'wood',
    baseColor: '#8B6F47',
    metallic: 0.0,
    roughness: 0.8
  })
});

Update Material

PUT/materials/:id
const response = await fetch(BASE_URL + '/materials/mat_123', {
  method: 'PUT',
  headers: {
    'Authorization': 'Bearer ' + accessToken,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ roughness: 0.5, metallic: 0.8 })
});

Delete Material

DELETE/materials/:id
const response = await fetch(BASE_URL + '/materials/mat_123', {
  method: 'DELETE',
  headers: { 'Authorization': 'Bearer ' + accessToken }
});

Gemstone & glass materials

A material can let light pass through it: faceted stones are ray traced inside their mesh (refraction, internal reflections, dispersion, body colour) and glass refracts through thin walls. All eight fields are optional — a material without them renders exactly as before.

FieldValueMeaning
transmission0–1How much light passes through. 0 or absent = an ordinary material.
transmissionMode"gemstone" | "glass"Gemstone ray traces a closed faceted mesh; glass refracts thin-walled objects (bottles, lenses).
ior1–2.5How strongly light bends (water 1.33, glass 1.5, sapphire 1.77, diamond 2.42).
dispersion0–1Colour fringing (“fire”), 20 / Abbe number (diamond 0.36, cubic zirconia 0.59).
attenuationColor#RGB or #RRGGBBBody colour: the colour light keeps after attenuationDistance.
attenuationDistance0–100Multiple of the object’s size. Smaller = deeper colour; 0 = colourless.
thickness0–4Glass wall thickness, as a multiple of the object’s size (e.g. 0.05).
envMapIntensity0–5Environment reflection strength of a transmissive material (sparkle).
gemPresetpreset idA label for the stone (the table below). The API stores the fields you send; it does not fill them from the preset.

Keep a fallback look. Devices without gemstone rendering, AR and older embedded viewers show the material’s ordinary look instead. Give a transmissive material transparencyMode: "Transparent" and an opacity below 100 so that look is see-through, not solid. validate_project (MCP) reports materials whose fallback is opaque.

const response = await fetch(BASE_URL + '/materials', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + accessToken,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Blue sapphire',
    gemPreset: 'Sapphire',
    transmission: 1,
    transmissionMode: 'gemstone',
    ior: 1.77,
    dispersion: 0.26,
    attenuationColor: '#2350e0',
    attenuationDistance: 1.1,
    envMapIntensity: 1.15,
    // fallback look
    baseColor: '#1f4fd6',
    transparencyMode: 'Transparent',
    opacity: 75,
    roughness: 0,
    specular: 1
  })
});

On PUT /materials/:id, null removes a gemstone field and transmission: 0 turns light transmission off. An invalid value is refused with 400, the field name and a stable code:

// 400
{ "success": false, "error": "transmission must be a number from 0 to 1.",
  "field": "transmission", "code": "invalid_material_field" }

For a material that transmits light, ior must be 1–2.5 whenever the request sets ior or transmission. Other materials accept any IOR, as before.

Preset values

gemPresetiordispersionattenuationColorattenuationDistanceFallback colour / opacity
Diamond Diamond2.4170.36#ffffff0#ffffff / 35
Sapphire Blue sapphire1.770.26#2350e01.1#1f4fd6 / 75
Ruby Ruby1.770.26#d1123a1.1#c8102e / 75
Emerald Emerald1.580.27#23a35c1.3#1e9e5a / 75
Amethyst Amethyst1.550.27#9058d81.8#8e4fd6 / 65
CubicZirconia Cubic zirconia2.170.59#ffffff0#ffffff / 35
Moissanite Moissanite2.50.75#fbfaf240#fbfaf2 / 35
Aquamarine Aquamarine1.580.27#84d8ea3.5#7fd3e6 / 55
Topaz Blue topaz1.620.26#4aade62.6#45a8e0 / 60
Morganite Morganite1.580.27#f4abb83.5#f2a6b3 / 55
Peridot Peridot1.670.33#a0cc3a1.8#9cc73a / 65
Citrine Citrine1.550.27#f4b43a2.2#f2b233 / 65
Tanzanite Tanzanite1.690.43#5e52d41.2#5b4fcf / 70
Garnet Garnet1.750.3#8f1d2e1#8b1a2b / 80
PinkSapphire Pink sapphire1.770.26#ea568e1.6#e8508a / 70

Lighting environments

The lighting a stone reflects is a project setting: projectSettings.environmentSource ("studio" — the default — "jewelry", "softbox", "daylight" or "custom") with environmentRotation in degrees, and projectSettings.gemQuality ("auto", "high", "standard", "fallback").

"custom" lights the project with your own equirectangular HDR panorama. Upload it as a Radiance RGBE .hdr — at most 16 MB and 4096×2048; 2048×1024 is plenty (the editor converts .exr files and resizes larger images before uploading) — then save the returned path as projectSettings.environmentCustomPath with environmentSource: "custom". These endpoints take a session token; an X-API-Key does not reach them.

POST/projects/:id/environment
const form = new FormData();
form.append('file', hdrFile);              // Radiance RGBE .hdr
form.append('originalName', 'studio.hdr'); // optional, shown in the editor

const response = await fetch(BASE_URL + '/projects/' + projectId + '/environment', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer ' + accessToken },
  body: form
});

// 200 { "success": true, "environmentId": "…",
//       "path": "<owner>/project-environments/<project>/<id>/environment.hdr",
//       "fileName": "studio.hdr", "width": 2048, "height": 1024, "bytes": 6291456,
//       "url": "<signed URL, valid 6 hours>" }
// 400 { "success": false, "code": "invalid_content", "error": "…" }  not RGBE, or over 4096×2048
// 413 { "success": false, "code": "file_too_large", "error": "…" }
GET/projects/:id/environment?path=…

Returns { "success": true, "url": "…", "expiresIn": 21600 } — a signed URL for a path in the project owner’s environment folder (404 for any other path). The public share payload (GET /share/:projectId/:token) carries project.data.environmentUrl, a one-year signed URL, when the project lights with its own file; a headless viewer loads that URL as an equirectangular RGBE image.

Uploaded files are kept when the setting changes: projects copied from this one as a template may still light with them.

Continue reading