Roomle Documentation
  • Overview
  • Quick Start Guides
    • Create you first 3D Viewer & AR
      • Sign up & upgrade your license plan in Rubens Admin
      • Create a product for the Rubens 3D Viewer
      • Batch upload products into your catalogue
    • Convert your static product into a material configurator
  • Rubens Products
    • Overview
    • Architectural overview
    • Rubens Products vs. Rubens SDK
    • Rubens 3D Viewer & AR
      • Getting started
    • Rubens Configurator
      • Getting started
      • Integration
        • Setup the configurator
        • Listen to events
          • Request a product
          • Parts list changes
          • Analytic events
          • Button clicks
        • Trigger API functions
          • Implement custom buttons
        • How to use prices in Rubens Configurator?
          • Use the parts list for calculating prices
          • Use Rubens Price Service
          • Use your own backend for calculating prices
        • Analytics Event
          • Rubens Configurator + Google Analytics (GDPR)
        • Customise shareable links
        • Handle CORS
      • Customisation
        • Hide Bottom Bar
        • Custom parts list print
        • Enable / Disable buttons
        • Override labels
        • Skinning options
          • Colors
          • Font
        • Localization
        • Change floor material
        • Highlighting in the configurator
      • Recipes
        • Implement custom AR button
        • Add product variants
        • Parameter outside of the configurator
        • Load different products into the configurator
    • Rubens Room Designer
      • Getting started
      • Integration
        • Setup Room Designer
        • Listen to events
          • onSavePlan
        • Call 3D Scene functions
          • Trigger request plan button
        • How to use prices in Room Designer?
          • Use Rubens Price Service
          • Use your own backend for calculating prices
        • Handle CORS
      • Customisation
        • Hide Bottom Bar
        • Enable / Disable buttons
        • Override labels
        • Skinning options
          • Colors
          • Font
        • Add products from an external catalog
        • Add custom overlays (advanced)
        • Localization
      • Recipes
        • Load different plans into the scene
        • Adding items to the scene
    • Rubens Products Reference
      • Classes
        • ExposedAnalyticsCallbacks.ExposedAnalyticsCallbacks
        • ExposedApi.ExposedApi
        • ExposedApi.Internal.Connector
        • ExposedApi.Internal.ExposedApiDragGhost
        • ExposedApi.Internal.ExposedApiDragHandler
        • ExposedApi.Internal.Libs
        • ExposedApi.Internal.MessageHandler
        • ExposedApi.Internal.RoomleSdkWrapper
        • ExposedApi.Internal.SdkConnector
        • ExposedApi.Internal.SdkConnectorConfigurator
        • ExposedApi.Internal.SdkConnectorPlanner
        • ExposedApi.Internal.SdkConnectorViewer
        • ExposedApi.Internal.UiCallbacks
        • ExposedApi.Internal.default-1
        • ExposedApi.Internal.default-2
        • ExposedApi.Internal.default
        • ExposedCallbacks.ExposedCallbacks
        • RoomleConfiguratorApi.default
      • Enums
        • ExposedApi.Internal.BUTTONACTIONS
        • ExposedApi.Internal.GACATEGORY
        • ExposedApi.Internal.GACUSTOM
        • ExposedApi.Internal.SDKMODULES
        • Types.UIBUTTON
        • Types.UIELEMENTS
      • Interfaces
        • ExposedApi.Internal.Analytics
        • ExposedApi.Internal.CollectionViewComponentIcon
        • ExposedApi.Internal.CollectionViewElement
        • ExposedApi.Internal.CollectionViewUiState
        • ExposedApi.Internal.CommonUiState
        • ExposedApi.Internal.CoreData
        • ExposedApi.Internal.CoreState
        • ExposedApi.Internal.DragGhostOptions
        • ExposedApi.Internal.DragInOptions
        • ExposedApi.Internal.DragInSettings
        • ExposedApi.Internal.EmbeddingCommand
        • ExposedApi.Internal.EmbeddingResponse
        • ExposedApi.Internal.GridViewElement
        • ExposedApi.Internal.GridViewUiState
        • ExposedApi.Internal.GridViewVariantElement
        • ExposedApi.Internal.IncomingMessageBus
        • ExposedApi.Internal.KernelBoundsFormatted
        • ExposedApi.Internal.OutgoingMessageBus
        • ExposedApi.Internal.OverlayState
        • ExposedApi.Internal.ParameterGroup
        • ExposedApi.Internal.PartlistResponse
        • ExposedApi.Internal.PlannerCoreData
        • ExposedApi.Internal.PlannerUiState
        • ExposedApi.Internal.PointRect
        • ExposedApi.Internal.SampleRoom
        • ExposedApi.Internal.SceneSelection
        • ExposedApi.Internal.SharedUiState
        • ExposedApi.Internal.StoreState
        • ExposedApi.Internal.TabCategory
        • ExposedApi.Internal.Translator
        • ExposedApi.Internal.UiLabels
        • ExposedApi.Internal.UiPossibleChildTagWithKey
        • ExposedApi.Internal.UiState
        • ExposedApi.Internal.WrapLines
        • ExposedCallbacks.Labels
        • ExposedCallbacks.Price
        • ExposedCallbacks.Internal.SaveDraftPayload
        • RoomleConfiguratorApi.RoomleEmbeddingApiKeys
        • Types.ConfiguratorSettings
        • Types.CustomViewSettingsForIframe
        • Types.CustomViewSettingsForOverlay
        • Types.CustomViews
        • Types.CustomViewsCallbacks
        • Types.EmbeddingSkin
        • Types.HelpCenterSetting
        • Types.ThumbnailsSettings
        • Types.UiInitData
        • Types.UiState
        • Types.VariantsMap
        • Types.Internal.CustomTutorialTranslation
        • Types.Internal.CustomViewSettingsBase
        • Types.Internal.PartlistPrintPayload
        • Types.Internal.SidebarEventTypes
        • Types.Internal.UiFeatureFlags
      • Modules
        • ExposedAnalyticsCallbacks
        • ExposedApi.Internal
        • ExposedApi
        • ExposedCallbacks.Internal
        • ExposedCallbacks
        • RoomleConfiguratorApi
        • Types.Internal
        • Types
      • Translate-labels
  • Rubens SDK
    • Overview
    • Getting Started
    • Rubens Modules
    • Rubens GLB Viewer
      • Getting started
      • Scene Customisation
        • Environment
        • Transparent Background
    • Rubens Configurator
      • Getting Started
      • Configurator Concepts
      • Listen to events
      • Control 3D Scene
      • Scene Customisation
        • Environment
        • Transparent Background
      • Different types of configurations
      • How to change a configuration
    • Rubens Planner (Room Designer)
      • Getting started
      • Listen to events
      • Control 3D Scene
      • Scene Customisation
        • Environment
        • Transparent Background
    • Rubens SDK Reference
      • Classes
        • EnvironmentDynamicEnvironmentSettingLoader.default
        • LightsettingDynamicLightSettingLoader.default
        • SceneSettingsLoader.default
        • Configurator.Configurator
        • Configurator.Internal.CommunicationInterface
        • Configurator.Internal.GlobalCallback
        • Configurator.Internal.MainThreadToWorker
        • Configurator.Internal.RoomleError
        • Configurator.Internal.UiCallback
        • Configurator.Internal.default-1
        • Configurator.Internal.default-2
        • Configurator.Internal.default-3
        • Configurator.Internal.default-4
        • Configurator.Internal.default-5
        • Configurator.Internal.default-6
        • Configurator.Internal.default-7
        • Configurator.Internal.default-8
        • Configurator.Internal.default-9
        • Configurator.Internal.default
        • RoomleConfigurator.Internal.AORenderPass
        • RoomleConfigurator.Internal.AbstractModel
        • RoomleConfigurator.Internal.AbstractModelWrapper
        • RoomleConfigurator.Internal.BakedGroundContactShadowPass
        • RoomleConfigurator.Internal.BaseMaterial
        • RoomleConfigurator.Internal.BlendAoAndAShadowMaterial
        • RoomleConfigurator.Internal.BlendAoPassDepthMaterial
        • RoomleConfigurator.Internal.BlurPass
        • RoomleConfigurator.Internal.CameraUpdate
        • RoomleConfigurator.Internal.CopyTransformMaterial
        • RoomleConfigurator.Internal.DebugPass
        • RoomleConfigurator.Internal.EMSConstant
        • RoomleConfigurator.Internal.EmsArray
        • RoomleConfigurator.Internal.EmsList
        • RoomleConfigurator.Internal.EnvironmentMapDecodeMaterial
        • RoomleConfigurator.Internal.GBufferMaterialCache
        • RoomleConfigurator.Internal.GBufferRenderPass
        • RoomleConfigurator.Internal.GroundContactCamera
        • RoomleConfigurator.Internal.GroundReflectionIntensityMaterial
        • RoomleConfigurator.Internal.GroundReflectionPass
        • RoomleConfigurator.Internal.InputEvent
        • RoomleConfigurator.Internal.KernelAtticArray
        • RoomleConfigurator.Internal.KernelObjectConfigurationArray
        • RoomleConfigurator.Internal.KernelPlanObjectList
        • RoomleConfigurator.Internal.LightSample
        • RoomleConfigurator.Internal.LightSource
        • RoomleConfigurator.Internal.LinearDepthRenderMaterial
        • RoomleConfigurator.Internal.NormalAndDepthRenderMaterial
        • RoomleConfigurator.Internal.ObjectRenderCache
        • RoomleConfigurator.Internal.ObjectToWallLineArray
        • RoomleConfigurator.Internal.OccurrenceMap
        • RoomleConfigurator.Internal.OutlinePass
        • RoomleConfigurator.Internal.OutlineRenderer
        • RoomleConfigurator.Internal.Panel
        • RoomleConfigurator.Internal.PassRenderer
        • RoomleConfigurator.Internal.PlanObjectList
        • RoomleConfigurator.Internal.PlanObjectPtr
        • RoomleConfigurator.Internal.PointArray
        • RoomleConfigurator.Internal.PoissonDenoiseRenderPass
        • RoomleConfigurator.Internal.PostProcessingMaterialPlugin
        • RoomleConfigurator.Internal.PromiseQueue
        • RoomleConfigurator.Internal.RenderCacheManager
        • RoomleConfigurator.Internal.RenderCacheMapItem
        • RoomleConfigurator.Internal.RenderPass
        • RoomleConfigurator.Internal.RenderPassManager
        • RoomleConfigurator.Internal.RoomLightSourceDistributionArray
        • RoomleConfigurator.Internal.RoomleWebGLRenderer
        • RoomleConfigurator.Internal.SceneEventInfo
        • RoomleConfigurator.Internal.SceneRenderPass
        • RoomleConfigurator.Internal.SceneRenderer
        • RoomleConfigurator.Internal.SceneRendererGUI
        • RoomleConfigurator.Internal.SceneVolume
        • RoomleConfigurator.Internal.ScreenSpaceShadowMapPass
        • RoomleConfigurator.Internal.SelectionHandlerEvent
        • RoomleConfigurator.Internal.ShadowAndAoPass
        • RoomleConfigurator.Internal.ShadowGroundPlane
        • RoomleConfigurator.Internal.ShadowMapPassOverrideMaterialCache
        • RoomleConfigurator.Internal.ShadowTypeConfiguration
        • RoomleConfigurator.Internal.default-1
        • RoomleConfigurator.Internal.default-10
        • RoomleConfigurator.Internal.default-11
        • RoomleConfigurator.Internal.default-12
        • RoomleConfigurator.Internal.default-13
        • RoomleConfigurator.Internal.default-14
        • RoomleConfigurator.Internal.default-15
        • RoomleConfigurator.Internal.default-16
        • RoomleConfigurator.Internal.default-17
        • RoomleConfigurator.Internal.default-18
        • RoomleConfigurator.Internal.default-19
        • RoomleConfigurator.Internal.default-2
        • RoomleConfigurator.Internal.default-20
        • RoomleConfigurator.Internal.default-21
        • RoomleConfigurator.Internal.default-22
        • RoomleConfigurator.Internal.default-23
        • RoomleConfigurator.Internal.default-24
        • RoomleConfigurator.Internal.default-25
        • RoomleConfigurator.Internal.default-26
        • RoomleConfigurator.Internal.default-27
        • RoomleConfigurator.Internal.default-28
        • RoomleConfigurator.Internal.default-29
        • RoomleConfigurator.Internal.default-3
        • RoomleConfigurator.Internal.default-30
        • RoomleConfigurator.Internal.default-31
        • RoomleConfigurator.Internal.default-32
        • RoomleConfigurator.Internal.default-33
        • RoomleConfigurator.Internal.default-34
        • RoomleConfigurator.Internal.default-35
        • RoomleConfigurator.Internal.default-36
        • RoomleConfigurator.Internal.default-37
        • RoomleConfigurator.Internal.default-38
        • RoomleConfigurator.Internal.default-39
        • RoomleConfigurator.Internal.default-4
        • RoomleConfigurator.Internal.default-40
        • RoomleConfigurator.Internal.default-41
        • RoomleConfigurator.Internal.default-42
        • RoomleConfigurator.Internal.default-43
        • RoomleConfigurator.Internal.default-44
        • RoomleConfigurator.Internal.default-45
        • RoomleConfigurator.Internal.default-46
        • RoomleConfigurator.Internal.default-47
        • RoomleConfigurator.Internal.default-48
        • RoomleConfigurator.Internal.default-49
        • RoomleConfigurator.Internal.default-5
        • RoomleConfigurator.Internal.default-50
        • RoomleConfigurator.Internal.default-51
        • RoomleConfigurator.Internal.default-52
        • RoomleConfigurator.Internal.default-53
        • RoomleConfigurator.Internal.default-54
        • RoomleConfigurator.Internal.default-55
        • RoomleConfigurator.Internal.default-6
        • RoomleConfigurator.Internal.default-7
        • RoomleConfigurator.Internal.default-8
        • RoomleConfigurator.Internal.default-9
        • RoomleConfigurator.Internal.default
        • RoomleConfigurator.default
        • ServicesConfiguratorUiCallback.Internal.AddOnSpotArray-1
        • ServicesConfiguratorUiCallback.Internal.AddOnSpotArray
        • ServicesConfiguratorUiCallback.Internal.CatalogItem
        • ServicesConfiguratorUiCallback.Internal.CatalogItemPtrList
        • ServicesConfiguratorUiCallback.Internal.ComponentArray-1
        • ServicesConfiguratorUiCallback.Internal.ComponentArray
        • ServicesConfiguratorUiCallback.Internal.ConfigurationArray-1
        • ServicesConfiguratorUiCallback.Internal.ConfigurationArray
        • ServicesConfiguratorUiCallback.Internal.ConstructionObject
        • ServicesConfiguratorUiCallback.Internal.ConstructionObjectSet
        • ServicesConfiguratorUiCallback.Internal.DockPairToLineArray-1
        • ServicesConfiguratorUiCallback.Internal.DockPairToLineArray
        • ServicesConfiguratorUiCallback.Internal.DockPairToPointArray-1
        • ServicesConfiguratorUiCallback.Internal.DockPairToPointArray
        • ServicesConfiguratorUiCallback.Internal.EMSReference
        • ServicesConfiguratorUiCallback.Internal.EmsSet
        • ServicesConfiguratorUiCallback.Internal.ExternalReference
        • ServicesConfiguratorUiCallback.Internal.Floor
        • ServicesConfiguratorUiCallback.Internal.FloorMaterial
        • ServicesConfiguratorUiCallback.Internal.IntArray-1
        • ServicesConfiguratorUiCallback.Internal.IntArray
        • ServicesConfiguratorUiCallback.Internal.LongArray
        • ServicesConfiguratorUiCallback.Internal.Node
        • ServicesConfiguratorUiCallback.Internal.ObjectGroup
        • ServicesConfiguratorUiCallback.Internal.ParamKeyValuePairArray-1
        • ServicesConfiguratorUiCallback.Internal.ParamKeyValuePairArray
        • ServicesConfiguratorUiCallback.Internal.ParameterArray-1
        • ServicesConfiguratorUiCallback.Internal.ParameterArray
        • ServicesConfiguratorUiCallback.Internal.ParameterGroupArray-1
        • ServicesConfiguratorUiCallback.Internal.ParameterGroupArray
        • ServicesConfiguratorUiCallback.Internal.ParameterValueArray-1
        • ServicesConfiguratorUiCallback.Internal.ParameterValueArray
        • ServicesConfiguratorUiCallback.Internal.PartArray-1
        • ServicesConfiguratorUiCallback.Internal.PartArray
        • ServicesConfiguratorUiCallback.Internal.PartListParameterArray-1
        • ServicesConfiguratorUiCallback.Internal.PartListParameterArray
        • ServicesConfiguratorUiCallback.Internal.Plan
        • ServicesConfiguratorUiCallback.Internal.PlanElement
        • ServicesConfiguratorUiCallback.Internal.PlanImage
        • ServicesConfiguratorUiCallback.Internal.PlanMeasure
        • ServicesConfiguratorUiCallback.Internal.PlanModelViewHelper
        • ServicesConfiguratorUiCallback.Internal.PlanObjectObjectSet
        • ServicesConfiguratorUiCallback.Internal.PlanObjectPtrList
        • ServicesConfiguratorUiCallback.Internal.PlanObjectPtrVector
        • ServicesConfiguratorUiCallback.Internal.PlanText
        • ServicesConfiguratorUiCallback.Internal.PointArray
        • ServicesConfiguratorUiCallback.Internal.PossibleChildArray-1
        • ServicesConfiguratorUiCallback.Internal.PossibleChildArray
        • ServicesConfiguratorUiCallback.Internal.RangeArray-1
        • ServicesConfiguratorUiCallback.Internal.RangeArray
        • ServicesConfiguratorUiCallback.Internal.SizeTArray
        • ServicesConfiguratorUiCallback.Internal.StringArray
        • ServicesConfiguratorUiCallback.Internal.UnitMeasureFormatter-1
        • ServicesConfiguratorUiCallback.Internal.UnitMeasureFormatter
        • ServicesConfiguratorUiCallback.Internal.VariableArray-1
        • ServicesConfiguratorUiCallback.Internal.VariableArray
        • ServicesConfiguratorUiCallback.Internal.VariantArray-1
        • ServicesConfiguratorUiCallback.Internal.VariantArray
        • ServicesConfiguratorUiCallback.Internal.Vector2fArray
        • ServicesConfiguratorUiCallback.Internal.Wall
        • ServicesConfiguratorUiCallback.Internal.WallMaterial
        • ServicesConfiguratorUiCallback.default
        • GlbViewer.GlbViewer
        • RoomleGlbViewer.GlbViewerUiCallbacks
        • RoomleGlbViewer.Internal.default-1
        • RoomleGlbViewer.Internal.default-2
        • RoomleGlbViewer.Internal.default-3
        • RoomleGlbViewer.Internal.default
        • RoomleGlbViewer.default
        • Planner.Planner
        • RoomlePlanner.Internal.PlannerSelectionHandlerEvent
        • RoomlePlanner.Internal.SceneEventInfo
        • RoomlePlanner.Internal.default-1
        • RoomlePlanner.Internal.default-10
        • RoomlePlanner.Internal.default-11
        • RoomlePlanner.Internal.default-12
        • RoomlePlanner.Internal.default-13
        • RoomlePlanner.Internal.default-14
        • RoomlePlanner.Internal.default-15
        • RoomlePlanner.Internal.default-16
        • RoomlePlanner.Internal.default-17
        • RoomlePlanner.Internal.default-18
        • RoomlePlanner.Internal.default-19
        • RoomlePlanner.Internal.default-2
        • RoomlePlanner.Internal.default-20
        • RoomlePlanner.Internal.default-21
        • RoomlePlanner.Internal.default-22
        • RoomlePlanner.Internal.default-23
        • RoomlePlanner.Internal.default-24
        • RoomlePlanner.Internal.default-3
        • RoomlePlanner.Internal.default-4
        • RoomlePlanner.Internal.default-5
        • RoomlePlanner.Internal.default-6
        • RoomlePlanner.Internal.default-7
        • RoomlePlanner.Internal.default-8
        • RoomlePlanner.Internal.default-9
        • RoomlePlanner.Internal.default
        • RoomlePlanner.default
        • RoomlePlannerUiCallback.default
        • TypingsKernel.Internal.BindingError
        • TypingsKernel.Internal.InternalError
        • TypingsKernel.Internal.UnboundTypeError
      • Enums
        • UtilsShims.WINDOWEVENT
        • Configurator.Internal.ASSETKEYS
        • Configurator.Internal.ERRORCODES
        • Configurator.Internal.PROMISECATEGORY
        • Configurator.Internal.RAPIPATHS
        • Configurator.Internal.SHORTTYPES
        • Configurator.Internal.WORKERMESSAGE
        • RoomleConfigurator.PARAMETERUPDATETYPE
        • RoomleConfigurator.Internal.BASECONTEXT
        • RoomleConfigurator.Internal.DrawingType
        • RoomleConfigurator.Internal.INPUTEVENTTYPE
        • RoomleConfigurator.Internal.INTERSECTIONMODE
        • RoomleConfigurator.Internal.KERNELTYPE
        • RoomleConfigurator.Internal.PARAMETERKERNELTYPE
        • RoomleConfigurator.Internal.RenderMode
        • RoomleConfigurator.Internal.ResizingAnchor
        • RoomleConfigurator.Internal.SELECTIONMODE
        • RoomleConfigurator.Internal.STATE-1
        • RoomleConfigurator.Internal.STATE-2
        • RoomleConfigurator.Internal.STATE
        • RoomleConfigurator.Internal.SnapMode
        • ServicesConfiguratorUiCallback.Internal.MaterialSourceType
        • ServicesConfiguratorUiCallback.Internal.PlanElementType
        • ServicesConfiguratorUiCallback.Internal.PlanObjectSide
        • ServicesConfiguratorUiCallback.Internal.Type-1
        • ServicesConfiguratorUiCallback.Internal.Type
        • ServicesConfiguratorUiCallback.Internal.Unit-1
        • ServicesConfiguratorUiCallback.Internal.Unit
        • ServicesConfiguratorUiCallback.Internal.UnitStringType-1
        • ServicesConfiguratorUiCallback.Internal.UnitStringType
        • ServicesConfiguratorUiCallback.Internal.UnitType-1
        • ServicesConfiguratorUiCallback.Internal.UnitType
        • RoomlePlanner.Internal.MODE
        • RoomlePlanner.Internal.PLANNERSCENEEVENTSTATE
        • TypingsKernel.DIMENSIONINGTYPE
        • TypingsKernel.ExportType
        • TypingsKernel.Internal.PARAMETERUNITTYPES
        • TypingsRapiTypes.MAILTYPE
        • TypingsRapiTypes.RAPIADDITIONALCONTENTS
        • TypingsRapiTypes.RAPITEXTURETYPE
        • TypingsRapiTypes.RapiFavoriteType
      • Interfaces
        • CommonInterfaces.Base64Image
        • CommonInterfaces.CanvasOffset
        • CommonInterfaces.Position2
        • CommonInterfaces.Position3
        • EnvironmentDynamicEnvironmentSettingLoader.EnvironmentDetails
        • EnvironmentDynamicEnvironmentSettingLoader.EnvironmentSetting
        • LightsettingDynamicLightSettingLoader.DynamicLight
        • LightsettingDynamicLightSettingLoader.DynamicLightSettingSource
        • SceneSettingsLoader.SceneSettings
        • UtilsShims.CommonInitData
        • UtilsShims.ConfiguratorInitData
        • UtilsShims.FeatureFlags
        • UtilsShims.GlobalInitDataDefinition
        • UtilsShims.InitDataDefinition
        • UtilsShims.PlannerInitData
        • Configurator.Internal.AppState
        • Configurator.Internal.CommunicationInterfaceCallback
        • Configurator.Internal.Context
        • Configurator.Internal.CustomShadingParameters
        • Configurator.Internal.ExternalEmbeddable
        • Configurator.Internal.ExternalObjectApiConfiguration
        • Configurator.Internal.ExternalObjectDebugConfiguration
        • Configurator.Internal.ExternalObjectMaterialConfiguration
        • Configurator.Internal.ExternalObjectUiConfiguration
        • Configurator.Internal.ExternalObjectUiSliderRange
        • Configurator.Internal.FetchOptions
        • Configurator.Internal.KernelMessageMetaInfo
        • Configurator.Internal.LifeCycleCallbacks
        • Configurator.Internal.ListenersMap
        • Configurator.Internal.NetworkRequestValidations
        • Configurator.Internal.QueuedElement
        • Configurator.Internal.RapiError
        • Configurator.Internal.RapiRelationDefinition
        • Configurator.Internal.SaveOptions
        • RoomleConfigurator.Internal.AOPassParameters
        • RoomleConfigurator.Internal.AORenderPassParameters
        • RoomleConfigurator.Internal.ActiveShadowLight
        • RoomleConfigurator.Internal.Anchor
        • RoomleConfigurator.Internal.AtticDimension
        • RoomleConfigurator.Internal.BakedGroundContactShadowConstructorParameters
        • RoomleConfigurator.Internal.BakedGroundContactShadowParameters
        • RoomleConfigurator.Internal.BlendAoAndAShadowMaterialParameters
        • RoomleConfigurator.Internal.BlendAoPassDepthMaterialParameters
        • RoomleConfigurator.Internal.CameraParameter
        • RoomleConfigurator.Internal.CameraTarget
        • RoomleConfigurator.Internal.ChildEntityMode
        • RoomleConfigurator.Internal.CleanupOptions
        • RoomleConfigurator.Internal.CommonConfiguratorKernelCallbackI
        • RoomleConfigurator.Internal.ComponentEventInfo
        • RoomleConfigurator.Internal.ConfigurationLoadedResponse
        • RoomleConfigurator.Internal.ConfiguratorDebugAPI
        • RoomleConfigurator.Internal.ConfiguratorKernelAccessCallbackI
        • RoomleConfigurator.Internal.ConfiguratorKernelCallbackI
        • RoomleConfigurator.Internal.ConfiguratorViewModelCallbackI
        • RoomleConfigurator.Internal.ContinuousDrawingManager
        • RoomleConfigurator.Internal.CopyTransformMaterialParameters
        • RoomleConfigurator.Internal.DenoisePass
        • RoomleConfigurator.Internal.DimensionDefinition
        • RoomleConfigurator.Internal.DynamicPassUpdateRequirements
        • RoomleConfigurator.Internal.EMSReference
        • RoomleConfigurator.Internal.EnvMapParams
        • RoomleConfigurator.Internal.EnvironmentMapResult
        • RoomleConfigurator.Internal.ExternalElement
        • RoomleConfigurator.Internal.ExternalMeta
        • RoomleConfigurator.Internal.ExternalObjectContour
        • RoomleConfigurator.Internal.ExternalObjectGroup
        • RoomleConfigurator.Internal.ExternalObjectGroupPosition
        • RoomleConfigurator.Internal.ExternalObjectLoadConfiguration
        • RoomleConfigurator.Internal.ExternalObjectRootModule
        • RoomleConfigurator.Internal.ExternalObjectSegment
        • RoomleConfigurator.Internal.GBufferNormalDepthMaterialParameters
        • RoomleConfigurator.Internal.GBufferParameters
        • RoomleConfigurator.Internal.GBufferRenderTargetsParameters
        • RoomleConfigurator.Internal.GBufferTextures
        • RoomleConfigurator.Internal.GLTFScene
        • RoomleConfigurator.Internal.GlobalAPI
        • RoomleConfigurator.Internal.GroundReflectionConstructorParameters
        • RoomleConfigurator.Internal.GroundReflectionIntensityMaterialParameters
        • RoomleConfigurator.Internal.GroundReflectionParameters
        • RoomleConfigurator.Internal.HomagIxArticleParams
        • RoomleConfigurator.Internal.HomagIxHeaderParams
        • RoomleConfigurator.Internal.HomagIxOrderParams
        • RoomleConfigurator.Internal.InputEventAttatchment
        • RoomleConfigurator.Internal.KernelAccessCallbackI
        • RoomleConfigurator.Internal.KernelAttic
        • RoomleConfigurator.Internal.KernelFloor
        • RoomleConfigurator.Internal.KernelFloorMaterial
        • RoomleConfigurator.Internal.KernelObject
        • RoomleConfigurator.Internal.KernelObjectConfiguration
        • RoomleConfigurator.Internal.KernelRoomWall
        • RoomleConfigurator.Internal.KernelUnitFormatter
        • RoomleConfigurator.Internal.KernelUtilityForUi
        • RoomleConfigurator.Internal.KernelWall
        • RoomleConfigurator.Internal.KernelWallMaterial
        • RoomleConfigurator.Internal.LightSourceConfiguration
        • RoomleConfigurator.Internal.Listener
        • RoomleConfigurator.Internal.ListenerCallback
        • RoomleConfigurator.Internal.ListenersMap
        • RoomleConfigurator.Internal.LutImageDefinition
        • RoomleConfigurator.Internal.LutPassParameters
        • RoomleConfigurator.Internal.MeasurementBase
        • RoomleConfigurator.Internal.MessageObject
        • RoomleConfigurator.Internal.NodePlanObject
        • RoomleConfigurator.Internal.Object3DRoomleEventMap
        • RoomleConfigurator.Internal.ObjectCacheData
        • RoomleConfigurator.Internal.ObjectCacheEntry
        • RoomleConfigurator.Internal.ObjectMeasurements
        • RoomleConfigurator.Internal.ObjectToWallLine
        • RoomleConfigurator.Internal.OffsetCamera
        • RoomleConfigurator.Internal.OrthographicOffsetCamera
        • RoomleConfigurator.Internal.OutlineParameters
        • RoomleConfigurator.Internal.OutlinePassParameters
        • RoomleConfigurator.Internal.OutlineRendererParameters
        • RoomleConfigurator.Internal.PerspectiveOffsetCamera
        • RoomleConfigurator.Internal.Plan
        • RoomleConfigurator.Internal.PlanElement
        • RoomleConfigurator.Internal.PlanInteractionHandler
        • RoomleConfigurator.Internal.PlanMeasure
        • RoomleConfigurator.Internal.PlanModelViewHelper
        • RoomleConfigurator.Internal.PlanNode
        • RoomleConfigurator.Internal.PlanObject
        • RoomleConfigurator.Internal.PlanOverview
        • RoomleConfigurator.Internal.PlannerKernelCallbackI
        • RoomleConfigurator.Internal.PluginConfigMap
        • RoomleConfigurator.Internal.PoissonDenoiseParameters
        • RoomleConfigurator.Internal.PoissonDenoisePassParameters
        • RoomleConfigurator.Internal.PrepareImageOptions
        • RoomleConfigurator.Internal.PreviewComponent
        • RoomleConfigurator.Internal.PreviewLineComponent
        • RoomleConfigurator.Internal.PreviewLineSegment
        • RoomleConfigurator.Internal.PromiseExecutor
        • RoomleConfigurator.Internal.Rectangle
        • RoomleConfigurator.Internal.RectangleReferencePoints
        • RoomleConfigurator.Internal.RenderCache
        • RoomleConfigurator.Internal.RoomLightSourceDistribution
        • RoomleConfigurator.Internal.RoomleComponent
        • RoomleConfigurator.Internal.RoomleComponentFactory
        • RoomleConfigurator.Internal.RoomleToolsCoreUICallback
        • RoomleConfigurator.Internal.RubensAPI
        • RoomleConfigurator.Internal.SavedIdbData
        • RoomleConfigurator.Internal.ScenePlugin
        • RoomleConfigurator.Internal.ScenePluginHooks
        • RoomleConfigurator.Internal.SceneRendererChangeParameters
        • RoomleConfigurator.Internal.SceneRendererParameters
        • RoomleConfigurator.Internal.ScreenSpaceShadowMapConstructorParameters
        • RoomleConfigurator.Internal.ScreenSpaceShadowMapParameters
        • RoomleConfigurator.Internal.ScriptData
        • RoomleConfigurator.Internal.ShadowAndAoPassConstructorParameters
        • RoomleConfigurator.Internal.ShadowAndAoPassParameters
        • RoomleConfigurator.Internal.ShadowAndAoPassSettings
        • RoomleConfigurator.Internal.ShadowGroundPlaneParameters
        • RoomleConfigurator.Internal.ShadowLightSource
        • RoomleConfigurator.Internal.ShadowParameters
        • RoomleConfigurator.Internal.ShadowTypeParameters
        • RoomleConfigurator.Internal.StaticComponent
        • RoomleConfigurator.Internal.SubComponentWaiter
        • RoomleConfigurator.Internal.ToolsCoreContextCallback
        • RoomleConfigurator.Internal.ToolsCoreInterface
        • RoomleConfigurator.Internal.WallSide
        • RoomleConfigurator.Internal.PassUpdateStates
        • ServicesConfiguratorUiCallback.UIComponentInfo
        • ServicesConfiguratorUiCallback.Internal.ConfiguratorDebugCallbacks
        • ServicesConfiguratorUiCallback.Internal.ConfiguratorKernelContainer
        • ServicesConfiguratorUiCallback.Internal.KernelCube
        • ServicesConfiguratorUiCallback.Internal.PlannerKernelContainer
        • ServicesConfiguratorUiCallback.Internal.WallExtensionType
        • RoomleGlbViewer.RenderEntry
        • RoomleGlbViewer.Internal.GLBRenderWorkerListener
        • RoomleGlbViewer.Internal.RenderCameraInformation
        • RoomlePlanner.BatchPaintMaterial
        • RoomlePlanner.InteractionOptions
        • RoomlePlanner.RoomlePlannerCallback
        • RoomlePlanner.SceneEvents
        • RoomlePlanner.WallDefinition
        • RoomlePlanner.Internal.ConstructionMeasurements
        • RoomlePlanner.Internal.ConstructionPlanObject
        • RoomlePlanner.Internal.ExternalObjectAPI
        • RoomlePlanner.Internal.FloorAreaData
        • RoomlePlanner.Internal.LeftOrRightWallSide
        • RoomlePlanner.Internal.LocalStorageEntry
        • RoomlePlanner.Internal.PlanObjectEventInfo
        • RoomlePlanner.Internal.PrepareImageOptionsPlanner
        • RoomlePlanner.Internal.Store
        • RoomlePlanner.Internal.WallPlanObject
        • RoomlePlannerUiCallback.SelectionPayload
        • RoomlePlannerUiCallback.Internal.ConstructionMeasurementsTransferable
        • RoomlePlannerUiCallback.Internal.ExternalObjectUiCallback
        • RoomlePlannerUiCallback.Internal.ObjectMeasurementsTransferable
        • RoomlePlannerUiCallback.Internal.PlanObjectPosition
        • TypingsKernel.AddOnSpot
        • TypingsKernel.AssetRequest
        • TypingsKernel.AssetResponse
        • TypingsKernel.AssetType
        • TypingsKernel.ConfigurationExporter
        • TypingsKernel.ConfigurationObject
        • TypingsKernel.ConfiguratorKernelClass
        • TypingsKernel.DebugClient
        • TypingsKernel.DebugValueMapChange
        • TypingsKernel.DebugValueMapDump
        • TypingsKernel.Dimensioning
        • TypingsKernel.DockLine
        • TypingsKernel.DockPair
        • TypingsKernel.DockingLineSegment
        • TypingsKernel.EmscriptenList
        • TypingsKernel.EmscriptenMap
        • TypingsKernel.ExternalAttributeInformation
        • TypingsKernel.ExternalModuleInformation
        • TypingsKernel.HomagIxArticleData
        • TypingsKernel.KernelActionValue
        • TypingsKernel.KernelAddOnSpot
        • TypingsKernel.KernelAnimation
        • TypingsKernel.KernelAnimationAction
        • TypingsKernel.KernelCatalogItem
        • TypingsKernel.KernelComponent
        • TypingsKernel.KernelComponentTypeDto
        • TypingsKernel.KernelConfiguration
        • TypingsKernel.KernelConfigurationLoadedData
        • TypingsKernel.KernelContainer
        • TypingsKernel.KernelCube
        • TypingsKernel.KernelDockPairToLine
        • TypingsKernel.KernelDockPairToPoint
        • TypingsKernel.KernelEnum
        • TypingsKernel.KernelExternalObjectDocking
        • TypingsKernel.KernelMatrix4
        • TypingsKernel.KernelMeshAnimation
        • TypingsKernel.KernelMeshAttributes
        • TypingsKernel.KernelMeshBuffer
        • TypingsKernel.KernelObjectInformation
        • TypingsKernel.KernelObjectPtrList
        • TypingsKernel.KernelParamKeyValuePair
        • TypingsKernel.KernelParameter
        • TypingsKernel.KernelParameterGroup
        • TypingsKernel.KernelParameterValue
        • TypingsKernel.KernelPart
        • TypingsKernel.KernelPartList
        • TypingsKernel.KernelPartListParameter
        • TypingsKernel.KernelPartListPrice
        • TypingsKernel.KernelPlanObject
        • TypingsKernel.KernelPlanObjectBase
        • TypingsKernel.KernelPlanObjectComponent
        • TypingsKernel.KernelPossibleChild
        • TypingsKernel.KernelRange
        • TypingsKernel.KernelValue
        • TypingsKernel.KernelVariable
        • TypingsKernel.KernelVariant
        • TypingsKernel.KernelVector2f
        • TypingsKernel.KernelVector3
        • TypingsKernel.KernelVector3f
        • TypingsKernel.KernelViewType
        • TypingsKernel.ObjectConfigurationType
        • TypingsKernel.ObjectGroupPtr
        • TypingsKernel.ParamterKeyValue
        • TypingsKernel.PlanObject
        • TypingsKernel.UiKernelParameter
        • TypingsKernel.UiKernelRange
        • TypingsKernel.UiPlanObject
        • TypingsKernel.UiPossibleChild
        • TypingsKernel.UiPossibleChildTag
        • TypingsKernel.VariantsList
        • TypingsKernel.WasmDbEntry
        • TypingsRapiTypes.AdditionalInfo
        • TypingsRapiTypes.Asset
        • TypingsRapiTypes.AssetItem
        • TypingsRapiTypes.Element
        • TypingsRapiTypes.LinksCollection
        • TypingsRapiTypes.RapiAdditionalContent
        • TypingsRapiTypes.RapiAuth
        • TypingsRapiTypes.RapiBaseColor
        • TypingsRapiTypes.RapiCatalog
        • TypingsRapiTypes.RapiComponent
        • TypingsRapiTypes.RapiConfiguration
        • TypingsRapiTypes.RapiConfigurationEnhanced
        • TypingsRapiTypes.RapiConfiguratorSettings
        • TypingsRapiTypes.RapiElement
        • TypingsRapiTypes.RapiFavorite
        • TypingsRapiTypes.RapiItem
        • TypingsRapiTypes.RapiJson
        • TypingsRapiTypes.RapiJsonBox
        • TypingsRapiTypes.RapiMaterial
        • TypingsRapiTypes.RapiMaterialGroup
        • TypingsRapiTypes.RapiMaterialShading
        • TypingsRapiTypes.RapiMesh
        • TypingsRapiTypes.RapiMeshData
        • TypingsRapiTypes.RapiMeta
        • TypingsRapiTypes.RapiPackage
        • TypingsRapiTypes.RapiPlan
        • TypingsRapiTypes.RapiPlanAsset
        • TypingsRapiTypes.RapiPlanSetting
        • TypingsRapiTypes.RapiPlanSnapshotGetData
        • TypingsRapiTypes.RapiPlanSnapshotGetDataWith3dUrls
        • TypingsRapiTypes.RapiPlanSnapshotPostData
        • TypingsRapiTypes.RapiPrice
        • TypingsRapiTypes.RapiRetailer
        • TypingsRapiTypes.RapiRole
        • TypingsRapiTypes.RapiShortId
        • TypingsRapiTypes.RapiSkin
        • TypingsRapiTypes.RapiTag
        • TypingsRapiTypes.RapiTagForUi
        • TypingsRapiTypes.RapiTagGeneric
        • TypingsRapiTypes.RapiTenant
        • TypingsRapiTypes.RapiTexture
        • TypingsRapiTypes.RapiUser
        • TypingsRapiTypes.RapiUserSetting
        • TypingsRapiTypes.RoomleSortable
        • TypingsRapiTypes.UserAction
      • Modules
        • CommonInterfaces
        • EnvironmentDynamicEnvironmentSettingLoader
        • LightsettingDynamicLightSettingLoader.Internal
        • LightsettingDynamicLightSettingLoader
        • SceneSettingsLoader
        • UtilsShims.Internal
        • UtilsShims
        • Configurator.Internal
        • Configurator
        • RoomleConfigurator.Internal
        • RoomleConfigurator
        • ServicesConfiguratorUiCallback.Internal
        • ServicesConfiguratorUiCallback
        • GlbViewer
        • RoomleGlbViewer.Internal
        • RoomleGlbViewer
        • Planner
        • RoomlePlanner.Internal
        • RoomlePlanner
        • RoomlePlannerUiCallback.Internal
        • RoomlePlannerUiCallback
        • TypingsKernel.Internal
        • TypingsKernel
        • TypingsRapiTypes
  • Rubens admin
    • Rubens Admin Help
      • Getting started
      • Dashboard
      • Catalogs
      • Tags
      • Products
      • Components
      • Meshes
      • Materials
      • Import/Export
      • Administration
    • Requirements & Sample products
      • 3D Assets Requirements
      • Material & Textures Requirements
      • Sample products and files
  • Content Creation
    • Overview
    • Material Definition
      • Ideas
      • References
      • Resources
    • Blender Addon
    • Scripting Resources
      • Environment Setup for Making Roomle Content
      • Hello World Example for Roomle Component Scripting
      • Rubens CLI Setup and Usage Tutorial
      • Using Code Snippets to Instantly Load Offline Component Definitions
      • How to Debug Content
      • Introduction to Making Level 2 Material Configurators
      • Meshes Conversion and Upload
      • Set Up Materials
      • Level 2 Component Definition
      • Set Up Product Entries
      • Level 2-4 Content Requirements for 3D Data
      • 3D Models & Meshes
      • Prerequisities for Roomle Content Scripters
      • Level 3 Component Definition Basics
      • Parameters
      • Roomle Script Language Reference
      • Example: Scripting a Table from Primitives
      • Example: Parameterized Shelf System
      • SubComponents
      • Basic Docking Topics
      • Global Parameters and Parameter Context
      • Advanced Part List Topics
      • Dimensioning
      • Pricing
      • Advanced Docking Logic
      • Using GetMaterialPropery Function
      • Tenant Settings
      • Roomle Content Project Data Structure
      • Roomle Content Naming Conventions
      • Processes and Good Practices
      • Real Configurator Examples - Chairs, Armchairs, Footstools
      • Simple Colour Changing Product Scripting Example
      • 4-Post Shelving System Example
      • Office Table System Example
      • Scripting Template: Complex Sofa
      • Scripting Template: Two-Part Sofa System
      • Scripting Template: Simple Sofa System
      • Scripting Template: Wardrobe
      • Processing Meshes with Blender
    • Importer
    • IDM
      • About IDM
      • Data format
      • Extension files
      • Conversion
      • Docking logic
      • Part lists
      • Price logic
    • RoomleScript Reference
      • Configurationformat
      • Configuration and Plan
      • Error code list
      • Configurator Features
      • Configurator Script
      • Coordinate Systems
  • WHATS NEW?
    • 2025
      • May 2025
      • April 2025
      • March 2025
      • February 2025
      • January 2025
    • 2024
      • December 2024
      • November 2024
      • October 2024
      • September 2024
      • August 2024
      • July 2024
      • June 2024
      • May 2024
      • April 2024
      • March 2024
  • REST API
    • REST API Reference
      • RAPI Documentation
      • Webhook
      • Endpoints
        • AssetController
        • Authentication
        • CatalogController
        • CatalogElementAdditionalInfo
        • ComponentController
        • ConfigurationController
        • ConfiguratorController
        • Item
        • MaterialController
        • MeshController
        • PlanController
        • PlanSnapshotController
        • PriceController
        • PriceServiceController
        • ShortIdController
        • Skin
        • Statistics
        • TagController
        • Tenant
        • TextureController
        • User
        • UserSettings
    • Product Matching
  • Changelogs
    • Rubens Configurator Changelog
    • Rubens SDK Changelog
    • Rubens Admin Changelog
    • Material Definition Changelog
