How To Handle States
In DynaMaker it is possible to switch between certain behaviors and control these states during the application. The most common examples of states are:
- 1. Switching visualization of models between 2D and 3D.
- 2. Enabling selecting or building certain parts of a product.
Remember that states are usually handled through the UI and should never be a property of a component, since they are not meant to be saved together with a configuration. So, a state should only refer to where you are in the app.
1. Switch between 2D and 3D
The most typical example that one can think of is switching the visualization of the models presented in the canvas between 2D and 3D through a QuickBar button or just by switching tabs. These examples are explained here in detail, however they can be extended to any other type of behavior, such as showing/hiding dimensions or switching between detailed and basic 3D geometry for performance for example.
A. By Switching Tabs
To switch the state between tabs you need to make sure to:
- In each
SKYUI.Tab
, use the optional argumentdesignStep
to set a state when switching to that tab. As an example of 2 tabs with one configurator each:
export function onInit() {
// logic to add new component to componentHandler
const tab1 = new SKYUI.Tab({
title: '2D',
icon: 'cog',
designStep: 'tab-2D',
onInit: () => {
const configurator1 = ACTIONS.generateConfigurator1()
configurator1.addCompletionCallback((valid) => {
if (valid) {
Studio.requestGeometryUpdate() // updates geometry for each valid update in the configurator
}
})
Studio.setTabContent([configurator1])
Studio.requestGeometryUpdate() // updates geometry when switching to this tab
},
})
const tab2 = new SKYUI.Tab({
title: '3D',
icon: 'cog',
designStep: 'tab-3D',
onInit: () => {
const configurator2 = ACTIONS.generateConfigurator2()
configurator2.addCompletionCallback((valid) => {
if (valid) {
Studio.requestGeometryUpdate() // updates geometry for each vaid update in the configurator
}
})
Studio.setTabContent([configurator2])
Studio.requestGeometryUpdate() // updates geometry when switching to this tab
},
})
Studio.setTabs([tab1, tab2])
}
You could store these values in the editor tab CONSTANTS.
// CONSTANTS
export const DESIGN_STEP = {
TAB_2D: 'tab-2d',
TAB_3D: 'tab-3d',
}
-
In the component shown in the UI (typically assembly) either:
- a) create a function that generates 2D geometry and a second one for 3D geometry:
export class Component extends STUDIO.BaseComponent<Properties> {
constructor() {
// properties
}
generateGeometry3D() {
const geometryGroup = new SKYCAD.GeometryGroup()
geometryGroup.addGeometry(GEOM3D.generateModel(this.properties))
return geometryGroup
}
generateGeometry2D() {
const geometryGroup = new SKYCAD.GeometryGroup()
geometryGroup.addGeometry(GEOM2D.generateLayout(this.properties))
return geometryGroup
}
generateGeometry() {
// used if it needs to be visualized in the Component Editor. It won't be used in the UI here.
return this.generateGeometry3D()
}
}- b) or use tags for identifying them and later take advantage of that:
export class Component extends STUDIO.BaseComponent<Properties> {
constructor() {
// properties
}
generateGeometry() {
const geometryGroup = new SKYCAD.GeometryGroup()
geometryGroup.addGeometry(GEOM3D.generateModel1(this.properties), { tags: ['3D'] })
geometryGroup.addGeometry(GEOM3D.generateModel2(this.properties), { tags: ['3D'] })
geometryGroup.addGeometry(GEOM2D.generateLayout1(this.properties), { tags: ['2D'] })
geometryGroup.addGeometry(GEOM2D.generateLayout2(this.properties), { tags: ['2D'] })
return geometryGroup
}
}Tags can be used in subcomponents' geometries too, just make sure these components/geometries are tagged accordingly when adding them to the component handler of the top level component.
-
In the main function
UI.onInit()
overwrite the function that takes care of updating the geometry by setting a new one withStudio.setUpdateGeometryFunction()
, which givesdata
(similar toStudio.getManager()
or justmanager
from ACTIONS) andworldControls
to use. Depending on the way you chose before:- a) Using different generate-geometry functions:
export function onInit() {
Studio.setUpdateGeometryFunction(async (data, worldControls) => {
const assemblyComponent = ACTIONS.getAssemblyComponent()
if (data.designStep === 'tab-2D') {
// notice data.designState is used
const geometryToShow = assemblyComponent.generateGeometry2D()
await worldControls.updateGeometry(geometryToShow, { groupId: CONSTANTS.GROUP_ID.MAIN })
} else {
// including 'tab-3D' and others
const geometryToShow = assemblyComponent.generateGeometry3D()
await worldControls.updateGeometry(geometryToShow, { groupId: CONSTANTS.GROUP_ID.MAIN })
}
})
// rest of the functions
}- b) Using geometry tags:
export function onInit() {
Studio.setUpdateGeometryFunction(async (data, worldControls) => {
if (data.designStep === 'tab-2D') {
const geometry = data.componentHandler.generateAllGeometry({ exludeTags: ['3D'] })
await worldControls.updateGeometry(geometry, { groupId: CONSTANTS.GROUP_ID.MAIN })
} else {
const geometry = data.componentHandler.generateAllGeometry({ exludeTags: ['2D'] })
await worldControls.updateGeometry(geometry, { groupId: CONSTANTS.GROUP_ID.MAIN })
}
})
// rest of the functions
}
See that
groupId
is used to identify the geometry, meaning that you can have multiple geometries that are being updated only when you want. If you don't want to show a geometry, send an empty geometry group (i.e.new SKYCAD.GeometryGroup()
) with the samegroupId
.
Here is an example of an app that switches to state exclusively to update the geometry through switching tabs:
B. By QuickBar Button
As an alternative, one can switch the state through a QuickBar button for example. Not only switching between models in 2D and 3D but there are other types of simple visualization changes that can be done through a QuickBar button, like showing/hiding dimensions.
In this case, we have used tags in the geometry to make it simpler. However, a second generate-geometry function can be
used instead (check the previous example for reference). Also, instead of using designStep
which only refers to
SKYUI.Tab
, we will use Studio.setState()
which can be used anywhere in UI within UI.onInit()
.
For this:
- Create a QuickBar button (through
Studio.setQuickbarItems()
) and set the state according to the value
export function onInit() {
// logic to add new component to componentHandler
Studio.setQuickBarItems([
{
icon: 'eye-open',
action: function () {
this.value = !this.value // true or false
const state = this.value ? 'show-2D' : 'show-3D'
this.icon = this.value ? 'eye-close' : 'eye-open'
this.tooltip = this.value ? `Show 3D` : `Show 2D`
Studio.setState(state)
Studio.requestGeometryUpdate()
},
tooltip: `Show 3D`,
},
])
const tab1 = new SKYUI.Tab({
// arguments of tab
})
Studio.setTabs([tab1])
}
- Overwrite the update-geometry function through
Studio.setUpdateGeometryFunction()
. Remember to usedata.state
to access the state that is being changed throughStudio.setState()
:
export function onInit() {
// logic to add new component to componentHandler
Studio.setUpdateGeometryFunction(async (data, worldControls) => {
const geometryToShow = new SKYCAD.GeometryGroup()
if (data.state === 'show-2D') {
// notice data.state is used
const geometry = data.componentHandler.generateAllGeometry({ exludeTags: ['3D'] })
await worldControls.updateGeometry(geometry, { groupId: CONSTANTS.GROUP_ID.MAIN })
} else {
const geometry = data.componentHandler.generateAllGeometry({ exludeTags: ['2D'] })
await worldControls.updateGeometry(geometry, { groupId: CONSTANTS.GROUP_ID.MAIN })
}
})
Studio.setQuickBarItems([
/*QuickBar item*/
])
const tab1 = new SKYUI.Tab({
/*arguments*/
})
Studio.setTabs([tab1])
}
Here is an example of an app that switches to state exclusively to update the geometry through a QuickBar button:
2. Switch between select and build
Being able to select and build components through buttons in the UI is a typical behavior wanted in certain apps that include mouse interaction. Although states can be helpful, using a DynaMaker in-built function for storing components or instances together with actions should be enough. For this:
-
In UI, prepare a button for each behavior, which is triggered through an action.
const selectButton = new SKYUI.Button('Select', () => ACTIONS.enableSelection(), {
activeOnClick: true,
icon: 'hand-up',
})
const buildButton = new SKYUI.Button('Build', () => ACTIONS.enableBuild(), { icon: 'plus' }) -
In ACTIONS, create both functions that create a temporary component or factory that is meant for handling the selection/building of the components.
export function enableBuild() {
const assemblyComponent = getMainComponent()
const moduleComponentToBuild = new MODULE.Component()
const buildModuleFactory = new BUILDMODULEFACTORY.Component(assemblyComponent, moduleComponentToBuild, {
onUpdate: () => {
manager.requestGeometryUpdate()
}, // additional functionality needed to update geometry while moving mouse, clicking, etc
})
manager.select(buildModuleFactory) // "stores" the factory to use it later
}
export function enableSelection() {
const assemblyComponent = getMainComponent()
const selectModuleFactory = new SELECTMODULEFACTORY.Component(assemblyComponent, {
onUpdate: () => {
manager.requestGeometryUpdate()
}, // additional functionality needed to update geometry while moving mouse, clicking, etc
})
manager.select(selectModuleFactory) // "stores" the factory to use it later
}Reach us at support@dynamaker.com if you need help understanding how a factory should look.
-
Overwrite the update-geometry function so it shows both the components (as usual) and the so-called factories.
Studio.setUpdateGeometryFunction(async (data, worldControls) => {
const { activeSelection, componentHandler } = data
// Components
const componentsGeometryGroup = componentHandler.generateAllGeometry()
await worldControls.updateGeometry(componentsGeometryGroup, { groupId: 'components' })
// Factory
const factoryGeometryGroup = new SKYCAD.GeometryGroup()
const isFactorySelected =
activeSelection instanceof BUILDMODULEFACTORY.Component ||
activeSelection instanceof SELECTMODULEFACTORY.Component
if (isFactorySelected) {
factoryGeometryGroup.addGeometry(activeSelection.generateGeometry())
}
await worldControls.updateGeometry(factoryGeometryGroup, { groupId: 'factory' })
})
Notice that
activeSelection
contains whatever is selected throughmanager.select()
from the actions. Also see that if a factory is not selected (e.g. mouse is outside the bounds of the component to select), thenfactoryGeometryGroup
will be empty and therefore no factory is shown.
In the following application both behaviors are shown in the second tab Modules:
- build through the flyout
Add module
, which is enabled after the popup modal. - select through the button
Select
Would you like to contribute with other examples or request new ones? Reach us at support@dynamaker.com and help the community to code faster!