@babylonjs/inspector 8.55.3 → 8.55.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { createContext, forwardRef, useContext, useState, useCallback, Component, useMemo, useEffect, useRef, useReducer, Children, isValidElement, useLayoutEffect, cloneElement, useImperativeHandle, createElement, Suspense, memo, Fragment as Fragment$1, lazy } from 'react';
3
- import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, Link as Link$1, Caption1, Body1, useFluent, Accordion as Accordion$1, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, MessageBar as MessageBar$1, MessageBarBody, AccordionItem, SearchBox as SearchBox$1, Portal, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, Body1Strong, mergeClasses, useId, useToastController, Toast, ToastTitle, FluentProvider, Toaster, Checkbox as Checkbox$1, createLightTheme, createDarkTheme, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Switch as Switch$1, createDOMRenderer, RendererProvider, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, treeItemLevelToken, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, MenuItemCheckbox, useMergedRefs, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, PresenceBadge, Slider as Slider$1, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBarTitle, Subtitle2, useComboboxFilter, Combobox, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, Field } from '@fluentui/react-components';
4
- import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, DocumentTextRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowBidirectionalUpDownFilled, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, CameraRegular, AddRegular, DeleteRegular, FullScreenMaximizeRegular, ChevronDownRegular, ChevronRightRegular, CircleSmallFilled, SaveRegular, PreviousRegular, ArrowPreviousRegular, TriangleLeftRegular, RecordStopRegular, PlayRegular, ArrowNextRegular, NextRegular, PauseRegular, LinkDismissRegular, LinkEditRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, EyeRegular, CloudArrowUpRegular, CloudArrowDownRegular, EyeOffFilled, ArrowMoveFilled, StopFilled, PlayFilled, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
3
+ import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, Link as Link$1, Caption1, Body1, useFluent, Accordion as Accordion$1, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, MessageBar as MessageBar$1, MessageBarBody, AccordionItem, SearchBox as SearchBox$1, Portal, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, Body1Strong, mergeClasses, useId, useToastController, Toast, ToastTitle, FluentProvider, Toaster, Checkbox as Checkbox$1, createLightTheme, createDarkTheme, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Switch as Switch$1, createDOMRenderer, RendererProvider, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, treeItemLevelToken, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, MenuItemCheckbox, useMergedRefs, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, PresenceBadge, Slider as Slider$1, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBarTitle, useComboboxFilter, Combobox, Subtitle2, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, Field } from '@fluentui/react-components';
4
+ import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, DocumentTextRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowBidirectionalUpDownFilled, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, CameraRegular, AddRegular, DeleteRegular, FullScreenMaximizeRegular, ChevronDownRegular, ChevronRightRegular, CircleSmallFilled, SaveRegular, PreviousRegular, ArrowPreviousRegular, TriangleLeftRegular, RecordStopRegular, PlayRegular, ArrowNextRegular, NextRegular, PauseRegular, LinkDismissRegular, LinkEditRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, EyeRegular, CloudArrowUpRegular, CloudArrowDownRegular, EyeOffFilled, ArrowMoveFilled, StopFilled, PlayFilled, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, BubbleMultipleRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
5
5
  import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
6
6
  import { Vector3, Quaternion, Matrix, Vector2, Vector4, TmpVectors } from '@babylonjs/core/Maths/math.vector.js';
7
7
  import { Observable } from '@babylonjs/core/Misc/observable.js';
@@ -58,6 +58,7 @@ import { GeospatialCamera } from '@babylonjs/core/Cameras/geospatialCamera.js';
58
58
  import { TargetCamera } from '@babylonjs/core/Cameras/targetCamera.js';
59
59
  import { Scene } from '@babylonjs/core/scene.js';
60
60
  import { FrameGraph } from '@babylonjs/core/FrameGraph/frameGraph.js';
