@babylonjs/inspector 9.29.0 → 9.31.0

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.
Files changed (71) hide show
  1. package/bin/inspector-bridge.mjs +4730 -4446
  2. package/bin/inspector-cli.mjs +4765 -3866
  3. package/lib/components/curveEditor/animatableAnimations.d.ts +27 -0
  4. package/lib/components/properties/nodes/gaussianSplattingProperties.d.ts +13 -2
  5. package/lib/{extensionsListService-DfE1ZOnF.js → extensionsListService-O8zrOnmL.js} +5 -2
  6. package/lib/{extensionsListService-DfE1ZOnF.js.map → extensionsListService-O8zrOnmL.js.map} +1 -1
  7. package/lib/gif-ut7hgn9U.js +45 -0
  8. package/lib/gif-ut7hgn9U.js.map +1 -0
  9. package/lib/{index-CZ1-Y-Rv.js → index-DbbhJJ1G.js} +321 -85
  10. package/lib/index-DbbhJJ1G.js.map +1 -0
  11. package/lib/index.js +4 -1
  12. package/lib/index.js.map +1 -1
  13. package/lib/lite/animationProperties-BKVw6_ls.js +95 -0
  14. package/lib/lite/animationProperties-BKVw6_ls.js.map +1 -0
  15. package/lib/lite/animationUtils.d.ts +47 -0
  16. package/lib/lite/components/properties/animationProperties.d.ts +48 -0
  17. package/lib/lite/directTextureBinding-C5MB2OV2.js +99 -0
  18. package/lib/lite/directTextureBinding-C5MB2OV2.js.map +1 -0
  19. package/lib/lite/dynamicMaterialField-L_nQAxAA.js +107 -0
  20. package/lib/lite/dynamicMaterialField-L_nQAxAA.js.map +1 -0
  21. package/lib/lite/engineExplorerService.d.ts +3 -1
  22. package/lib/lite/{extensionsListService-D1JivcAu.js → extensionsListService-Bl1rTNl1.js} +2 -2
  23. package/lib/lite/{extensionsListService-D1JivcAu.js.map → extensionsListService-Bl1rTNl1.js.map} +1 -1
  24. package/lib/lite/{index-D8vFySa7.js → index-Cy8gzpYG.js} +1453 -394
  25. package/lib/lite/index-Cy8gzpYG.js.map +1 -0
  26. package/lib/lite/index.js +1 -1
  27. package/lib/lite/nodeMaterialProperties-ELFMlzS1.js +182 -0
  28. package/lib/lite/nodeMaterialProperties-ELFMlzS1.js.map +1 -0
  29. package/lib/lite/pbrMaterialProperties-jfJddBxY.js +614 -0
  30. package/lib/lite/pbrMaterialProperties-jfJddBxY.js.map +1 -0
  31. package/lib/lite/services/panes/properties/animationPropertiesService.d.ts +5 -0
  32. package/lib/lite/services/panes/properties/materialPropertiesService.d.ts +3 -1
  33. package/lib/lite/services/panes/properties/materials/directTextureBinding.d.ts +29 -0
  34. package/lib/lite/services/panes/properties/materials/dynamicMaterialField.d.ts +35 -0
  35. package/lib/lite/services/panes/properties/materials/materialAdapterTypes.d.ts +11 -0
  36. package/lib/lite/services/panes/properties/materials/materialReachability.d.ts +9 -0
  37. package/lib/lite/services/panes/properties/materials/nodeMaterialProperties.d.ts +8 -0
  38. package/lib/lite/services/panes/properties/materials/pbrMaterialProperties.d.ts +8 -0
  39. package/lib/lite/services/panes/properties/materials/shaderMaterialProperties.d.ts +8 -0
  40. package/lib/lite/services/panes/properties/materials/standardMaterialProperties.d.ts +8 -0
  41. package/lib/lite/services/panes/properties/materials/useDirectMaterialOperations.d.ts +31 -0
  42. package/lib/lite/services/panes/properties/textureMetadataProperties.d.ts +14 -0
  43. package/lib/lite/services/panes/properties/texturePropertiesService.d.ts +3 -1
  44. package/lib/lite/services/panes/properties/useLatestAsyncOperation.d.ts +24 -0
  45. package/lib/lite/services/panes/scene/animationGroupExplorerService.d.ts +7 -0
  46. package/lib/lite/services/panes/scene/materialExplorerService.d.ts +2 -1
  47. package/lib/lite/services/panes/scene/materialTopologyBindings.d.ts +13 -0
  48. package/lib/lite/services/panes/scene/sceneResourceIndexService.d.ts +36 -0
  49. package/lib/lite/services/panes/scene/sceneResources.d.ts +55 -11
  50. package/lib/lite/services/panes/scene/textureExplorerService.d.ts +2 -1
  51. package/lib/lite/services/pickingService.d.ts +10 -0
  52. package/lib/lite/shaderMaterialProperties-CTH_KfBK.js +229 -0
  53. package/lib/lite/shaderMaterialProperties-CTH_KfBK.js.map +1 -0
  54. package/lib/lite/standardMaterialProperties-BbYdFX86.js +287 -0
  55. package/lib/lite/standardMaterialProperties-BbYdFX86.js.map +1 -0
  56. package/lib/lite/textureMetadataProperties-BdNmp0Pk.js +221 -0
  57. package/lib/lite/textureMetadataProperties-BdNmp0Pk.js.map +1 -0
  58. package/lib/lite/useDirectMaterialOperations-D6V7OtC1.js +83 -0
  59. package/lib/lite/useDirectMaterialOperations-D6V7OtC1.js.map +1 -0
  60. package/lib/lite/useLatestAsyncOperation-k59AgA3B.js +78 -0
  61. package/lib/lite/useLatestAsyncOperation-k59AgA3B.js.map +1 -0
  62. package/lib/{quickCreateToolsService-owfaw163.js → quickCreateToolsService-Cs46Amv7.js} +5 -2
  63. package/lib/{quickCreateToolsService-owfaw163.js.map → quickCreateToolsService-Cs46Amv7.js.map} +1 -1
  64. package/lib/{reflectorService-oIAxxpQt.js → reflectorService-C43FgOiC.js} +5 -2
  65. package/lib/{reflectorService-oIAxxpQt.js.map → reflectorService-C43FgOiC.js.map} +1 -1
  66. package/lib/services/panes/properties/gaussianSplattingDebugController.d.ts +46 -0
  67. package/package.json +8 -8
  68. package/lib/gif-5DieAkSF.js +0 -38
  69. package/lib/gif-5DieAkSF.js.map +0 -1
  70. package/lib/index-CZ1-Y-Rv.js.map +0 -1
  71. package/lib/lite/index-D8vFySa7.js.map +0 -1
@@ -2,7 +2,7 @@ import { Observable } from '@babylonjs/core/Misc/observable.js';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { createContext, forwardRef, useContext, useState, useCallback, Component, useEffect, useMemo, useReducer, useRef, Children, isValidElement, useLayoutEffect, useImperativeHandle, cloneElement, createElement, Suspense, lazy } from 'react';
4
4
  import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, useFluent, Accordion as Accordion$1, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, MessageBar as MessageBar$1, MessageBarBody, AccordionItem, SearchBox as SearchBox$1, Portal, mergeClasses, createLightTheme, createDarkTheme, createDOMRenderer, FluentProvider, RendererProvider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, useId, useToastController, Toast, ToastTitle, Toaster, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, Link as Link$1, Caption1, Body1, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, Switch as Switch$1, Dropdown as Dropdown$1, Option, useMergedRefs, Input, Slider as Slider$1, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, useComboboxFilter, Combobox, MessageBarTitle, Popover as Popover$1, PopoverTrigger, PopoverSurface, Field, Textarea as Textarea$1, PresenceBadge, treeItemLevelToken, typographyStyles, FlatTree, FlatTreeItem, MenuDivider, TreeItemLayout, MenuItemCheckbox, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch } from '@fluentui/react-components';
5
- import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, DocumentTextRegular, ArrowBidirectionalUpDownFilled, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, InfoRegular, WarningRegular, CheckmarkCircleRegular, DeleteFilled, LinkDismissRegular, LinkEditRegular, AddRegular, DeleteRegular, ArrowUploadRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, ChevronRightRegular, CubeTreeRegular, EngineRegular, WindowRegular, TextFieldRegular, PersonSquareRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CameraRegular, LightbulbRegular, EyeRegular, MyLocationRegular, LayerDiagonalPersonRegular, TextTRegular, ImageRegular } from '@fluentui/react-icons';
5
+ import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, DocumentTextRegular, ArrowBidirectionalUpDownFilled, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, InfoRegular, WarningRegular, CheckmarkCircleRegular, DeleteFilled, LinkDismissRegular, LinkEditRegular, AddRegular, DeleteRegular, ArrowUploadRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, ChevronRightRegular, CubeTreeRegular, EngineRegular, WindowRegular, TextFieldRegular, PersonSquareRegular, TargetRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CameraRegular, PauseFilled, PlayFilled, StackRegular, FilmstripRegular, LightbulbRegular, EyeRegular, MyLocationRegular, LayerDiagonalPersonRegular, TextTRegular, ImageRegular } from '@fluentui/react-icons';
6
6
  import { DataStorage } from '@babylonjs/core/Misc/dataStorage.js';
7
7
  import { AsyncLock } from '@babylonjs/core/Misc/asyncLock.js';
8
8
  import { Deferred } from '@babylonjs/core/Misc/deferred.js';
@@ -10,7 +10,7 @@ import { useResizeHandle } from '@fluentui-contrib/react-resize-handle';
10
10
  import { Collapse as Collapse$1, Fade } from '@fluentui/react-motion-components-preview';
11
11
  import { Logger } from '@babylonjs/core/Misc/logger.js';
12
12
  import { createRoot } from 'react-dom/client';
13
- import { getRenderingContexts, getRenderingContextKind, quatToEulerXYZ, eulerToQuat, getMaterialFamily, setSubtreeVisible, spriteBlendOpaque, spriteBlendOneOne, spriteBlendMultiply, spriteBlendAdditive, spriteBlendPremultiplied, spriteBlendAlpha, removeFromScene, getMaterialTextures, setParent, resizeEngine } from '@babylonjs/lite';
13
+ import { getRenderingContexts, getRenderingContextKind, disposePicker, createGpuPicker, pickAsync, goToFrame, playAnimation, quatToEulerXYZTuple, eulerXYZToQuatTuple, getMaterialSource, getMaterialFamily, getShaderTexture, getPbrMetallicReflectance, getPbrClearCoat, getPbrSheen, getPbrIridescence, getPbrAnisotropy, getPbrSubsurface, getPbrTransmission, getStandardEmissiveTexture, getStandardBumpTexture, getStandardSpecularTexture, getStandardAmbientTexture, getStandardLightmapTexture, getStandardOpacityTexture, getStandardReflectionTexture, getStandardReflectionCubeTexture, getTextureMetadata, getTextureCoordinateIndex, hasTextureTransform, getTextureTransform, isMaterialView, setSubtreeVisible, spriteBlendOpaque, spriteBlendOneOne, spriteBlendMultiply, spriteBlendAdditive, spriteBlendPremultiplied, spriteBlendAlpha, removeFromScene, pauseAnimation, setParent, resizeEngine } from '@babylonjs/lite';
14
14
  import { VirtualizerScrollView } from '@fluentui-contrib/react-virtualizer';
15
15
  import { UniqueIdGenerator } from '@babylonjs/core/Misc/uniqueIdGenerator.js';
16
16
  import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
@@ -3254,29 +3254,22 @@ const useStyles$g = makeStyles({
3254
3254
  },
3255
3255
  });
3256
3256
  /**
3257
- * Toggles between two states using a button with icons.
3258
- * If no disabledIcon is provided, the button will toggle between visual enabled/disabled states without an icon change
3257
+ * Displays a controlled toggle button with icons.
3258
+ * The parent supplies the checked state through value; clicks request the opposite value through onChange.
3259
+ * If no uncheckedIcon is provided, the same icon is used for both states.
3259
3260
  *
3260
- * @param props
3261
- * @returns
3261
+ * @param props The controlled state, change callback, icons, and button presentation.
3262
+ * @returns The toggle button.
3262
3263
  */
3263
3264
  const ToggleButton = (props) => {
3264
3265
  ToggleButton.displayName = "ToggleButton";
3265
3266
  const { value, onChange, title, appearance = "subtle", ariaLabel } = props;
3266
3267
  const { size } = useContext(ToolContext);
3267
3268
  const classes = useStyles$g();
3268
- const [checked, setChecked] = useState(value);
3269
3269
  const toggle = useCallback(() => {
3270
- setChecked((prevChecked) => {
3271
- const enabled = !prevChecked;
3272
- onChange(enabled);
3273
- return enabled;
3274
- });
3275
- }, [onChange]);
3276
- useEffect(() => {
3277
- setChecked(props.value);
3278
- }, [props.value]);
3279
- return (jsx(Tooltip, { content: title ?? "", positioning: props.titlePositioning, children: jsx(ToggleButton$1, { className: classes.button, size: size, "aria-label": ariaLabel ?? title, icon: checked ? jsx(props.checkedIcon, {}) : props.uncheckedIcon ? jsx(props.uncheckedIcon, {}) : jsx(props.checkedIcon, {}), appearance: appearance, checked: checked, onClick: toggle }) }));
3270
+ onChange(!value);
3271
+ }, [value, onChange]);
3272
+ return (jsx(Tooltip, { content: title ?? "", positioning: props.titlePositioning, children: jsx(ToggleButton$1, { className: classes.button, size: size, "aria-label": ariaLabel ?? title, icon: value ? jsx(props.checkedIcon, {}) : props.uncheckedIcon ? jsx(props.uncheckedIcon, {}) : jsx(props.checkedIcon, {}), appearance: appearance, checked: value, onClick: toggle }) }));
3280
3273
  };
3281
3274
 
3282
3275
  const useInfoLabelStyles = makeStyles({
@@ -5137,7 +5130,7 @@ function MakeModularTool(options) {
5137
5130
  }
5138
5131
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
5139
5132
  if (extensionFeeds.length > 0) {
5140
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-D1JivcAu.js');
5133
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-Bl1rTNl1.js');
5141
5134
  serviceContainer.addService(ExtensionListServiceDefinition);
5142
5135
  }
5143
5136
  // Register all external services (that make up a unique tool).
@@ -5558,6 +5551,11 @@ const useStyles$9 = makeStyles({
5558
5551
  minWidth: "fit-content",
5559
5552
  maxWidth: "350px",
5560
5553
  },
5554
+ compactOption: {
5555
+ fontSize: tokens.fontSizeBase200,
5556
+ lineHeight: tokens.lineHeightBase200,
5557
+ padding: `${tokens.spacingVerticalXXS} ${tokens.spacingHorizontalS}`,
5558
+ },
5561
5559
  });
5562
5560
  /**
5563
5561
  * Wrapper around a Fluent ComboBox that allows for filtering options.
@@ -5581,13 +5579,13 @@ const ComboBox = forwardRef((props, ref) => {
5581
5579
  noOptionsMessage: "No items match your search.",
5582
5580
  optionToReactKey: (option) => option.value,
5583
5581
  optionToText: (option) => option.children,
5584
- renderOption: (option) => (jsx(Option, { value: option.value, text: option.children, children: option.children }, option.value)),
5582
+ renderOption: (option) => (jsx(Option, { value: option.value, text: option.children, className: size === "small" ? styles.compactOption : undefined, children: option.children }, option.value)),
5585
5583
  });
5586
5584
  const onOptionSelect = (_e, data) => {
5587
5585
  setQuery(data.optionText ?? "");
5588
5586
  data.optionValue && props.onChange(data.optionValue);
5589
5587
  };
5590
- 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 })] }));
5588
+ return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { ref: ref, defaultOpen: props.defaultOpen, disabled: props.disabled, size: size, root: { className: styles.comboBox }, input: { className: styles.input }, listbox: { className: styles.listbox }, onOptionSelect: onOptionSelect, "aria-label": props.ariaLabel, "aria-labelledby": props.ariaLabel ? undefined : comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
5591
5589
  });
5592
5590
 
5593
5591
  const useDraggableStyles = makeStyles({
@@ -5661,26 +5659,18 @@ const useStyles$8 = makeStyles({
5661
5659
  },
5662
5660
  });
5663
5661
  /**
5664
- * A generic primitive component with a ComboBox for selecting from a list of entities.
5665
- * Supports entities with duplicate names by using uniqueId for identity.
5666
- * @param props ChooseEntityProps
5667
- * @returns EntitySelector component
5662
+ * Displays a linked resource with one unlink-or-edit action, or a selector when unlinked.
5663
+ * @param props Resource identity, selection, navigation, and supported actions.
5664
+ * @returns The linked value or its replacement selector.
5668
5665
  */
5669
- function EntitySelector(props) {
5670
- const { value, onLink, getEntities, getName, filter, defaultValue } = props;
5671
- const onChange = props.onChange;
5666
+ function ResourceSelector(props) {
5667
+ const { value, getEntities, getName, getId, filter, onSelect, onClear, onLink, disabled, ariaLabel, showEmptySelector } = props;
5672
5668
  const classes = useStyles$8();
5673
5669
  const comboBoxRef = useRef(null);
5674
- // Build options with uniqueId as key
5675
- const options = useMemo(() => {
5676
- return getEntities()
5677
- .filter((e) => e.uniqueId !== undefined && (!filter || filter(e)))
5678
- .map((entity) => ({
5679
- label: getName(entity)?.toString() || "",
5680
- value: entity.uniqueId.toString(),
5681
- }))
5682
- .sort((a, b) => a.label.localeCompare(b.label));
5683
- }, [getEntities, getName, filter]);
5670
+ const options = useMemo(() => getEntities()
5671
+ .filter((entity) => !filter || filter(entity))
5672
+ .map((entity) => ({ label: getName(entity)?.toString() || "", value: getId(entity) }))
5673
+ .sort((a, b) => a.label.localeCompare(b.label)), [getEntities, getName, getId, filter]);
5684
5674
  const [isEditing, setIsEditing] = useState(false);
5685
5675
  const [enteringEditMode, pulseEnteringEditMode] = useImpulse();
5686
5676
  useEffect(() => {
@@ -5688,28 +5678,37 @@ function EntitySelector(props) {
5688
5678
  comboBoxRef.current?.focus();
5689
5679
  }
5690
5680
  }, [enteringEditMode]);
5691
- const handleEntitySelect = (key) => {
5692
- const entity = getEntities().find((e) => e.uniqueId.toString() === key);
5693
- onChange?.(entity ?? null);
5694
- setIsEditing(false);
5695
- };
5696
- // Get current entity key for display
5697
- const currentKey = value ? value.uniqueId.toString() : "";
5698
- if (value && !isEditing) {
5699
- // If there is a value and we are not editing, show the link view
5700
- 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 &&
5701
- (defaultValue !== undefined ? (jsx(Tooltip$1, { content: "Unlink", relationship: "label", children: jsx(Button, { icon: LinkDismissRegular, onClick: () => {
5702
- pulseEnteringEditMode(true);
5703
- onChange(defaultValue);
5704
- } }) })) : (jsx(Tooltip$1, { content: "Edit Link", relationship: "label", children: jsx(Button, { icon: LinkEditRegular, onClick: () => {
5705
- pulseEnteringEditMode(true);
5706
- setIsEditing(true);
5707
- } }) })))] }));
5708
- }
5709
- else {
5710
- // Otherwise, show the ComboBox for selection
5711
- return jsx(ComboBox, { ref: comboBoxRef, defaultOpen: enteringEditMode, label: "", options: options, value: currentKey, onChange: handleEntitySelect });
5712
- }
5681
+ if (value !== null && !isEditing) {
5682
+ 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 ? () => onLink(value) : undefined, "aria-label": onLink && ariaLabel ? `${ariaLabel}: open ${getName(value)}` : undefined }) }), onClear ? (jsx(Tooltip$1, { content: "Unlink", relationship: "label", children: jsx(Button, { icon: LinkDismissRegular, ariaLabel: ariaLabel ? `Clear ${ariaLabel}` : "Unlink", disabled: disabled, onClick: () => {
5683
+ pulseEnteringEditMode(true);
5684
+ onClear();
5685
+ } }) })) : onSelect ? (jsx(Tooltip$1, { content: "Edit Link", relationship: "label", children: jsx(Button, { icon: LinkEditRegular, ariaLabel: ariaLabel ? `Change ${ariaLabel}` : "Edit Link", disabled: disabled, onClick: () => {
5686
+ pulseEnteringEditMode(true);
5687
+ setIsEditing(true);
5688
+ } }) })) : undefined] }));
5689
+ }
5690
+ if (!onSelect && !showEmptySelector) {
5691
+ return jsx(Link, { value: "None" });
5692
+ }
5693
+ return (jsx(ComboBox, { ref: comboBoxRef, defaultOpen: enteringEditMode, label: "", ariaLabel: ariaLabel, disabled: disabled, options: options, value: value !== null ? getId(value) : "", onChange: (id) => {
5694
+ onSelect?.(getEntities().find((entity) => getId(entity) === id) ?? null);
5695
+ setIsEditing(false);
5696
+ } }));
5697
+ }
5698
+ ResourceSelector.displayName = "ResourceSelector";
5699
+
5700
+ const GetEntityId$1 = (entity) => entity.uniqueId.toString();
5701
+ /**
5702
+ * A generic primitive component with a ComboBox for selecting from a list of entities.
5703
+ * Supports entities with duplicate names by using uniqueId for identity.
5704
+ * @param props ChooseEntityProps
5705
+ * @returns EntitySelector component
5706
+ */
5707
+ function EntitySelector(props) {
5708
+ const { value, onLink, getEntities, getName, filter, defaultValue, disabled } = props;
5709
+ const onChange = props.onChange;
5710
+ const filterEntity = useCallback((entity) => entity.uniqueId !== undefined && (!filter || filter(entity)), [filter]);
5711
+ return (jsx(ResourceSelector, { value: value, getEntities: getEntities, getName: getName, getId: GetEntityId$1, filter: filterEntity, onSelect: onChange, onClear: onChange && defaultValue !== undefined ? () => onChange(defaultValue) : undefined, onLink: onLink, disabled: disabled, showEmptySelector: true }));
5713
5712
  }
5714
5713
  EntitySelector.displayName = "EntitySelector";
5715
5714
 
@@ -7101,10 +7100,19 @@ const ActionCommand = (props) => {
7101
7100
  };
7102
7101
  const ToggleCommand = (props) => {
7103
7102
  const { command } = props;
7103
+ const readCommandState = useCallback(() => [command.icon, command.isEnabled], [command]);
7104
7104
  // eslint-disable-next-line @typescript-eslint/naming-convention
7105
- const [Icon, isEnabled] = useObservableState(useCallback(() => [command.icon, command.isEnabled], [command]), command.onChange);
7105
+ const [[Icon, isEnabled], setCommandState] = useState(readCommandState);
7106
+ useEffect(() => {
7107
+ const observer = command.onChange?.add(() => setCommandState(readCommandState()));
7108
+ setCommandState(readCommandState());
7109
+ return () => observer?.remove();
7110
+ }, [command, readCommandState]);
7106
7111
  // TODO-iv2: Consolidate icon prop passing approach for inspector and shared components
7107
- return (jsx(ToggleButton, { appearance: "transparent", title: GetCommandDescription(command), titlePositioning: "after", checkedIcon: Icon, value: isEnabled, onChange: (val) => (command.isEnabled = val) }));
7112
+ return (jsx(ToggleButton, { appearance: "transparent", title: GetCommandDescription(command), titlePositioning: "after", checkedIcon: Icon, value: isEnabled, onChange: (value) => {
7113
+ command.isEnabled = value;
7114
+ setCommandState(readCommandState());
7115
+ } }));
7108
7116
  };
7109
7117
  // This "placeholder" command has a blank icon and is a no-op. It is used for aside
7110
7118
  // alignment when some toggle commands are enabled. See more details on the commands
@@ -7587,6 +7595,7 @@ function UntypeRenderingContextNodeProvider(provider) {
7587
7595
  predicate: (context) => provider.predicate(context),
7588
7596
  getNodes: (context) => (provider.predicate(context) ? provider.getNodes(context) : []),
7589
7597
  getSnapshot: (context) => (provider.predicate(context) ? provider.getSnapshot(context) : []),
7598
+ onChanged: provider.onChanged,
7590
7599
  };
7591
7600
  }
7592
7601
  // The predicate validates the subtype before the presentation is requested. Wrapping the icon then
@@ -7793,6 +7802,7 @@ const EngineExplorerServiceDefinition = {
7793
7802
  order: provider.order,
7794
7803
  predicate: (parent) => typeof parent === "object" && parent !== null && isRenderingContext(parent) && untypedProvider.predicate(parent),
7795
7804
  getNodes: untypedProvider.getNodes,
7805
+ onChanged: untypedProvider.onChanged,
7796
7806
  });
7797
7807
  let isRegistered = true;
7798
7808
  return {
@@ -7842,6 +7852,337 @@ const EngineSelectionServiceDefinition = {
7842
7852
  },
7843
7853
  };
7844
7854
 
