@babylonjs/inspector 8.55.2 → 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';
@@ -77,11 +80,10 @@ import { SkyMaterial } from '@babylonjs/materials/sky/skyMaterial.js';
77
80
  import { Constants } from '@babylonjs/core/Engines/constants.js';
78
81
  import { Engine } from '@babylonjs/core/Engines/engine.js';
79
82
  import { ParticleSystem } from '@babylonjs/core/Particles/particleSystem.js';
83
+ import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial.js';
80
84
  import { GradientBlockColorStep } from '@babylonjs/core/Materials/Node/Blocks/gradientBlock.js';
81
85
  import { NodeMaterialBlockConnectionPointTypes } from '@babylonjs/core/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes.js';
82
86
  import { Color3Gradient, ColorGradient, FactorGradient } from '@babylonjs/core/Misc/gradients.js';
83
- import { ReadFile } from '@babylonjs/core/Misc/fileTools.js';
84
- import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture.js';
85
87
  import { Mesh } from '@babylonjs/core/Meshes/mesh.js';
86
88
  import { SkeletonViewer } from '@babylonjs/core/Debug/skeletonViewer.js';
87
89
  import { VertexBuffer } from '@babylonjs/core/Meshes/buffer.js';
@@ -130,7 +132,6 @@ import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents.js';
130
132
  import { HtmlElementTexture } from '@babylonjs/core/Materials/Textures/htmlElementTexture.js';
131
133
  import { ShaderMaterial } from '@babylonjs/core/Materials/shaderMaterial.js';
132
134
  import { CreatePlane } from '@babylonjs/core/Meshes/Builders/planeBuilder.js';
133
- import { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer.js';
134
135
  import '@babylonjs/core/Rendering/boundingBoxRenderer.js';
135
136
  import '@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipelineManagerSceneComponent.js';
136
137
  import '@babylonjs/core/Sprites/spriteSceneComponent.js';
@@ -280,7 +281,7 @@ const Button = forwardRef((props, ref) => {
280
281
  });
281
282
  Button.displayName = "Button";
282
283
 
283
- const useStyles$Y = makeStyles({
284
+ const useStyles$Z = makeStyles({
284
285
  root: {
285
286
  display: "flex",
286
287
  flexDirection: "column",
@@ -361,7 +362,7 @@ class ErrorBoundary extends Component {
361
362
  }
362
363
  }
363
364
  function ErrorFallback({ error, onRetry }) {
364
- const styles = useStyles$Y();
365
+ const styles = useStyles$Z();
365
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 })] }));
366
367
  }
367
368
 
