Configurators
In this tutorial, you will learn the basics of one of the most powerful features in DynaMaker, i.e. Configurators, together with texturing. As a product example, we will use a three-point flexural test with a profile configurable in cross-section.

For this we will follow 3 steps:
1. Prepare Components
For this app we will create 3 components:
- a) BEAM
- b) LOADTESTER
- c) ASSEMBLY
A. Beam
It will have 2 different configurable profiles: I & O.
A1. Sketches
In GEOM2D create 2 functions for each profile with the inputs defined:
- Use the snippet Sketch at the top to go with a faster setup.
- For the O-profile add the boolean input
hollow.

Doublecheck your solution here:
export function getProfileISketch(
profileWidth: number,
profileHeight: number,
webThickness: number,
flangeThickness: number,
) {
const sketch = new SKYCAD.Sketch()
sketch.moveTo(0, 0)
sketch.lineTo(profileWidth, 0)
sketch.lineTo(profileWidth, flangeThickness)
sketch.lineTo((profileWidth + webThickness) / 2, flangeThickness)
sketch.lineTo((profileWidth + webThickness) / 2, profileHeight - flangeThickness)
sketch.lineTo(profileWidth, profileHeight - flangeThickness)
sketch.lineTo(profileWidth, profileHeight)
sketch.lineTo(0, profileHeight)
sketch.lineTo(0, profileHeight - flangeThickness)
sketch.lineTo((profileWidth - webThickness) / 2, profileHeight - flangeThickness)
sketch.lineTo((profileWidth - webThickness) / 2, flangeThickness)
sketch.lineTo(0, flangeThickness)
sketch.lineToId(0) // don't forget to close the sketch to create a closed contour
sketch.translate(-profileWidth / 2, 0)
return sketch
}
export function getProfileOSketch(profileWidth: number, profileHeight: number, hollow: boolean, pipeThickness: number) {
const sketch = SKYCAD.generateEllipseSketch(profileWidth / 2, profileHeight / 2)
if (hollow) {
const holeSketch = SKYCAD.generateEllipseSketch(profileWidth / 2 - pipeThickness, profileHeight / 2 - pipeThickness)
sketch.mergeSketch(holeSketch)
}
sketch.translate(0, profileHeight / 2)
return sketch
}
A2. Model
Let's do the same for the models:
- use the snippet Model/Extrude from these sketches at the top for a faster setup.
- make sure to have both extrusions in X-direction (
const plane = new SKYCAD.Plane(1, 0, 0)).
Doublecheck your solution here:
export function generateIModel(
profileWidth: number,
profileHeight: number,
webThickness: number,
flangeThickness: number,
extrudeLength: number,
) {
const model = new SKYCAD.ParametricModel()
const sketch = GEOM2D.getProfileISketch(profileWidth, profileHeight, webThickness, flangeThickness)
const plane = new SKYCAD.Plane(1, 0, 0)
model.addExtrude(sketch, plane, extrudeLength)
return model
}
export function generateOModel(
profileWidth: number,
profileHeight: number,
hollow: boolean,
pipeThickness: number,
extrudeLength: number,
) {
const model = new SKYCAD.ParametricModel()
const sketch = GEOM2D.getProfileOSketch(profileWidth, profileHeight, hollow, pipeThickness)
const plane = new SKYCAD.Plane(1, 0, 0)
model.addExtrude(sketch, plane, extrudeLength)
return model
}
A3. Component
Finally let's create a single component with the corresponding properties for both profiles.
- use the snippet Component/Part from the e.g. I-model at the top for a faster setup.
- include properties of the other profile (both under the definition
ComponentPropertiesand inthis.properties = {}).
export interface ComponentProperties {
profileWidth: number
profileHeight: number
extrudeLength: number
webThickness: number
flangeThickness: number
hollow: boolean
pipeThickness: number
}
export class Component extends STUDIO.BaseComponent<ComponentProperties> {
constructor() {
super()
this.properties = {
profileWidth: 300,
profileHeight: 500,
extrudeLength: 1000,
// Properties of I-profile
webThickness: 150,
flangeThickness: 30,
// Properties of O-profile
hollow: true,
pipeThickness: 20,
}
}
generateGeometry() {
const { profileWidth, profileHeight, webThickness, flangeThickness, extrudeLength } = this.properties
const geometryGroup = new SKYCAD.GeometryGroup()
const model = GEOM3D.generateIModel(profileWidth, profileHeight, webThickness, flangeThickness, extrudeLength)
geometryGroup.addGeometry(model, {})
return geometryGroup
}
}
The editor should look something like this so far:

