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-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 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: