Skip to main content

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:

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 argument designStep 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 with Studio.setUpdateGeometryFunction(), which gives data (similar to Studio.getManager() or just manager from ACTIONS) and worldControls 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 same groupId.

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 use data.state to access the state that is being changed through Studio.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 through manager.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), then factoryGeometryGroup 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!