A4. Switching Profiles
As final step, the component needs to know which profile model to use. This can be controlled via a property
profileType that drives the geometry. For that:
- create a new property
profileTypethat can take the string values'type-i'or'type-o':
export interface ComponentProperties {
profileType: 'type-i' | 'type-o' // <-- forces to be either value but nothing different
// ...rest of properties
}
export class Component extends STUDIO.BaseComponent<ComponentProperties> {
constructor() {
super()
this.properties = {
profileType: 'type-i', // default value
// ...rest of properties
}
}
generateGeometry() {
// ...logic for generating geometry
}
}
-
adjust
generateGeometry()so that it uses the correct GEOM3D-function according to this new property (a simpleif-statement should do the trick):export interface ComponentProperties {
// ...property definitions
}
export class Component extends STUDIO.BaseComponent<ComponentProperties> {
constructor() {
super()
this.properties = {
// ...properties
}
}
generateGeometry() {
const {
profileType,
profileWidth,
profileHeight,
webThickness,
flangeThickness,
extrudeLength,
hollow,
pipeThickness,
} = this.properties
const geometryGroup = new SKYCAD.GeometryGroup()
if (profileType === 'type-i') {
const iModel = GEOM3D.generateIModel(profileWidth, profileHeight, webThickness, flangeThickness, extrudeLength)
geometryGroup.addGeometry(iModel, {})
} else if (profileType === 'type-o') {
const oModel = GEOM3D.generateOModel(profileWidth, profileHeight, hollow, pipeThickness, extrudeLength)
geometryGroup.addGeometry(oModel, {})
}
return geometryGroup
}
}
As quick test, you change the default value of profileType from 'type-i' to 'type-o'. However, it's best practices
to create component tests to properly capture different behaviors and not altering the default component.
B. Load Tester
As for the three-point load tester, we will simplify the machine by creating a model that consists of 2 supports or fixtures on top of a base with the load cell where the force is applied from in the middle. However we know that these parts are always static - because we are configuring the beam and not the machine after all! - so let's reuse the models created in another software. So let's create a new component LOADTESTER.
B1. Models
Let's start with the geometry that will consist of static 3D files:
- Download these 3 GLB files:
- In the dashboard, via Upload File, upload all at once with Automatically create static models from GLB files and Scale = 1000.

- Inside your new component LOADTESTER, at the top of the left sidebar click IMPORTS and check ASSETS.

-
In GEOM3D create a function that generates a geometry group that puts all these models together:
- use
geometryGroup.addGeometry(ASSETS.STATIC_MODELS.MY_MODEL, { position, rotation })to add the models accordingly. - the base GLB model doesn't need extra adjustments in position or rotation.
- the cell model position-z should depend on
profileHeight, so that it's tangent to the top of the beam. - the 2 fixtures position-x should be at 1/4 and 3/4 of the
extrudeLengthof the beam. - you end up having something like:
export function getLoadTesterGeometry(profileHeight: number, extrudeLength: number) {
const geometryGroup = new SKYCAD.GeometryGroup()
geometryGroup.addGeometry(ASSETS.STATIC_MODELS.LOAD_BASE)
geometryGroup.addGeometry(ASSETS.STATIC_MODELS.LOAD_CELL, {
position: new SKYMATH.Vector3D(0, 0, profileHeight),
})
const fixtureModel = ASSETS.STATIC_MODELS.LOAD_FIXTURE
geometryGroup.addGeometry(fixtureModel, {
position: new SKYMATH.Vector3D(0.25 * extrudeLength, 0, 0),
})
geometryGroup.addGeometry(fixtureModel, {
position: new SKYMATH.Vector3D(-0.25 * extrudeLength, 0, 0),
rotation: new SKYMATH.Vector3D(0, 0, Math.PI),
})
return geometryGroup
} - use

Notice that all GLB files have been created with a convenient origin for this application, which is typically not the usual case!
So if you ever see a model behaving weirdly (e.g. not showing, very small, wrongly placed, etc), it's best to create a GEOM3D function exclusively for each model, so that you can isolate it from the rest of the models and identify the problem more easily.
B2. Component
- Create the component via the snippet Component/Part at the top from this GEOM3D function:

C. Assembly
Time to assemble! The ASSEMBLY component will contain the properties of both subcomponents BEAM and LOADTESTER, so we need to make sure that they are passed correctly.
C1. Import Subcomponents
- In your new component ASSEMBLY, import BEAM and LOADTESTER via IMPORTS at the top of the left sidebar.

C2. Component
- Create a component via snippet Component/Part for a faster setup.
- Copy the properties (and their definition) from the BEAM component to be the properties of the ASSEMBLY.
- Add logic needed in
update()to update subcomponents from the assembly properties
Review Tutorial 6. Assemblies on how update() should look like.
Doublecheck your solution so far (i.e. assembly component with static subcomponents):
export interface ComponentProperties {
profileType: 'type-i' | 'type-o'
profileWidth: number
profileHeight: number
extrudeLength: number
webThickness: number
flangeThickness: number
hollow: boolean
pipeThickness: number
}
export class Component extends STUDIO.BaseComponent<ComponentProperties> {
constructor() {
super()
this.properties = {
profileType: 'type-i',
profileWidth: 50,
profileHeight: 80,
extrudeLength: 400,
// Properties of I-profile
webThickness: 30,
flangeThickness: 5,
// Properties of O-profile
hollow: true,
pipeThickness: 20,
}
this.update() // <-- don't forget to add this!
}
update() {
const {
profileType,
profileWidth,
profileHeight,
extrudeLength,
webThickness,
flangeThickness,
hollow,
pipeThickness,
} = this.getProperties()
this.componentHandler.clearAll()
const beamComponent = new BEAM.Component()
beamComponent.setProperties({
profileType,
profileWidth,
profileHeight,
extrudeLength,
webThickness,
flangeThickness,
hollow,
pipeThickness,
})
this.componentHandler.add(beamComponent, {
position: new SKYMATH.Vector3D(-0.5 * extrudeLength, 0, 0),
})
const loadTesterComponent = new LOADTESTER.Component()
loadTesterComponent.setProperties({
extrudeLength,
profileHeight,
})
this.componentHandler.add(loadTesterComponent)
}
generateGeometry() {
return this.componentHandler.generateAllGeometry()
}
}