7855
+ function GetSceneContexts(engine) {
7856
+ return engine.surfaces.flatMap((surface) => getRenderingContexts(surface).filter((context) => getRenderingContextKind(context) === "scene"));
7857
+ }
7858
+ function IsSceneNode(entity) {
7859
+ return (typeof entity === "object" &&
7860
+ entity !== null &&
7861
+ "name" in entity &&
7862
+ "children" in entity &&
7863
+ "position" in entity &&
7864
+ "rotationQuaternion" in entity &&
7865
+ "scaling" in entity &&
7866
+ "parent" in entity);
7867
+ }
7868
+ function IsMesh(entity) {
7869
+ return IsSceneNode(entity) && "material" in entity && "receiveShadows" in entity;
7870
+ }
7871
+ function IsCamera(entity) {
7872
+ return typeof entity === "object" && entity !== null && "fov" in entity && "nearPlane" in entity && "farPlane" in entity && "children" in entity && "worldMatrix" in entity;
7873
+ }
7874
+ function IsArcRotateCamera(entity) {
7875
+ return IsCamera(entity) && "alpha" in entity && "beta" in entity && "radius" in entity && "target" in entity;
7876
+ }
7877
+ function IsFreeCamera(entity) {
7878
+ return IsCamera(entity) && "position" in entity && "target" in entity && "speed" in entity;
7879
+ }
7880
+ function IsLight(entity) {
7881
+ return typeof entity === "object" && entity !== null && "lightType" in entity && "children" in entity && "parent" in entity && "worldMatrix" in entity;
7882
+ }
7883
+ function IsDirectionalLight(entity) {
7884
+ return IsLight(entity) && entity.lightType === "directional";
7885
+ }
7886
+ function IsPointLight(entity) {
7887
+ return IsLight(entity) && entity.lightType === "point";
7888
+ }
7889
+ function IsSpotLight(entity) {
7890
+ return IsLight(entity) && entity.lightType === "spot";
7891
+ }
7892
+ function IsHemisphericLight(entity) {
7893
+ return IsLight(entity) && entity.lightType === "hemispheric";
7894
+ }
7895
+ function GetSceneNodes(scene) {
7896
+ const nodes = new Set();
7897
+ const addNode = (node) => {
7898
+ if (nodes.has(node)) {
7899
+ return;
7900
+ }
7901
+ nodes.add(node);
7902
+ for (const child of node.children) {
7903
+ addNode(child);
7904
+ }
7905
+ };
7906
+ for (const mesh of scene.meshes) {
7907
+ let root = mesh;
7908
+ while (IsSceneNode(root.parent)) {
7909
+ root = root.parent;
7910
+ }
7911
+ addNode(root);
7912
+ }
7913
+ return [...nodes];
7914
+ }
7915
+ function GetSceneNodeRoots(scene) {
7916
+ const nodes = GetSceneNodes(scene);
7917
+ const nodeSet = new Set(nodes);
7918
+ return nodes.filter((node) => !IsSceneNode(node.parent) || !nodeSet.has(node.parent));
7919
+ }
7920
+ function IsSceneNodeDescendantOf(node, ancestor) {
7921
+ let parent = node.parent;
7922
+ while (typeof parent === "object" && parent !== null) {
7923
+ if (parent === ancestor) {
7924
+ return true;
7925
+ }
7926
+ if (!IsSceneNode(parent)) {
7927
+ return false;
7928
+ }
7929
+ parent = parent.parent;
7930
+ }
7931
+ return false;
7932
+ }
7933
+ function SetVector3Value(target, value) {
7934
+ const x = "x" in value ? value.x : value[0];
7935
+ const y = "x" in value ? value.y : value[1];
7936
+ const z = "x" in value ? value.z : value[2];
7937
+ const observableTarget = target;
7938
+ if (typeof observableTarget.set === "function") {
7939
+ observableTarget.set(x, y, z);
7940
+ }
7941
+ else {
7942
+ target.x = x;
7943
+ target.y = y;
7944
+ target.z = z;
7945
+ }
7946
+ }
7947
+ function SetQuaternionValue(target, value) {
7948
+ const x = "x" in value ? value.x : value[0];
7949
+ const y = "x" in value ? value.y : value[1];
7950
+ const z = "x" in value ? value.z : value[2];
7951
+ const w = "x" in value ? value.w : value[3];
7952
+ const observableTarget = target;
7953
+ if (typeof observableTarget.set === "function") {
7954
+ observableTarget.set(x, y, z, w);
7955
+ }
7956
+ else {
7957
+ target.x = x;
7958
+ target.y = y;
7959
+ target.z = z;
7960
+ target.w = w;
7961
+ }
7962
+ }
7963
+ function GetLightDisplayName(light, index) {
7964
+ const typeName = `${light.lightType.charAt(0).toUpperCase()}${light.lightType.slice(1)}`;
7965
+ return `${typeName} Light ${index + 1}`;
7966
+ }
7967
+ function GetShadowGeneratorDisplayName(scene, shadowGenerator) {
7968
+ const lightIndex = scene.lights.findIndex((light) => light.shadowGenerator === shadowGenerator);
7969
+ return lightIndex === -1 ? `Shadow Generator ${scene.shadowGenerators.indexOf(shadowGenerator) + 1}` : `${GetLightDisplayName(scene.lights[lightIndex], lightIndex)} Shadows`;
7970
+ }
7971
+
7972
+ /**
7973
+ * Adds opt-in mesh picking to the canvases hosting Babylon Lite scenes.
7974
+ * Picking uses CSS canvas coordinates and updates the shared Inspector selection.
7975
+ */
7976
+ const PickingServiceDefinition = {
7977
+ friendlyName: "Babylon Lite Picking",
7978
+ consumes: [EngineContextIdentity, ShellServiceIdentity, SelectionServiceIdentity, WatcherServiceIdentity],
7979
+ factory: (engineContext, shellService, selectionService, watcherService) => {
7980
+ const onChanged = new Observable();
7981
+ const pickers = new Map();
7982
+ const canvasBindings = new Map();
7983
+ const documentBindings = new Map();
7984
+ let toolbarRegistration;
7985
+ let enabled = false;
7986
+ let disposed = false;
7987
+ let generation = 0;
7988
+ const retirePicker = (scene, state) => {
7989
+ pickers.delete(scene);
7990
+ state.retired = true;
7991
+ // GPU readback cannot be cancelled. Keep its buffers alive until all outstanding picks finish.
7992
+ if (state.pending === 0) {
7993
+ disposePicker(state.picker);
7994
+ }
7995
+ };
7996
+ const pickCanvasAsync = async (canvas, x, y) => {
7997
+ const requestGeneration = ++generation;
7998
+ const scenes = GetSceneContexts(engineContext.engine).filter((scene) => scene.surface.canvas === canvas);
7999
+ const isCurrent = () => !disposed && enabled && generation === requestGeneration;
8000
+ try {
8001
+ // Later scenes render over earlier ones on the same surface.
8002
+ for (const scene of scenes.reverse()) {
8003
+ if (!isCurrent()) {
8004
+ return;
8005
+ }
8006
+ const camera = scene.camera;
8007
+ if (!camera || scene.meshes.length === 0) {
8008
+ continue;
8009
+ }
8010
+ let state = pickers.get(scene);
8011
+ if (!state) {
8012
+ state = { picker: createGpuPicker(scene), pending: 0, retired: false };
8013
+ pickers.set(scene, state);
8014
+ }
8015
+ state.pending++;
8016
+ let result;
8017
+ try {
8018
+ // Lite serializes overlapping requests on each picker. A mesh filter also excludes
8019
+ // contributors (such as billboards) that do not yet have Inspector selection support.
8020
+ // eslint-disable-next-line no-await-in-loop
8021
+ result = await pickAsync(state.picker, x, y, { filter: (mesh) => mesh.visible !== false });
8022
+ }
8023
+ finally {
8024
+ state.pending--;
8025
+ if (state.retired && state.pending === 0) {
8026
+ disposePicker(state.picker);
8027
+ }
8028
+ }
8029
+ if (!isCurrent() || state.retired || scene.camera !== camera || !GetSceneContexts(engineContext.engine).includes(scene)) {
8030
+ return;
8031
+ }
8032
+ if (result.hit && result.pickedMesh && scene.meshes.some((mesh) => mesh === result.pickedMesh && mesh.visible !== false && mesh.pickable !== false)) {
8033
+ selectionService.selectedEntity = result.pickedMesh;
8034
+ return;
8035
+ }
8036
+ }
8037
+ }
8038
+ catch (error) {
8039
+ Logger.Warn(`Babylon Lite GPU picking failed: ${error}`);
8040
+ }
8041
+ };
8042
+ const bindCanvas = (canvas) => {
8043
+ const originalCursor = canvas.style.cursor;
8044
+ canvas.style.cursor = "crosshair";
8045
+ let pointer;
8046
+ const isDragged = (event) => !!pointer && Math.hypot(event.clientX - pointer.x, event.clientY - pointer.y) > 5;
8047
+ const onPointerDown = (event) => {
8048
+ pointer = event.isPrimary && event.button === 0 ? { id: event.pointerId, x: event.clientX, y: event.clientY, dragged: false } : undefined;
8049
+ };
8050
+ const onPointerMove = (event) => {
8051
+ if (pointer?.id === event.pointerId && isDragged(event)) {
8052
+ pointer.dragged = true;
8053
+ }
8054
+ };
8055
+ const onPointerCancel = () => {
8056
+ pointer = undefined;
8057
+ };
8058
+ const onPointerUp = (event) => {
8059
+ const tap = pointer?.id === event.pointerId && event.button === 0 && !pointer.dragged && !isDragged(event);
8060
+ pointer = undefined;
8061
+ if (!tap) {
8062
+ return;
8063
+ }
8064
+ const rect = canvas.getBoundingClientRect();
8065
+ if (rect.width <= 0 || rect.height <= 0) {
8066
+ return;
8067
+ }
8068
+ // Account for CSS scaling without applying DPR; Lite's picker handles backing-store scaling.
8069
+ const x = ((event.clientX - rect.left) * (canvas.offsetWidth || canvas.clientWidth)) / rect.width - canvas.clientLeft;
8070
+ const y = ((event.clientY - rect.top) * (canvas.offsetHeight || canvas.clientHeight)) / rect.height - canvas.clientTop;
8071
+ if (x < 0 || y < 0 || x >= canvas.clientWidth || y >= canvas.clientHeight) {
8072
+ return;
8073
+ }
8074
+ void pickCanvasAsync(canvas, x, y);
8075
+ };
8076
+ canvas.addEventListener("pointerdown", onPointerDown);
8077
+ canvas.addEventListener("pointermove", onPointerMove);
8078
+ canvas.addEventListener("pointerup", onPointerUp);
8079
+ canvas.addEventListener("pointercancel", onPointerCancel);
8080
+ canvas.addEventListener("pointerleave", onPointerCancel);
8081
+ return () => {
8082
+ canvas.removeEventListener("pointerdown", onPointerDown);
8083
+ canvas.removeEventListener("pointermove", onPointerMove);
8084
+ canvas.removeEventListener("pointerup", onPointerUp);
8085
+ canvas.removeEventListener("pointercancel", onPointerCancel);
8086
+ canvas.removeEventListener("pointerleave", onPointerCancel);
8087
+ canvas.style.cursor = originalCursor;
8088
+ };
8089
+ };
8090
+ const refresh = () => {
8091
+ const scenes = disposed ? [] : GetSceneContexts(engineContext.engine);
8092
+ if (scenes.length === 0) {
8093
+ enabled = false;
8094
+ toolbarRegistration?.dispose();
8095
+ toolbarRegistration = undefined;
8096
+ }
8097
+ else if (!toolbarRegistration) {
8098
+ toolbarRegistration = shellService.addToolbarItem({
8099
+ key: "Picking Service",
8100
+ verticalLocation: "top",
8101
+ horizontalLocation: "left",
8102
+ teachingMoment: false,
8103
+ component: pickingToolbar,
8104
+ });
8105
+ }
8106
+ const activeScenes = enabled ? scenes : [];
8107
+ // DOM canvas constructors differ across windows; offscreen canvases have no ownerDocument.
8108
+ const canvases = new Set(activeScenes.map((scene) => scene.surface.canvas).filter((canvas) => "ownerDocument" in canvas));
8109
+ const documents = new Set([...canvases].map((canvas) => canvas.ownerDocument));
8110
+ for (const [canvas, unbind] of canvasBindings) {
8111
+ if (!canvases.has(canvas)) {
8112
+ unbind();
8113
+ canvasBindings.delete(canvas);
8114
+ }
8115
+ }
8116
+ for (const canvas of canvases) {
8117
+ if (!canvasBindings.has(canvas)) {
8118
+ canvasBindings.set(canvas, bindCanvas(canvas));
8119
+ }
8120
+ }
8121
+ for (const [document, unbind] of documentBindings) {
8122
+ if (!documents.has(document)) {
8123
+ unbind();
8124
+ documentBindings.delete(document);
8125
+ }
8126
+ }
8127
+ for (const document of documents) {
8128
+ if (!documentBindings.has(document)) {
8129
+ const onKeyDown = (event) => {
8130
+ if (event.key === "Escape") {
8131
+ setEnabled(false);
8132
+ }
8133
+ };
8134
+ document.addEventListener("keydown", onKeyDown);
8135
+ documentBindings.set(document, () => document.removeEventListener("keydown", onKeyDown));
8136
+ }
8137
+ }
8138
+ for (const [scene, state] of pickers) {
8139
+ if (!activeScenes.includes(scene)) {
8140
+ retirePicker(scene, state);
8141
+ }
8142
+ }
8143
+ };
8144
+ const setEnabled = (value) => {
8145
+ if (disposed || enabled === value) {
8146
+ return;
8147
+ }
8148
+ enabled = value;
8149
+ generation++;
8150
+ refresh();
8151
+ onChanged.notifyObservers();
8152
+ };
8153
+ const pickingToolbar = () => {
8154
+ const pickingEnabled = useObservableState(() => enabled, onChanged);
8155
+ useKeyListener({
8156
+ onKeyDown: (event) => {
8157
+ if (event.key === "Escape") {
8158
+ setEnabled(false);
8159
+ }
8160
+ },
8161
+ });
8162
+ return (jsx(ToggleButton, { title: `${pickingEnabled ? "Disable" : "Enable"} Picking`, appearance: "transparent", checkedIcon: TargetRegular, value: pickingEnabled, onChange: setEnabled }));
8163
+ };
8164
+ const selectionObserver = selectionService.onSelectedEntityChanged.add(() => generation++);
8165
+ const topologyWatcher = watcherService.watchValue(() => GetSceneContexts(engineContext.engine), () => {
8166
+ generation++;
8167
+ refresh();
8168
+ }, (left, right) => left.length === right.length && left.every((scene, index) => scene === right[index]));
8169
+ refresh();
8170
+ return {
8171
+ dispose: () => {
8172
+ if (disposed) {
8173
+ return;
8174
+ }
8175
+ disposed = true;
8176
+ generation++;
8177
+ refresh();
8178
+ topologyWatcher.dispose();
8179
+ selectionObserver.remove();
8180
+ onChanged.clear();
8181
+ },
8182
+ };
8183
+ },
8184
+ };
8185
+
7845
8186
  /**
7846
8187
  * Translates a property value to react state, updating the state whenever the property changes.
7847
8188
  * @param target The object containing the property to observe.
@@ -8226,123 +8567,291 @@ const EnginePropertiesServiceDefinition = {
8226
8567
  },
8227
8568
  };
8228
8569
 
8229
- function GetSceneContexts(engine) {
8230
- return engine.surfaces.flatMap((surface) => getRenderingContexts(surface).filter((context) => getRenderingContextKind(context) === "scene"));
8231
- }
8232
- function IsSceneNode(entity) {
8233
- return (typeof entity === "object" &&
8234
- entity !== null &&
8235
- "name" in entity &&
8236
- "children" in entity &&
8237
- "position" in entity &&
8238
- "rotationQuaternion" in entity &&
8239
- "scaling" in entity &&
8240
- "parent" in entity);
8241
- }
8242
- function IsMesh(entity) {
8243
- return IsSceneNode(entity) && "material" in entity && "receiveShadows" in entity;
8244
- }
8245
- function IsCamera(entity) {
8246
- return typeof entity === "object" && entity !== null && "fov" in entity && "nearPlane" in entity && "farPlane" in entity && "children" in entity && "worldMatrix" in entity;
8570
+ const PrettyJSONIndent = 2;
8571
+ function IsParsable(metadata) {
8572
+ try {
8573
+ const parsed = JSON.parse(metadata);
8574
+ return !!parsed && !IsString(parsed);
8575
+ }
8576
+ catch {
8577
+ return false;
8578
+ }
8247
8579
  }
8248
- function IsArcRotateCamera(entity) {
8249
- return IsCamera(entity) && "alpha" in entity && "beta" in entity && "radius" in entity && "target" in entity;
8580
+ /**
8581
+ * Checks if the input is a string.
8582
+ * @param input - any input to check
8583
+ * @returns boolean - true if the input is a string, false otherwise
8584
+ */
8585
+ function IsString(input) {
8586
+ return typeof input === "string" || input instanceof String;
8250
8587
  }