Powered by GitBook
On this page
  • Utilizing connection.isPreview Check
  • Storing Data in the connection Context in assignmentScripts
  • Sibling Points
  • Example: Grid Shelf System
  • Example: Deciding Which of the Neighbours Should Draw a Post in the 4 Post Shelf System
  • Docking Ranges
  • Limiting Amount of Children Docked in a Range
  • Getting Connection Index in a 1D Range
  • Getting Connection Index in a 2D or 3D Range
  • Docking Range Examples
  • Collision Detection
  • Example: Using Assignment Scripts in Docking Ranges to Prevent Collisions in Wardrobe Equipment
  • Using collisionCondition
  1. Content Creation
  2. Scripting Resources

Advanced Docking Logic

PreviousPricingNextUsing GetMaterialPropery Function

Last updated 4 months ago

This chapter requires the reader to have already an understanding of the Roomle platform and to have some experience with scripting, including topics described in the chapter. Topics described in this chapter are ones of the most complex you can achieve in the Roomle platform.

Utilizing connection.isPreview Check

In the docking points context, a boolean getter connection.isPreview can be utilized. Usually the condition updates itself in every update loop. If the condition uses extensive computations, like searching through many arrays slowing down the configuration, you can utilize a pattern like the one following:

{
    "mask": "shelves",
    "position": "{ 100, 200, 300}",
    "condition": "
        if (connection.isPreview) {
            /* 
            adding a new add-on, preview phase
            compute whethet it fits
            */
        } else {
            /* already docked, keep it */
            condition = true;
        }
    "
}

