Skip to main content

STUDIO

This library has a collection of UI features, camera handling, component handler and more:


Actions​

As best practices in DynaMaker, when we are working in the product configurator, we use actions to wrap specific logic into functions, under ACTIONS, so that they can be used easily in UI. Actions refer to something that happens, so it is very common and convenient to use actions when we click on certain buttons and tabs, or when we are done with a configuration model in its callback.

Taking the example of a product configurator with a conveyor in the following picture, there are 4 actions:

  • ACTIONS.addSection(): to add a new section to the assembly according to the parameters.
  • ACTIONS.removeSection(): to remove the last added section to the assembly.
  • ACTIONS.generateConfigurator(): to generate the configuration model from the assembly component.
  • ACTIONS.generateStlData(): to generate the data to download the proper file.

Remember to write export before function, so that they can be called in other code-editor tabs, like UI, HOTKEYS, etc.

As any function in TypeScript, actions in DynaMaker can do some logic (like ACTIONS.addSection()), generate something (like ACTIONS.generateConfigurator()), get a component that could be used as input in drawings or update the camera with a default travel time and direction when switching tab or clicking a quickbar item:

export function getAssemblyComponent() {
const component = manager.getComponentHandler().getComponents(ASSEMBLY.Component)[0]!
if (!component) throw new Error('Could not find assembly component')
return component
}

export function updateCamera({ travelTime = 750, direction = new SKYMATH.Vector3D(1, 1.5, -0.75) } = {}) {
manager.setCameraToFitBounds({ direction, travelTime })
}

UI Items​

There are other ways to customize the UI apart from adding SKYUI.Button and SKYUI.Tab. In your DynaMaker product configurator it is possible to add:

These can be added in the predefined function onInit() in your product configurator (UI).

You can add a Menu dropdown to the top-right corner, where you add SKYUI.Button which any functionality (e.g. change unit system, language, rename configuration, save as new configuration, etc).

The following example contains a Menu with Save and Load buttons. Custom pop-ups have been made for the picture below, but you can find more info about save & load briefly here.

Studio.setMainMenuItems([
new SKYUI.Button('Save', () => {
/*logic to save*/
}),
new SKYUI.Button('Load', () => {
/*logic to load*/
}),
new SKYUI.Button('Language', () => {
/*logic to change language*/
}),
new SKYUI.Button('Unit system', () => {
/*logic to change unit system*/
}),
])

Quickbar Items​

You can add buttons to the Quickbar at the top.