8251
- function IsFreeCamera(entity) {
8252
- return IsCamera(entity) && "position" in entity && "target" in entity && "speed" in entity;
8253
- }
8254
- function IsLight(entity) {
8255
- return typeof entity === "object" && entity !== null && "lightType" in entity && "children" in entity && "parent" in entity && "worldMatrix" in entity;
8256
- }
8257
- function IsDirectionalLight(entity) {
8258
- return IsLight(entity) && entity.lightType === "directional";
8259
- }
8260
- function IsPointLight(entity) {
8261
- return IsLight(entity) && entity.lightType === "point";
8588
+ /**
8589
+ * Checks recursively for functions on an object and returns `false` if any are found.
8590
+ * @param o any object, string or number
8591
+ * @returns boolean
8592
+ */
8593
+ function ObjectCanSafelyStringify(o) {
8594
+ if (typeof o === "function") {
8595
+ return false;
8596
+ }
8597
+ if (o === null || o === true || o === false || typeof o === "number" || IsString(o)) {
8598
+ return true;
8599
+ }
8600
+ if (typeof o === "object") {
8601
+ if (Object.values(o).length === 0) {
8602
+ return true;
8603
+ }
8604
+ return Object.values(o).every((value) => ObjectCanSafelyStringify(value));
8605
+ }
8606
+ if (Array.isArray(o)) {
8607
+ return o.every((value) => ObjectCanSafelyStringify(value));
8608
+ }
8609
+ return false;
8262
8610
  }
8263
- function IsSpotLight(entity) {
8264
- return IsLight(entity) && entity.lightType === "spot";
8611
+ function GetMetadataEntityType(metadata) {
8612
+ if (metadata == null) {
8613
+ return "null";
8614
+ }
8615
+ else if (IsString(metadata)) {
8616
+ return "string";
8617
+ }
8618
+ else if (!ObjectCanSafelyStringify(metadata)) {
8619
+ return "object";
8620
+ }
8621
+ else {
8622
+ return "JSON";
8623
+ }
8265
8624
  }
8266
- function IsHemisphericLight(entity) {
8267
- return IsLight(entity) && entity.lightType === "hemispheric";
8625
+ function HasGltfExtras(metadata) {
8626
+ return IsParsable(metadata) && !!JSON.parse(metadata).gltf;
8268
8627
  }
8269
- function GetSceneNodes(scene) {
8270
- const nodes = new Set();
8271
- const addNode = (node) => {
8272
- if (nodes.has(node)) {
8273
- return;
8274
- }
8275
- nodes.add(node);
8276
- for (const child of node.children) {
8277
- addNode(child);
8628
+ function StringifyMetadata(metadata, format) {
8629
+ if (IsString(metadata)) {
8630
+ return metadata;
8631
+ }
8632
+ if (metadata) {
8633
+ // Try JSON.stringify even for objects with functions — it safely omits
8634
+ // function-valued and undefined properties. Only fall back to String()
8635
+ // for cases that actually throw (e.g. circular references).
8636
+ try {
8637
+ return JSON.stringify(metadata, undefined, format ? PrettyJSONIndent : undefined);
8278
8638
  }
8279
- };
8280
- for (const mesh of scene.meshes) {
8281
- let root = mesh;
8282
- while (IsSceneNode(root.parent)) {
8283
- root = root.parent;
8639
+ catch {
8640
+ return String(metadata);
8284
8641
  }
8285
- addNode(root);
8286
8642
  }
8287
- return [...nodes];
8643
+ return null;
8288
8644
  }
8289
- function GetSceneNodeRoots(scene) {
8290
- const nodes = GetSceneNodes(scene);
8291
- const nodeSet = new Set(nodes);
8292
- return nodes.filter((node) => !IsSceneNode(node.parent) || !nodeSet.has(node.parent));
8645
+ function Restringify(value, format) {
8646
+ return IsParsable(value) ? JSON.stringify(JSON.parse(value), undefined, format ? PrettyJSONIndent : undefined) : value;
8293
8647
  }
8294
- function IsSceneNodeDescendantOf(node, ancestor) {
8295
- let parent = node.parent;
8296
- while (typeof parent === "object" && parent !== null) {
8297
- if (parent === ancestor) {
8298
- return true;
8299
- }
8300
- if (!IsSceneNode(parent)) {
8301
- return false;
8302
- }
8303
- parent = parent.parent;
8648
+ function PopulateGLTFExtras(metadata) {
8649
+ if (!metadata) {
8650
+ metadata = "{}";
8304
8651
  }
8305
- return false;
8306
- }
8307
- function SetVector3Value(target, value) {
8308
- const x = "x" in value ? value.x : value[0];
8309
- const y = "x" in value ? value.y : value[1];
8310
- const z = "x" in value ? value.z : value[2];
8311
- const observableTarget = target;
8312
- if (typeof observableTarget.set === "function") {
8313
- observableTarget.set(x, y, z);
8652
+ if (!IsParsable(metadata)) {
8653
+ return metadata;
8314
8654
  }
8315
- else {
8316
- target.x = x;
8317
- target.y = y;
8318
- target.z = z;
8655
+ try {
8656
+ const parsedJson = JSON.parse(metadata);
8657
+ if (parsedJson) {
8658
+ if (Reflect.has(parsedJson, "gltf")) {
8659
+ if (!Reflect.has(parsedJson.gltf, "extras")) {
8660
+ parsedJson.gltf.extras = {};
8661
+ }
8662
+ }
8663
+ else {
8664
+ parsedJson.gltf = { extras: {} };
8665
+ }
8666
+ }
8667
+ return JSON.stringify(parsedJson);
8668
+ }
8669
+ catch {
8670
+ return metadata;
8319
8671
  }
8320
8672
  }
8321
- function SetQuaternionValue(target, value) {
8322
- const x = "x" in value ? value.x : value[0];
8323
- const y = "x" in value ? value.y : value[1];
8324
- const z = "x" in value ? value.z : value[2];
8325
- const w = "x" in value ? value.w : value[3];
8326
- const observableTarget = target;
8327
- if (typeof observableTarget.set === "function") {
8328
- observableTarget.set(x, y, z, w);
8673
+ function SaveMetadata(entity, metadata) {
8674
+ if (IsParsable(metadata)) {
8675
+ entity.metadata = JSON.parse(metadata);
8329
8676
  }
8330
8677
  else {
8331
- target.x = x;
8332
- target.y = y;
8333
- target.z = z;
8334
- target.w = w;
8678
+ entity.metadata = metadata;
8335
8679
  }
8336
8680
  }
8337
- function GetLightDisplayName(light, index) {
8338
- const typeName = `${light.lightType.charAt(0).toUpperCase()}${light.lightType.slice(1)}`;
8339
- return `${typeName} Light ${index + 1}`;
8681
+ const useStyles$1 = makeStyles({
8682
+ mainDiv: {
8683
+ display: "flex",
8684
+ flexDirection: "column",
8685
+ gap: tokens.spacingVerticalXS,
8686
+ },
8687
+ buttonDiv: {
8688
+ display: "grid",
8689
+ gridAutoFlow: "column",
8690
+ gridTemplateColumns: "1fr auto auto auto",
8691
+ gap: tokens.spacingHorizontalXS,
8692
+ },
8693
+ });
8694
+ /**
8695
+ * Component to display metadata properties of an entity.
8696
+ * @param props - The properties for the component.
8697
+ * @returns A React component that displays metadata properties.
8698
+ */
8699
+ const MetadataProperties = (props) => {
8700
+ const { entity } = props;
8701
+ const classes = useStyles$1();
8702
+ const { size } = useContext(ToolContext);
8703
+ const metadata = useProperty(entity, "metadata");
8704
+ const stringifiedMetadata = useMemo(() => StringifyMetadata(metadata, false) ?? "", [metadata]);
8705
+ const metadataType = useMemo(() => GetMetadataEntityType(metadata), [metadata]);
8706
+ const canPreventObjectCorruption = metadataType === "object";
8707
+ const [preventObjectCorruption, setPreventObjectCorruption] = useState(false);
8708
+ const isReadonly = canPreventObjectCorruption && preventObjectCorruption;
8709
+ const [editedMetadata, setEditedMetadata] = useState(stringifiedMetadata);
8710
+ useEffect(() => {
8711
+ setEditedMetadata(stringifiedMetadata);
8712
+ }, [stringifiedMetadata]);
8713
+ const isEditedMetadataJSON = useMemo(() => IsParsable(editedMetadata), [editedMetadata]);
8714
+ const unformattedEditedMetadata = useMemo(() => Restringify(editedMetadata, false), [editedMetadata]);
8715
+ return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "Property Type", value: metadataType }), jsx(Collapse, { visible: canPreventObjectCorruption, children: jsx(SwitchPropertyLine, { label: "Prevent Object Corruption", value: isReadonly, onChange: setPreventObjectCorruption }) }), jsx(LineContainer, { uniqueId: "MetadataTextarea", children: jsx(Textarea, { disabled: isReadonly, value: editedMetadata, onChange: setEditedMetadata }) }), jsx(ButtonLine, { label: "Populate glTF extras", disabled: !!editedMetadata && (!IsParsable(editedMetadata) || HasGltfExtras(editedMetadata)), onClick: () => {
8716
+ const isFormatted = Restringify(editedMetadata, true) === editedMetadata;
8717
+ let withGLTFExtras = PopulateGLTFExtras(editedMetadata);
8718
+ if (isFormatted) {
8719
+ withGLTFExtras = Restringify(withGLTFExtras, true);
8720
+ }
8721
+ setEditedMetadata(withGLTFExtras);
8722
+ } }), jsx(LineContainer, { uniqueId: "MetadataButtonDiv", children: jsxs("div", { className: classes.buttonDiv, children: [jsx(Button$1, { size: size, icon: jsx(SaveRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => SaveMetadata(entity, editedMetadata), children: jsx(Body1, { children: "Save" }) }), jsx(Tooltip$1, { content: "Undo Changes", relationship: "label", children: jsx(Button$1, { size: size, icon: jsx(ArrowUndoRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => setEditedMetadata(stringifiedMetadata) }) }), jsx(Tooltip$1, { content: "Format (Pretty Print)", relationship: "label", children: jsx(Button$1, { size: size, icon: jsx(BracesRegular, {}), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, true)) }) }), jsx(Tooltip$1, { content: "Clear Formatting (Undo Pretty Print)", relationship: "label", children: jsx(Button$1, { size: size, icon: jsx(BracesDismiss16Regular, {}), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, false)) }) })] }) })] }));
8723
+ };
8724
+
8725
+ /**
8726
+ * Gets the distinct animation groups exposed by the inspected engine's registered scenes.
8727
+ * @param engine The engine to inspect.
8728
+ * @returns The animation groups in scene order, without duplicates.
8729
+ */
8730
+ function GetSceneAnimationGroups(engine) {
8731
+ return [...new Set(GetSceneContexts(engine).flatMap((scene) => scene.animationGroups))];
8340
8732
  }
8341
- function GetShadowGeneratorDisplayName(scene, shadowGenerator) {
8342
- const lightIndex = scene.lights.findIndex((light) => light.shadowGenerator === shadowGenerator);
8343
- return lightIndex === -1 ? `Shadow Generator ${scene.shadowGenerators.indexOf(shadowGenerator) + 1}` : `${GetLightDisplayName(scene.lights[lightIndex], lightIndex)} Shadows`;
8733
+ /**
8734
+ * Tests whether an entity is an animation group in a registered scene.
8735
+ * @param engine The inspected engine.
8736
+ * @param entity The entity to test.
8737
+ * @returns Whether the entity is a reachable animation group.
8738
+ */
8739
+ function IsAnimationGroup(engine, entity) {
8740
+ return GetSceneAnimationGroups(engine).some((group) => group === entity);
8741
+ }
8742
+ /**
8743
+ * Tests whether an entity is a public target description in a registered scene's animation group.
8744
+ * @param engine The inspected engine.
8745
+ * @param entity The entity to test.
8746
+ * @returns Whether the entity is a reachable targeted animation.
8747
+ */
8748
+ function IsTargetedAnimation(engine, entity) {
8749
+ return GetSceneAnimationGroups(engine).some((group) => group.targetedAnimations.some((animation) => animation === entity));
8750
+ }
8751
+ /**
8752
+ * Gets animation groups that directly animate an entity or one of a scene node's transform values.
8753
+ * @param engine The inspected engine.
8754
+ * @param entity The entity whose animations should be listed.
8755
+ * @returns Reachable groups with matching public runtime targets.
8756
+ */
8757
+ function GetEntityAnimationGroups(engine, entity) {
8758
+ return GetSceneAnimationGroups(engine).filter((group) => group.targetedAnimations.some((animation) => animation.target === entity ||
8759
+ (IsSceneNode(entity) &&
8760
+ (animation.target === entity.position ||
8761
+ animation.target === entity.rotationQuaternion ||
8762
+ animation.target === entity.rotation ||
8763
+ animation.target === entity.scaling))));
8764
+ }
8765
+ /**
8766
+ * Gets a display name using the live runtime name when available, without requiring a runtime target.
8767
+ * @param animation The targeted animation.
8768
+ * @returns The target name or node identity and its animated path.
8769
+ */
8770
+ function GetTargetedAnimationDisplayName(animation) {
8771
+ const target = animation.target;
8772
+ const name = target && "name" in target && typeof target.name === "string" ? target.name : undefined;
8773
+ const targetName = name || animation.targetName || (animation.nodeIndex !== undefined ? `Node ${animation.nodeIndex}` : "Target");
8774
+ return `${targetName}: ${animation.path}`;
8775
+ }
8776
+ /**
8777
+ * Gets the frame rate used by Lite's goToFrame API.
8778
+ * @param group The animation group.
8779
+ * @returns The authored frame rate, or Lite's default of 60 frames per second.
8780
+ */
8781
+ function GetAnimationFrameRate(group) {
8782
+ return group.frameRate || 60;
8783
+ }
8784
+ /**
8785
+ * Seeks through Lite's public API while preserving whether playback was active.
8786
+ * @param engine The engine used to apply GPU-backed animation poses.
8787
+ * @param group The animation group to seek.
8788
+ * @param frame The requested frame.
8789
+ */
8790
+ function SeekAnimationFrame(engine, group, frame) {
8791
+ const wasPlaying = group.isPlaying;
8792
+ goToFrame(group, frame, engine);
8793
+ if (wasPlaying) {
8794
+ playAnimation(group);
8795
+ }
8344
8796
  }
8345
8797
 
8798
+ const AnimationGroupControlProperties = MakeLazyComponent(async () => (await import('./animationProperties-BKVw6_ls.js')).AnimationGroupControlProperties);
8799
+ const AnimationGroupInfoProperties = MakeLazyComponent(async () => (await import('./animationProperties-BKVw6_ls.js')).AnimationGroupInfoProperties);
8800
+ const TargetedAnimationProperties = MakeLazyComponent(async () => (await import('./animationProperties-BKVw6_ls.js')).TargetedAnimationProperties);
8801
+ const EntityAnimationProperties = MakeLazyComponent(async () => (await import('./animationProperties-BKVw6_ls.js')).EntityAnimationProperties);
8802
+ const AnimationPropertiesServiceDefinition = {
8803
+ friendlyName: "Babylon Lite Animation Properties",
8804
+ consumes: [PropertiesServiceIdentity, EngineContextIdentity, SelectionServiceIdentity],
8805
+ factory: (propertiesService, engineContext, selectionService) => {
8806
+ const engine = engineContext.engine;
8807
+ const groupRegistration = propertiesService.addSectionContent({
8808
+ key: "Babylon Lite Animation Group Properties",
8809
+ predicate: (entity) => IsAnimationGroup(engine, entity),
8810
+ content: [
8811
+ {
8812
+ section: "Control",
8813
+ component: ({ context }) => jsx(AnimationGroupControlProperties, { engine: engine, group: context }),
8814
+ },
8815
+ {
8816
+ section: "Info",
8817
+ component: ({ context }) => jsx(AnimationGroupInfoProperties, { group: context }),
8818
+ },
8819
+ {
8820
+ section: "Metadata",
8821
+ component: ({ context }) => jsx(MetadataProperties, { entity: context }),
8822
+ },
8823
+ ],
8824
+ });
8825
+ const targetedAnimationRegistration = propertiesService.addSectionContent({
8826
+ key: "Babylon Lite Targeted Animation Properties",
8827
+ predicate: (entity) => IsTargetedAnimation(engine, entity),
8828
+ content: [
8829
+ {
8830
+ section: "General",
8831
+ component: ({ context }) => jsx(TargetedAnimationProperties, { animation: context, selectionService: selectionService }),
8832
+ },
8833
+ ],
8834
+ });
8835
+ const entityRegistration = propertiesService.addSectionContent({
8836
+ key: "Babylon Lite Entity Animation Properties",
8837
+ predicate: (entity) => typeof entity === "object" && entity !== null && !IsAnimationGroup(engine, entity) && !IsTargetedAnimation(engine, entity),
8838
+ content: [
8839
+ {
8840
+ section: "Animation",
8841
+ component: ({ context }) => jsx(EntityAnimationProperties, { engine: engine, entity: context, selectionService: selectionService }),
8842
+ },
8843
+ ],
8844
+ });
8845
+ return {
8846
+ dispose: () => {
8847
+ entityRegistration.dispose();
8848
+ targetedAnimationRegistration.dispose();
8849
+ groupRegistration.dispose();
8850
+ },
8851
+ };
8852
+ },
8853
+ };
8854
+
8346
8855
  /**
8347
8856
  * Runtime-neutral controlled editor for two-, three-, and four-component numeric values.
8348
8857
  * @param props The value, change callback, component adapter, and property-line presentation.
@@ -8363,11 +8872,11 @@ const ControlledTensorPropertyLine = (props) => {
8363
8872
  props.onChange(newValue);
8364
8873
  };
8365
8874
  const summary = adapter.components.map((component) => formatted(adapter.getComponent(props.value, component))).join(", ");
8366
- return (jsx(PropertyLine, { ...props, expandedContent: jsxs(Fragment, { children: [props.expandedContent, jsx(TensorSliders, { value: value, adapter: adapter, min: min, max: max, unit: unit, step: step, precision: precision, converted: converted, onChange: onComponentChange })] }), children: jsx(Body1, { children: `[${summary}]` }) }));
8875
+ return (jsx(PropertyLine, { ...props, expandedContent: jsxs(Fragment, { children: [props.expandedContent, jsx(TensorSliders, { value: value, adapter: adapter, min: min, max: max, unit: unit, step: step, precision: precision, disabled: props.disabled, converted: converted, onChange: onComponentChange })] }), children: jsx(Body1, { children: `[${summary}]` }) }));
8367
8876
  };
8368
8877
  const TensorSliders = (props) => {
8369
- const { value, adapter, min, max, unit, step, precision, converted, onChange } = props;
8370
- return (jsx(Fragment, { children: adapter.components.map((component) => (jsx(NumberInputPropertyLine, { label: component.toUpperCase(), value: converted(adapter.getComponent(value, component)), min: min, max: max, onChange: (componentValue) => onChange(componentValue, component), unit: unit, step: step, precision: precision }, component))) }));
8878
+ const { value, adapter, min, max, unit, step, precision, disabled, converted, onChange } = props;
8879
+ return (jsx(Fragment, { children: adapter.components.map((component) => (jsx(NumberInputPropertyLine, { label: component.toUpperCase(), value: converted(adapter.getComponent(value, component)), min: min, max: max, onChange: (componentValue) => onChange(componentValue, component), unit: unit, step: step, precision: precision, disabled: disabled }, component))) }));
8371
8880
  };
8372
8881
  const RadiansToDegreesConverter = {
8373
8882
  from: (value) => (value * 180) / Math.PI,
@@ -8419,6 +8928,11 @@ function GetComponent(value, component) {
8419
8928
  return "w" in value ? value.w : 0;
8420
8929
  }
8421
8930
  }
8931
+ const Vector2Adapter = {
8932
+ components: ["x", "y"],
8933
+ getComponent: GetComponent,
8934
+ withComponent: (value, component, componentValue) => IsNumberTuple(value) ? [component === "x" ? componentValue : value[0], component === "y" ? componentValue : value[1]] : { ...value, [component]: componentValue },
8935
+ };
8422
8936
  const Vector3Adapter = {
8423
8937
  components: ["x", "y", "z"],
8424
8938
  getComponent: GetComponent,
@@ -8426,6 +8940,18 @@ const Vector3Adapter = {
8426
8940
  ? [component === "x" ? componentValue : value[0], component === "y" ? componentValue : value[1], component === "z" ? componentValue : value[2]]
8427
8941
  : { ...value, [component]: componentValue },
8428
8942
  };
8943
+ const Vector4Adapter = {
8944
+ components: ["x", "y", "z", "w"],
8945
+ getComponent: GetComponent,
8946
+ withComponent: (value, component, componentValue) => IsNumberTuple(value)
8947
+ ? [
8948
+ component === "x" ? componentValue : value[0],
8949
+ component === "y" ? componentValue : value[1],
8950
+ component === "z" ? componentValue : value[2],
8951
+ component === "w" ? componentValue : value[3],
8952
+ ]
8953
+ : { ...value, [component]: componentValue },
8954
+ };
8429
8955
  const QuaternionTensorAdapter = {
8430
8956
  components: ["x", "y", "z", "w"],
8431
8957
  getComponent: GetComponent,
@@ -8446,7 +8972,7 @@ const QuaternionTensorAdapter = {
8446
8972
  */
8447
8973
  function CreateQuaternionFromEuler(value, source) {
8448
8974
  const [x, y, z] = "x" in value ? [value.x, value.y, value.z] : value;
8449
- const quaternion = eulerToQuat(x, y, z);
8975
+ const quaternion = eulerXYZToQuatTuple(x, y, z);
8450
8976
  return "x" in source ? { x: quaternion[0], y: quaternion[1], z: quaternion[2], w: quaternion[3] } : quaternion;
8451
8977
  }
8452
8978
  const QuaternionAdapter = {
@@ -8455,10 +8981,12 @@ const QuaternionAdapter = {
8455
8981
  fromEuler: CreateQuaternionFromEuler,
8456
8982
  toEuler: (value) => {
8457
8983
  const [x, y, z, w] = "x" in value ? [value.x, value.y, value.z, value.w] : value;
8458
- return quatToEulerXYZ(x, y, z, w);
8984
+ return quatToEulerXYZTuple(x, y, z, w);
8459
8985
  },
8460
8986
  };
8987
+ const Vector2PropertyLine = (props) => jsx(ControlledTensorPropertyLine, { ...props, adapter: Vector2Adapter });
8461
8988
  const Vector3PropertyLine = (props) => jsx(ControlledTensorPropertyLine, { ...props, adapter: Vector3Adapter });
8989
+ const Vector4PropertyLine = (props) => jsx(ControlledTensorPropertyLine, { ...props, adapter: Vector4Adapter });
8462
8990
  const QuaternionPropertyLine = (props) => jsx(ControlledQuaternionPropertyLine, { ...props, adapter: QuaternionAdapter });
8463
8991
 
8464
8992
  function GetPosition$2(camera) {
@@ -8555,7 +9083,7 @@ const ControlledColorPropertyLine = (props) => {
8555
9083
  }, currentValue));
8556
9084
  };
8557
9085
  const color = adapter.getColor(currentValue);
8558
- return (jsx(PropertyLine, { ref: propertyLineRef, ...props, expandedContent: jsx(ColorSliders, { color: color, onSliderChange: onSliderChange }), children: createElement(adapter.picker, {
9086
+ return (jsx(PropertyLine, { ref: propertyLineRef, ...props, expandedContent: jsx(ColorSliders, { color: color, onSliderChange: onSliderChange, disabled: props.disabled }), children: createElement(adapter.picker, {
8559
9087
  value: currentValue,
8560
9088
  onChange: handleChange,
8561
9089
  isLinearMode: props.isLinearMode,
@@ -8566,11 +9094,11 @@ const ControlledColorPropertyLine = (props) => {
8566
9094
  }) }));
8567
9095
  };
8568
9096
  const ColorSliders = (props) => {
8569
- const { color, onSliderChange } = props;
8570
- return (jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "R", value: color.r * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "r") }), jsx(SyncedSliderPropertyLine, { label: "G", value: color.g * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "g") }), jsx(SyncedSliderPropertyLine, { label: "B", value: color.b * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "b") }), color.a === undefined ? undefined : (jsx(SyncedSliderPropertyLine, { label: "A", value: color.a, min: 0, max: 1, step: 0.01, onChange: (value) => onSliderChange(value, "a") }))] }));
9097
+ const { color, disabled, onSliderChange } = props;
9098
+ return (jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "R", value: color.r * 255, min: 0, max: 255, disabled: disabled, onChange: (value) => onSliderChange(value, "r") }), jsx(SyncedSliderPropertyLine, { label: "G", value: color.g * 255, min: 0, max: 255, disabled: disabled, onChange: (value) => onSliderChange(value, "g") }), jsx(SyncedSliderPropertyLine, { label: "B", value: color.b * 255, min: 0, max: 255, disabled: disabled, onChange: (value) => onSliderChange(value, "b") }), color.a === undefined ? undefined : (jsx(SyncedSliderPropertyLine, { label: "A", value: color.a, min: 0, max: 1, step: 0.01, disabled: disabled, onChange: (value) => onSliderChange(value, "a") }))] }));
8571
9099
  };
