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!
Dropdown
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
Dropdown With Input
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:
- In tab Cabinet, for the three first parameters
Width
,Height
andDepth
. - In tab Cabinet, for the two last parameters
Min rail spacing
andCabinet color
. - In tab Modules, when adding a module through the flyout
Add module
. - 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:
- To update the camera when updating the size.
- Not to update the camera when updating the rail spacing or color.
- Not to limit any module size parameter when creating a new one.
- 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-editor (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 givestrue
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 objectPARAMETER
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 functioncomponent.update()
, which is empty in the default template. However, additional functions can be called withinconfigurator.addCompletionCallback()
by simply adding them aftercomponent.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 typingexport
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 fromPARAMETERS
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 functiongenerateConfigurator()
, 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:
- 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.
- 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 outsidePARAMETERS.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 asparameter.getValue()
, is the current value of the parameterHeight
.directUpdate
becomesfalse
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 asparameter.getValue()
, is the current value of the parameterDepth
.directUpdate
becomesfalse
if any of the parameters above, i.e.Width
orHeight
, 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
andHeight
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-editor 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!