You check in the preview phase whether the addon fits. You prevent the situation where you have an illogical configuration, because it won't delete the child as if you were using the condition in the common way. However, you can still add some computations whether it is still valid. Example: you have a docking range of shelves inside a wardrobe. They must be placed at least 5 docking positions from each other, which is something you would do in the conneciton.isPreview == true branch, because every docking point in the range cycles through an array in 10 indices and it would be expensive to compute the conditions in every update loop. But you can get to a situation where you change the wardrobe height from 2500 to 1600 mm, therefore you need to delete the shelves that go through the wardrobe ceiling. In this case, you can of course use the connection.isPreview == false branch as in any other script.

if (connection.isPreview) {
    /* 
    for (from position - 5 to position + 5) - check if there is a shelf docked
    Note: we will show this further in this chapter
    */
} else {
    _.positionZ = zFromVector(connection.position);
    condition = dockRangeHeight > _.positionZ;
}

Storing Data in the connection Context in assignmentScripts

In cases where you need to compute a value once in the first time and you are certain that you do not need to recompute it later, you can compute it only in onDock and retrieve its value in both onUpdate and onUnDock scripts. See following example:

"assignmentScripts": {
    "onDock": "
        _.i = round(xFromVector(connection.position) / stepX, 0);
        _.j = round(yFromVector(connection.position) / stepY, 0);
        connection._index = _.i * self.maxX + _.j;
        set(self.dockedWidths, connection._index, other.width);
    ",
    "onUpdate": "
        if (connection._index >= 0) {
            set(self.dockedWidths, connection._index, other.width);
        }
    ",
    "onUnDock": "
        if (connection._index >= 0) {
            set(self.dockedWidths, connection._index, 0);
        }
    "
}

