Conditional Logic
Define rules that dynamically show/hide option blocks or specific variants based on user selections. Rules evaluate at runtime using "all" (AND) or "any" (OR) logic operators.
How Conditional Logic Works
Hide rules: Block/variants start visible and become hidden when conditions match.
Show rules: Block/variants start hidden and become visible when conditions match.
Evaluation order: Hide rules first, then show rules can override.
Match types: is-selected and is-not-selected
Auto-apply on show: When a block transitions from hidden → visible (e.g. via a show / block rule), the default or previously-selected material from that block is automatically applied to the 3D viewport. This ensures the model always reflects the newly-visible block's material without requiring an explicit user click.
Add Conditional Rules to a Block
// Hide the "Armrest Color" block when "Armrests" toggle is set to "without"
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/blk_armrest_color', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
conditionalRules: [
{
id: crypto.randomUUID(),
action: 'hide',
targetScope: 'block',
targetVariantIds: [],
operator: 'all',
conditions: [
{
id: crypto.randomUUID(),
sourceBlockId: 'blk_armrests',
matchType: 'is-selected',
sourceVariantIds: ['var_without_armrests']
}
]
}
]
})
});Variant-Level Conditional Logic
// Hide specific fabric options when "Outdoor" is selected in another block
const rules = [{
id: crypto.randomUUID(),
action: 'hide',
targetScope: 'variants',
targetVariantIds: ['var_silk', 'var_velvet'], // These fabrics are indoor-only
operator: 'all',
conditions: [{
id: crypto.randomUUID(),
sourceBlockId: 'blk_environment',
matchType: 'is-selected',
sourceVariantIds: ['var_outdoor']
}]
}];
// Show premium materials only when "Enterprise" plan is detected
const showPremiumRule = {
id: crypto.randomUUID(),
action: 'show',
targetScope: 'variants',
targetVariantIds: ['var_carbon_fiber', 'var_titanium'],
operator: 'any',
conditions: [{
id: crypto.randomUUID(),
sourceBlockId: 'blk_tier',
matchType: 'is-selected',
sourceVariantIds: ['var_enterprise', 'var_premium']
}]
};Multi-Condition Rules (AND / OR)
// Show the "Heated Seats" option ONLY when BOTH conditions are met:
// 1. Vehicle type is "SUV" or "Sedan"
// 2. Climate package is selected
const rule = {
id: crypto.randomUUID(),
action: 'show',
targetScope: 'block',
targetVariantIds: [],
operator: 'all', // ALL conditions must match (AND logic)
conditions: [
{
id: crypto.randomUUID(),
sourceBlockId: 'blk_vehicle_type',
matchType: 'is-selected',
sourceVariantIds: ['var_suv', 'var_sedan'] // ANY of these
},
{
id: crypto.randomUUID(),
sourceBlockId: 'blk_climate_pkg',
matchType: 'is-selected',
sourceVariantIds: ['var_climate_yes']
}
]
};3D Object Visibility Rules
Use targetScope: '3d' to show/hide 3D objects in the viewport without affecting the UI block. This lets you keep the option panel visible while controlling 3D representation independently.
// Hide the armrest 3D objects when "Minimalist" style is selected
const rule = {
id: crypto.randomUUID(),
action: 'hide',
targetScope: '3d',
target3dObjectNames: ['Armrest_Left', 'Armrest_Right'],
targetVariantIds: [],
operator: 'all',
conditions: [{
id: crypto.randomUUID(),
sourceBlockId: 'blk_style',
matchType: 'is-selected',
sourceVariantIds: ['var_minimalist']
}]
};
// A block can have BOTH a block-hide rule and a 3D-hide rule:
// Rule 1: Hide the block UI when condition X
// Rule 2: Hide 3D objects when condition Y3D Visibility Rules
Use targetScope: '3d' with target3dObjectNames to show/hide whole 3D objects, or targetScope: '3d-parts' with target3dPartNames to show/hide individual mesh parts (sub-meshes) in the viewport. Both fields hold names, not ids — see Two keyspaces below.
// Hide decorative stitching and logo when "Plain" finish is selected
const rule = {
id: crypto.randomUUID(),
action: 'hide',
targetScope: '3d-parts',
target3dPartNames: ['stitching_detail', 'logo_emboss', 'button_caps'],
targetVariantIds: [],
operator: 'all',
conditions: [{
id: crypto.randomUUID(),
sourceBlockId: 'blk_finish',
matchType: 'is-selected',
sourceVariantIds: ['var_plain']
}]
};
// Use '3d' scope for whole objects, '3d-parts' for individual meshes.
// Both keep the UI block visible while controlling viewport visibility.
// Enterprise + Modular projects (modular option block) can target 3D parts
// across MULTIPLE objects in one rule via target3dPartsByObject — a map of
// model.fileName -> part names that supersedes the single-object
// target3dPartObjectName + target3dPartNames pair. Each object hides only the
// parts listed under its own key, so same-named meshes elsewhere are untouched.
const multiObjectRule = {
id: crypto.randomUUID(),
action: 'hide',
targetScope: '3d-parts',
target3dPartsByObject: {
'Main_roof_3x3.glb': ['ridge_caps', 'flashing'],
'Extra_roof_3x3.glb': ['ridge_caps_extra', 'flashing_extra']
},
targetVariantIds: [],
operator: 'all',
conditions: [{
id: crypto.randomUUID(),
sourceBlockId: 'blk_treatment',
matchType: 'is-selected',
sourceVariantIds: ['var_smooth']
}]
};Evaluate Conditions (Runtime)
Evaluate all conditional logic rules against a set of consumer selections. Returns which blocks and variants are visible.
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/evaluate', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
selections: {
dropdownSelections: { 'blk_frame': 'black', 'blk_size': 'large' },
selectMaterialSelections: { 'blk_fabric': 'linen' },
checkboxSelections: { 'blk_accessories': ['cup-holder', 'monitor-stand'] },
toggleSwitchSelections: { 'blk_armrests': 'with' },
carouselSelections: {}
}
})
});
const data = await response.json();
// {
// "success": true,
// "results": [
// { "blockId": "blk_001", "blockVisible": true, "hiddenVariantIds": [],
// "hidden3dObjectNames": [], "hidden3dObjectIds": [],
// "hidden3dPartNames": [], "hiddenMaterialIds": [] },
// { "blockId": "blk_002", "blockVisible": true, "hiddenVariantIds": ["var_silk"],
// "hidden3dObjectNames": [], "hidden3dObjectIds": [],
// "hidden3dPartNames": [], "hiddenMaterialIds": ["mat_silk"] },
// { "blockId": "blk_armrest_color", "blockVisible": true, "hiddenVariantIds": [],
// "hidden3dObjectNames": ["armrest_left", "armrest_right"],
// "hidden3dObjectIds": ["mdl_7f2a", "mdl_9c31"],
// "hidden3dPartNames": ["logo_emboss"], "hiddenMaterialIds": [] },
// { "blockId": "blk_heated", "blockVisible": false, "hiddenVariantIds": [],
// "hidden3dObjectNames": [], "hidden3dObjectIds": [],
// "hidden3dPartNames": [], "hiddenMaterialIds": [] }
// ],
// // Join table: resolves the NAMES above to the model IDs your scene is keyed by.
// "models": [
// { "id": "mdl_7f2a", "fileName": "Armrest_Left.glb", "name": "armrest_left" },
// { "id": "mdl_9c31", "fileName": "Armrest_Right.glb", "name": "armrest_right" }
// ]
// }Two keyspaces: names vs. model IDs
Conditional-logic rules and variant targeting do not address 3D objects the same way, and mixing them up is the usual cause of “the rule evaluates but nothing hides”:
| Surface | Addresses objects by |
|---|---|
Conditional-logic rules — target3dObjectNames, target3dPartNames, target3dPartsByObject keys | Name — the mesh / object name authored in the GLB, or the model’s fileName for the per-object map. |
Variant targeting — targetObjectId, selectedTargetParts keys | Opaque model ID — the scene’s models[].id, which is stable across model version replacement. |
The evaluate response bridges the two so you never have to build the mapping yourself:
- •
modelsis the join table —{ id, fileName, name }for every model in the scene. Index it byname(orfileName) to reach theidyour renderer is keyed by, or byidto label a model for a human. - •
hidden3dObjectIdsishidden3dObjectNamesalready resolved through that table. Prefer it when your scene graph is keyed by model id; a name that matches no loaded model simply does not appear in it. - •
hiddenMaterialIdscarriestargetScope: 'materials'results — material ids to suppress from the block’s swatches. This field has always been returned; it is now documented.
Backwards compatible
models, hidden3dObjectIds and hiddenMaterialIds are additive. Existing integrations reading only blockVisible, hiddenVariantIds, hidden3dObjectNames and hidden3dPartNames are unaffected. Treat all of them as possibly-absent (?? []) so your client also works against an older deployment.
