Skip to main content

SKYPARAM

This library is intended to handle the configurator and its parameters.


Example

As an example, let's think about a configurator for a T-shirt with 9 parameters (only configurator is shown, no visualization). Try it out!


Parameter

Parameters represent the user interaction with the component for creating a good product.

Difference from Properties

Properties represent the values that a component can have. It is recommended to bundle governing properties in the same object. They are usually found in the constructor of each component under COMPONENT, having their type defined before in an interface.

interface Properties {
material: CONSTANTS.MATERIAL
size: number
sleeveLength: number
style: number
mainColor: SKYCAD.RalColorCode
detailsColor: SKYCAD.RgbColor
logoColor: SKYCAD.RgbColor
logoName: string
hasPlayerNumber: boolean
}

export class Component extends STUDIO.BaseComponent<Properties> {
constructor() {
super()

this.properties = {
material: CONSTANTS.MATERIAL.LINEN,
size: 40,
sleeveLength: 20,
style: 3,
mainColor: 9005,
detailsColor: new SKYCAD.RgbColor(255, 50, 50),
logoColor: new SKYCAD.RgbColor(50, 50, 50),
logoName: '💡 SkyMaker',
hasPlayerNumber: true,
}
}

update() {
/* ... */
}

generateGeometry() {
/* ... */
}
}

Although properties are merely objects and don't belong to any library, it is important to distinguish them from the parameters, that a configurator may have, and they could be related to a property in many ways (e.g. through rules, a combination of them, default parameter values, etc). Although it's usual to find 1 parameter for each configurable property, a configurator can have e.g. 3 parameters, one for each coordinate of an item that can be placed in 3D, but all these parameters can be connected to 1 single property (e.g. as a SKYMATH.Vector3D). So it's important to know the clear difference between a property and a parameter.

Also it's good practice that the default values of the parameters match the default properties of the component. Otherwise if you hard-code these values, they will be reset when the configurator is loaded again (e.g. when switching tabs, updating configurator rules, etc). For this reason, we show here how to set the default value for each type of parameter. Check the final configurator to see where these values come from exactly!

Types

These are all the available:

You can explore yourself all the optional arguments available for every SKYPARAM function and element (parameters, items, etc). Simply, add , { } at then end of it, and press Ctrl+Space within the curly brackets to see the list available, similarly explained in Code Completion. If you have any trouble understanding them, simply reach support@dynamaker.com and we will help you with some examples!

const dropdownParameter = SKYPARAM.generateDropdown(PARAMETER_ID.MATERIAL, 'Material', [
new SKYPARAM.DropdownItem('Linen', CONSTANTS.MATERIAL.LINEN),
new SKYPARAM.DropdownItem('Cotton', CONSTANTS.MATERIAL.COTTON),
new SKYPARAM.DropdownItem('Lycra', CONSTANTS.MATERIAL.LYCRA),
])
dropdownParameter.setValue(material) // sets default value
const dropdownWithInputParamater = SKYPARAM.generateDropdownWithInput(
PARAMETER_ID.SIZE,
'Size',
[
new SKYPARAM.DropdownItem('XL', 48),
new SKYPARAM.DropdownItem('L', 44),
new SKYPARAM.DropdownItem('M', 40),
new SKYPARAM.DropdownItem('S', 36),
],
{ defaultValue: size },
)

Input

const inputParameter = SKYPARAM.generateInputParameter(PARAMETER_ID.SLEEVE_LENGTH, 'Sleeve length', {
defaultValue: sleeveLength,
minValue: 0,
maxValue: 50,
stepSize: 1,
unit: 'mm',
})

Slider

const sliderParameter = SKYPARAM.generateSlider(PARAMETER_ID.STYLE, 'Style', {
defaultValue: style,
minValue: 1,
maxValue: 7,
stepSize: 1,
})

RAL Color

const ralColorParameter = SKYPARAM.generateRalParameter(
PARAMETER_ID.MAIN_COLOR,
'Main color',
[
new SKYPARAM.DropdownItem('Blue (RAL 5012)', 5012),
new SKYPARAM.DropdownItem('Yellow (RAL 1018)', 1018),
new SKYPARAM.DropdownItem('Black (RAL 9005)', 9005),
],
{ defaultValue: mainColor },
)