Sibling Points

Up until now, you knew how to detect a neighbour if it has been docked via the docking points. This is a perfectly possible and recommended way to detect neighbours, as long as your product docks in a single line. If you can fork the abstract connecting line, you can end up in loops or parallel configurations, where you need to detect what is next to the current component and eventually transfer data. This is a common topic in shelf systems or also in docking ranges. To directly communicate with a neighbouring element, you can use the Sibling Points scripting feature, where you define a connection point in one or more components at a given position with a mask. If there are two siblings points in one place with matching masks, they connect together and standard assignments, as you already know them, ensure the ability to share data between the components in the configuration regardless of their position in the parent-child hierarchy.

{
    "id":"example:siblings",
    ...
    "siblings": [
        {
            "mask": "horizontalSibling",
            "position": "{ -width / 2, 0, 100}",
            "rotation": "{0, 0, 0}",
            "selfAssignments": {}
        },
        {
            "mask": "horizontalSibling",
            "position": "{width / 2, 0, 100}",
            "rotation": "{0, 0, 0}",
            "selfAssignments": {}
        }
    ]
}

Note: The siblings arrtibute of type List<ConnectionWithAssignment>. Docking points inherits ConnectionWithAssignments and adds a condition and rotation. Therefore, siblings points have neither condition nor rotation.

