@babylonjs/inspector 9.30.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
@@ -108,6 +108,9 @@ import { MeshBlendingRadiusClass, UnpackMeshBlendingTag, PackMeshBlendingTag } f
108
108
  import { RenderingManager } from '@babylonjs/core/Rendering/renderingManager.js';
109
109
  import '@babylonjs/core/Rendering/edgesRenderer.js';
110
110
  import '@babylonjs/core/Rendering/outlineRenderer.js';
111
+ import { GaussianSplattingMaterial } from '@babylonjs/core/Materials/GaussianSplatting/gaussianSplattingMaterial.js';
112
+ import { GaussianSplattingSizeMaterialPlugin } from '@babylonjs/core/Materials/GaussianSplatting/gaussianSplattingSizeMaterialPlugin.js';
113
+ import { GaussianSplattingOverdrawMaterialPlugin } from '@babylonjs/core/Materials/GaussianSplatting/gaussianSplattingOverdrawMaterialPlugin.js';
111
114
  import { GPUParticleSystem } from '@babylonjs/core/Particles/gpuParticleSystem.js';
112
115
  import { ConvertToNodeParticleSystemSetAsync } from '@babylonjs/core/Particles/Node/nodeParticleSystemSet.helper.js';
113
116
  import { ParticleHelper } from '@babylonjs/core/Particles/particleHelper.js';
@@ -3413,29 +3416,22 @@ const useStyles$W = makeStyles({
3413
3416
  },
3414
3417
  });
3415
3418
  /**
3416
- * Toggles between two states using a button with icons.
3417
- * If no disabledIcon is provided, the button will toggle between visual enabled/disabled states without an icon change
3419
+ * Displays a controlled toggle button with icons.
3420
+ * The parent supplies the checked state through value; clicks request the opposite value through onChange.
3421
+ * If no uncheckedIcon is provided, the same icon is used for both states.
3418
3422
  *
3419
- * @param props
3420
- * @returns
3423
+ * @param props The controlled state, change callback, icons, and button presentation.
3424
+ * @returns The toggle button.
3421
3425
  */
3422
3426
  const ToggleButton = (props) => {
3423
3427
  ToggleButton.displayName = "ToggleButton";
3424
3428
  const { value, onChange, title, appearance = "subtle", ariaLabel } = props;
3425
3429
  const { size } = useContext(ToolContext);
3426
3430
  const classes = useStyles$W();
3427
- const [checked, setChecked] = useState(value);
3428
3431
  const toggle = useCallback(() => {
3429
- setChecked((prevChecked) => {
3430
- const enabled = !prevChecked;
3431
- onChange(enabled);
3432
- return enabled;
3433
- });
3434
- }, [onChange]);
3435
- useEffect(() => {
3436
- setChecked(props.value);
3437
- }, [props.value]);
3438
- 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 }) }));
3432
+ onChange(!value);
3433
+ }, [value, onChange]);
3434
+ 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 }) }));
3439
3435
  };
3440
3436
 
3441
3437
  const useInfoLabelStyles = makeStyles({
@@ -5296,7 +5292,7 @@ function MakeModularTool(options) {
5296
5292
  }
5297
5293
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
5298
5294
  if (extensionFeeds.length > 0) {
5299
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-DfE1ZOnF.js');
5295
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-O8zrOnmL.js');
5300
5296
  serviceContainer.addService(ExtensionListServiceDefinition);
5301
5297
  }
5302
5298
  // Register all external services (that make up a unique tool).
@@ -5717,6 +5713,11 @@ const useStyles$P = makeStyles({
5717
5713
  minWidth: "fit-content",
5718
5714
  maxWidth: "350px",
5719
5715
  },
5716
+ compactOption: {
5717
+ fontSize: tokens.fontSizeBase200,
5718
+ lineHeight: tokens.lineHeightBase200,
5719
+ padding: `${tokens.spacingVerticalXXS} ${tokens.spacingHorizontalS}`,
5720
+ },
5720
5721
  });
5721
5722
  /**
5722
5723
  * Wrapper around a Fluent ComboBox that allows for filtering options.
@@ -5740,13 +5741,13 @@ const ComboBox = forwardRef((props, ref) => {
5740
5741
  noOptionsMessage: "No items match your search.",
5741
5742
  optionToReactKey: (option) => option.value,
5742
5743
  optionToText: (option) => option.children,
5743
- renderOption: (option) => (jsx(Option, { value: option.value, text: option.children, children: option.children }, option.value)),
5744
+ renderOption: (option) => (jsx(Option, { value: option.value, text: option.children, className: size === "small" ? styles.compactOption : undefined, children: option.children }, option.value)),
5744
5745
  });
5745
5746
  const onOptionSelect = (_e, data) => {
5746
5747
  setQuery(data.optionText ?? "");
5747
5748
  data.optionValue && props.onChange(data.optionValue);
5748
5749
  };
5749
- 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 })] }));
5750
+ 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 })] }));
5750
5751
  });
5751
5752
 
5752
5753
  const useDraggableStyles = makeStyles({
@@ -5820,26 +5821,18 @@ const useStyles$O = makeStyles({
5820
5821
  },
5821
5822
  });
5822
5823
  /**
5823
- * A generic primitive component with a ComboBox for selecting from a list of entities.
5824
- * Supports entities with duplicate names by using uniqueId for identity.
5825
- * @param props ChooseEntityProps
5826
- * @returns EntitySelector component
5824
+ * Displays a linked resource with one unlink-or-edit action, or a selector when unlinked.
5825
+ * @param props Resource identity, selection, navigation, and supported actions.
5826
+ * @returns The linked value or its replacement selector.
5827
5827
  */
5828
- function EntitySelector(props) {
5829
- const { value, onLink, getEntities, getName, filter, defaultValue } = props;
5830
- const onChange = props.onChange;
5828
+ function ResourceSelector(props) {
5829
+ const { value, getEntities, getName, getId, filter, onSelect, onClear, onLink, disabled, ariaLabel, showEmptySelector } = props;
5831
5830
  const classes = useStyles$O();
5832
5831
  const comboBoxRef = useRef(null);
5833
- // Build options with uniqueId as key
5834
- const options = useMemo(() => {
5835
- return getEntities()
5836
- .filter((e) => e.uniqueId !== undefined && (!filter || filter(e)))
5837
- .map((entity) => ({
5838
- label: getName(entity)?.toString() || "",
5839
- value: entity.uniqueId.toString(),
5840
- }))
5841
- .sort((a, b) => a.label.localeCompare(b.label));
5842
- }, [getEntities, getName, filter]);
5832
+ const options = useMemo(() => getEntities()
5833
+ .filter((entity) => !filter || filter(entity))
5834
+ .map((entity) => ({ label: getName(entity)?.toString() || "", value: getId(entity) }))
5835
+ .sort((a, b) => a.label.localeCompare(b.label)), [getEntities, getName, getId, filter]);
5843
5836
  const [isEditing, setIsEditing] = useState(false);
5844
5837
  const [enteringEditMode, pulseEnteringEditMode] = useImpulse();
5845
5838
  useEffect(() => {
@@ -5847,28 +5840,37 @@ function EntitySelector(props) {
5847
5840
  comboBoxRef.current?.focus();
5848
5841
  }
5849
5842
  }, [enteringEditMode]);
5850
- const handleEntitySelect = (key) => {
5851
- const entity = getEntities().find((e) => e.uniqueId.toString() === key);
5852
- onChange?.(entity ?? null);
5853
- setIsEditing(false);
5854
- };
5855
- // Get current entity key for display
5856
- const currentKey = value ? value.uniqueId.toString() : "";
5857
- if (value && !isEditing) {
5858
- // If there is a value and we are not editing, show the link view
5859
- 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 &&
5860
- (defaultValue !== undefined ? (jsx(Tooltip$1, { content: "Unlink", relationship: "label", children: jsx(Button, { icon: LinkDismissRegular, onClick: () => {
5861
- pulseEnteringEditMode(true);
5862
- onChange(defaultValue);
5863
- } }) })) : (jsx(Tooltip$1, { content: "Edit Link", relationship: "label", children: jsx(Button, { icon: LinkEditRegular, onClick: () => {
5864
- pulseEnteringEditMode(true);
5865
- setIsEditing(true);
5866
- } }) })))] }));
5867
- }
5868
- else {
5869
- // Otherwise, show the ComboBox for selection
5870
- return jsx(ComboBox, { ref: comboBoxRef, defaultOpen: enteringEditMode, label: "", options: options, value: currentKey, onChange: handleEntitySelect });
5871
- }
5843
+ if (value !== null && !isEditing) {
5844
+ 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: () => {
5845
+ pulseEnteringEditMode(true);
5846
+ onClear();
5847
+ } }) })) : onSelect ? (jsx(Tooltip$1, { content: "Edit Link", relationship: "label", children: jsx(Button, { icon: LinkEditRegular, ariaLabel: ariaLabel ? `Change ${ariaLabel}` : "Edit Link", disabled: disabled, onClick: () => {
5848
+ pulseEnteringEditMode(true);
5849
+ setIsEditing(true);
5850
+ } }) })) : undefined] }));
5851
+ }
5852
+ if (!onSelect && !showEmptySelector) {
5853
+ return jsx(Link, { value: "None" });
5854
+ }
5855
+ return (jsx(ComboBox, { ref: comboBoxRef, defaultOpen: enteringEditMode, label: "", ariaLabel: ariaLabel, disabled: disabled, options: options, value: value !== null ? getId(value) : "", onChange: (id) => {
5856
+ onSelect?.(getEntities().find((entity) => getId(entity) === id) ?? null);
5857
+ setIsEditing(false);
5858
+ } }));
5859
+ }
5860
+ ResourceSelector.displayName = "ResourceSelector";
5861
+
5862
+ const GetEntityId$2 = (entity) => entity.uniqueId.toString();
5863
+ /**
5864
+ * A generic primitive component with a ComboBox for selecting from a list of entities.
5865
+ * Supports entities with duplicate names by using uniqueId for identity.
5866
+ * @param props ChooseEntityProps
5867
+ * @returns EntitySelector component
5868
+ */
5869
+ function EntitySelector(props) {
5870
+ const { value, onLink, getEntities, getName, filter, defaultValue, disabled } = props;
5871
+ const onChange = props.onChange;
5872
+ const filterEntity = useCallback((entity) => entity.uniqueId !== undefined && (!filter || filter(entity)), [filter]);
5873
+ return (jsx(ResourceSelector, { value: value, getEntities: getEntities, getName: getName, getId: GetEntityId$2, filter: filterEntity, onSelect: onChange, onClear: onChange && defaultValue !== undefined ? () => onChange(defaultValue) : undefined, onLink: onLink, disabled: disabled, showEmptySelector: true }));
5872
5874
  }
5873
5875
  EntitySelector.displayName = "EntitySelector";
5874
5876
 
@@ -7428,10 +7430,19 @@ const ActionCommand = (props) => {
7428
7430
  };