8572
9100
 
8573
- const useStyles$1 = makeStyles({
9101
+ const useStyles = makeStyles({
8574
9102
  picker: {
8575
9103
  width: "350px",
8576
9104
  height: "350px",
@@ -8669,7 +9197,7 @@ function ColorToHex(color) {
8669
9197
  */
8670
9198
  const StructuralColorPickerPopup = (props) => {
8671
9199
  const { value, onChange, adapter, isLinearMode, ...rest } = props;
8672
- const classes = useStyles$1();
9200
+ const classes = useStyles();
8673
9201
  const { size } = useContext(ToolContext);
8674
9202
  const [currentValue, setCurrentValue] = useState(value);
8675
9203
  useEffect(() => {
@@ -8733,6 +9261,7 @@ const Adapter = {
8733
9261
  createColor: (color, source) => (IsColor4Value(source) ? CreateColor4Value(color, source) : CreateColor3Value(color, source)),
8734
9262
  };
8735
9263
  const Color3PropertyLine = (props) => (jsx(ControlledColorPropertyLine, { ...props, adapter: Adapter }));
9264
+ const Color4PropertyLine = (props) => (jsx(ControlledColorPropertyLine, { ...props, adapter: Adapter }));
8736
9265
 
8737
9266
  function GetVector3(value) {
8738
9267
  return [value.x, value.y, value.z];
@@ -8857,28 +9386,598 @@ const LightPropertiesServiceDefinition = {
8857
9386
  },
8858
9387
  };
8859
9388
 
8860
- function IsMaterial(entity) {
8861
- return typeof entity === "object" && entity !== null && getMaterialFamily(entity) !== undefined;
8862
- }
8863
- const MaterialProperties = (props) => {
8864
- const { material } = props;
8865
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextInputPropertyLine, label: "Name", target: material, propertyKey: "name", ignoreNullable: true, defaultValue: "" }), jsx(TextPropertyLine, { label: "Family", value: getMaterialFamily(material) ?? "Unknown" })] }));
8866
- };
8867
- const MaterialPropertiesServiceDefinition = {
8868
- friendlyName: "Babylon Lite Material Properties",
8869
- consumes: [PropertiesServiceIdentity],
8870
- factory: (propertiesService) => propertiesService.addSectionContent({
8871
- key: "Babylon Lite Material Properties",
8872
- predicate: IsMaterial,
8873
- content: [
8874
- {
8875
- section: "General",
8876
- component: ({ context }) => jsx(MaterialProperties, { material: context }),
8877
- },
8878
- ],
8879
- }),
8880
- };
8881
-
9389
+ /**
9390
+ * Returns scenes that still contain the selected material, rather than just another view of its source.
9391
+ * @param record The indexed source record.
9392
+ * @param material The selected source or view.
9393
+ * @returns Scenes with a live mesh referencing the selected material.
9394
+ */
9395
+ function GetMaterialOwningScenes(record, material) {
9396
+ return record.scenes.filter((scene) => Array.isArray(scene.meshes) &&
9397
+ scene.meshes.some((mesh) => mesh?.material && (material === record.source ? getMaterialSource(mesh.material) === record.source : mesh.material === material)));
9398
+ }
9399
+
9400
+ function Add(bindings, id, entity) {
9401
+ if (entity) {
9402
+ bindings.push({ id, entity });
9403
+ }
9404
+ }
9405
+ function GetStandardBindings(material) {
9406
+ const bindings = [];
9407
+ Add(bindings, "standard.diffuse", material.diffuseTexture);
9408
+ Add(bindings, "standard.emissive", getStandardEmissiveTexture(material));
9409
+ Add(bindings, "standard.bump", getStandardBumpTexture(material));
9410
+ Add(bindings, "standard.specular", getStandardSpecularTexture(material));
9411
+ Add(bindings, "standard.ambient", getStandardAmbientTexture(material));
9412
+ Add(bindings, "standard.lightmap", getStandardLightmapTexture(material));
9413
+ Add(bindings, "standard.opacity", getStandardOpacityTexture(material));
9414
+ Add(bindings, "standard.reflection2d", getStandardReflectionTexture(material));
9415
+ Add(bindings, "standard.reflectionCube", getStandardReflectionCubeTexture(material));
9416
+ return bindings;
9417
+ }
9418
+ function GetPbrBindings(material) {
9419
+ const bindings = [];
9420
+ Add(bindings, "pbr.baseColor", material.baseColorTexture);
9421
+ Add(bindings, "pbr.normal", material.normalTexture);
9422
+ Add(bindings, "pbr.orm", material.ormTexture);
9423
+ Add(bindings, "pbr.occlusion", material.occlusionTexture);
9424
+ Add(bindings, "pbr.emissive", material.emissiveTexture);
9425
+ Add(bindings, "pbr.specGloss", material.specGlossTexture);
9426
+ Add(bindings, "pbr.lightmap", material.lightmapTexture);
9427
+ const metallicReflectance = getPbrMetallicReflectance(material);
9428
+ Add(bindings, "pbr.metallicReflectance", metallicReflectance?.texture);
9429
+ Add(bindings, "pbr.reflectance", metallicReflectance?.reflectanceTexture);
9430
+ const clearCoat = getPbrClearCoat(material);
9431
+ Add(bindings, "pbr.clearCoat", clearCoat?.texture);
9432
+ Add(bindings, "pbr.clearCoatRoughness", clearCoat?.roughnessTexture);
9433
+ Add(bindings, "pbr.clearCoatBump", clearCoat?.bumpTexture);
9434
+ const sheen = getPbrSheen(material);
9435
+ Add(bindings, "pbr.sheen", sheen?.texture);
9436
+ Add(bindings, "pbr.sheenRoughness", sheen?.roughnessTexture);
9437
+ const iridescence = getPbrIridescence(material);
9438
+ Add(bindings, "pbr.iridescence", iridescence?.texture);
9439
+ Add(bindings, "pbr.iridescenceThickness", iridescence?.thicknessTexture);
9440
+ Add(bindings, "pbr.anisotropy", getPbrAnisotropy(material)?.texture);
9441
+ const subsurface = getPbrSubsurface(material);
9442
+ Add(bindings, "pbr.translucencyColor", subsurface?.translucency?.colorTexture);
9443
+ Add(bindings, "pbr.translucencyIntensity", subsurface?.translucency?.intensityTexture);
9444
+ Add(bindings, "pbr.thickness", subsurface?.thickness?.texture);
9445
+ Add(bindings, "pbr.transmission", getPbrTransmission(material)?.texture);
9446
+ return bindings;
9447
+ }
9448
+ function GetShaderBindings(material) {
9449
+ const bindings = [];
9450
+ for (const declaration of material.samplerDecls) {
9451
+ Add(bindings, `shader.sampler:${declaration.name}`, getShaderTexture(material, declaration.name));
9452
+ }
9453
+ return bindings;
9454
+ }
9455
+ function GetNodeBindings(material) {
9456
+ const bindings = [];
9457
+ for (const [name, input] of Object.entries(material.inputs)) {
9458
+ if (input.type === "texture2d") {
9459
+ Add(bindings, `node.texture:${name}`, input.texture);
9460
+ }
9461
+ }
9462
+ return bindings;
9463
+ }
9464
+ /**
9465
+ * Builds canonical topology edges without retaining any UI descriptor implementation.
9466
+ * @param material The exact source material.
9467
+ * @returns Texture edges in canonical family order.
9468
+ * @internal
9469
+ */
9470
+ function GetMaterialTopologyBindings(material) {
9471
+ switch (getMaterialFamily(material)) {
9472
+ case "standard":
9473
+ return GetStandardBindings(material);
9474
+ case "pbr":
9475
+ return GetPbrBindings(material);
9476
+ case "shader":
9477
+ return GetShaderBindings(material);
9478
+ case "node":
9479
+ return GetNodeBindings(material);
9480
+ default:
9481
+ return [];
9482
+ }
9483
+ }
9484
+
9485
+ function IsObject(value) {
9486
+ return typeof value === "object" && value !== null;
9487
+ }
9488
+ function GetSceneMaterials(scene) {
9489
+ const sources = new Set();
9490
+ const materials = [];
9491
+ const views = new Set();
9492
+ if (!Array.isArray(scene.meshes)) {
9493
+ return { sources: materials, views: [] };
9494
+ }
9495
+ for (const mesh of scene.meshes) {
9496
+ if (!IsObject(mesh) || !IsObject(mesh.material)) {
9497
+ continue;
9498
+ }
9499
+ try {
9500
+ const source = getMaterialSource(mesh.material);
9501
+ if (!sources.has(source)) {
9502
+ sources.add(source);
9503
+ materials.push(source);
9504
+ }
9505
+ if (mesh.material !== source) {
9506
+ views.add(mesh.material);
9507
+ }
9508
+ }
9509
+ catch {
9510
+ // Stale or malformed materials are omitted until a later topology update.
9511
+ }
9512
+ }
9513
+ return { sources: materials, views: [...views] };
9514
+ }
9515
+ function AppendTextureState(snapshot, texture) {
9516
+ try {
9517
+ const metadata = getTextureMetadata(texture);
9518
+ if (!metadata) {
9519
+ snapshot.push("stale-texture");
9520
+ return;
9521
+ }
9522
+ const texture2d = texture;
9523
+ snapshot.push(metadata.kind, metadata.name, metadata.origin, metadata.width, metadata.height, metadata.layers, metadata.depth, metadata.format, metadata.mipLevelCount, metadata.sampleType, metadata.colorSpace, metadata.invertY, metadata.sampler?.addressModeU, metadata.sampler?.addressModeV, metadata.sampler?.addressModeW, metadata.sampler?.minFilter, metadata.sampler?.magFilter, metadata.sampler?.mipmapFilter, metadata.sampler?.maxAnisotropy, metadata.capabilities.renderAttachment, metadata.capabilities.dynamicUpdate, metadata.capabilities.sampledDepth, getTextureCoordinateIndex(texture2d), hasTextureTransform(texture2d));
9524
+ if (hasTextureTransform(texture2d)) {
9525
+ const transform = getTextureTransform(texture2d);
9526
+ snapshot.push(transform?.uOffset, transform?.vOffset, transform?.uScale, transform?.vScale, transform?.uAng);
9527
+ }
9528
+ }
9529
+ catch {
9530
+ snapshot.push("stale-texture");
9531
+ }
9532
+ }
9533
+ function AppendMaterialStructure(snapshot, material, family) {
9534
+ if (family === "node") {
9535
+ const inputs = Object.entries(material.inputs);
9536
+ snapshot.push(inputs.length);
9537
+ for (const [name, input] of inputs) {
9538
+ snapshot.push(name, input.type);
9539
+ }
9540
+ }
9541
+ else if (family === "shader") {
9542
+ const shader = material;
9543
+ snapshot.push(shader.attributes.length, ...shader.attributes, shader.uniformDecls.length);
9544
+ for (const declaration of shader.uniformDecls) {
9545
+ snapshot.push(declaration.name, declaration.type);
9546
+ }
9547
+ snapshot.push(shader.samplerDecls.length);
9548
+ for (const declaration of shader.samplerDecls) {
9549
+ snapshot.push(declaration.name, declaration.sampleType, declaration.viewDimension, declaration.comparison);
9550
+ }
9551
+ snapshot.push(shader.storageBufferDecls.length);
9552
+ for (const declaration of shader.storageBufferDecls) {
9553
+ snapshot.push(declaration.name, declaration.type);
9554
+ }
9555
+ snapshot.push(shader.defines.length);
9556
+ for (const define of shader.defines) {
9557
+ snapshot.push(define.name, define.value);
9558
+ }
9559
+ }
9560
+ else if (family === "pbr") {
9561
+ const pbr = material;
9562
+ const subsurface = getPbrSubsurface(pbr);
9563
+ snapshot.push(!!getPbrMetallicReflectance(pbr), !!getPbrClearCoat(pbr), !!getPbrSheen(pbr), !!getPbrIridescence(pbr), !!getPbrAnisotropy(pbr), !!subsurface, !!subsurface?.translucency, !!subsurface?.thickness, !!getPbrTransmission(pbr));
9564
+ }
9565
+ }
9566
+ /**
9567
+ * Maintains deterministic, Inspector-owned resource records for one Lite engine.
9568
+ * @internal
9569
+ */
9570
+ class SceneResourceIndex {
9571
+ constructor(_engine) {
9572
+ this._engine = _engine;
9573
+ this._sceneSnapshots = new Map();
9574
+ this._materialRecords = new Map();
9575
+ this._textureRecords = new Map();
9576
+ this._textureOrdinals = new WeakMap();
9577
+ this._nextTextureOrdinal = 1;
9578
+ this.refresh();
9579
+ }
9580
+ /** Rebuilds the resource topology from current public scene and binding state. */
9581
+ refresh() {
9582
+ const scenes = this._getScenes();
9583
+ const sceneMaterials = new Map();
9584
+ const sceneViews = new Map();
9585
+ const materialDrafts = new Map();
9586
+ for (const scene of scenes) {
9587
+ const { sources, views } = GetSceneMaterials(scene);
9588
+ sceneMaterials.set(scene, sources);
9589
+ sceneViews.set(scene, views);
9590
+ for (const source of sources) {
9591
+ let draft = materialDrafts.get(source);
9592
+ if (!draft) {
9593
+ draft = {
9594
+ source,
9595
+ family: getMaterialFamily(source),
9596
+ displayName: source.name || "Material",
9597
+ scenes: [],
9598
+ bindings: GetMaterialTopologyBindings(source),
9599
+ };
9600
+ materialDrafts.set(source, draft);
9601
+ }
9602
+ draft.scenes.push(scene);
9603
+ }
9604
+ }
9605
+ const materialRecords = new Map();
9606
+ for (const draft of materialDrafts.values()) {
9607
+ materialRecords.set(draft.source, draft);
9608
+ }
9609
+ const viewBindings = new Map();
9610
+ for (const views of sceneViews.values()) {
9611
+ for (const view of views) {
9612
+ if (viewBindings.has(view)) {
9613
+ continue;
9614
+ }
9615
+ const sourceBindings = materialRecords.get(getMaterialSource(view))?.bindings ?? [];
9616
+ viewBindings.set(view, GetMaterialTopologyBindings(view).filter((binding) => sourceBindings.find((candidate) => candidate.id === binding.id)?.entity !== binding.entity));
9617
+ }
9618
+ }
9619
+ const textureDrafts = new Map();
9620
+ const addBindings = (material, bindings) => {
9621
+ for (const binding of bindings) {
9622
+ let texture = textureDrafts.get(binding.entity);
9623
+ if (!texture) {
9624
+ try {
9625
+ const metadata = getTextureMetadata(binding.entity);
9626
+ if (!metadata) {
9627
+ continue;
9628
+ }
9629
+ texture = {
9630
+ entity: binding.entity,
9631
+ metadata,
9632
+ ordinal: this._getTextureOrdinal(binding.entity),
9633
+ consumers: [],
9634
+ };
9635
+ }
9636
+ catch {
9637
+ continue;
9638
+ }
9639
+ textureDrafts.set(binding.entity, texture);
9640
+ }
9641
+ texture.consumers.push({ material, bindingId: binding.id });
9642
+ }
9643
+ };
9644
+ for (const material of materialRecords.values()) {
9645
+ addBindings(material.source, material.bindings);
9646
+ }
9647
+ for (const [view, bindings] of viewBindings) {
9648
+ addBindings(view, bindings);
9649
+ }
9650
+ const textureRecords = new Map();
9651
+ for (const draft of textureDrafts.values()) {
9652
+ textureRecords.set(draft.entity, draft);
9653
+ }
9654
+ const sceneSnapshots = new Map();
9655
+ for (const scene of scenes) {
9656
+ const materials = (sceneMaterials.get(scene) ?? []).flatMap((source) => {
9657
+ const record = materialRecords.get(source);
9658
+ return record ? [record] : [];
9659
+ });
9660
+ const seenTextures = new Set();
9661
+ const textures = [];
9662
+ for (const material of materials) {
9663
+ for (const binding of material.bindings) {
9664
+ const record = textureRecords.get(binding.entity);
9665
+ if (record && !seenTextures.has(binding.entity)) {
9666
+ seenTextures.add(binding.entity);
9667
+ textures.push(record);
9668
+ }
9669
+ }
9670
+ }
9671
+ for (const view of sceneViews.get(scene) ?? []) {
9672
+ for (const binding of viewBindings.get(view) ?? []) {
9673
+ const record = textureRecords.get(binding.entity);
9674
+ if (record && !seenTextures.has(binding.entity)) {
9675
+ seenTextures.add(binding.entity);
9676
+ textures.push(record);
9677
+ }
9678
+ }
9679
+ }
9680
+ sceneSnapshots.set(scene, { scene, materials, textures });
9681
+ }
9682
+ this._sceneSnapshots = sceneSnapshots;
9683
+ this._materialRecords = materialRecords;
9684
+ this._textureRecords = textureRecords;
9685
+ }
9686
+ /**
9687
+ * Captures the public identities and values that drive index and Properties refresh.
9688
+ * @returns A deterministic topology snapshot.
9689
+ */
9690
+ getTopologySnapshot() {
9691
+ const snapshot = [];
9692
+ const seenMaterials = new Set();
9693
+ const seenTextures = new Set();
9694
+ const appendMaterial = (material) => {
9695
+ if (seenMaterials.has(material)) {
9696
+ return;
9697
+ }
9698
+ seenMaterials.add(material);
9699
+ const family = getMaterialFamily(material);
9700
+ const bindings = GetMaterialTopologyBindings(material);
9701
+ snapshot.push(family, material.name, bindings.length);
9702
+ AppendMaterialStructure(snapshot, material, family);
9703
+ for (const binding of bindings) {
9704
+ snapshot.push(binding.id, binding.entity);
9705
+ if (!seenTextures.has(binding.entity)) {
9706
+ seenTextures.add(binding.entity);
9707
+ AppendTextureState(snapshot, binding.entity);
9708
+ }
9709
+ }
9710
+ };
9711
+ for (const surface of this._engine.surfaces) {
9712
+ snapshot.push(surface);
9713
+ for (const context of getRenderingContexts(surface)) {
9714
+ snapshot.push(context);
9715
+ if (getRenderingContextKind(context) !== "scene") {
9716
+ continue;
9717
+ }
9718
+ const scene = context;
9719
+ if (!Array.isArray(scene.meshes)) {
9720
+ snapshot.push("invalid-meshes");
9721
+ continue;
9722
+ }
9723
+ snapshot.push(scene.meshes.length);
9724
+ for (const mesh of scene.meshes) {
9725
+ snapshot.push(mesh);
9726
+ if (!IsObject(mesh) || !IsObject(mesh.material)) {
9727
+ snapshot.push(mesh?.material);
9728
+ continue;
9729
+ }
9730
+ try {
9731
+ const source = getMaterialSource(mesh.material);
9732
+ snapshot.push(mesh.material, source);
9733
+ appendMaterial(source);
9734
+ if (mesh.material !== source) {
9735
+ appendMaterial(mesh.material);
9736
+ }
9737
+ }
9738
+ catch {
9739
+ snapshot.push("stale-material");
9740
+ }
9741
+ }
9742
+ }
9743
+ }
9744
+ return snapshot;
9745
+ }
9746
+ static AreTopologySnapshotsEqual(left, right) {
9747
+ return left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
9748
+ }
9749
+ getSceneSnapshot(scene) {
9750
+ return this._sceneSnapshots.get(scene) ?? { scene, materials: [], textures: [] };
9751
+ }
9752
+ getMaterialRecord(material) {
9753
+ return this._materialRecords.get(material);
9754
+ }
9755
+ getTextureRecord(texture) {
9756
+ return this._textureRecords.get(texture);
9757
+ }
9758
+ dispose() {
9759
+ this._sceneSnapshots.clear();
9760
+ this._materialRecords.clear();
9761
+ this._textureRecords.clear();
9762
+ this._textureOrdinals = new WeakMap();
9763
+ }
9764
+ _getTextureOrdinal(texture) {
9765
+ let ordinal = this._textureOrdinals.get(texture);
9766
+ if (ordinal === undefined) {
9767
+ ordinal = this._nextTextureOrdinal++;
9768
+ this._textureOrdinals.set(texture, ordinal);
9769
+ }
9770
+ return ordinal;
9771
+ }
9772
+ _getScenes() {
9773
+ const scenes = [];
9774
+ const seen = new Set();
9775
+ for (const surface of this._engine.surfaces) {
9776
+ for (const context of getRenderingContexts(surface)) {
9777
+ if (getRenderingContextKind(context) === "scene" && !seen.has(context)) {
9778
+ const scene = context;
9779
+ seen.add(scene);
9780
+ scenes.push(scene);
9781
+ }
9782
+ }
9783
+ }
9784
+ return scenes;
9785
+ }
9786
+ }
9787
+
9788
+ /**
9789
+ * The unique identity for the Inspector-owned Lite scene resource index.
9790
+ * @internal
9791
+ */
9792
+ const SceneResourceIndexServiceIdentity = Symbol("SceneResourceIndex");
9793
+ /**
9794
+ * Creates one disposable Lite scene resource index per Inspector service container.
9795
+ * @internal
9796
+ */
9797
+ const SceneResourceIndexServiceDefinition = {
9798
+ friendlyName: "Babylon Lite Scene Resource Index",
9799
+ produces: [SceneResourceIndexServiceIdentity],
9800
+ consumes: [EngineContextIdentity, WatcherServiceIdentity],
9801
+ factory: (engineContext, watcherService) => {
9802
+ const index = new SceneResourceIndex(engineContext.engine);
9803
+ const onChanged = new Observable();
9804
+ const onDisposed = new Observable();
9805
+ let refreshPending = false;
9806
+ let isDisposed = false;
9807
+ const topologyWatcher = watcherService.watchValue(() => index.getTopologySnapshot(), () => {
9808
+ if (refreshPending || isDisposed) {
9809
+ return;
9810
+ }
9811
+ refreshPending = true;
9812
+ queueMicrotask(() => {
9813
+ refreshPending = false;
9814
+ if (!isDisposed) {
9815
+ index.refresh();
9816
+ onChanged.notifyObservers();
9817
+ }
9818
+ });
9819
+ }, SceneResourceIndex.AreTopologySnapshotsEqual);
9820
+ return {
9821
+ getSceneSnapshot: (scene) => index.getSceneSnapshot(scene),
9822
+ getMaterialRecord: (material) => index.getMaterialRecord(material),
9823
+ getTextureRecord: (texture) => index.getTextureRecord(texture),
9824
+ onChanged,
9825
+ onDisposed,
9826
+ get isDisposed() {
9827
+ return isDisposed;
9828
+ },
9829
+ refresh: () => {
9830
+ if (!isDisposed) {
9831
+ index.refresh();
9832
+ onChanged.notifyObservers();
9833
+ }
9834
+ },
9835
+ dispose: () => {
9836
+ if (isDisposed) {
9837
+ return;
9838
+ }
9839
+ isDisposed = true;
9840
+ topologyWatcher.dispose();
9841
+ onDisposed.notifyObservers();
9842
+ onDisposed.clear();
9843
+ index.dispose();
9844
+ onChanged.clear();
9845
+ },
9846
+ };
9847
+ },
9848
+ };
9849
+
9850
+ const StandardMaterialAdapter = MakeLazyComponent(async () => (await import('./standardMaterialProperties-BbYdFX86.js')).StandardMaterialAdapter, {
9851
+ spinnerLabel: "Loading Standard material properties",
9852
+ });
9853
+ const PbrMaterialAdapter = MakeLazyComponent(async () => (await import('./pbrMaterialProperties-jfJddBxY.js')).PbrMaterialAdapter, {
9854
+ spinnerLabel: "Loading PBR material properties",
9855
+ });
9856
+ const ShaderMaterialAdapter = MakeLazyComponent(async () => (await import('./shaderMaterialProperties-CTH_KfBK.js')).ShaderMaterialAdapter, {
9857
+ spinnerLabel: "Loading Shader material properties",
9858
+ });
9859
+ const NodeMaterialAdapter = MakeLazyComponent(async () => (await import('./nodeMaterialProperties-ELFMlzS1.js')).NodeMaterialAdapter, {
9860
+ spinnerLabel: "Loading Node material properties",
9861
+ });
9862
+ const FamilyConfigurations = {
9863
+ standard: {
9864
+ component: StandardMaterialAdapter,
9865
+ sections: ["general", "transparency", "lighting-colors", "textures", "texture-settings", "transform", "stencil"],
9866
+ },
9867
+ pbr: {
9868
+ component: PbrMaterialAdapter,
9869
+ sections: [
9870
+ "general",
9871
+ "transparency",
9872
+ "lighting-colors",
9873
+ "textures",
9874
+ "occlusion",
9875
+ "lightmap",
9876
+ "metallic-reflectance",
9877
+ "clear-coat",
9878
+ "sheen",
9879
+ "iridescence",
9880
+ "anisotropy",
9881
+ "subsurface-translucency",
9882
+ "subsurface-thickness",
9883
+ "subsurface-tint",
9884
+ "transmission",
9885
+ "special-modes",
9886
+ "transform",
9887
+ "stencil",
9888
+ ],
9889
+ },
9890
+ shader: {
9891
+ component: ShaderMaterialAdapter,
9892
+ sections: ["general", "inputs", "textures", "configuration"],
9893
+ },
9894
+ node: {
9895
+ component: NodeMaterialAdapter,
9896
+ sections: ["general", "inputs"],
9897
+ },
9898
+ };
9899
+ function GetSectionLabel(section) {
9900
+ return section
9901
+ .split("-")
9902
+ .map((part) => part[0].toUpperCase() + part.slice(1))
9903
+ .join(" ")
9904
+ .replace("Lighting Colors", "Lighting & Colors")
9905
+ .replace("Subsurface ", "Subsurface / ");
9906
+ }
9907
+ function TryDescribeMaterial(entity, resourceIndexService) {
9908
+ if (typeof entity !== "object" || entity === null) {
9909
+ return undefined;
9910
+ }
9911
+ try {
9912
+ const material = entity;
9913
+ const source = getMaterialSource(material);
9914
+ const record = typeof source === "object" && source !== null ? resourceIndexService.getMaterialRecord(source) : undefined;
9915
+ if (!record || !GetMaterialOwningScenes(record, material).length) {
9916
+ return undefined;
9917
+ }
9918
+ const family = getMaterialFamily(source);
9919
+ const name = material.name;
9920
+ const displayName = typeof name === "string" && name.length > 0 ? name : family ? `${family[0].toUpperCase()}${family.slice(1)} Material` : "Material";
9921
+ return { source, family, displayName, isView: isMaterialView(material) };
9922
+ }
9923
+ catch {
9924
+ return undefined;
9925
+ }
9926
+ }
9927
+ function IsSupportedFamily(family) {
9928
+ return family === "standard" || family === "pbr" || family === "shader" || family === "node";
9929
+ }
9930
+ const UnknownMaterialProperties = (props) => {
9931
+ const { material, resourceIndexService } = props;
9932
+ const getMaterialIdentity = useCallback(() => {
9933
+ const identity = TryDescribeMaterial(material, resourceIndexService);
9934
+ const record = identity && resourceIndexService.getMaterialRecord(identity.source);
9935
+ return record && identity ? identity : undefined;
9936
+ }, [material, resourceIndexService]);
9937
+ const identity = useObservableState(getMaterialIdentity, resourceIndexService.onChanged);
9938
+ if (!identity) {
9939
+ return jsx(TextPropertyLine, { label: "Error", value: "This material is unavailable." });
9940
+ }
9941
+ return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "Name", value: identity.displayName }), jsx(TextPropertyLine, { label: "Family", value: identity.family ?? "Unknown" }), jsx(TextPropertyLine, { label: "Selection", value: identity.isView ? "MaterialView" : "Material" }), identity.isView ? jsx(TextPropertyLine, { label: "Source", value: resourceIndexService.getMaterialRecord(identity.source)?.displayName ?? "Unavailable" }) : undefined] }));
9942
+ };
9943
+ const MaterialPropertiesServiceDefinition = {
9944
+ friendlyName: "Babylon Lite Material Properties",
9945
+ consumes: [PropertiesServiceIdentity, SceneResourceIndexServiceIdentity, SelectionServiceIdentity],
9946
+ factory: (propertiesService, resourceIndexService, selectionService) => {
9947
+ const registrations = Object.entries(FamilyConfigurations).map(([family, configuration]) => propertiesService.addSectionContent({
9948
+ key: `Babylon Lite ${family} Material Properties`,
9949
+ predicate: (entity) => TryDescribeMaterial(entity, resourceIndexService)?.family === family,
9950
+ content: configuration.sections.map((section) => {
9951
+ const component = configuration.component;
9952
+ const sectionContent = (props) => {
9953
+ const { context } = props;
9954
+ return createElement(component, { material: context, section, resourceIndexService, selectionService });
9955
+ };
9956
+ return {
9957
+ section: GetSectionLabel(section),
9958
+ component: sectionContent,
9959
+ };
9960
+ }),
9961
+ }));
9962
+ registrations.push(propertiesService.addSectionContent({
9963
+ key: "Babylon Lite Unknown Material Properties",
9964
+ predicate: (entity) => {
9965
+ const identity = TryDescribeMaterial(entity, resourceIndexService);
9966
+ return identity !== undefined && !IsSupportedFamily(identity.family);
9967
+ },
9968
+ content: [
9969
+ {
9970
+ section: "General",
9971
+ component: ({ context }) => jsx(UnknownMaterialProperties, { material: context, resourceIndexService: resourceIndexService }),
9972
+ },
9973
+ ],
9974
+ }));
9975
+ return {
9976
+ dispose: () => registrations.forEach((registration) => registration.dispose()),
9977
+ };
9978
+ },
9979
+ };
9980
+
8882
9981
  const MeshGeneralProperties = (props) => {
8883
9982
  const { mesh } = props;
8884
9983
  return jsx(BoundProperty, { component: SwitchPropertyLine, label: "Receive Shadows", target: mesh, propertyKey: "receiveShadows" });
@@ -8969,161 +10068,6 @@ const RenderingContextPropertiesServiceDefinition = {
8969
10068
  },
8970
10069
  };