Note: You do not have a (direct) possibility to know what component is on the other side of the sibling point, neither can you get data from the other sibling point. Therefore, you need to rather pull the data from the other side than to push it and use the pulled data to compute what you need afterwards. Therefore we recommend using selfAssignments or assignmentScripts where you assign to self. values based on the other. values.

Example: Grid Shelf System

We start from the logic implementation, using abstract geometry in order not to overwhelm the script from the beginning. We start with visualizing the blank spaces and preparing the docking points.

Unfold to see the component definition
{
    "id": "usm:frame",
    "parameters": [
        {
            "key": "width",
            "labels": {
                "de": "Breite",
                "en": "Width"
            },
            "type": "Decimal",
            "unitType": "length",
            "defaultValue": 750,
            "validValues": [
                350,
                395,
                500,
                750
            ],
            "visible": "true"
        },
        {
            "key": "depth",
            "sort": 10,
            "global": true,
            "labels": {
                "de": "Tiefe",
                "en": "Depth"
            },
            "type": "Decimal",
            "unitType": "length",
            "defaultValue": 350,
            "validValues": [
                350,
                500
            ],
            "visible": false
        },
        {
            "sort": 10,
            "key": "height",
            "labels": {
                "de": "Höhe",
                "en": "Height"
            },
            "type": "Decimal",
            "unitType": "length",
            "defaultValue": 350,
            "validValues": [
                100,
                175,
                250,
                350,
                395
            ],
            "visible": "true"
        }
    ],
    "onUpdate": "
        if (ifnull(inited, false) == false) {
            inited = true;
            isRoot = true;
        }
    ",
    "geometry": "
        if (isRoot) {
            coordSystemAxesLength = 200;
            coordSystemAxesThickness = 10;
            BeginObjGroup();
                AddPlainCube(Vector3f{coordSystemAxesThickness, coordSystemAxesThickness, coordSystemAxesLength}); SetObjSurface('demoCatalogId:test_crazy_gree');
                AddPlainCube(Vector3f{coordSystemAxesThickness, coordSystemAxesLength, coordSystemAxesThickness}); SetObjSurface('demoCatalogId:cyan');
                AddPlainCube(Vector3f{coordSystemAxesLength, coordSystemAxesThickness, coordSystemAxesThickness}); SetObjSurface('demoCatalogId:red');
            EndObjGroup();
        }
        AddCube(Vector3f{width, depth, height});
         MoveMatrixBy(Vector3f{ -width / 2, 0, 0});
         SetObjSurface('isdt:black_transparent');
    ",
    "parentDockings": {
        "points": [
            {
                "mask": "gridLeft",
                "position": "{ -width / 2, 0, 0}",
                "rotation": "{0, 0, 0}",
                "condition": "true"
            },
            {
                "mask": "gridRight",
                "position": "{width / 2, 0, 0}",
                "rotation": "{0, 0, 0}",
                "condition": "true"
            },
            {
                "mask": "gridTop",
                "position": "{0, 0, height}",
                "rotation": "{0, 0, 0}",
                "condition": "true"
            }
        ]
    },
    "childDockings": {
        "points": [
            {
                "mask": "gridLeft",
                "position": "{width / 2, 0, 0}",
                "rotation": "{0, 0, 0}",
                "condition": "true",
                "selfAssignments": {
                    "onDock": {
                        "isRoot": false
                    },
                    "onUnDock": {
                        "isRoot": true
                    }
                }
            },
            {
                "mask": "gridRight",
                "position": "{ -width / 2, 0, 0}",
                "rotation": "{0, 0, 0}",
                "condition": "true",
                "selfAssignments": {
                    "onDock": {
                        "isRoot": false
                    },
                    "onUnDock": {
                        "isRoot": true
                    }
                }
            },
            {
                "mask": "gridTop",
                "position": "{0, 0, 0}",
                "rotation": "{0, 0, 0}",
                "condition": "true",
                "selfAssignments": {
                    "onDock": {
                        "isRoot": false
                    },
                    "onUnDock": {
                        "isRoot": true
                    }
                }
            }
        ]
    },
    "possibleChildren": [
        {
            "componentId": "usm:frame"
        }
    ]
}

We already show a coordinate system axes in the root component - see figure above. You can understand from the code, that only the top-level parent has the isRoot variable set to true. Any other component has it set to false. In order to be less error-prone, we add visualization of the sibling points. We will use spheres with a diameter of 50 units, and we will also detect using sibling points, if there is a neighbour in the respective direction. Therefore, we add some values to the onUpdate - inited block:

hasLeftNeighbour = false;
hasRightNeighbour = false;
hasTopNeighbour = false;
hasBottomNeighbour = false;

Which we visualize in the geometry and colour them in red if they are not connected, green when they are connected.

AddSphere(Vector3f{50, 50, 50});
 MoveMatrixBy(Vector3f{ -width / 2 + 25, 0, 50});
if (hasLeftNeighbour) {
    SetObjSurface('isdt:green');
} else {
    SetObjSurface('isdt:red');
}

AddSphere(Vector3f{50, 50, 50});
 MoveMatrixBy(Vector3f{width / 2 - 25, 0, 50});
if (hasRightNeighbour) {
    SetObjSurface('isdt:green');
} else {
    SetObjSurface('isdt:red');
}

AddSphere(Vector3f{50, 50, 50});
 MoveMatrixBy(Vector3f{0, 0, height - 25});
if (hasTopNeighbour) {
    SetObjSurface('isdt:green');
} else {
    SetObjSurface('isdt:red');
}

AddSphere(Vector3f{50, 50, 50});
 MoveMatrixBy(Vector3f{0, 0, 25});