61
+ import { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer.js';
61
62
  import { DirectionalLight } from '@babylonjs/core/Lights/directionalLight.js';
62
63
  import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight.js';
63
64
  import { PointLight } from '@babylonjs/core/Lights/pointLight.js';
@@ -66,6 +67,8 @@ import { ShadowLight } from '@babylonjs/core/Lights/shadowLight.js';
66
67
  import { SpotLight } from '@babylonjs/core/Lights/spotLight.js';
67
68
  import { CascadedShadowGenerator } from '@babylonjs/core/Lights/Shadows/cascadedShadowGenerator.js';
68
69
  import { DirectionalLightFrustumViewer } from '@babylonjs/core/Debug/directionalLightFrustumViewer.js';
70
+ import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture.js';
71
+ import { ReadFile } from '@babylonjs/core/Misc/fileTools.js';
69
72
  import { ShadowGenerator } from '@babylonjs/core/Lights/Shadows/shadowGenerator.js';
70
73
  import '@babylonjs/core/Lights/Shadows/shadowGeneratorSceneComponent.js';
71
74
  import { Material } from '@babylonjs/core/Materials/material.js';
@@ -81,8 +84,6 @@ import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial.js';
81
84
  import { GradientBlockColorStep } from '@babylonjs/core/Materials/Node/Blocks/gradientBlock.js';
82
85
  import { NodeMaterialBlockConnectionPointTypes } from '@babylonjs/core/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes.js';
83
86
  import { Color3Gradient, ColorGradient, FactorGradient } from '@babylonjs/core/Misc/gradients.js';
84
- import { ReadFile } from '@babylonjs/core/Misc/fileTools.js';
85
- import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture.js';
86
87
  import { Mesh } from '@babylonjs/core/Meshes/mesh.js';
87
88
  import { SkeletonViewer } from '@babylonjs/core/Debug/skeletonViewer.js';
88
89
  import { VertexBuffer } from '@babylonjs/core/Meshes/buffer.js';
@@ -131,7 +132,6 @@ import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents.js';
131
132
  import { HtmlElementTexture } from '@babylonjs/core/Materials/Textures/htmlElementTexture.js';
132
133
  import { ShaderMaterial } from '@babylonjs/core/Materials/shaderMaterial.js';
133
134
  import { CreatePlane } from '@babylonjs/core/Meshes/Builders/planeBuilder.js';
134
- import { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer.js';
135
135
  import '@babylonjs/core/Rendering/boundingBoxRenderer.js';
136
136
  import '@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipelineManagerSceneComponent.js';
137
137
  import '@babylonjs/core/Sprites/spriteSceneComponent.js';
@@ -281,7 +281,7 @@ const Button = forwardRef((props, ref) => {
281
281
  });
282
282
  Button.displayName = "Button";
283
283
 
284
- const useStyles$Y = makeStyles({
284
+ const useStyles$Z = makeStyles({
285
285
  root: {
286
286
  display: "flex",
287
287
  flexDirection: "column",
@@ -362,7 +362,7 @@ class ErrorBoundary extends Component {
362
362
  }
363
363
  }
364
364
  function ErrorFallback({ error, onRetry }) {
365
- const styles = useStyles$Y();
365
+ const styles = useStyles$Z();
366
366
  return (jsxs("div", { className: styles.root, children: [jsx(ErrorCircleRegular, { className: styles.icon }), jsx("div", { className: styles.title, children: "Something went wrong" }), jsx("div", { className: styles.message, children: "An error occurred in this component. You can try again or continue using other parts of the tool." }), jsx(Button, { label: "Try Again", appearance: "primary", onClick: onRetry }), error && jsx("div", { className: styles.details, children: error.message })] }));
367
367
  }
368
368
 
@@ -553,8 +553,8 @@ function InterceptFunction(target, propertyKey, hooks) {
553
553
  hooksMap.set(propertyKey, (hooksForKey = []));
554
554
  if (
555
555
  // Replace the function with a new one that calls the hooks in addition to the original function.
556
- !Reflect.set(target, propertyKey, (...args) => {
557
- const result = Reflect.apply(originalFunction, target, args);
556
+ !Reflect.set(target, propertyKey, function (...args) {
557
+ const result = Reflect.apply(originalFunction, this, args);
558
558
  for (const { afterCall } of hooksForKey) {
559
559
  afterCall?.(...args);
560
560
  }
@@ -1363,7 +1363,7 @@ function useIsSectionEmpty(sectionId) {
1363
1363
  return hasItems;
1364
1364
  }
1365
1365
 
1366
- const useStyles$X = makeStyles({
1366
+ const useStyles$Y = makeStyles({
1367
1367
  accordion: {
1368
1368
  display: "flex",
1369
1369
  flexDirection: "column",
@@ -1455,7 +1455,7 @@ const useStyles$X = makeStyles({
1455
1455
  */
1456
1456
  const AccordionMenuBar = () => {
1457
1457
  AccordionMenuBar.displayName = "AccordionMenuBar";
1458
- const classes = useStyles$X();
1458
+ const classes = useStyles$Y();
1459
1459
  const accordionCtx = useContext(AccordionContext);
1460
1460
  if (!accordionCtx) {
1461
1461
  return null;
@@ -1479,7 +1479,7 @@ const AccordionMenuBar = () => {
1479
1479
  const AccordionSectionBlock = (props) => {
1480
1480
  AccordionSectionBlock.displayName = "AccordionSectionBlock";
1481
1481
  const { children, sectionId } = props;
1482
- const classes = useStyles$X();
1482
+ const classes = useStyles$Y();
1483
1483
  const accordionCtx = useContext(AccordionContext);
1484
1484
  const { context: sectionContext, isEmpty } = useAccordionSectionBlockContext(props);
1485
1485
  if (accordionCtx) {
@@ -1499,7 +1499,7 @@ const AccordionSectionBlock = (props) => {
1499
1499
  const AccordionSectionItem = (props) => {
1500
1500
  AccordionSectionItem.displayName = "AccordionSectionItem";
1501
1501
  const { children, staticItem } = props;
1502
- const classes = useStyles$X();
1502
+ const classes = useStyles$Y();
1503
1503
  const accordionCtx = useContext(AccordionContext);
1504
1504
  const itemState = useAccordionSectionItemState(props);
1505
1505
  const [ctrlMode, setCtrlMode] = useState(false);
@@ -1539,7 +1539,7 @@ const AccordionSectionItem = (props) => {
1539
1539
  */
1540
1540
  const AccordionPinnedContainer = () => {
1541
1541
  AccordionPinnedContainer.displayName = "AccordionPinnedContainer";
1542
- const classes = useStyles$X();
1542
+ const classes = useStyles$Y();
1543
1543
  const accordionCtx = useContext(AccordionContext);
1544
1544
  return (jsx("div", { ref: accordionCtx?.pinnedContainerRef, className: classes.pinnedContainer, children: jsx(MessageBar$1, { className: classes.pinnedContainerEmpty, children: jsx(MessageBarBody, { children: "No pinned items" }) }) }));
1545
1545
  };
@@ -1550,7 +1550,7 @@ const AccordionPinnedContainer = () => {
1550
1550
  */
1551
1551
  const AccordionSearchBox = () => {
1552
1552
  AccordionSearchBox.displayName = "AccordionSearchBox";
1553
- const classes = useStyles$X();
1553
+ const classes = useStyles$Y();
1554
1554
  const accordionCtx = useContext(AccordionContext);
1555
1555
  if (!accordionCtx?.features.search) {
1556
1556
  return null;
@@ -1566,7 +1566,7 @@ const AccordionSearchBox = () => {
1566
1566
  */
1567
1567
  const AccordionSection = (props) => {
1568
1568
  AccordionSection.displayName = "AccordionSection";
1569
- const classes = useStyles$X();
1569
+ const classes = useStyles$Y();
1570
1570
  return jsx("div", { className: classes.panelDiv, children: props.children });
1571
1571
  };
1572
1572
  const StringAccordion = Accordion$1;
@@ -1574,7 +1574,7 @@ const Accordion = forwardRef((props, ref) => {
1574
1574
  Accordion.displayName = "Accordion";
1575
1575
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1576
1576
  const { children, highlightSections, uniqueId, enablePinnedItems, enableHiddenItems, enableSearchItems, ...rest } = props;
1577
- const classes = useStyles$X();
1577
+ const classes = useStyles$Y();
1578
1578
  const { size } = useContext(ToolContext);
1579
1579
  const accordionCtx = useAccordionContext(props);
1580
1580
  const hasPinning = accordionCtx?.features.pinning ?? false;
@@ -1671,7 +1671,7 @@ const Collapse = (props) => {
1671
1671
  return (jsx(Collapse$1, { visible: props.visible, orientation: props.orientation, unmountOnExit: true, children: jsx("div", { className: `${classes.collapseContent} ${props.orientation === "horizontal" ? classes.horizontal : classes.vertical}`, children: props.children }) }));
1672
1672
  };
1673
1673
 
1674
- const useStyles$W = makeStyles({
1674
+ const useStyles$X = makeStyles({
1675
1675
  button: {
1676
1676
  display: "flex",
1677
1677
  alignItems: "center",
@@ -1689,7 +1689,7 @@ const ToggleButton = (props) => {
1689
1689
  ToggleButton.displayName = "ToggleButton";
1690
1690
  const { value, onChange, title, appearance = "subtle" } = props;
1691
1691
  const { size } = useContext(ToolContext);
1692
- const classes = useStyles$W();
1692
+ const classes = useStyles$X();
1693
1693
  const [checked, setChecked] = useState(value);
1694
1694
  const toggle = useCallback(() => {
1695
1695
  setChecked((prevChecked) => {
@@ -2034,7 +2034,7 @@ const UXContextProvider = (props) => {
2034
2034
  function AsReadonlyArray(array) {
2035
2035
  return array;
2036
2036
  }
2037
- const useStyles$V = makeStyles({
2037
+ const useStyles$W = makeStyles({
2038
2038
  rootDiv: {
2039
2039
  flex: 1,
2040
2040
  overflow: "hidden",
@@ -2049,7 +2049,7 @@ const useStyles$V = makeStyles({
2049
2049
  * @returns The extensible accordion component.
2050
2050
  */
2051
2051
  function ExtensibleAccordion(props) {
2052
- const classes = useStyles$V();
2052
+ const classes = useStyles$W();
2053
2053
  const { children, sections, sectionContent, context, sectionsRef, ...rest } = props;
2054
2054
  const defaultSections = useMemo(() => {
2055
2055
  const defaultSections = [];
@@ -2174,7 +2174,7 @@ function ExtensibleAccordion(props) {
2174
2174
  })] }) })) }));
2175
2175
  }
2176
2176
 
2177
- const useStyles$U = makeStyles({
2177
+ const useStyles$V = makeStyles({
2178
2178
  paneRootDiv: {
2179
2179
  display: "flex",
2180
2180
  flex: 1,
@@ -2187,7 +2187,7 @@ const useStyles$U = makeStyles({
2187
2187
  */
2188
2188
  const SidePaneContainer = forwardRef((props, ref) => {
2189
2189
  const { className, ...rest } = props;
2190
- const classes = useStyles$U();
2190
+ const classes = useStyles$V();
2191
2191
  return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
2192
2192
  });
2193
2193
 
@@ -2458,7 +2458,7 @@ function useTheme(invert = false) {
2458
2458
  }
2459
2459
 
2460
2460
  // Fluent doesn't apply styling to scrollbars by default, so provide our own reasonable default.
2461
- const useStyles$T = makeStyles({
2461
+ const useStyles$U = makeStyles({
2462
2462
  root: {
2463
2463
  scrollbarColor: `${tokens.colorNeutralForeground3} ${tokens.colorTransparentBackground}`,
2464
2464
  },
@@ -2474,11 +2474,11 @@ const Theme = (props) => {
2474
2474
  // break any UI within the portal. Therefore, default to false.
2475
2475
  const { invert = false, applyStylesToPortals = false, className, ...rest } = props;
2476
2476
  const theme = useTheme(invert);
2477
- const classes = useStyles$T();
2477
+ const classes = useStyles$U();
2478
2478
  return (jsx(FluentProvider, { theme: theme, className: mergeClasses(classes.root, className), applyStylesToPortals: applyStylesToPortals, ...rest, children: props.children }));
2479
2479
  };
2480
2480
 
2481
- const useStyles$S = makeStyles({
2481
+ const useStyles$T = makeStyles({
2482
2482
  extensionTeachingPopover: {
2483
2483
  maxWidth: "320px",
2484
2484
  },
@@ -2489,7 +2489,7 @@ const useStyles$S = makeStyles({
2489
2489
  * @returns The teaching moment popover.
2490
2490
  */
2491
2491
  const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
2492
- const classes = useStyles$S();
2492
+ const classes = useStyles$T();
2493
2493
  return (jsx(TeachingPopover, { appearance: "brand", open: shouldDisplay, positioning: { positioningRef }, onOpenChange: onOpenChange, children: jsxs(TeachingPopoverSurface, { className: classes.extensionTeachingPopover, children: [jsx(TeachingPopoverHeader, { children: title }), jsx(TeachingPopoverBody, { children: description })] }) }));
2494
2494
  };
2495
2495
 
@@ -2692,13 +2692,13 @@ function useImpulse() {
2692
2692
  return [value, pulse];
2693
2693
  }
2694
2694
 
2695
- const useStyles$R = makeStyles({
2695
+ const useStyles$S = makeStyles({
2696
2696
  placeholderDiv: {
2697
2697
  padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
2698
2698
  },
2699
2699
  });
2700
2700
  const PropertiesPane = (props) => {
2701
- const classes = useStyles$R();
2701
+ const classes = useStyles$S();
2702
2702
  const entity = props.context;
2703
2703
  return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
2704
2704
  };
@@ -3024,7 +3024,7 @@ const RootComponentServiceIdentity = Symbol("RootComponent");
3024
3024
  * The unique identity symbol for the shell service.
3025
3025
  */
3026
3026
  const ShellServiceIdentity = Symbol("ShellService");
3027
- const useStyles$Q = makeStyles({
3027
+ const useStyles$R = makeStyles({
3028
3028
  mainView: {
3029
3029
  flex: 1,
3030
3030
  display: "flex",
@@ -3237,14 +3237,14 @@ const DockMenu = (props) => {
3237
3237
  };
3238
3238
  const PaneHeader = (props) => {
3239
3239
  const { id, title, dockOptions } = props;
3240
- const classes = useStyles$Q();
3240
+ const classes = useStyles$R();
3241
3241
  return (jsxs("div", { className: classes.paneHeaderDiv, children: [props.icon && (jsx("div", { className: classes.paneHeaderIcon, children: jsx(props.icon, {}) })), jsx(Subtitle2Stronger, { className: mergeClasses(classes.paneHeaderText, !props.icon && classes.paneHeaderTextNoIcon), children: title }), jsx(DockMenu, { sidePaneId: id, dockOptions: dockOptions, children: jsx(Button$1, { className: classes.paneHeaderButton, appearance: "transparent", icon: jsx(MoreHorizontalRegular, {}) }) })] }));
3242
3242
  };
3243
3243
  // This is a wrapper for an item in a toolbar that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
3244
3244
  const ToolbarItem = (props) => {
3245
3245
  // eslint-disable-next-line @typescript-eslint/naming-convention
3246
3246
  const { verticalLocation, horizontalLocation, id, component: Component, displayName } = props;
3247
- const classes = useStyles$Q();
3247
+ const classes = useStyles$R();
3248
3248
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
3249
3249
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3250
3250
  const title = typeof props.teachingMoment === "object" ? props.teachingMoment.title : (displayName ?? id);
@@ -3254,7 +3254,7 @@ const ToolbarItem = (props) => {
3254
3254
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
3255
3255
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
3256
3256
  const Toolbar = ({ location, components }) => {
3257
- const classes = useStyles$Q();
3257
+ const classes = useStyles$R();
3258
3258
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
3259
3259
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
3260
3260
  return (jsx(Fragment, { children: components.length > 0 && (jsxs("div", { className: `${classes.bar} ${location === "top" ? classes.barTop : classes.barBottom}`, children: [jsx("div", { className: classes.barLeft, children: leftComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) }), jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) })] })) }));
@@ -3264,7 +3264,7 @@ const SidePaneTab = (props) => {
3264
3264
  const { location, id, isSelected, isFirst, isLast, dockOptions,
3265
3265
  // eslint-disable-next-line @typescript-eslint/naming-convention
3266
3266
  icon: Icon, title, } = props;
3267
- const classes = useStyles$Q();
3267
+ const classes = useStyles$R();
3268
3268
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
3269
3269
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3270
3270
  const tabClass = mergeClasses(classes.tab, isSelected ? classes.selectedTab : classes.unselectedTab, isFirst ? classes.firstTab : undefined, isLast ? classes.lastTab : undefined);
@@ -3276,7 +3276,7 @@ const SidePaneTab = (props) => {
3276
3276
  // In "compact" mode, the tab list is integrated into the pane itself.
3277
3277
  // In "full" mode, the returned tab list is later injected into the toolbar.
3278
3278
  function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
3279
- const classes = useStyles$Q();
3279
+ const classes = useStyles$R();
3280
3280
  const [topSelectedTab, setTopSelectedTab] = useState();
3281
3281
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
3282
3282
  const [collapsed, setCollapsed] = useState(initialCollapsed);
@@ -3473,7 +3473,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
3473
3473
  expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
3474
3474
  };
3475
3475
  const rootComponent = () => {
3476
- const classes = useStyles$Q();
3476
+ const classes = useStyles$R();
3477
3477
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
3478
3478
  // This function returns a promise that resolves after the dock change takes effect so that
3479
3479
  // we can then select the re-docked pane.
@@ -4237,7 +4237,7 @@ function CoerceEntityArray(entities, sort) {
4237
4237
  }
4238
4238
  return entities;
4239
4239
  }
4240
- const useStyles$P = makeStyles({
4240
+ const useStyles$Q = makeStyles({
4241
4241
  rootDiv: {
4242
4242
  flex: 1,
4243
4243
  overflow: "hidden",
@@ -4346,14 +4346,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
4346
4346
  }
4347
4347
  const SceneTreeItem = (props) => {
4348
4348
  const { isSelected, select } = props;
4349
- const classes = useStyles$P();
4349
+ const classes = useStyles$Q();
4350
4350
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4351
4351
  const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
4352
4352
  return (jsx(FlatTreeItem, { className: classes.treeItem, value: "scene", itemType: "leaf", parentValue: undefined, "aria-level": 1, "aria-setsize": 1, "aria-posinset": 1, onClick: select, children: jsx(TreeItemLayout, { iconBefore: jsx(GlobeRegular, {}), className: treeItemLayoutClass, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, children: jsx(Body1Strong, { wrap: false, truncate: true, children: "Scene" }) }) }, "scene"));
4353
4353
  };
4354
4354
  const SectionTreeItem = (props) => {
4355
4355
  const { section, isFiltering, commandProviders, expandAll, collapseAll, isDropTarget, ...dropProps } = props;
4356
- const classes = useStyles$P();
4356
+ const classes = useStyles$Q();
4357
4357
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4358
4358
  // Get the commands that apply to this section.
4359
4359
  const commands = useResource(useCallback(() => {
@@ -4370,7 +4370,7 @@ const SectionTreeItem = (props) => {
4370
4370
  };
4371
4371
  const EntityTreeItem = (props) => {
4372
4372
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll, isDragging, isDropTarget, ...dragProps } = props;
4373
- const classes = useStyles$P();
4373
+ const classes = useStyles$Q();
4374
4374
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4375
4375
  const hasChildren = !!entityItem.children?.length;
4376
4376
  const displayInfo = useResource(useCallback(() => {
@@ -4486,7 +4486,7 @@ const EntityTreeItem = (props) => {
4486
4486
  }, children: jsx(Tooltip$1, { content: name, relationship: "description", children: jsx(Body1, { wrap: false, truncate: true, children: name }) }) }) }, GetEntityId(entityItem.entity)) }), jsx(MenuPopover, { hidden: !hasChildren && contextMenuCommands.length === 0, children: jsxs(MenuList, { children: [hasChildren && (jsxs(Fragment, { children: [jsx(MenuItem, { icon: jsx(ArrowExpandAllRegular, {}), onClick: expandAll, children: jsx(Body1, { children: "Expand All" }) }), jsx(MenuItem, { icon: jsx(ArrowCollapseAllRegular, {}), onClick: collapseAll, children: jsx(Body1, { children: "Collapse All" }) })] })), hasChildren && contextMenuCommands.length > 0 && jsx(MenuDivider, {}), contextMenuItems] }) })] }));
4487
4487
  };
4488
4488
  const SceneExplorer = (props) => {
4489
- const classes = useStyles$P();
4489
+ const classes = useStyles$Q();
4490
4490
  const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity = null } = props;
4491
4491
  const [openItems, setOpenItems] = useState(new Set());
4492
4492
  const [sceneVersion, setSceneVersion] = useState(0);
@@ -6111,7 +6111,7 @@ class CanvasGraphService {
6111
6111
  }
6112
6112
  }
6113
6113
 
6114
- const useStyles$O = makeStyles({
6114
+ const useStyles$P = makeStyles({
6115
6115
  canvas: {
6116
6116
  flexGrow: 1,
6117
6117
  width: "100%",
@@ -6120,7 +6120,7 @@ const useStyles$O = makeStyles({
6120
6120
  });
6121
6121
  const CanvasGraph = (props) => {
6122
6122
  const { collector, scene, layoutObservable, returnToPlayheadObservable, onVisibleRangeChangedObservable, initialGraphSize } = props;
6123
- const classes = useStyles$O();
6123
+ const classes = useStyles$P();
6124
6124
  const canvasRef = useRef(null);
6125
6125
  useEffect(() => {
6126
6126
  if (!canvasRef.current) {
@@ -6197,7 +6197,7 @@ function EvaluateExpression(rawValue) {
6197
6197
  return NaN;
6198
6198
  }
6199
6199
  }
6200
- const useStyles$N = makeStyles({
6200
+ const useStyles$O = makeStyles({
6201
6201
  icon: {
6202
6202
  "&:hover": {
6203
6203
  color: tokens.colorBrandForeground1,
@@ -6211,7 +6211,7 @@ const useStyles$N = makeStyles({
6211
6211
  const SpinButton = forwardRef((props, ref) => {
6212
6212
  SpinButton.displayName = "SpinButton2";
6213
6213
  const inputClasses = useInputStyles$1();
6214
- const classes = useStyles$N();
6214
+ const classes = useStyles$O();
6215
6215
  const { size } = useContext(ToolContext);
6216
6216
  const { min, max } = props;
6217
6217
  const baseStep = props.step ?? 1;
@@ -6475,7 +6475,7 @@ const Dropdown = (props) => {
6475
6475
  const NumberDropdown = Dropdown;
6476
6476
  const StringDropdown = Dropdown;
6477
6477
 
6478
- const useStyles$M = makeStyles({
6478
+ const useStyles$N = makeStyles({
6479
6479
  surface: {
6480
6480
  maxWidth: "400px",
6481
6481
  },
@@ -6490,7 +6490,7 @@ const useStyles$M = makeStyles({
6490
6490
  const Popover = forwardRef((props, ref) => {
6491
6491
  const { children, open: controlledOpen, onOpenChange, positioning, surfaceClassName } = props;
6492
6492
  const [internalOpen, setInternalOpen] = useState(false);
6493
- const classes = useStyles$M();
6493
+ const classes = useStyles$N();
6494
6494
  const isControlled = controlledOpen !== undefined;
6495
6495
  const popoverOpen = isControlled ? controlledOpen : internalOpen;
6496
6496
  const handleOpenChange = (_, data) => {
@@ -6734,7 +6734,7 @@ const InputAlphaField = (props) => {
6734
6734
  } }));
6735
6735
  };
6736
6736
 
6737
- const useStyles$L = makeStyles({
6737
+ const useStyles$M = makeStyles({
6738
6738
  sidebar: {
6739
6739
  display: "flex",
6740
6740
  flexDirection: "column",
@@ -6798,7 +6798,7 @@ const useStyles$L = makeStyles({
6798
6798
  });
6799
6799
  const PerformanceSidebar = (props) => {
6800
6800
  const { collector, onVisibleRangeChangedObservable } = props;
6801
- const classes = useStyles$L();
6801
+ const classes = useStyles$M();
6802
6802
  // Map from id to IPerfMetadata information
6803
6803
  const [metadataMap, setMetadataMap] = useState();
6804
6804
  // Map from category to all the ids belonging to that category
@@ -6871,7 +6871,7 @@ const PerformanceSidebar = (props) => {
6871
6871
  })] }, `category-${category || "version"}`))) }));
6872
6872
  };
6873
6873
 
6874
- const useStyles$K = makeStyles({
6874
+ const useStyles$L = makeStyles({
6875
6875
  container: {
6876
6876
  display: "flex",
6877
6877
  flexDirection: "row",
@@ -6900,7 +6900,7 @@ const useStyles$K = makeStyles({
6900
6900
  });
6901
6901
  const PerformanceViewer = (props) => {
6902
6902
  const { scene, layoutObservable, returnToLiveObservable, performanceCollector, initialGraphSize } = props;
6903
- const classes = useStyles$K();
6903
+ const classes = useStyles$L();
6904
6904
  const [onVisibleRangeChangedObservable] = useState(() => new Observable());
6905
6905
  const onReturnToPlayheadClick = () => {
6906
6906
  returnToLiveObservable.notifyObservers();
@@ -7069,14 +7069,14 @@ const TextPropertyLine = (props) => {
7069
7069
  return (jsx(PropertyLine, { ...props, children: jsx(Body1, { title: title, children: value ?? "" }) }));
7070
7070
  };
7071
7071
 
7072
- const useStyles$J = makeStyles({
7072
+ const useStyles$K = makeStyles({
7073
7073
  pinnedStatsPane: {
7074
7074
  flex: "0 1 auto",
7075
7075
  paddingBottom: tokens.spacingHorizontalM,
7076
7076
  },
7077
7077
  });
7078
7078
  const StatsPane = (props) => {
7079
- const classes = useStyles$J();
7079
+ const classes = useStyles$K();
7080
7080
  const scene = props.context;
7081
7081
  const engine = scene.getEngine();
7082
7082
  const pollingObservable = usePollingObservable(250);
@@ -7234,7 +7234,7 @@ const ToolsServiceDefinition = {
7234
7234
  },
7235
7235
  };
7236
7236
 
7237
- const useStyles$I = makeStyles({
7237
+ const useStyles$J = makeStyles({
7238
7238
  dropdown: {
7239
7239
  ...UniformWidthStyling,
7240
7240
  },
@@ -7246,7 +7246,7 @@ const useStyles$I = makeStyles({
7246
7246
  */
7247
7247
  const DropdownPropertyLine = forwardRef((props, ref) => {
7248
7248
  DropdownPropertyLine.displayName = "DropdownPropertyLine";
7249
- const classes = useStyles$I();
7249
+ const classes = useStyles$J();
7250
7250
  return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
7251
7251
  });
7252
7252
  /**
@@ -7404,7 +7404,7 @@ const SyncedSliderInput = (props) => {
7404
7404
  return (jsxs("div", { className: mergeClasses(classes.container, props.className), children: [infoLabel && jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [hasSlider && (jsx(Slider, { className: getSliderClassName(), value: value, onChange: handleSliderChange, min: props.min, max: props.max, step: props.step, disabled: props.disabled, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...passthroughProps, className: useCompactSizing ? classes.compactSpinButton : classes.spinButton, inputClassName: useCompactSizing ? classes.compactSpinButtonInput : classes.spinButtonInput, value: value, onChange: handleInputChange, step: props.step, disabled: props.disabled, disableDragButton: true })] })] }));
7405
7405
  };
7406
7406
 
7407
- const useStyles$H = makeStyles({
7407
+ const useStyles$I = makeStyles({
7408
7408
  uniformWidth: {
7409
7409
  ...UniformWidthStyling,
7410
7410
  },
@@ -7416,7 +7416,7 @@ const useStyles$H = makeStyles({
7416
7416
  */
7417
7417
  const SyncedSliderPropertyLine = forwardRef((props, ref) => {
7418
7418
  SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
7419
- const classes = useStyles$H();
7419
+ const classes = useStyles$I();
7420
7420
  const { label, description, ...sliderProps } = props;
7421
7421
  return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps, className: mergeClasses(classes.uniformWidth, props.className) }) }));
7422
7422
  });
@@ -7859,7 +7859,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7859
7859
  keywords: ["creation", "tools"],
7860
7860
  ...BabylonWebResources,
7861
7861
  author: { name: "Babylon.js", forumUserName: "" },
7862
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-DDcd57ne.js'),
7862
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-CU-OcaMm.js'),
7863
7863
  },
7864
7864
  {
7865
7865
  name: "Reflector",
@@ -7867,7 +7867,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7867
7867
  keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
7868
7868
  ...BabylonWebResources,
7869
7869
  author: { name: "Babylon.js", forumUserName: "" },
7870
- getExtensionModuleAsync: async () => await import('./reflectorService-B_dpjAQ3.js'),
7870
+ getExtensionModuleAsync: async () => await import('./reflectorService-C9gCqhs6.js'),
7871
7871
  },
7872
7872
  ]);
7873
7873
 
@@ -7905,7 +7905,7 @@ const ColorSliders = ({ color, onSliderChange }) => (jsxs(Fragment, { children:
7905
7905
  const Color3PropertyLine = ColorPropertyLine;
7906
7906
  const Color4PropertyLine = ColorPropertyLine;
7907
7907
 
7908
- const useStyles$G = makeStyles({
7908
+ const useStyles$H = makeStyles({
7909
7909
  uniformWidth: {
7910
7910
  ...UniformWidthStyling,
7911
7911
  },
@@ -7917,7 +7917,7 @@ const useStyles$G = makeStyles({
7917
7917
  */
7918
7918
  const TextInputPropertyLine = (props) => {
7919
7919
  TextInputPropertyLine.displayName = "TextInputPropertyLine";
7920
- const classes = useStyles$G();
7920
+ const classes = useStyles$H();
7921
7921
  return (jsx(PropertyLine, { ...props, children: jsx(TextInput, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
7922
7922
  };
7923
7923
  /**
@@ -7928,7 +7928,7 @@ const TextInputPropertyLine = (props) => {
7928
7928
  */
7929
7929
  const NumberInputPropertyLine = (props) => {
7930
7930
  NumberInputPropertyLine.displayName = "NumberInputPropertyLine";
7931
- const classes = useStyles$G();
7931
+ const classes = useStyles$H();
7932
7932
  return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
7933
7933
  };
7934
7934
 
@@ -8484,7 +8484,7 @@ class ServiceContainer {
8484
8484
  }
8485
8485
  }
8486
8486
 
8487
- const useStyles$F = makeStyles({
8487
+ const useStyles$G = makeStyles({
8488
8488
  themeButton: {
8489
8489
  margin: 0,
8490
8490
  },
@@ -8503,7 +8503,7 @@ const ThemeSelectorServiceDefinition = {
8503
8503
  teachingMoment: false,
8504
8504
  order: -300,
8505
8505
  component: () => {
8506
- const classes = useStyles$F();
8506
+ const classes = useStyles$G();
8507
8507
  const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
8508
8508
  const onSelectedThemeChange = useCallback((e, data) => {
8509
8509
  setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
@@ -8520,7 +8520,7 @@ const ThemeSelectorServiceDefinition = {
8520
8520
  },
8521
8521
  };
8522
8522
 
8523
- const useStyles$E = makeStyles({
8523
+ const useStyles$F = makeStyles({
8524
8524
  app: {
8525
8525
  colorScheme: "light dark",
8526
8526
  flexGrow: 1,
@@ -8560,7 +8560,7 @@ function MakeModularTool(options) {
8560
8560
  settingsStore.writeSetting(ThemeModeSettingDescriptor, themeMode);
8561
8561
  }
8562
8562
  const modularToolRootComponent = () => {
8563
- const classes = useStyles$E();
8563
+ const classes = useStyles$F();
8564
8564
  const [extensionManagerContext, setExtensionManagerContext] = useState();
8565
8565
  const [requiredExtensions, setRequiredExtensions] = useState();
8566
8566
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
@@ -8603,7 +8603,7 @@ function MakeModularTool(options) {
8603
8603
  }
8604
8604
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
8605
8605
  if (extensionFeeds.length > 0) {
8606
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-BSD2sNr6.js');
8606
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-o37oCGIy.js');
8607
8607
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
8608
8608
  }
8609
8609
  // Register all external services (that make up a unique tool).
@@ -8710,7 +8710,7 @@ const BreakTangentIcon = createFluentIcon("BreakTangent", "20", '<g transform="s
8710
8710
  const UnifyTangentIcon = createFluentIcon("UnifyTangent", "20", '<g transform="scale(0.5)"><path d="M27.94,18.28a1.49,1.49,0,0,0-1.41,1h-5l-1.62-1.63-1.62,1.63h-5a1.5,1.5,0,1,0,0,1h5l1.62,1.62,1.62-1.62h5a1.5,1.5,0,1,0,1.41-2Z"/></g>');
8711
8711
  const StepTangentIcon = createFluentIcon("StepTangent", "20", '<g transform="scale(0.5)"><path d="M29,16.71a1.5,1.5,0,1,0-2,1.41v5.67H11v1H28V18.12A1.51,1.51,0,0,0,29,16.71Z"/></g>');
8712
8712
 
8713
- const useStyles$D = makeStyles({
8713
+ const useStyles$E = makeStyles({
8714
8714
  coordinatesModeButton: {
8715
8715
  margin: `0 0 0 ${tokens.spacingHorizontalXS}`,
8716
8716
  },
@@ -8726,7 +8726,7 @@ const useStyles$D = makeStyles({
8726
8726
  });
8727
8727
  const GizmoToolbar = (props) => {
8728
8728
  const { gizmoService, sceneContext } = props;
8729
- const classes = useStyles$D();
8729
+ const classes = useStyles$E();
8730
8730
  const gizmoMode = useObservableState(() => gizmoService.gizmoMode, gizmoService.onGizmoModeChanged);
8731
8731
  const coordinatesMode = useObservableState(() => gizmoService.coordinatesMode, gizmoService.onCoordinatesModeChanged);
8732
8732
  const cameraGizmo = useObservableState(() => gizmoService.gizmoCamera, gizmoService.onCameraGizmoChanged);
@@ -8855,7 +8855,7 @@ const HighlightServiceDefinition = {
8855
8855
  },
8856
8856
  };
8857
8857
 
8858
- const useStyles$C = makeStyles({
8858
+ const useStyles$D = makeStyles({
8859
8859
  badge: {
8860
8860
  margin: tokens.spacingHorizontalXXS,
8861
8861
  fontFamily: "monospace",
@@ -8872,7 +8872,7 @@ const MiniStatsServiceDefinition = {
8872
8872
  order: 300 /* DefaultToolbarItemOrder.FrameRate */,
8873
8873
  teachingMoment: false,
8874
8874
  component: () => {
8875
- const classes = useStyles$C();
8875
+ const classes = useStyles$D();
8876
8876
  const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
8877
8877
  const engine = scene?.getEngine();
8878
8878
  const pollingObservable = usePollingObservable(250);
@@ -9201,7 +9201,7 @@ function useCurveEditor() {
9201
9201
  return context;
9202
9202
  }
9203
9203
 
9204
- const useStyles$B = makeStyles({
9204
+ const useStyles$C = makeStyles({
9205
9205
  root: {
9206
9206
  display: "flex",
9207
9207
  flexDirection: "row",
@@ -9245,7 +9245,7 @@ const useStyles$B = makeStyles({
9245
9245
  * @returns The top bar component
9246
9246
  */
9247
9247
  const TopBar = () => {
9248
- const styles = useStyles$B();
9248
+ const styles = useStyles$C();
9249
9249
  const { state, observables } = useCurveEditor();
9250
9250
  const [keyFrameValue, setKeyFrameValue] = useState(null);
9251
9251
  const [keyValue, setKeyValue] = useState(null);
@@ -9338,7 +9338,7 @@ const ColorChannelColors = {
9338
9338
  */
9339
9339
  const DefaultCurveColor = "#ffffff";
9340
9340
 
9341
- const useStyles$A = makeStyles({
9341
+ const useStyles$B = makeStyles({
9342
9342
  root: {
9343
9343
  display: "flex",
9344
9344
  flexDirection: "column",
@@ -9380,7 +9380,7 @@ const LOOP_MODES$1 = [
9380
9380
  * @returns The edit animation panel component
9381
9381
  */
9382
9382
  const EditAnimationPanel = ({ animation, onClose }) => {
9383
- const styles = useStyles$A();
9383
+ const styles = useStyles$B();
9384
9384
  const { observables } = useCurveEditor();
9385
9385
  const [name, setName] = useState(animation.name);
9386
9386
  const [property, setProperty] = useState(animation.targetProperty);
@@ -9411,7 +9411,7 @@ const EditAnimationPanel = ({ animation, onClose }) => {
9411
9411
  return (jsxs("div", { className: styles.root, children: [jsxs("div", { className: styles.form, children: [jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Display Name" }), jsx(Input, { value: name, onChange: (_, data) => setName(data.value), placeholder: "Animation name" })] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Property" }), jsx(Input, { value: property, onChange: (_, data) => setProperty(data.value), placeholder: "e.g., position, rotation, scaling" })] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Loop Mode" }), jsx(Dropdown$1, { value: getLoopModeLabel(loopMode), selectedOptions: [loopMode.toString()], onOptionSelect: (_, data) => setLoopMode(Number(data.optionValue)), positioning: "below", inlinePopup: true, children: LOOP_MODES$1.map((mode) => (jsx(Option, { value: mode.value.toString(), children: mode.label }, mode.value))) })] })] }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: saveChanges, label: "Save", disabled: !isValid }), jsx(Button, { appearance: "subtle", onClick: onClose, label: "Cancel" })] })] }));
9412
9412
  };
9413
9413
 
9414
- const useStyles$z = makeStyles({
9414
+ const useStyles$A = makeStyles({
9415
9415
  root: {
9416
9416
  display: "flex",
9417
9417
  flexDirection: "column",
@@ -9477,7 +9477,7 @@ const useStyles$z = makeStyles({
9477
9477
  * @returns Animation entry component
9478
9478
  */
9479
9479
  const AnimationEntry = ({ animation }) => {
9480
- const styles = useStyles$z();
9480
+ const styles = useStyles$A();
9481
9481
  const { state, actions, observables } = useCurveEditor();
9482
9482
  const [isExpanded, setIsExpanded] = useState(false);
9483
9483
  const [isHovered, setIsHovered] = useState(false);
@@ -9556,7 +9556,7 @@ const AnimationEntry = ({ animation }) => {
9556
9556
  * @returns Animation sub-entry component
9557
9557
  */
9558
9558
  const AnimationSubEntry = ({ animation, subName, color }) => {
9559
- const styles = useStyles$z();
9559
+ const styles = useStyles$A();
9560
9560
  const { actions, observables } = useCurveEditor();
9561
9561
  const activeChannel = actions.getActiveChannel(animation);
9562
9562
  const isThisChannelActive = activeChannel === color;
@@ -9579,7 +9579,7 @@ const AnimationSubEntry = ({ animation, subName, color }) => {
9579
9579
  * @returns Animation list component
9580
9580
  */
9581
9581
  const AnimationList = () => {
9582
- const styles = useStyles$z();
9582
+ const styles = useStyles$A();
9583
9583
  const { state, observables } = useCurveEditor();
9584
9584
  // Re-render when animations are loaded or changed (e.g. animation deleted)
9585
9585
  // useCallback stabilizes the accessor to prevent infinite re-render loops
@@ -9592,7 +9592,7 @@ const AnimationList = () => {
9592
9592
  }) }));
9593
9593
  };
9594
9594
 
9595
- const useStyles$y = makeStyles({
9595
+ const useStyles$z = makeStyles({
9596
9596
  root: {
9597
9597
  display: "flex",
9598
9598
  flexDirection: "column",
@@ -9641,7 +9641,7 @@ const LoopModeOptions = LOOP_MODES.map((lm) => ({ label: lm, value: lm }));
9641
9641
  * @returns The add animation panel component
9642
9642
  */
9643
9643
  const AddAnimationPanel = ({ onClose }) => {
9644
- const styles = useStyles$y();
9644
+ const styles = useStyles$z();
9645
9645
  const { state, actions, observables } = useCurveEditor();
9646
9646
  const [name, setName] = useState("");
9647
9647
  const [mode, setMode] = useState("List");
@@ -9858,7 +9858,7 @@ const AddAnimationPanel = ({ onClose }) => {
9858
9858
  return (jsxs("div", { className: styles.root, children: [jsx("div", { className: styles.header, children: jsx("div", { className: styles.title, children: "Add Animation" }) }), jsxs("div", { className: styles.form, children: [jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Display Name" }), jsx(TextInput, { value: name, onChange: setName })] }), jsx("div", { className: styles.row, children: jsx(StringDropdown, { value: mode, onChange: (val) => setMode(val), options: ModeOptions, disabled: properties.length === 0, infoLabel: { label: "Mode" } }) }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Property" }), isCustomMode ? (jsx(TextInput, { value: customProperty, onChange: setCustomProperty })) : (jsx(StringDropdown, { value: selectedProperty, onChange: (val) => setSelectedProperty(val), options: properties.map((p) => ({ label: p, value: p })) }))] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Type" }), isCustomMode ? (jsx(StringDropdown, { value: animationType, onChange: (val) => setAnimationType(val), options: AnimationTypeOptions })) : (jsx("div", { className: styles.typeDisplay, children: inferredType }))] }), jsx("div", { className: styles.row, children: jsx(StringDropdown, { value: loopMode, onChange: (val) => setLoopMode(val), options: LoopModeOptions, infoLabel: { label: "Loop Mode" } }) })] }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: createAnimation, disabled: !isValid, label: "Create" }), jsx(Button, { appearance: "subtle", onClick: onClose, label: "Cancel" })] })] }));
9859
9859
  };
9860
9860
 
9861
- const useStyles$x = makeStyles({
9861
+ const useStyles$y = makeStyles({
9862
9862
  root: {
9863
9863
  display: "flex",
9864
9864
  flexDirection: "column",
@@ -9901,7 +9901,7 @@ const useStyles$x = makeStyles({
9901
9901
  * @returns The load animation panel component
9902
9902
  */
9903
9903
  const LoadAnimationPanel = ({ onClose }) => {
9904
- const styles = useStyles$x();
9904
+ const styles = useStyles$y();
9905
9905
  const { state, actions, observables } = useCurveEditor();
9906
9906
  const [snippetIdInput, setSnippetIdInput] = useState("");
9907
9907
  const [loadedSnippetId, setLoadedSnippetId] = useState(null);
@@ -10040,7 +10040,7 @@ class StringTools {
10040
10040
  }
10041
10041
  }
10042
10042
 
10043
- const useStyles$w = makeStyles({
10043
+ const useStyles$x = makeStyles({
10044
10044
  root: {
10045
10045
  display: "flex",
10046
10046
  flexDirection: "column",
@@ -10085,7 +10085,7 @@ const useStyles$w = makeStyles({
10085
10085
  * @returns The save animation panel component
10086
10086
  */
10087
10087
  const SaveAnimationPanel = ({ onClose: _onClose }) => {
10088
- const styles = useStyles$w();
10088
+ const styles = useStyles$x();
10089
10089
  const { state } = useCurveEditor();
10090
10090
  const [selectedAnimations, setSelectedAnimations] = useState(() => {
10091
10091
  if (!state.animations) {
@@ -10158,7 +10158,7 @@ const SaveAnimationPanel = ({ onClose: _onClose }) => {
10158
10158
  }) }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: saveToSnippetServer, disabled: selectedAnimations.length === 0 || isSaving, label: isSaving ? "Saving..." : "Save to Snippet Server" }), jsx(Button, { appearance: "secondary", onClick: saveToFile, disabled: selectedAnimations.length === 0, label: "Save to File" })] }), saveError && jsx("div", { className: styles.errorText, children: saveError }), snippetId && jsxs("div", { className: styles.snippetId, children: ["Saved! Snippet ID: ", snippetId] })] }));
10159
10159
  };
10160
10160
 
10161
- const useStyles$v = makeStyles({
10161
+ const useStyles$w = makeStyles({
10162
10162
  root: {
10163
10163
  display: "flex",
10164
10164
  flexDirection: "column",
@@ -10212,7 +10212,7 @@ const useStyles$v = makeStyles({
10212
10212
  * @returns The sidebar component
10213
10213
  */
10214
10214
  const SideBar = () => {
10215
- const styles = useStyles$v();
10215
+ const styles = useStyles$w();
10216
10216
  const { state, actions, observables } = useCurveEditor();
10217
10217
  const [openPopover, setOpenPopover] = useState(null);
10218
10218
  const [fps, setFps] = useState(60);
@@ -11200,7 +11200,7 @@ const KeyPointComponent = (props) => {
11200
11200
  } })] }))] }))] }));
11201
11201
  };
11202
11202
 
11203
- const useStyles$u = makeStyles({
11203
+ const useStyles$v = makeStyles({
11204
11204
  root: {
11205
11205
  position: "absolute",
11206
11206
  top: 0,
@@ -11384,7 +11384,7 @@ function ExtractValuesFromKeys(keys, curves) {
11384
11384
  * @returns The graph component
11385
11385
  */
11386
11386
  const Graph = ({ width, height }) => {
11387
- const styles = useStyles$u();
11387
+ const styles = useStyles$v();
11388
11388
  const { state, actions, observables } = useCurveEditor();
11389
11389
  const svgRef = useRef(null);
11390
11390
  const [scale, setScale] = useState(1);
@@ -11747,7 +11747,7 @@ const Graph = ({ width, height }) => {
11747
11747
  }), renderValueAxis()] })] }));
11748
11748
  };
11749
11749
 
11750
- const useStyles$t = makeStyles({
11750
+ const useStyles$u = makeStyles({
11751
11751
  root: {
11752
11752
  position: "absolute",
11753
11753
  top: 0,
@@ -11790,7 +11790,7 @@ const useStyles$t = makeStyles({
11790
11790
  * @returns The playhead component
11791
11791
  */
11792
11792
  const PlayHead = ({ width, height: _height }) => {
11793
- const styles = useStyles$t();
11793
+ const styles = useStyles$u();
11794
11794
  const { state, actions, observables } = useCurveEditor();
11795
11795
  const [isDragging, setIsDragging] = useState(false);
11796
11796
  // Use refs for all mutable values to avoid render cycles
@@ -11941,7 +11941,7 @@ const PlayHead = ({ width, height: _height }) => {
11941
11941
  return (jsxs("div", { className: styles.root, children: [jsx("div", { ref: lineRef, className: styles.line, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp }), jsx("div", { ref: handleRef, className: styles.handle, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp })] }));
11942
11942
  };
11943
11943
 
11944
- const useStyles$s = makeStyles({
11944
+ const useStyles$t = makeStyles({
11945
11945
  root: {
11946
11946
  display: "flex",
11947
11947
  flexDirection: "row",
@@ -11973,7 +11973,7 @@ const useStyles$s = makeStyles({
11973
11973
  * @returns The frame bar component
11974
11974
  */
11975
11975
  const FrameBar = ({ width }) => {
11976
- const styles = useStyles$s();
11976
+ const styles = useStyles$t();
11977
11977
  const { state, observables } = useCurveEditor();
11978
11978
  const containerRef = useRef(null);
11979
11979
  const [scale, setScale] = useState(1);
@@ -12031,7 +12031,7 @@ const FrameBar = ({ width }) => {
12031
12031
  return (jsx("div", { className: styles.root, ref: containerRef, children: renderTicks() }));
12032
12032
  };
12033
12033
 
12034
- const useStyles$r = makeStyles({
12034
+ const useStyles$s = makeStyles({
12035
12035
  root: {
12036
12036
  display: "flex",
12037
12037
  flexDirection: "column",
@@ -12072,7 +12072,7 @@ const OFFSET_X = 10;
12072
12072
  * @returns The range frame bar component
12073
12073
  */
12074
12074
  const RangeFrameBar = ({ width }) => {
12075
- const styles = useStyles$r();
12075
+ const styles = useStyles$s();
12076
12076
  const { state, actions, observables } = useCurveEditor();
12077
12077
  const svgRef = useRef(null);
12078
12078
  const [viewWidth, setViewWidth] = useState(width);
@@ -12183,7 +12183,7 @@ const RangeFrameBar = ({ width }) => {
12183
12183
  }), renderKeyframes, renderActiveFrame] }) }));
12184
12184
  };
12185
12185
 
12186
- const useStyles$q = makeStyles({
12186
+ const useStyles$r = makeStyles({
12187
12187
  root: {
12188
12188
  display: "flex",
12189
12189
  flexDirection: "column",
@@ -12215,7 +12215,7 @@ const useStyles$q = makeStyles({
12215
12215
  * @returns The canvas component
12216
12216
  */
12217
12217
  const Canvas = () => {
12218
- const styles = useStyles$q();
12218
+ const styles = useStyles$r();
12219
12219
  const { observables } = useCurveEditor();
12220
12220
  const containerRef = useRef(null);
12221
12221
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
@@ -12245,7 +12245,7 @@ const Canvas = () => {
12245
12245
  return (jsxs("div", { className: styles.root, ref: containerRef, children: [jsx("div", { className: styles.frameBar, children: jsx(FrameBar, { width: dimensions.width }) }), jsxs("div", { className: styles.canvasArea, children: [jsx(Graph, { width: dimensions.width, height: dimensions.height - 70 }), jsx(PlayHead, { width: dimensions.width, height: dimensions.height - 70 })] }), jsx("div", { className: styles.rangeFrameBar, children: jsx(RangeFrameBar, { width: dimensions.width }) })] }));
12246
12246
  };
12247
12247
 
12248
- const useStyles$p = makeStyles({
12248
+ const useStyles$q = makeStyles({
12249
12249
  root: {
12250
12250
  flex: 1,
12251
12251
  height: "25px",
@@ -12307,7 +12307,7 @@ const useStyles$p = makeStyles({
12307
12307
  * @returns The range selector component
12308
12308
  */
12309
12309
  const RangeSelector = () => {
12310
- const styles = useStyles$p();
12310
+ const styles = useStyles$q();
12311
12311
  const { state, actions, observables } = useCurveEditor();
12312
12312
  const containerRef = useRef(null);
12313
12313
  const scrollbarRef = useRef(null);
@@ -12452,7 +12452,7 @@ function GetKeyAtAnyFrameIndex(animations, frame) {
12452
12452
  }
12453
12453
  return false;
12454
12454
  }
12455
- const useStyles$o = makeStyles({
12455
+ const useStyles$p = makeStyles({
12456
12456
  root: {
12457
12457
  display: "flex",
12458
12458
  flexDirection: "row",
@@ -12509,7 +12509,7 @@ MediaControls.displayName = "MediaControls";
12509
12509
  * @returns The BottomBar component.
12510
12510
  */
12511
12511
  const BottomBar = () => {
12512
- const styles = useStyles$o();
12512
+ const styles = useStyles$p();
12513
12513
  const { state, actions, observables } = useCurveEditor();
12514
12514
  // Track display frame separately for smooth updates during playback
12515
12515
  const [displayFrame, setDisplayFrame] = useState(state.activeFrame);
@@ -12624,7 +12624,7 @@ const BottomBar = () => {
12624
12624
  return (jsxs("div", { className: styles.root, children: [jsx("div", { className: styles.mediaControls, children: jsx(MediaControls, { hasActiveAnimations: hasActiveAnimations, isPlaying: state.isPlaying, forwardAnimation: state.forwardAnimation, onPlayForward: handlePlayForward, onPlayBackward: handlePlayBackward, onStop: handleStop, onPrevKey: handlePrevKey, onNextKey: handleNextKey, onFirstFrame: handleFirstFrame, onLastFrame: handleLastFrame }) }), jsxs("div", { className: styles.frameDisplay, children: [jsx("div", { className: styles.frameLabel, children: "Frame:" }), jsx(SpinButton, { className: styles.spinButton, value: displayFrame, onChange: handleFrameChange, min: state.fromKey, max: state.toKey, disabled: !hasActiveAnimations })] }), jsx(RangeSelector, {}), jsxs("div", { className: styles.clipLengthSection, children: [jsx("div", { className: styles.frameLabel, children: "Clip Length:" }), jsx(SpinButton, { className: styles.spinButton, value: clipLength, onChange: handleClipLengthChange, min: 1, disabled: !hasActiveAnimations })] })] }));
12625
12625
  };
12626
12626
 
12627
- const useStyles$n = makeStyles({
12627
+ const useStyles$o = makeStyles({
12628
12628
  root: {
12629
12629
  display: "flex",
12630
12630
  flexDirection: "column",
@@ -12673,7 +12673,7 @@ const useStyles$n = makeStyles({
12673
12673
  * @returns The curve editor content
12674
12674
  */
12675
12675
  const CurveEditorContent = () => {
12676
- const styles = useStyles$n();
12676
+ const styles = useStyles$o();
12677
12677
  const { state, actions, observables } = useCurveEditor();
12678
12678
  const rootRef = useRef(null);
12679
12679
  const prepareRef = useRef(() => actions.prepare());
@@ -13498,6 +13498,38 @@ const AreaLightSetupProperties = ({ context: areaLight }) => {
13498
13498
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Diffuse", component: Color3PropertyLine, target: areaLight, propertyKey: "diffuse" }), jsx(BoundProperty, { label: "Specular", component: Color3PropertyLine, target: areaLight, propertyKey: "specular" }), jsx(BoundProperty, { label: "Position", component: Vector3PropertyLine, target: areaLight, propertyKey: "position" }), jsx(BoundProperty, { label: "Width", component: NumberInputPropertyLine, target: areaLight, propertyKey: "width" }), jsx(BoundProperty, { label: "Height", component: NumberInputPropertyLine, target: areaLight, propertyKey: "height" }), jsx(BoundProperty, { label: "Intensity", component: NumberInputPropertyLine, target: areaLight, propertyKey: "intensity" })] }));
13499
13499
  };
13500
13500
 
13501
+ /**
13502
+ * Return a copied array and re-render when array mutators run.
13503
+ * Intercept add/remove/change functions because the underlying APIs update internal arrays in-place.
13504
+ * @param target The target object containing the observable array, or null if the array is not applicable.
13505
+ * @param getItems A function to get the current items in the array.
13506
+ * @param addFn The name of the function to add an item to the array.
13507
+ * @param removeFn The name of the function to remove an item from the array.
13508
+ * @param changeFn The name of the function to change an item in the array.
13509
+ * @returns A copied array that re-renders when array mutators run.
13510
+ */
13511
+ function useObservableArray(target, getItems, addFn, removeFn, changeFn) {
13512
+ return useObservableState(useCallback(() => {
13513
+ const value = getItems();
13514
+ return [...(value ?? [])];
13515
+ }, [getItems]), useInterceptObservable("function", target, addFn), useInterceptObservable("function", target, removeFn), changeFn ? useInterceptObservable("function", target, changeFn) : undefined);
13516
+ }
13517
+
13518
+ const useStyles$n = makeStyles({
13519
+ lightsListDiv: {
13520
+ display: "flex",
13521
+ flexDirection: "column",
13522
+ },
13523
+ });
13524
+ const ClusteredLightContainerSetupProperties = ({ context: container }) => {
13525
+ return (jsxs(Fragment, { children: [jsx(BooleanBadgePropertyLine, { label: "Is Supported", value: container.isSupported }), jsx(BoundProperty, { label: "Horizontal Tiles", component: NumberInputPropertyLine, target: container, propertyKey: "horizontalTiles", step: 1, min: 1, forceInt: true }), jsx(BoundProperty, { label: "Vertical Tiles", component: NumberInputPropertyLine, target: container, propertyKey: "verticalTiles", step: 1, min: 1, forceInt: true }), jsx(BoundProperty, { label: "Depth Slices", component: NumberInputPropertyLine, target: container, propertyKey: "depthSlices", step: 1, min: 1, forceInt: true }), jsx(BoundProperty, { label: "Max Range", component: NumberInputPropertyLine, target: container, propertyKey: "maxRange", min: 1 })] }));
13526
+ };
13527
+ const ClusteredLightContainerLightsProperties = ({ container, selectionService, }) => {
13528
+ const classes = useStyles$n();
13529
+ const lights = useObservableArray(container, useCallback(() => container.lights, [container]), "addLight", "removeLight");
13530
+ return (jsx(PropertyLine, { label: "Lights", expandedContent: jsx("div", { className: classes.lightsListDiv, children: lights.map((light) => (jsx(LinkToEntityPropertyLine, { label: light.getClassName(), entity: light, selectionService: selectionService }, light.uniqueId))) }), children: jsx(Badge, { appearance: "filled", children: lights.length }) }));
13531
+ };
13532
+
13501
13533
  const DirectionalLightSetupProperties = ({ context: directionalLight }) => {
13502
13534
  const scene = directionalLight.getScene();
13503
13535
  const camera = scene.activeCamera;
@@ -13543,19 +13575,316 @@ const HemisphericLightSetupProperties = ({ context: hemisphericLight }) => {
13543
13575
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Direction", component: Vector3PropertyLine, target: hemisphericLight, propertyKey: "direction" }), jsx(BoundProperty, { label: "Diffuse", component: Color3PropertyLine, target: hemisphericLight, propertyKey: "diffuse" }), jsx(BoundProperty, { label: "Ground", component: Color3PropertyLine, target: hemisphericLight, propertyKey: "groundColor" }), jsx(BoundProperty, { label: "Intensity", component: NumberInputPropertyLine, target: hemisphericLight, propertyKey: "intensity" })] }));
13544
13576
  };
13545
13577
 
13546
- const PointLightSetupProperties = ({ context: pointLight }) => {
13547
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Diffuse", component: Color3PropertyLine, target: pointLight, propertyKey: "diffuse" }), jsx(BoundProperty, { label: "Specular", component: Color3PropertyLine, target: pointLight, propertyKey: "specular" }), jsx(BoundProperty, { label: "Position", component: Vector3PropertyLine, target: pointLight, propertyKey: "position" }), jsx(BoundProperty, { label: "Intensity", component: NumberInputPropertyLine, target: pointLight, propertyKey: "intensity" })] }));
13548
- };
13578
+ const useStyles$m = makeStyles({
13579
+ root: {
13580
+ display: "grid",
13581
+ gridTemplateRows: "repeat(1fr)",
13582
+ justifyItems: "start",
13583
+ gap: "2px",
13584
+ maxWidth: "400px",
13585
+ },
13586
+ comboBox: {
13587
+ width: CustomTokens.valueWidth,
13588
+ minWidth: CustomTokens.valueWidth,
13589
+ boxSizing: "border-box",
13590
+ },
13591
+ input: {
13592
+ minWidth: 0,
13593
+ },
13594
+ listbox: {
13595
+ width: "fit-content",
13596
+ minWidth: "fit-content",
13597
+ maxWidth: "350px",
13598
+ },
13599
+ });
13600
+ /**
13601
+ * Wrapper around a Fluent ComboBox that allows for filtering options.
13602
+ * @param props
13603
+ * @returns
13604
+ */
13605
+ const ComboBox = forwardRef((props, ref) => {
13606
+ ComboBox.displayName = "ComboBox";
13607
+ const comboId = useId();
13608
+ const styles = useStyles$m();
13609
+ const { size } = useContext(ToolContext);
13610
+ // Find the label for the current value
13611
+ const getLabel = (value) => props.options.find((opt) => opt.value === value)?.label ?? "";
13612
+ const [query, setQuery] = useState(getLabel(props.value ?? ""));
13613
+ useEffect(() => {
13614
+ setQuery(getLabel(props.value ?? ""));
13615
+ }, [props.value, props.options]);
13616
+ // Convert to Fluent's { children, value } format
13617
+ const normalizedOptions = props.options.map((opt) => ({ children: opt.label, value: opt.value }));
13618
+ const children = useComboboxFilter(query, normalizedOptions, {
13619
+ noOptionsMessage: "No items match your search.",
13620
+ optionToReactKey: (option) => option.value,
13621
+ optionToText: (option) => option.children,
13622
+ renderOption: (option) => (jsx(Option, { value: option.value, text: option.children, children: option.children }, option.value)),
13623
+ });
13624
+ const onOptionSelect = (_e, data) => {
13625
+ setQuery(data.optionText ?? "");
13626
+ data.optionValue && props.onChange(data.optionValue);
13627
+ };
13628
+ return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { ref: ref, defaultOpen: props.defaultOpen, size: size, root: { className: styles.comboBox }, input: { className: styles.input }, listbox: { className: styles.listbox }, onOptionSelect: onOptionSelect, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
13629
+ });
13549
13630
 
13550
- const DefaultShadowGeneratorOptions = [{ label: "Shadow Generator", value: "Default" }];
13551
- const DirectionalLightGeneratorOptions = [
13552
- ...DefaultShadowGeneratorOptions,
13553
- { label: "Cascaded Shadow Generator", value: "Cascade" },
13554
- ];
13555
- const MapSizeOptions = [
13556
- { label: "4096 x 4096", value: 4096 },
13557
- { label: "2048 x 2048", value: 2048 },
13558
- { label: "1024 x 1024", value: 1024 },
13631
+ const useStyles$l = makeStyles({
13632
+ linkDiv: {
13633
+ display: "flex",
13634
+ flexDirection: "row",
13635
+ alignItems: "center",
13636
+ gap: tokens.spacingHorizontalS,
13637
+ minWidth: 0,
13638
+ overflow: "hidden",
13639
+ },
13640
+ link: {
13641
+ minWidth: 0,
13642
+ overflow: "hidden",
13643
+ textOverflow: "ellipsis",
13644
+ whiteSpace: "nowrap",
13645
+ },
13646
+ });
13647
+ /**
13648
+ * A generic primitive component with a ComboBox for selecting from a list of entities.
13649
+ * Supports entities with duplicate names by using uniqueId for identity.
13650
+ * @param props ChooseEntityProps
13651
+ * @returns EntitySelector component
13652
+ */
13653
+ function EntitySelector(props) {
13654
+ const { value, onLink, getEntities, getName, filter, defaultValue } = props;
13655
+ const onChange = props.onChange;
13656
+ const classes = useStyles$l();
13657
+ const comboBoxRef = useRef(null);
13658
+ // Build options with uniqueId as key
13659
+ const options = useMemo(() => {
13660
+ return getEntities()
13661
+ .filter((e) => e.uniqueId !== undefined && (!filter || filter(e)))
13662
+ .map((entity) => ({
13663
+ label: getName(entity)?.toString() || "",
13664
+ value: entity.uniqueId.toString(),
13665
+ }))
13666
+ .sort((a, b) => a.label.localeCompare(b.label));
13667
+ }, [getEntities, getName, filter]);
13668
+ const [isEditing, setIsEditing] = useState(false);
13669
+ const [enteringEditMode, pulseEnteringEditMode] = useImpulse();
13670
+ useEffect(() => {
13671
+ if (enteringEditMode) {
13672
+ comboBoxRef.current?.focus();
13673
+ }
13674
+ }, [enteringEditMode]);
13675
+ const handleEntitySelect = (key) => {
13676
+ const entity = getEntities().find((e) => e.uniqueId.toString() === key);
13677
+ onChange?.(entity ?? null);
13678
+ setIsEditing(false);
13679
+ };
13680
+ // Get current entity key for display
13681
+ const currentKey = value ? value.uniqueId.toString() : "";
13682
+ if (value && !isEditing) {
13683
+ // If there is a value and we are not editing, show the link view
13684
+ return (jsxs("div", { className: classes.linkDiv, children: [jsx(Tooltip$1, { content: getName(value), relationship: "label", children: jsx(Link, { className: classes.link, value: getName(value), onLink: () => onLink(value) }) }), onChange &&
13685
+ (defaultValue !== undefined ? (
13686
+ // If the defaultValue is specified, then allow resetting to the default
13687
+ jsx(Tooltip$1, { content: "Unlink", relationship: "label", children: jsx(Button, { icon: LinkDismissRegular, onClick: () => {
13688
+ pulseEnteringEditMode(true);
13689
+ onChange(defaultValue);
13690
+ } }) })) : (
13691
+ // Otherwise, just allow editing to a new value
13692
+ jsx(Tooltip$1, { content: "Edit Link", relationship: "label", children: jsx(Button, { icon: LinkEditRegular, onClick: () => {
13693
+ pulseEnteringEditMode(true);
13694
+ setIsEditing(true);
13695
+ } }) })))] }));
13696
+ }
13697
+ else {
13698
+ // Otherwise, show the ComboBox for selection
13699
+ return jsx(ComboBox, { ref: comboBoxRef, defaultOpen: enteringEditMode, label: "", options: options, value: currentKey, onChange: handleEntitySelect });
13700
+ }
13701
+ }
13702
+ EntitySelector.displayName = "EntitySelector";
13703
+
13704
+ /**
13705
+ * A primitive component with a ComboBox for selecting from existing scene clustered light containers.
13706
+ * @param props ClusteredLightContainerSelectorProps
13707
+ * @returns ClusteredLightContainerSelector component
13708
+ */
13709
+ const ClusteredLightContainerSelector = (props) => {
13710
+ ClusteredLightContainerSelector.displayName = "ClusteredLightContainerSelector";
13711
+ const { scene, ...rest } = props;
13712
+ const getClusteredLightContainers = useCallback(() => scene.lights.filter((light) => light instanceof ClusteredLightContainer), [scene.lights]);
13713
+ const getName = useCallback((container) => container.name, []);
13714
+ return jsx(EntitySelector, { ...rest, getEntities: getClusteredLightContainers, getName: getName });
13715
+ };
13716
+
13717
+ /**
13718
+ * A primitive component with a ComboBox for selecting from existing scene materials.
13719
+ * @param props MaterialSelectorProps
13720
+ * @returns MaterialSelector component
13721
+ */
13722
+ const MaterialSelector = (props) => {
13723
+ MaterialSelector.displayName = "MaterialSelector";
13724
+ const { scene, ...rest } = props;
13725
+ const getMaterials = useCallback(() => scene.materials, [scene.materials]);
13726
+ const getName = useCallback((material) => material.name, []);
13727
+ return jsx(EntitySelector, { ...rest, getEntities: getMaterials, getName: getName });
13728
+ };
13729
+
13730
+ /**
13731
+ * A primitive component with a ComboBox for selecting from existing scene nodes.
13732
+ * @param props NodeSelectorProps
13733
+ * @returns NodeSelector component
13734
+ */
13735
+ const NodeSelector = (props) => {
13736
+ NodeSelector.displayName = "NodeSelector";
13737
+ const { scene, ...rest } = props;
13738
+ const getNodes = useCallback(() => scene.getNodes(), [scene]);
13739
+ const getName = useCallback((node) => node.name, []);
13740
+ return jsx(EntitySelector, { ...rest, getEntities: getNodes, getName: getName });
13741
+ };
13742
+
13743
+ /**
13744
+ * A primitive component with a ComboBox for selecting from existing scene skeletons.
13745
+ * @param props SkeletonSelectorProps
13746
+ * @returns SkeletonSelector component
13747
+ */
13748
+ const SkeletonSelector = (props) => {
13749
+ SkeletonSelector.displayName = "SkeletonSelector";
13750
+ const { scene, ...rest } = props;
13751
+ const getSkeletons = useCallback(() => scene.skeletons, [scene.skeletons]);
13752
+ const getName = useCallback((skeleton) => skeleton.name, []);
13753
+ return jsx(EntitySelector, { ...rest, getEntities: getSkeletons, getName: getName });
13754
+ };
13755
+
13756
+ /**
13757
+ * A button that uploads a file and either:
13758
+ * - Updates an existing Texture or CubeTexture via updateURL (if texture prop is provided)
13759
+ * - Creates a new Texture or CubeTexture (if scene/onChange props are provided)
13760
+ * @param props TextureUploadProps
13761
+ * @returns UploadButton component that handles texture upload
13762
+ */
13763
+ const TextureUpload = (props) => {
13764
+ TextureUpload.displayName = "TextureUpload";
13765
+ const label = props.texture ? "Upload Texture" : undefined;
13766
+ // TODO: This should probably be dynamically fetching a list of supported texture extensions
13767
+ const accept = ".jpg, .png, .tga, .dds, .env, .exr";
13768
+ const handleUpload = useCallback((files) => {
13769
+ const file = files[0];
13770
+ if (!file) {
13771
+ return;
13772
+ }
13773
+ ReadFile(file, (data) => {
13774
+ const blob = new Blob([data], { type: "octet/stream" });
13775
+ // Update existing texture
13776
+ if (props.texture) {
13777
+ const { texture, onChange } = props;
13778
+ const reader = new FileReader();
13779
+ reader.readAsDataURL(blob);
13780
+ reader.onloadend = () => {
13781
+ const base64data = reader.result;
13782
+ if (texture instanceof CubeTexture) {
13783
+ let extension = undefined;
13784
+ if (file.name.toLowerCase().indexOf(".dds") > 0) {
13785
+ extension = ".dds";
13786
+ }
13787
+ else if (file.name.toLowerCase().indexOf(".env") > 0) {
13788
+ extension = ".env";
13789
+ }
13790
+ texture.updateURL(base64data, extension, () => onChange?.(texture));
13791
+ }
13792
+ else if (texture instanceof Texture) {
13793
+ texture.updateURL(base64data, null, () => onChange?.(texture));
13794
+ }
13795
+ };
13796
+ }
13797
+ else {
13798
+ // Create new texture
13799
+ const { scene, cubeOnly, onChange } = props;
13800
+ const url = URL.createObjectURL(blob);
13801
+ const extension = file.name.split(".").pop()?.toLowerCase();
13802
+ // Revoke the object URL after texture loads to prevent memory leak
13803
+ const revokeUrl = () => URL.revokeObjectURL(url);
13804
+ const newTexture = cubeOnly
13805
+ ? new CubeTexture(url, scene, [], false, undefined, revokeUrl, undefined, undefined, false, extension ? "." + extension : undefined)
13806
+ : new Texture(url, scene, false, false, undefined, revokeUrl);
13807
+ onChange(newTexture);
13808
+ }
13809
+ }, undefined, true);
13810
+ }, [props]);
13811
+ return jsx(UploadButton, { onUpload: handleUpload, accept: accept, title: "Upload Texture", label: label });
13812
+ };
13813
+
13814
+ const useStyles$k = makeStyles({
13815
+ container: {
13816
+ display: "flex",
13817
+ flexDirection: "row",
13818
+ alignItems: "center",
13819
+ gap: tokens.spacingHorizontalS,
13820
+ },
13821
+ });
13822
+ /**
13823
+ * A primitive component with a ComboBox for selecting from existing scene textures
13824
+ * and a button for uploading new texture files.
13825
+ * @param props TextureSelectorProps
13826
+ * @returns TextureSelector component
13827
+ */
13828
+ const TextureSelector = (props) => {
13829
+ TextureSelector.displayName = "TextureSelector";
13830
+ const { scene, cubeOnly, value, onChange, onLink, defaultValue } = props;
13831
+ const classes = useStyles$k();
13832
+ const getTextures = useCallback(() => scene.textures, [scene.textures]);
13833
+ const getName = useCallback((texture) => texture.displayName || texture.name || `${texture.getClassName() || "Unnamed Texture"} (${texture.uniqueId})`, []);
13834
+ const filter = useCallback((texture) => !cubeOnly || texture.isCube, [cubeOnly]);
13835
+ return (jsxs("div", { className: classes.container, children: [jsx(EntitySelector, { value: value, onChange: onChange, onLink: onLink, defaultValue: defaultValue, getEntities: getTextures, getName: getName, filter: filter }), !value && jsx(TextureUpload, { scene: scene, onChange: onChange, cubeOnly: cubeOnly })] }));
13836
+ };
13837
+
13838
+ const NodeSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(NodeSelector, { ...props }) });
13839
+ const MaterialSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(MaterialSelector, { ...props }) });
13840
+ const TextureSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(TextureSelector, { ...props }) });
13841
+ const SkeletonSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(SkeletonSelector, { ...props }) });
13842
+ const ClusteredLightContainerSelectorPropertyLine = (props) => (jsx(PropertyLine, { ...props, children: jsx(ClusteredLightContainerSelector, { ...props }) }));
13843
+
13844
+ function FindOwnerContainer(light) {
13845
+ for (const sceneLight of light.getScene().lights) {
13846
+ if (sceneLight instanceof ClusteredLightContainer && sceneLight.lights.includes(light)) {
13847
+ return sceneLight;
13848
+ }
13849
+ }
13850
+ return null;
13851
+ }
13852
+ function HasClusteredLightContainers(light) {
13853
+ return light.getScene().lights.some((l) => l instanceof ClusteredLightContainer);
13854
+ }
13855
+ const LightGeneralProperties = ({ light, selectionService }) => {
13856
+ const scene = light.getScene();
13857
+ // Intercept addLight/removeLight on the prototype so the observable fires after the
13858
+ // full operation completes (scene observables fire mid-operation, before the internal
13859
+ // lights array is updated).
13860
+ const afterAddLight = useInterceptObservable("function", ClusteredLightContainer.prototype, "addLight");
13861
+ const afterRemoveLight = useInterceptObservable("function", ClusteredLightContainer.prototype, "removeLight");
13862
+ const hasContainers = useObservableState(useCallback(() => HasClusteredLightContainers(light), [light]), scene.onNewLightAddedObservable, scene.onLightRemovedObservable);
13863
+ const container = useObservableState(useCallback(() => FindOwnerContainer(light), [light]), afterAddLight, afterRemoveLight);
13864
+ const onChange = (newContainer) => {
13865
+ if (container) {
13866
+ container.removeLight(light);
13867
+ }
13868
+ if (newContainer) {
13869
+ newContainer.addLight(light);
13870
+ }
13871
+ };
13872
+ return (jsx(Collapse, { visible: hasContainers && ClusteredLightContainer.IsLightSupported(light), children: jsx(ClusteredLightContainerSelectorPropertyLine, { label: "Cluster", description: "The Clustered Light Container that contains this light (if any).", value: container, onChange: onChange, scene: scene, defaultValue: null, onLink: (entity) => (selectionService.selectedEntity = entity) }) }));
13873
+ };
13874
+
13875
+ const PointLightSetupProperties = ({ context: pointLight }) => {
13876
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Diffuse", component: Color3PropertyLine, target: pointLight, propertyKey: "diffuse" }), jsx(BoundProperty, { label: "Specular", component: Color3PropertyLine, target: pointLight, propertyKey: "specular" }), jsx(BoundProperty, { label: "Position", component: Vector3PropertyLine, target: pointLight, propertyKey: "position" }), jsx(BoundProperty, { label: "Intensity", component: NumberInputPropertyLine, target: pointLight, propertyKey: "intensity" })] }));
13877
+ };
13878
+
13879
+ const DefaultShadowGeneratorOptions = [{ label: "Shadow Generator", value: "Default" }];
13880
+ const DirectionalLightGeneratorOptions = [
13881
+ ...DefaultShadowGeneratorOptions,
13882
+ { label: "Cascaded Shadow Generator", value: "Cascade" },
13883
+ ];
13884
+ const MapSizeOptions = [
13885
+ { label: "4096 x 4096", value: 4096 },
13886
+ { label: "2048 x 2048", value: 2048 },
13887
+ { label: "1024 x 1024", value: 1024 },
13559
13888
  { label: "512 x 512", value: 512 },
13560
13889
  { label: "256 x 256", value: 256 },
13561
13890
  ];
@@ -13655,8 +13984,18 @@ const SpotLightSetupProperties = ({ context: spotLight }) => {
13655
13984
 
13656
13985
  const LightPropertiesServiceDefinition = {
13657
13986
  friendlyName: "Light Properties",
13658
- consumes: [PropertiesServiceIdentity],
13659
- factory: (propertiesService) => {
13987
+ consumes: [PropertiesServiceIdentity, SelectionServiceIdentity],
13988
+ factory: (propertiesService, selectionService) => {
13989
+ const lightContentRegistration = propertiesService.addSectionContent({
13990
+ key: "Light Properties",
13991
+ predicate: (entity) => entity instanceof Light,
13992
+ content: [
13993
+ {
13994
+ section: "General",
13995
+ component: ({ context }) => jsx(LightGeneralProperties, { light: context, selectionService: selectionService }),
13996
+ },
13997
+ ],
13998
+ });
13660
13999
  const directionalLightContentRegistration = propertiesService.addSectionContent({
13661
14000
  key: "Directional Light Properties",
13662
14001
  predicate: (entity) => entity instanceof DirectionalLight,
@@ -13725,14 +14064,30 @@ const LightPropertiesServiceDefinition = {
13725
14064
  },
13726
14065
  ],
13727
14066
  });
14067
+ const clusteredLightContainerContentRegistration = propertiesService.addSectionContent({
14068
+ key: "Clustered Light Container Properties",
14069
+ predicate: (entity) => entity instanceof ClusteredLightContainer,
14070
+ content: [
14071
+ {
14072
+ section: "Setup",
14073
+ component: ClusteredLightContainerSetupProperties,
14074
+ },
14075
+ {
14076
+ section: "Lights",
14077
+ component: ({ context }) => jsx(ClusteredLightContainerLightsProperties, { container: context, selectionService: selectionService }),
14078
+ },
14079
+ ],
14080
+ });
13728
14081
  return {
13729
14082
  dispose: () => {
14083
+ clusteredLightContainerContentRegistration.dispose();
13730
14084
  areaLightContentRegistration.dispose();
13731
14085
  shadowLightContentRegistration.dispose();
13732
14086
  spotLightContentRegistration.dispose();
13733
14087
  hemisphericLightContentRegistration.dispose();
13734
14088
  pointLightContentRegistration.dispose();
13735
14089
  directionalLightContentRegistration.dispose();
14090
+ lightContentRegistration.dispose();
13736
14091
  },
13737
14092
  };
13738
14093
  },
@@ -13838,8 +14193,8 @@ const MaterialGeneralProperties = (props) => {
13838
14193
  const MaterialTransparencyProperties = (props) => {
13839
14194
  const { material } = props;
13840
14195
  const hasAlphaCutOff = material instanceof PBRMaterial || material instanceof StandardMaterial;
13841
- const useAlphaTest = useProperty(material, "transparencyMode") == Material.MATERIAL_ALPHATEST;
13842
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Alpha", target: material, propertyKey: "alpha", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Transparency Mode", docLink: "https://doc.babylonjs.com/features/featuresDeepDive/materials/advanced/transparent_rendering/#the-transparencymode-property", target: material, propertyKey: "transparencyMode", options: TransparencyModeOptions, nullable: true, defaultValue: Material.MATERIAL_OPAQUE }), hasAlphaCutOff && useAlphaTest && (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "alphaCutOff", target: material, propertyKey: "alphaCutOff", min: 0, max: 1, step: 0.01 })), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Alpha Mode", docLink: "https://doc.babylonjs.com/features/featuresDeepDive/materials/using/blendModes/#available-blend-modes", target: material, propertyKey: "alphaMode", options: AlphaModeOptions }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Separate Culling Pass", target: material, propertyKey: "separateCullingPass" })] }));
14196
+ const useAlphaTest = useProperty(material, "transparencyMode") === Material.MATERIAL_ALPHATEST;
14197
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Alpha", target: material, propertyKey: "alpha", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Transparency Mode", docLink: "https://doc.babylonjs.com/features/featuresDeepDive/materials/advanced/transparent_rendering/#the-transparencymode-property", target: material, propertyKey: "transparencyMode", options: TransparencyModeOptions, nullable: true, defaultValue: Material.MATERIAL_OPAQUE }), jsx(Collapse, { visible: hasAlphaCutOff && useAlphaTest, children: jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "alphaCutOff", target: material, propertyKey: "alphaCutOff", min: 0, max: 1, step: 0.01 }) }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Alpha Mode", docLink: "https://doc.babylonjs.com/features/featuresDeepDive/materials/using/blendModes/#available-blend-modes", target: material, propertyKey: "alphaMode", options: AlphaModeOptions }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Separate Culling Pass", target: material, propertyKey: "separateCullingPass" })] }));
13843
14198
  };
13844
14199
  const MaterialStencilProperties = (props) => {
13845
14200
  const { material } = props;
@@ -14062,7 +14417,7 @@ async function EditNodeMaterial(material) {
14062
14417
  await material.edit({ nodeEditorConfig: { backgroundColor: material.getScene().clearColor } });
14063
14418
  }
14064
14419
 
14065
- const useStyles$m = makeStyles({
14420
+ const useStyles$j = makeStyles({
14066
14421
  subsection: {
14067
14422
  marginTop: tokens.spacingVerticalM,
14068
14423
  },
@@ -14136,7 +14491,7 @@ const GradientBlockPropertyLine = (props) => {
14136
14491
  };
14137
14492
  const NodeMaterialInputProperties = (props) => {
14138
14493
  const { material } = props;
14139
- const classes = useStyles$m();
14494
+ const classes = useStyles$j();
14140
14495
  const inputBlocks = useObservableState(useCallback(() => {
14141
14496
  const inspectorVisibleInputBlocks = material
14142
14497
  .getInputBlocks()
@@ -14213,403 +14568,151 @@ const OpenPBRMaterialSpecularProperties = (props) => {
14213
14568
  } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness", target: material, propertyKey: "specularRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Specular Roughness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14214
14569
  if (files.length > 0) {
14215
14570
  UpdateTexture(files[0], material, (texture) => (material.specularRoughnessTexture = texture));
14216
- }
14217
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropy", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Specular Roughness Anisotropy", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14218
- if (files.length > 0) {
14219
- UpdateTexture(files[0], material, (texture) => (material.specularRoughnessAnisotropyTexture = texture));
14220
- }
14221
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular IOR", target: material, propertyKey: "specularIor", min: 1, max: 3, step: 0.01 })] }));
14222
- };
14223
- const OpenPBRMaterialTransmissionProperties = (props) => {
14224
- const { material } = props;
14225
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Weight", target: material, propertyKey: "transmissionWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Transmission Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14226
- if (files.length > 0) {
14227
- UpdateTexture(files[0], material, (texture) => (material.transmissionWeightTexture = texture));
14228
- }
14229
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Color", target: material, propertyKey: "transmissionColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Transmission Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14230
- if (files.length > 0) {
14231
- UpdateTexture(files[0], material, (texture) => (material.transmissionColorTexture = texture));
14232
- }
14233
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Depth", target: material, propertyKey: "transmissionDepth", min: 0, step: 0.01 }), jsx(FileUploadLine, { label: "Transmission Depth", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14234
- if (files.length > 0) {
14235
- UpdateTexture(files[0], material, (texture) => (material.transmissionDepthTexture = texture));
14236
- }
14237
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Scatter", target: material, propertyKey: "transmissionScatter", isLinearMode: true }), jsx(FileUploadLine, { label: "Transmission Scatter", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14238
- if (files.length > 0) {
14239
- UpdateTexture(files[0], material, (texture) => (material.transmissionScatterTexture = texture));
14240
- }
14241
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Scatter Anisotropy", target: material, propertyKey: "transmissionScatterAnisotropy", min: -1, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Abbe Number", target: material, propertyKey: "transmissionDispersionAbbeNumber", min: 1, max: 100, step: 1 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Scale", target: material, propertyKey: "transmissionDispersionScale", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Transmission Dispersion Scale", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14242
- if (files.length > 0) {
14243
- UpdateTexture(files[0], material, (texture) => (material.transmissionDispersionScaleTexture = texture));
14244
- }
14245
- } })] }));
14246
- };
14247
- /**
14248
- * Displays the coat layer properties of an OpenPBR material.
14249
- * @param props - The required properties
14250
- * @returns A JSX element representing the coat layer properties.
14251
- */
14252
- const OpenPBRMaterialCoatProperties = (props) => {
14253
- const { material } = props;
14254
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14255
- if (files.length > 0) {
14256
- UpdateTexture(files[0], material, (texture) => (material.coatWeightTexture = texture));
14257
- }
14258
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Coat Color", target: material, propertyKey: "coatColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Coat Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14259
- if (files.length > 0) {
14260
- UpdateTexture(files[0], material, (texture) => (material.coatColorTexture = texture));
14261
- }
14262
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Roughness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14263
- if (files.length > 0) {
14264
- UpdateTexture(files[0], material, (texture) => (material.coatRoughnessTexture = texture));
14265
- }
14266
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropy", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Roughness Anisotropy", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14267
- if (files.length > 0) {
14268
- UpdateTexture(files[0], material, (texture) => (material.coatRoughnessAnisotropyTexture = texture));
14269
- }
14270
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat IOR", target: material, propertyKey: "coatIor", min: 1, max: 3, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkening", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Darkening", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14271
- if (files.length > 0) {
14272
- UpdateTexture(files[0], material, (texture) => (material.coatDarkeningTexture = texture));
14273
- }
14274
- } })] }));
14275
- };
14276
- /**
14277
- * Displays the fuzz layer properties of an OpenPBR material.
14278
- * @param props - The required properties
14279
- * @returns A JSX element representing the fuzz layer properties.
14280
- */
14281
- const OpenPBRMaterialFuzzProperties = (props) => {
14282
- const { material } = props;
14283
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Fuzz Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14284
- if (files.length > 0) {
14285
- UpdateTexture(files[0], material, (texture) => (material.fuzzWeightTexture = texture));
14286
- }
14287
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Fuzz Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14288
- if (files.length > 0) {
14289
- UpdateTexture(files[0], material, (texture) => (material.fuzzColorTexture = texture));
14290
- }
14291
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Fuzz Roughness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14292
- if (files.length > 0) {
14293
- UpdateTexture(files[0], material, (texture) => (material.fuzzRoughnessTexture = texture));
14294
- }
14295
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Number of Samples", target: material, propertyKey: "fuzzSampleNumber", min: 4, max: 64, step: 1 })] }));
14296
- };
14297
- /**
14298
- * Displays the emission properties of an OpenPBR material.
14299
- * @param props - The required properties
14300
- * @returns A JSX element representing the emission properties.
14301
- */
14302
- const OpenPBRMaterialEmissionProperties = (props) => {
14303
- const { material } = props;
14304
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color3PropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Emission Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14305
- if (files.length > 0) {
14306
- UpdateTexture(files[0], material, (texture) => (material.emissionColorTexture = texture));
14307
- }
14308
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Emission Luminance", target: material, propertyKey: "emissionLuminance", min: 0, max: 10, step: 0.01 })] }));
14309
- };
14310
- /**
14311
- * Displays the thin film properties of an OpenPBR material.
14312
- * @param props - The required properties
14313
- * @returns A JSX element representing the thin film properties.
14314
- */
14315
- const OpenPBRMaterialThinFilmProperties = (props) => {
14316
- const { material } = props;
14317
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Thin Film Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14318
- if (files.length > 0) {
14319
- UpdateTexture(files[0], material, (texture) => (material.thinFilmWeightTexture = texture));
14320
- }
14321
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThickness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Thin Film Thickness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14322
- if (files.length > 0) {
14323
- UpdateTexture(files[0], material, (texture) => (material.thinFilmThicknessTexture = texture));
14324
- }
14325
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film IOR", target: material, propertyKey: "thinFilmIor", min: 1, max: 3, step: 0.01 })] }));
14326
- };
14327
- /**
14328
- * Displays the geometry properties of an OpenPBR material.
14329
- * @param props - The required properties
14330
- * @returns A JSX element representing the geometry properties.
14331
- */
14332
- const OpenPBRMaterialGeometryProperties = (props) => {
14333
- const { material } = props;
14334
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Opacity", target: material, propertyKey: "geometryOpacity", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Opacity", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14335
- if (files.length > 0) {
14336
- UpdateTexture(files[0], material, (texture) => (material.geometryOpacityTexture = texture));
14337
- }
14338
- } }), jsx(FileUploadLine, { label: "Geometry Normal", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14339
- if (files.length > 0) {
14340
- UpdateTexture(files[0], material, (texture) => (material.geometryNormalTexture = texture));
14341
- }
14342
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Tangent Angle", target: material, propertyKey: "geometryTangentAngle", min: 0, max: Math.PI, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Tangent", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14343
- if (files.length > 0) {
14344
- UpdateTexture(files[0], material, (texture) => (material.geometryTangentTexture = texture));
14345
- }
14346
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Tangent Angle", target: material, propertyKey: "geometryCoatTangentAngle", min: 0, max: Math.PI, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Coat Normal", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14347
- if (files.length > 0) {
14348
- UpdateTexture(files[0], material, (texture) => (material.geometryCoatNormalTexture = texture));
14349
- }
14350
- } }), jsx(FileUploadLine, { label: "Geometry Coat Tangent", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14351
- if (files.length > 0) {
14352
- UpdateTexture(files[0], material, (texture) => (material.geometryCoatTangentTexture = texture));
14353
- }
14354
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Geometry Thickness", target: material, propertyKey: "geometryThickness", min: 0, step: 0.1 }), jsx(FileUploadLine, { label: "Geometry Thickness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14355
- if (files.length > 0) {
14356
- UpdateTexture(files[0], material, (texture) => (material.geometryThicknessTexture = texture));
14357
- }
14358
- } })] }));
14359
- };
14360
-
14361
- const useStyles$l = makeStyles({
14362
- root: {
14363
- display: "grid",
14364
- gridTemplateRows: "repeat(1fr)",
14365
- justifyItems: "start",
14366
- gap: "2px",
14367
- maxWidth: "400px",
14368
- },
14369
- comboBox: {
14370
- width: CustomTokens.valueWidth,
14371
- minWidth: CustomTokens.valueWidth,
14372
- boxSizing: "border-box",
14373
- },
14374
- input: {
14375
- minWidth: 0,
14376
- },
14377
- listbox: {
14378
- width: "fit-content",
14379
- minWidth: "fit-content",
14380
- maxWidth: "350px",
14381
- },
14382
- });
14383
- /**
14384
- * Wrapper around a Fluent ComboBox that allows for filtering options.
14385
- * @param props
14386
- * @returns
14387
- */
14388
- const ComboBox = forwardRef((props, ref) => {
14389
- ComboBox.displayName = "ComboBox";
14390
- const comboId = useId();
14391
- const styles = useStyles$l();
14392
- const { size } = useContext(ToolContext);
14393
- // Find the label for the current value
14394
- const getLabel = (value) => props.options.find((opt) => opt.value === value)?.label ?? "";
14395
- const [query, setQuery] = useState(getLabel(props.value ?? ""));
14396
- useEffect(() => {
14397
- setQuery(getLabel(props.value ?? ""));
14398
- }, [props.value, props.options]);
14399
- // Convert to Fluent's { children, value } format
14400
- const normalizedOptions = props.options.map((opt) => ({ children: opt.label, value: opt.value }));
14401
- const children = useComboboxFilter(query, normalizedOptions, {
14402
- noOptionsMessage: "No items match your search.",
14403
- optionToReactKey: (option) => option.value,
14404
- optionToText: (option) => option.children,
14405
- renderOption: (option) => (jsx(Option, { value: option.value, text: option.children, children: option.children }, option.value)),
14406
- });
14407
- const onOptionSelect = (_e, data) => {
14408
- setQuery(data.optionText ?? "");
14409
- data.optionValue && props.onChange(data.optionValue);
14410
- };
14411
- return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { ref: ref, defaultOpen: props.defaultOpen, size: size, root: { className: styles.comboBox }, input: { className: styles.input }, listbox: { className: styles.listbox }, onOptionSelect: onOptionSelect, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
14412
- });
14413
-
14414
- const useStyles$k = makeStyles({
14415
- linkDiv: {
14416
- display: "flex",
14417
- flexDirection: "row",
14418
- alignItems: "center",
14419
- gap: tokens.spacingHorizontalS,
14420
- minWidth: 0,
14421
- overflow: "hidden",
14422
- },
14423
- link: {
14424
- minWidth: 0,
14425
- overflow: "hidden",
14426
- textOverflow: "ellipsis",
14427
- whiteSpace: "nowrap",
14428
- },
14429
- });
14430
- /**
14431
- * A generic primitive component with a ComboBox for selecting from a list of entities.
14432
- * Supports entities with duplicate names by using uniqueId for identity.
14433
- * @param props ChooseEntityProps
14434
- * @returns EntitySelector component
14435
- */
14436
- function EntitySelector(props) {
14437
- const { value, onLink, getEntities, getName, filter, defaultValue } = props;
14438
- const onChange = props.onChange;
14439
- const classes = useStyles$k();
14440
- const comboBoxRef = useRef(null);
14441
- // Build options with uniqueId as key
14442
- const options = useMemo(() => {
14443
- return getEntities()
14444
- .filter((e) => e.uniqueId !== undefined && (!filter || filter(e)))
14445
- .map((entity) => ({
14446
- label: getName(entity)?.toString() || "",
14447
- value: entity.uniqueId.toString(),
14448
- }))
14449
- .sort((a, b) => a.label.localeCompare(b.label));
14450
- }, [getEntities, getName, filter]);
14451
- const [isEditing, setIsEditing] = useState(false);
14452
- const [enteringEditMode, pulseEnteringEditMode] = useImpulse();
14453
- useEffect(() => {
14454
- if (enteringEditMode) {
14455
- comboBoxRef.current?.focus();
14456
- }
14457
- }, [enteringEditMode]);
14458
- const handleEntitySelect = (key) => {
14459
- const entity = getEntities().find((e) => e.uniqueId.toString() === key);
14460
- onChange?.(entity ?? null);
14461
- setIsEditing(false);
14462
- };
14463
- // Get current entity key for display
14464
- const currentKey = value ? value.uniqueId.toString() : "";
14465
- if (value && !isEditing) {
14466
- // If there is a value and we are not editing, show the link view
14467
- return (jsxs("div", { className: classes.linkDiv, children: [jsx(Tooltip$1, { content: getName(value), relationship: "label", children: jsx(Link, { className: classes.link, value: getName(value), onLink: () => onLink(value) }) }), onChange &&
14468
- (defaultValue !== undefined ? (
14469
- // If the defaultValue is specified, then allow resetting to the default
14470
- jsx(Tooltip$1, { content: "Unlink", relationship: "label", children: jsx(Button, { icon: LinkDismissRegular, onClick: () => {
14471
- pulseEnteringEditMode(true);
14472
- onChange(defaultValue);
14473
- } }) })) : (
14474
- // Otherwise, just allow editing to a new value
14475
- jsx(Tooltip$1, { content: "Edit Link", relationship: "label", children: jsx(Button, { icon: LinkEditRegular, onClick: () => {
14476
- pulseEnteringEditMode(true);
14477
- setIsEditing(true);
14478
- } }) })))] }));
14479
- }
14480
- else {
14481
- // Otherwise, show the ComboBox for selection
14482
- return jsx(ComboBox, { ref: comboBoxRef, defaultOpen: enteringEditMode, label: "", options: options, value: currentKey, onChange: handleEntitySelect });
14483
- }
14484
- }
14485
- EntitySelector.displayName = "EntitySelector";
14486
-
14571
+ }
14572
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropy", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Specular Roughness Anisotropy", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14573
+ if (files.length > 0) {
14574
+ UpdateTexture(files[0], material, (texture) => (material.specularRoughnessAnisotropyTexture = texture));
14575
+ }
14576
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular IOR", target: material, propertyKey: "specularIor", min: 1, max: 3, step: 0.01 })] }));
14577
+ };
14578
+ const OpenPBRMaterialTransmissionProperties = (props) => {
14579
+ const { material } = props;
14580
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Weight", target: material, propertyKey: "transmissionWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Transmission Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14581
+ if (files.length > 0) {
14582
+ UpdateTexture(files[0], material, (texture) => (material.transmissionWeightTexture = texture));
14583
+ }
14584
+ } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Color", target: material, propertyKey: "transmissionColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Transmission Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14585
+ if (files.length > 0) {
14586
+ UpdateTexture(files[0], material, (texture) => (material.transmissionColorTexture = texture));
14587
+ }
14588
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Depth", target: material, propertyKey: "transmissionDepth", min: 0, step: 0.01 }), jsx(FileUploadLine, { label: "Transmission Depth", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14589
+ if (files.length > 0) {
14590
+ UpdateTexture(files[0], material, (texture) => (material.transmissionDepthTexture = texture));
14591
+ }
14592
+ } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Scatter", target: material, propertyKey: "transmissionScatter", isLinearMode: true }), jsx(FileUploadLine, { label: "Transmission Scatter", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14593
+ if (files.length > 0) {
14594
+ UpdateTexture(files[0], material, (texture) => (material.transmissionScatterTexture = texture));
14595
+ }
14596
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Scatter Anisotropy", target: material, propertyKey: "transmissionScatterAnisotropy", min: -1, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Abbe Number", target: material, propertyKey: "transmissionDispersionAbbeNumber", min: 1, max: 100, step: 1 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Scale", target: material, propertyKey: "transmissionDispersionScale", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Transmission Dispersion Scale", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14597
+ if (files.length > 0) {
14598
+ UpdateTexture(files[0], material, (texture) => (material.transmissionDispersionScaleTexture = texture));
14599
+ }
14600
+ } })] }));
14601
+ };
14487
14602
  /**
14488
- * A primitive component with a ComboBox for selecting from existing scene materials.
14489
- * @param props MaterialSelectorProps
14490
- * @returns MaterialSelector component
14603
+ * Displays the coat layer properties of an OpenPBR material.
14604
+ * @param props - The required properties
14605
+ * @returns A JSX element representing the coat layer properties.
14491
14606
  */
14492
- const MaterialSelector = (props) => {
14493
- MaterialSelector.displayName = "MaterialSelector";
14494
- const { scene, ...rest } = props;
14495
- const getMaterials = useCallback(() => scene.materials, [scene.materials]);
14496
- const getName = useCallback((material) => material.name, []);
14497
- return jsx(EntitySelector, { ...rest, getEntities: getMaterials, getName: getName });
14607
+ const OpenPBRMaterialCoatProperties = (props) => {
14608
+ const { material } = props;
14609
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14610
+ if (files.length > 0) {
14611
+ UpdateTexture(files[0], material, (texture) => (material.coatWeightTexture = texture));
14612
+ }
14613
+ } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Coat Color", target: material, propertyKey: "coatColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Coat Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14614
+ if (files.length > 0) {
14615
+ UpdateTexture(files[0], material, (texture) => (material.coatColorTexture = texture));
14616
+ }
14617
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Roughness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14618
+ if (files.length > 0) {
14619
+ UpdateTexture(files[0], material, (texture) => (material.coatRoughnessTexture = texture));
14620
+ }
14621
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropy", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Roughness Anisotropy", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14622
+ if (files.length > 0) {
14623
+ UpdateTexture(files[0], material, (texture) => (material.coatRoughnessAnisotropyTexture = texture));
14624
+ }
14625
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat IOR", target: material, propertyKey: "coatIor", min: 1, max: 3, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkening", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Darkening", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14626
+ if (files.length > 0) {
14627
+ UpdateTexture(files[0], material, (texture) => (material.coatDarkeningTexture = texture));
14628
+ }
14629
+ } })] }));
14498
14630
  };
14499
-
14500
14631
  /**
14501
- * A primitive component with a ComboBox for selecting from existing scene nodes.
14502
- * @param props NodeSelectorProps
14503
- * @returns NodeSelector component
14632
+ * Displays the fuzz layer properties of an OpenPBR material.
14633
+ * @param props - The required properties
14634
+ * @returns A JSX element representing the fuzz layer properties.
14504
14635
  */
14505
- const NodeSelector = (props) => {
14506
- NodeSelector.displayName = "NodeSelector";
14507
- const { scene, ...rest } = props;
14508
- const getNodes = useCallback(() => scene.getNodes(), [scene]);
14509
- const getName = useCallback((node) => node.name, []);
14510
- return jsx(EntitySelector, { ...rest, getEntities: getNodes, getName: getName });
14636
+ const OpenPBRMaterialFuzzProperties = (props) => {
14637
+ const { material } = props;
14638
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Fuzz Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14639
+ if (files.length > 0) {
14640
+ UpdateTexture(files[0], material, (texture) => (material.fuzzWeightTexture = texture));
14641
+ }
14642
+ } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Fuzz Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14643
+ if (files.length > 0) {
14644
+ UpdateTexture(files[0], material, (texture) => (material.fuzzColorTexture = texture));
14645
+ }
14646
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Fuzz Roughness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14647
+ if (files.length > 0) {
14648
+ UpdateTexture(files[0], material, (texture) => (material.fuzzRoughnessTexture = texture));
14649
+ }
14650
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Number of Samples", target: material, propertyKey: "fuzzSampleNumber", min: 4, max: 64, step: 1 })] }));
14511
14651
  };
14512
-
14513
14652
  /**
14514
- * A button that uploads a file and either:
14515
- * - Updates an existing Texture or CubeTexture via updateURL (if texture prop is provided)
14516
- * - Creates a new Texture or CubeTexture (if scene/onChange props are provided)
14517
- * @param props TextureUploadProps
14518
- * @returns UploadButton component that handles texture upload
14653
+ * Displays the emission properties of an OpenPBR material.
14654
+ * @param props - The required properties
14655
+ * @returns A JSX element representing the emission properties.
14519
14656
  */
14520
- const TextureUpload = (props) => {
14521
- TextureUpload.displayName = "TextureUpload";
14522
- const label = props.texture ? "Upload Texture" : undefined;
14523
- // TODO: This should probably be dynamically fetching a list of supported texture extensions
14524
- const accept = ".jpg, .png, .tga, .dds, .env, .exr";
14525
- const handleUpload = useCallback((files) => {
14526
- const file = files[0];
14527
- if (!file) {
14528
- return;
14529
- }
14530
- ReadFile(file, (data) => {
14531
- const blob = new Blob([data], { type: "octet/stream" });
14532
- // Update existing texture
14533
- if (props.texture) {
14534
- const { texture, onChange } = props;
14535
- const reader = new FileReader();
14536
- reader.readAsDataURL(blob);
14537
- reader.onloadend = () => {
14538
- const base64data = reader.result;
14539
- if (texture instanceof CubeTexture) {
14540
- let extension = undefined;
14541
- if (file.name.toLowerCase().indexOf(".dds") > 0) {
14542
- extension = ".dds";
14543
- }
14544
- else if (file.name.toLowerCase().indexOf(".env") > 0) {
14545
- extension = ".env";
14546
- }
14547
- texture.updateURL(base64data, extension, () => onChange?.(texture));
14548
- }
14549
- else if (texture instanceof Texture) {
14550
- texture.updateURL(base64data, null, () => onChange?.(texture));
14657
+ const OpenPBRMaterialEmissionProperties = (props) => {
14658
+ const { material } = props;
14659
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color3PropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Emission Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14660
+ if (files.length > 0) {
14661
+ UpdateTexture(files[0], material, (texture) => (material.emissionColorTexture = texture));
14551
14662
  }
14552
- };
14553
- }
14554
- else {
14555
- // Create new texture
14556
- const { scene, cubeOnly, onChange } = props;
14557
- const url = URL.createObjectURL(blob);
14558
- const extension = file.name.split(".").pop()?.toLowerCase();
14559
- // Revoke the object URL after texture loads to prevent memory leak
14560
- const revokeUrl = () => URL.revokeObjectURL(url);
14561
- const newTexture = cubeOnly
14562
- ? new CubeTexture(url, scene, [], false, undefined, revokeUrl, undefined, undefined, false, extension ? "." + extension : undefined)
14563
- : new Texture(url, scene, false, false, undefined, revokeUrl);
14564
- onChange(newTexture);
14565
- }
14566
- }, undefined, true);
14567
- }, [props]);
14568
- return jsx(UploadButton, { onUpload: handleUpload, accept: accept, title: "Upload Texture", label: label });
14663
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Emission Luminance", target: material, propertyKey: "emissionLuminance", min: 0, max: 10, step: 0.01 })] }));
14569
14664
  };
14570
-
14571
- const useStyles$j = makeStyles({
14572
- container: {
14573
- display: "flex",
14574
- flexDirection: "row",
14575
- alignItems: "center",
14576
- gap: tokens.spacingHorizontalS,
14577
- },
14578
- });
14579
14665
  /**
14580
- * A primitive component with a ComboBox for selecting from existing scene textures
14581
- * and a button for uploading new texture files.
14582
- * @param props TextureSelectorProps
14583
- * @returns TextureSelector component
14666
+ * Displays the thin film properties of an OpenPBR material.
14667
+ * @param props - The required properties
14668
+ * @returns A JSX element representing the thin film properties.
14584
14669
  */
14585
- const TextureSelector = (props) => {
14586
- TextureSelector.displayName = "TextureSelector";
14587
- const { scene, cubeOnly, value, onChange, onLink, defaultValue } = props;
14588
- const classes = useStyles$j();
14589
- const getTextures = useCallback(() => scene.textures, [scene.textures]);
14590
- const getName = useCallback((texture) => texture.displayName || texture.name || `${texture.getClassName() || "Unnamed Texture"} (${texture.uniqueId})`, []);
14591
- const filter = useCallback((texture) => !cubeOnly || texture.isCube, [cubeOnly]);
14592
- return (jsxs("div", { className: classes.container, children: [jsx(EntitySelector, { value: value, onChange: onChange, onLink: onLink, defaultValue: defaultValue, getEntities: getTextures, getName: getName, filter: filter }), !value && jsx(TextureUpload, { scene: scene, onChange: onChange, cubeOnly: cubeOnly })] }));
14670
+ const OpenPBRMaterialThinFilmProperties = (props) => {
14671
+ const { material } = props;
14672
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Thin Film Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14673
+ if (files.length > 0) {
14674
+ UpdateTexture(files[0], material, (texture) => (material.thinFilmWeightTexture = texture));
14675
+ }
14676
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThickness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Thin Film Thickness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14677
+ if (files.length > 0) {
14678
+ UpdateTexture(files[0], material, (texture) => (material.thinFilmThicknessTexture = texture));
14679
+ }
14680
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film IOR", target: material, propertyKey: "thinFilmIor", min: 1, max: 3, step: 0.01 })] }));
14593
14681
  };
14594
-
14595
14682
  /**
14596
- * A primitive component with a ComboBox for selecting from existing scene skeletons.
14597
- * @param props SkeletonSelectorProps
14598
- * @returns SkeletonSelector component
14683
+ * Displays the geometry properties of an OpenPBR material.
14684
+ * @param props - The required properties
14685
+ * @returns A JSX element representing the geometry properties.
14599
14686
  */
14600
- const SkeletonSelector = (props) => {
14601
- SkeletonSelector.displayName = "SkeletonSelector";
14602
- const { scene, ...rest } = props;
14603
- const getSkeletons = useCallback(() => scene.skeletons, [scene.skeletons]);
14604
- const getName = useCallback((skeleton) => skeleton.name, []);
14605
- return jsx(EntitySelector, { ...rest, getEntities: getSkeletons, getName: getName });
14687
+ const OpenPBRMaterialGeometryProperties = (props) => {
14688
+ const { material } = props;
14689
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Opacity", target: material, propertyKey: "geometryOpacity", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Opacity", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14690
+ if (files.length > 0) {
14691
+ UpdateTexture(files[0], material, (texture) => (material.geometryOpacityTexture = texture));
14692
+ }
14693
+ } }), jsx(FileUploadLine, { label: "Geometry Normal", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14694
+ if (files.length > 0) {
14695
+ UpdateTexture(files[0], material, (texture) => (material.geometryNormalTexture = texture));
14696
+ }
14697
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Tangent Angle", target: material, propertyKey: "geometryTangentAngle", min: 0, max: Math.PI, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Tangent", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14698
+ if (files.length > 0) {
14699
+ UpdateTexture(files[0], material, (texture) => (material.geometryTangentTexture = texture));
14700
+ }
14701
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Tangent Angle", target: material, propertyKey: "geometryCoatTangentAngle", min: 0, max: Math.PI, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Coat Normal", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14702
+ if (files.length > 0) {
14703
+ UpdateTexture(files[0], material, (texture) => (material.geometryCoatNormalTexture = texture));
14704
+ }
14705
+ } }), jsx(FileUploadLine, { label: "Geometry Coat Tangent", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14706
+ if (files.length > 0) {
14707
+ UpdateTexture(files[0], material, (texture) => (material.geometryCoatTangentTexture = texture));
14708
+ }
14709
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Geometry Thickness", target: material, propertyKey: "geometryThickness", min: 0, step: 0.1 }), jsx(FileUploadLine, { label: "Geometry Thickness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14710
+ if (files.length > 0) {
14711
+ UpdateTexture(files[0], material, (texture) => (material.geometryThicknessTexture = texture));
14712
+ }
14713
+ } })] }));
14606
14714
  };
14607
14715
 
14608
- const NodeSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(NodeSelector, { ...props }) });
14609
- const MaterialSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(MaterialSelector, { ...props }) });
14610
- const TextureSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(TextureSelector, { ...props }) });
14611
- const SkeletonSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(SkeletonSelector, { ...props }) });
14612
-
14613
14716
  const LightFalloffOptions = [
14614
14717
  { label: "Physical", value: PBRBaseMaterial.LIGHTFALLOFF_PHYSICAL },
14615
14718
  { label: "glTF", value: PBRBaseMaterial.LIGHTFALLOFF_GLTF },
@@ -16123,23 +16226,6 @@ const ParticleSystemEmitterProperties = (props) => {
16123
16226
  } })) : (jsx(Property, { component: TextPropertyLine, propertyPath: "source", label: "Source", value: "No meshes in scene." })) })), particleEmitterType instanceof BoxParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Min emit box", target: particleEmitterType, propertyKey: "minEmitBox" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Max emit box", target: particleEmitterType, propertyKey: "maxEmitBox" })] })), particleEmitterType instanceof ConeParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height range", target: particleEmitterType, propertyKey: "heightRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Emit from spawn point only", target: particleEmitterType, propertyKey: "emitFromSpawnPointOnly" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof SphereParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof CylinderParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height", target: particleEmitterType, propertyKey: "height", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof HemisphericParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof PointParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" })] }))] })), !scene && jsx(TextPropertyLine, { label: "Emitter", value: "No scene available." })] }));
16124
16227
  };
16125
16228
 
16126
- /**
16127
- * Return a copied array and re-render when array mutators run.
16128
- * Intercept add/remove/change functions because the underlying APIs update internal arrays in-place.
16129
- * @param target The target object containing the observable array, or null if the array is not applicable.
16130
- * @param getItems A function to get the current items in the array.
16131
- * @param addFn The name of the function to add an item to the array.
16132
- * @param removeFn The name of the function to remove an item from the array.
16133
- * @param changeFn The name of the function to change an item in the array.
16134
- * @returns A copied array that re-renders when array mutators run.
16135
- */
16136
- function useObservableArray(target, getItems, addFn, removeFn, changeFn) {
16137
- return useObservableState(useCallback(() => {
16138
- const value = getItems();
16139
- return [...(value ?? [])];
16140
- }, [getItems]), useInterceptObservable("function", target, addFn), useInterceptObservable("function", target, removeFn), changeFn ? useInterceptObservable("function", target, changeFn) : undefined);
16141
- }
16142
-
16143
16229
  const useStyles$h = makeStyles({
16144
16230
  subsection: {
16145
16231
  marginTop: tokens.spacingVerticalM,
@@ -17770,18 +17856,19 @@ const SpriteManagerActionsProperties = (props) => {
17770
17856
 
17771
17857
  /**
17772
17858
  * Gets the data of the specified texture by rendering it to an intermediate RGBA texture and retrieving the bytes from it.
17773
- * This is convienent to get 8-bit RGBA values for a texture in a GPU compressed format.
17859
+ * This is convenient to get 8-bit RGBA values for a texture in a GPU compressed format.
17774
17860
  * @param texture the source texture
17775
17861
  * @param width the width of the result, which does not have to match the source texture width
17776
17862
  * @param height the height of the result, which does not have to match the source texture height
17777
- * @param face if the texture has multiple faces, the face index to use for the source
17863
+ * @param faceOrLayer if the texture has multiple faces, the face index to use for the source. For 2D array textures, this is the layer index.
17778
17864
  * @param channels a filter for which of the RGBA channels to return in the result
17779
17865
  * @param lod if the texture has multiple LODs, the lod index to use for the source
17780
17866
  * @returns the 8-bit texture data
17781
17867
  */
17782
- async function ApplyChannelsToTextureDataAsync(texture, width, height, face, channels, lod = 0) {
17868
+ async function ApplyChannelsToTextureDataAsync(texture, width, height, faceOrLayer, channels, lod = 0) {
17783
17869
  // For cube maps, force RTT path to ensure correct face orientation and gamma correction
17784
- const data = await GetTextureDataAsync(texture, width, height, face, lod, texture.isCube);
17870
+ // For 2D array textures, face is reinterpreted as the layer index for direct pixel readback
17871
+ const data = await GetTextureDataAsync(texture, width, height, faceOrLayer, lod, texture.isCube);
17785
17872
  if (!channels.R || !channels.G || !channels.B || !channels.A) {
17786
17873
  for (let i = 0; i < width * height * 4; i += 4) {
17787
17874
  // If alpha is the only channel, just display alpha across all channels
@@ -17863,6 +17950,7 @@ const useStyles$9 = makeStyles({
17863
17950
  controls: {
17864
17951
  display: "flex",
17865
17952
  gap: tokens.spacingHorizontalXS,
17953
+ padding: 0,
17866
17954
  },
17867
17955
  controlButton: {
17868
17956
  minWidth: "auto",
@@ -17883,7 +17971,7 @@ const useStyles$9 = makeStyles({
17883
17971
  display: "flex",
17884
17972
  justifyContent: "center",
17885
17973
  marginTop: tokens.spacingVerticalXS,
17886
- marginBottom: tokens.spacingVerticalS,
17974
+ marginBottom: tokens.spacingVerticalXS,
17887
17975
  width: "100%",
17888
17976
  },
17889
17977
  });
@@ -17901,8 +17989,14 @@ const TexturePreview = (props) => {
17901
17989
  const canvasRef = useRef(null);
17902
17990
  const [channels, setChannels] = useState(TextureChannelStates.ALL);
17903
17991
  const [face, setFace] = useState(0);
17992
+ const [layer, setLayer] = useState(0);
17904
17993
  const [canvasStyle, setCanvasStyle] = useState();
17905
17994
  const internalTexture = useProperty(texture, "_texture");
17995
+ const showLayerDropdown = texture.is2DArray;
17996
+ const layerCount = texture.is2DArray && internalTexture ? internalTexture.depth : 0;
17997
+ useEffect(() => {
17998
+ setLayer((layer) => Clamp(layer, 0, Math.max(0, layerCount - 1)));
17999
+ }, [layerCount]);
17906
18000
  const { size } = useContext(ToolContext);
17907
18001
  // Watch for pinned state changes - when portaled, the canvas needs to be redrawn
17908
18002
  const accordionCtx = useContext(AccordionContext);
@@ -17924,7 +18018,7 @@ const TexturePreview = (props) => {
17924
18018
  const imageWidth = `min(${maxWidth}, calc(${maxHeight} * ${aspectRatio}))`;
17925
18019
  setCanvasStyle({ width: imageWidth });
17926
18020
  // Fetch texture data BEFORE clearing the canvas to avoid flicker
17927
- const data = await ApplyChannelsToTextureDataAsync(texture, textureWidth, textureHeight, face, channels);
18021
+ const data = await ApplyChannelsToTextureDataAsync(texture, textureWidth, textureHeight, texture.is2DArray ? layer : face, channels);
17928
18022
  // Now set canvas dimensions (this clears the canvas) and draw immediately
17929
18023
  canvas.width = canvasWidth;
17930
18024
  canvas.height = canvasHeight;
@@ -17939,7 +18033,7 @@ const TexturePreview = (props) => {
17939
18033
  catch {
17940
18034
  // If we fail, leave the canvas empty
17941
18035
  }
17942
- }, [texture, face, channels, offsetX, offsetY, width, height, internalTexture]);
18036
+ }, [texture, face, channels, offsetX, offsetY, width, height, internalTexture, layer]);
17943
18037
  useImperativeHandle(imperativeRef, () => ({ refresh: updatePreviewAsync }), [updatePreviewAsync]);
17944
18038
  useEffect(() => {
17945
18039
  void updatePreviewAsync();
@@ -17948,7 +18042,7 @@ const TexturePreview = (props) => {
17948
18042
  useEffect(() => {
17949
18043
  void updatePreviewAsync();
17950
18044
  }, [isPinned]);
17951
- return (jsx(LineContainer, { uniqueId: "TexturePreview", children: jsxs("div", { className: classes.root, children: [disableToolbar ? null : texture.isCube ? (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Cube Faces", children: ["+X", "-X", "+Y", "-Y", "+Z", "-Z"].map((label, idx) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: face === idx ? "primary" : "subtle", onClick: () => setFace(idx), children: label }, label))) })) : (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Channels", children: ["R", "G", "B", "A", "ALL"].map((ch) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: channels === TextureChannelStates[ch] ? "primary" : "subtle", onClick: () => setChannels(TextureChannelStates[ch]), children: ch }, ch))) })), jsx("div", { className: classes.previewContainer, children: jsx("canvas", { ref: canvasRef, className: classes.preview, style: canvasStyle }) }), texture.isRenderTarget && (jsx(Button$1, { appearance: "outline", onClick: () => {
18045
+ return (jsx(LineContainer, { uniqueId: "TexturePreview", children: jsxs("div", { className: classes.root, children: [disableToolbar ? null : texture.isCube ? (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Cube Faces", children: ["+X", "-X", "+Y", "-Y", "+Z", "-Z"].map((label, idx) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: face === idx ? "primary" : "subtle", onClick: () => setFace(idx), children: label }, label))) })) : (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Channels", children: ["R", "G", "B", "A", "ALL"].map((ch) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: channels === TextureChannelStates[ch] ? "primary" : "subtle", onClick: () => setChannels(TextureChannelStates[ch]), children: ch }, ch))) })), jsx("div", { className: classes.previewContainer, children: jsx("canvas", { ref: canvasRef, className: classes.preview, style: canvasStyle }) }), !disableToolbar && showLayerDropdown && layerCount > 0 && (jsx(SyncedSliderPropertyLine, { label: "Layer", value: layer, onChange: setLayer, min: 0, max: layerCount - 1, step: 1 })), texture.isRenderTarget && (jsx(Button$1, { appearance: "outline", onClick: () => {
17952
18046
  void updatePreviewAsync();
17953
18047
  }, children: "Refresh" }))] }) }));
17954
18048
  };
@@ -20597,7 +20691,7 @@ const NodeExplorerServiceDefinition = {
20597
20691
  },
20598
20692
  };
20599
20693
  },
20600
- entityIcon: ({ entity: node }) => node instanceof AbstractMesh ? (jsx(MeshIcon, { color: tokens.colorPaletteBlueForeground2 })) : node instanceof TransformNode ? (jsx(MyLocationRegular, { color: tokens.colorPaletteBlueForeground2 })) : node instanceof Camera ? (jsx(CameraRegular, { color: tokens.colorPaletteGreenForeground2 })) : node instanceof Light ? (jsx(LightbulbRegular, { color: tokens.colorPaletteYellowForeground2 })) : (jsx(Fragment, {})),
20694
+ entityIcon: ({ entity: node }) => node instanceof AbstractMesh ? (jsx(MeshIcon, { color: tokens.colorPaletteBlueForeground2 })) : node instanceof TransformNode ? (jsx(MyLocationRegular, { color: tokens.colorPaletteBlueForeground2 })) : node instanceof Camera ? (jsx(CameraRegular, { color: tokens.colorPaletteGreenForeground2 })) : node instanceof ClusteredLightContainer ? (jsx(BubbleMultipleRegular, { color: tokens.colorPaletteYellowForeground2 })) : node instanceof Light ? (jsx(LightbulbRegular, { color: tokens.colorPaletteYellowForeground2 })) : (jsx(Fragment, {})),
20601
20695
  getEntityAddedObservables: () => [
20602
20696
  scene.onNewMeshAddedObservable,
20603
20697
  scene.onNewTransformNodeAddedObservable,
@@ -22922,5 +23016,5 @@ const TextAreaPropertyLine = (props) => {
22922
23016
  // Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
22923
23017
  AttachDebugLayer();
22924
23018
 
22925
- export { HexPropertyLine as $, Accordion as A, Button as B, CheckboxPropertyLine as C, ColorStepGradientComponent as D, ComboBox as E, ComboBoxPropertyLine as F, ConstructorFactory as G, ConvertOptions as H, DebugServiceIdentity as I, DetachDebugLayer as J, DraggableLine as K, LinkToEntity as L, MessageBar as M, NumberInputPropertyLine as N, Dropdown as O, Popover as P, EntitySelector as Q, ErrorBoundary as R, SpinButtonPropertyLine as S, TextInputPropertyLine as T, ExtensibleAccordion as U, Vector3PropertyLine as V, FactorGradientComponent as W, FactorGradientList as X, FileUploadLine as Y, GetPropertyDescriptor as Z, GizmoServiceIdentity as _, useProperty as a, Vector2PropertyLine as a$, InfoLabel as a0, InputHexField as a1, InputHsvField as a2, Inspector as a3, InterceptFunction as a4, InterceptProperty as a5, IsPropertyReadonly as a6, LineContainer as a7, LinkPropertyLine as a8, LinkToEntityPropertyLine as a9, SettingsStoreIdentity as aA, ShowInspector as aB, SidePaneContainer as aC, SkeletonSelector as aD, Slider as aE, SpinButton as aF, StatsServiceIdentity as aG, StringDropdown as aH, StringDropdownPropertyLine as aI, StringifiedPropertyLine as aJ, Switch as aK, SwitchPropertyLine as aL, SyncedSliderInput as aM, SyncedSliderPropertyLine as aN, TeachingMoment as aO, TextAreaPropertyLine as aP, TextInput as aQ, TextPropertyLine as aR, Textarea as aS, TextureSelector as aT, TextureUpload as aU, Theme as aV, ThemeServiceIdentity as aW, ToastProvider as aX, ToggleButton as aY, Tooltip as aZ, UploadButton as a_, List as aa, MakeDialogTeachingMoment as ab, MakeLazyComponent as ac, MakePopoverTeachingMoment as ad, MakePropertyHook as ae, MakeTeachingMoment as af, MaterialSelector as ag, NodeSelector as ah, NumberDropdown as ai, NumberDropdownPropertyLine as aj, ObservableCollection as ak, Pane as al, PlaceholderPropertyLine as am, PositionedPopover as an, PropertiesServiceIdentity as ao, Property as ap, PropertyContext as aq, PropertyLine as ar, QuaternionPropertyLine as as, RotationVectorPropertyLine as at, SceneExplorerServiceIdentity as au, SearchBar as av, SearchBox as aw, SelectionServiceDefinition as ax, SettingsServiceIdentity as ay, SettingsStore as az, ShellServiceIdentity as b, Vector4PropertyLine as b0, WatcherServiceIdentity as b1, useAngleConverters as b2, useAsyncResource as b3, useColor3Property as b4, useColor4Property as b5, useEventListener as b6, useEventfulState as b7, useInterceptObservable as b8, useKeyListener as b9, useKeyState as ba, useObservableCollection as bb, useOrderedObservableCollection as bc, usePollingObservable as bd, usePropertyChangedNotifier as be, useQuaternionProperty as bf, useResource as bg, useSetting as bh, useTheme as bi, useThemeMode as bj, useVector3Property as bk, SceneContextIdentity as c, SelectionServiceIdentity as d, useObservableState as e, AccordionSection as f, ButtonLine as g, ToolsServiceIdentity as h, useExtensionManager as i, Link as j, AccordionSectionItem as k, AttachDebugLayer as l, BooleanBadgePropertyLine as m, BoundProperty as n, BuiltInsExtensionFeed as o, Checkbox as p, ChildWindow as q, Collapse as r, Color3GradientComponent as s, Color3GradientList as t, useToast as u, Color3PropertyLine as v, Color4GradientComponent as w, Color4GradientList as x, Color4PropertyLine as y, ColorPickerPopup as z };
22926
- //# sourceMappingURL=index-_q2JI3vX.js.map
23019
+ export { GizmoServiceIdentity as $, Accordion as A, Button as B, CheckboxPropertyLine as C, ColorPickerPopup as D, ColorStepGradientComponent as E, ComboBox as F, ComboBoxPropertyLine as G, ConstructorFactory as H, ConvertOptions as I, DebugServiceIdentity as J, DetachDebugLayer as K, LinkToEntity as L, MessageBar as M, NumberInputPropertyLine as N, DraggableLine as O, Popover as P, Dropdown as Q, EntitySelector as R, SpinButtonPropertyLine as S, TextInputPropertyLine as T, ErrorBoundary as U, Vector3PropertyLine as V, ExtensibleAccordion as W, FactorGradientComponent as X, FactorGradientList as Y, FileUploadLine as Z, GetPropertyDescriptor as _, useInterceptObservable as a, UploadButton as a$, HexPropertyLine as a0, InfoLabel as a1, InputHexField as a2, InputHsvField as a3, Inspector as a4, InterceptFunction as a5, InterceptProperty as a6, IsPropertyReadonly as a7, LineContainer as a8, LinkPropertyLine as a9, SettingsStore as aA, SettingsStoreIdentity as aB, ShowInspector as aC, SidePaneContainer as aD, SkeletonSelector as aE, Slider as aF, SpinButton as aG, StatsServiceIdentity as aH, StringDropdown as aI, StringDropdownPropertyLine as aJ, StringifiedPropertyLine as aK, Switch as aL, SwitchPropertyLine as aM, SyncedSliderInput as aN, SyncedSliderPropertyLine as aO, TeachingMoment as aP, TextAreaPropertyLine as aQ, TextInput as aR, TextPropertyLine as aS, Textarea as aT, TextureSelector as aU, TextureUpload as aV, Theme as aW, ThemeServiceIdentity as aX, ToastProvider as aY, ToggleButton as aZ, Tooltip as a_, LinkToEntityPropertyLine as aa, List as ab, MakeDialogTeachingMoment as ac, MakeLazyComponent as ad, MakePopoverTeachingMoment as ae, MakePropertyHook as af, MakeTeachingMoment as ag, MaterialSelector as ah, NodeSelector as ai, NumberDropdown as aj, NumberDropdownPropertyLine as ak, ObservableCollection as al, Pane as am, PlaceholderPropertyLine as an, PositionedPopover as ao, PropertiesServiceIdentity as ap, Property as aq, PropertyContext as ar, PropertyLine as as, QuaternionPropertyLine as at, RotationVectorPropertyLine as au, SceneExplorerServiceIdentity as av, SearchBar as aw, SearchBox as ax, SelectionServiceDefinition as ay, SettingsServiceIdentity as az, useProperty as b, Vector2PropertyLine as b0, Vector4PropertyLine as b1, WatcherServiceIdentity as b2, useAngleConverters as b3, useAsyncResource as b4, useColor3Property as b5, useColor4Property as b6, useEventListener as b7, useEventfulState as b8, useKeyListener as b9, useKeyState as ba, useObservableCollection as bb, useOrderedObservableCollection as bc, usePollingObservable as bd, usePropertyChangedNotifier as be, useQuaternionProperty as bf, useResource as bg, useSetting as bh, useTheme as bi, useThemeMode as bj, useVector3Property as bk, ShellServiceIdentity as c, SceneContextIdentity as d, SelectionServiceIdentity as e, useObservableState as f, AccordionSection as g, ButtonLine as h, ToolsServiceIdentity as i, useExtensionManager as j, Link as k, AccordionSectionItem as l, AttachDebugLayer as m, BooleanBadgePropertyLine as n, BoundProperty as o, BuiltInsExtensionFeed as p, Checkbox as q, ChildWindow as r, Collapse as s, Color3GradientComponent as t, useToast as u, Color3GradientList as v, Color3PropertyLine as w, Color4GradientComponent as x, Color4GradientList as y, Color4PropertyLine as z };
23020
+ //# sourceMappingURL=index-CjZdtI-w.js.map