8971
10070
 
8972
- const PrettyJSONIndent = 2;
8973
- function IsParsable(metadata) {
8974
- try {
8975
- const parsed = JSON.parse(metadata);
8976
- return !!parsed && !IsString(parsed);
8977
- }
8978
- catch {
8979
- return false;
8980
- }
8981
- }
8982
- /**
8983
- * Checks if the input is a string.
8984
- * @param input - any input to check
8985
- * @returns boolean - true if the input is a string, false otherwise
8986
- */
8987
- function IsString(input) {
8988
- return typeof input === "string" || input instanceof String;
8989
- }
8990
- /**
8991
- * Checks recursively for functions on an object and returns `false` if any are found.
8992
- * @param o any object, string or number
8993
- * @returns boolean
8994
- */
8995
- function ObjectCanSafelyStringify(o) {
8996
- if (typeof o === "function") {
8997
- return false;
8998
- }
8999
- if (o === null || o === true || o === false || typeof o === "number" || IsString(o)) {
9000
- return true;
9001
- }
9002
- if (typeof o === "object") {
9003
- if (Object.values(o).length === 0) {
9004
- return true;
9005
- }
9006
- return Object.values(o).every((value) => ObjectCanSafelyStringify(value));
9007
- }
9008
- if (Array.isArray(o)) {
9009
- return o.every((value) => ObjectCanSafelyStringify(value));
9010
- }
9011
- return false;
9012
- }
9013
- function GetMetadataEntityType(metadata) {
9014
- if (metadata == null) {
9015
- return "null";
9016
- }
9017
- else if (IsString(metadata)) {
9018
- return "string";
9019
- }
9020
- else if (!ObjectCanSafelyStringify(metadata)) {
9021
- return "object";
9022
- }
9023
- else {
9024
- return "JSON";
9025
- }
9026
- }
9027
- function HasGltfExtras(metadata) {
9028
- return IsParsable(metadata) && !!JSON.parse(metadata).gltf;
9029
- }
9030
- function StringifyMetadata(metadata, format) {
9031
- if (IsString(metadata)) {
9032
- return metadata;
9033
- }
9034
- if (metadata) {
9035
- // Try JSON.stringify even for objects with functions — it safely omits
9036
- // function-valued and undefined properties. Only fall back to String()
9037
- // for cases that actually throw (e.g. circular references).
9038
- try {
9039
- return JSON.stringify(metadata, undefined, format ? PrettyJSONIndent : undefined);
9040
- }
9041
- catch {
9042
- return String(metadata);
9043
- }
9044
- }
9045
- return null;
9046
- }
9047
- function Restringify(value, format) {
9048
- return IsParsable(value) ? JSON.stringify(JSON.parse(value), undefined, format ? PrettyJSONIndent : undefined) : value;
9049
- }
9050
- function PopulateGLTFExtras(metadata) {
9051
- if (!metadata) {
9052
- metadata = "{}";
9053
- }
9054
- if (!IsParsable(metadata)) {
9055
- return metadata;
9056
- }
9057
- try {
9058
- const parsedJson = JSON.parse(metadata);
9059
- if (parsedJson) {
9060
- if (Reflect.has(parsedJson, "gltf")) {
9061
- if (!Reflect.has(parsedJson.gltf, "extras")) {
9062
- parsedJson.gltf.extras = {};
9063
- }
9064
- }
9065
- else {
9066
- parsedJson.gltf = { extras: {} };
9067
- }
9068
- }
9069
- return JSON.stringify(parsedJson);
9070
- }
9071
- catch {
9072
- return metadata;
9073
- }
9074
- }
9075
- function SaveMetadata(entity, metadata) {
9076
- if (IsParsable(metadata)) {
9077
- entity.metadata = JSON.parse(metadata);
9078
- }
9079
- else {
9080
- entity.metadata = metadata;
9081
- }
9082
- }
9083
- const useStyles = makeStyles({
9084
- mainDiv: {
9085
- display: "flex",
9086
- flexDirection: "column",
9087
- gap: tokens.spacingVerticalXS,
9088
- },
9089
- buttonDiv: {
9090
- display: "grid",
9091
- gridAutoFlow: "column",
9092
- gridTemplateColumns: "1fr auto auto auto",
9093
- gap: tokens.spacingHorizontalXS,
9094
- },
9095
- });
9096
- /**
9097
- * Component to display metadata properties of an entity.
9098
- * @param props - The properties for the component.
9099
- * @returns A React component that displays metadata properties.
9100
- */
9101
- const MetadataProperties = (props) => {
9102
- const { entity } = props;
9103
- const classes = useStyles();
9104
- const { size } = useContext(ToolContext);
9105
- const metadata = useProperty(entity, "metadata");
9106
- const stringifiedMetadata = useMemo(() => StringifyMetadata(metadata, false) ?? "", [metadata]);
9107
- const metadataType = useMemo(() => GetMetadataEntityType(metadata), [metadata]);
9108
- const canPreventObjectCorruption = metadataType === "object";
9109
- const [preventObjectCorruption, setPreventObjectCorruption] = useState(false);
9110
- const isReadonly = canPreventObjectCorruption && preventObjectCorruption;
9111
- const [editedMetadata, setEditedMetadata] = useState(stringifiedMetadata);
9112
- useEffect(() => {
9113
- setEditedMetadata(stringifiedMetadata);
9114
- }, [stringifiedMetadata]);
9115
- const isEditedMetadataJSON = useMemo(() => IsParsable(editedMetadata), [editedMetadata]);
9116
- const unformattedEditedMetadata = useMemo(() => Restringify(editedMetadata, false), [editedMetadata]);
9117
- return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "Property Type", value: metadataType }), jsx(Collapse, { visible: canPreventObjectCorruption, children: jsx(SwitchPropertyLine, { label: "Prevent Object Corruption", value: isReadonly, onChange: setPreventObjectCorruption }) }), jsx(LineContainer, { uniqueId: "MetadataTextarea", children: jsx(Textarea, { disabled: isReadonly, value: editedMetadata, onChange: setEditedMetadata }) }), jsx(ButtonLine, { label: "Populate glTF extras", disabled: !!editedMetadata && (!IsParsable(editedMetadata) || HasGltfExtras(editedMetadata)), onClick: () => {
9118
- const isFormatted = Restringify(editedMetadata, true) === editedMetadata;
9119
- let withGLTFExtras = PopulateGLTFExtras(editedMetadata);
9120
- if (isFormatted) {
9121
- withGLTFExtras = Restringify(withGLTFExtras, true);
9122
- }
9123
- setEditedMetadata(withGLTFExtras);
9124
- } }), jsx(LineContainer, { uniqueId: "MetadataButtonDiv", children: jsxs("div", { className: classes.buttonDiv, children: [jsx(Button$1, { size: size, icon: jsx(SaveRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => SaveMetadata(entity, editedMetadata), children: jsx(Body1, { children: "Save" }) }), jsx(Tooltip$1, { content: "Undo Changes", relationship: "label", children: jsx(Button$1, { size: size, icon: jsx(ArrowUndoRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => setEditedMetadata(stringifiedMetadata) }) }), jsx(Tooltip$1, { content: "Format (Pretty Print)", relationship: "label", children: jsx(Button$1, { size: size, icon: jsx(BracesRegular, {}), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, true)) }) }), jsx(Tooltip$1, { content: "Clear Formatting (Undo Pretty Print)", relationship: "label", children: jsx(Button$1, { size: size, icon: jsx(BracesDismiss16Regular, {}), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, false)) }) })] }) })] }));
9125
- };
9126
-
9127
10071
  function GetPosition$1(node) {
9128
10072
  return [node.position.x, node.position.y, node.position.z];
9129
10073
  }