if (hasBottomNeighbour) {
    SetObjSurface('isdt:green');
} else {
    SetObjSurface('isdt:red');
}

Now the most important part: The sibling points themselves:

{
    "mask": "horizontalSibling",
    "position": "{ -width / 2, 0, 50}",
    "selfAssignments": {
        "onDock": {
            "hasLeftNeighbour": true
        },
        "onUnDock": {
            "hasLeftNeighbour": false
        }
    }
},
{
    "mask": "horizontalSibling",
    "position": "{width / 2, 0, 50}",
    "selfAssignments": {
        "onDock": {
            "hasRightNeighbour": true
        },
        "onUnDock": {
            "hasRightNeighbour": false
        }
    }
},
{
    "mask": "verticalSibling",
    "position": "{0, 0, height}",
    "selfAssignments": {
        "onDock": {
            "hasTopNeighbour": true
        },
        "onUnDock": {
            "hasTopNeighbour": false
        }
    }
},
{
    "mask": "verticalSibling",
    "position": "{0, 0, 0}",
    "selfAssignments": {
        "onDock": {
            "hasBottomNeighbour": true
        },
        "onUnDock": {
            "hasBottomNeighbour": false
        }
    }
}

If you have two of those components docked, they will always match with the sibling points. Notice that the horizontal sibling points are not in the corners, but at the height of 50. This way, you can be sure that you won't connect with a component diagonally (there would have to be sibling points in the upper corner as well, but it is more understandable). In the current state, there are sibling points where their connections are visualized using the debug spheres:

Because we now have all the neccessary data in the component regarding what can fit where, we can now add parent-side conditions. The simplest docking pattern in such shelf systems is a "pitchfork-like" hierarchy - only the components that are on the bottom can dock to the left and right, while all can dock in the vertical up direction. Therefore, the conditions will be:

(!hasBottomNeighbour) && ((!connection.isPreview) || (!hasLeftNeighbour)) for the left docking point (!hasBottomNeighbour) && ((!connection.isPreview) || (!hasRightNeighbour)) for the right docking point

!hasBottomNeighbour Implicates this is the bottom element -> therefore it even should have the left and right docking points. (!connection.isPreview) || (!hasLeftNeighbour) Allows docking as long as something is docked on the left side. However, after docking, this will immediately delete the docked child. Therefore this check needs to be there only in preview.

A more simple-to-understand version of above:

if (connection.isPreview) {
    if (hasBottomNeighbour) {
        condition = false;
    } else {
        condition = hasLeftNeighbour /* or hasRightNeighbour */
    }
} else {
    condition = true;
}

Now the time comes to the geometry. The shelf system consists of pipes and connecting heads that connected together form frames. Into these frames, walls, doors, floors, trays and other parts can be mounted. The heads have 5 holes, allowing to screw the pipes together. Because the heads can be shared among up to 4 components, we have to define a rule which of the components will draw the head. Also, the frames on the bottom have legs. Check out subComponent definitions from the USM shelf system.

Unfold subComponent definitions

{ "internalId": "HEAD", "componentId": "usm:head", "numberInPartList": "1" }, { "internalId": "PIPE_HORIZONTAL", "componentId": "usm:pipe", "assignments": { "length": "width" }, "numberInPartList": "1" }, { "internalId": "PIPE_VERTICAL", "componentId": "usm:pipe", "assignments": { "length": "height" }, "numberInPartList": "1" }, { "internalId": "PIPE_FORWARD", "componentId": "usm:pipe", "assignments": { "length": "depth" }, "numberInPartList": "1" }, { "internalId": "FOOT", "componentId": "usm:levelingfoot", "numberInPartList": "1" }

The task now is to define which of the neighbours draws which heads, which pipes etc. Before you read further, please try to yourself define a ruleset, which will ensure that all parts will be there once and only once. Hint: we've already got more than enough data in the hasLeftNeighour, hasRightNeighbour, hasBottomNeighbour and hasTopNeighbour parameters.

In our example we follow with building the ruleset in a way that we first build the component that is most to the left on the bottom. If add another component to the right, it will be missing its left parts. If we build upwards, the bottom will be missing. If we are filling a top-right corner, where there is the left and the right neighbour, the top and right parts will be present. Therefore we can say that we always have the two top pipes, two right pipes and the two top-right heads. Left top heads, and left pipes are there when !hasLeftNeighbour. Bottom heads, legs and pipes are there if !hasBottomNeighbour. The bottom left parts are there whenever there is no bottom or left neighbour -> (!hasLeftNeighbour) && (!hasRightNeighbour)

We prepare logical variables for this in onUpdate in order to make the code more legible. These computations are really simple and might seem trivial. However, an uninitiated person will read that the geometry and part list counts depend on whether the component HAS the parts and the component has the parts as long as there are no neighbours, making the code provide the answer to the question: "Why does it have the parts?"

hasBottomParts = !hasBottomNeighbour;
hasLeftParts = !hasLeftNeighbour;
hasBottomLeftParts = hasBottomParts && hasLeftParts;

Therefore we can build the geometry:

if (hasLeftParts) {
    BeginObjGroup();
        SubComponent('PIPE_VERTICAL');
        SubComponent('HEAD');
         MoveMatrixBy(Vector3f{0, 0, height});
    EndObjGroup();
     MoveMatrixBy(Vector3f{ -width / 2, 0, 0});
    Copy();
     MoveMatrixBy(Vector3f{0, depth, 0});
    SubComponent('PIPE_FORWARD');
     RotateMatrixBy(Vector3f{1, 0, 0}, Vector3f{0, 0, 0}, -90);
     MoveMatrixBy(Vector3f{ -width / 2, 0, height});
}
if (hasBottomParts) {
    BeginObjGroup();
        SubComponent('HEAD');
        SubComponent('PIPE_HORIZONTAL');
         RotateMatrixBy(Vector3f{0, 1, 0}, Vector3f{0, 0, 0}, -90);
        SubComponent('FOOT');
    EndObjGroup();
     MoveMatrixBy(Vector3f{width / 2, 0, 0});
    Copy();
     MoveMatrixBy(Vector3f{0, depth, 0});
    SubComponent('PIPE_FORWARD');
     RotateMatrixBy(Vector3f{1, 0, 0}, Vector3f{0, 0, 0}, -90);
     MoveMatrixBy(Vector3f{width / 2, 0, 0});
}
if (hasBottomLeftParts) {
    SubComponent('HEAD');
     MoveMatrixBy(Vector3f{ -width / 2, 0, 0});
    Copy();
     MoveMatrixBy(Vector3f{0, depth, 0});
     SubComponent('FOOT');
     MoveMatrixBy(Vector3f{-width / 2, 0, 0});
     Copy();
     MoveMatrixBy(Vector3f{0, depth, 0});
    SubComponent('PIPE_FORWARD');
     RotateMatrixBy(Vector3f{1, 0, 0}, Vector3f{0, 0, 0}, -90);
     MoveMatrixBy(Vector3f{ -width / 2, 0, 0});
}
BeginObjGroup();
    SubComponent('HEAD');
     MoveMatrixBy(Vector3f{width / 2, 0, height});
    SubComponent('PIPE_HORIZONTAL');
     RotateMatrixBy(Vector3f{0, 1, 0}, Vector3f{0, 0, 0}, 90);
     MoveMatrixBy(Vector3f{ -width / 2, 0, height});
    SubComponent('PIPE_VERTICAL');
     MoveMatrixBy(Vector3f{width / 2, 0, 0});
EndObjGroup();
Copy();
 MoveMatrixBy(Vector3f{0, depth, 0});
SubComponent('PIPE_FORWARD');
 RotateMatrixBy(Vector3f{1, 0, 0}, Vector3f{0, 0, 0}, -90);
 MoveMatrixBy(Vector3f{width / 2, 0, height});

The part list counts (numberInPartList expressions):

PIPE_FORWARD: (1 + hasBottomParts + hasLeftParts + hasBottomLeftParts) - top right always, then based on the rest parameters HEAD: 2 * (1 + hasBottomParts + hasLeftParts + hasBottomLeftParts) - like the forward pipes, but two times PIPE_HORIZONTAL: 2 * (1 + hasBottomParts) - top, then optionally bottom PIPE_VERTICAL: 2 * (1 + hasLeftParts) - right, then optionally left FOOT: 2 * (1 + hasBottomLeftParts) - right, then optionally left


In order to add the infills, walls etc., there are more possibilities. Every floor or wall could be docked. In such a case, it would be sometimes hard to dock the components, for example to dock floors if there are already all four walls docked. Also it would have been more suitable to compute the above variables not from left to right, but from child to parent. The reason for this might not be clear at first thought: if you delete a shelf on the left, also the left wall deletes. You would need the isLeft/RightChild logic for this.

We'll choose the approach that the configurator end-user docks ready-made assemblies together, which will compute which of the components will draw which parts of the accessories. We keep the logic the same: if the left or lower component already has the part, do not add it anymore.

We start with the variables driving the logic:

  • add those to onUpdate - init block:

hasLeftWall = true;
hasRightWall = true;
hasCeiling = true;
hasBottom = true;
hasLeftPanel = true;
hasRightPanel = true;
hasTopPanel = true;
hasBottomPanel = true;
leftNeighbourHasRightPanel = false;
bottomNeighbourHasTopPanel = false;
  • sibling points assignmentScripts:

left sibling point:

"assignmentScripts": {
    "onUpdate": "self.leftNeighbourHasRightPanel = other.hasRightPanel;",
    "onUnDock": "self.leftNeighbourHasRightPanel = false;"
}

bottom sibling point:

"assignmentScripts": {
    "onUpdate": "self.bottomNeighbourHasTopPanel = other.hasTopPanel;",
    "onUnDock": "self.bottomNeighbourHasTopPanel = false;"
}
  • subComponent definition for USM's wall panel:

{
    "internalId": "PANEL",
    "componentId": "usm:metalelement",
    "assignments": {
        "width": [350, 395, 750 ], 
        "height":[100, 175, 250, 350, 395, 500],
        "colorMaterial": Material,
        "perforated": bool
    }
}

Those need to be there for all 5 walls except for the front:

  • colour material selector - simply global for now:

{
    "key": "material",
    "global": true,
    "labels": {
        "de": "Verkleidungsfarbe",
        "en": "Color"
    },
    "type": "Material",
    "defaultValue": "usm:RAL9010",
    "validGroups": [
        "usm:metalcolors",
    ],
    "visible": "true"
}
  • Change element type parameter