The following example contains a button Home to recenter the camera to an ISO view, and Delete with a custom function to delete any possible selected component. Notice that there are some optional arguments to use:

  • action to trigger any functionality needed.
  • icon to change its appearance with a predefined (icon)[library-studio#icons].
  • image to change its appearance with an image (created from assets).
  • tooltip (as string) to show when hovering the mouse over it.
  • disabled to disable it with a greyed-out appearance.

Studio.setQuickBarItems([
{
name: 'Home',
icon: 'home',
action: () => {
Studio.setCameraToFitBounds()
},
tooltip: 'Reset Camera',
},
{
name: 'Delete',
image: ASSETS.IMAGES.TRASH,
action: () => {
ACTIONS.deleteSelectedComponent()
},
disabled: true,
tooltip: 'Delete',
},
])

How to update Quickbar Items dynamically​

If you want to update dynamically the Quickbar items (e.g. if something is selected then the delete item with the trash image should be enabled, otherwise disabled), then simply reset the quickbar items by using Studio.setQuickbarItems(). For that then it's recommended to wrap the quickbar items in a function so that you can call it within an action of a SKYCAD.Button or within a callback of a configuration model, as Studio.setQuickbarItems(getQuickbarItems(disableDelete: true)).

function getQuickbarItems({ disableDelete = false } = {}) {
return [
{
name: 'Home',
icon: 'home',
action: () => {
Studio.setCameraToFitBounds()
},
tooltip: 'Reset Camera',
},
{
name: 'Delete',
image: ASSETS.IMAGES.TRASH,
action: () => {
ACTIONS.deleteSelectedComponent()
},
disabled: disableDelete,
tooltip: 'Delete',
},
] as STUDIO.QuickBarItem[]
}

In the product configurator below, notice how the quickbar item Download 3D (STEP) becomes disabled when height < 3000.

🎥 Video on Quickbar & Metrics.

Metrics​

Sometimes in your product configurator you want to let the user know about certain values while designing. Usually when you are configuring your product, you want to update those values dynamically.

The following example contains information about the material, color & volume of the cube.

Studio.defineVisibleMetrics([
{
id: 'material',
value: `PVC`,
label: 'Material: ',
},
{
id: 'color',
value: `Blue`,
label: 'Color: ',
},
{
id: 'volume',
value: `1000 cm³`,
label: 'Volume: ',
},
])

How To Update Them Dynamically​

In order to update the metrics dynamically, we need to update the metrics when we update the parameters.

For example, the cube volume needs to be updated when changing the size given by the parameters. Since the only configuration model that exists in the template is in the Product tab, we can add an addUpdateCallback() so that it updates the metric with id 'volume' through Studio.updateVisibleMetric(). Then, you should have the following:

function generateProductTab(Studio: STUDIO.StudioService): SKYUI.Tab {
const tabTitle: string = 'Product'

const toolbarGenerator: STUDIO.ToolbarGenerator = () => {
const configurator = ACTIONS.configureMainComponent()

configurator.addUpdateCallback((valid, value) => {
if (valid) {
const cubeVolume = ACTIONS.getCubeVolume()
Studio.updateVisibleMetric('volume', `${(cubeVolume / 1000).toFixed(1)} cm³`)
}
})

const toolbarItems: STUDIO.ToolbarItem[] = [
configurator,
]
return toolbarItems
}

const tabArgs: STUDIO.TabArgs = {
... // whatever you have
}

return new SKYUI.Tab(tabTitle, toolbarGenerator, tabArgs)
}

Notice that here we have created previously a function getCubeVolume() in ACTIONS that gives the multiplication of width, depth and height of the component in mm³. Then we divide by 1000 to make it cm³ and leave 1 decimal with .toFixed(1).


As another example, in the product configurator below, notice how the metric item Download 3D (STEP) becomes hidden when height < 3000.

🎥 Video on Quickbar & Metrics.

If you wonder how to update the metrics instantly, read more here.

Show/hide metrics​

Studio.updateVisibleMetric('volume', { hidden: true })
Studio.updateVisibleMetric('volume', { hidden: false })

HTML Elements​

In DynaMaker it's possible to add HTML elements directly to the tab. As an example of adding some text into the product configurator's template:

const tab1 = new SKYUI.Tab({
title: 'Configure',
icon: 'cog',
onInit: () => {
// widthInput & updateButton

const divElement = document.createElement('div')
divElement.innerText = `Update to change cube's width`

const tabContent = [widthInput, updateButton, divElement]
Studio.setTabContent(tabContent)

// other functions (e.g. camera and geometry updates)
},
})

You can find more info about HTML elements here.

Icons​

Icons can be added to other UI elements, mainly used in tabs and buttons. In order to use them, take the name of the icon and add it as a string in the optional argument icon of your tab or button, like:

The default set of icons available in DynaMaker can be found here.

Use the IntelliSense to assist you with the code completion, press Ctrl+Space within the string to see them like:

Alternatively, pictures from ASSETS can be used as well instead, like: icon: ASSETS.IMAGES.MY_COG. Additional adjustments might need to be done with a custom CSS snippet in STYLE.

Modals​

Use modals to get the attention of the user in certain moments. Giving short tips when entering a complex selection mode, forcing to select certain parameters that heavily affect the model or filling a form to complete a quotation with the user's email with an attached custom drawing, are some examples of use. There are many predefined modals built in DynaMaker that are already to use:

Info Modal​

Use an info modal to give any information to the user as a pure description or warning. For example when clicking a button that allows a special selection mode or a warning about constraints from the parameters selected.

Studio.openInfoModal('Title', 'Description of the info modal', 'This is the body of an info modal')

It is possible to have a custom header and footer in HTML, so anything can be included in those, like text, pictures, videos, links, iframes, etc (see HTML examples here). Simply use the optional arguments headerHtml and footerHtml.

Studio.openInfoModal('Title', 'Description of the info modal', 'This is the body of an info modal', {
headerHtml: `<p>This is a custom header</p>`,
footerHtml: `<p>This is a custom footer</p>`,
})

Keep in mind that the custom header replaces the title and description, and the custom footer replaces the default close button.

Items Modal​

As a variation of the info modal, the items modal allows you to show information in multiple rows by default. It is also possible to add extra functionality at the top-right of the pop-up by using the optional argument headerActions. Also, it allows you to show/hide and rename the OK & Cancel button at the bottom right.

Studio.openItemsModal('Title', 'This is a description', ['item 1', 'item 2', 'item 3'], {
okButtonText: 'Send Quotation',
hideOkButton: false,

cancelButtonText: 'Cancel',
hideCancelButton: false,

headerActions: [
{
label: 'Help',
action: () => {
window.open('https://docs.dynamaker.com')
},
icon: 'question-sign',
},
{
label: 'Close',
action: (modalControl) => {
modalControl.close()
},
icon: 'remove',
},
],
})

You could also use the optional arguments bodyHtml, headerHtml and footerHtml to have any custom HTML, but keep in mind that these replace the item list, title & description, and ok & cancel buttons respectively. headerActions are still available.

Parameter Modal​

With the parameter modal you can show all parameters of a configuration model in a pop-up. A difference from showing configuration models in a tab as usual is that the thumbnail or icon is visible to easily identify what the parameter is related to for example. Also, you can disable the default visual transition when open with the optional boolean argument animation. See that headerActions, footerHtml and headerHtml can be used as well.

const configurator = ACTIONS.configureMainComponent()

Studio.openParameterModal(configurator, 'Configure cube', 'Choose width, depth and height', {
animation: true,
headerActions: [ ... ], // same as item modals
// footerHtml: 'This is a custom footer',
// headerHtml: 'This is a custom header',
})

You can use predefined icons or custom pictures from ASSETS as thumbnails to the parameters in a modal using the optional arguments icon and thumbnail respectively as:

const widthParameter = SKYPARAM.generateSlider('width', 'Width (x)', {
icon: 'resize-full',
})
const depthParameter = SKYPARAM.generateInputParameter('depth', 'Depth (y)', {
thumbnail: ASSETS.URLS.THUMBNAIL_DEPTH_PNG,
})
const heightParameter = SKYPARAM.generateDropdown('height', 'Height (z)', options, {
thumbnail: ASSETS.URLS.THUMBNAIL_HEIGHT_PNG,
})

Notice that the URL of the picture file has to be used for the thumbnail. The URL of an asset is generated automatically when the file is uploaded to the dashboard.

Keep in mind that this configuration model still follows the rules and callbacks of the configuration model from ACTIONS.configureMainComponent().

However, we usually want to update the geometry right after we are done with the configuration model to see the changes, or perhaps we want to go into a second configuration model that depends on this one. Since openParameterModal() is a promise, these examples require .then() so the functionality mentioned can be easily implemented.

Update Geometry After Configuration Model​

const configurator = ACTIONS.configureMainComponent()

Studio.openParameterModal(configurator, 'Configure cube', 'Choose width, depth and height').then((result) => {
if (result !== undefined) {
Studio.requestGeometryUpdate()
Studio.requestLightweightGeometryUpdate()
// other functions
}
})

Notice that result is undefined when clicking Cancel. On the other hand, when the configuration model is valid, i.e. all parameters are valid, clicking OK gives an object as result as in the following case, giving all the information of the configuration model and its parameters:

result: {
success: boolean,
configurator: SKYPARAM.Configurator(),
parameters: [
SKYPARAM.SliderParameter,
SKYPARAM.InputParameter,
SKYPARAM.DropdownParameter,
]
}

In the example used before, you can see that when result !== undefined, we will update the geometry. However, more functions can be added there of course.

Nested Configuration Models​

Another common example is when we have two nested configuration models, called e.g. configuratorA and configuratorB, with the following conditions:

  • configuratorA should be the first configuration model to be open.
  • clicking OK in configuratorA should open configuratorB.
  • geometry and properties should be updated only if OK is clicked in both configuration models.
  • clicking Cancel in configuratorB should not apply changes from configuratorA.
  • configuratorB depends on the parameters from configuratorA.
let savedResultA
const configuratorA = ACTIONS.getConfiguratorA()
Studio.openParameterModal(configuratorA, 'Configure cube', 'Choose width, depth and height').then((resultA) => {
if (resultA !== undefined) {
savedResultA = resultA

const configuratorB = ACTIONS.getConfiguratorB(savedResultA.parameters)
Studio.openParameterModal(configuratorB, 'Configure appearance', 'Choose material and color').then((resultB) => {
if (resultB !== undefined) {
ACTIONS.setComponentProperties(savedResultA, resultB)
Studio.requestGeometryUpdate()
Studio.requestLightweightGeometryUpdate()
}
})
}
})

Notice that resultA needs to be saved as savedResultA to be able to use it in ACTIONS.setComponentProperties().

Form Modal​

A form modal is a dialog box that contains a form for users to input data. The following steps explain how to create and open a form modal using Studio.createFormModal():

1. Define the form fields​

Define the form fields using an array of objects that represent each field. Each object must have the following properties:

  • type: the type of input field (e.g., text, number, date).
  • id: the ID of the input field.

You may also specify additional properties for each input field, such as a label text or a required boolean flag.

2. Create the form modal​

Create the form modal using the Studio.createFormModal() method. This method takes two arguments:

  • An array of form field objects (created in step 1).
  • An options object that specifies the configuration for the form modal, such as animation, modalClassName, and texts (for the title and feedback texts).

Assign the result of the Studio.createFormModal() method to a variable to reference the form modal object.

3. Open the form modal​

To initiate the form modal, use the .open() method on the form modal object. This method will return a promise that resolves with the form data once the user submits the form.

Here's an example of a form modal used to request a quotation when clicking on a button. Upon completion of the form, the Studio.openInfoModal() function is used to confirm the request:

const quotationButton = new SKYUI.Button(`Request Quotation`, () => {
const formModal = Studio.createFormModal(
[
{
type: 'text',
id: 'customerName',
label: 'Name',
required: true,
},
{
type: 'email',
id: 'customerEmail',
label: 'Email',
required: true,
},
],
{
animation: true,
modalClassName: 'quotation-modal',
texts: {
title: 'Quotation Request',
description: 'Fill in the details of your request',
cancelButton: 'Cancel',
okButton: 'Send',
},
},
)

formModal.open().then((formData) => {
if (formData) {
Studio.openInfoModal(
'Thank you for your request!',
'Your request has been sent!',
'You will receive an email with information. You can close this pop-up.',
)
} else {
// Form modal was canceled
}
})
})

After the user is done with a modal, an email could be automatically sent to them or any other person at the same time. Read more about how to get the best use of the form modal with the DynaMaker plugin SendGrid.

Custom Modal​

Would you like to request a completely customized modal that does not fit any of the options provided above? If so, please send an email to support@dynamaker.com outlining the specific features you require, and we will assist you in getting started.

Camera Handling​

All Studio-related functions to the camera are best explained in the how-to example with a product configurator.

Taking Pictures​

Do you want to take a picture of your project? There is an easy-to-use function in the Studio for that! Here is an example of downloading a picture in the callback of a button.

const downloadImageButton = new SKYUI.Button(
'Take picture',
() => {
Studio.requestImage({
type: 'png',
size: new SKYMATH.Vector2D(1600, 900),
orthographic: true,
background: {
color: new SKYCAD.RgbColor(200, 200, 200, { alpha: 0 }),
},
disableCastShadows: true,
camera: {
behavior: 'fit-to-bounds',
boundsOffset: 50,
direction: new SKYMATH.Vector3D(1, 1, -0.25), // 'front' | 'rear' | 'iso' | 'top' | 'bottom' | 'left' | 'right'
// bounds: new SKYCAD.Bounds3D(),
// ignoreGeometryGroups: ['2D'],
},
}).then((image) => {
return Studio.downloadFile(`My project.png`, image)
})
},
{ icon: 'download' },
)

where:

  • type: gives the format of the picture as .png or .jpeg.
  • size: gives the size of the picture (increasing quality and therefore file size!) and ratio.
  • orthographic: changes between perspective or orthographic view.
  • background: allows changing the color of the background of the picture, and by setting alpha: 0 from a SKYCAD.RgbColor then the background becomes transparent as a typical png picture.
  • disableCastShadows: removes any possible shadow casted to the ground or other elements.
  • camera with either:
    • behavior as 'fit-to-bounds' with:
      • boundsOffset increasing the visible bounds with a number in all directions (useful when we want some margin in the picture)
      • direction: as either a SKYMATH.Vector3D or predefined direction such as iso, front and left.
      • bounds to override the default bounds (what you see in the visualization).
      • ignoreGeometryGroups to ignore a list of string IDs that won't be taken into account for autofit.
    • behavior as 'fit-to-bounds' with:
      • cameraPosition as SKYMATH.Vector3D of the point where the picture is taken from.
      • cameraTarget as SKYMATH.Vector3D of the point where the picture is taken towards.

🎥 Video on Taking pictures.

Selection Handling​

There are up to 5 different types of events you can reach through clicking or moving the mouse. Below we describe how to get them, and must not be mixed with the camera controls:

Read more here to rebind the camera default buttons.

They can be found only in the Product Configurator, which the user will interact with, under SELECTION, in the function selectionEvent(). The specific type can be found in the first argument like event.type and it is common to follow this pattern using a switch statement:

export function selectionEvent(event, selectionPosition: SKYMATH.Vector3D, manager: STUDIO.Manager) {
switch (event.type) {
case 'mouse-move': // moving the mouse without clicking
break
case 'selection-start': // left-clicking the mouse (down click)
break
case 'selection-move': // left-clicking & moving mouse at the same time (down & drag click)
break
case 'selection-end': // releasing the left-button the mouse (up click)
break
case 'selection-empty': // double left-clicking the mouse quickly
break
}
}

selection-empty empty can be reached also by:

  • pressing key Esc.
  • single left-clicking outside the world.

In order to the see/change the world:

  • Go to CONTROLLER.
  • In the function productConfigurationFactory(), check two things:
    • sceneSelection to toggle the visibility of the ground, which can be:
      • WORLD.SCENES.STANDARD: to make the ground visible.
      • WORLD.SCENES.CLEAN: to make the ground invisible.
    • worldSize: make sure it is bigger than your product, so worldSize: { x: 20000, y: 20000, z: 300 } could be a good size for now to avoid strange selection behavior.

Notice that the component always starts in the origin of the world size (0, 0, 0). But don't worry, we will make this more simple in the future, so you don't have to care about the world and its size anymore.

Notifications​

When interacting with the canvas, sometimes the visual feedback given by layouts or block zones is not enough and we want to know whether an action took place successfully or not. We can use a notification to help the user to know what happened at a specific point. There are four different types to use and here we show how to implement them in the product configurator.

How To Use​

Wherever the Studio or manager exists:

Studio.displayNotification('Action completed', { type: 'success' })
manager.displayNotification('Invalid position', { type: 'danger' })

Examples​

As inspiration, these are the most common examples to use the notifications on:

  • Info:
    • to briefly describe the functionality of a tab/button that has been clicked
  • Success:
    • files have been downloaded
    • order has been put in the production queue
    • an algorithm, that automatically adds multiple components, has finished
  • Warning:
    • a component has been added but due to some constraints, it doesn't have a specific subcomponent
    • when changing a parameter that removes subcomponents automatically due to internal design rules
  • Danger:
    • when trying to place components in invalid positions

Remember that the goal of these messages should help the user when designing and should not give information about invalid or improducible designs. Then, we are talking about bugs in the product configurator and these should be handled and not shown to the end-user.

Configuration Info​

You can see the information regarding your configuration by using the following in UI of the product configurator, which contains:

  • id that is unique for all the configurations saved in the product configurator.
  • isUnsaved as a boolean to check if the configuration has been saved previously or not.
  • name given when saving the configuration
  • revision as the saved version of the configuration (starting in 0). E.g. a configuration saved 3 times will have revision: 3.
const configurationInfo = Studio.getConfigurationInfo() // returns { id: string, isUnsaved: boolean, name: string, revision: number }

The most common example of use of this can be found in the drawing, e.g. to show the name and revision in the header. Also remember that you can access the previous versions of saved configurations through the URL, read more about it here.

Scene Settings​

The editor tab EXPORTS can be found in the UI of your product configurator. Besides what's needed to run the product configurator, you can find some scene settings in the predefined function productConfigurationFactory():

export function productConfigurationFactory(): STUDIO.IProductConfiguration {
return {
// Scene
sceneOptions: {
type: 'room',
ambientIntensity: 1,
lightIntensity: 1,
lightRotation: 0,
clearColor: 0xffffff,

// type: 'outdoor',
// ambientIntensity: 1,
// sunIntensity: 1,
// sunPosition: new SKYMATH.Vector3D(1000, -1000, 1000),
// clearColor: 0xffffff,
// subjectSize: 1000,
// shadowMapSize: 1000,
},

// Others
showEdges: true,
disableLoadingEffectOnGeometryUpdate: true,
ui: {
disableTabCollapse: true,
animatedModals: true,
},
useSelectiveGeometryInteraction: true,

// Do not remove these 3:
generator: ProductGenerator,
componentIdMappings: getComponentIdMappings(),
textures: getTextureReferences(),
}
}

In here you will find about:

sceneOptions​

  • sceneOptions with the alternatives:
    • room with multiple light sources but no shadow-casting, with these options:
      a) ambientIntensity and lightIntensity, whose combination can achieve different results:
      b) lightRotation in radians to rotate the lighting:

      c) clearColor in hexadecimal code to change the background color:

    • outdoor with 1 light source and shadow-casting, with these options:
      a) ambientIntensity and sunIntensity, whose combination can achieve different results:

      b) sunPosition as SKYCAD.Vector3D to determine the only light source position and thus the shadow directions:

      c) clearColor in hexadecimal code to change the background and light color affecting the material appearances:

      c) subjectSize to adjust manually the shadows to the subject (undefined will find the most suitable shadow size):

      d) shadowMapSize to adjust the blurriness/sharpness of the shadows.

      tip

      Casting shadows generally takes much higher computational time - this is why room is preferable over outdoor.