2. Configurator
A configurator acts as a bridge between the user and your assembly component, linking its parameters to the component's properties. Each property should represent a specific part that can be configured, so that the configurator shapes the modeling and viceversa. For a configurator to work we need:
- a) Parameters
- b) Callback
- c) Rules
A. Parameters
In this case, we have 8 properties so we need a configurator with 8 parameters:
- Create a configurator via snippet Configurator/Configurator from the component at the top for a faster setup.

See that this snippet automatically creates a configurator from the properties that are of the type string, number
and boolean. Other types are not considered and therefore skipped like profileType which has a custom type in this
case.
Let's create a dropdown parameter for profileType:
- similarly to
SKYPARAM.generateSlider, create aSKYPARAM.generateDropdown - notice that this parameter type needs a 3rd input with all the options as a list.
- an option can be written as
new SKYPARAM.DropdownItem('Type I', 'type-i'), with label and value as inputs. - something like the following should do the trick
const profileTypeParameter = SKYPARAM.generateDropdown(
'profileType', // id (same as property name)
'Profile Type', // label
[
new SKYPARAM.DropdownItem('Type I', 'type-i'), // option 1
new SKYPARAM.DropdownItem('Type O', 'type-o') // option 2
],
{ defaultValue: component.getProperty('profileType') },
)
- finally add it to the list of
parametersso it's added to theconfigurator.
Doublecheck your solution so far (i.e. configurator with 8 parameters):
export function generateConfigurator(component: COMPONENTS.Component): SKYPARAM.Configurator {
const profileTypeParameter = SKYPARAM.generateDropdown(
'profileType',
'Profile Type',
[new SKYPARAM.DropdownItem('Type I', 'type-i'), new SKYPARAM.DropdownItem('Type O', 'type-o')],
{ defaultValue: component.getProperty('profileType') },
)
const profileWidthParameter = SKYPARAM.generateSlider('profileWidth', 'Profile width', {
defaultValue: component.getProperty('profileWidth'),
minValue: 10,
maxValue: 250,
})
const profileHeightParameter = SKYPARAM.generateSlider('profileHeight', 'Profile height', {
defaultValue: component.getProperty('profileHeight'),
minValue: 16,
maxValue: 400,
})
const extrudeLengthParameter = SKYPARAM.generateSlider('extrudeLength', 'Extrude length', {
defaultValue: component.getProperty('extrudeLength'),
minValue: 80,
maxValue: 2000,
})
const webThicknessParameter = SKYPARAM.generateSlider('webThickness', 'Web thickness', {
defaultValue: component.getProperty('webThickness'),
minValue: 6,
maxValue: 150,
})
const flangeThicknessParameter = SKYPARAM.generateSlider('flangeThickness', 'Flange thickness', {
defaultValue: component.getProperty('flangeThickness'),
minValue: 1,
maxValue: 25,
})
const hollowParameter = SKYPARAM.generateCheckbox('hollow', 'Hollow', {
defaultValue: component.getProperty('hollow'),
})
const pipeThicknessParameter = SKYPARAM.generateSlider('pipeThickness', 'Pipe thickness', {
defaultValue: component.getProperty('pipeThickness'),
minValue: 4,
maxValue: 100,
})
const parameters = [
profileTypeParameter, // <-- add it to the configurator here
profileWidthParameter,
profileHeightParameter,
extrudeLengthParameter,
webThicknessParameter,
flangeThicknessParameter,
hollowParameter,
pipeThicknessParameter,
]
const configurator = new SKYPARAM.Configurator(parameters)
configurator.addCompletionCallback((valid, values) => {
if (valid) {
component.setProperties(values)
}
})
return configurator
}
- the new dropdown parameter should now show up at the top and already connected to the geometry:

B. Callback
As you have probably noticed, the result of the parameters and the actual properties are connected via the callback of
the configurator, configurator.addCompletionCallback(), i.e. a function that is triggered for every change in the
configurator.
export function generateConfigurator(component: COMPONENTS.Component): SKYPARAM.Configurator {
// ...parameters
const configurator = new SKYPARAM.Configurator(parameters)
configurator.addCompletionCallback((valid, values) => {
if (valid) {
component.setProperties(values)
}
})
return configurator
}
As seen in the code above, this callback gives you 2 inputs to use:
-
validas boolean to detect if a parameter has wrong input (e.g.valid: falsewhen the user types something different from anumberor goes out of min/max-range in aSKYPARAM.generateInputParameter). -
valuesas an object with the values of all the parameters with the IDs of the parameters, like:values = {
profileType: 'type-i',
profileWidth: 50,
profileHeight: 80,
extrudeLength: 400,
webThickness: 30,
flangeThickness: 5,
hollow: true,
pipeThickness: 20,
}
Notice that in this snippet, the parameter IDs match the property names so that component.setProperties(values) can
be done directly. However as soon as a property name or parameter ID are not synced, this will not be handled there.
Therefore to avoid the typical misspelling at some point, it's best to separate these like the following:
Notice the differences in this alternative configurator:
const ID = {
PROFILE_TYPE: 'profile-type',
PROFILE_WIDTH: 'profile-width',
PROFILE_HEIGHT: 'profile-height',
EXTRUDE_LENGTH: 'extrude-length',
WEB_THICKNESS: 'web-thickness',
FLANGE_THICKNESS: 'flange-thickness',
HOLLOW: 'hollow',
PIPE_THICKNESS: 'pipe-thickness',
}
export function generateConfigurator2(component: COMPONENTS.Component): SKYPARAM.Configurator {
const profileTypeParameter = SKYPARAM.generateDropdown(
ID.PROFILE_TYPE,
'Profile Type',
[new SKYPARAM.DropdownItem('Type I', 'type-i'), new SKYPARAM.DropdownItem('Type O', 'type-o')],
{ defaultValue: component.getProperty('profileType') },
)
const profileWidthParameter = SKYPARAM.generateSlider(ID.PROFILE_WIDTH, 'Profile width', {
defaultValue: component.getProperty('profileWidth'),
minValue: 10,
maxValue: 250,
})
const profileHeightParameter = SKYPARAM.generateSlider(ID.PROFILE_HEIGHT, 'Profile height', {
defaultValue: component.getProperty('profileHeight'),
minValue: 16,
maxValue: 400,
})
const extrudeLengthParameter = SKYPARAM.generateSlider(ID.EXTRUDE_LENGTH, 'Extrude length', {
defaultValue: component.getProperty('extrudeLength'),
minValue: 80,
maxValue: 2000,
})
const webThicknessParameter = SKYPARAM.generateSlider(ID.WEB_THICKNESS, 'Web thickness', {
defaultValue: component.getProperty('webThickness'),
minValue: 6,
maxValue: 150,
})
const flangeThicknessParameter = SKYPARAM.generateSlider(ID.FLANGE_THICKNESS, 'Flange thickness', {
defaultValue: component.getProperty('flangeThickness'),
minValue: 1,
maxValue: 25,
})
const hollowParameter = SKYPARAM.generateCheckbox(ID.HOLLOW, 'Hollow', {
defaultValue: component.getProperty('hollow'),
})
const pipeThicknessParameter = SKYPARAM.generateSlider(ID.PIPE_THICKNESS, 'Pipe thickness', {
defaultValue: component.getProperty('pipeThickness'),
minValue: 4,
maxValue: 100,
})
const parameters = [
profileTypeParameter,
profileWidthParameter,
profileHeightParameter,
extrudeLengthParameter,
webThicknessParameter,
flangeThicknessParameter,
hollowParameter,
pipeThicknessParameter,
]
const configurator = new SKYPARAM.Configurator(parameters)
configurator.addCompletionCallback((valid, values) => {
console.log(values)
if (valid) {
component.setProperties({
profileType: values[ID.PROFILE_TYPE],
profileWidth: values[ID.PROFILE_WIDTH],
profileHeight: values[ID.PROFILE_HEIGHT],
extrudeLength: values[ID.EXTRUDE_LENGTH],
webThickness: values[ID.WEB_THICKNESS],
flangeThickness: values[ID.FLANGE_THICKNESS],
hollow: values[ID.HOLLOW],
pipeThickness: values[ID.PIPE_THICKNESS],
})
}
})
return configurator
}
C. Rules
Now that you have all your parameters in the configurator, they need to interact with each other in some way. A min/max value or toggling the visibily of a parameter depending on another's is a very typical rule to implement.
In this case we will implement a rule that for changing:
- C1) Visibility
- C2) Max-min values
C1. Visiblity
To set up a rule in a configurator setUpdateRule() is needed for each parameter you want a rule for. It gives you 4
inputs in this order (see together with example for webThickness below):
valueas the current value of the parameter you set the rule for.directUpdateas boolean:falsewhen some other parameter is being updated (common case), ortruewhen is being updated.parameteras the current parameter that allows you e.g. change its max valueparameter.setMax(maxValue), its visibiltyparameter.setVisible(false), or even override its current value withparameter.setValue(132).valueswith only the values of the parameters that are exclusively above this one.
configurator.setUpdateRule('webThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(true)
break
case 'type-o':
parameter.setVisible(false)
break
}
}
})
Implement simple similar rules for togglilng the visibility for flangeThickness, hollow and pipeThickness.
Doublecheck your solution so far (configurator with visibilty rules):
export function generateConfigurator(component: COMPONENTS.Component): SKYPARAM.Configurator {
const profileTypeParameter = SKYPARAM.generateDropdown(
'profileType',
'Profile Type',
[new SKYPARAM.DropdownItem('Type I', 'type-i'), new SKYPARAM.DropdownItem('Type O', 'type-o')],
{ defaultValue: component.getProperty('profileType') },
)
const profileWidthParameter = SKYPARAM.generateSlider('profileWidth', 'Profile width', {
defaultValue: component.getProperty('profileWidth'),
minValue: 10,
maxValue: 250,
})
const profileHeightParameter = SKYPARAM.generateSlider('profileHeight', 'Profile height', {
defaultValue: component.getProperty('profileHeight'),
minValue: 16,
maxValue: 400,
})
const extrudeLengthParameter = SKYPARAM.generateSlider('extrudeLength', 'Extrude length', {
defaultValue: component.getProperty('extrudeLength'),
minValue: 80,
maxValue: 2000,
})
const webThicknessParameter = SKYPARAM.generateSlider('webThickness', 'Web thickness', {
defaultValue: component.getProperty('webThickness'),
minValue: 6,
maxValue: 150,
})
const flangeThicknessParameter = SKYPARAM.generateSlider('flangeThickness', 'Flange thickness', {
defaultValue: component.getProperty('flangeThickness'),
minValue: 1,
maxValue: 25,
})
const hollowParameter = SKYPARAM.generateCheckbox('hollow', 'Hollow', {
defaultValue: component.getProperty('hollow'),
})
const pipeThicknessParameter = SKYPARAM.generateSlider('pipeThickness', 'Pipe thickness', {
defaultValue: component.getProperty('pipeThickness'),
minValue: 4,
maxValue: 100,
})
const parameters = [
profileTypeParameter,
profileWidthParameter,
profileHeightParameter,
extrudeLengthParameter,
webThicknessParameter,
flangeThicknessParameter,
hollowParameter,
pipeThicknessParameter,
]
const configurator = new SKYPARAM.Configurator(parameters)
configurator.setUpdateRule('webThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(true)
break
case 'type-o':
parameter.setVisible(false)
break
}
}
})
configurator.setUpdateRule('flangeThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(true)
break
case 'type-o':
parameter.setVisible(false)
break
}
}
})
configurator.setUpdateRule('hollow', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(false)
break
case 'type-o':
parameter.setVisible(true)
break
}
}
})
configurator.setUpdateRule('pipeThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(false)
break
case 'type-o':
const newHollow = values.hollow
parameter.setVisible(newHollow)
break
}
}
})
configurator.addCompletionCallback((valid, values) => {
console.log(values)
if (valid) {
component.setProperties(values)
}
})
return configurator
}
C2. Max-Min Values
Let's refine the max-min values of certain parameters. For example, it makes no sense to have a web wider than the actual profile width and so on for its flanges. A typical rule to update a parameter max value looks like this:
configurator.setUpdateRule('webThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
// ... other rules
const newMaxValue = values.profileWidth
parameter.setMax(newMaxValue)
if (value > newMaxValue) parameter.setValue(newMaxValue) // (optional) update value if current value goes over newMaxValue
}
})
Implement this rule inside their own setUpdateRule() for:
webThicknessbased onprofileWidth(as the example above).flangeThicknessbased onprofileHeight.pipeThicknessbased onprofileWidthandprofileHeight.
Make sure to only update its max value when parameter.getVisible() === true or when the correct profileType is used.
Doublecheck your solution so far (configurator with visibilty and max-min rules):
export function generateConfigurator(component: COMPONENTS.Component): SKYPARAM.Configurator {
const profileTypeParameter = SKYPARAM.generateDropdown(
'profileType',
'Profile Type',
[new SKYPARAM.DropdownItem('Type I', 'type-i'), new SKYPARAM.DropdownItem('Type O', 'type-o')],
{ defaultValue: component.getProperty('profileType') },
)
const profileWidthParameter = SKYPARAM.generateSlider('profileWidth', 'Profile width', {
defaultValue: component.getProperty('profileWidth'),
minValue: 10,
maxValue: 250,
})
const profileHeightParameter = SKYPARAM.generateSlider('profileHeight', 'Profile height', {
defaultValue: component.getProperty('profileHeight'),
minValue: 16,
maxValue: 400,
})
const extrudeLengthParameter = SKYPARAM.generateSlider('extrudeLength', 'Extrude length', {
defaultValue: component.getProperty('extrudeLength'),
minValue: 80,
maxValue: 2000,
})
const webThicknessParameter = SKYPARAM.generateSlider('webThickness', 'Web thickness', {
defaultValue: component.getProperty('webThickness'),
minValue: 6,
maxValue: 150,
})
const flangeThicknessParameter = SKYPARAM.generateSlider('flangeThickness', 'Flange thickness', {
defaultValue: component.getProperty('flangeThickness'),
minValue: 1,
maxValue: 25,
})
const hollowParameter = SKYPARAM.generateCheckbox('hollow', 'Hollow', {
defaultValue: component.getProperty('hollow'),
})
const pipeThicknessParameter = SKYPARAM.generateSlider('pipeThickness', 'Pipe thickness', {
defaultValue: component.getProperty('pipeThickness'),
minValue: 4,
maxValue: 100,
})
const parameters = [
profileTypeParameter,
profileWidthParameter,
profileHeightParameter,
extrudeLengthParameter,
webThicknessParameter,
flangeThicknessParameter,
hollowParameter,
pipeThicknessParameter,
]
const configurator = new SKYPARAM.Configurator(parameters)
configurator.setUpdateRule('webThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
// visibility rule
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(true)
break
case 'type-o':
parameter.setVisible(false)
break
}
// max rule
if (parameter.getVisible() === true) {
const newMaxValue = values.profileWidth
parameter.setMax(newMaxValue)
if (value > newMaxValue) parameter.setValue(newMaxValue)
}
}
})
configurator.setUpdateRule('flangeThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
// visibillity rule
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(true)
break
case 'type-o':
parameter.setVisible(false)
break
}
// max rule
if (parameter.getVisible() === true) {
const maxValue = Math.floor(0.5 * values.profileHeight) - 1 // rounded with Math.floor to avoid decimals
parameter.setMax(maxValue)
if (value > maxValue) parameter.setValue(maxValue)
}
}
})
configurator.setUpdateRule('hollow', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
// visiblity rule
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(false)
break
case 'type-o':
parameter.setVisible(true)
break
}
}
})
configurator.setUpdateRule('pipeThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
// visibility rule
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(false)
break
case 'type-o':
const newHollow = values.hollow
parameter.setVisible(newHollow)
break
}
// max rule
if (parameter.getVisible() === true) {
const maxValue = Math.floor(0.5 * Math.min(values.profileWidth, values.profileHeight)) - 1 // rounded with Math.floor to avoid decimals
parameter.setMax(maxValue)
if (value > maxValue) parameter.setValue(maxValue)
}
}
})
configurator.addCompletionCallback((valid, values) => {
console.log(values)
if (valid) {
component.setProperties(values)
}
})
return configurator
}
3. Materials
As final step we will:
A. Parametric Models
As similarly done in Tutorial 4. Static Assets, we will update the appearance of the beam by applying a texture as an asset. For that:
- Download color and normal maps needed for a texture: METAL_COLOR.png
& METAL_NORMAL.png.