{
    "key": "elementType",
    "defaultValue": "no_front",
    "enabled": true,
    "labels": {
        "de": "Fachtyp ändern",
        "en": "Change elementtype"
    },
    "type": "String",
    "valueObjects": [
        {
            "value": "no_front",
            "labels": {
                "en": "Without Doors",
                "de": "Ohne Tür"
            },
            "thumbnail": "https://storage.googleapis.com/roomle-catalogs/1e9dbe16-bb11-446a-a28d-1cc42a3c16e4/thumbnails/parameters/elementtype/usm_3.png"
        },
        {
            "value": "onlyTop",
            "labels": {
                "en": "Top only",
                "de": "Ohne Seitenwände"
            },
            "thumbnail": "https://storage.googleapis.com/roomle-catalogs/1e9dbe16-bb11-446a-a28d-1cc42a3c16e4/thumbnails/parameters/elementtype/onlyTop.png"
        },
        {
            "value": "skeleton",
            "labels": {
                "en": "Without walls",
                "de": "Ohne Wände"
            },
            "thumbnail": "https://storage.googleapis.com/roomle-catalogs/1e9dbe16-bb11-446a-a28d-1cc42a3c16e4/thumbnails/parameters/elementtype/skeleton.png"
        }
    ],
    "visible": true
}

We have the elementType parameter which defines whether the current component should have the walls in the first place. We need to combine it with properties from the left and bottom neighbours: if they have the right or top panel, do not draw it regardless whether the current component should have them or not - similarily as with the parts.

hasOwnLeftPanel = (!leftNeighbourHasRightPanel) && (in(elementType, 'no_front'));
hasOwnRightPanel = in(elementType, 'no_front');
hasOwnBottomPanel = (!bottomNeighbourHasTopPanel) && (in(elementType, 'no_front'));
hasOwnTopPanel = in(elementType, 'no_front', 'onlyTop');
hasOwnRearPanel = in(elementType, 'no_front');

Therefore, we can draw the geometry:

if (hasOwnRearPanel) {
    SubComponent('PANEL_REAR');
        MoveMatrixBy(Vector3f{ -width / 2, 0, 0});
        RotateMatrixBy(Vector3f{1, 0, 0}, Vector3f{0, 0, 0}, -90);
}
if (hasOwnLeftPanel) {
    SubComponent('PANEL_LEFT');
        RotateMatrixBy(Vector3f{1, 0, 0}, Vector3f{0, 0, 0}, -90);
        RotateMatrixBy(Vector3f{0, 0, 1}, Vector3f{0, 0, 0}, 90);
        MoveMatrixBy(Vector3f{ -width / 2, 0, 0});
}
if (hasOwnRightPanel) {
    SubComponent('PANEL_RIGHT');
        RotateMatrixBy(Vector3f{1, 0, 0}, Vector3f{0, 0, 0}, -90);
        RotateMatrixBy(Vector3f{0, 0, 1}, Vector3f{0, 0, 0}, 90);
        MoveMatrixBy(Vector3f{width / 2, 0, 0});
}
if (hasOwnBottomPanel) {
    SubComponent('PANEL_BOTTOM');
        MoveMatrixBy(Vector3f{ -width / 2, depth, 0});
}
if (hasOwnTopPanel) {
    SubComponent('PANEL_TOP');
        MoveMatrixBy(Vector3f{ -width / 2, depth, height});
}

Note: Look at how simply the geometry works with using the widths and depths. The panels are actually smaller than those values with their pivot slightly off the panels - everything is set up to match the reference dimensions.

SubComponent's numberInPartList definitions are as simple as:

PANEL_BOTTOM - hasOwnBottomPanel PANEL_TOP - hasOwnTopPanel PANEL_LEFT - hasOwnLeftPanel PANEL_RIGHT - hasOwnRightPanel PANEL_REAR - hasOwnRearPanel

We've come to another problem with this: the colour definition of the individual panels. Because the parameter is both global and local, and some panels are in one component and some another, you can not define which panels exactly you wish to have in which colours. We will utilize the sibling points' assignments to solve this.

We will solve this by adding following parameters: material_rear, material_left, material_right, material_top, material_bottom, which will be bound together using assignments in the sibling points:

  1. Add the material definitions - just copy the parameters, with a different key. Also the parameters will be displayed based on the value of elementType variable and only the rear will be global:

Unfold to see the parameters code
{
    "key": "material",
    "global": true,
    "labels": {
        "de": "Verkleidungsfarbe",
        "en": "Color"
    },
    "type": "Material",
    "defaultValue": "usm:RAL9010",
    "validGroups": [
        "usm:metalcolors"
    ],
    "visible": "true",
    "onValueChange": "
        if (parameter.userTriggeredChange) {
            material_rear = material;
            material_left = material;
            material_right = material;
            material_bottom = material;
            material_top = material;
        }
    "
},
{
    "key": "material_rear",
    "global": true,
    "labels": {
        "de": "Verkleidungsfarbe - hinten",
        "en": "Color - rear"
    },
    "type": "Material",
    "defaultValue": "usm:RAL9010",
    "validGroups": [
        "usm:metalcolors"
    ],
    "visible": "in(elementType, 'no_front')",
    "visibleAsGlobal": "in(elementType, 'no_front')"
},
{
    "key": "material_left",
    "labels": {
        "de": "Verkleidungsfarbe - links",
        "en": "Color - left"
    },
    "type": "Material",
    "defaultValue": "usm:RAL9010",
    "validGroups": [
        "usm:metalcolors"
    ],
    "visible": "in(elementType, 'no_front')"
},
{
    "key": "material_right",
    "labels": {
        "de": "Verkleidungsfarbe - rechts",
        "en": "Color - right"
    },
    "type": "Material",
    "defaultValue": "usm:RAL9010",
    "validGroups": [
        "usm:metalcolors"
    ],
    "visible": "in(elementType, 'no_front')"
},
{
    "key": "material_top",
    "labels": {
        "de": "Verkleidungsfarbe - oben",
        "en": "Color - top"
    },
    "type": "Material",
    "defaultValue": "usm:RAL9010",
    "validGroups": [
        "usm:metalcolors"
    ],
    "visible": " in(elementType, 'no_front', 'onlyTop')"
},
{
    "key": "material_bottom",
    "labels": {
        "de": "Verkleidungsfarbe - unten",
        "en": "Color - bottom"
    },
    "type": "Material",
    "defaultValue": "usm:RAL9010",
    "validGroups": [
        "usm:metalcolors"
    ],
    "visible": "in(elementType, 'no_front')"
}
  1. Assign the side-relevant materials to the respective subComponents

PANEL_BOTTOM - "colorMaterial": "material_bottom" PANEL_TOP - "colorMaterial": "material_top" PANEL_LEFT - "colorMaterial": "material_left" PANEL_RIGHT - "colorMaterial": "material_right" PANEL_REAR - "colorMaterial": "material_rear"

  1. Add onValueChange script to the material parameter

if (parameter.userTriggeredChange) {
    material_rear = material;
    material_left = material;
    material_right = material;
    material_bottom = material;
    material_top = material;
}
  1. Add assignments into the siblings points:

left - "assignmentsOnUpdateSilent": { "material_right": "material_left" } right - "assignmentsOnUpdateSilent": { "material_left": "material_right" } top - "assignmentsOnUpdateSilent": { "material_bottom": "material_top" } bottom - "assignmentsOnUpdateSilent": { "material_top": "material_bottom" }

This will ensure that all the materials will propagate the respective panels. If someone wants a 2x2 shelf with the rear panels in mustard, bottom panels in dark grey, panels of the top-left element in red and the rest in white, it can be achieved:

Example: Deciding Which of the Neighbours Should Draw a Post in the 4 Post Shelf System

Docking Ranges

"parentDockings": {
    "ranges": [
        {
            "mask": "mask",
            "position": "{0, 0, 0}",
            "stepEnd": "{0, 0, 1000}",
            "rotation": "{0, 0, 0}",
            "condition": "true",
            "stepX": 0,
            "stepY": 0,
            "stepZ": 100
        }
    ]
}

Ranges can be 1, 2, or 3 dimensional. The range above is a 1D range, with a total of 11 docking points between 0 and 1000 on the Z-axis, with a step of 100 in between. The range is axis-aligned in the coordinate system of its component. If you need 2D range, you must have two delta values between stepEnd and position and two of stepX, stepY, stepZ defined.

Limiting Amount of Children Docked in a Range

Docking range has no native way of limiting how many children are possible to be docked to it. You must write your own logic to achieve this. Examples how to achieve this are folded below.

Unfold to see template for given number of docked children.
"onUpdate":"
    if (ifnull(inited, false) == false) {
        inited = true;
        countMaskChildrenDocked = 0;
        limitMaskChildrenDocked = 5;
    }
",

...

{
    "mask": "mask",
    "position": "{0, 0, 0}",
    "stepEnd": "{0, 0, 1000}",
    "condition": "
        if (connection.isPreview) {
            condition = countMaskChildrenDocked < limitMaskChildrenDocked;
        } else {
            condition = true;
        }
    ",
    "stepZ": 100,
    "selfAssignments": {
        "onDock": {
            "countMaskChildrenDocked": "countMaskChildrenDocked + 1"
        },
        "onUnDock": {
            "countMaskChildrenDocked": "countMaskChildrenDocked - 1"
        }
    }
}
Simplification for maximum of one child in the range.
"onUpdate":"
    if (ifnull(inited, false) == false) {
        inited = true;
        maskChildDocked = false;
    }
"

...

{
    "mask": "mask",
    "position": "{0, 0, 0}",
    "stepEnd": "{0, 0, 1000}",
    "condition": "condition = (!maskChildDocked) || (!connection.isPreview)",
    "stepZ": 100,
    "selfAssignments": {
        "onDock": {
            "maskChildDocked": true
        },
        "onUnDock": {
            "maskChildDocked": false
        }
    }
}

Note: Although DockingPointRange is dependent on ConnectionWithAssignments, which has maxConnections, this is not useful for ranges, because maxConnections apply to a single point, not a range of the points.

Getting Connection Index in a 1D Range

You can use the connection.index getter in most cases. If your range's position changes, you might need to compute it from the connection.position using following formula:

i = round(xFromVector(connection.position) / stepX, 0);

Be aware, that if you change the range's position, it will not cut the first position of the range, but move them (cutting the last positions instead). If this is unintended, the range should not move, but the first elements should still delete, consider following (at cost of some possible, but usally small, performance decrease):

  1. keep the position and stepEnd constant (not in means of position, but in means of length).

  2. disable the relevant docking points in the condition instead: condition = connection.index >= dockRange<MaskName>_startIndex && connection.index < dockRange<MaskName>_endIndex;, while defining dockRange<MaskName>_startIndex and dockRange<MaskName>_endIndex in onUpdate.

Getting Connection Index in a 2D or 3D Range

To get i, j, k coordinates from the range, you can:

i = round(xFromVector(connection.position) / stepX, 0);
j = round(yFromVector(connection.position) / stepY, 0);
k = round(zFromVector(connection.position) / stepZ, 0);

If you have a 1D docking range, you can easily use the connection.index. However, in 2D or 3D arrays, it is more advantageous to compute it from the coordinates on your own, so that you have more control over it, should the docking grid dimension change and also that you're certain in which order of the directions the indices are added. If you have a backing 1D array behind the range, where every field of the array corresponds to one docking point of the range, you can then:

/* 1D array */
index = connection.index;
/* or */
index = i;

/* 2D array */
index = i * maxX + j;

/* 3D array */
index = (i * maxX + j) * maxY + k;

Warning: If you need the value in condition too, you must compute it BOTH in assignmentScripts.onDock AND condition.