@@ -9421,30 +10365,48 @@ const TextLayerPropertiesServiceDefinition = {
9421
10365
  }),
9422
10366
  };
9423
10367
 
9424
- function IsTexture2D(entity) {
10368
+ const TextureMetadataProperties = MakeLazyComponent(async () => (await import('./textureMetadataProperties-BdNmp0Pk.js')).TextureMetadataProperties, {
10369
+ spinnerLabel: "Loading texture metadata",
10370
+ });
10371
+ function TryGetTextureMetadata(entity) {
9425
10372
  if (typeof entity !== "object" || entity === null) {
9426
10373
  return false;
9427
10374
  }
9428
- const texture = entity;
9429
- return typeof texture.width === "number" && typeof texture.height === "number" && texture.texture !== undefined && texture.view !== undefined && texture.sampler !== undefined;
10375
+ try {
10376
+ return getTextureMetadata(entity) !== undefined;
10377
+ }
10378
+ catch {
10379
+ return false;
10380
+ }
9430
10381
  }
9431
- const TextureProperties = (props) => {
9432
- const { texture } = props;
9433
- return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Width", value: texture.width, units: "px" }), jsx(StringifiedPropertyLine, { label: "Height", value: texture.height, units: "px" }), jsx(BooleanBadgePropertyLine, { label: "Invert Y", value: texture.invertY ?? false })] }));
9434
- };
9435
10382
  const TexturePropertiesServiceDefinition = {
9436
10383
  friendlyName: "Babylon Lite Texture Properties",
9437
- consumes: [PropertiesServiceIdentity],
9438
- factory: (propertiesService) => propertiesService.addSectionContent({
9439
- key: "Babylon Lite Texture Properties",
9440
- predicate: IsTexture2D,
9441
- content: [
9442
- {
9443
- section: "General",
9444
- component: ({ context }) => jsx(TextureProperties, { texture: context }),
10384
+ consumes: [PropertiesServiceIdentity, SceneResourceIndexServiceIdentity, SelectionServiceIdentity],
10385
+ factory: (propertiesService, resourceIndexService, selectionService) => {
10386
+ const recognizedTextures = new WeakSet();
10387
+ return propertiesService.addSectionContent({
10388
+ key: "Babylon Lite Texture Properties",
10389
+ predicate: (entity) => {
10390
+ if (typeof entity !== "object" || entity === null) {
10391
+ return false;
10392
+ }
10393
+ if (TryGetTextureMetadata(entity) || resourceIndexService.getTextureRecord(entity)) {
10394
+ recognizedTextures.add(entity);
10395
+ return true;
10396
+ }
10397
+ return recognizedTextures.has(entity);
9445
10398
  },
9446
- ],
9447
- }),
10399
+ content: [
10400
+ {
10401
+ section: "General",
10402
+ component: ((props) => {
10403
+ const { context } = props;
10404
+ return createElement(TextureMetadataProperties, { texture: context, resourceIndexService, selectionService });
10405
+ }),
10406
+ },
10407
+ ],
10408
+ });
10409
+ },
9448
10410
  };
9449
10411
 
9450
10412
  function ClearRemovedSelection(selectionService, engineContext, selectionWasRemoved) {
@@ -9499,6 +10461,103 @@ const CameraExplorerServiceDefinition = {
9499
10461
  },
9500
10462
  };
9501
10463
 
10464
+ const AnimationGroupIcon = () => jsx(StackRegular, { color: tokens.colorPaletteBlueForeground2 });
10465
+ const TargetedAnimationIcon = () => jsx(FilmstripRegular, { color: tokens.colorPaletteBlueForeground2 });
10466
+ const AnimationGroupExplorerServiceDefinition = {
10467
+ friendlyName: "Babylon Lite Animation Group Explorer",
10468
+ consumes: [EngineExplorerServiceIdentity, ExplorerServiceIdentity, WatcherServiceIdentity, EngineContextIdentity, SelectionServiceIdentity],
10469
+ factory: (engineExplorerService, explorerService, watcherService, engineContext, selectionService) => {
10470
+ const engine = engineContext.engine;
10471
+ const providerRegistration = engineExplorerService.addRenderingContextNodeProvider({
10472
+ order: 300,
10473
+ predicate: IsSceneContext,
10474
+ getNodes: (scene) => [
10475
+ {
10476
+ id: "animation-groups",
10477
+ kind: "group",
10478
+ getDisplayInfo: () => ({ name: "Animation Groups" }),
10479
+ getChildren: () => scene.animationGroups.map((group) => ({
10480
+ id: GetEntityId(group).toString(),
10481
+ kind: "item",
10482
+ entity: group,
10483
+ icon: AnimationGroupIcon,
10484
+ getDisplayInfo: () => CreateWatchedNameDisplayInfo(watcherService, group, () => group.name || "Unnamed Animation Group"),
10485
+ getChildren: () => group.targetedAnimations.map((animation) => ({
10486
+ id: GetEntityId(animation).toString(),
10487
+ kind: "item",
10488
+ entity: animation,
10489
+ icon: TargetedAnimationIcon,
10490
+ getDisplayInfo: () => {
10491
+ const onChange = new Observable();
10492
+ const watcher = watcherService.watchValue(() => GetTargetedAnimationDisplayName(animation), () => onChange.notifyObservers());
10493
+ return {
10494
+ get name() {
10495
+ return GetTargetedAnimationDisplayName(animation);
10496
+ },
10497
+ onChange,
10498
+ dispose: () => {
10499
+ watcher.dispose();
10500
+ onChange.clear();
10501
+ },
10502
+ };
10503
+ },
10504
+ })),
10505
+ })),
10506
+ },
10507
+ ],
10508
+ getSnapshot: (scene) => scene.animationGroups.flatMap((group) => [group, ...group.targetedAnimations]),
10509
+ });
10510
+ const playPauseRegistration = explorerService.addItemCommand({
10511
+ predicate: (entity) => IsAnimationGroup(engine, entity),
10512
+ order: 1100,
10513
+ getCommand: (group) => {
10514
+ const onChange = new Observable();
10515
+ const watcher = watcherService.watchProperty(group, "isPlaying", () => onChange.notifyObservers());
10516
+ return {
10517
+ type: "toggle",
10518
+ get displayName() {
10519
+ return `${group.isPlaying ? "Pause" : "Play"} Animation`;
10520
+ },
10521
+ icon: () => (group.isPlaying ? jsx(PauseFilled, {}) : jsx(PlayFilled, {})),
10522
+ hotKey: { keyCode: "Space", control: true },
10523
+ get isEnabled() {
10524
+ return group.isPlaying;
10525
+ },
10526
+ set isEnabled(enabled) {
10527
+ if (enabled) {
10528
+ playAnimation(group);
10529
+ }
10530
+ else {
10531
+ pauseAnimation(group);
10532
+ }
10533
+ watcherService.refresh();
10534
+ },
10535
+ onChange,
10536
+ dispose: () => {
10537
+ watcher.dispose();
10538
+ onChange.clear();
10539
+ },
10540
+ };
10541
+ },
10542
+ });
10543
+ const getAnimationEntities = () => GetSceneAnimationGroups(engine).flatMap((group) => [group, ...group.targetedAnimations]);
10544
+ let previousEntities = getAnimationEntities();
10545
+ const selectionWatcher = watcherService.watchValue(getAnimationEntities, (entities) => {
10546
+ if (selectionService.selectedEntity && previousEntities.includes(selectionService.selectedEntity) && !entities.includes(selectionService.selectedEntity)) {
10547
+ selectionService.selectedEntity = null;
10548
+ }
10549
+ previousEntities = entities;
10550
+ }, (left, right) => left.length === right.length && left.every((entity, index) => entity === right[index]));
10551
+ return {
10552
+ dispose: () => {
10553
+ selectionWatcher.dispose();
10554
+ playPauseRegistration.dispose();
10555
+ providerRegistration.dispose();
10556
+ },
10557
+ };
10558
+ },
10559
+ };
10560
+
9502
10561
  const LightIcon = () => jsx(LightbulbRegular, { color: tokens.colorPaletteYellowForeground2 });
9503
10562
  const LightExplorerServiceDefinition = {
9504
10563
  friendlyName: "Babylon Lite Light Explorer",
@@ -9550,37 +10609,23 @@ createFluentIcon("BreakTangent", "20", '<g transform="scale(0.5)"><path d="M27.1
9550
10609
  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>');
9551
10610
  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>');
9552
10611
 
9553
- /**
9554
- * Gets the unique materials referenced by a scene's meshes.
9555
- * @param scene The scene to inspect.
9556
- * @returns The referenced materials in mesh order.
9557
- */
9558
- function GetSceneMaterials(scene) {
9559
- return [...new Set(scene.meshes.map((mesh) => mesh.material))];
9560
- }
9561
- /**
9562
- * Gets the unique textures referenced by a scene's materials.
9563
- * @param scene The scene to inspect.
9564
- * @returns The referenced textures in material order.
9565
- */
9566
- function GetSceneTextures(scene) {
9567
- return [...new Set(GetSceneMaterials(scene).flatMap((material) => getMaterialTextures(material)))];
9568
- }
9569
-
9570
10612
  function GetMaterialDisplayName(material) {
9571
10613
  const family = getMaterialFamily(material);
9572
10614
  return material.name ?? (family ? `${family.charAt(0).toUpperCase()}${family.slice(1)} Material` : "Material");
9573
10615
  }
9574
10616
  const MaterialExplorerServiceDefinition = {
9575
10617
  friendlyName: "Babylon Lite Material Explorer",
9576
- consumes: [EngineExplorerServiceIdentity, WatcherServiceIdentity],
9577
- factory: (engineExplorerService, watcherService) => engineExplorerService.addRenderingContextNodeProvider({
10618
+ consumes: [EngineExplorerServiceIdentity, WatcherServiceIdentity, SceneResourceIndexServiceIdentity],
10619
+ factory: (engineExplorerService, watcherService, resourceIndexService) => engineExplorerService.addRenderingContextNodeProvider({
9578
10620
  order: 100,
9579
10621
  predicate: IsSceneContext,
9580
- getNodes: (scene) => [
9581
- CreateSceneExplorerSectionNode("materials", "Materials", GetSceneMaterials(scene), (material) => CreateWatchedNameDisplayInfo(watcherService, material, () => GetMaterialDisplayName(material)), () => jsx(MaterialIcon, { color: tokens.colorPaletteMarigoldForeground2 })),
9582
- ],
9583
- getSnapshot: GetSceneMaterials,
10622
+ getNodes: (scene) => {
10623
+ return [
10624
+ CreateSceneExplorerSectionNode("materials", "Materials", resourceIndexService.getSceneSnapshot(scene).materials.map((record) => record.source), (material) => CreateWatchedNameDisplayInfo(watcherService, material, () => GetMaterialDisplayName(material)), () => jsx(MaterialIcon, { color: tokens.colorPaletteMarigoldForeground2 })),
10625
+ ];
10626
+ },
10627
+ getSnapshot: (scene) => resourceIndexService.getSceneSnapshot(scene).materials.map((record) => record.source),
10628
+ onChanged: resourceIndexService.onChanged,
9584
10629
  }),
9585
10630
  };
9586
10631
 
@@ -9829,19 +10874,29 @@ const TextLayerExplorerServiceDefinition = CreateRenderingLayerExplorerServiceDe
9829
10874
  icon: TextLayerIcon,
9830
10875
  });
9831
10876
 
9832
- function GetTextureDisplayName(texture, index) {
9833
- return `Texture ${index + 1} (${texture.width} x ${texture.height})`;
10877
+ function GetTextureDisplayName(record) {
10878
+ if (record.metadata.kind === "cube") {
10879
+ return `Texture ${record.ordinal} (Cube)`;
10880
+ }
10881
+ return typeof record.metadata.width === "number" && typeof record.metadata.height === "number"
10882
+ ? `Texture ${record.ordinal} (${record.metadata.width} x ${record.metadata.height})`
10883
+ : `Texture ${record.ordinal}`;
9834
10884
  }
9835
10885
  const TextureExplorerServiceDefinition = {
9836
10886
  friendlyName: "Babylon Lite Texture Explorer",
9837
- consumes: [EngineExplorerServiceIdentity],
9838
- factory: (engineExplorerService) => engineExplorerService.addRenderingContextNodeProvider({
10887
+ consumes: [EngineExplorerServiceIdentity, SceneResourceIndexServiceIdentity],
10888
+ factory: (engineExplorerService, resourceIndexService) => engineExplorerService.addRenderingContextNodeProvider({
9839
10889
  order: 200,
9840
10890
  predicate: IsSceneContext,
9841
- getNodes: (scene) => [
9842
- CreateSceneExplorerSectionNode("textures", "Textures", GetSceneTextures(scene), (texture, index) => ({ name: GetTextureDisplayName(texture, index) }), () => jsx(ImageRegular, { color: tokens.colorPaletteGrapeForeground2 })),
9843
- ],
9844
- getSnapshot: GetSceneTextures,
10891
+ getNodes: (scene) => {
10892
+ const records = resourceIndexService.getSceneSnapshot(scene).textures;
10893
+ const recordsByEntity = new Map(records.map((record) => [record.entity, record]));
10894
+ return [
10895
+ CreateSceneExplorerSectionNode("textures", "Textures", records.map((record) => record.entity), (texture) => ({ name: GetTextureDisplayName(recordsByEntity.get(texture)) }), () => jsx(ImageRegular, { color: tokens.colorPaletteGrapeForeground2 })),
10896
+ ];
10897
+ },
10898
+ getSnapshot: (scene) => resourceIndexService.getSceneSnapshot(scene).textures.map((record) => record.entity),
10899
+ onChanged: resourceIndexService.onChanged,
9845
10900
  }),
9846
10901
  };
9847
10902
 
@@ -9882,6 +10937,7 @@ function ShowInspector(engine, options = {}) {
9882
10937
  engineContextServiceDefinition,
9883
10938
  watcherServiceDefinition,
9884
10939
  EngineExplorerServiceDefinition,
10940
+ SceneResourceIndexServiceDefinition,
9885
10941
  MeshExplorerServiceDefinition,
9886
10942
  CameraExplorerServiceDefinition,
9887
10943
  LightExplorerServiceDefinition,
@@ -9890,6 +10946,7 @@ function ShowInspector(engine, options = {}) {
9890
10946
  TextureExplorerServiceDefinition,
9891
10947
  TextLayerExplorerServiceDefinition,
9892
10948
  SpriteLayerExplorerServiceDefinition,
10949
+ AnimationGroupExplorerServiceDefinition,
9893
10950
  PropertiesServiceDefinition,
9894
10951
  EnginePropertiesServiceDefinition,
9895
10952
  RenderingContextPropertiesServiceDefinition,
@@ -9902,12 +10959,14 @@ function ShowInspector(engine, options = {}) {
9902
10959
  TexturePropertiesServiceDefinition,
9903
10960
  TextLayerPropertiesServiceDefinition,
9904
10961
  SpriteLayerPropertiesServiceDefinition,
10962
+ AnimationPropertiesServiceDefinition,
9905
10963
  SettingsServiceDefinition,
9906
10964
  watcherSettingsServiceDefinition,
9907
10965
  ShellSettingsServiceDefinition,
9908
10966
  watcherRefreshToolbarServiceDefinition,
9909
10967
  SelectionServiceDefinition,
9910
10968
  EngineSelectionServiceDefinition,
10969
+ PickingServiceDefinition,
9911
10970
  ];
9912
10971
  return {
9913
10972
  serviceDefinitions,
@@ -9917,5 +10976,5 @@ function ShowInspector(engine, options = {}) {
9917
10976
  });
9918
10977
  }
9919
10978
 
9920
- export { PropertyLine as $, Accordion as A, BooleanBadgePropertyLine as B, Checkbox as C, DraggableLine as D, EngineContextIdentity as E, FileUploadLine as F, GetPropertyDescriptor as G, HexPropertyLine as H, InfoLabel as I, MakeModularBridge as J, MakeModularTool as K, Link as L, MakeDialogTeachingMoment as M, MakePopoverTeachingMoment as N, MakeTeachingMoment as O, MessageBar as P, NumberDropdown as Q, NumberDropdownPropertyLine as R, ShellServiceIdentity as S, NumberInputPropertyLine as T, ObservableCollection as U, Pane as V, PlaceholderPropertyLine as W, Popover as X, PositionedPopover as Y, PropertiesServiceIdentity as Z, PropertyContext as _, AccordionSection as a, SearchBar as a0, SearchBox as a1, SelectionServiceDefinition as a2, SelectionServiceIdentity as a3, SettingsServiceIdentity as a4, SettingsStore as a5, SettingsStoreIdentity as a6, ShowInspector as a7, SidePaneContainer as a8, Slider as a9, useInterceptObservable as aA, useKeyListener as aB, useKeyState as aC, useObservableCollection as aD, useObservableState as aE, useOrderedObservableCollection as aF, usePollingObservable as aG, usePropertyChangedNotifier as aH, useResource as aI, useTheme as aJ, useThemeMode as aK, useToast as aL, SpinButton as aa, SpinButtonPropertyLine as ab, StringDropdown as ac, StringDropdownPropertyLine as ad, StringifiedPropertyLine as ae, Switch as af, SwitchPropertyLine as ag, SyncedSliderInput as ah, SyncedSliderPropertyLine as ai, TeachingMoment as aj, TextAreaPropertyLine as ak, TextInput as al, TextInputPropertyLine as am, TextPropertyLine as an, Textarea as ao, Theme as ap, ThemeServiceIdentity as aq, ToastProvider as ar, ToggleButton as as, Tooltip as at, UploadButton as au, WatcherServiceIdentity as av, useAngleConverters as aw, useAsyncResource as ax, useEventListener as ay, useEventfulState as az, AccordionSectionItem as b, BridgeCommandRegistryIdentity as c, BuiltInsExtensionFeed as d, Button as e, ButtonLine as f, CheckboxPropertyLine as g, ChildWindow as h, Collapse as i, ComboBox as j, ComboBoxPropertyLine as k, ConstructorFactory as l, Dropdown as m, EngineExplorerServiceIdentity as n, EntitySelector as o, ErrorBoundary as p, ExplorerServiceIdentity as q, ExtensibleAccordion as r, InterceptFunction as s, InterceptProperty as t, useExtensionManager as u, IsPropertyReadonly as v, LineContainer as w, LinkPropertyLine as x, List as y, MakeLazyComponent as z };
9921
- //# sourceMappingURL=index-D8vFySa7.js.map
10979
+ export { EntitySelector as $, Accordion as A, ButtonLine as B, ComputedProperty as C, DerivedProperty as D, BridgeCommandRegistryIdentity as E, BuiltInsExtensionFeed as F, GetAnimationFrameRate as G, Button as H, Checkbox as I, CheckboxPropertyLine as J, ChildWindow as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, Collapse as O, PropertyLine as P, ComboBox as Q, ResourceSelector as R, ShellServiceIdentity as S, TextPropertyLine as T, ComboBoxPropertyLine as U, Vector2PropertyLine as V, ConstructorFactory as W, DraggableLine as X, Dropdown as Y, EngineContextIdentity as Z, EngineExplorerServiceIdentity as _, useWatcher as a, useTheme as a$, ErrorBoundary as a0, ExplorerServiceIdentity as a1, ExtensibleAccordion as a2, FileUploadLine as a3, GetPropertyDescriptor as a4, HexPropertyLine as a5, InfoLabel as a6, InterceptFunction as a7, InterceptProperty as a8, IsPropertyReadonly as a9, SpinButton as aA, SpinButtonPropertyLine as aB, StringDropdown as aC, Switch as aD, SyncedSliderInput as aE, TeachingMoment as aF, TextAreaPropertyLine as aG, TextInput as aH, Textarea as aI, Theme as aJ, ThemeServiceIdentity as aK, ToastProvider as aL, ToggleButton as aM, Tooltip as aN, UploadButton as aO, WatcherServiceIdentity as aP, useAngleConverters as aQ, useAsyncResource as aR, useEventListener as aS, useEventfulState as aT, useInterceptObservable as aU, useKeyListener as aV, useKeyState as aW, useObservableCollection as aX, useOrderedObservableCollection as aY, usePollingObservable as aZ, useResource as a_, LineContainer as aa, List as ab, MakeDialogTeachingMoment as ac, MakeLazyComponent as ad, MakeModularBridge as ae, MakeModularTool as af, MakePopoverTeachingMoment as ag, MakeTeachingMoment as ah, NumberDropdown as ai, ObservableCollection as aj, Pane as ak, PlaceholderPropertyLine as al, Popover as am, PositionedPopover as an, PropertiesServiceIdentity as ao, PropertyContext as ap, SearchBar as aq, SearchBox as ar, SelectionServiceDefinition as as, SelectionServiceIdentity as at, SettingsServiceIdentity as au, SettingsStore as av, SettingsStoreIdentity as aw, ShowInspector as ax, SidePaneContainer as ay, Slider as az, useWatchedValue as b, useThemeMode as b0, useToast as b1, SeekAnimationFrame as c, BoundProperty as d, SyncedSliderPropertyLine as e, SwitchPropertyLine as f, StringifiedPropertyLine as g, GetTargetedAnimationDisplayName as h, GetEntityAnimationGroups as i, GetEntityId as j, TextInputPropertyLine as k, Color3PropertyLine as l, NumberDropdownPropertyLine as m, StringDropdownPropertyLine as n, useObservableState as o, usePropertyChangedNotifier as p, GetMaterialOwningScenes as q, Color4PropertyLine as r, Vector4PropertyLine as s, Vector3PropertyLine as t, useExtensionManager as u, BooleanBadgePropertyLine as v, LinkPropertyLine as w, GetMaterialTopologyBindings as x, AccordionSection as y, AccordionSectionItem as z };
10980
+ //# sourceMappingURL=index-Cy8gzpYG.js.map