7429
7431
  const ToggleCommand = (props) => {
7430
7432
  const { command } = props;
7433
+ const readCommandState = useCallback(() => [command.icon, command.isEnabled], [command]);
7431
7434
  // eslint-disable-next-line @typescript-eslint/naming-convention
7432
- const [Icon, isEnabled] = useObservableState(useCallback(() => [command.icon, command.isEnabled], [command]), command.onChange);
7435
+ const [[Icon, isEnabled], setCommandState] = useState(readCommandState);
7436
+ useEffect(() => {
7437
+ const observer = command.onChange?.add(() => setCommandState(readCommandState()));
7438
+ setCommandState(readCommandState());
7439
+ return () => observer?.remove();
7440
+ }, [command, readCommandState]);
7433
7441
  // TODO-iv2: Consolidate icon prop passing approach for inspector and shared components
7434
- return (jsx(ToggleButton, { appearance: "transparent", title: GetCommandDescription(command), titlePositioning: "after", checkedIcon: Icon, value: isEnabled, onChange: (val) => (command.isEnabled = val) }));
7442
+ return (jsx(ToggleButton, { appearance: "transparent", title: GetCommandDescription(command), titlePositioning: "after", checkedIcon: Icon, value: isEnabled, onChange: (value) => {
7443
+ command.isEnabled = value;
7444
+ setCommandState(readCommandState());
7445
+ } }));
7435
7446
  };
7436
7447
  // This "placeholder" command has a blank icon and is a no-op. It is used for aside
7437
7448
  // alignment when some toggle commands are enabled. See more details on the commands
@@ -10434,14 +10445,14 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
10434
10445
  description: "Adds a new panel for easy creation of various Babylon assets. This is a WIP extension...expect changes!",
10435
10446
  keywords: ["creation", "tools"],
10436
10447
  ...BabylonWebResources,
10437
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-owfaw163.js'),
10448
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-Cs46Amv7.js'),
10438
10449
  },
10439
10450
  {
10440
10451
  name: "Reflector",
10441
10452
  description: "Sends a serialized snapshot of the scene to the Babylon.js Sandbox through the Reflector Bridge. The snapshot is sent once and is read-only, so it does not stay in sync with this scene.",
10442
10453
  keywords: ["reflector", "bridge", "snapshot", "sync", "sandbox", "tools"],
10443
10454
  ...BabylonWebResources,
10444
- getExtensionModuleAsync: async () => await import('./reflectorService-oIAxxpQt.js'),
10455
+ getExtensionModuleAsync: async () => await import('./reflectorService-C43FgOiC.js'),
10445
10456
  },
10446
10457
  ]);
10447
10458
 
@@ -11380,7 +11391,7 @@ const ControlledColorPropertyLine = (props) => {
11380
11391
  }, currentValue));
11381
11392
  };
11382
11393
  const color = adapter.getColor(currentValue);
11383
- return (jsx(PropertyLine, { ref: propertyLineRef, ...props, expandedContent: jsx(ColorSliders, { color: color, onSliderChange: onSliderChange }), children: createElement(adapter.picker, {
11394
+ return (jsx(PropertyLine, { ref: propertyLineRef, ...props, expandedContent: jsx(ColorSliders, { color: color, onSliderChange: onSliderChange, disabled: props.disabled }), children: createElement(adapter.picker, {
11384
11395
  value: currentValue,
11385
11396
  onChange: handleChange,
11386
11397
  isLinearMode: props.isLinearMode,
@@ -11391,8 +11402,8 @@ const ControlledColorPropertyLine = (props) => {
11391
11402
  }) }));
11392
11403
  };
11393
11404
  const ColorSliders = (props) => {
11394
- const { color, onSliderChange } = props;
11395
- 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") }))] }));
11405
+ const { color, disabled, onSliderChange } = props;
11406
+ 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") }))] }));
11396
11407
  };
11397
11408
 
11398
11409
  const Picker = (props) => jsx(ColorPickerPopup, { ...props });
@@ -11441,11 +11452,11 @@ const ControlledTensorPropertyLine = (props) => {
11441
11452
  props.onChange(newValue);
11442
11453
  };
11443
11454
  const summary = adapter.components.map((component) => formatted(adapter.getComponent(props.value, component))).join(", ");
11444
- 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}]` }) }));
11455
+ 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}]` }) }));
11445
11456
  };
11446
11457
  const TensorSliders = (props) => {
11447
- const { value, adapter, min, max, unit, step, precision, converted, onChange } = props;
11448
- 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))) }));
11458
+ const { value, adapter, min, max, unit, step, precision, disabled, converted, onChange } = props;
11459
+ 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))) }));
11449
11460
  };