Recommendation: Store the values in the connection. context, e.g.:

connection.i = round(xFromVector(connection.position) / stepX, 0) * self.maxX + round(yFromVector(connection.position) / stepY, 0)

Docking Range Examples

Collision Detection

There are two ways of collisions detection in the Roomle Rubens Configurator. One is by using assignments that will store information about what is docked to what and if another part would collide or fit. The second way is to use the collisionCondition script of the parent dockings.

Example: Using Assignment Scripts in Docking Ranges to Prevent Collisions in Wardrobe Equipment

When using docking ranges, collisions can be prevented by tracking information about which points of the range are occupied in an array. Every field of such an array represents the status of every docking point. The array is set up in the way, that the index of the connection matches the array index. In 1D ranges, you can use the connection.index directly, in 2D or 3D docking arrays, the method to compute the index by yourself is recommended.

In this example, we're going to create a docking range for internal equipment of a wardrobe, that has a vertical array of mounting points - standard drill holes every 5 cm, as is usual in wardrobes. We will dock a shelf, a clothes rod and an internal drawer.

We will not cover parameter, geometry and part list creation of the wardrobe in this example, but you can check it yourself. We will focus on the docking instead. First, the accessory component or components are prepared. We have chosen an "element type" design pattern for this. The reason behind is that the parent docking side will need some data from the child component, effectively requiring what could be called an interface. If there are too many accessories, from which some would provide further features, like further docking, it would make sense to split to multiple components. The second reason for using the "element type" pattern is that there is only geometry and articleNr provided by the different element types, without any further logic.

Important to say is that the shelf, the clothes rod and the drawer all take a different amount of space, meaning they occcupy a different amount of docking points. The shelf is flat and fits to a single hole. You could place shelves in every point, but in reality, that doesn't make sense, because the space between the shelf would not be useful. Therefore, we define that we occupy more docking points than it is physically necessary. The drawer has some height and physically occupies more space. Drawers do not need extra clearance above them, so the number of occupied positions is equal to the physically occupied positions. The clothes rod on the other hand requires some amount of space below it and only little space above.

For simplicity, we consider that all accessories have their child docking point at the bottom and occupy a certain amount of docking points in the upward direction. Therefore, we end up with following docking points positions as marked with the coordinate crosses.

Because we want to occupy the space only in the upward direction, we solve the space occupation below the clothes rod by placing it above into the occupied space. This will simplify the code that will not have to check against negative indices in the docking points occupations array and only occupying them in one direction.

In the parent component, we initialize the values necessary for the occupations array.

if (isnull(occupations)) {
    rangeStepZ = 50;
    FREE = 0;
    SHADOW = 1;
    OCCUPIED = 100;
    UNAVAILABLE = -1;
    occupations = [FREE];
    _.maxHeight = 2360;
    _.maxAccessoryHeight = 1000;
    _.occupations_maxIndex = _.maxHeight + _.maxAccessoryHeight;
    for (_.i = 0; _.i < _.occupations_maxIndex; _.i = _.i + rangeStepZ) {
        pushBack(occupations, FREE);
    }
    occupations_spaceAbove = occupations;
}

We define, that the docking point can be either FREE, OCCUPIED (the docking itself; connection exists at that point) or shadowed (space occupied by the accessory). We also add the status UNAVAILABLE where the docking points are deactivated for some reason.

In the assignmentScripts.onDock scripts of the docking array, we do:

// set this point as occupied
set(self.occupations, connection.index, get(self.occupations, connection.index) + OCCUPIED);
// set points above this one as shadowed
for (_.i = connection.index + 1; _.i < connection.index + ceil(other.dock_minimumSpaceAbove / rangeStepZ, 0); _.i = _.i + 1) {
    set(self.occupations, _.i, get(self.occupations, _.i) + SHADOW);
}

Notice, that we do not set the values, but rather increment. This prevents us from leaving unexpected errors in development. Should one point be shadowed twice, we would know that an invalid value is in the array and we can detect it more easy.

On the other hand, we must not forget to do a cleanup in the onUnDock by doing an inverse operation:

set(self.occupations, connection.index, get(self.occupations, connection.index) - OCCUPIED);
for (_.i = connection.index + 1; _.i < connection.index + ceil(other.dock_minimumSpaceAbove / rangeStepZ, 0); _.i = _.i + 1) {
    set(self.occupations, _.i, get(self.occupations, _.i) - SHADOW);
}

We can already create the condition that checks against statuses of the docking points. We keep return true; as a fallthrough at the end of the condition and if we detect any occupation, we return false;.

If a docking point occupation value is SHADOW, we disable docking. If the value is FREE, we let the code reach the terminating return statement. If the value is OCCUPIED, we also do nothing, because we need to keep the condition of the docked points to evaluate as true.

if (get(self.occupations, connection.index) == SHADOW) {
    return false;
}
return true;

The prevents us from placing a shelf directly above another shelf. But it does not prevent us from placing the shelf below it. We would end up with a mixture of OCCUPIED + SHADOW and 2 * SHADOW. We could compare against values that are not FREE or OCCUPIED instead, but that is not a solution - a preview would still show below the shelf, deleting the previous shelf above the new shelf after the docking would finish. The true correct solution is to check, if there are enough free points above the docking point. A for loop in the condition can be done to check a sub-range of the docking points above and eventually return false if an unusable point is reached:

_.minimumSpaceAbove = floor(other.dock_minimumSpaceAbove / rangeStepZ, 0);
for (_.i = connection.index + _.minimumSpaceAbove; _.i > connection.index; _.i = _.i - 1) {
    if (get(self.occupations, _.i) == OCCUPIED) {
        return false;
    }
}

However, there are two approaches against doing this in the condition. Firstly, it is a loop inside a loop which is evaluated in every update call. This alone is not a performance issue in simple projects, but can sum up with other features leading to poor performance. Secondly, you might need the information of available space above a point in more places, like changing the child's element type. Therefore, we compute this array in the onUpdate call instead:

_.lastOccupiedIndex = 0;
for (_.i = length(occupations_spaceAbove) - 1; _.i >= 0; _.i = _.i - 1) {
    _.position = zFromVector(internalBoxPosition) + _.i * rangeStepZ;
    _.occupation = get(occupations, _.i);
    if (_.position > zFromVector(internalBoxPosition) + zFromVector(internalBoxSize)) {
        set(occupations_spaceAbove, _.i, UNAVAILABLE);
        _.lastOccupiedIndex = _.i;
    }
    else { 
        if (_.occupation == OCCUPIED) {
            set(occupations_spaceAbove, _.i, _.lastOccupiedIndex - _.i);
        }
        _.lastOccupiedIndex = _.i;
    }
}

This for loop goes through the occupations in a descending order and writes the sums of free indices above each docking point into the occupations_spaceAbove array. Condition for the docking point is then much more simple:

_.spaceAbove = get(self.occupations_spaceAbove, connection.index) * rangeStepZ;
if (ifnull(other.dock_minimumSpaceAbove, 0) > _.spaceAbove) {
    return false;
}

If you do connection.spaceAbove in the onUpdate call of the connection, you can also use it elsewhere, i.e. assignmentsOnUpdate:

{
    "mask": "'FRAME_TO_ACCESSORY'",
    ...
    "onUpdate": "
        connection.spaceAbove = get(self.occupations_spaceAbove, connection.index) * rangeStepZ;
    ",
    "condition": "
        ...
        if (ifnull(other.dock_minimumSpaceAbove, 0) > connection.spaceAbove) {
            return false;
        }
        ...
    "
}

To debug this more easily, you can also add a debugGeometry script, which gets concatenated to the geometry script if your rubens.config.ts file has the concatenateDebugGeometry modifier call uncommented. A debug geometry can look like this:

for (i = 0; i <= (height - zFromVector(internalBoxPosition)) / rangeStepZ; i = i + 1) {
    occupationValue = get(occupations, i);
    AddSphere(Vector3f{rangeStepZ / 2, rangeStepZ / 2, rangeStepZ / 2});
        MoveMatrixBy(Vector3f{ -rangeStepZ, depth - rangeStepZ / 2, i * rangeStepZ + zFromVector(internalBoxPosition)});
    debugPointColor = 'isdt:red';
    if (occupationValue == FREE) {
        debugPointColor = 'isdt:green';
    }
    else if (occupationValue == SHADOW) {
        debugPointColor = 'isdt:blue';
    }
    else if (occupationValue == OCCUPIED) {
        debugPointColor = 'isdt:yellow';
    }
        SetObjSurface(debugPointColor);

    for (j = 0; j < get(occupations_spaceAbove, i); j = j + 1) {
        AddPlainCube(Vector3f{20, 20, 20});
            MoveMatrixBy(Vector3f{width + 20 + j * 50, depth - 20, i * rangeStepZ + zFromVector(internalBoxPosition)});
        if ((j + 1) % 5 == 0) { SetObjSurface('isdt:black'); } else { SetObjSurface(debugPointColor); }
    }
}

Resulting in view of the docking point occupation and space above status:

Using collisionCondition

In this example we have to create a shelf system consisting of spaces dockable to left, right and top, with a similar logic to our demo , where widths and heights synchronize across the columns and lines (like in an Excel table, where you can not have two cells with different heights in a single row). We will work step-by-step in implementing a similar logic.

Option 2: Implement this without connection.isPreview using hasLeftChild and hasRightChild variables, as described in the .

The sibling points will be used, among other, to lock the widths and heights in the rows and columns. Therefore, assignmentsOnUpdateSilent will be used. "height": "height" in the horizontal siblings, "width": "width" in the vertical siblings. Why have we just picked assignmentsOnUpdateSilent instead of assignmentsOnUpdate? As written , we need to keep the assigned parameters enabled.

In further steps we'll delete the debug geometry. Check out the if you are interested.

See the final state of this example

See the scripting example, where this is described in detail.

Docking ranges have already been mentioned in the chapter. Docking ranges are internally implemented as arrays of docking points, therefore one docked component in a range can have the same feature as any component docked via a docking point. To define a range, use the parentDocking.ranges array, where you use the roomleDockingRange snippet, which looks like this:

See commented example in

The cheapest way is to track the collisions by yourself, but you have to implement logic in a pattern of "If A is docked with B, X fits. If A is docked with C, X doesn't fit.". You can track the status of what is docked with or next to what by assignmentsOnDock, assignmentsOnUpdate and assignmentsOnUnDock. This of course can get complex and it is easy to compute such things only with direct neighbours. Such an example has already been described in the .

See the final example in our or run it in the

Basic Docking Logic
USM configurator
Basic Docking Logic
Basic Docking Logic
component definition up to the current state
here
4-Posts Shelving System
Basic Docking Logic
4-Posts Shelving System
public content sample GitHub repository
configurator
Two-Way Docking of Parametrized Shelf
Initial state of the grid docking
Debug geometry with sibling points
parent child hieararchy visualisation
Current state of the configurator - rods and heads
parrot-coloured shelf
Colour specific sides
wardrobe accesssories docking points
debug geometry of the wardrobe docking