Others​

  • showEdges to show/hide the edges of the model.

  • disableLoadingEffectOnGeometryUpdate: to disable/enable the loading effect when the geometry updates:

    • true (left in video below) to disable the blur effect between geometry updates (recommended for light/fast geometries).
    • false (right in video below) to enable the blur effect between geomrtry updates (recommended for heavy/slow geometries).

UI tabs & modals​

  • ui with:
    • disableTabCollapse to disable/enable the SKYUI.Tab to be collapsible when clicking on it.
    • animatedModals to disable/enable modals animation, i.e. if false they show up instantly.

Environment Mapping (HDRI)​

The reflection or environment mapping is an efficient lighting technique that allows reflections on the object surfaces more cheaply with little loss of accuracy compared to other expensive ones like ray tracing. This is achieved with a high dynamic range image or HDRI that acts as a 3D surface surrounding the object.

🎥 Video on Environment Mapping.

In order to use them:

  1. Get the HDRI in EXR format (you can try downloading any of these: Workshop (.exr), Airport (.exr), Bathroom (.exr))
  2. In the dashboard, upload your EXR file to your product configurator under Upload File
  3. Once uploaded successfully, step into the UI editor and import ASSETS as usual:
  4. Finally in EXPORTS, in productConfigurationFactory(), within sceneOptions use environmentMapUrl: ASSETS.URLS.WORKSHOP_EXR to enable it.