11450
11461
  const RadiansToDegreesConverter = {
11451
11462
  from: (value) => (value * 180) / Math.PI,
@@ -14436,6 +14447,39 @@ const TopBar = () => {
14436
14447
  return (jsxs("div", { className: styles.root, children: [jsx("div", { className: styles.title, children: state.title }), jsx(Divider, { vertical: true, className: styles.divider }), jsxs("div", { className: styles.inputGroup, children: [jsx("div", { className: styles.inputLabel, children: "Frame:" }), jsx(SpinButton, { className: styles.spinButton, value: keyFrameValue ?? 0, onChange: handleFrameChange, disabled: !hasActiveAnimations || !frameControlEnabled })] }), jsxs("div", { className: styles.inputGroup, children: [jsx("div", { className: styles.inputLabel, children: "Value:" }), jsx(SpinButton, { className: styles.spinButton, value: keyValue ?? 0, onChange: handleValueChange, disabled: !hasActiveAnimations || !valueControlEnabled, step: 0.1 })] }), jsx(Divider, { vertical: true, className: styles.divider }), jsxs("div", { className: styles.buttonGroup, children: [jsx(Button, { icon: AddRegular, appearance: "subtle", disabled: !hasActiveAnimations, onClick: () => observables.onCreateOrUpdateKeyPointRequired.notifyObservers(), title: "New key" }), jsx(Button, { icon: DeleteRegular, appearance: "subtle", disabled: !hasActiveKeyPoints, onClick: () => observables.onDeleteKeyActiveKeyPoints.notifyObservers(), title: "Delete key" }), jsx(Button, { icon: FullScreenMaximizeRegular, appearance: "subtle", onClick: () => observables.onFrameRequired.notifyObservers(), title: "Frame canvas" })] }), jsx(Divider, { vertical: true, className: styles.divider }), jsxs("div", { className: styles.buttonGroup, children: [jsx(Button, { icon: FlatTangentIcon, appearance: "subtle", disabled: !hasActiveKeyPoints, onClick: () => observables.onFlattenTangentRequired.notifyObservers(), title: "Flatten tangent" }), jsx(Button, { icon: LinearTangentIcon, appearance: "subtle", disabled: !hasActiveKeyPoints, onClick: () => observables.onLinearTangentRequired.notifyObservers(), title: "Linear tangent" }), jsx(Button, { icon: BreakTangentIcon, appearance: "subtle", disabled: !hasActiveKeyPoints, onClick: () => observables.onBreakTangentRequired.notifyObservers(), title: "Break tangent" }), jsx(Button, { icon: UnifyTangentIcon, appearance: "subtle", disabled: !hasActiveKeyPoints, onClick: () => observables.onUnifyTangentRequired.notifyObservers(), title: "Unify tangent" }), jsx(Button, { icon: StepTangentIcon, appearance: "subtle", disabled: !hasActiveKeyPoints, onClick: () => observables.onStepTangentRequired.notifyObservers(), title: "Step tangent" })] })] }));
14437
14448
  };
14438
14449
 
14450
+ /**
14451
+ * Gets the animations owned by the child animatables of a target.
14452
+ * @param target - The animatable whose children are read
14453
+ * @returns The animations of all child animatables, in order
14454
+ */
14455
+ function GetChildAnimations(target) {
14456
+ return target.getAnimatables?.().flatMap((animatable) => animatable.animations ?? []) ?? [];
14457
+ }
14458
+ /**
14459
+ * Gets the animations owned by a target followed by the animations of its child animatables.
14460
+ * @param target - The animatable to read
14461
+ * @returns The target's own animations, then its children's
14462
+ */
14463
+ function GetAnimationsWithChildren(target) {
14464
+ return (target.animations ?? []).concat(GetChildAnimations(target));
14465
+ }
14466
+ /**
14467
+ * Removes an animation from the animatable that owns it: the target itself or one of its child animatables.
14468
+ * @param target - The animatable shown in the editor
14469
+ * @param animation - The animation to remove
14470
+ * @returns true if an owner was found and the animation was removed
14471
+ */
14472
+ function RemoveAnimationFromOwner(target, animation) {
14473
+ const owners = [target, ...(target.getAnimatables?.() ?? [])];
14474
+ for (const owner of owners) {
14475
+ if (owner.animations?.includes(animation)) {
14476
+ owner.animations = owner.animations.filter((a) => a !== animation);
14477
+ return true;
14478
+ }
14479
+ }
14480
+ return false;
14481
+ }
14482
+
14439
14483
  /**
14440
14484
  * Color constants for the curve editor
14441
14485
  * These colors are used consistently across the curve editor UI for
@@ -14720,8 +14764,9 @@ const AnimationList = () => {
14720
14764
  // Re-render when animations are loaded or changed (e.g. animation deleted)
14721
14765
  // useCallback stabilizes the accessor to prevent infinite re-render loops
14722
14766
  useObservableState(useCallback(() => ({}), []), observables.onAnimationsLoaded, observables.onActiveAnimationChanged);
14723
- // Get animations from target if available (for dynamically added animations), otherwise from state
14724
- const animations = state.target?.animations ?? state.animations;
14767
+ // Read from the target if available (for dynamically added animations), including its child animatables'
14768
+ // animations (e.g. a skeleton's bones), so the list matches the animations pane. Otherwise use state.
14769
+ const animations = state.target && !state.useTargetAnimations ? GetAnimationsWithChildren(state.target) : state.animations;
14725
14770
  return (jsx("div", { className: styles.root, children: animations?.map((a, i) => {
14726
14771
  const animation = state.useTargetAnimations ? a.animation : a;
14727
14772
  return jsx(AnimationEntry, { animation: animation }, animation.uniqueId ?? i);
@@ -15368,27 +15413,33 @@ const SideBar = () => {
15368
15413
  observables.onAnimationsLoaded.remove(observer);
15369
15414
  };
15370
15415
  }, [observables]);
15371
- // Subscribe to delete animation request - use ref to access current state.target
15416
+ // Subscribe to delete animation request - use refs to access the current state.target and state.animations.
15417
+ // The animations pane passes a new aggregate array on every render, so the mount-time array goes stale.
15372
15418
  const targetRef = useRef(state.target);
15373
15419
  targetRef.current = state.target;
15420
+ const animationsRef = useRef(state.animations);
15421
+ animationsRef.current = state.animations;
15422
+ const useTargetAnimationsRef = useRef(state.useTargetAnimations);
15423
+ useTargetAnimationsRef.current = state.useTargetAnimations;
15374
15424
  useEffect(() => {
15375
15425
  const observer = observables.onDeleteAnimation.add((animation) => {
15376
15426
  // Remove from active animations
15377
15427
  actions.setActiveAnimations((prev) => prev.filter((a) => a !== animation));
15378
- // Update target if exists
15428
+ // Remove from whichever animatable owns it: the target or one of its child animatables (e.g. a bone of a skeleton)
15379
15429
  const target = targetRef.current;
15380
- if (target && target.animations) {
15381
- target.animations = target.animations.filter((a) => a !== animation);
15430
+ if (target) {
15431
+ RemoveAnimationFromOwner(target, animation);
15382
15432
  }
15383
15433
  // Also update state.animations if it's an array we can filter
15384
15434
  // This mutates the array in place since we can't setState on a prop
15385
- if (state.animations) {
15386
- const index = state.animations.findIndex((a) => {
15387
- const anim = state.useTargetAnimations ? a.animation : a;
15435
+ const animations = animationsRef.current;
15436
+ if (animations) {
15437
+ const index = animations.findIndex((a) => {
15438
+ const anim = useTargetAnimationsRef.current ? a.animation : a;
15388
15439
  return anim === animation;
15389
15440
  });
15390
15441
  if (index !== -1) {
15391
- state.animations.splice(index, 1);
15442
+ animations.splice(index, 1);
15392
15443
  }
15393
15444
  }
15394
15445
  observables.onAnimationsLoaded.notifyObservers();
@@ -17990,10 +18041,8 @@ const AnimationsProperties = (props) => {
17990
18041
  const { scene, entity } = props;
17991
18042
  // Track animations array changes via property interception
17992
18043
  const trackedAnimations = useProperty(entity, "animations");
17993
- const animations = trackedAnimations ?? [];
17994
18044
  const ranges = entity.getAnimationRanges?.()?.filter((range) => !!range) ?? [];
17995
- const childAnimatablesAnimations = entity.getAnimatables?.().flatMap((animatable) => animatable.animations ?? []) ?? [];
17996
- animations.concat(childAnimatablesAnimations);
18045
+ const animations = (trackedAnimations ?? []).concat(GetChildAnimations(entity));
17997
18046
  const lastFrom = useRef(0);
17998
18047
  const lastTo = useRef(0);
17999
18048
  const lastLoop = useRef(false);
@@ -20909,10 +20958,41 @@ const GaussianSplattingStreamDiagnostics = (props) => {
20909
20958
  const { stream } = props;
20910
20959
  return (jsxs(Fragment, { children: [jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Visible Splats", description: RenderedSplatCountDescription, target: stream, getValue: GetRenderedSplatCount }), jsx(ComputedProperty, { component: SplatBudgetPropertyLine, target: stream, getValue: GetResidentSplatBudget }), jsx(ComputedProperty, { component: Lod0SplatCountPropertyLine, target: stream, getValue: GetLod0SplatCount })] }));
20911
20960
  };
20961
+ const DebugModeOptions = [
20962
+ { label: "Normal", value: "normal" },
20963
+ { label: "Projected size", value: "size" },
20964
+ { label: "Overdraw", value: "overdraw" },
20965
+ ];
20966
+ /**
20967
+ * Displays Gaussian mesh diagnostics and per-mesh debug rendering controls.
20968
+ * @param props Selected mesh and Inspector-owned debug controller
20969
+ * @returns Gaussian splat property lines
20970
+ */
20912
20971
  const GaussianSplattingDisplayProperties = (props) => {
20913
- const { mesh } = props;
20972
+ const { mesh, debugController } = props;
20973
+ const [mode, setMode] = useState(() => debugController.getMode(mesh));
20974
+ useEffect(() => {
20975
+ const observer = debugController.onModeChangedObservable.add((changedMesh) => {
20976
+ if (changedMesh === mesh) {
20977
+ setMode(debugController.getMode(mesh));
20978
+ }
20979
+ });
20980
+ setMode(debugController.getMode(mesh));
20981
+ return () => {
20982
+ debugController.onModeChangedObservable.remove(observer);
20983
+ };
20984
+ }, [mesh, debugController]);
20985
+ const plugins = debugController.getPlugins(mesh);
20914
20986
  const stream = mesh.getClassName() === "GaussianSplattingStream" ? mesh : null;
20915
- return (jsxs(Fragment, { children: [jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Splat Count", description: SplatCountDescription, target: mesh, getValue: GetSplatCount }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "SH Degree", options: ShDegreeOptions, target: mesh, propertyKey: "shDegree" }), jsx(StringifiedPropertyLine, { label: "Max SH Degree", value: mesh.maxShDegree }), jsx(BooleanBadgePropertyLine, { label: "Has Compensation", value: mesh.compensation }), jsx(StringifiedPropertyLine, { label: "Kernel Size", value: mesh.kernelSize }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Min Pixel Size", description: "Discard splats projected smaller than this many pixels. 0 = disabled.", target: mesh, propertyKey: "minPixelSize", min: 0, max: 20, step: 0.5 }), stream && (jsxs(Fragment, { children: [jsx(GaussianSplattingStreamDiagnostics, { stream: stream }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Max Detail LOD", description: "Finest LOD level any node may render. 0 = full detail; higher values force a coarser maximum detail.", target: stream, propertyKey: "maxDetailLod", min: 0, max: stream.maxLodLevel, step: 1 })] }))] }));
20987
+ return (jsxs(Fragment, { children: [jsx(StringDropdownPropertyLine, { label: "Debug Rendering", description: debugController.canDebug(mesh)
20988
+ ? "Per-mesh visualization; Normal restores the original material unless it was disposed."
20989
+ : "Unavailable: this mesh uses a non-Gaussian material.", options: DebugModeOptions, value: mode, disabled: !debugController.canDebug(mesh), onChange: (value) => {
20990
+ debugController.setMode(mesh, value);
20991
+ setMode(debugController.getMode(mesh));
20992
+ } }), jsx(Collapse, { visible: mode === "size" && !!plugins, children: plugins && (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Size Scale (pixels)", target: plugins.size, propertyKey: "sizeScale", min: 0.5, max: 128, step: 0.5 })) }), jsx(Collapse, { visible: mode === "overdraw" && !!plugins, children: plugins && (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Overdraw Intensity", target: plugins.overdraw, propertyKey: "intensity", min: 0.001, max: 0.25, step: 0.001 })) }), jsx(ComputedProperty, { component: StringifiedPropertyLine, label: "Splat Count", description: SplatCountDescription, target: mesh, getValue: GetSplatCount }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "SH Degree", options: ShDegreeOptions, target: mesh, propertyKey: "shDegree" }), jsx(StringifiedPropertyLine, { label: "Max SH Degree", value: mesh.maxShDegree }), jsx(BooleanBadgePropertyLine, { label: "Has Compensation", value: mesh.compensation }), jsx(StringifiedPropertyLine, { label: "Kernel Size", value: mesh.kernelSize }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Min Pixel Size", description: "Discard splats projected smaller than this many pixels. 0 = disabled.", target: mesh, propertyKey: "minPixelSize", convertFrom: (value) => {
20993
+ debugController.setMinPixelSize(mesh, value);
20994
+ return value;
20995
+ }, min: 0, max: 20, step: 0.5 }), stream && (jsxs(Fragment, { children: [jsx(GaussianSplattingStreamDiagnostics, { stream: stream }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Max Detail LOD", description: "Finest LOD level any node may render. 0 = full detail; higher values force a coarser maximum detail.", target: stream, propertyKey: "maxDetailLod", min: 0, max: stream.maxLodLevel, step: 1 })] }))] }));
20916
20996
  };
20917
20997
 
20918
20998
  function GetNodeGeometry(mesh) {
@@ -20962,10 +21042,165 @@ const NodeGeneralProperties = (props) => {
20962
21042
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NodeSelectorPropertyLine, label: "Parent", target: node, propertyKey: "parent", scene: node.getScene(), defaultValue: null, onLink: (parentNode) => (selectionService.selectedEntity = parentNode) }), jsx(Property, { component: SwitchPropertyLine, label: "Is Enabled", description: "Whether the node is enabled or not.", value: isEnabled, onChange: (checked) => node.setEnabled(checked), functionPath: "setEnabled" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Inherit Visibility", description: "Whether the node inherits visibility from its parent.", target: node, propertyKey: "inheritVisibility" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Is Visible", description: "Whether the node is visible or not.", target: node, propertyKey: "isVisible" })] }));
20963
21043
  };
20964
21044
 
21045
+ /** Owns Inspector-only per-mesh visualization materials for the lifetime of the properties service. */
21046
+ class GaussianSplattingDebugController {
21047
+ constructor() {
21048
+ this._entries = new Map();
21049
+ /** Notifies controls when a mesh's debug mode changes, including external material replacement. */
21050
+ this.onModeChangedObservable = new Observable();
21051
+ }
21052
+ /**
21053
+ * Gets the active per-mesh mode, releasing a material replaced by the application.
21054
+ * @param mesh Selected Gaussian mesh
21055
+ * @returns Active mode
21056
+ */
21057
+ getMode(mesh) {
21058
+ const entry = this._entries.get(mesh);
21059
+ if (entry && mesh.material !== entry.temporary) {
21060
+ this._release(mesh, false);
21061
+ return "normal";
21062
+ }
21063
+ return entry?.mode ?? "normal";
21064
+ }
21065
+ /**
21066
+ * Gets the two plugins on an Inspector-owned material.
21067
+ * @param mesh Selected Gaussian mesh
21068
+ * @returns Plugins when the mesh is being debugged
21069
+ */
21070
+ getPlugins(mesh) {
21071
+ return this._entries.get(mesh);
21072
+ }
21073
+ /**
21074
+ * @param mesh Selected Gaussian mesh
21075
+ * @returns Whether its current material supports debugging
21076
+ */
21077
+ canDebug(mesh) {
21078
+ return this._entries.has(mesh) || mesh.material instanceof GaussianSplattingMaterial;
21079
+ }
21080
+ /**
21081
+ * Keeps Inspector edits to minimum pixel size on the source material while a visualization is active.
21082
+ * @param mesh Selected Gaussian mesh
21083
+ * @param value Minimum projected pixel size
21084
+ */
21085
+ setMinPixelSize(mesh, value) {
21086
+ const entry = this._entries.get(mesh);
21087
+ if (entry && !entry.originalDisposed && entry.original instanceof GaussianSplattingMaterial) {
21088
+ entry.original.minPixelSize = Math.max(0, value);
21089
+ }
21090
+ }
21091
+ /**
21092
+ * Selects the rendering mode without changing the application's source material.
21093
+ * @param mesh Gaussian mesh to visualize
21094
+ * @param mode Rendering mode
21095
+ */
21096
+ setMode(mesh, mode) {
21097
+ const current = this.getMode(mesh);
21098
+ if (mode === "normal") {
21099
+ if (current !== "normal") {
21100
+ this._release(mesh, true);
21101
+ }
21102
+ return;
21103
+ }
21104
+ let entry = this._entries.get(mesh);
21105
+ if (!entry) {
21106
+ const original = mesh.material;
21107
+ if (!(original instanceof GaussianSplattingMaterial) || mesh.isDisposed()) {
21108
+ return;
21109
+ }
21110
+ const scene = mesh.getScene();
21111
+ const temporary = new GaussianSplattingMaterial(`${mesh.name}_inspectorDebug`, scene);
21112
+ const shadow = temporary.shadowDepthWrapper;
21113
+ temporary.doNotSerialize = true;
21114
+ temporary.reservedDataStore = { hidden: true };
21115
+ shadow.baseMaterial.reservedDataStore = { hidden: true };
21116
+ temporary.setSourceMesh(mesh);
21117
+ temporary.kernelSize = original.kernelSize;
21118
+ temporary.minPixelSize = original.minPixelSize;
21119
+ temporary.compensation = original.compensation;
21120
+ temporary.alpha = original.alpha;
21121
+ temporary.alphaMode = original.alphaMode;
21122
+ temporary.depthFunction = original.depthFunction;
21123
+ temporary.disableDepthWrite = original.disableDepthWrite;
21124
+ temporary.forceDepthWrite = original.forceDepthWrite;
21125
+ temporary.clipPlane = original.clipPlane;
21126
+ temporary.clipPlane2 = original.clipPlane2;
21127
+ temporary.clipPlane3 = original.clipPlane3;
21128
+ temporary.clipPlane4 = original.clipPlane4;
21129
+ temporary.clipPlane5 = original.clipPlane5;
21130
+ temporary.clipPlane6 = original.clipPlane6;
21131
+ temporary.useLogarithmicDepth = original.useLogarithmicDepth;
21132
+ const size = new GaussianSplattingSizeMaterialPlugin(temporary);
21133
+ const overdraw = new GaussianSplattingOverdrawMaterialPlugin(temporary);
21134
+ size.isEnabled = false;
21135
+ overdraw.isEnabled = false;
21136
+ entry = {
21137
+ original,
21138
+ originalDisposed: false,
21139
+ originalObserver: original.onDisposeObservable.add(() => {
21140
+ const current = this._entries.get(mesh);
21141
+ if (current) {
21142
+ current.originalDisposed = true;
21143
+ }
21144
+ }),
21145
+ temporary,
21146
+ shadow,
21147
+ size,
21148
+ overdraw,
21149
+ mode: "normal",
21150
+ meshObserver: mesh.onDisposeObservable.add(() => this._release(mesh, false)),
21151
+ sceneObserver: scene.onDisposeObservable.add(() => this._release(mesh, false)),
21152
+ materialObserver: mesh.onMaterialChangedObservable.add(() => {
21153
+ if (mesh.material !== temporary) {
21154
+ this._release(mesh, false);
21155
+ }
21156
+ }),
21157
+ };
21158
+ this._entries.set(mesh, entry);
21159
+ mesh.material = temporary;
21160
+ if (this._entries.get(mesh) !== entry) {
21161
+ return;
21162
+ }
21163
+ }
21164
+ entry.size.isEnabled = mode === "size";
21165
+ entry.overdraw.isEnabled = mode === "overdraw";
21166
+ entry.mode = mode;
21167
+ if (current !== mode) {
21168
+ this.onModeChangedObservable.notifyObservers(mesh);
21169
+ }
21170
+ }
21171
+ _release(mesh, restore) {
21172
+ const entry = this._entries.get(mesh);
21173
+ if (!entry) {
21174
+ return;
21175
+ }
21176
+ this._entries.delete(mesh);
21177
+ mesh.onDisposeObservable.remove(entry.meshObserver);
21178
+ entry.original.onDisposeObservable.remove(entry.originalObserver);
21179
+ const scene = mesh.getScene();
21180
+ scene.onDisposeObservable.remove(entry.sceneObserver);
21181
+ mesh.onMaterialChangedObservable.remove(entry.materialObserver);
21182
+ if (restore && !mesh.isDisposed() && mesh.material === entry.temporary) {
21183
+ mesh.material = entry.originalDisposed ? null : entry.original;
21184
+ }
21185
+ entry.shadow.dispose();
21186
+ entry.shadow.baseMaterial.dispose(false, false);
21187
+ entry.temporary.dispose(false, false);
21188
+ this.onModeChangedObservable.notifyObservers(mesh);
21189
+ }
21190
+ /** Releases Inspector-owned materials, restoring a source material only if it remains alive. */
21191
+ dispose() {
21192
+ for (const mesh of this._entries.keys()) {
21193
+ this._release(mesh, true);
21194
+ }
21195
+ this.onModeChangedObservable.clear();
21196
+ }
21197
+ }
21198
+
20965
21199
  const NodePropertiesServiceDefinition = {
20966
21200
  friendlyName: "Mesh Properties",
20967
21201
  consumes: [PropertiesServiceIdentity, SelectionServiceIdentity],
20968
21202
  factory: (propertiesService, selectionService) => {
21203
+ const gaussianDebugController = new GaussianSplattingDebugController();
20969
21204
  const nodeContentRegistration = propertiesService.addSectionContent({
20970
21205
  key: "Node Properties",
20971
21206
  predicate: (entity) => entity instanceof Node$1,
@@ -21035,12 +21270,13 @@ const NodePropertiesServiceDefinition = {
21035
21270
  content: [
21036
21271
  {
21037
21272
  section: "Gaussian Splatting",
21038
- component: ({ context }) => jsx(GaussianSplattingDisplayProperties, { mesh: context }),
21273
+ component: ({ context }) => jsx(GaussianSplattingDisplayProperties, { mesh: context, debugController: gaussianDebugController }),
21039
21274
  },
21040
21275
  ],
21041
21276
  });
21042
21277
  return {
21043
21278
  dispose: () => {
21279
+ gaussianDebugController.dispose();
21044
21280
  nodeContentRegistration.dispose();
21045
21281
  abstractMeshContentRegistration.dispose();
21046
21282
  meshContentRegistration.dispose();
@@ -27199,7 +27435,7 @@ const EquirectangularCaptureTool = ({ scene }) => {
27199
27435
  };
27200
27436
 
27201
27437
  const GIFCaptureTool = MakeLazyComponent(async () => {
27202
- const gif = (await import('./gif-5DieAkSF.js').then(function (n) { return n.g; })).default;
27438
+ const gif = (await import('./gif-ut7hgn9U.js').then(function (n) { return n.g; })).default;
27203
27439
  // TODO: Figure out how to grab this from NPM package instead of CDN
27204
27440
  const workerContent = await Tools.LoadFileAsync("https://cdn.jsdelivr.net/gh/terikon/gif.js.optimized@0.1.6/dist/gif.worker.js");
27205
27441
  const workerBlob = new Blob([workerContent], { type: "application/javascript" });
@@ -28774,4 +29010,4 @@ function DetachDebugLayer() {
28774
29010
  AttachDebugLayer();
28775
29011
 
28776
29012
  export { FactorGradientComponent as $, Accordion as A, Button as B, CheckboxPropertyLine as C, Color4PropertyLine as D, ColorPickerPopup as E, ColorStepGradientComponent as F, ComboBox as G, ComboBoxPropertyLine as H, ComputedProperty as I, ConstructorFactory as J, ConvertOptions as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, DebugServiceIdentity as O, Popover as P, DerivedProperty as Q, DetachDebugLayer as R, ShellServiceIdentity as S, TextInputPropertyLine as T, DraggableLine as U, Vector3PropertyLine as V, Dropdown as W, EntitySelector as X, ErrorBoundary as Y, ExplorerServiceIdentity as Z, ExtensibleAccordion as _, useToast as a, TextureSelector as a$, FactorGradientList as a0, FileUploadLine as a1, GetPropertyDescriptor as a2, GizmoServiceIdentity as a3, HexPropertyLine as a4, InfoLabel as a5, InputHexField as a6, InputHsvField as a7, Inspector as a8, InterceptFunction as a9, RotationVectorPropertyLine as aA, SceneExplorerServiceIdentity as aB, SearchBar as aC, SearchBox as aD, SelectionServiceDefinition as aE, SettingsServiceIdentity as aF, SettingsStore as aG, SettingsStoreIdentity as aH, ShowInspector as aI, SidePaneContainer as aJ, SkeletonSelector as aK, Slider as aL, SpinButton as aM, StartInspectable as aN, StatsServiceIdentity as aO, StringDropdown as aP, StringDropdownPropertyLine as aQ, StringifiedPropertyLine as aR, Switch as aS, SwitchPropertyLine as aT, SyncedSliderInput as aU, SyncedSliderPropertyLine as aV, TeachingMoment as aW, TextAreaPropertyLine as aX, TextInput as aY, TextPropertyLine as aZ, Textarea as a_, InterceptProperty as aa, IsPropertyReadonly as ab, LineContainer as ac, LinkPropertyLine as ad, LinkToEntityPropertyLine as ae, List as af, MakeDialogTeachingMoment as ag, MakeLazyComponent as ah, MakeModularBridge as ai, MakeModularTool as aj, MakePopoverTeachingMoment as ak, MakePropertyHook as al, MakeTeachingMoment as am, MaterialSelector as an, NodeSelector as ao, NumberDropdown as ap, NumberDropdownPropertyLine as aq, ObservableCollection as ar, Pane as as, PlaceholderPropertyLine as at, PositionedPopover as au, PropertiesServiceIdentity as av, Property as aw, PropertyContext as ax, PropertyLine as ay, QuaternionPropertyLine as az, useInterceptObservable as b, TextureUpload as b0, Theme as b1, ThemeServiceIdentity as b2, ToastProvider as b3, ToggleButton as b4, Tooltip as b5, UploadButton as b6, Vector2PropertyLine as b7, Vector4PropertyLine as b8, WatcherServiceIdentity as b9, inspectorAssetNotFoundHandler as ba, useAngleConverters as bb, useAsyncResource as bc, useColor3Property as bd, useColor4Property as be, useEventListener as bf, useEventfulState as bg, useKeyListener as bh, useKeyState as bi, useObservableCollection as bj, useOrderedObservableCollection as bk, usePollingObservable as bl, usePropertyChangedNotifier as bm, useQuaternionProperty as bn, useResource as bo, useTheme as bp, useThemeMode as bq, useVector3Property as br, LinkToEntity as c, SpinButtonPropertyLine as d, useProperty as e, SceneContextIdentity as f, SelectionServiceIdentity as g, useObservableState as h, AccordionSection as i, ButtonLine as j, ToolsServiceIdentity as k, AccordionSectionItem as l, AttachDebugLayer as m, BooleanBadgePropertyLine as n, BoundProperty as o, BridgeCommandRegistryIdentity as p, BuiltInsExtensionFeed as q, Checkbox as r, ChildWindow as s, Collapse as t, useExtensionManager as u, Color3GradientComponent as v, Color3GradientList as w, Color3PropertyLine as x, Color4GradientComponent as y, Color4GradientList as z };
28777
- //# sourceMappingURL=index-CZ1-Y-Rv.js.map
29013
+ //# sourceMappingURL=index-DbbhJJ1G.js.map