- In the app dashboard, click Upload File and upload both without creating an Image asset.
- Click Create Asset/Texture, with name e.g. METAL, and upload both without creating an Image asset.

-
Go into the BEAM component.
-
At the top of the left sidebar, via IMPORTS, import ASSETS.
-
In COMPONENT, in
generateGeometry()creatematerialsas:const materials = [
new SKYCAD.Material({
textureId: ASSETS.TEXTURES.METAL,
color: 0xadd8e6, // light blue
textureRotation: Math.PI / 2, // so the dents of the texture become horizontal
textureHeight: 500, // sets smaller scale (1024 is default for this texture)
metalness: 1,
roughness: 0,
}),
] -
And add it to the corresponding I- and O-models respectively:
geometryGroup.addGeometry(iModel, { materials })
geometryGroup.addGeometry(oModel, { materials })

Notice the difference it makes to use a texture with and without normal map, since the purpose of a normal map defines
how the lighting should interact with the texture, faking a certain degree of 3D. See that metalness and roughness
play also a huge role!

You can read more about textures in this how-to example, where textures can be applied to specified parts of the model or even make them transparent to fake grids and meshes.
B. Static Models
In constrast with parametric models, static ones are by definition static and cannot change. Static models usually have complex geometries and is best that these come with textures built in the model (supported in GLB format) as it has been done with the load tester. These GLB files come with materials that can be overwritten.
As example the GLB files of this tutorial have been intentionally prepared for this, and we will change the color of the SkyMaker logo built in the GLB file. For this:
-
go back into the component LOADTESTER.
-
go to
GEOM3D.getLoadTesterGeometry. -
create new
materials(e.g. same as the beam's blue brushed metal) for the logo of the load-base model, overwritting the color oflogo_material(name given inLOAD_BASE.glb) like:export function getLoadTesterGeometry(profileHeight: number, extrudeLength: number) {
const geometryGroup = new SKYCAD.GeometryGroup()
const materials = [
new SKYCAD.Material({
name: 'logo_material',
textureId: ASSETS.TEXTURES.METAL,
color: 0xadd8e6,
}),
]
geometryGroup.addGeometry(ASSETS.STATIC_MODELS.LOAD_BASE, {
materials,
})
// ... load cell model
// ... fixture models
return geometryGroup
}