export function productConfigurationFactory(): STUDIO.IProductConfiguration {
return {
sceneOptions: {
type: 'room',
environmentMapUrl: ASSETS.URLS.WORKSHOP_EXR,
// showEnvironmentBackground: true,
// environmentBackgroundBlurriness: 0.07,
// lightIntensity: 2,
// ambientIntensity: 0.5
},

// ... rest of settings
}
}
tip

With an HDRI in place, try changing lightIntensity, ambientIntensity and lightRotation to tweak the lighting setup.

In the picture below we show 4 different scenarios:

  • A: default HDRI
  • B: custom HDRI but hidden (showEnvironmentBackground: false) - recommended to use!
  • C: custom HDRI shown (showEnvironmentBackground: true)
  • D: custom HDRI with blurriness (environmentBackgroundBlurriness: 0.07)

Try rotating the camera and see the effects of lighting and reflection on the cube's and sphere's surfaces:

note

See that the objects might appear as they are "floating" in the air. You can trick the user by adding the objects on some sort of static object, like almost-infinite ground if you use an outdoor HDRI with a skyline or even a simple table for the case of this workshop. Limiting the camera angle (maxPolarAngle: Math.PI/2 - see How to move camera) so that the user is not allowed to see e.g. under ground or behind a wall that the object is attached to usually helps with the immersion. Depending on the desired result, sometimes showEdges: true helps to see what should be configurable when enabled, although showEdges: false might make blend the objects with the environment better. It's up to you!

tip

Notice that each HDRI comes with its own light sources and colors, so the true colors of your models might look different under certain HDRIs. In the lightbulb example below, notice how the metallic bottom cap changes depending on the HDRI applied.


If you would like to explore the other available optional arguments or need help understanding them reach us at support@dynamaker.com.