@@ -552,8 +553,8 @@ function InterceptFunction(target, propertyKey, hooks) {
552
553
  hooksMap.set(propertyKey, (hooksForKey = []));
553
554
  if (
554
555
  // Replace the function with a new one that calls the hooks in addition to the original function.
555
- !Reflect.set(target, propertyKey, (...args) => {
556
- const result = Reflect.apply(originalFunction, target, args);
556
+ !Reflect.set(target, propertyKey, function (...args) {
557
+ const result = Reflect.apply(originalFunction, this, args);
557
558
  for (const { afterCall } of hooksForKey) {
558
559
  afterCall?.(...args);
559
560
  }
@@ -1362,7 +1363,7 @@ function useIsSectionEmpty(sectionId) {
1362
1363
  return hasItems;
1363
1364
  }
1364
1365
 
1365
- const useStyles$X = makeStyles({
1366
+ const useStyles$Y = makeStyles({
1366
1367
  accordion: {
1367
1368
  display: "flex",
1368
1369
  flexDirection: "column",
@@ -1454,7 +1455,7 @@ const useStyles$X = makeStyles({
1454
1455
  */
1455
1456
  const AccordionMenuBar = () => {
1456
1457
  AccordionMenuBar.displayName = "AccordionMenuBar";
1457
- const classes = useStyles$X();
1458
+ const classes = useStyles$Y();
1458
1459
  const accordionCtx = useContext(AccordionContext);
1459
1460
  if (!accordionCtx) {
1460
1461
  return null;
@@ -1478,7 +1479,7 @@ const AccordionMenuBar = () => {
1478
1479
  const AccordionSectionBlock = (props) => {
1479
1480
  AccordionSectionBlock.displayName = "AccordionSectionBlock";
1480
1481
  const { children, sectionId } = props;
1481
- const classes = useStyles$X();
1482
+ const classes = useStyles$Y();
1482
1483
  const accordionCtx = useContext(AccordionContext);
1483
1484
  const { context: sectionContext, isEmpty } = useAccordionSectionBlockContext(props);
1484
1485
  if (accordionCtx) {
@@ -1498,7 +1499,7 @@ const AccordionSectionBlock = (props) => {
1498
1499
  const AccordionSectionItem = (props) => {
1499
1500
  AccordionSectionItem.displayName = "AccordionSectionItem";
1500
1501
  const { children, staticItem } = props;
1501
- const classes = useStyles$X();
1502
+ const classes = useStyles$Y();
1502
1503
  const accordionCtx = useContext(AccordionContext);
1503
1504
  const itemState = useAccordionSectionItemState(props);
1504
1505
  const [ctrlMode, setCtrlMode] = useState(false);
@@ -1538,7 +1539,7 @@ const AccordionSectionItem = (props) => {
1538
1539
  */
1539
1540
  const AccordionPinnedContainer = () => {
1540
1541
  AccordionPinnedContainer.displayName = "AccordionPinnedContainer";
1541
- const classes = useStyles$X();
1542
+ const classes = useStyles$Y();
1542
1543
  const accordionCtx = useContext(AccordionContext);
1543
1544
  return (jsx("div", { ref: accordionCtx?.pinnedContainerRef, className: classes.pinnedContainer, children: jsx(MessageBar$1, { className: classes.pinnedContainerEmpty, children: jsx(MessageBarBody, { children: "No pinned items" }) }) }));
1544
1545
  };
@@ -1549,7 +1550,7 @@ const AccordionPinnedContainer = () => {
1549
1550
  */
1550
1551
  const AccordionSearchBox = () => {
1551
1552
  AccordionSearchBox.displayName = "AccordionSearchBox";
1552
- const classes = useStyles$X();
1553
+ const classes = useStyles$Y();
1553
1554
  const accordionCtx = useContext(AccordionContext);
1554
1555
  if (!accordionCtx?.features.search) {
1555
1556
  return null;
@@ -1565,7 +1566,7 @@ const AccordionSearchBox = () => {
1565
1566
  */
1566
1567
  const AccordionSection = (props) => {
1567
1568
  AccordionSection.displayName = "AccordionSection";
1568
- const classes = useStyles$X();
1569
+ const classes = useStyles$Y();
1569
1570
  return jsx("div", { className: classes.panelDiv, children: props.children });
1570
1571
  };
1571
1572
  const StringAccordion = Accordion$1;
@@ -1573,7 +1574,7 @@ const Accordion = forwardRef((props, ref) => {
1573
1574
  Accordion.displayName = "Accordion";
1574
1575
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1575
1576
  const { children, highlightSections, uniqueId, enablePinnedItems, enableHiddenItems, enableSearchItems, ...rest } = props;
1576
- const classes = useStyles$X();
1577
+ const classes = useStyles$Y();
1577
1578
  const { size } = useContext(ToolContext);
1578
1579
  const accordionCtx = useAccordionContext(props);
1579
1580
  const hasPinning = accordionCtx?.features.pinning ?? false;
@@ -1670,7 +1671,7 @@ const Collapse = (props) => {
1670
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 }) }));
1671
1672
  };
1672
1673
 
1673
- const useStyles$W = makeStyles({
1674
+ const useStyles$X = makeStyles({
1674
1675
  button: {
1675
1676
  display: "flex",
1676
1677
  alignItems: "center",
@@ -1688,7 +1689,7 @@ const ToggleButton = (props) => {
1688
1689
  ToggleButton.displayName = "ToggleButton";
1689
1690
  const { value, onChange, title, appearance = "subtle" } = props;
1690
1691
  const { size } = useContext(ToolContext);
1691
- const classes = useStyles$W();
1692
+ const classes = useStyles$X();
1692
1693
  const [checked, setChecked] = useState(value);
1693
1694
  const toggle = useCallback(() => {
1694
1695
  setChecked((prevChecked) => {
@@ -2033,7 +2034,7 @@ const UXContextProvider = (props) => {
2033
2034
  function AsReadonlyArray(array) {
2034
2035
  return array;
2035
2036
  }
2036
- const useStyles$V = makeStyles({
2037
+ const useStyles$W = makeStyles({
2037
2038
  rootDiv: {
2038
2039
  flex: 1,
2039
2040
  overflow: "hidden",
@@ -2048,7 +2049,7 @@ const useStyles$V = makeStyles({
2048
2049
  * @returns The extensible accordion component.
2049
2050
  */
2050
2051
  function ExtensibleAccordion(props) {
2051
- const classes = useStyles$V();
2052
+ const classes = useStyles$W();
2052
2053
  const { children, sections, sectionContent, context, sectionsRef, ...rest } = props;
2053
2054
  const defaultSections = useMemo(() => {
2054
2055
  const defaultSections = [];
@@ -2173,7 +2174,7 @@ function ExtensibleAccordion(props) {
2173
2174
  })] }) })) }));
2174
2175
  }
2175
2176
 
2176
- const useStyles$U = makeStyles({
2177
+ const useStyles$V = makeStyles({
2177
2178
  paneRootDiv: {
2178
2179
  display: "flex",
2179
2180
  flex: 1,
@@ -2186,7 +2187,7 @@ const useStyles$U = makeStyles({
2186
2187
  */
2187
2188
  const SidePaneContainer = forwardRef((props, ref) => {
2188
2189
  const { className, ...rest } = props;
2189
- const classes = useStyles$U();
2190
+ const classes = useStyles$V();
2190
2191
  return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
2191
2192
  });
2192
2193
 
@@ -2457,7 +2458,7 @@ function useTheme(invert = false) {
2457
2458
  }
2458
2459
 
2459
2460
  // Fluent doesn't apply styling to scrollbars by default, so provide our own reasonable default.
2460
- const useStyles$T = makeStyles({
2461
+ const useStyles$U = makeStyles({
2461
2462
  root: {
2462
2463
  scrollbarColor: `${tokens.colorNeutralForeground3} ${tokens.colorTransparentBackground}`,
2463
2464
  },
@@ -2473,11 +2474,11 @@ const Theme = (props) => {
2473
2474
  // break any UI within the portal. Therefore, default to false.
2474
2475
  const { invert = false, applyStylesToPortals = false, className, ...rest } = props;
2475
2476
  const theme = useTheme(invert);
2476
- const classes = useStyles$T();
2477
+ const classes = useStyles$U();
2477
2478
  return (jsx(FluentProvider, { theme: theme, className: mergeClasses(classes.root, className), applyStylesToPortals: applyStylesToPortals, ...rest, children: props.children }));
2478
2479
  };
2479
2480
 
2480
- const useStyles$S = makeStyles({
2481
+ const useStyles$T = makeStyles({
2481
2482
  extensionTeachingPopover: {
2482
2483
  maxWidth: "320px",
2483
2484
  },
@@ -2488,7 +2489,7 @@ const useStyles$S = makeStyles({
2488
2489
  * @returns The teaching moment popover.
2489
2490
  */
2490
2491
  const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
2491
- const classes = useStyles$S();
2492
+ const classes = useStyles$T();
2492
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 })] }) }));
2493
2494
  };
2494
2495
 
@@ -2691,13 +2692,13 @@ function useImpulse() {
2691
2692
  return [value, pulse];
2692
2693
  }
2693
2694
 
2694
- const useStyles$R = makeStyles({
2695
+ const useStyles$S = makeStyles({
2695
2696
  placeholderDiv: {
2696
2697
  padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
2697
2698
  },
2698
2699
  });
2699
2700
  const PropertiesPane = (props) => {
2700
- const classes = useStyles$R();
2701
+ const classes = useStyles$S();
2701
2702
  const entity = props.context;
2702
2703
  return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
2703
2704
  };
@@ -3023,7 +3024,7 @@ const RootComponentServiceIdentity = Symbol("RootComponent");
3023
3024
  * The unique identity symbol for the shell service.
3024
3025
  */
3025
3026
  const ShellServiceIdentity = Symbol("ShellService");
3026
- const useStyles$Q = makeStyles({
3027
+ const useStyles$R = makeStyles({
3027
3028
  mainView: {
3028
3029
  flex: 1,
3029
3030
  display: "flex",
@@ -3236,14 +3237,14 @@ const DockMenu = (props) => {
3236
3237
  };
3237
3238
  const PaneHeader = (props) => {
3238
3239
  const { id, title, dockOptions } = props;
3239
- const classes = useStyles$Q();
3240
+ const classes = useStyles$R();
3240
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, {}) }) })] }));
3241
3242
  };
3242
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.
3243
3244
  const ToolbarItem = (props) => {
3244
3245
  // eslint-disable-next-line @typescript-eslint/naming-convention
3245
3246
  const { verticalLocation, horizontalLocation, id, component: Component, displayName } = props;
3246
- const classes = useStyles$Q();
3247
+ const classes = useStyles$R();
3247
3248
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
3248
3249
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3249
3250
  const title = typeof props.teachingMoment === "object" ? props.teachingMoment.title : (displayName ?? id);
@@ -3253,7 +3254,7 @@ const ToolbarItem = (props) => {
3253
3254
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
3254
3255
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
3255
3256
  const Toolbar = ({ location, components }) => {
3256
- const classes = useStyles$Q();
3257
+ const classes = useStyles$R();
3257
3258
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
3258
3259
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
3259
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))) })] })) }));
@@ -3263,7 +3264,7 @@ const SidePaneTab = (props) => {
3263
3264
  const { location, id, isSelected, isFirst, isLast, dockOptions,
3264
3265
  // eslint-disable-next-line @typescript-eslint/naming-convention
3265
3266
  icon: Icon, title, } = props;
3266
- const classes = useStyles$Q();
3267
+ const classes = useStyles$R();
3267
3268
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
3268
3269
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3269
3270
  const tabClass = mergeClasses(classes.tab, isSelected ? classes.selectedTab : classes.unselectedTab, isFirst ? classes.firstTab : undefined, isLast ? classes.lastTab : undefined);
@@ -3275,7 +3276,7 @@ const SidePaneTab = (props) => {
3275
3276
  // In "compact" mode, the tab list is integrated into the pane itself.
3276
3277
  // In "full" mode, the returned tab list is later injected into the toolbar.
3277
3278
  function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
3278
- const classes = useStyles$Q();
3279
+ const classes = useStyles$R();
3279
3280
  const [topSelectedTab, setTopSelectedTab] = useState();
3280
3281
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
3281
3282
  const [collapsed, setCollapsed] = useState(initialCollapsed);
@@ -3472,7 +3473,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
3472
3473
  expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
3473
3474
  };
3474
3475
  const rootComponent = () => {
3475
- const classes = useStyles$Q();
3476
+ const classes = useStyles$R();
3476
3477
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
3477
3478
  // This function returns a promise that resolves after the dock change takes effect so that
3478
3479
  // we can then select the re-docked pane.
@@ -4236,7 +4237,7 @@ function CoerceEntityArray(entities, sort) {
4236
4237
  }
4237
4238
  return entities;
4238
4239
  }
4239
- const useStyles$P = makeStyles({
4240
+ const useStyles$Q = makeStyles({
4240
4241
  rootDiv: {
4241
4242
  flex: 1,
4242
4243
  overflow: "hidden",
@@ -4345,14 +4346,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
4345
4346
  }
4346
4347
  const SceneTreeItem = (props) => {
4347
4348
  const { isSelected, select } = props;
4348
- const classes = useStyles$P();
4349
+ const classes = useStyles$Q();
4349
4350
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4350
4351
  const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
4351
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"));
4352
4353
  };
4353
4354
  const SectionTreeItem = (props) => {
4354
4355
  const { section, isFiltering, commandProviders, expandAll, collapseAll, isDropTarget, ...dropProps } = props;
4355
- const classes = useStyles$P();
4356
+ const classes = useStyles$Q();
4356
4357
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4357
4358
  // Get the commands that apply to this section.
4358
4359
  const commands = useResource(useCallback(() => {
@@ -4369,7 +4370,7 @@ const SectionTreeItem = (props) => {
4369
4370
  };
4370
4371
  const EntityTreeItem = (props) => {
4371
4372
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll, isDragging, isDropTarget, ...dragProps } = props;
4372
- const classes = useStyles$P();
4373
+ const classes = useStyles$Q();
4373
4374
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4374
4375
  const hasChildren = !!entityItem.children?.length;
4375
4376
  const displayInfo = useResource(useCallback(() => {
@@ -4482,10 +4483,10 @@ const EntityTreeItem = (props) => {
4482
4483
  }, main: {
4483
4484
  // Prevent the "main" content (the Body1 below) from growing too large and pushing the actions/aside out of view.
4484
4485
  className: classes.treeItemLayoutMain,
4485
- }, 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] }) })] }));
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] }) })] }));
4486
4487
  };
4487
4488
  const SceneExplorer = (props) => {
4488
- const classes = useStyles$P();
4489
+ const classes = useStyles$Q();
4489
4490
  const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity = null } = props;
4490
4491
  const [openItems, setOpenItems] = useState(new Set());
4491
4492
  const [sceneVersion, setSceneVersion] = useState(0);
@@ -6110,7 +6111,7 @@ class CanvasGraphService {
6110
6111
  }
6111
6112
  }
6112
6113
 
6113
- const useStyles$O = makeStyles({
6114
+ const useStyles$P = makeStyles({
6114
6115
  canvas: {
6115
6116
  flexGrow: 1,
6116
6117
  width: "100%",
@@ -6119,7 +6120,7 @@ const useStyles$O = makeStyles({
6119
6120
  });
6120
6121
  const CanvasGraph = (props) => {
6121
6122
  const { collector, scene, layoutObservable, returnToPlayheadObservable, onVisibleRangeChangedObservable, initialGraphSize } = props;
6122
- const classes = useStyles$O();
6123
+ const classes = useStyles$P();
6123
6124
  const canvasRef = useRef(null);
6124
6125
  useEffect(() => {
6125
6126
  if (!canvasRef.current) {
@@ -6196,7 +6197,7 @@ function EvaluateExpression(rawValue) {
6196
6197
  return NaN;
6197
6198
  }
6198
6199
  }
6199
- const useStyles$N = makeStyles({
6200
+ const useStyles$O = makeStyles({
6200
6201
  icon: {
6201
6202
  "&:hover": {
6202
6203
  color: tokens.colorBrandForeground1,
@@ -6210,7 +6211,7 @@ const useStyles$N = makeStyles({
6210
6211
  const SpinButton = forwardRef((props, ref) => {
6211
6212
  SpinButton.displayName = "SpinButton2";
6212
6213
  const inputClasses = useInputStyles$1();
6213
- const classes = useStyles$N();
6214
+ const classes = useStyles$O();
6214
6215
  const { size } = useContext(ToolContext);
6215
6216
  const { min, max } = props;
6216
6217
  const baseStep = props.step ?? 1;
@@ -6474,7 +6475,7 @@ const Dropdown = (props) => {
6474
6475
  const NumberDropdown = Dropdown;
6475
6476
  const StringDropdown = Dropdown;
6476
6477
 
6477
- const useStyles$M = makeStyles({
6478
+ const useStyles$N = makeStyles({
6478
6479
  surface: {
6479
6480
  maxWidth: "400px",
6480
6481
  },
@@ -6489,7 +6490,7 @@ const useStyles$M = makeStyles({
6489
6490
  const Popover = forwardRef((props, ref) => {
6490
6491
  const { children, open: controlledOpen, onOpenChange, positioning, surfaceClassName } = props;
6491
6492
  const [internalOpen, setInternalOpen] = useState(false);
6492
- const classes = useStyles$M();
6493
+ const classes = useStyles$N();
6493
6494
  const isControlled = controlledOpen !== undefined;
6494
6495
  const popoverOpen = isControlled ? controlledOpen : internalOpen;
6495
6496
  const handleOpenChange = (_, data) => {
@@ -6733,7 +6734,7 @@ const InputAlphaField = (props) => {
6733
6734
  } }));
6734
6735
  };
6735
6736
 
6736
- const useStyles$L = makeStyles({
6737
+ const useStyles$M = makeStyles({
6737
6738
  sidebar: {
6738
6739
  display: "flex",
6739
6740
  flexDirection: "column",
@@ -6797,7 +6798,7 @@ const useStyles$L = makeStyles({
6797
6798
  });
6798
6799
  const PerformanceSidebar = (props) => {
6799
6800
  const { collector, onVisibleRangeChangedObservable } = props;
6800
- const classes = useStyles$L();
6801
+ const classes = useStyles$M();
6801
6802
  // Map from id to IPerfMetadata information
6802
6803
  const [metadataMap, setMetadataMap] = useState();
6803
6804
  // Map from category to all the ids belonging to that category
@@ -6870,7 +6871,7 @@ const PerformanceSidebar = (props) => {
6870
6871
  })] }, `category-${category || "version"}`))) }));
6871
6872
  };
6872
6873
 
6873
- const useStyles$K = makeStyles({
6874
+ const useStyles$L = makeStyles({
6874
6875
  container: {
6875
6876
  display: "flex",
6876
6877
  flexDirection: "row",
@@ -6899,7 +6900,7 @@ const useStyles$K = makeStyles({
6899
6900
  });
6900
6901
  const PerformanceViewer = (props) => {
6901
6902
  const { scene, layoutObservable, returnToLiveObservable, performanceCollector, initialGraphSize } = props;
6902
- const classes = useStyles$K();
6903
+ const classes = useStyles$L();
6903
6904
  const [onVisibleRangeChangedObservable] = useState(() => new Observable());
6904
6905
  const onReturnToPlayheadClick = () => {
6905
6906
  returnToLiveObservable.notifyObservers();
@@ -7068,14 +7069,14 @@ const TextPropertyLine = (props) => {
7068
7069
  return (jsx(PropertyLine, { ...props, children: jsx(Body1, { title: title, children: value ?? "" }) }));
7069
7070
  };
7070
7071
 
7071
- const useStyles$J = makeStyles({
7072
+ const useStyles$K = makeStyles({
7072
7073
  pinnedStatsPane: {
7073
7074
  flex: "0 1 auto",
7074
7075
  paddingBottom: tokens.spacingHorizontalM,
7075
7076
  },
7076
7077
  });
7077
7078
  const StatsPane = (props) => {
7078
- const classes = useStyles$J();
7079
+ const classes = useStyles$K();
7079
7080
  const scene = props.context;
7080
7081
  const engine = scene.getEngine();
7081
7082
  const pollingObservable = usePollingObservable(250);
@@ -7233,7 +7234,7 @@ const ToolsServiceDefinition = {
7233
7234
  },
7234
7235
  };
7235
7236
 
7236
- const useStyles$I = makeStyles({
7237
+ const useStyles$J = makeStyles({
7237
7238
  dropdown: {
7238
7239
  ...UniformWidthStyling,
7239
7240
  },
@@ -7245,7 +7246,7 @@ const useStyles$I = makeStyles({
7245
7246
  */
7246
7247
  const DropdownPropertyLine = forwardRef((props, ref) => {
7247
7248
  DropdownPropertyLine.displayName = "DropdownPropertyLine";
7248
- const classes = useStyles$I();
7249
+ const classes = useStyles$J();
7249
7250
  return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
7250
7251
  });
7251
7252
  /**
@@ -7403,7 +7404,7 @@ const SyncedSliderInput = (props) => {
7403
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 })] })] }));
7404
7405
  };
7405
7406
 
7406
- const useStyles$H = makeStyles({
7407
+ const useStyles$I = makeStyles({
7407
7408
  uniformWidth: {
7408
7409
  ...UniformWidthStyling,
7409
7410
  },
@@ -7415,7 +7416,7 @@ const useStyles$H = makeStyles({
7415
7416
  */
7416
7417
  const SyncedSliderPropertyLine = forwardRef((props, ref) => {
7417
7418
  SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
7418
- const classes = useStyles$H();
7419
+ const classes = useStyles$I();
7419
7420
  const { label, description, ...sliderProps } = props;
7420
7421
  return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps, className: mergeClasses(classes.uniformWidth, props.className) }) }));
7421
7422
  });
@@ -7858,7 +7859,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7858
7859
  keywords: ["creation", "tools"],
7859
7860
  ...BabylonWebResources,
7860
7861
  author: { name: "Babylon.js", forumUserName: "" },
7861
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-C2KCEQS-.js'),
7862
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-CU-OcaMm.js'),
7862
7863
  },
7863
7864
  {
7864
7865
  name: "Reflector",
@@ -7866,7 +7867,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
7866
7867
  keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
7867
7868
  ...BabylonWebResources,
7868
7869
  author: { name: "Babylon.js", forumUserName: "" },
7869
- getExtensionModuleAsync: async () => await import('./reflectorService-D1aC0qmT.js'),
7870
+ getExtensionModuleAsync: async () => await import('./reflectorService-C9gCqhs6.js'),
7870
7871
  },
7871
7872
  ]);
7872
7873
 
@@ -7904,7 +7905,7 @@ const ColorSliders = ({ color, onSliderChange }) => (jsxs(Fragment, { children:
7904
7905
  const Color3PropertyLine = ColorPropertyLine;
7905
7906
  const Color4PropertyLine = ColorPropertyLine;
7906
7907
 
7907
- const useStyles$G = makeStyles({
7908
+ const useStyles$H = makeStyles({
7908
7909
  uniformWidth: {
7909
7910
  ...UniformWidthStyling,
7910
7911
  },
@@ -7916,7 +7917,7 @@ const useStyles$G = makeStyles({
7916
7917
  */
7917
7918
  const TextInputPropertyLine = (props) => {
7918
7919
  TextInputPropertyLine.displayName = "TextInputPropertyLine";
7919
- const classes = useStyles$G();
7920
+ const classes = useStyles$H();
7920
7921
  return (jsx(PropertyLine, { ...props, children: jsx(TextInput, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
7921
7922
  };
7922
7923
  /**
@@ -7927,7 +7928,7 @@ const TextInputPropertyLine = (props) => {
7927
7928
  */
7928
7929
  const NumberInputPropertyLine = (props) => {
7929
7930
  NumberInputPropertyLine.displayName = "NumberInputPropertyLine";
7930
- const classes = useStyles$G();
7931
+ const classes = useStyles$H();
7931
7932
  return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
7932
7933
  };
7933
7934
 
@@ -8483,7 +8484,7 @@ class ServiceContainer {
8483
8484
  }
8484
8485
  }
8485
8486
 
8486
- const useStyles$F = makeStyles({
8487
+ const useStyles$G = makeStyles({
8487
8488
  themeButton: {
8488
8489
  margin: 0,
8489
8490
  },
@@ -8502,7 +8503,7 @@ const ThemeSelectorServiceDefinition = {
8502
8503
  teachingMoment: false,
8503
8504
  order: -300,
8504
8505
  component: () => {
8505
- const classes = useStyles$F();
8506
+ const classes = useStyles$G();
8506
8507
  const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
8507
8508
  const onSelectedThemeChange = useCallback((e, data) => {
8508
8509
  setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
@@ -8519,7 +8520,7 @@ const ThemeSelectorServiceDefinition = {
8519
8520
  },
8520
8521
  };
8521
8522
 
8522
- const useStyles$E = makeStyles({
8523
+ const useStyles$F = makeStyles({
8523
8524
  app: {
8524
8525
  colorScheme: "light dark",
8525
8526
  flexGrow: 1,
@@ -8559,7 +8560,7 @@ function MakeModularTool(options) {
8559
8560
  settingsStore.writeSetting(ThemeModeSettingDescriptor, themeMode);
8560
8561
  }
8561
8562
  const modularToolRootComponent = () => {
8562
- const classes = useStyles$E();
8563
+ const classes = useStyles$F();
8563
8564
  const [extensionManagerContext, setExtensionManagerContext] = useState();
8564
8565
  const [requiredExtensions, setRequiredExtensions] = useState();
8565
8566
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
@@ -8602,7 +8603,7 @@ function MakeModularTool(options) {
8602
8603
  }
8603
8604
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
8604
8605
  if (extensionFeeds.length > 0) {
8605
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-DnOKIWRz.js');
8606
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-o37oCGIy.js');
8606
8607
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
8607
8608
  }
8608
8609
  // Register all external services (that make up a unique tool).
@@ -8709,7 +8710,7 @@ const BreakTangentIcon = createFluentIcon("BreakTangent", "20", '<g transform="s
8709
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>');
8710
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>');
8711
8712
 
8712
- const useStyles$D = makeStyles({
8713
+ const useStyles$E = makeStyles({
8713
8714
  coordinatesModeButton: {
8714
8715
  margin: `0 0 0 ${tokens.spacingHorizontalXS}`,
8715
8716
  },
@@ -8725,7 +8726,7 @@ const useStyles$D = makeStyles({
8725
8726
  });
8726
8727
  const GizmoToolbar = (props) => {
8727
8728
  const { gizmoService, sceneContext } = props;
8728
- const classes = useStyles$D();
8729
+ const classes = useStyles$E();
8729
8730
  const gizmoMode = useObservableState(() => gizmoService.gizmoMode, gizmoService.onGizmoModeChanged);
8730
8731
  const coordinatesMode = useObservableState(() => gizmoService.coordinatesMode, gizmoService.onCoordinatesModeChanged);
8731
8732
  const cameraGizmo = useObservableState(() => gizmoService.gizmoCamera, gizmoService.onCameraGizmoChanged);
@@ -8854,7 +8855,7 @@ const HighlightServiceDefinition = {
8854
8855
  },
8855
8856
  };
8856
8857
 
8857
- const useStyles$C = makeStyles({
8858
+ const useStyles$D = makeStyles({
8858
8859
  badge: {
8859
8860
  margin: tokens.spacingHorizontalXXS,
8860
8861
  fontFamily: "monospace",
@@ -8871,7 +8872,7 @@ const MiniStatsServiceDefinition = {
8871
8872
  order: 300 /* DefaultToolbarItemOrder.FrameRate */,
8872
8873
  teachingMoment: false,
8873
8874
  component: () => {
8874
- const classes = useStyles$C();
8875
+ const classes = useStyles$D();
8875
8876
  const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
8876
8877
  const engine = scene?.getEngine();
8877
8878
  const pollingObservable = usePollingObservable(250);
@@ -9200,7 +9201,7 @@ function useCurveEditor() {
9200
9201
  return context;
9201
9202
  }
9202
9203
 
9203
- const useStyles$B = makeStyles({
9204
+ const useStyles$C = makeStyles({
9204
9205
  root: {
9205
9206
  display: "flex",
9206
9207
  flexDirection: "row",
@@ -9244,7 +9245,7 @@ const useStyles$B = makeStyles({
9244
9245
  * @returns The top bar component
9245
9246
  */
9246
9247
  const TopBar = () => {
9247
- const styles = useStyles$B();
9248
+ const styles = useStyles$C();
9248
9249
  const { state, observables } = useCurveEditor();
9249
9250
  const [keyFrameValue, setKeyFrameValue] = useState(null);
9250
9251
  const [keyValue, setKeyValue] = useState(null);
@@ -9337,7 +9338,7 @@ const ColorChannelColors = {
9337
9338
  */
9338
9339
  const DefaultCurveColor = "#ffffff";
9339
9340
 
9340
- const useStyles$A = makeStyles({
9341
+ const useStyles$B = makeStyles({
9341
9342
  root: {
9342
9343
  display: "flex",
9343
9344
  flexDirection: "column",
@@ -9379,7 +9380,7 @@ const LOOP_MODES$1 = [
9379
9380
  * @returns The edit animation panel component
9380
9381
  */
9381
9382
  const EditAnimationPanel = ({ animation, onClose }) => {
9382
- const styles = useStyles$A();
9383
+ const styles = useStyles$B();
9383
9384
  const { observables } = useCurveEditor();
9384
9385
  const [name, setName] = useState(animation.name);
9385
9386
  const [property, setProperty] = useState(animation.targetProperty);
@@ -9410,7 +9411,7 @@ const EditAnimationPanel = ({ animation, onClose }) => {
9410
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" })] })] }));
9411
9412
  };
9412
9413
 
9413
- const useStyles$z = makeStyles({
9414
+ const useStyles$A = makeStyles({
9414
9415
  root: {
9415
9416
  display: "flex",
9416
9417
  flexDirection: "column",
@@ -9476,7 +9477,7 @@ const useStyles$z = makeStyles({
9476
9477
  * @returns Animation entry component
9477
9478
  */
9478
9479
  const AnimationEntry = ({ animation }) => {
9479
- const styles = useStyles$z();
9480
+ const styles = useStyles$A();
9480
9481
  const { state, actions, observables } = useCurveEditor();
9481
9482
  const [isExpanded, setIsExpanded] = useState(false);
9482
9483
  const [isHovered, setIsHovered] = useState(false);
@@ -9555,7 +9556,7 @@ const AnimationEntry = ({ animation }) => {
9555
9556
  * @returns Animation sub-entry component
9556
9557
  */
9557
9558
  const AnimationSubEntry = ({ animation, subName, color }) => {
9558
- const styles = useStyles$z();
9559
+ const styles = useStyles$A();
9559
9560
  const { actions, observables } = useCurveEditor();
9560
9561
  const activeChannel = actions.getActiveChannel(animation);
9561
9562
  const isThisChannelActive = activeChannel === color;
@@ -9578,7 +9579,7 @@ const AnimationSubEntry = ({ animation, subName, color }) => {
9578
9579
  * @returns Animation list component
9579
9580
  */
9580
9581
  const AnimationList = () => {
9581
- const styles = useStyles$z();
9582
+ const styles = useStyles$A();
9582
9583
  const { state, observables } = useCurveEditor();
9583
9584
  // Re-render when animations are loaded or changed (e.g. animation deleted)
9584
9585
  // useCallback stabilizes the accessor to prevent infinite re-render loops
@@ -9591,7 +9592,7 @@ const AnimationList = () => {
9591
9592
  }) }));
9592
9593
  };
9593
9594
 
9594
- const useStyles$y = makeStyles({
9595
+ const useStyles$z = makeStyles({
9595
9596
  root: {
9596
9597
  display: "flex",
9597
9598
  flexDirection: "column",
@@ -9640,7 +9641,7 @@ const LoopModeOptions = LOOP_MODES.map((lm) => ({ label: lm, value: lm }));
9640
9641
  * @returns The add animation panel component
9641
9642
  */
9642
9643
  const AddAnimationPanel = ({ onClose }) => {
9643
- const styles = useStyles$y();
9644
+ const styles = useStyles$z();
9644
9645
  const { state, actions, observables } = useCurveEditor();
9645
9646
  const [name, setName] = useState("");
9646
9647
  const [mode, setMode] = useState("List");
@@ -9857,7 +9858,7 @@ const AddAnimationPanel = ({ onClose }) => {
9857
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" })] })] }));
9858
9859
  };
9859
9860
 
9860
- const useStyles$x = makeStyles({
9861
+ const useStyles$y = makeStyles({
9861
9862
  root: {
9862
9863
  display: "flex",
9863
9864
  flexDirection: "column",
@@ -9900,7 +9901,7 @@ const useStyles$x = makeStyles({
9900
9901
  * @returns The load animation panel component
9901
9902
  */
9902
9903
  const LoadAnimationPanel = ({ onClose }) => {
9903
- const styles = useStyles$x();
9904
+ const styles = useStyles$y();
9904
9905
  const { state, actions, observables } = useCurveEditor();
9905
9906
  const [snippetIdInput, setSnippetIdInput] = useState("");
9906
9907
  const [loadedSnippetId, setLoadedSnippetId] = useState(null);
@@ -10039,7 +10040,7 @@ class StringTools {
10039
10040
  }
10040
10041
  }
10041
10042
 
10042
- const useStyles$w = makeStyles({
10043
+ const useStyles$x = makeStyles({
10043
10044
  root: {
10044
10045
  display: "flex",
10045
10046
  flexDirection: "column",
@@ -10084,7 +10085,7 @@ const useStyles$w = makeStyles({
10084
10085
  * @returns The save animation panel component
10085
10086
  */
10086
10087
  const SaveAnimationPanel = ({ onClose: _onClose }) => {
10087
- const styles = useStyles$w();
10088
+ const styles = useStyles$x();
10088
10089
  const { state } = useCurveEditor();
10089
10090
  const [selectedAnimations, setSelectedAnimations] = useState(() => {
10090
10091
  if (!state.animations) {
@@ -10157,7 +10158,7 @@ const SaveAnimationPanel = ({ onClose: _onClose }) => {
10157
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] })] }));
10158
10159
  };
10159
10160
 
10160
- const useStyles$v = makeStyles({
10161
+ const useStyles$w = makeStyles({
10161
10162
  root: {
10162
10163
  display: "flex",
10163
10164
  flexDirection: "column",
@@ -10211,7 +10212,7 @@ const useStyles$v = makeStyles({
10211
10212
  * @returns The sidebar component
10212
10213
  */
10213
10214
  const SideBar = () => {
10214
- const styles = useStyles$v();
10215
+ const styles = useStyles$w();
10215
10216
  const { state, actions, observables } = useCurveEditor();
10216
10217
  const [openPopover, setOpenPopover] = useState(null);
10217
10218
  const [fps, setFps] = useState(60);
@@ -11199,7 +11200,7 @@ const KeyPointComponent = (props) => {
11199
11200
  } })] }))] }))] }));
11200
11201
  };
11201
11202
 
11202
- const useStyles$u = makeStyles({
11203
+ const useStyles$v = makeStyles({
11203
11204
  root: {
11204
11205
  position: "absolute",
11205
11206
  top: 0,
@@ -11383,7 +11384,7 @@ function ExtractValuesFromKeys(keys, curves) {
11383
11384
  * @returns The graph component
11384
11385
  */
11385
11386
  const Graph = ({ width, height }) => {
11386
- const styles = useStyles$u();
11387
+ const styles = useStyles$v();
11387
11388
  const { state, actions, observables } = useCurveEditor();
11388
11389
  const svgRef = useRef(null);
11389
11390
  const [scale, setScale] = useState(1);
@@ -11746,7 +11747,7 @@ const Graph = ({ width, height }) => {
11746
11747
  }), renderValueAxis()] })] }));
11747
11748
  };
11748
11749
 
11749
- const useStyles$t = makeStyles({
11750
+ const useStyles$u = makeStyles({
11750
11751
  root: {
11751
11752
  position: "absolute",
11752
11753
  top: 0,
@@ -11789,7 +11790,7 @@ const useStyles$t = makeStyles({
11789
11790
  * @returns The playhead component
11790
11791
  */
11791
11792
  const PlayHead = ({ width, height: _height }) => {
11792
- const styles = useStyles$t();
11793
+ const styles = useStyles$u();
11793
11794
  const { state, actions, observables } = useCurveEditor();
11794
11795
  const [isDragging, setIsDragging] = useState(false);
11795
11796
  // Use refs for all mutable values to avoid render cycles
@@ -11940,7 +11941,7 @@ const PlayHead = ({ width, height: _height }) => {
11940
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 })] }));
11941
11942
  };
11942
11943
 
11943
- const useStyles$s = makeStyles({
11944
+ const useStyles$t = makeStyles({
11944
11945
  root: {
11945
11946
  display: "flex",
11946
11947
  flexDirection: "row",
@@ -11972,7 +11973,7 @@ const useStyles$s = makeStyles({
11972
11973
  * @returns The frame bar component
11973
11974
  */
11974
11975
  const FrameBar = ({ width }) => {
11975
- const styles = useStyles$s();
11976
+ const styles = useStyles$t();
11976
11977
  const { state, observables } = useCurveEditor();
11977
11978
  const containerRef = useRef(null);
11978
11979
  const [scale, setScale] = useState(1);
@@ -12030,7 +12031,7 @@ const FrameBar = ({ width }) => {
12030
12031
  return (jsx("div", { className: styles.root, ref: containerRef, children: renderTicks() }));
12031
12032
  };
12032
12033
 
12033
- const useStyles$r = makeStyles({
12034
+ const useStyles$s = makeStyles({
12034
12035
  root: {
12035
12036
  display: "flex",
12036
12037
  flexDirection: "column",
@@ -12071,7 +12072,7 @@ const OFFSET_X = 10;
12071
12072
  * @returns The range frame bar component
12072
12073
  */
12073
12074
  const RangeFrameBar = ({ width }) => {
12074
- const styles = useStyles$r();
12075
+ const styles = useStyles$s();
12075
12076
  const { state, actions, observables } = useCurveEditor();
12076
12077
  const svgRef = useRef(null);
12077
12078
  const [viewWidth, setViewWidth] = useState(width);
@@ -12182,7 +12183,7 @@ const RangeFrameBar = ({ width }) => {
12182
12183
  }), renderKeyframes, renderActiveFrame] }) }));
12183
12184
  };
12184
12185
 
12185
- const useStyles$q = makeStyles({
12186
+ const useStyles$r = makeStyles({
12186
12187
  root: {
12187
12188
  display: "flex",
12188
12189
  flexDirection: "column",
@@ -12214,7 +12215,7 @@ const useStyles$q = makeStyles({
12214
12215
  * @returns The canvas component
12215
12216
  */
12216
12217
  const Canvas = () => {
12217
- const styles = useStyles$q();
12218
+ const styles = useStyles$r();
12218
12219
  const { observables } = useCurveEditor();
12219
12220
  const containerRef = useRef(null);
12220
12221
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
@@ -12244,7 +12245,7 @@ const Canvas = () => {
12244
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 }) })] }));
12245
12246
  };
12246
12247
 
12247
- const useStyles$p = makeStyles({
12248
+ const useStyles$q = makeStyles({
12248
12249
  root: {
12249
12250
  flex: 1,
12250
12251
  height: "25px",
@@ -12306,7 +12307,7 @@ const useStyles$p = makeStyles({
12306
12307
  * @returns The range selector component
12307
12308
  */
12308
12309
  const RangeSelector = () => {
12309
- const styles = useStyles$p();
12310
+ const styles = useStyles$q();
12310
12311
  const { state, actions, observables } = useCurveEditor();
12311
12312
  const containerRef = useRef(null);
12312
12313
  const scrollbarRef = useRef(null);
@@ -12451,7 +12452,7 @@ function GetKeyAtAnyFrameIndex(animations, frame) {
12451
12452
  }
12452
12453
  return false;
12453
12454
  }
12454
- const useStyles$o = makeStyles({
12455
+ const useStyles$p = makeStyles({
12455
12456
  root: {
12456
12457
  display: "flex",
12457
12458
  flexDirection: "row",
@@ -12508,7 +12509,7 @@ MediaControls.displayName = "MediaControls";
12508
12509
  * @returns The BottomBar component.
12509
12510
  */
12510
12511
  const BottomBar = () => {
12511
- const styles = useStyles$o();
12512
+ const styles = useStyles$p();
12512
12513
  const { state, actions, observables } = useCurveEditor();
12513
12514
  // Track display frame separately for smooth updates during playback
12514
12515
  const [displayFrame, setDisplayFrame] = useState(state.activeFrame);
@@ -12623,7 +12624,7 @@ const BottomBar = () => {
12623
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 })] })] }));
12624
12625
  };
12625
12626
 
12626
- const useStyles$n = makeStyles({
12627
+ const useStyles$o = makeStyles({
12627
12628
  root: {
12628
12629
  display: "flex",
12629
12630
  flexDirection: "column",
@@ -12672,7 +12673,7 @@ const useStyles$n = makeStyles({
12672
12673
  * @returns The curve editor content
12673
12674
  */
12674
12675
  const CurveEditorContent = () => {
12675
- const styles = useStyles$n();
12676
+ const styles = useStyles$o();
12676
12677
  const { state, actions, observables } = useCurveEditor();
12677
12678
  const rootRef = useRef(null);
12678
12679
  const prepareRef = useRef(() => actions.prepare());
@@ -13497,6 +13498,38 @@ const AreaLightSetupProperties = ({ context: areaLight }) => {
13497
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" })] }));
13498
13499
  };
13499
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
+
13500
13533
  const DirectionalLightSetupProperties = ({ context: directionalLight }) => {
13501
13534
  const scene = directionalLight.getScene();
13502
13535
  const camera = scene.activeCamera;
@@ -13542,19 +13575,316 @@ const HemisphericLightSetupProperties = ({ context: hemisphericLight }) => {
13542
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" })] }));
13543
13576
  };
13544
13577
 
13545
- const PointLightSetupProperties = ({ context: pointLight }) => {
13546
- 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" })] }));
13547
- };
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
+ });
13548
13630
 
13549
- const DefaultShadowGeneratorOptions = [{ label: "Shadow Generator", value: "Default" }];
13550
- const DirectionalLightGeneratorOptions = [
13551
- ...DefaultShadowGeneratorOptions,
13552
- { label: "Cascaded Shadow Generator", value: "Cascade" },
13553
- ];
13554
- const MapSizeOptions = [
13555
- { label: "4096 x 4096", value: 4096 },
13556
- { label: "2048 x 2048", value: 2048 },
13557
- { 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 },
13558
13888
  { label: "512 x 512", value: 512 },
13559
13889
  { label: "256 x 256", value: 256 },
13560
13890
  ];
@@ -13654,8 +13984,18 @@ const SpotLightSetupProperties = ({ context: spotLight }) => {
13654
13984
 
13655
13985
  const LightPropertiesServiceDefinition = {
13656
13986
  friendlyName: "Light Properties",
13657
- consumes: [PropertiesServiceIdentity],
13658
- 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
+ });
13659
13999
  const directionalLightContentRegistration = propertiesService.addSectionContent({
13660
14000
  key: "Directional Light Properties",
13661
14001
  predicate: (entity) => entity instanceof DirectionalLight,
@@ -13724,14 +14064,30 @@ const LightPropertiesServiceDefinition = {
13724
14064
  },
13725
14065
  ],
13726
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
+ });
13727
14081
  return {
13728
14082
  dispose: () => {
14083
+ clusteredLightContainerContentRegistration.dispose();
13729
14084
  areaLightContentRegistration.dispose();
13730
14085
  shadowLightContentRegistration.dispose();
13731
14086
  spotLightContentRegistration.dispose();
13732
14087
  hemisphericLightContentRegistration.dispose();
13733
14088
  pointLightContentRegistration.dispose();
13734
14089
  directionalLightContentRegistration.dispose();
14090
+ lightContentRegistration.dispose();
13735
14091
  },
13736
14092
  };
13737
14093
  },
@@ -13836,7 +14192,9 @@ const MaterialGeneralProperties = (props) => {
13836
14192
  };
13837
14193
  const MaterialTransparencyProperties = (props) => {
13838
14194
  const { material } = props;
13839
- 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(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" })] }));
14195
+ const hasAlphaCutOff = material instanceof PBRMaterial || material instanceof StandardMaterial;
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" })] }));
13840
14198
  };
13841
14199
  const MaterialStencilProperties = (props) => {
13842
14200
  const { material } = props;
@@ -14059,7 +14417,7 @@ async function EditNodeMaterial(material) {
14059
14417
  await material.edit({ nodeEditorConfig: { backgroundColor: material.getScene().clearColor } });
14060
14418
  }
14061
14419
 
14062
- const useStyles$m = makeStyles({
14420
+ const useStyles$j = makeStyles({
14063
14421
  subsection: {
14064
14422
  marginTop: tokens.spacingVerticalM,
14065
14423
  },
@@ -14133,7 +14491,7 @@ const GradientBlockPropertyLine = (props) => {
14133
14491
  };
14134
14492
  const NodeMaterialInputProperties = (props) => {
14135
14493
  const { material } = props;
14136
- const classes = useStyles$m();
14494
+ const classes = useStyles$j();
14137
14495
  const inputBlocks = useObservableState(useCallback(() => {
14138
14496
  const inspectorVisibleInputBlocks = material
14139
14497
  .getInputBlocks()
@@ -14210,403 +14568,151 @@ const OpenPBRMaterialSpecularProperties = (props) => {
14210
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) => {
14211
14569
  if (files.length > 0) {
14212
14570
  UpdateTexture(files[0], material, (texture) => (material.specularRoughnessTexture = texture));
14213
- }
14214
- } }), 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) => {
14215
- if (files.length > 0) {
14216
- UpdateTexture(files[0], material, (texture) => (material.specularRoughnessAnisotropyTexture = texture));
14217
- }
14218
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular IOR", target: material, propertyKey: "specularIor", min: 1, max: 3, step: 0.01 })] }));
14219
- };
14220
- const OpenPBRMaterialTransmissionProperties = (props) => {
14221
- const { material } = props;
14222
- 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) => {
14223
- if (files.length > 0) {
14224
- UpdateTexture(files[0], material, (texture) => (material.transmissionWeightTexture = texture));
14225
- }
14226
- } }), 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) => {
14227
- if (files.length > 0) {
14228
- UpdateTexture(files[0], material, (texture) => (material.transmissionColorTexture = texture));
14229
- }
14230
- } }), 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) => {
14231
- if (files.length > 0) {
14232
- UpdateTexture(files[0], material, (texture) => (material.transmissionDepthTexture = texture));
14233
- }
14234
- } }), 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) => {
14235
- if (files.length > 0) {
14236
- UpdateTexture(files[0], material, (texture) => (material.transmissionScatterTexture = texture));
14237
- }
14238
- } }), 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) => {
14239
- if (files.length > 0) {
14240
- UpdateTexture(files[0], material, (texture) => (material.transmissionDispersionScaleTexture = texture));
14241
- }
14242
- } })] }));
14243
- };
14244
- /**
14245
- * Displays the coat layer properties of an OpenPBR material.
14246
- * @param props - The required properties
14247
- * @returns A JSX element representing the coat layer properties.
14248
- */
14249
- const OpenPBRMaterialCoatProperties = (props) => {
14250
- const { material } = props;
14251
- 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) => {
14252
- if (files.length > 0) {
14253
- UpdateTexture(files[0], material, (texture) => (material.coatWeightTexture = texture));
14254
- }
14255
- } }), 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) => {
14256
- if (files.length > 0) {
14257
- UpdateTexture(files[0], material, (texture) => (material.coatColorTexture = texture));
14258
- }
14259
- } }), 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) => {
14260
- if (files.length > 0) {
14261
- UpdateTexture(files[0], material, (texture) => (material.coatRoughnessTexture = texture));
14262
- }
14263
- } }), 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) => {
14264
- if (files.length > 0) {
14265
- UpdateTexture(files[0], material, (texture) => (material.coatRoughnessAnisotropyTexture = texture));
14266
- }
14267
- } }), 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) => {
14268
- if (files.length > 0) {
14269
- UpdateTexture(files[0], material, (texture) => (material.coatDarkeningTexture = texture));
14270
- }
14271
- } })] }));
14272
- };
14273
- /**
14274
- * Displays the fuzz layer properties of an OpenPBR material.
14275
- * @param props - The required properties
14276
- * @returns A JSX element representing the fuzz layer properties.
14277
- */
14278
- const OpenPBRMaterialFuzzProperties = (props) => {
14279
- const { material } = props;
14280
- 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) => {
14281
- if (files.length > 0) {
14282
- UpdateTexture(files[0], material, (texture) => (material.fuzzWeightTexture = texture));
14283
- }
14284
- } }), 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) => {
14285
- if (files.length > 0) {
14286
- UpdateTexture(files[0], material, (texture) => (material.fuzzColorTexture = texture));
14287
- }
14288
- } }), 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) => {
14289
- if (files.length > 0) {
14290
- UpdateTexture(files[0], material, (texture) => (material.fuzzRoughnessTexture = texture));
14291
- }
14292
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Number of Samples", target: material, propertyKey: "fuzzSampleNumber", min: 4, max: 64, step: 1 })] }));
14293
- };
14294
- /**
14295
- * Displays the emission properties of an OpenPBR material.
14296
- * @param props - The required properties
14297
- * @returns A JSX element representing the emission properties.
14298
- */
14299
- const OpenPBRMaterialEmissionProperties = (props) => {
14300
- const { material } = props;
14301
- 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) => {
14302
- if (files.length > 0) {
14303
- UpdateTexture(files[0], material, (texture) => (material.emissionColorTexture = texture));
14304
- }
14305
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Emission Luminance", target: material, propertyKey: "emissionLuminance", min: 0, max: 10, step: 0.01 })] }));
14306
- };
14307
- /**
14308
- * Displays the thin film properties of an OpenPBR material.
14309
- * @param props - The required properties
14310
- * @returns A JSX element representing the thin film properties.
14311
- */
14312
- const OpenPBRMaterialThinFilmProperties = (props) => {
14313
- const { material } = props;
14314
- 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) => {
14315
- if (files.length > 0) {
14316
- UpdateTexture(files[0], material, (texture) => (material.thinFilmWeightTexture = texture));
14317
- }
14318
- } }), 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) => {
14319
- if (files.length > 0) {
14320
- UpdateTexture(files[0], material, (texture) => (material.thinFilmThicknessTexture = texture));
14321
- }
14322
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film IOR", target: material, propertyKey: "thinFilmIor", min: 1, max: 3, step: 0.01 })] }));
14323
- };
14324
- /**
14325
- * Displays the geometry properties of an OpenPBR material.
14326
- * @param props - The required properties
14327
- * @returns A JSX element representing the geometry properties.
14328
- */
14329
- const OpenPBRMaterialGeometryProperties = (props) => {
14330
- const { material } = props;
14331
- 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) => {
14332
- if (files.length > 0) {
14333
- UpdateTexture(files[0], material, (texture) => (material.geometryOpacityTexture = texture));
14334
- }
14335
- } }), jsx(FileUploadLine, { label: "Geometry Normal", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14336
- if (files.length > 0) {
14337
- UpdateTexture(files[0], material, (texture) => (material.geometryNormalTexture = texture));
14338
- }
14339
- } }), 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) => {
14340
- if (files.length > 0) {
14341
- UpdateTexture(files[0], material, (texture) => (material.geometryTangentTexture = texture));
14342
- }
14343
- } }), 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) => {
14344
- if (files.length > 0) {
14345
- UpdateTexture(files[0], material, (texture) => (material.geometryCoatNormalTexture = texture));
14346
- }
14347
- } }), jsx(FileUploadLine, { label: "Geometry Coat Tangent", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14348
- if (files.length > 0) {
14349
- UpdateTexture(files[0], material, (texture) => (material.geometryCoatTangentTexture = texture));
14350
- }
14351
- } }), 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) => {
14352
- if (files.length > 0) {
14353
- UpdateTexture(files[0], material, (texture) => (material.geometryThicknessTexture = texture));
14354
- }
14355
- } })] }));
14356
- };
14357
-
14358
- const useStyles$l = makeStyles({
14359
- root: {
14360
- display: "grid",
14361
- gridTemplateRows: "repeat(1fr)",
14362
- justifyItems: "start",
14363
- gap: "2px",
14364
- maxWidth: "400px",
14365
- },
14366
- comboBox: {
14367
- width: CustomTokens.valueWidth,
14368
- minWidth: CustomTokens.valueWidth,
14369
- boxSizing: "border-box",
14370
- },
14371
- input: {
14372
- minWidth: 0,
14373
- },
14374
- listbox: {
14375
- width: "fit-content",
14376
- minWidth: "fit-content",
14377
- maxWidth: "350px",
14378
- },
14379
- });
14380
- /**
14381
- * Wrapper around a Fluent ComboBox that allows for filtering options.
14382
- * @param props
14383
- * @returns
14384
- */
14385
- const ComboBox = forwardRef((props, ref) => {
14386
- ComboBox.displayName = "ComboBox";
14387
- const comboId = useId();
14388
- const styles = useStyles$l();
14389
- const { size } = useContext(ToolContext);
14390
- // Find the label for the current value
14391
- const getLabel = (value) => props.options.find((opt) => opt.value === value)?.label ?? "";
14392
- const [query, setQuery] = useState(getLabel(props.value ?? ""));
14393
- useEffect(() => {
14394
- setQuery(getLabel(props.value ?? ""));
14395
- }, [props.value, props.options]);
14396
- // Convert to Fluent's { children, value } format
14397
- const normalizedOptions = props.options.map((opt) => ({ children: opt.label, value: opt.value }));
14398
- const children = useComboboxFilter(query, normalizedOptions, {
14399
- noOptionsMessage: "No items match your search.",
14400
- optionToReactKey: (option) => option.value,
14401
- optionToText: (option) => option.children,
14402
- renderOption: (option) => (jsx(Option, { value: option.value, text: option.children, children: option.children }, option.value)),
14403
- });
14404
- const onOptionSelect = (_e, data) => {
14405
- setQuery(data.optionText ?? "");
14406
- data.optionValue && props.onChange(data.optionValue);
14407
- };
14408
- 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 })] }));
14409
- });
14410
-
14411
- const useStyles$k = makeStyles({
14412
- linkDiv: {
14413
- display: "flex",
14414
- flexDirection: "row",
14415
- alignItems: "center",
14416
- gap: tokens.spacingHorizontalS,
14417
- minWidth: 0,
14418
- overflow: "hidden",
14419
- },
14420
- link: {
14421
- minWidth: 0,
14422
- overflow: "hidden",
14423
- textOverflow: "ellipsis",
14424
- whiteSpace: "nowrap",
14425
- },
14426
- });
14427
- /**
14428
- * A generic primitive component with a ComboBox for selecting from a list of entities.
14429
- * Supports entities with duplicate names by using uniqueId for identity.
14430
- * @param props ChooseEntityProps
14431
- * @returns EntitySelector component
14432
- */
14433
- function EntitySelector(props) {
14434
- const { value, onLink, getEntities, getName, filter, defaultValue } = props;
14435
- const onChange = props.onChange;
14436
- const classes = useStyles$k();
14437
- const comboBoxRef = useRef(null);
14438
- // Build options with uniqueId as key
14439
- const options = useMemo(() => {
14440
- return getEntities()
14441
- .filter((e) => e.uniqueId !== undefined && (!filter || filter(e)))
14442
- .map((entity) => ({
14443
- label: getName(entity)?.toString() || "",
14444
- value: entity.uniqueId.toString(),
14445
- }))
14446
- .sort((a, b) => a.label.localeCompare(b.label));
14447
- }, [getEntities, getName, filter]);
14448
- const [isEditing, setIsEditing] = useState(false);
14449
- const [enteringEditMode, pulseEnteringEditMode] = useImpulse();
14450
- useEffect(() => {
14451
- if (enteringEditMode) {
14452
- comboBoxRef.current?.focus();
14453
- }
14454
- }, [enteringEditMode]);
14455
- const handleEntitySelect = (key) => {
14456
- const entity = getEntities().find((e) => e.uniqueId.toString() === key);
14457
- onChange?.(entity ?? null);
14458
- setIsEditing(false);
14459
- };
14460
- // Get current entity key for display
14461
- const currentKey = value ? value.uniqueId.toString() : "";
14462
- if (value && !isEditing) {
14463
- // If there is a value and we are not editing, show the link view
14464
- 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 &&
14465
- (defaultValue !== undefined ? (
14466
- // If the defaultValue is specified, then allow resetting to the default
14467
- jsx(Tooltip$1, { content: "Unlink", relationship: "label", children: jsx(Button, { icon: LinkDismissRegular, onClick: () => {
14468
- pulseEnteringEditMode(true);
14469
- onChange(defaultValue);
14470
- } }) })) : (
14471
- // Otherwise, just allow editing to a new value
14472
- jsx(Tooltip$1, { content: "Edit Link", relationship: "label", children: jsx(Button, { icon: LinkEditRegular, onClick: () => {
14473
- pulseEnteringEditMode(true);
14474
- setIsEditing(true);
14475
- } }) })))] }));
14476
- }
14477
- else {
14478
- // Otherwise, show the ComboBox for selection
14479
- return jsx(ComboBox, { ref: comboBoxRef, defaultOpen: enteringEditMode, label: "", options: options, value: currentKey, onChange: handleEntitySelect });
14480
- }
14481
- }
14482
- EntitySelector.displayName = "EntitySelector";
14483
-
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
+ };
14484
14602
  /**
14485
- * A primitive component with a ComboBox for selecting from existing scene materials.
14486
- * @param props MaterialSelectorProps
14487
- * @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.
14488
14606
  */
14489
- const MaterialSelector = (props) => {
14490
- MaterialSelector.displayName = "MaterialSelector";
14491
- const { scene, ...rest } = props;
14492
- const getMaterials = useCallback(() => scene.materials, [scene.materials]);
14493
- const getName = useCallback((material) => material.name, []);
14494
- 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
+ } })] }));
14495
14630
  };
14496
-
14497
14631
  /**
14498
- * A primitive component with a ComboBox for selecting from existing scene nodes.
14499
- * @param props NodeSelectorProps
14500
- * @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.
14501
14635
  */
14502
- const NodeSelector = (props) => {
14503
- NodeSelector.displayName = "NodeSelector";
14504
- const { scene, ...rest } = props;
14505
- const getNodes = useCallback(() => scene.getNodes(), [scene]);
14506
- const getName = useCallback((node) => node.name, []);
14507
- 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 })] }));
14508
14651
  };
14509
-
14510
14652
  /**
14511
- * A button that uploads a file and either:
14512
- * - Updates an existing Texture or CubeTexture via updateURL (if texture prop is provided)
14513
- * - Creates a new Texture or CubeTexture (if scene/onChange props are provided)
14514
- * @param props TextureUploadProps
14515
- * @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.
14516
14656
  */
14517
- const TextureUpload = (props) => {
14518
- TextureUpload.displayName = "TextureUpload";
14519
- const label = props.texture ? "Upload Texture" : undefined;
14520
- // TODO: This should probably be dynamically fetching a list of supported texture extensions
14521
- const accept = ".jpg, .png, .tga, .dds, .env, .exr";
14522
- const handleUpload = useCallback((files) => {
14523
- const file = files[0];
14524
- if (!file) {
14525
- return;
14526
- }
14527
- ReadFile(file, (data) => {
14528
- const blob = new Blob([data], { type: "octet/stream" });
14529
- // Update existing texture
14530
- if (props.texture) {
14531
- const { texture, onChange } = props;
14532
- const reader = new FileReader();
14533
- reader.readAsDataURL(blob);
14534
- reader.onloadend = () => {
14535
- const base64data = reader.result;
14536
- if (texture instanceof CubeTexture) {
14537
- let extension = undefined;
14538
- if (file.name.toLowerCase().indexOf(".dds") > 0) {
14539
- extension = ".dds";
14540
- }
14541
- else if (file.name.toLowerCase().indexOf(".env") > 0) {
14542
- extension = ".env";
14543
- }
14544
- texture.updateURL(base64data, extension, () => onChange?.(texture));
14545
- }
14546
- else if (texture instanceof Texture) {
14547
- 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));
14548
14662
  }
14549
- };
14550
- }
14551
- else {
14552
- // Create new texture
14553
- const { scene, cubeOnly, onChange } = props;
14554
- const url = URL.createObjectURL(blob);
14555
- const extension = file.name.split(".").pop()?.toLowerCase();
14556
- // Revoke the object URL after texture loads to prevent memory leak
14557
- const revokeUrl = () => URL.revokeObjectURL(url);
14558
- const newTexture = cubeOnly
14559
- ? new CubeTexture(url, scene, [], false, undefined, revokeUrl, undefined, undefined, false, extension ? "." + extension : undefined)
14560
- : new Texture(url, scene, false, false, undefined, revokeUrl);
14561
- onChange(newTexture);
14562
- }
14563
- }, undefined, true);
14564
- }, [props]);
14565
- 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 })] }));
14566
14664
  };
14567
-
14568
- const useStyles$j = makeStyles({
14569
- container: {
14570
- display: "flex",
14571
- flexDirection: "row",
14572
- alignItems: "center",
14573
- gap: tokens.spacingHorizontalS,
14574
- },
14575
- });
14576
14665
  /**
14577
- * A primitive component with a ComboBox for selecting from existing scene textures
14578
- * and a button for uploading new texture files.
14579
- * @param props TextureSelectorProps
14580
- * @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.
14581
14669
  */
14582
- const TextureSelector = (props) => {
14583
- TextureSelector.displayName = "TextureSelector";
14584
- const { scene, cubeOnly, value, onChange, onLink, defaultValue } = props;
14585
- const classes = useStyles$j();
14586
- const getTextures = useCallback(() => scene.textures, [scene.textures]);
14587
- const getName = useCallback((texture) => texture.displayName || texture.name || `${texture.getClassName() || "Unnamed Texture"} (${texture.uniqueId})`, []);
14588
- const filter = useCallback((texture) => !cubeOnly || texture.isCube, [cubeOnly]);
14589
- 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 })] }));
14590
14681
  };
14591
-
14592
14682
  /**
14593
- * A primitive component with a ComboBox for selecting from existing scene skeletons.
14594
- * @param props SkeletonSelectorProps
14595
- * @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.
14596
14686
  */
14597
- const SkeletonSelector = (props) => {
14598
- SkeletonSelector.displayName = "SkeletonSelector";
14599
- const { scene, ...rest } = props;
14600
- const getSkeletons = useCallback(() => scene.skeletons, [scene.skeletons]);
14601
- const getName = useCallback((skeleton) => skeleton.name, []);
14602
- 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
+ } })] }));
14603
14714
  };
14604
14715
 
14605
- const NodeSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(NodeSelector, { ...props }) });
14606
- const MaterialSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(MaterialSelector, { ...props }) });
14607
- const TextureSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(TextureSelector, { ...props }) });
14608
- const SkeletonSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(SkeletonSelector, { ...props }) });
14609
-
14610
14716
  const LightFalloffOptions = [
14611
14717
  { label: "Physical", value: PBRBaseMaterial.LIGHTFALLOFF_PHYSICAL },
14612
14718
  { label: "glTF", value: PBRBaseMaterial.LIGHTFALLOFF_GLTF },
@@ -16120,23 +16226,6 @@ const ParticleSystemEmitterProperties = (props) => {
16120
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." })] }));
16121
16227
  };
16122
16228
 
16123
- /**
16124
- * Return a copied array and re-render when array mutators run.
16125
- * Intercept add/remove/change functions because the underlying APIs update internal arrays in-place.
16126
- * @param target The target object containing the observable array, or null if the array is not applicable.
16127
- * @param getItems A function to get the current items in the array.
16128
- * @param addFn The name of the function to add an item to the array.
16129
- * @param removeFn The name of the function to remove an item from the array.
16130
- * @param changeFn The name of the function to change an item in the array.
16131
- * @returns A copied array that re-renders when array mutators run.
16132
- */
16133
- function useObservableArray(target, getItems, addFn, removeFn, changeFn) {
16134
- return useObservableState(useCallback(() => {
16135
- const value = getItems();
16136
- return [...(value ?? [])];
16137
- }, [getItems]), useInterceptObservable("function", target, addFn), useInterceptObservable("function", target, removeFn), changeFn ? useInterceptObservable("function", target, changeFn) : undefined);
16138
- }
16139
-
16140
16229
  const useStyles$h = makeStyles({
16141
16230
  subsection: {
16142
16231
  marginTop: tokens.spacingVerticalM,
@@ -17767,18 +17856,19 @@ const SpriteManagerActionsProperties = (props) => {
17767
17856
 
17768
17857
  /**
17769
17858
  * Gets the data of the specified texture by rendering it to an intermediate RGBA texture and retrieving the bytes from it.
17770
- * 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.
17771
17860
  * @param texture the source texture
17772
17861
  * @param width the width of the result, which does not have to match the source texture width
17773
17862
  * @param height the height of the result, which does not have to match the source texture height
17774
- * @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.
17775
17864
  * @param channels a filter for which of the RGBA channels to return in the result
17776
17865
  * @param lod if the texture has multiple LODs, the lod index to use for the source
17777
17866
  * @returns the 8-bit texture data
17778
17867
  */
17779
- async function ApplyChannelsToTextureDataAsync(texture, width, height, face, channels, lod = 0) {
17868
+ async function ApplyChannelsToTextureDataAsync(texture, width, height, faceOrLayer, channels, lod = 0) {
17780
17869
  // For cube maps, force RTT path to ensure correct face orientation and gamma correction
17781
- 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);
17782
17872
  if (!channels.R || !channels.G || !channels.B || !channels.A) {
17783
17873
  for (let i = 0; i < width * height * 4; i += 4) {
17784
17874
  // If alpha is the only channel, just display alpha across all channels
@@ -17860,6 +17950,7 @@ const useStyles$9 = makeStyles({
17860
17950
  controls: {
17861
17951
  display: "flex",
17862
17952
  gap: tokens.spacingHorizontalXS,
17953
+ padding: 0,
17863
17954
  },
17864
17955
  controlButton: {
17865
17956
  minWidth: "auto",
@@ -17880,7 +17971,7 @@ const useStyles$9 = makeStyles({
17880
17971
  display: "flex",
17881
17972
  justifyContent: "center",
17882
17973
  marginTop: tokens.spacingVerticalXS,
17883
- marginBottom: tokens.spacingVerticalS,
17974
+ marginBottom: tokens.spacingVerticalXS,
17884
17975
  width: "100%",
17885
17976
  },
17886
17977
  });
@@ -17898,8 +17989,14 @@ const TexturePreview = (props) => {
17898
17989
  const canvasRef = useRef(null);
17899
17990
  const [channels, setChannels] = useState(TextureChannelStates.ALL);
17900
17991
  const [face, setFace] = useState(0);
17992
+ const [layer, setLayer] = useState(0);
17901
17993
  const [canvasStyle, setCanvasStyle] = useState();
17902
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]);
17903
18000
  const { size } = useContext(ToolContext);
17904
18001
  // Watch for pinned state changes - when portaled, the canvas needs to be redrawn
17905
18002
  const accordionCtx = useContext(AccordionContext);
@@ -17921,7 +18018,7 @@ const TexturePreview = (props) => {
17921
18018
  const imageWidth = `min(${maxWidth}, calc(${maxHeight} * ${aspectRatio}))`;
17922
18019
  setCanvasStyle({ width: imageWidth });
17923
18020
  // Fetch texture data BEFORE clearing the canvas to avoid flicker
17924
- const data = await ApplyChannelsToTextureDataAsync(texture, textureWidth, textureHeight, face, channels);
18021
+ const data = await ApplyChannelsToTextureDataAsync(texture, textureWidth, textureHeight, texture.is2DArray ? layer : face, channels);
17925
18022
  // Now set canvas dimensions (this clears the canvas) and draw immediately
17926
18023
  canvas.width = canvasWidth;
17927
18024
  canvas.height = canvasHeight;
@@ -17936,7 +18033,7 @@ const TexturePreview = (props) => {
17936
18033
  catch {
17937
18034
  // If we fail, leave the canvas empty
17938
18035
  }
17939
- }, [texture, face, channels, offsetX, offsetY, width, height, internalTexture]);
18036
+ }, [texture, face, channels, offsetX, offsetY, width, height, internalTexture, layer]);
17940
18037
  useImperativeHandle(imperativeRef, () => ({ refresh: updatePreviewAsync }), [updatePreviewAsync]);
17941
18038
  useEffect(() => {
17942
18039
  void updatePreviewAsync();
@@ -17945,7 +18042,7 @@ const TexturePreview = (props) => {
17945
18042
  useEffect(() => {
17946
18043
  void updatePreviewAsync();
17947
18044
  }, [isPinned]);
17948
- 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: () => {
17949
18046
  void updatePreviewAsync();
17950
18047
  }, children: "Refresh" }))] }) }));
17951
18048
  };
@@ -20594,7 +20691,7 @@ const NodeExplorerServiceDefinition = {
20594
20691
  },
20595
20692
  };
20596
20693
  },
20597
- 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, {})),
20598
20695
  getEntityAddedObservables: () => [
20599
20696
  scene.onNewMeshAddedObservable,
20600
20697
  scene.onNewTransformNodeAddedObservable,
@@ -22919,5 +23016,5 @@ const TextAreaPropertyLine = (props) => {
22919
23016
  // Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
22920
23017
  AttachDebugLayer();
22921
23018
 
22922
- 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 };
22923
- //# sourceMappingURL=index-jSClUjQ1.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