Overwriting a material of a GLB file is specially useful when e.g. you want to keep the texture (e.g. horizontal planks) but change only the color. Think of it as if you are adding a layer of color on top of the current texture.
Doublecheck your solution:
export interface ComponentProperties {
profileType: 'type-i' | 'type-o'
profileWidth: number
profileHeight: number
webThickness: number
flangeThickness: number
hollow: boolean
pipeThickness: number
extrudeLength: number
}
export class Component extends STUDIO.BaseComponent<ComponentProperties> {
constructor() {
super()
this.properties = {
profileType: 'type-i',
profileWidth: 300,
profileHeight: 200,
extrudeLength: 1000,
// Properties of I-profile
webThickness: 150,
flangeThickness: 30,
// Properties of O-profile
hollow: true,
pipeThickness: 20,
}
} generateGeometry() { const { profileType, profileWidth, profileHeight, webThickness, flangeThickness, extrudeLength,
hollow, pipeThickness, } = this.properties const geometryGroup = new SKYCAD.GeometryGroup()
const materials = [
new SKYCAD.Material({
textureId: ASSETS.TEXTURES.METAL,
color: 0xadd8e6, // light blue
textureRotation: Math.PI / 2,
textureHeight: 500,
metalness: 1,
roughness: 0,
}),
]
if (profileType === 'type-i') {
const iModel = GEOM3D.generateIModel(profileWidth, profileHeight, webThickness, flangeThickness, extrudeLength)
geometryGroup.addGeometry(iModel, { materials })
} else if (profileType === 'type-o') {
const oModel = GEOM3D.generateOModel(profileWidth, profileHeight, hollow, pipeThickness, extrudeLength)
geometryGroup.addGeometry(oModel, { materials })
}
return geometryGroup
} }
export function generateIModel(
profileWidth: number,
profileHeight: number,
webThickness: number,
flangeThickness: number,
extrudeLength: number,
) {
const model = new SKYCAD.ParametricModel()
const sketch = GEOM2D.getProfileISketch(profileWidth, profileHeight, webThickness, flangeThickness)
const plane = new SKYCAD.Plane(1, 0, 0)
model.addExtrude(sketch, plane, extrudeLength)
return model
}
export function generateOModel(
profileWidth: number,
profileHeight: number,
hollow: boolean,
pipeThickness: number,
extrudeLength: number,
) {
const model = new SKYCAD.ParametricModel()
const sketch = GEOM2D.getProfileOSketch(profileWidth, profileHeight, hollow, pipeThickness)
const plane = new SKYCAD.Plane(1, 0, 0)
model.addExtrude(sketch, plane, extrudeLength)
return model
}
export function getProfileISketch(
profileWidth: number,
profileHeight: number,
webThickness: number,
flangeThickness: number,
) {
const sketch = new SKYCAD.Sketch()
sketch.moveTo(0, 0)
sketch.lineTo(profileWidth, 0)
sketch.lineTo(profileWidth, flangeThickness)
sketch.lineTo((profileWidth + webThickness) / 2, flangeThickness)
sketch.lineTo((profileWidth + webThickness) / 2, profileHeight - flangeThickness)
sketch.lineTo(profileWidth, profileHeight - flangeThickness)
sketch.lineTo(profileWidth, profileHeight)
sketch.lineTo(0, profileHeight)
sketch.lineTo(0, profileHeight - flangeThickness)
sketch.lineTo((profileWidth - webThickness) / 2, profileHeight - flangeThickness)
sketch.lineTo((profileWidth - webThickness) / 2, flangeThickness)
sketch.lineTo(0, flangeThickness)
sketch.lineToId(0) // don't forget to close the sketch to create a closed contour
sketch.translate(-profileWidth / 2, 0)
return sketch
}
export function getProfileOSketch(profileWidth: number, profileHeight: number, hollow: boolean, pipeThickness: number) {
const sketch = SKYCAD.generateEllipseSketch(profileWidth / 2, profileHeight / 2)
if (hollow) {
const holeSketch = SKYCAD.generateEllipseSketch(profileWidth / 2 - pipeThickness, profileHeight / 2 - pipeThickness)
sketch.mergeSketch(holeSketch)
}
sketch.translate(0, profileHeight / 2)
return sketch
}
export default function (): BEAM.Component {
const component = new BEAM.Component()
component.setProperties({
profileType: 'type-i',
})
return component
}
export default function (): BEAM.Component {
const component = new BEAM.Component()
component.setProperties({
profileType: 'type-o',
})
return component
}
export interface ComponentProperties {
profileHeight: number
extrudeLength: number
}
export class Component extends STUDIO.BaseComponent<ComponentProperties> {
constructor() {
super()
this.properties = {
profileHeight: 100,
extrudeLength: 100,
}
}
generateGeometry() {
const { profileHeight, extrudeLength } = this.properties
const geometryGroup = new SKYCAD.GeometryGroup()
const model = GEOM3D.getLoadTesterGeometry(profileHeight, extrudeLength)
geometryGroup.addGeometry(model)
return geometryGroup
}
}
export function getLoadTesterGeometry(profileHeight: number, extrudeLength: number) {
const geometryGroup = new SKYCAD.GeometryGroup()
const materials = [
new SKYCAD.Material({
name: 'logo_material',
textureId: ASSETS.TEXTURES.METAL,
color: 0xadd8e6,
}),
]
geometryGroup.addGeometry(ASSETS.STATIC_MODELS.LOAD_BASE, {
materials,
})
geometryGroup.addGeometry(ASSETS.STATIC_MODELS.LOAD_CELL, {
position: new SKYMATH.Vector3D(0, 0, profileHeight),
})
const fixtureModel = ASSETS.STATIC_MODELS.LOAD_FIXTURE
geometryGroup.addGeometry(fixtureModel, {
position: new SKYMATH.Vector3D(0.25 * extrudeLength, 0, 0),
})
geometryGroup.addGeometry(fixtureModel, {
position: new SKYMATH.Vector3D(-0.25 * extrudeLength, 0, 0),
rotation: new SKYMATH.Vector3D(0, 0, Math.PI),
})
return geometryGroup
}
export interface ComponentProperties {
profileType: 'type-i' | 'type-o'
profileWidth: number
profileHeight: number
extrudeLength: number
webThickness: number
flangeThickness: number
hollow: boolean
pipeThickness: number
}
export class Component extends STUDIO.BaseComponent<ComponentProperties> {
constructor() {
super()
this.properties = {
profileType: 'type-i',
profileWidth: 50,
profileHeight: 80,
extrudeLength: 400,
// Properties of I-profile
webThickness: 30,
flangeThickness: 5,
// Properties of O-profile
hollow: true,
pipeThickness: 20,
}
this.update()
}
update() {
const {
profileType,
profileWidth,
profileHeight,
extrudeLength,
webThickness,
flangeThickness,
hollow,
pipeThickness,
} = this.getProperties()
this.componentHandler.clearAll()
const beamComponent = new BEAM.Component()
beamComponent.setProperties({
profileType,
profileWidth,
profileHeight,
extrudeLength,
webThickness,
flangeThickness,
hollow,
pipeThickness,
})
this.componentHandler.add(beamComponent, {
position: new SKYMATH.Vector3D(-0.5 * extrudeLength, 0, 0),
})
const loadTesterComponent = new LOADTESTER.Component()
loadTesterComponent.setProperties({
extrudeLength,
profileHeight,
})
this.componentHandler.add(loadTesterComponent)
}
generateGeometry() {
return this.componentHandler.generateAllGeometry()
}
}
export function generateConfigurator(component: COMPONENTS.Component): SKYPARAM.Configurator {
const profileTypeParameter = SKYPARAM.generateDropdown(
'profileType',
'Profile Type',
[new SKYPARAM.DropdownItem('Type I', 'type-i'), new SKYPARAM.DropdownItem('Type O', 'type-o')],
{ defaultValue: component.getProperty('profileType') },
)
const profileWidthParameter = SKYPARAM.generateSlider('profileWidth', 'Profile width', {
defaultValue: component.getProperty('profileWidth'),
minValue: 10,
maxValue: 50,
})
const profileHeightParameter = SKYPARAM.generateSlider('profileHeight', 'Profile height', {
defaultValue: component.getProperty('profileHeight'),
minValue: 10,
maxValue: 150,
})
const extrudeLengthParameter = SKYPARAM.generateSlider('extrudeLength', 'Extrude length', {
defaultValue: component.getProperty('extrudeLength'),
minValue: 80,
maxValue: 800,
})
const webThicknessParameter = SKYPARAM.generateSlider('webThickness', 'Web thickness', {
defaultValue: component.getProperty('webThickness'),
minValue: 6,
maxValue: 150,
})
const flangeThicknessParameter = SKYPARAM.generateSlider('flangeThickness', 'Flange thickness', {
defaultValue: component.getProperty('flangeThickness'),
minValue: 1,
maxValue: 25,
})
const hollowParameter = SKYPARAM.generateCheckbox('hollow', 'Hollow', {
defaultValue: component.getProperty('hollow'),
})
const pipeThicknessParameter = SKYPARAM.generateSlider('pipeThickness', 'Pipe thickness', {
defaultValue: component.getProperty('pipeThickness'),
minValue: 4,
maxValue: 100,
})
const parameters = [
profileTypeParameter,
profileWidthParameter,
profileHeightParameter,
extrudeLengthParameter,
webThicknessParameter,
flangeThicknessParameter,
hollowParameter,
pipeThicknessParameter,
]
const configurator = new SKYPARAM.Configurator(parameters)
configurator.setUpdateRule('webThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
// visibility rule
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(true)
break
case 'type-o':
parameter.setVisible(false)
break
}
// max rule
if (parameter.getVisible() === true) {
const newMaxValue = values.profileWidth
parameter.setMax(newMaxValue)
if (value > newMaxValue) parameter.setValue(newMaxValue)
}
}
})
configurator.setUpdateRule('flangeThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
// visibillity rule
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(true)
break
case 'type-o':
parameter.setVisible(false)
break
}
// max rule
if (parameter.getVisible() === true) {
const maxValue = Math.floor(0.5 * values.profileHeight) - 1 // rounded with Math.floor to avoid decimals
parameter.setMax(maxValue)
if (value > maxValue) parameter.setValue(maxValue)
}
}
})
configurator.setUpdateRule('hollow', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
// visiblity rule
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(false)
break
case 'type-o':
parameter.setVisible(true)
break
}
}
})
configurator.setUpdateRule('pipeThickness', (value, directUpdate, parameter, values) => {
if (!directUpdate) {
// visibility rule
const newProfileType = values.profileType
switch (newProfileType) {
case 'type-i':
parameter.setVisible(false)
break
case 'type-o':
const newHollow = values.hollow
parameter.setVisible(newHollow)
break
}
// max rule
if (parameter.getVisible() === true) {
const maxValue = Math.floor(0.5 * Math.min(values.profileWidth, values.profileHeight)) - 1
parameter.setMax(maxValue)
if (value > maxValue) parameter.setValue(maxValue)
}
}
})
configurator.addUpdateCallback((valid, values) => {
console.log(values)
if (valid) {
component.setProperties(values)
}
})
return configurator
}
Congratulations! You have learned the basics of how to work with configurators and a bit more about materials. If we put this into an app it could look something like this:
Now that you know the basics of components, drawings & configurators, it's time to go through the user interface of the application that uses all this and for that, we will go through the UI Editor in the next tutorial User Interface.