API Reference
Complete reference for the Wedding Band Builder API.
Accessing the API
Direct Integration (Same Page)
window.addEventListener('ijewel-viewer-ready', (e) => {
const viewer = e.detail.viewer;
const api = viewer.getPluginByType('WeddingBandBuilder').controller;
api.setWidthMm(4.4);
});Same-Origin iframe
The iframe page fires ijewel-viewer-ready on its own window. That event does not bubble to the parent page. Register a parent callback before the iframe loads, then forward the viewer from a listener inside the iframe page.
Parent page:
<iframe id="wbb-iframe" title="Wedding Band Builder"></iframe>window.handleWbbViewerReady = (viewer) => {
const api = viewer.getPluginByType('WeddingBandBuilder').controller;
api.setWidthMm(4.4);
};
const iframe = document.getElementById('wbb-iframe');
iframe.src = './wbb-viewer.html';Inside wbb-viewer.html, register this listener before creating Mini Viewer:
window.addEventListener('ijewel-viewer-ready', ({ detail }) => {
window.parent.handleWbbViewerReady?.(detail.viewer);
}, { once: true });This direct callback requires both pages to use the same origin. You can also use the postMessage protocol below for a same-origin iframe.
Cross-Origin (PostMessage)
For iframes on a different domain:
// Parent page sends to iframe
const viewerOrigin = 'https://ijewel3d.com';
iframe.contentWindow.postMessage({
id: 'req-1',
method: 'setWidthMm',
args: [4.4]
}, viewerOrigin);
// Listen for responses from iframe
window.addEventListener('message', (event) => {
if (event.source !== iframe.contentWindow) return;
if (event.origin !== viewerOrigin) return;
if (event.data.id === 'req-1') {
if (event.data.error) {
// Structured error: { message, source, details }
console.log(event.data.error.message);
} else {
console.log('Result:', event.data.result);
}
}
if (event.data.event) {
console.log('Event:', event.data.event, event.data.data);
}
});Events (including 'log' and 'error') are automatically forwarded from the iframe to the parent window.
Read Methods
getSnapshot(bandName?)
Returns a customer-facing summary snapshot for one band. It includes the outside and inside features and each slot's base, variant and finish. Use toJSON() when exact state restoration is required.
const snapshot = api.getSnapshot('her');Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
bandName | string | active band | 'her' or 'his' |
Returns: RingSnapshot
exportConfig()
Exports one RingSnapshot summary for every band included in the project. This is the readable record for a cart, an order line, or a manufacturer handoff.
const config = api.exportConfig();
// { her: RingSnapshot, his: RingSnapshot }Returns: Record<string, RingSnapshot>
The summary covers the profile, dimensions, materials — including each slot's base, variant and finish — outside and inside features, diamonds, edges, engraving, and price. importConfig() reads it back and reproduces the same rings.
Pricing is the one thing it does not restore. The snapshot carries computed totals, not the PricingParams that produced them, so remove pricing from each band before you import the record back. Use api.toJSON() when you need the exact internal state, including anything the readable summary does not model.
getBandNames()
Returns the IDs of all bands included in the loaded project. Do not assume that both her and his exist—a Drive project can be created with only one.
const bands = api.getBandNames(); // ['her', 'his'], ['her'], or ['his']Returns: string[]
getManifest()
Returns the loaded Wedding Band manifest, including project catalogs, icons, limits, engraving options, and theme configuration. Prefer the dedicated catalog getters for building UI; use this method when you need project metadata that has no dedicated getter.
const manifest = api.getManifest();
console.log(manifest?.icons?.logo);Returns: WeddingBandManifest | null
getSpecSheetData(bandName?)
Returns one manufacturing/export payload containing the ring snapshot, price, diamond size information, and actual physical dimensions.
const {
snapshot,
price,
diamondSizeInfo,
actualDimsMm,
} = api.getSpecSheetData('her');Returns:
{
snapshot: RingSnapshot;
price: PriceBreakdown | null;
diamondSizeInfo: { diameterMm: number; carats: number } | null;
actualDimsMm: { widthMm: number; heightMm: number };
}See Specification Sheet for a printable implementation.
getRawState(bandName?)
Returns a read-only snapshot of the controller's internal BandState. This is primarily useful for debugging and advanced migration work; storefront order data should use exportConfig() or getSnapshot() for a readable summary, and api.toJSON() when an exact restore is needed.
const internalState = api.getRawState('her');Returns: Readonly<BandState>
getActiveBand()
Returns the name of the currently active band.
const band = api.getActiveBand(); // 'her' or 'his'Returns: string
getDimensions(bandName?)
Returns the current dimension state. For compatibility, widthMm and heightMm are legacy field names: their values are profile scale multipliers, not physical millimeters. radiusMm is a physical radius in millimeters.
const dims = api.getDimensions();
// { widthMm: 1.0, heightMm: 1.0, radiusMm: 8.5 }
console.log(`Width scale: ×${dims.widthMm}`);Returns: DimensionsSnapshot
getRawProfileDimensions(bandName?)
Returns the source profile's width and height at a 1.0 scale, in millimeters.
const raw = api.getRawProfileDimensions();
// { widthMm: 4.0, heightMm: 1.8 }Returns: { widthMm: number; heightMm: number }
getActualDimensionsMm(bandName?)
Returns the physical profile width and height after applying the current scale multipliers.
const actual = api.getActualDimensionsMm();
// { widthMm: 4.4, heightMm: 1.8 }Returns: { widthMm: number; heightMm: number }
getDiamondBaseDiameterMm()
Returns the manifest's source diamond diameter at stoneSize = 1.
const baseDiameterMm = api.getDiamondBaseDiameterMm();Returns: number
getDiamondSizeInfo(stoneSize)
Converts a Wedding Band stone-size multiplier into its physical diameter and approximate round-brilliant carat weight.
const stone = api.getDiamondSizeInfo(1.5);
// { diameterMm: number, carats: number }Returns: { diameterMm: number; carats: number }
getMaterials(bandName?)
Returns the current partition, material slots, and outside/inside material features. The historical metal field contains the base material ID.
const mats = api.getMaterials();
// {
// partition: 2,
// slots: [
// { slot: 1, metal: 'White', base: 'White', variant: '18k', finish: 'Polished' },
// { slot: 2, metal: 'Yellow', base: 'Yellow', variant: '14k', finish: 'Hammered' }
// ],
// insidePolished: true,
// splitAtGroove: true,
// inlays: [{ centerZ: 0, widthMm: 1, metal: 'wood', variant: 'koa' }],
// overlays: [],
// sleeve: { enabled: true, metal: 'yellow', variant: '18k', full: true }
// }Every slot names all three material axes. base is the base material and metal carries the same value under its original name, so code written before variants existed keeps working. variant is the quality or sub-category — 18k versus 14k, or a wood species — and is always filled in: a slot the customer never changed reports the material's own default rather than nothing. finish is empty for a material with no finish axis, such as wood.
Returns: MaterialSnapshot
getDiamonds(bandName?)
Returns the current diamond setting configuration, or null if no diamonds are set.
const diamonds = api.getDiamonds();Returns: DiamondSnapshot | null
getEdge(bandName?)
Returns the current edge configuration.
const edge = api.getEdge();
// { type: 'Beveled', side: 'Both' }Returns: EdgeSnapshot
getPrice(bandName?)
Returns the full price breakdown, or null if pricing is not configured.
const price = api.getPrice('her');
console.log(`$${price.totalUsd.toFixed(2)}`);Returns: PriceBreakdown | null
See Pricing API for detailed pricing documentation.
getWaveParams(bandName?)
Returns the clamped wave-path parameters plus the current amplitude ceiling, so a slider can bound itself. frequency stays within 2 to 6, and amplitudeMax is 0.7 / frequency under the engine's slope rule.
const { frequency, amplitude, amplitudeMax } = api.getWaveParams();Returns: { frequency: number; amplitude: number; amplitudeMax: number }
getEdges(bandName?)
Returns the independent configuration of both band edges.
Returns: { left: { type, width, depth, finish }; right: { type, width, depth, finish } }
getDivisionBoundary(bandName?)
Returns the wave that the color seam actually rides on the mesh, or undefined when the seam runs straight. Draw this in any 2D placement surface that must agree with the ring. An enabled Wavy Grooves feature owns the wave and splits the colors along it only when wavy split is on, so a wavy Division can still draw a straight seam.
Returns: { frequency: number; amplitude: number } | undefined
getRelationship(bandName?)
Returns the current segment-width ratio, normalized to the partition count.
Returns: number[]
getRingSizeForBand(bandName?)
Returns the standard size the band currently reads as, in every system. The spec sheet resolves sizes the same way, so the two can never disagree.
const { entry, radiusMm, diameterMm, label } = api.getRingSizeForBand();
console.log(label('US'));Returns: { entry: RingSizeEntry; radiusMm: number; diameterMm: number; label: (system: RingSizeSystem) => string }
getAutoOptimalHeight(bandName?)
Returns true when the band tracks the manufacturer's optimal thickness.
Returns: boolean
getThicknessRangeMm(bandName?)
Returns the manufacturable thickness range for the active profile at its current width, or null when that profile has no rule table. Use it to bound the height control dynamically.
Returns: { minMm: number; maxMm: number } | null
getMaxDiamondCount(bandName?)
Returns how many stones the current setting can physically fit across the band. This is the ceiling the engine clamps requests to, not the count last placed.
Returns: number
isSettingTypeAvailable(settingType, bandName?)
Returns true when a stone-setting type suits the band's current profile and path.
Returns: boolean
getUIOptions()
Returns the resolved UI option block from the manifest theme.
Returns: Partial<UITheme>
getParallaxEnabled()
Returns whether relief parallax mapping is on.
Returns: boolean
Catalog Methods
These methods return the available options defined in the project manifest.
getAvailableProfiles()
const profiles = api.getAvailableProfiles();
// [{ index: 0, id: 'd-shape', name: 'D-Shape', thumbnail: '...' }, ...]Returns: { index: number; id: string; name: string; thumbnail?: string; iconUrl?: string }[]
getAvailableMetals()
Returns the base materials allowed for the main band body, as a flat metal-style list. Prefer getAvailableMaterials('band') when the UI also needs variants or usage roles.
const metals = api.getAvailableMetals();
// [{ id: 'yellow', name: 'Gold', iconUrl: '...' }, ...]The catalog reports the material icon as iconUrl. An older manifest's thumbnail is carried into the same field, so always read iconUrl.
Returns: { id: string; name: string; iconUrl?: string }[]
getAvailableMaterials(usage?)
Returns the base-material catalog. Pass a usage role to build the correct picker for the band body, an inlay, an overlay, or the inside sleeve.
const bandMaterials = api.getAvailableMaterials('band');
const inlayMaterials = api.getAvailableMaterials('inlay');usage is optional and accepts 'band', 'inlay', 'overlay', or 'sleeve'. A material with no usage list is returned for every role.
Returns: MaterialEntry[]
getAvailableVariants(baseId)
Returns the qualities or subcategories for one base material.
const variants = api.getAvailableVariants('yellow');
// [{ id: '14k', name: '14k', files: { polished: '...' } }, ...]Returns: MaterialVariant[]
getAvailableFinishesFor(baseId)
Returns only the finishes supported by a base material. It returns an empty array when that material has no finish axis.
const goldFinishes = api.getAvailableFinishesFor('yellow');
const woodFinishes = api.getAvailableFinishesFor('wood'); // []Returns: { id: string; name: string; iconUrl?: string }[]
getAvailableFinishes()
Returns the complete top-level finish display catalog. Prefer getAvailableFinishesFor(baseId) for a material picker, because one material can support only part of this list.
const finishes = api.getAvailableFinishes();
// [{ id: 'polished', name: 'Polished', thumbnail: '...' }, ...]Returns: { id: string; name: string; thumbnail?: string; iconUrl?: string }[]
getAvailablePartitions()
const partitions = api.getAvailablePartitions();
// ['1 Color', '2 Color', '3 Color']Returns: string[]
getAvailableSettingTypes()
const settings = api.getAvailableSettingTypes();
// [{ id: 'none', name: 'None', iconKey: 'no-diamonds' }, ...]Returns: { id: string; name: string; iconKey?: string }[]
Use option.id when calling a setter and option.name for the visible label:
settings.forEach((option) => {
const button = document.createElement('button');
button.textContent = option.name;
button.onclick = () => option.id === 'none'
? api.setDiamonds(null)
: api.setDiamonds({ settingType: option.id });
});getAvailableEdgeTypes()
const edges = api.getAvailableEdgeTypes();
// [{ id: 'None', name: 'None' }, { id: 'Beveled', name: 'Beveled' }, ...]Returns: { id: string; name: string }[]
getAvailableEdgeSides()
const sides = api.getAvailableEdgeSides();
// [{ id: 'Left', name: 'Left' }, { id: 'Both', name: 'Both' }, ...]Returns: { id: string; name: string }[]
getAvailableDiamondSpans()
const spans = api.getAvailableDiamondSpans();
// [{ id: 'half', name: '1/2', fraction: 0.5 }, ...]Returns: { id: string; name: string; fraction: number | string }[]
getAvailableDiamondSpacings()
const spacings = api.getAvailableDiamondSpacings();
// [{ id: 'half-stone', name: '1/2 Stone', value: 1.5 }, ...]Returns: { id: string; name: string; value: number | string }[]
getDiamondSpanFraction(span)
Returns the numeric or manifest-defined fraction for a diamond span ID.
Returns: number | string
getDiamondSpacingValue(spacing)
Returns the numeric or manifest-defined value for a diamond spacing ID.
Returns: number | string
getAvailablePositionSnaps()
Returns: { name: string; value: number }[]
getEngravingFonts()
Returns the project-defined engraving fonts, or the built-in font catalog when the manifest does not override it.
Returns: string[]
getEngravingSymbols()
Returns the symbols offered by the built-in engraving UI.
const symbols = api.getEngravingSymbols();
// [{ title: 'Heart', char: '♥' }, ...]Returns: { title: string; char: string }[]
getEngravingDefaults()
Returns the resolved project defaults used when a band has no explicit engraving font, size, or rotation.
Returns: { font: string; fontSize: number; rotation: number }
getEngravingVOffset(profileIndex, partition)
Returns a profile-specific vertical engraving offset from the manifest, or undefined when the engine should calculate the inner-surface center.
const offset = api.getEngravingVOffset(0, '1 Color');Returns: number | undefined
getLimits()
Returns the manifest-driven numeric ranges used by the controller. Use these values for sliders instead of hard-coding ranges.
const limits = api.getLimits();
widthSlider.min = limits.width.min;
widthSlider.max = limits.width.max;
widthSlider.step = limits.width.step || 0.05;Returns: an object containing { min, max, step? } ranges for width, height, radius, stone size, diamond count and position, engraving, and wavy groove controls.
getAvailableThemes()
const themes = api.getAvailableThemes();
// ['default', 'luxury-gold', 'modern-minimal', 'dark', 'rose-elegant',
// 'coral-modern', 'fresh-teal', 'minimal-blue', 'warm-beige', 'classic-gold', 'ijewel']Returns: ThemePresetName[]
getAvailablePaths()
Returns the extrusion paths, which are the band centerline shapes. The list holds the built-in circular path plus every manifest entry, with icon URLs already resolved.
const paths = api.getAvailablePaths();
// [{ id: 'circular', name: 'Classic' }, { id: 'wave', name: 'Wave', iconUrl: '...' }]Returns: { id: string; name: string; iconUrl?: string }[]
getAvailableDivisionTypes(bandName?)
Returns the color-division orientations the band can use. An entry with partitionCount suits exactly one partition count.
Returns: { id: string; name: string; iconUrl?: string; partitionCount?: number }[]
getAvailableGrooveTypes()
Returns the groove cross sections. type names the engine groove, and angle carries the opening angle of a V groove.
Returns: { id: string; name: string; iconUrl?: string; type?: 'square' | 'v' | 'u' | 'convex-u' | 'milgrain' | 'milgrain2'; angle?: number }[]
getAvailableGrooveFinishes()
Returns the interior finishes a groove or joint can take.
Returns: { id: string; name: string; iconUrl?: string }[]
getAvailableGrooveDirections()
Returns the groove directions, which run around the ring or across the band.
Returns: { id: string; name: string; iconUrl?: string }[]
getAvailableSideEdgeTypes()
Returns the treatments an independent left or right edge can take.
Returns: { id: string; name: string; iconUrl?: string }[]
getAvailableEyeOrientations()
Returns the eye-setting orientations. Each entry carries the numeric value the engine applies.
Returns: { id: string; name: string; iconUrl?: string; value: number }[]
getAvailableRingTypes()
Returns the ring types a compilation accepts.
Returns: { id: 'wedding' | 'memoire' | 'engagement'; name: string; iconUrl?: string }[]
getAvailableSideSettingOptions(bandName?)
Returns the side-setting catalog from the manifest, already filtered by compatibility with the current profile.
Returns: { id: string; name: string; iconKey?: string }[]
getAvailableSettingTypeIds(bandName?) / getAvailableEdgeTypeIds()
Return plain id lists, for integrations that do not need the catalog metadata.
Returns: string[]
getRelationshipPresets(partitionOrCount?)
Returns the named segment-width ratios offered for a partition, such as 2:1:1.
Returns: { name: string; ratios: number[] }[]
getFreeStonePresetOptions()
Returns the curated free-stone patterns, each with the stones it places.
Returns: { id: string; name: string; stones: FreeStone[]; iconUrl?: string }[]
getDiamondColors() / getDefaultDiamondColor()
getDiamondColors() returns the stone colors whose material loaded from the manifest. getDefaultDiamondColor() returns the color a stone takes when it names none, which is the first catalog entry. It returns an empty string when no color loaded, and stones then keep the material of the diamond model.
Returns: { id: string; name: string; swatch?: string; iconUrl?: string }[] / string
getRingSizes() / getRingSizeSystems()
getRingSizes() returns the standard size catalog that every size control and the spec sheet resolve against. A formula drifts from it by up to half a size, so a UI that offers sizes must offer these. getRingSizeSystems() returns the systems this build can label a ring in.
Returns: readonly RingSizeEntry[] / readonly RingSizeSystem[]
Write Methods
All write methods accept an optional bandName parameter ('her' or 'his'). If omitted, the currently active band is used.
setLogLevel(level)
Set the minimum log level. Messages below this level are suppressed.
api.setLogLevel('info'); // show everything
api.setLogLevel('warn'); // errors + warnings (default)
api.setLogLevel('error'); // errors only
api.setLogLevel('silent'); // suppress all output and events| Parameter | Type | Default | Description |
|---|---|---|---|
level | LogLevel | 'warn' | Minimum severity to emit |
Returns: void
setWidthMm(mm, bandName?)
Sets the band width in absolute millimeters, clamped to the product range (1.5–10 mm). The value is stored internally as a profile-relative multiplier (mm ÷ the profile's natural width). Changing the width reconciles the height against the manufacturer rules — the optimal thickness when auto-optimal height is enabled, otherwise a clamp to the manufacturable range. Out-of-range values are clamped and emit validation:warning.
api.setWidthMm(4.4); // band is now 4.4 mm wideReturns: void
setHeightMm(mm, bandName?)
Sets the band thickness (height) in absolute millimeters, clamped to the manufacturable range — the manufacturer possibility rule for the active profile + width when one exists, else the profile's natural height × 0.5–1.5.
api.setHeightMm(1.8);Returns: void
setWidthMultiplier(multiplier, bandName?) / setHeightMultiplier(multiplier, bandName?)
Set width or height as a profile-relative multiplier: 1.0 = the active profile's natural dimension. Width is bounded by the same 1.5–10 mm product range as setWidthMm; height by 0.5–1.5. Changing the width reconciles the height as above.
api.setWidthMultiplier(1.1); // 110% of the source profile width
api.setHeightMultiplier(0.95);Returns: void
setWidth(multiplier, bandName?) / setHeight(multiplier, bandName?) deprecated
Deprecated aliases of setWidthMultiplier / setHeightMultiplier. Despite the historical names, the argument is a multiplier, not millimeters. Calling them logs a deprecation warning; use setWidthMultiplier()/setHeightMultiplier() or setWidthMm()/setHeightMm() instead.
Note there is no setRingSizeMm(): setRingSize() already takes the inner radius in millimeters.
Returns: void
setRingSize(radiusMm, bandName?)
Sets the ring's inner radius in millimeters. If your UI works with inner diameter, divide the diameter by two before calling this method.
api.setRingSize(8.5); // 17 mm inner diameter
api.setRingSize(10, 'his');The value is clamped to getLimits().radius.
Returns: void
setProfile(indexOrName, bandName?)
Sets the ring profile by index or name. Returns a promise because the profile geometry needs to be loaded.
await api.setProfile(0); // by index
await api.setProfile('Flat'); // by nameReturns: Promise<void>
WARNING
setProfile() is async. Always await it before reading dimensions or other state that depends on the new geometry:
await api.setProfile(0);
const dims = api.getDimensions(); // safe, new geometry is loadedWithout the await, you may read stale dimensions from the previous profile.
setMaterial(slot, metal, finish, bandName?)
Sets the base material and finish for a specific slot. It does not select a variant. Use setMaterialRef() for new interfaces.
await api.setMaterial(1, 'Yellow', 'Polished'); // Slot 1
await api.setMaterial(2, 'White', 'Hammered'); // Slot 2
await api.setMaterial(3, 'Rose', 'Brush'); // Slot 3| Parameter | Type | Description |
|---|---|---|
slot | number | One-based material slot number |
metal | string | Base material ID or accepted display name |
finish | string | Finish ID or accepted display name |
Returns: Promise<void>
setMaterialRef(slot, ref, bandName?)
Sets a full base, variant, and finish reference for one material slot.
await api.setMaterialRef(1, {
base: 'yellow',
variant: '18k',
finish: 'polished',
});Omitted variant and finish values keep the current slot values. When the base changes, the controller clears a stale finish for a finishless material. It otherwise replaces an unsupported finish with polished or the first available finish. A variant the new base material does not offer is dropped — the slot falls back to that material's default and a validation:warning names the variants it could have had.
Returns: Promise<void>
setPartition(numColors, bandName?)
Sets the number of material color zones.
await api.setPartition(2); // 2-color band| Parameter | Type | Description |
|---|---|---|
numColors | number | Number of material zones supported by a manifest preset |
Returns: Promise<void>
setPartitionByName(presetName, bandName?)
Applies an exact partition preset from getAvailablePartitions(). Use this instead of setPartition() for named layouts such as 2 Color Vertical.
const preset = api.getAvailablePartitions()[0];
await api.setPartitionByName(preset);Returns: Promise<void>
setDiamonds(config, bandName?)
Configures diamond settings. Accepts a partial DiamondSnapshot — only the fields you pass are changed. Pass null to remove diamonds.
api.setDiamonds({
settingType: 'Channel',
span: 'half',
spacing: 'half-stone',
count: 12,
stoneSize: 1.5, // 0.5–3.0
});
// A row of stones across the band at 45°
api.setDiamonds({ settingType: 'Cross Bezel', count: 4, positionAngle: 45 });
// Tension setting with two stones and a custom bridge width
api.setDiamonds({ settingType: 'Tension', count: 2, bridgeWidth: 2.4 });
// Remove diamonds
api.setDiamonds(null);Available settingType values: Channel, Channel All Around, Bezel, Prong, Cross Bezel, Cross Channel, Cross Around, Tension, Tension Diagonal, Eye, Free (use getAvailableSettingTypes() — some depend on the profile). Type-specific options: positionAngle (cross, tension, eye — degrees around the ring), bridgeWidth (tension), eyeOrient (eye — 0, 90, ±45), freeStones (free placement). Side-face settings have their own API — see Side stone settings.
Returns: void
Diamond size
Stone size is the stoneSize field. Update it on its own by passing only that field:
api.setDiamonds({ stoneSize: 2.0 }); // 0.5–3.0Diamond position
Position along the ring's width is the position field. It is automatically clamped based on band width, diamond size, and groove width to keep diamonds inside the ring:
api.setDiamonds({ position: -1 }); // Left
api.setDiamonds({ position: 0 }); // Center (default)
api.setDiamonds({ position: 1 }); // Right
api.setDiamonds({ position: 0.5 }); // Between center and right| Field | Type | Description |
|---|---|---|
position | number | Position from -1 (left) to 1 (right), 0 = center |
INFO
Diamond position is automatically reset to center (0) when wavy grooves are enabled.
setEdge(type, side?, bandName?)
Sets the edge treatment.
api.setEdge('Beveled', 'Both');
api.setEdge('None'); // Remove edge| Parameter | Type | Default | Description |
|---|---|---|---|
type | string | Edge type (see getAvailableEdgeTypes()) | |
side | string | current | Side ID from getAvailableEdgeSides() ('Left', 'Right', or 'Both') |
Returns: void
setEngraving(text, font?, fontSize?, bandName?, rotation?)
Sets the interior engraving text. Pass null to remove.
api.setEngraving('Forever & Always', 'serif', 80);
// Remove engraving
api.setEngraving(null);| Parameter | Type | Default | Description |
|---|---|---|---|
text | string | null | Engraving text, or null to remove | |
font | string | current | Font family name |
fontSize | number | current | Font size in pixels |
bandName | string | active band | Registered band name |
rotation | number | current | Engraving rotation, clamped to the manifest limit |
Returns: void
setInsidePolished(value, bandName?)
Toggles whether the ring interior is polished.
await api.setInsidePolished(true);Returns: Promise<void>
setPath(pathId, bandName?)
Sets the extrusion path, which is the band centerline shape. circular is the built-in default, and other ids come from the manifest path registry.
A custom path drops the settings that need a circular band — the Tension, Eye and Side family — and drops engraving. Regular stone settings ride the path.
await api.setPath('wave');Returns: Promise<void>
setPathStrength(strength, bandName?)
Sets the path displacement strength. 1 gives the full wave, and 0 matches the classic band.
Returns: void
setWaveParams(params, bandName?)
Sets the frequency and amplitude of the built-in wave path. Frequency is an integer from 2 to 6. Amplitude is a fraction of the radius, and the engine clamps it by the slope rule amplitude x frequency <= 0.7. Both values apply only while the wave path is active.
await api.setWaveParams({ frequency: 4, amplitude: 0.12 });Returns: Promise<void>
setRelationship(ratio, bandName?)
Sets the width ratio between color segments. The ratio length must equal the current partition count. Pass null to return to a uniform split.
await api.setRelationship([2, 1, 1]);Returns: Promise<void>
setRingSizeByStandard(system, size, bandName?)
Sets the ring size by its standard designation. The value resolves through the size table, so the band lands on an exact standard radius rather than on a formula's approximation. An unknown designation is refused with a validation:warning event.
api.setRingSizeByStandard('US', 7);
api.setRingSizeByStandard('UK', 'N½');Returns: void
setAutoOptimalHeight(on, bandName?)
Turns on automatic thickness. The band then takes the manufacturer's optimal height for its width whenever the width changes. Turning it on applies the optimal height immediately.
Returns: void
setEdgeSide(side, cfg, bandName?)
Configures one edge independently. Pass 'left' or 'right' and any of type, width, depth and finish. An edge that turns on takes a polished finish unless the call names another.
api.setEdgeSide('left', { type: 'Edge', width: 0.3, depth: 0.2, finish: 'matt' });Returns: void
setMilgrain(cfg, bandName?)
Sets the band-wide milgrain bead style: the bead type, its size, and its spacing. Milgrain turns on per host, through an edge of type Milgrain or a groove or joint of type milgrain, never through this call. Legacy enabled, onEdges and onGrooves inputs are accepted and ignored.
api.setMilgrain({ beadType: 'round', sizeMm: 0.18, spacingFactor: 1.1 });Returns: void
setEngravingStyle(color?, roughness?, bandName?, bumpOnly?, bumpScale?, mode?, bump?)
Sets the appearance of the engraved marks. color is a diffuse tint multiplied into the metal color, and '#ffffff' or null means no tint. roughness runs from 0, which stays polished, to 1, which is fully matte. bumpOnly gives pure relief with no roughness or tint. bumpScale is in engine units, and a negative value embosses.
await api.setEngravingStyle('#ffffff', 0.6, undefined, false, 0.4, 'engrave');Returns: Promise<void>
setInnerStone(cfg, bandName?)
Sets the hidden stone, which is a single flush or bezel-set stone in the bore next to the engraving. Its position is an explicit angle in degrees around the ring, independent of where the engraving text sits. Pass enabled: false to remove it.
await api.setInnerStone({ enabled: true, angleDeg: 180, stoneSize: 1.2 });Returns: Promise<void>
setDiamondColor(colorId, bandName?)
Sets the stone color for one band. His and her rings keep independent colors even though they share the diamond model. Pass null to restore the material of the model.
Returns: void
setParallaxEnabled(enabled)
Turns relief parallax mapping on or off. The extension applies to every material with a bump map, which covers engraving and bump finishes such as brush, so the setting is viewer-wide rather than per band.
Returns: void
Outside and Inside Material Features
Inlays and overlays paint regions on the outer surface. A sleeve paints the inside bore. These features change material assignment only; they do not add solid geometry or change weight.
They render only on a circular path with a plain vertical-style material division. Axial, diagonal, and wavy divisions do not support them. Use areOutsideFeaturesAvailable() before showing custom controls.
setOutsideFeatures(features, bandName?)
Replaces all inlays and overlays in one rebuild. Use this method when several features change together to avoid rendering intermediate states.
await api.setOutsideFeatures({
inlays: [
{ centerZ: 0, widthMm: 1.2, metal: 'wood', variant: 'koa' },
],
overlays: [
{
side: 'left',
widthMm: 0.8,
metal: 'rose',
variant: '18k',
finish: 'polished',
rimCoverage: 0.5,
},
],
sleeve: {
enabled: true,
metal: 'yellow',
variant: '18k',
finish: 'polished',
full: true,
},
});The inlays and overlays arrays replace their current arrays. Omitted arrays therefore clear those feature types. Omit sleeve to preserve the current sleeve, or pass sleeve: null to remove it.
The controller accepts up to three inlays and one overlay on each rim. It clamps feature widths and positions to prevent overlap and edge overflow.
Returns: Promise<void>
setInlay(index, inlay, bandName?)
Adds an inlay at the next index or updates an existing inlay. centerZ is the stripe center in millimeters from the band's center width. widthMm is the stripe width.
await api.setInlay(0, {
centerZ: 0,
widthMm: 1,
metal: 'marble',
variant: '1',
});The controller keeps inlays sorted by centerZ. An index is not a stable identifier after a move. Read getInlays() again after each update.
Returns: Promise<void>
removeInlay(index, bandName?) / getInlays(bandName?)
Removes one inlay or returns copies of all current inlay configurations.
await api.removeInlay(0);
const inlays = api.getInlays();Returns: Promise<void> / InlayConfig[]
getInlayBounds(index, bandName?)
Returns the legal center range and maximum width for an inlay. Re-read these bounds after any band-width, inlay, or overlay change.
const { centerMin, centerMax, widthMax } = api.getInlayBounds(0);Returns: { centerMin: number; centerMax: number; widthMax: number }
setOverlay(side, overlay, bandName?)
Adds or updates the overlay anchored to the left or right rim. Only one overlay can exist on each side.
await api.setOverlay('right', {
widthMm: 1,
metal: 'white',
variant: '18k',
finish: 'brush',
rimCoverage: 1,
});rimCoverage controls how far the material reaches down the flat rim face. Use 0 for none, 1 for the complete face, or an intermediate fraction. rimCoverage takes precedence over the older coversRim boolean.
Returns: Promise<void>
removeOverlay(side, bandName?) / getOverlays(bandName?)
Removes one rim overlay or returns copies of all overlay configurations.
Returns: Promise<void> / OverlayConfig[]
getOverlayWidthMax(side, bandName?)
Returns the largest permitted overlay width for one side after accounting for the band width, other overlays, and inlays.
Returns: number
setSleeve(sleeve, bandName?) / getSleeve(bandName?)
Sets the material on the inside bore. Pass a boolean to change only the enabled state. A full sleeve covers the complete bore. A partial sleeve is centered and uses widthMm.
await api.setSleeve({
enabled: true,
metal: 'yellow',
variant: '18k',
finish: 'polished',
full: false,
widthMm: 3,
});
const sleeve = api.getSleeve();Changing the sleeve reapplies interior engraving against the new bore material.
Returns: Promise<void> / SleeveConfig | undefined
areOutsideFeaturesAvailable(bandName?)
Returns true when the current path and division can render inlays, overlays, and a sleeve.
Returns: boolean
outsideFeaturesFit(bandName?)
Returns false when the current inlay and overlay set overlaps or exceeds the band width. Public setters clamp invalid dimensions, but this method is useful when validating imported state.
Returns: boolean
setSplitAtGroove(value, bandName?)
Toggles whether material splits align to grooves.
await api.setSplitAtGroove(true);Returns: Promise<void>
setWavyGrooves(enabled, frequency?, amplitude?, wavySplit?, bandName?)
Enables or disables wavy groove patterns.
api.setWavyGrooves(true, 8, 0.3); // Enable with frequency 8, amplitude 0.3
api.setWavyGrooves(true, 8, 0.3, true); // Also split/separate the wave
api.setWavyGrooves(false); // Disable| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | boolean | Enable or disable wavy grooves | |
frequency | number | current | Wave frequency |
amplitude | number | current | Wave amplitude |
wavySplit | boolean | current | Enable separation along the wavy groove |
bandName | string | active band | Registered band name |
Returns: void
INFO
Enabling wavy grooves automatically resets the diamond position to center (0), since offset diamond positions are not supported with wavy grooves.
Division methods
setDivision(division, bandName?)setDivisionParams(frequency?, amplitude?, bandName?)setDivisionParams(params, bandName?)getDivision(bandName?)
Sets how the color zones separate: 'vertical', 'axial', 'diagonal', or 'wavy'. setDivisionParams adjusts the wavy/diagonal boundary. frequency is the number of wave periods around the band and applies to 'wavy' only; amplitude (0–1) is the boundary excursion and applies to both 'wavy' and 'diagonal'.
await api.setDivision('wavy');
await api.setDivisionParams(4, 0.4); // frequency, amplitude
// Object form, supported by Mini Viewer 0.6.18 and later.
await api.setDivisionParams({ frequency: 4, amplitude: 0.4 });TIP
Mini Viewer 0.6.18 and later accept both call shapes. On an older viewer, use the positional form.
setSegmentWidthsMm(widths, bandName?) / getSegmentWidthsMm(bandName?)
Per-zone widths in millimeters for multi-color partitions (e.g. a 2:1:1 split).
setSeparationGroove(cfg, bandName?) / getSeparationGroove(bandName?)
Configures the joint groove on color boundaries: { enabled?, type?, width?, depth?, angle?, finish?, boundaries? }. boundaries is a per-boundary enable array (disc 1–2, 2–3, ...). Works on straight, diagonal, and wavy divisions alike.
api.setSeparationGroove({ type: 'v', angle: 60, depth: 0.15 });
api.setSeparationGroove({ enabled: false }); // clean grooveless seamDesign grooves
Freely placed decorative grooves: getDesignGrooves(), addDesignGroove(groove?), updateDesignGroove(index, changes), removeDesignGroove(index?), clearDesignGrooves(). Each groove: { type, width, depth, position, angle?, finish?, orientation?, positionAngle? } — orientation: 'horizontal' runs the groove across the band at positionAngle degrees.
Side stone settings
Stones on the band's flat side faces, independent of the top setting:
setSideSetting(type, bandName?)/getSideSetting(bandName?)—'none','Side Bezel','Side Channel','Side Prong'getAvailableSideSettingTypes(bandName?)— filtered by profile compatibility (side settings need a flat side face)setSideBezelSides(sides, sourceSide?, bandName?)—'left','right', or'both'; newly enabled sides clone fromsourceSidesetSideBezelSide(side, config, bandName?)— per-side span / count / spacing / stone sizecopySideBezelSide(from, bandName?)— copy one side's settings to the othergetSideBezel(bandName?)— full per-side state including computed stone counts
Free stone placement
The 'Free' setting type places individual bezel stones anywhere on the band:
getFreeStones()/setFreeStones(stones)— each stone:{ angleDeg, offset, size }(offset−1..1 across the band)addFreeStone(size?),updateFreeStone(index, changes),removeFreeStone(index),clearFreeStones()applyFreeStonePreset(name)/getFreeStonePresets()— Scatter, Orbit, Cascade, Constellation, Wave
setSmoothSeats(on, bandName?) / getSmoothSeats(bandName?)
Switches bezel-style settings (Bezel, Cross Bezel, Free, Side Bezel) between classic sharp-rim pockets and smooth scooped seats that blend tangentially into the surface.
History
The builder records every configuration change, so a UI can offer undo and redo without tracking state itself.
undo() / redo()
Step back or forward by exactly one change. Calls queue, so rapid presses walk the history instead of collapsing into one step. redo() returns false when nothing was undone.
if (api.canUndo()) await api.undo();Returns: Promise<boolean>
canUndo() / canRedo()
Report whether a step exists in each direction. The history:changed event fires whenever either answer changes.
Returns: boolean
Saved Configurations
A saved configuration is a complete ring design under a short readable id, such as WB-BA5A-DZ58, with a viewer snapshot as its thumbnail.
Storage is localStorage until you supply somewhere else to put it. That is the right default for a shopper, whose designs never need to leave their browser. To make one project's designs appear in every embed of it, on any device, give the viewer a project catalogue host — see The project design catalogue below.
To hand a shopper a link that carries their design with no storage at all, see Design Links.
saveConfiguration(name?)
Saves the current configuration and returns its id and name, or null when the save fails.
const saved = await api.saveConfiguration('Emma and Tom');Returns: Promise<{ id: string; name: string } | null>
loadConfiguration(id)
Loads a saved configuration. Returns false when the id is unknown.
Returns: Promise<boolean>
listConfigurations()
Returns the saved configurations, newest first, with id, name, date and thumbnail.
Returns: Promise<SavedConfigSummary[]>
deleteConfiguration(id)
Deletes one saved configuration.
Returns: Promise<void>
setConfigStorageAdapter(adapter)
Replaces the persistence backend, so saved designs can live in your own REST service or in host-supplied storage instead of localStorage.
api.setConfigStorageAdapter({
async list() { /* … */ },
async save(record) { /* … */ },
async load(id) { /* … */ },
async remove(id) { /* … */ },
});Returns: void
The project design catalogue
Saved designs can live in the project instead of the browser. A design saved once then appears in every embed of that project, on any device.
This is a viewer option, not an API call. Set wbbDesignHost when you create the viewer. Mini Viewer installs it on the builder for you.
new ijewelViewer.Viewer(container, project, {
wbbDesignHost: {
// Write the whole catalogue and return its public URL.
async save(designs) {
return myBackend.putProjectFile('saved-designs.json', designs);
},
// Optional, but recommended. See the note below.
async list() {
return myBackend.getProjectFile('saved-designs.json');
},
},
});Omit the option to keep saving to localStorage.
save receives the complete catalogue and rewrites it whole, so the URL it returns must serve exactly what it was given. The file has to be publicly readable: a shopper reads it without signing in.
Implement list if two people can save to the same project. Every save writes a new file at a new URL, so a viewer that loaded before someone else's save holds a stale catalogue. list re-reads the current one first, and without it that save would drop the other person's designs.
When the host is unavailable, saving falls back to localStorage rather than failing. A catalogue that cannot be read is never replaced with an empty one.
Same-page integrations only
wbbDesignHost holds functions, so it cannot cross the postMessage bridge. A cross-origin iframe host must set it inside the iframe page. The same limit applies to setConfigStorageAdapter().
See ViewerOptions for the full option list.
Design Links
A design link carries the whole design in the URL. Nothing is written to a server, so there is no record to keep and nothing to clean up. Use it for a share button, or to put a design in a product field on your own storefront.
Only the restorable part travels — the bands, their pricing, and the ring compilation. The manifest and the thumbnail do not, so a typical design compresses to roughly 500 characters.
getDesignCode()
The current design as a compact code, with no link around it. Use it when you want to keep the design in your own field, such as a line on an order.
const code = await api.getDesignCode();
// Store it on the order, then restore it later with applyDesignLink(code).Returns null when there is no band state to share.
Returns: Promise<string | null>
getShareUrl()
A ready link to the current design, with the code in the URL fragment.
const url = await api.getShareUrl();
await navigator.clipboard.writeText(url);The code goes in the fragment on purpose. A fragment never reaches a server log, which matters because a design can carry engraving text, and it avoids proxy limits on query-string length. Any route or query already in the address is kept, so a storefront path such as https://shop.example/#/product/42 survives being shared.
Returns null when there is no design to share, or no address to build on.
Returns: Promise<string | null>
setShareBaseUrl(url)
Sets the address getShareUrl() builds on.
api.setShareBaseUrl('https://shop.example/#/product/42');Needed inside a cross-origin iframe, where the builder cannot read the storefront's address and would otherwise build a link to the iframe itself.
Returns: void
applyDesignLink(text)
Restores a design. Accepts a bare code, a ?design= URL, or a #?design= URL, so you can pass whatever the shopper pasted.
const restored = await api.applyDesignLink(window.location.href);
if (!restored) {
// The text carried no readable design, or none of its rings exist here.
}Returns false, and emits a validation:warning, when the text holds no readable design or when none of its bands exist in this project. A design made against a different manifest is still applied, with a warning, since the same catalogue served from another URL is normal between staging and production.
This is also the only way a cross-origin host can restore an exact design. The postMessage bridge can call toJSON() but not the plugin's fromJSON().
Returns: Promise<boolean>
Manufacturing Export
exportManufacturing(config?)
Returns manufacturing data in the metrix and confmetrix format: per-zone volumes and weights, grooves, the stone layout, and hallmark engraving. Pass a partial config to override the client vocabulary, such as alloy names or profile ids.
Use this for a production handoff. Use getSpecSheetData() for the readable summary, and api.toJSON() for restorable state.
const rows = api.exportManufacturing();Returns: any[]
Band Linking
setBandsLinked(linked) / isBandsLinked()
Link or unlink the wedding bands, overriding ui.linkBands in the manifest. Linking records the state of every band and changes none of them, so only later edits mirror across. Ring size and engraving stay personal.
Returns: void / boolean
Bead Library
The milgrain bead designer stores custom beads in localStorage alongside the built-in presets.
getBeadTypes()
Returns the built-in bead presets plus the saved beads of this user. A saved entry carries custom: true.
Returns: { id: string; name: string; params: MilgrainBeadParams; custom?: boolean }[]
saveBeadType(name, params) / deleteBeadType(id)
Save a bead into the library and return its id, or delete one by id.
Returns: string / void
createBeadPreview(canvas)
Attaches a live 3D bead preview to a canvas. The preview uses polished white metal under the same HDR environment as the builder viewer.
Call dispose() when the modal closes. The renderer holds GPU resources until you do.
const preview = api.createBeadPreview(canvasEl);
preview.update(params);
preview.render();
// later
preview.dispose();Returns: BeadPreviewRenderer — { update(params), render(), dispose() }
Plugin Serialization (Advanced)
The controller's toJSON() delegates to the WeddingBandBuilderPlugin and returns complete project/plugin state. Full restoration uses the plugin's fromJSON() method. This format preserves raw band states, including material variants and outside/inside features.
toJSON()
Exports the complete plugin state as a JSON-serializable object. Includes the manifest URL, all band states, and pricing configuration.
const plugin = viewer.getPluginByType('WeddingBandBuilder');
const data = api.toJSON(); // equivalent to plugin.toJSON()
// Save or transmit the configuration
localStorage.setItem('wbb-state', JSON.stringify(data));Returns: { type, manifestUrl? or inline manifest fields, bands?, pricing?, rings? }
fromJSON(data)
Restores plugin state from a previously exported JSON object. If bands are already loaded, it applies states in-place without recreating geometry. If the plugin hasn't been initialized yet, it performs a full cold load.
const plugin = viewer.getPluginByType('WeddingBandBuilder');
const saved = JSON.parse(localStorage.getItem('wbb-state'));
await plugin.fromJSON(saved);Returns: the plugin instance (or a promise resolving after cold loading)
Cross-origin restoration
The postMessage bridge can call controller toJSON(), but it cannot call the plugin-only fromJSON() method. A cross-origin iframe must load full saved state inside its viewer page or expose an application-specific restore command.
Batch Operations
batch(config, bandName?)
Apply multiple changes in a single operation. This is more efficient than calling individual setters because it triggers only one geometry rebuild.
await api.batch({
profile: { name: 'D-Shape' },
dimensions: { widthMm: 1.1, heightMm: 0.95 },
materials: {
partition: 2,
slots: [
{ slot: 1, metal: 'White', finish: 'Polished' },
{ slot: 2, metal: 'Yellow', finish: 'Hammered' },
],
insidePolished: true,
splitAtGroove: true,
},
diamonds: {
settingType: 'Channel',
span: 'half',
spacing: 'half-stone',
count: 12,
stoneSize: 1.5,
},
edge: { type: 'Beveled', side: 'Both' },
engraving: { text: 'Forever', font: 'serif' },
pricing: {
metalPricePerGram: 52.0,
markupMultiplier: 2.5,
},
});All fields in BatchConfig are optional. Include only the sections you want to change.
batch().materials applies the partition, the slots — base, variant and finish alike — inside polish, split state, inlays, overlays, and the sleeve. It touches only the keys the config names: an absent inlays or overlays list leaves the current one in place, and sleeve: null removes the sleeve while an absent sleeve key leaves it alone.
Returns: Promise<void>
importConfig(config)
Applies one BatchConfig to each named band. A RingSnapshot is a valid BatchConfig after you remove its computed pricing value, as shown below. Bands the config does not name are left alone, and so is a name that is not registered in the project.
await api.importConfig({
her: {
profile: { name: 'Comfort' },
dimensions: { widthMm: 0.9 },
materials: {
partition: 1,
slots: [{ slot: 1, metal: 'Rose', finish: 'Polished' }],
},
},
his: {
profile: { name: 'Flat' },
dimensions: { widthMm: 1.2 },
materials: {
partition: 1,
slots: [{ slot: 1, metal: 'White', finish: 'Brush' }],
},
},
});// Round-trip an order record. Drop the computed price first — see below.
const order = api.exportConfig();
for (const band of Object.values(order)) delete band.pricing;
await api.importConfig(order);importConfig() restores the profile, dimensions, material slots with their variants, inlays, overlays, the sleeve, diamonds, edges, and engraving.
Remove pricing before you re-import
exportConfig() writes a computed price breakdown into pricing. On the way in, that same key means the PricingParams that produce a price. importConfig() merges what it finds there into the stored pricing parameters, so a raw round trip pollutes them. Delete pricing from each band first, as above. Then set the real values with setPricingParams().
Use plugin.fromJSON(savedPluginState) when you need the exact internal state.
Returns: Promise<void>
applyBandState(bandName, state)
Applies a complete BandState to one band and resolves after the profile and material rebuilds finish. Use it to restore a state object you stored yourself. Mirroring is suspended for the duration, so a linked pair does not flatten one band's state onto the other.
await api.applyBandState('her', savedState);Returns: Promise<void>
Navigation
switchBand(bandName)
Switches the active band between "her" and "his".
api.switchBand('his');Returns: void
Fires: band:switched
WARNING
Switching bands does not update your custom UI automatically. In headless mode, listen to band:switched and refresh all controls from the new band's snapshot:
api.events.on('band:switched', (data) => {
const snapshot = api.getSnapshot(data.to);
updateAllControls(snapshot);
});Ring Compilation
Manage the set of rings in the scene: parametric wedding bands plus pre-modelled engagement and memoire rings from the catalog. All changes fire rings:changed.
getRings()
Returns the compilation in scene order.
api.getRings();
// [{ id: 'her', type: 'wedding', name: 'Her Ring' },
// { id: 'ring-3', type: 'engagement', name: 'Engagement 1',
// catalogId: 'ring-1', metalId: 'white', visible: true }]Returns: { id, type, name, catalogId?, metalId?, visible? }[]
getRingCatalog(type)
Catalog entries available for a type, with resolved thumbnail URLs.
const models = api.getRingCatalog('engagement');
// [{ id: 'ring-1', name: 'Solitaire I', thumbnail: 'https://...' }, ...]Parameters: type — 'engagement' or 'memoire'
addRing(type, catalogId?)
Adds a ring and returns its id. Wedding rings are new parametric bands; engagement/memoire load the given catalog model (first entry when omitted) with white gold applied. Async — resolves when the model is in the scene.
const id = await api.addRing('engagement', 'ring-2');removeRing(id)
Removes a ring. The last remaining wedding band cannot be removed.
moveRing(id, direction)
Moves a ring one slot 'left' or 'right'; the 3D layout follows.
renameRing(id, name)
Sets the display name shown on the ring's tab and in the manager dialog.
swapRingModel(id, catalogId)
Swaps a loaded ring's model in place — same id, tab, name, slot, and metal. The current model stays visible until the replacement has loaded. Async.
setRingMetal(id, metalId) / getRingMetal(id)
Applies a metal variant ('white', 'yellow', 'rose') to a loaded ring. Uses the same polished .pmat materials as the wedding bands, so colors match across the compilation.
setRingVisible(id, visible)
Shows or hides a ring without removing it (it keeps its slot). The camera re-frames the visible scene and shadows rebake.
focusRing(id)
Smoothly centers the camera on one ring, any type — the same fit switchBand performs for bands.
Layout
getAvailableLayouts()
Returns the layout IDs registered by the active UI build.
console.log(api.getAvailableLayouts());
// ['panel', 'boutique', 'modern-metals']Returns: string[]
getLayout()
Returns the selected layout ID, or null when the UI should use its default.
Returns: string | null
setLayout(id)
Switches to a registered layout and emits layout:changed. Unknown IDs are ignored and produce a validation:warning event.
api.setLayout('boutique');Changing layout affects presentation only. It does not add or remove rings. Drive-created Modern Metals projects are single-ring because Drive scaffolds their project state that way.
Returns: void
registerLayouts(ids)
Declares the layout ids this UI build provides, so getAvailableLayouts() and setLayout() can validate against them. The bundled React UI calls this for you. A custom front end calls it only when it registers layouts of its own.
api.registerLayouts(['panel', 'boutique', 'modern-metals']);Returns: void
Theme
setTheme(theme)
Apply a preset theme or custom theme configuration.
// Preset
api.setTheme('dark');
// Custom with preset base
api.setTheme({
preset: 'luxury-gold',
colors: { primary: '#8B7355' },
fonts: {
body: "'Cormorant Garamond', serif",
googleFonts: ['Cormorant+Garamond:wght@400;500;600'],
},
panelWidth: 380,
showPoses: true,
showARButton: true,
});
// Fully custom
api.setTheme({
colors: {
primary: '#2E5B3C',
text: '#1a1a1a',
background: '#F5F7F5',
surface: '#ffffff',
border: '#D4DDD4',
},
});See Theming for full details.
Returns: void
cycleTheme()
Cycles to the next preset theme. Useful for previewing themes.
const nextTheme = api.cycleTheme();
console.log(`Now using: ${nextTheme}`);Returns: ThemePresetName (the name of the newly applied theme)
getThemeCSSVariables()
Returns the core CSS custom properties resolved from the active theme. This is useful when styling a specification sheet or host-page controls to match the built-in Wedding Band UI.
const variables = api.getThemeCSSVariables();
Object.entries(variables).forEach(([name, value]) => {
document.documentElement.style.setProperty(name, value);
});Returns: Record<string, string>
Pricing
setPricingParams(params, bandName?)
Override pricing parameters at runtime.
api.setPricingParams({
metalDensityGcm3: 15.5,
metalPricePerGram: 52.0,
diamondPricePerCarat: 1500,
markupMultiplier: 2.5,
weightUnit: 'gram', // 'gram' | 'ounce' | 'troy_ounce'
});Returns: void
See Pricing API for detailed documentation.
Events
Subscribe to events using the events property:
const unsubscribe = api.events.on('price:updated', (data) => {
console.log(data);
});
// Unsubscribe
unsubscribe();
// or
api.events.off('price:updated', handler);For direct integrations, wait for the window-level ijewel-viewer-ready event before reading the controller. For cross-origin iframes, wait for the forwarded { event: 'ready' } message. The controller's own ready event is emitted during initialization and may occur before a same-page host obtains the controller.
Event Reference
| Event | Payload | Fired When |
|---|---|---|
profile:changed | { bandName, profileIndex, profileName } | Profile shape changes |
path:changed | { bandName, pathId, strength? } | Extrusion path changes |
dimensions:changed | { bandName, width, height, radius } | Width, height, or radius changes |
material:changed | { bandName, slot, metal, finish } | Material on any slot changes |
partition:changed | { bandName, numColors } | Number of color zones changes |
diamonds:changed | { bandName, settingType, numStones } | Diamond configuration changes |
edge:changed | { bandName, type, side } | Edge treatment changes |
engraving:changed | { bandName, text, font } | Engraving text or font changes |
milgrain:changed | { bandName, enabled } | Milgrain state changes |
finish:changed | { bandName, insidePolished, splitGroove } | Inside polish or groove split toggles |
inlays:changed | { bandName, inlays } | The inlay list changes |
overlays:changed | { bandName, overlays } | The rim-overlay list changes |
sleeve:changed | { bandName, sleeve } | The bore sleeve changes or clears |
build:started | { bandName } | Ring geometry rebuild begins |
build:complete | { bandName, durationMs } | Ring geometry rebuild finishes |
price:updated | { bandName, pricing } | Price recalculated (see PriceBreakdown) |
history:changed | { canUndo, canRedo } | Undo or redo availability changes |
config:saved | { id, name } | A saved configuration is created or replaced |
config:loaded | { id, name? } | A saved configuration is loaded |
config:deleted | { id } | A saved configuration is deleted |
band:switched | { from, to } | Active band changes |
link:changed | { linked } | Wedding-band linking changes |
rings:changed | { rings } | Ring compilation changes (add / remove / reorder / rename / metal / visibility) |
compatibility:resolved | { bandName, feature, value, action, reason, message } | A configuration change made a feature incompatible and it was auto-removed, or an incompatible request was rejected |
pose:changed | { poseIndex } | Camera pose changes |
ar:started | {} | AR try-on session begins |
ar:stopped | {} | AR try-on session ends |
theme:changed | { theme, ui? } | Theme or UI options are applied |
layout:changed | { from, to } | Built-in layout changes |
ready | {} | Plugin fully initialized |
disposed | {} | Plugin disposed and cleaned up |
validation:warning | { field, message, provided, corrected } | A value was clamped or rejected (out of range input) |
error | { source, message, details? } | An error occurred (also emitted as a log event with level 'error') |
log | { level, source, message, details?, timestamp } | Any log entry emitted (all levels) |
The cross-origin bridge forwards every event in this table except pose:changed, ar:started, and ar:stopped. Those three events are available only through direct api.events subscriptions.
Lifecycle
dispose()
Removes controller event listeners and marks the controller unavailable. The Mini Viewer calls this automatically when a Wedding Band project is cleared, reloaded, or the viewer is destroyed. Application code normally should not call it directly.
api.dispose(); // advanced/manual teardown onlyAfter disposal, get the new controller from the next viewer/project initialization instead of reusing the old instance.
Types
RingSnapshot
interface RingSnapshot {
bandName: string
profile: ProfileSnapshot
dimensions: DimensionsSnapshot
materials: MaterialSnapshot
diamonds: DiamondSnapshot | null
edge: EdgeSnapshot
engraving: EngravingSnapshot | null
pricing: PriceBreakdown | null
}ProfileSnapshot
interface ProfileSnapshot {
index: number
name: string
fileName: string
}DimensionsSnapshot
interface DimensionsSnapshot {
widthMm: number // Legacy name: profile width scale multiplier
heightMm: number // Legacy name: profile height scale multiplier
radiusMm: number // Physical radius in millimeters
}MaterialSnapshot
interface MaterialSnapshot {
partition: number // Number of active material zones
slots: MaterialSlot[]
insidePolished: boolean
splitAtGroove: boolean
inlays?: InlayConfig[]
overlays?: OverlayConfig[]
sleeve?: SleeveConfig
}
interface MaterialSlot {
slot: number // One-based active material slot
metal: string // Base material id — same value as `base`
finish: string // Finish id; '' for a material with no finish axis
base?: string // Base material id
variant?: string // Quality or sub-category: '18k', '14k', 'koa'
}
// The same slot on the way in, through batch() / importConfig(): only `slot`
// is required, and an omitted field keeps that slot's current value.
interface MaterialSlotInput extends Partial<MaterialSlot> {
slot: number
}MaterialEntry, MaterialVariant, and MaterialRef
type MaterialUsage = 'band' | 'inlay' | 'overlay' | 'sleeve'
interface MaterialEntry {
id: string
name: string
kind?: 'metal' | 'ceramic' | 'organic' | 'composite'
iconUrl?: string
swatch?: string
usage?: MaterialUsage[]
variants: MaterialVariant[]
defaultVariant?: string
finishes?: string[]
defaultFinish?: string | null
}
interface MaterialVariant {
id: string
name: string
iconUrl?: string
swatch?: string
files?: Record<string, string>
file?: string
density?: number
pricePerGram?: number
}
interface MaterialRef {
base: string
variant?: string
finish?: string
}density and pricePerGram are reserved fields. The current runtime pricing engine does not read per-variant values from them.
InlayConfig
interface InlayConfig {
centerZ: number
widthMm: number
metal: string
variant?: string
finish?: string
}OverlayConfig
interface OverlayConfig {
side: 'left' | 'right'
widthMm: number
metal: string
variant?: string
finish?: string
coversRim?: boolean
rimCoverage?: number
}SleeveConfig
interface SleeveConfig {
enabled: boolean
metal: string
variant?: string
finish?: string
full?: boolean
widthMm?: number
}DiamondSnapshot
interface DiamondSnapshot {
settingType: string
span: string
spacing: string
count: number
placement: number
stoneSize: number
position: number // -1 (left) to 1 (right), 0 = center
}EdgeSnapshot
interface EdgeSnapshot {
type: string
side: string
}EngravingSnapshot
interface EngravingSnapshot {
text: string
font: string
fontSize: number
}PriceBreakdown
interface PriceBreakdown {
volumeMm3: number
weightGrams: number
metalPrice: {
name: string // e.g. "White Gold" - reflects active metal
densityGcm3: number
pricePerGram: number
totalUsd: number
}
makingCharge: {
mode: 'none' | 'percent' | 'per-gram'
percent: number
perGram: number
totalUsd: number
}
diamonds: {
count: number
totalCarats: number
pricePerCarat: number
totalUsd: number
} | null
settingCost: {
costPerStone: number
count: number
totalUsd: number
} | null
finishSurcharge: {
name: string // e.g. "Hammered"
totalUsd: number
} | null
subtotalUsd: number
markupMultiplier: number
totalBeforeRounding: number
totalUsd: number
}PricingParams
interface PricingParams {
metalDensityGcm3?: number
metalPricePerGram?: number
metalPrices?: Record<string, MetalPriceEntry>
finishSurcharges?: Record<string, number>
diamondPricePerCarat?: number
markupMultiplier?: number
weightUnit?: WeightUnit
makingChargeMode?: MakingChargeMode
makingChargePercent?: number
makingChargePerGram?: number
settingCostPerStone?: number
roundingEnabled?: boolean
roundingStep?: number
}
interface MetalPriceEntry {
density: number
pricePerGram: number
}
type WeightUnit = 'gram' | 'ounce' | 'troy_ounce'
type MakingChargeMode = 'none' | 'percent' | 'per-gram'See Pricing Engine for detailed documentation on pricing models, per-metal pricing, making charges, and rounding.
BatchConfig
interface BatchConfig {
profile?: { index: number } | { name: string }
dimensions?: Partial<DimensionsSnapshot>
// Slots are MaterialSlotInput, so a `materials` block read straight out of
// exportConfig() can be handed back unchanged. `sleeve: null` removes the
// sleeve; omitting the key leaves the current one alone.
materials?: Omit<Partial<MaterialSnapshot>, 'slots' | 'sleeve'> & {
slots?: MaterialSlotInput[]
sleeve?: SleeveConfig | null
}
diamonds?: Partial<DiamondSnapshot> | null
edge?: Partial<EdgeSnapshot>
engraving?: Partial<EngravingSnapshot> | null
pricing?: PricingParams
}UITheme
interface UITheme {
preset?: ThemePresetName
colors?: ThemeColors
fonts?: ThemeFonts
shapes?: ThemeShapes
components?: ThemeComponentOverrides
customCSS?: string
panelWidth?: number
showPoses?: boolean
showARButton?: boolean
showExportButton?: boolean
showRingToggle?: boolean
initialTab?: string
}ThemeColors
interface ThemeColors {
primary?: string
primaryHover?: string
secondary?: string
text?: string
textMuted?: string
textPlaceholder?: string
background?: string
surface?: string
surfaceHover?: string
border?: string
borderLight?: string
borderHover?: string
}ThemeFonts
interface ThemeFonts {
body?: string
headline?: string
light?: string
googleFonts?: string[] // e.g., ['Inter:wght@300;400;500']
}ThemePresetName
type ThemePresetName =
| 'default'
| 'luxury-gold'
| 'modern-minimal'
| 'dark'
| 'rose-elegant'
| 'coral-modern'
| 'fresh-teal'
| 'minimal-blue'
| 'warm-beige'
| 'classic-gold'
| 'ijewel'LogLevel
type LogLevel = 'error' | 'warn' | 'info' | 'silent'LogSource
type LogSource = 'api' | 'material' | 'geometry' | 'ar' | 'config' | 'engraving' | 'pricing'LogEntry
interface LogEntry {
level: Exclude<LogLevel, 'silent'>
source: LogSource
message: string
details?: any
timestamp: number
}CSS Custom Properties
The theme system injects CSS custom properties that you can use in your own stylesheets:
| Property | Description |
|---|---|
--rb-color-primary | Primary accent color |
--rb-color-primary-hover | Primary hover state |
--rb-color-secondary | Secondary accent color |
--rb-color-text | Main text color |
--rb-color-text-muted | Secondary text color |
--rb-color-background | Page background |
--rb-color-surface | Card/panel background |
--rb-color-border | Border color |
--rb-font-body | Body font family |
--rb-font-headline | Headline font family |
--rb-radius-md | Border radius |
/* Use theme variables in your custom styles */
.my-custom-panel {
background: var(--rb-color-surface);
color: var(--rb-color-text);
border: 1px solid var(--rb-color-border);
font-family: var(--rb-font-body);
}