How to remove RAL colors

In case you want to remove some of the default colors because of certain reasons (e.g. RAL 2005 is not manufacturable), you can remap the list of predefined RAL colors that come with the parameter, updating the parameter's colorMap from a list of RAL colors as string as:

const defaultRalMap = SKYPARAM.getDefaultRalMap()
const excludedRalList = ['1026', '2005']
const filteredRalMap = Object.keys(defaultRalMap)
.filter((key) => !excludedRalList.includes(key))
.reduce((obj, key) => {
obj[key] = defaultRalMap[key]
return obj
}, {})

const ralColorParameter = SKYPARAM.generateRalParameter(PARAMETER_ID.MAIN_COLOR, 'Main color',
[
new SKYPARAM.DropdownItem('Blue (RAL 5012)', 5012),
new SKYPARAM.DropdownItem('Yellow (RAL 1018)', 1018),
new SKYPARAM.DropdownItem('Black (RAL 9005)', 9005),
],
{
defaultValue: mainColor,
colorMap: filteredRalMap,
}

How to add RAL colors

In case you want to add some custom RAL colors because of certain reasons (e.g. RAL 4847 is manufacturable), you can remap the list of predefined RAL colors that come with the parameter, updating the parameter's colorMap with a combined list of default RAL colors together with a list of your own RAL colors in a object with their RGB code, as:

const defaultRalMap = SKYPARAM.getDefaultRalMap()
const customRalList = {
4847: '166, 115, 89',
1234: '250, 32, 165'
}
const combinedRalMap = { ...defaultRalMap, ...customRalList }

const ralColorParameter = SKYPARAM.generateRalParameter(PARAMETER_ID.MAIN_COLOR, 'Main color',
[
new SKYPARAM.DropdownItem('Blue (RAL 5012)', 5012),
new SKYPARAM.DropdownItem('Yellow (RAL 1018)', 1018),
new SKYPARAM.DropdownItem('Black (RAL 9005)', 9005),
],
{
defaultValue: mainColor,
colorMap: combinedRalMap,
}

Color Picker

const colorPickerParameter = SKYPARAM.generateColorPickerParameter(PARAMETER_ID.DETAILS_COLOR, 'Details color', [
{
title: 'Predefined options',
options: [
{ label: 'Red', value: new SKYCAD.RgbColor(225, 0, 0) },
{ label: 'Green', value: new SKYCAD.RgbColor(0, 225, 0) },
{ label: 'Blue', value: new SKYCAD.RgbColor(0, 0, 225) },
],
},
])
colorPickerParameter.setValue(detailsColor) // sets default value

Palette Color

const paletteParameter = SKYPARAM.generatePaletteParameter(PARAMETER_ID.LOGO_COLOR, 'Logo color', [
new SKYPARAM.DropdownItem('Light', new SKYCAD.RgbColor(200, 200, 200).toRgbNumber()),
new SKYPARAM.DropdownItem('Neutral', new SKYCAD.RgbColor(125, 125, 125).toRgbNumber()),
new SKYPARAM.DropdownItem('Dark', new SKYCAD.RgbColor(50, 50, 50).toRgbNumber()),
])
paletteParameter.setValue(logoColor.toRgbNumber()) // sets default value

Text

const textParameter = SKYPARAM.generateTextParameter(PARAMETER_ID.LOGO_NAME, 'Logo name', logoName)

Checkbox

The value given by a checkbox parameter is a boolean.

const checkboxParameter = SKYPARAM.generateCheckbox(PARAMETER_ID.HAS_PLAYER_NUMBER, 'Add player number?', {
defaultValue: hasPlayerNumber,
})

Configurator

A configurator allows us to customize the product through parameters. Since this is the key feature of all CAD configurators done in DynaMaker, the workflow of the app will depend heavily on them. Therefore, in order to have a good user experience (UX), one needs to design them accordingly (complemented with other UI items like tabs and buttons of course), including the rules that affect the parameters with one another, and most importantly their order.

To show the difference that having rules makes, here are two types of configurators:

Configurator without rules

A configurator without rules is rare to have since you probably want to use the full potential of what they can offer. However, sometimes a configurator with 1 to 3 parameters should be enough for certain parts of your application. Here is an example of the previous Shirt configurator that doesn't contain any rule. See how simple is despite having 9 parameters (its structure is explained later):

Show configurator without rules:
const PARAMETER_ID = {
MATERIAL: 'material-parameter',
SIZE: 'size-parameter',
SLEEVE_LENGTH: 'sleeve-length-parameter',
STYLE: 'style-parameter',
MAIN_COLOR: 'main-parameter',
LOGO_COLOR: 'logo-parameter',
DETAILS_COLOR: 'details-parameter',
LOGO_NAME: 'logo-name-parameter',
HAS_PLAYER_NUMBER: 'has-player-number-parameter',
}

export function generateConfigurator(component: COMPONENT.Component): SKYPARAM.Configurator {
const { material, size, sleeveLength, style, mainColor, logoColor, detailsColor, logoName, hasPlayerNumber } =
component.getProperties()

const dropdownParameter = SKYPARAM.generateDropdown(PARAMETER_ID.MATERIAL, 'Material', [
new SKYPARAM.DropdownItem('Linen', CONSTANTS.MATERIAL.LINEN),
new SKYPARAM.DropdownItem('Cotton', CONSTANTS.MATERIAL.COTTON),
new SKYPARAM.DropdownItem('Lycra', CONSTANTS.MATERIAL.LYCRA),
])
dropdownParameter.setValue(material) // sets default value

const dropdownWithInputParamater = SKYPARAM.generateDropdownWithInput(
PARAMETER_ID.SIZE,
'Size',
[
new SKYPARAM.DropdownItem('XL', 48),
new SKYPARAM.DropdownItem('L', 44),
new SKYPARAM.DropdownItem('M', 40),
new SKYPARAM.DropdownItem('S', 36),
],
{ defaultValue: size },
)

const inputParameter = SKYPARAM.generateInputParameter(PARAMETER_ID.SLEEVE_LENGTH, 'Sleeve length', {
defaultValue: sleeveLength,
minValue: 0,
maxValue: 50,
stepSize: 1,
unit: 'mm',
})

const sliderParameter = SKYPARAM.generateSlider(PARAMETER_ID.STYLE, 'Style', {
defaultValue: style,
minValue: 1,
maxValue: 7,
stepSize: 1,
})

const ralColorParameter = SKYPARAM.generateRalParameter(
PARAMETER_ID.MAIN_COLOR,
'Main color',
[
new SKYPARAM.DropdownItem('Blue (RAL 5012)', 5012),
new SKYPARAM.DropdownItem('Yellow (RAL 1018)', 1018),
new SKYPARAM.DropdownItem('Black (RAL 9005)', 9005),
],
{ defaultValue: mainColor },
)

const paletteParameter = SKYPARAM.generatePaletteParameter(PARAMETER_ID.LOGO_COLOR, 'Logo color', [
new SKYPARAM.DropdownItem('Light', new SKYCAD.RgbColor(200, 200, 200).toRgbNumber()),
new SKYPARAM.DropdownItem('Neutral', new SKYCAD.RgbColor(125, 125, 125).toRgbNumber()),
new SKYPARAM.DropdownItem('Dark', new SKYCAD.RgbColor(50, 50, 50).toRgbNumber()),
])
paletteParameter.setValue(logoColor.toRgbNumber()) // sets default value

const colorPickerParameter = SKYPARAM.generateColorPickerParameter(PARAMETER_ID.DETAILS_COLOR, 'Details color', [
{
title: 'Predefined options',
options: [
{ label: 'Red', value: new SKYCAD.RgbColor(225, 0, 0) },
{ label: 'Green', value: new SKYCAD.RgbColor(0, 225, 0) },
{ label: 'Blue', value: new SKYCAD.RgbColor(0, 0, 225) },
],
},
])
colorPickerParameter.setValue(detailsColor)

const textParameter = SKYPARAM.generateTextParameter(PARAMETER_ID.LOGO_NAME, 'Logo name', logoName)

const checkboxParameter = SKYPARAM.generateCheckbox(PARAMETER_ID.HAS_PLAYER_NUMBER, 'Add player number?', {
defaultValue: hasPlayerNumber,
})

const configurator = new SKYPARAM.Configurator([
dropdownParameter,
dropdownWithInputParamater,
inputParameter,
sliderParameter,
ralColorParameter,
colorPickerParameter,
paletteParameter,
textParameter,
checkboxParameter,
])

configurator.addCompletionCallback((validUserInput, values) => {
if (validUserInput) {
component.setProperties({
material: values[PARAMETER_ID.MATERIAL],
size: values[PARAMETER_ID.SIZE],
sleeveLength: values[PARAMETER_ID.SLEEVE_LENGTH],
style: values[PARAMETER_ID.STYLE],
mainColor: values[PARAMETER_ID.MAIN_COLOR],
detailsColor: values[PARAMETER_ID.DETAILS_COLOR],
logoColor: values[PARAMETER_ID.LOGO_COLOR],
logoName: values[PARAMETER_ID.LOGO_NAME],
hasPlayerNumber: values[PARAMETER_ID.HAS_PLAYER_NUMBER],
})
}
})

return configurator
}

Configurator with rules

So what could you do when you want to have a connection between parameters or that they can interact with each other (e.g. by disabling certain dropdown options, changing their max/min values, etc)? Use rules!

As an example we take the configurators from the open template Electronic Cabinet:


In this case, we have up to 4 different configurators:

  1. In tab Cabinet, for the three first parameters Width, Height and Depth.
  2. In tab Cabinet, for the two last parameters Min rail spacing and Cabinet color.
  3. In tab Modules, when adding a module through the flyout Add module.
  4. In tab Modules, when configuring a selected module through the button Configure.

Since UX is a key factor in every app, we have created these 4 configurators according to specific behaviors:

  1. To update the camera when updating the size.
  2. Not to update the camera when updating the rail spacing or color.
  3. Not to limit any module size parameter when creating a new one.
  4. To limit the width when re-configuring a selected module, so no extra collision detection needs to be made.

Once you have very clear what behaviors your app should have, creating configurators should be straightforward!

How To Create A Configurator

In your component, under PARAMETERS, create a function that creates a configurator from a list of parameters, like:

export function generateConfigurator1(component: COMPONENT.Component): SKYPARAM.Configurator {
// create parameters

const configurableParameters = [
// parameters are shown in the configurator with the same order as in this list
widthParameter,
heightParameter,
depthParameter,
]

const configurator = new SKYPARAM.Configurator(configurableParameters)

// add callback and rules

return configurator
}

You can keep adding more configurators to your component as long as they are in different functions. Whatever you export in PARAMETERS must return a SKYCAD.Configurator, so that you can preview the configurator in the component-maker (using the dropdown at the top-left corner). After creating the functions that generate configurators, you should be able to preview them like this:

Update Properties From Parameters

After creating the configurator, use addUpdateCallback to enter this function instantly when updating a parameter, or use addCompletionCallback if you want a small time delay due to performance, user experience, etc. Provide a callback function that takes two arguments:

  • valid as a boolean gives true if all parameters have valid input (e.g. out of range value in an input parameter)
  • values with all the parameter values existing in the configurator.
configurator.addCompletionCallback((valid, values) => {
if (valid) {
component.setProperties({
height: values[PARAMETER.HEIGHT],
width: values[PARAMETER.WIDTH],
depth: values[PARAMETER.DEPTH],
})
}
})

You can find the value of any parameter in values by matching it with the parameter ID. In order not to mix up the name of the property with the name of the parameter, you can create an object PARAMETER containing all parameter IDs as shown in the previous video, so that it is easier and safer to update properties accordingly.

Remember that component.setProperties() triggers automatically the function component.update(), which is empty in the default template. However, additional functions can be called within configurator.addCompletionCallback() by simply adding them after component.setProperties().

How To Use Configurator in UI

Once we have the configurator created, with or without rules, we would simply need to:

  • In the Component-editor tab PARAMETERS, make sure that the function is exported, by typing export before the function:

    export function generateConfigurator(component: COMPONENT.Component): SKYPARAM.Configurator {
    // logic to create configurator
    }
  • In the Component-editor tab API, make sure that all from PARAMETERS is exported outside the component:

    export * from './parameters.js'
  • In the UI-editor tab ACTIONS, make sure to have a function that gets the component where the configurator is located:

    export function getAssemblyComponent() {
    const componentHandler = manager.getComponentHandler()
    const assemblyComponent = componentHandler.getComponents(ASSEMBLY.Component)[0]!

    if (assemblyComponent === undefined) {
    throw new Error('No assembly component found')
    }

    return assemblyComponent
    }
  • In the UI-editor tab UI, get the assembly component from that action and send it as input to the function generateConfigurator(), so that the tab includes the configurator as content, like:

    export function onInit() {
    const componentHandler = Studio.getComponentHandler()
    const newAssemblyComponent = new ASSEMBLY.Component()
    componentHandler.add(newAssemblyComponent)
    Studio.requestGeometryUpdate()

    const tab1 = new SKYUI.Tab({
    title: 'Configure',
    onInit: () => {
    const assemblyComponent = ACTIONS.getAssemblyComponent()
    const configurator = ASSEMBLY.generateConfigurator(assemblyComponent)
    Studio.setTabContent([configurator /*, configurator2, buttonA, etc*/])
    },
    })

    Studio.setTabs([tab1 /*, tab2, etc*/])
    }
  • As a last step, still in the UI-editor tab UI, create a new callback function after the configurator, so that if valid, then update the geometry:

    export function onInit() {
    // create new assembly & update geometry

    const tab1 = new SKYUI.Tab({
    title: 'Configure',
    onInit: () => {
    const assemblyComponent = ACTIONS.getAssemblyComponent()
    const configurator = ASSEMBLY.generateConfigurator(assemblyComponent)
    configurator.addCompletionCallback((valid, values) => {
    // <-- this one
    if (valid) {
    Studio.requestGeometryUpdate()
    }
    })
    Studio.setTabContent([configurator])
    },
    })

    Studio.setTabs([tab1])
    }

Rules

Usually some parameter values might get constrained due to others. In DynaMaker we use configurator rules to update parameters according to others. However, there is a small difference in what a "rule" is depending on whether the parameters belong to the same configurator or not. Then we have rules for:

Parameters within the same configurator

Use setUpdateRule to create a rule between multiple parameters of the same configurator. In this case we can think of the following simple rules:

  1. The min value of height should be 0.5 times the width, and the max value of height should be 1.5 times the width.
  2. The depth option "Shallow (50 mm)" should be disabled when height is over 1000 mm".

Translating this into a few lines of code, we need a:

  • rule for the parameter Height:
configurator.setUpdateRule(PARAMETER.HEIGHT, (newValue, directUpdate, parameter, values) => {
if (!directUpdate) {
const currentWidth = values[PARAMETER.WIDTH]
const newHeight = parameter.getValue() // or newValue

// min value
const minValue = 0.5 * currentWidth
parameter.setMin(minValue)
if (newHeight < minValue) parameter.setValue(minValue) // (optional) if lower than the min, update its value to be minValue

// max value
const maxValue = 1.5 * currentWidth
parameter.setMax(maxValue)
if (newHeight > maxValue) parameter.setValue(maxValue) // (optional) if larger than the max, update its value to be maxValue
}
})
  • rule for the parameter Depth (might seem complex at first but what it does is resetting the options and keeps the old value if it is still available, otherwise it looks for the nearest option):
configurator.setUpdateRule(PARAMETER.DEPTH, (newValue, directUpdate, parameter, values) => {
if (!directUpdate) {
const currentHeight = values[PARAMETER.HEIGHT]
if (parameter.disabled || !parameter.visible) return // stops the rule here if parameter is disabled or hidden

const outdatedCurrentOption = parameter.getOption()
const newOptions = getDepthOptions(currentHeight) // see function in note below*
parameter.setItems(newOptions) // updates options but chooses the first option by default, regardless of its availability or the previous selected one

const updatedCurrentOption = parameter.getOptionByValue(outdatedCurrentOption.value)
const newOptionsValues = newOptions.map((option) => option.value)
if (updatedCurrentOption !== undefined) {
// it could happen that the outdated option is not in the options anymore

if (updatedCurrentOption.disabled) {
// checks if the updated option is disabled (e.g. "Shallow (50 mm)" can be disabled according to the height value)
const currentOptionId = newOptionsValues.indexOf(updatedCurrentOption.value)

for (let id = currentOptionId; id >= newOptionsValues.length; id++) {
// goes through the options looking for the next available option in ascending order
const newOptionId = parameter.getOptionByValue(newOptions[id - 1].value).id
parameter.setOptionById(newOptionId)
const newOption = parameter.getOption()
if (!newOption.disabled) return
}

// for (let id = currentOptionId; id >= 0; id--) { // goes through the options looking for the next available option in descending order
// const newOptionId = parameter.getOptionByValue(newOptions[id - 1].value).id
// parameter.setOptionById(newOptionId)
// const newOption = parameter.getOption()
// if (!newOption.disabled) return
// }
} else {
parameter.setOptionById(updatedCurrentOption.id)
}
} else {
parameter.setValue(newOptionsValues[0]) // extra logic can be added here to choose a specific default value, instead of the first available one
}
}
})

*See that getDepthOptions() should be a function outside PARAMETERS.generateConfigurator1() like:

function getDepthOptions(height: number) {
const isShallowOptionDisabled = height > 1000
return [
new SKYPARAM.DropdownItem('Shallow (50 mm)', 50, { disabled: isShallowOptionDisabled }),
new SKYPARAM.DropdownItem('Standard (80 mm)', 80),
new SKYPARAM.DropdownItem('Deep (120 mm)', 120),
]
}

Having the options in a separate function allows you to reuse it for the parameter and its rule, without duplicating code.

Here we see some arguments that configurator.setUpdateRule() uses:

  • rule for the height:

    • newValue, same as parameter.getValue(), is the current value of the parameter Height.
    • directUpdate becomes false if any of the parameters above, i.e. Width, is being changed.
    • parameter contains all the info about the parameter where the rule is being set to, i.e. Height here.
    • values contains all the values of the parameters above the current one, i.e. Width here.
  • rule for the depth:

    • newValue, same as parameter.getValue(), is the current value of the parameter Depth.
    • directUpdate becomes false if any of the parameters above, i.e. Width or Height, is being changed.
    • parameter contains all the info about the parameter where the rule is being set to, i.e. Depth here.
    • values contains all the values of the parameters above the current one, i.e. Width and Height here.

If you start realizing that some parameters are being affected by something below it, then the design of the configurator is wrong. Therefore, you would need to update the order of the parameters, so that no parameter is affected by another below it. Taking this into account is crucial for a good user experience and workflow of the app.

Parameters in different configurators

When we want to update parameters that belong to different configurators, there is no need to use configurator.setUpdateRule(). If we want to update a parameter from a configurator in tab A, based on the values from parameters of a different configurator in tab B, then the value we want should come from the properties of the component.

For example, if we want the module width (from a configurator in Modules) not to be larger than the cabinet width (from a configurator in Cabinet), the parameter for the module width would look like this:

const maxModuleWidth = component.getProperty('width')

const moduleWidthParameter = SKYPARAM.generateInputParameter(PARAMETER.MODULE_WIDTH, 'Module Width', {
defaultValue: component.getProperty('moduleWidth'),
maxValue: maxModuleWidth
minValue: 10,
stepSize: 0.1,
unit: 'mm',
})

There's no need for a configurator rule for it since the max value of the property can be directly retrieved from the property cabinetWidth.

However, there is a small difference in behavior when the configurators are in the same or different tab, and that's because when they are created. As mentioned before, in the component-maker we can test the configurators right away in the preview, but individually. It's in the app where we should test the behavior between configurators, since "more things" can happen than just rather changes in parameters max values, like geometry or camera updates and so on.

So then we have these cases when:

Configurators in different tabs

The most common case here applies the same thing as mentioned before: it is enough if we get the values we want from the properties.

As a more complex example, let's think about this case: since the cabinet width affects the width of all the existing modules, we want not to remove the invalid modules but update their properties. In that case, we need to add this logic in the callback of the configurator that is affecting the rest, so like:

// PARAMETERS

export function generateConfigurator1(component: COMPONENT.Component): SKYPARAM.Configurator {
// configurableParameters

const configurator = new SKYPARAM.Configurator(configurableParameters)

// configurator rules

configurator.addCompletionCallback((valid, values) => {
if (valid) {
component.setProperties({
height: values[PARAMETER.HEIGHT],
width: values[PARAMETER.WIDTH],
depth: values[PARAMETER.DEPTH],
})

component.updateExistingModules() // extra logic to add the mentioned "rule"
}
})

return configurator
}

where component.updateExistingModules() could do something like:

// COMPONENT

export class Component extends STUDIO.BaseComponent<CONSTANTS.Properties> {
constructor() {
// ...
}

updateExistingModules() {
const cabinetWidth = this.getProperty('width')
const moduleInstances = this.componentHandler.getInstances(MODULE.Component)
moduleInstances.forEach((instance) => {
const moduleComponent = instance.getComponent()
const moduleWidth = moduleComponent.getProperty('width')
if (moduleWidth > cabinetWidth) {
moduleComponent.setProperties({ width: cabinetWidth })
}
})
}

generateGeometry() {
// ...
}

// more possible functions
}

So again there is no configurator rule but extra logic in the configurator callback that calls a class function in the component.

Configurators in same tab

There is a special case when the configurators are in the same tab. As we said, when we switch between tabs in the app, the buttons and configurators are created again, so the rules will be triggered automatically. Here for example, we add callbacks for both configurators to update the camera in the first case and the geometry in both. And yes, the configurator.addCompletionCallback() in the component that sets the properties accordingly is also triggered, so they are not exclusive and multiple callbacks can coexist.

// App / UI

const cabinetTab = new SKYUI.Tab({
title: 'Cabinet',
icon: 'modal-window',
onInit: () => {
const configurator1 = ACTIONS.generateConfigurator1()
configurator1.addCompletionCallback((valid, values) => {
if (valid) {
Studio.setCameraToFitBounds({ bounds: ACTIONS.getCabinetBounds(), travelTime: 500 })
Studio.requestGeometryUpdate()
}
})

const configurator2 = ACTIONS.generateConfigurator2()
configurator2.addCompletionCallback((valid, values) => {
if (valid) {
Studio.requestGeometryUpdate()
}
})

Studio.setTabContent([configurator1, configurator2])

Studio.requestGeometryUpdate()
Studio.requestLightweightGeometryUpdate()
},
})

But to add even more complexity, let's say that the height limits the min rail spacing, or in other words, parameters that affect each other that belong to different configurators that are in the same tab. In this case, configurators are already created and we can't take advantage of the behavior of regenerating configurators when switching tabs, because again both configurators are in the same tab.

In this case, we still don't need a configurator rule, so in order to force a regeneration of the configurators, there's no other way than regenerating the tab, as if we switched the tab. And that can be done through Studio.reloadActiveTab(). So the tab would look like this:

const cabinetTab = new SKYUI.Tab({
title: 'Cabinet',
icon: 'modal-window',
onInit: () => {
const configurator1 = ACTIONS.generateConfigurator1()
configurator1.addCompletionCallback((valid, values) => {
if (valid) {
// camera and geometry update
Studio.reloadActiveTab()
}
})

const configurator2 = ACTIONS.generateConfigurator2()
// configurator2 callback

// rest of functions
},
})

Now with Studio.reloadActiveTab() in the configurator callback, we make sure that both configurators are created again, right after setting the properties of configurator1, and therefore the parameters of configurator2 (including their rules) will be created again.

Although these are the most common examples of rules, you might run into an even more complex one. Reach us at support@dynamaker.com if you need help with a specific behavior between configurators that is not explained here!