@babylonjs/inspector 9.1.0 → 9.2.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 (31) hide show
  1. package/bin/inspector-bridge.mjs +4734 -0
  2. package/bin/inspector-cli.mjs +4931 -0
  3. package/lib/cli/protocol.d.ts +180 -0
  4. package/lib/components/properties/materials/openpbrMaterialProperties.d.ts +3 -0
  5. package/lib/{extensionsListService-BmiNjZiw.js → extensionsListService-eRZtqcfj.js} +3 -3
  6. package/lib/{extensionsListService-BmiNjZiw.js.map → extensionsListService-eRZtqcfj.js.map} +1 -1
  7. package/lib/{index-BCbXjPTn.js → index-FWuITINA.js} +1134 -266
  8. package/lib/index-FWuITINA.js.map +1 -0
  9. package/lib/index.d.ts +3 -0
  10. package/lib/index.js +2 -2
  11. package/lib/inspectable.d.ts +67 -0
  12. package/lib/misc/defaultPerfStrategies.d.ts +16 -0
  13. package/lib/modularTool.d.ts +6 -1
  14. package/lib/modularity/serviceContainer.d.ts +24 -1
  15. package/lib/{quickCreateToolsService-BoqrJqoM.js → quickCreateToolsService-MzZbVrvr.js} +3 -3
  16. package/lib/{quickCreateToolsService-BoqrJqoM.js.map → quickCreateToolsService-MzZbVrvr.js.map} +1 -1
  17. package/lib/{reflectorService-CFTva2GG.js → reflectorService-DdPEZLjO.js} +3 -3
  18. package/lib/{reflectorService-CFTva2GG.js.map → reflectorService-DdPEZLjO.js.map} +1 -1
  19. package/lib/services/cli/cliConnectionStatus.d.ts +19 -0
  20. package/lib/services/cli/entityQueryService.d.ts +8 -0
  21. package/lib/services/cli/inspectableBridgeService.d.ts +22 -0
  22. package/lib/services/cli/inspectableCommandRegistry.d.ts +58 -0
  23. package/lib/services/cli/perfTraceCommandService.d.ts +9 -0
  24. package/lib/services/cli/screenshotCommandService.d.ts +8 -0
  25. package/lib/services/cli/shaderCommandService.d.ts +7 -0
  26. package/lib/services/cli/statsCommandService.d.ts +9 -0
  27. package/lib/services/cliConnectionStatusService.d.ts +4 -0
  28. package/lib/services/defaultToolbarMetadata.d.ts +2 -1
  29. package/package.json +11 -4
  30. package/readme.md +27 -0
  31. package/lib/index-BCbXjPTn.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { createContext, forwardRef, useContext, useState, useCallback, Component, useMemo, useEffect, useRef, useReducer, Children, isValidElement, useLayoutEffect, cloneElement, useImperativeHandle, createElement, Suspense, memo, Fragment as Fragment$1, lazy } from 'react';
2
+ import { createContext, forwardRef, useContext, useState, useCallback, Component, useMemo, useEffect, useRef, useReducer, Children, isValidElement, useLayoutEffect, useImperativeHandle, cloneElement, createElement, Suspense, memo, Fragment as Fragment$1, lazy } from 'react';
3
3
  import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, Link as Link$1, Caption1, Body1, useFluent, Accordion as Accordion$1, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, MessageBar as MessageBar$1, MessageBarBody, AccordionItem, SearchBox as SearchBox$1, Portal, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, Body1Strong, mergeClasses, useId, useToastController, Toast, ToastTitle, FluentProvider, Toaster, Checkbox as Checkbox$1, createLightTheme, createDarkTheme, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, createDOMRenderer, RendererProvider, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, Switch as Switch$1, treeItemLevelToken, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, MenuItemCheckbox, useMergedRefs, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, PresenceBadge, Slider as Slider$1, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBarTitle, useComboboxFilter, Combobox, Subtitle2, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, Field } from '@fluentui/react-components';
4
- import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, DocumentTextRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowBidirectionalUpDownFilled, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, CameraRegular, AddRegular, DeleteRegular, FullScreenMaximizeRegular, ChevronDownRegular, ChevronRightRegular, CircleSmallFilled, SaveRegular, PreviousRegular, ArrowPreviousRegular, TriangleLeftRegular, RecordStopRegular, PlayRegular, ArrowNextRegular, NextRegular, PauseRegular, LinkDismissRegular, LinkEditRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, EyeRegular, CloudArrowUpRegular, CloudArrowDownRegular, EyeOffFilled, ArrowMoveFilled, StopFilled, PlayFilled, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, BubbleMultipleRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
4
+ import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, DocumentTextRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowBidirectionalUpDownFilled, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, PlugConnectedRegular, PlugDisconnectedRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, CameraRegular, AddRegular, DeleteRegular, FullScreenMaximizeRegular, ChevronDownRegular, ChevronRightRegular, CircleSmallFilled, SaveRegular, PreviousRegular, ArrowPreviousRegular, TriangleLeftRegular, RecordStopRegular, PlayRegular, ArrowNextRegular, NextRegular, PauseRegular, LinkDismissRegular, LinkEditRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, EyeRegular, CloudArrowUpRegular, CloudArrowDownRegular, EyeOffFilled, ArrowMoveFilled, StopFilled, PlayFilled, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, BubbleMultipleRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
5
5
  import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
6
6
  import { Vector3, Quaternion, Matrix, Vector2, Vector4, TmpVectors } from '@babylonjs/core/Maths/math.vector.js';
7
7
  import { Observable } from '@babylonjs/core/Misc/observable.js';
@@ -45,6 +45,7 @@ import { Light } from '@babylonjs/core/Lights/light.js';
45
45
  import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh.js';
46
46
  import { Node as Node$1 } from '@babylonjs/core/node.js';
47
47
  import { createRoot } from 'react-dom/client';
48
+ import { CreateScreenshotUsingRenderTargetAsync } from '@babylonjs/core/Misc/screenshotTools.js';
48
49
  import { SelectionOutlineLayer } from '@babylonjs/core/Layers/selectionOutlineLayer.js';
49
50
  import { GaussianSplattingMesh } from '@babylonjs/core/Meshes/GaussianSplatting/gaussianSplattingMesh.js';
50
51
  import { AnimationGroup, TargetedAnimation } from '@babylonjs/core/Animations/animationGroup.js';
@@ -138,7 +139,6 @@ import '@babylonjs/core/Sprites/spriteSceneComponent.js';
138
139
  import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture.js';
139
140
  import { captureEquirectangularFromScene } from '@babylonjs/core/Misc/equirectangularCapture.js';
140
141
  import { SceneRecorder } from '@babylonjs/core/Misc/sceneRecorder.js';
141
- import { CreateScreenshotUsingRenderTargetAsync } from '@babylonjs/core/Misc/screenshotTools.js';
142
142
  import { VideoRecorder } from '@babylonjs/core/Misc/videoRecorder.js';
143
143
  import { SceneSerializer } from '@babylonjs/core/Misc/sceneSerializer.js';
144
144
  import { EnvironmentTextureTools } from '@babylonjs/core/Misc/environmentTextureTools.js';
@@ -1743,13 +1743,18 @@ const InfoLabel = (props) => {
1743
1743
  };
1744
1744
 
1745
1745
  const ToastContext = createContext({ showToast: () => { } });
1746
- const ToastProvider = ({ children }) => {
1746
+ /**
1747
+ * Provides toast notification functionality to child components via context and an optional imperative ref.
1748
+ * @returns The toast provider component tree.
1749
+ */
1750
+ const ToastProvider = ({ children, imperativeRef }) => {
1747
1751
  const toasterId = useId("toaster");
1748
1752
  const { dispatchToast } = useToastController(toasterId);
1749
1753
  const { targetDocument } = useFluent();
1750
- const showToast = useCallback((message) => {
1751
- dispatchToast(jsx(Toast, { children: jsx(ToastTitle, { children: message }) }), { intent: "info", timeout: 2000 });
1754
+ const showToast = useCallback((message, options) => {
1755
+ dispatchToast(jsx(Toast, { children: jsx(ToastTitle, { children: message }) }), { intent: options?.intent ?? "info", timeout: 2000 });
1752
1756
  }, [dispatchToast]);
1757
+ useImperativeHandle(imperativeRef, () => ({ showToast }), [showToast]);
1753
1758
  return (jsxs(ToastContext.Provider, { value: { showToast }, children: [children, jsx(FluentProvider, { applyStylesToPortals: true, targetDocument: targetDocument, children: jsx(Toaster, { toasterId: toasterId, position: "bottom" }) })] }));
1754
1759
  };
1755
1760
  /**
@@ -4147,14 +4152,14 @@ class GraphUtils {
4147
4152
  }
4148
4153
  }
4149
4154
 
4150
- const SyntheticUniqueIds = new WeakMap();
4151
- function GetEntityId(entity) {
4155
+ const SyntheticUniqueIds$1 = new WeakMap();
4156
+ function GetEntityId$1(entity) {
4152
4157
  if ("uniqueId" in entity && typeof entity.uniqueId === "number") {
4153
4158
  return entity.uniqueId;
4154
4159
  }
4155
- let id = SyntheticUniqueIds.get(entity);
4160
+ let id = SyntheticUniqueIds$1.get(entity);
4156
4161
  if (!id) {
4157
- SyntheticUniqueIds.set(entity, (id = UniqueIdGenerator.UniqueId));
4162
+ SyntheticUniqueIds$1.set(entity, (id = UniqueIdGenerator.UniqueId));
4158
4163
  }
4159
4164
  return id;
4160
4165
  }
@@ -4174,7 +4179,7 @@ function GetEntitySection(entityItem) {
4174
4179
  }
4175
4180
  function ExpandOrCollapseAll(treeItem, open, openItems) {
4176
4181
  const addOrRemove = open ? openItems.add.bind(openItems) : openItems.delete.bind(openItems);
4177
- TraverseGraph([treeItem], (treeItem) => treeItem.children, (treeItem) => addOrRemove(treeItem.type === "entity" ? GetEntityId(treeItem.entity) : treeItem.sectionName));
4182
+ TraverseGraph([treeItem], (treeItem) => treeItem.children, (treeItem) => addOrRemove(treeItem.type === "entity" ? GetEntityId$1(treeItem.entity) : treeItem.sectionName));
4178
4183
  }
4179
4184
  function GetCommandHotKeyDescription(command) {
4180
4185
  if (!command.hotKey) {
@@ -4470,16 +4475,16 @@ const EntityTreeItem = (props) => {
4470
4475
  }
4471
4476
  }
4472
4477
  }, [commands]);
4473
- return (jsxs(Menu, { openOnContext: true, checkedValues: checkedContextMenuItems, onCheckedValueChange: onContextMenuCheckedValueChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: jsx(FlatTreeItem, { className: mergeClasses(classes.treeItem, isDragging && classes.treeItemDragging, isDropTarget && classes.treeItemDropTarget), value: GetEntityId(entityItem.entity),
4478
+ return (jsxs(Menu, { openOnContext: true, checkedValues: checkedContextMenuItems, onCheckedValueChange: onContextMenuCheckedValueChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: jsx(FlatTreeItem, { className: mergeClasses(classes.treeItem, isDragging && classes.treeItemDragging, isDropTarget && classes.treeItemDropTarget), value: GetEntityId$1(entityItem.entity),
4474
4479
  // Disable manual expand/collapse when a filter is active.
4475
- itemType: !isFiltering && hasChildren ? "branch" : "leaf", parentValue: entityItem.parent.type === "section" ? entityItem.parent.sectionName : GetEntityId(entityItem.parent.entity), "aria-level": entityItem.depth, "aria-setsize": 1, "aria-posinset": 1, onClick: select, onKeyDown: onKeyDown, style: { [treeItemLevelToken]: entityItem.depth }, ...dragProps, children: jsx(TreeItemLayout, { iconBefore: entityItem.icon ? jsx(entityItem.icon, { entity: entityItem.entity }) : null, className: mergeClasses(hasChildren ? classes.treeItemLayoutBranch : classes.treeItemLayoutLeaf, compactMode ? classes.treeItemLayoutCompact : undefined, isDropTarget && classes.treeItemDropTarget), style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, actions: actions, aside: {
4480
+ itemType: !isFiltering && hasChildren ? "branch" : "leaf", parentValue: entityItem.parent.type === "section" ? entityItem.parent.sectionName : GetEntityId$1(entityItem.parent.entity), "aria-level": entityItem.depth, "aria-setsize": 1, "aria-posinset": 1, onClick: select, onKeyDown: onKeyDown, style: { [treeItemLevelToken]: entityItem.depth }, ...dragProps, children: jsx(TreeItemLayout, { iconBefore: entityItem.icon ? jsx(entityItem.icon, { entity: entityItem.entity }) : null, className: mergeClasses(hasChildren ? classes.treeItemLayoutBranch : classes.treeItemLayoutLeaf, compactMode ? classes.treeItemLayoutCompact : undefined, isDropTarget && classes.treeItemDropTarget), style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, actions: actions, aside: {
4476
4481
  // Match the gap and padding of the actions.
4477
4482
  className: classes.treeItemLayoutAside,
4478
4483
  children: aside,
4479
4484
  }, main: {
4480
4485
  // Prevent the "main" content (the Body1 below) from growing too large and pushing the actions/aside out of view.
4481
4486
  className: classes.treeItemLayoutMain,
4482
- }, children: jsx(Tooltip$1, { content: name, relationship: "description", children: jsx(Body1, { wrap: false, truncate: true, children: name }) }) }) }, GetEntityId(entityItem.entity)) }), jsx(MenuPopover, { hidden: !hasChildren && contextMenuCommands.length === 0, children: jsxs(MenuList, { children: [hasChildren && (jsxs(Fragment, { children: [jsx(MenuItem, { icon: jsx(ArrowExpandAllRegular, {}), onClick: expandAll, children: jsx(Body1, { children: "Expand All" }) }), jsx(MenuItem, { icon: jsx(ArrowCollapseAllRegular, {}), onClick: collapseAll, children: jsx(Body1, { children: "Collapse All" }) })] })), hasChildren && contextMenuCommands.length > 0 && jsx(MenuDivider, {}), contextMenuItems] }) })] }));
4487
+ }, children: jsx(Tooltip$1, { content: name, relationship: "description", children: jsx(Body1, { wrap: false, truncate: true, children: name }) }) }) }, GetEntityId$1(entityItem.entity)) }), jsx(MenuPopover, { hidden: !hasChildren && contextMenuCommands.length === 0, children: jsxs(MenuList, { children: [hasChildren && (jsxs(Fragment, { children: [jsx(MenuItem, { icon: jsx(ArrowExpandAllRegular, {}), onClick: expandAll, children: jsx(Body1, { children: "Expand All" }) }), jsx(MenuItem, { icon: jsx(ArrowCollapseAllRegular, {}), onClick: collapseAll, children: jsx(Body1, { children: "Collapse All" }) })] })), hasChildren && contextMenuCommands.length > 0 && jsx(MenuDivider, {}), contextMenuItems] }) })] }));
4483
4488
  };
4484
4489
  const SceneExplorer = (props) => {
4485
4490
  const classes = useStyles$Q();
@@ -4502,7 +4507,7 @@ const SceneExplorer = (props) => {
4502
4507
  if (targetEntity) {
4503
4508
  setOpenItems((prev) => {
4504
4509
  const next = new Set(prev);
4505
- next.add(GetEntityId(targetEntity));
4510
+ next.add(GetEntityId$1(targetEntity));
4506
4511
  return next;
4507
4512
  });
4508
4513
  }
@@ -4519,7 +4524,7 @@ const SceneExplorer = (props) => {
4519
4524
  };
4520
4525
  const onSceneItemRemoved = (item) => {
4521
4526
  setSceneVersion((version) => version + 1);
4522
- if (openItems.delete(GetEntityId(item))) {
4527
+ if (openItems.delete(GetEntityId$1(item))) {
4523
4528
  setOpenItems(new Set(openItems));
4524
4529
  }
4525
4530
  if (item === selectedEntity) {
@@ -4575,7 +4580,7 @@ const SceneExplorer = (props) => {
4575
4580
  parent.children = [];
4576
4581
  }
4577
4582
  parent.children.push(treeItemData);
4578
- allTreeItems.set(GetEntityId(entity), treeItemData);
4583
+ allTreeItems.set(GetEntityId$1(entity), treeItemData);
4579
4584
  return treeItemData;
4580
4585
  };
4581
4586
  const rootEntityTreeItems = rootEntities.map((entity) => createEntityTreeItemData(entity, sectionTreeItem));
@@ -4623,7 +4628,7 @@ const SceneExplorer = (props) => {
4623
4628
  TraverseGraph(children,
4624
4629
  // Get children
4625
4630
  (treeItem) => {
4626
- if (filter || openItems.has(GetEntityId(treeItem.entity))) {
4631
+ if (filter || openItems.has(GetEntityId$1(treeItem.entity))) {
4627
4632
  if (!treeItem.children) {
4628
4633
  return null;
4629
4634
  }
@@ -4671,8 +4676,8 @@ const SceneExplorer = (props) => {
4671
4676
  }, [sceneTreeItem, sectionTreeItems, allTreeItems, openItems, itemsFilter, isSorted]);
4672
4677
  const getParentStack = useCallback((entity) => {
4673
4678
  const parentStack = [];
4674
- for (let treeItem = allTreeItems.get(GetEntityId(entity)); treeItem; treeItem = treeItem?.type === "entity" ? treeItem.parent : undefined) {
4675
- parentStack.push(treeItem.type === "entity" ? GetEntityId(treeItem.entity) : treeItem.sectionName);
4679
+ for (let treeItem = allTreeItems.get(GetEntityId$1(entity)); treeItem; treeItem = treeItem?.type === "entity" ? treeItem.parent : undefined) {
4680
+ parentStack.push(treeItem.type === "entity" ? GetEntityId$1(treeItem.entity) : treeItem.sectionName);
4676
4681
  }
4677
4682
  // The first item will be the entity itself, so just remove it.
4678
4683
  parentStack.shift();
@@ -4680,7 +4685,7 @@ const SceneExplorer = (props) => {
4680
4685
  }, [allTreeItems]);
4681
4686
  const selectEntity = useCallback((selectedEntity) => {
4682
4687
  const entity = selectedEntity;
4683
- if (entity && GetEntityId(entity) != undefined) {
4688
+ if (entity && GetEntityId$1(entity) != undefined) {
4684
4689
  const parentStack = getParentStack(entity);
4685
4690
  if (parentStack.length > 0) {
4686
4691
  const newOpenItems = new Set(openItems);
@@ -4752,7 +4757,7 @@ const SceneExplorer = (props) => {
4752
4757
  return name;
4753
4758
  };
4754
4759
  const dragProps = createDragProps(item.entity, getName, section.dragDropConfig);
4755
- return (jsx(EntityTreeItem, { scene: scene, entityItem: item, isSelected: selectedEntity === item.entity, select: () => setSelectedEntity?.(item.entity), isFiltering: !!itemsFilter, commandProviders: entityCommandProviders, expandAll: () => expandAll(item), collapseAll: () => collapseAll(item), isDragging: draggedEntity === item.entity, isDropTarget: dropTarget === item.entity, ...dragProps }, GetEntityId(item.entity)));
4760
+ return (jsx(EntityTreeItem, { scene: scene, entityItem: item, isSelected: selectedEntity === item.entity, select: () => setSelectedEntity?.(item.entity), isFiltering: !!itemsFilter, commandProviders: entityCommandProviders, expandAll: () => expandAll(item), collapseAll: () => collapseAll(item), isDragging: draggedEntity === item.entity, isDropTarget: dropTarget === item.entity, ...dragProps }, GetEntityId$1(item.entity)));
4756
4761
  }
4757
4762
  } }) })] }));
4758
4763
  };
@@ -6343,7 +6348,10 @@ const SpinButton = forwardRef((props, ref) => {
6343
6348
  // Update edit text to reflect the new value so the user sees the change
6344
6349
  setEditText(formatValue(newValue));
6345
6350
  }
6346
- HandleKeyDown(event);
6351
+ // Ignore modifier keys so the useKeyState calls above can still observe them.
6352
+ if (event.key !== "Alt" && event.key !== "Shift") {
6353
+ HandleKeyDown(event);
6354
+ }
6347
6355
  }, [value, step, constrainValue, tryCommitValue, commitEditText, formatValue]);
6348
6356
  const id = useId("spin-button2");
6349
6357
  // Real-time validation: when editing, validate the expression; otherwise validate the committed value.
@@ -6904,35 +6912,18 @@ const PerformanceViewer = (props) => {
6904
6912
  return (jsxs("div", { className: classes.container, children: [jsx(Button, { className: classes.returnButton, onClick: onReturnToPlayheadClick, label: "Return", title: "Return to Playhead" }), jsx("div", { className: classes.sidebar, children: jsx(PerformanceSidebar, { collector: performanceCollector, onVisibleRangeChangedObservable: onVisibleRangeChangedObservable }) }), jsx("div", { className: classes.graph, children: jsx(CanvasGraph, { returnToPlayheadObservable: returnToLiveObservable, layoutObservable: layoutObservable, scene: scene, collector: performanceCollector, onVisibleRangeChangedObservable: onVisibleRangeChangedObservable, initialGraphSize: initialGraphSize }) })] }));
6905
6913
  };
6906
6914
 
6907
- function AddStrategies(perfCollector) {
6908
- perfCollector.addCollectionStrategies(...DefaultStrategiesList);
6909
- if (PressureObserverWrapper.IsAvailable) {
6910
- // Do not enable for now as the Pressure API does not
6911
- // report factors at the moment.
6912
- // perfCollector.addCollectionStrategies({
6913
- // strategyCallback: PerfCollectionStrategy.ThermalStrategy(),
6914
- // category: IPerfMetadataCategory.FrameSteps,
6915
- // hidden: true,
6916
- // });
6917
- // perfCollector.addCollectionStrategies({
6918
- // strategyCallback: PerfCollectionStrategy.PowerSupplyStrategy(),
6919
- // category: IPerfMetadataCategory.FrameSteps,
6920
- // hidden: true,
6921
- // });
6922
- perfCollector.addCollectionStrategies({
6923
- strategyCallback: PerfCollectionStrategy.PressureStrategy(),
6924
- category: "Frame Steps Duration" /* PerfMetadataCategory.FrameSteps */,
6925
- hidden: true,
6926
- });
6927
- }
6928
- }
6915
+ /**
6916
+ * Performance metadata categories for grouping strategies.
6917
+ */
6929
6918
  var PerfMetadataCategory;
6930
6919
  (function (PerfMetadataCategory) {
6931
6920
  PerfMetadataCategory["Count"] = "Count";
6932
6921
  PerfMetadataCategory["FrameSteps"] = "Frame Steps Duration";
6933
6922
  })(PerfMetadataCategory || (PerfMetadataCategory = {}));
6934
- // list of strategies to add to perf graph automatically.
6935
- const DefaultStrategiesList = [
6923
+ /**
6924
+ * Default list of performance collection strategies used by the performance viewer and CLI perf trace.
6925
+ */
6926
+ const DefaultPerfStrategies = [
6936
6927
  { strategyCallback: PerfCollectionStrategy.FpsStrategy() },
6937
6928
  { strategyCallback: PerfCollectionStrategy.TotalMeshesStrategy(), category: "Count" /* PerfMetadataCategory.Count */, hidden: true },
6938
6929
  { strategyCallback: PerfCollectionStrategy.ActiveMeshesStrategy(), category: "Count" /* PerfMetadataCategory.Count */, hidden: true },
@@ -6956,6 +6947,21 @@ const DefaultStrategiesList = [
6956
6947
  { strategyCallback: PerfCollectionStrategy.InterFrameStrategy(), category: "Frame Steps Duration" /* PerfMetadataCategory.FrameSteps */, hidden: true },
6957
6948
  { strategyCallback: PerfCollectionStrategy.GpuFrameTimeStrategy(), category: "Frame Steps Duration" /* PerfMetadataCategory.FrameSteps */, hidden: true },
6958
6949
  ];
6950
+
6951
+ /**
6952
+ * Adds default and platform-specific performance collection strategies to the collector.
6953
+ * @param perfCollector - The performance viewer collector to add strategies to.
6954
+ */
6955
+ function AddStrategies(perfCollector) {
6956
+ perfCollector.addCollectionStrategies(...DefaultPerfStrategies);
6957
+ if (PressureObserverWrapper.IsAvailable) {
6958
+ perfCollector.addCollectionStrategies({
6959
+ strategyCallback: PerfCollectionStrategy.PressureStrategy(),
6960
+ category: "Frame Steps Duration" /* PerfMetadataCategory.FrameSteps */,
6961
+ hidden: true,
6962
+ });
6963
+ }
6964
+ }
6959
6965
  // arbitrary window size
6960
6966
  const InitialWindowSize = { width: 1024, height: 512 };
6961
6967
  const InitialGraphSize = new Vector2(724, 512);
@@ -8143,12 +8149,20 @@ function SortServiceDefinitions(serviceDefinitions) {
8143
8149
  * passing dependencies through to services, and disposing of services when the container is disposed.
8144
8150
  */
8145
8151
  class ServiceContainer {
8146
- constructor(_friendlyName) {
8152
+ /**
8153
+ * Creates a new ServiceContainer.
8154
+ * @param _friendlyName A human-readable name for debugging.
8155
+ * @param _parent An optional parent container. Dependencies not found locally will be resolved from the parent.
8156
+ */
8157
+ constructor(_friendlyName, _parent) {
8147
8158
  this._friendlyName = _friendlyName;
8159
+ this._parent = _parent;
8148
8160
  this._isDisposed = false;
8149
8161
  this._serviceDefinitions = new Map();
8150
8162
  this._serviceDependents = new Map();
8151
8163
  this._serviceInstances = new Map();
8164
+ this._children = new Set();
8165
+ _parent?._children.add(this);
8152
8166
  }
8153
8167
  /**
8154
8168
  * Adds a set of service definitions in the service container.
@@ -8209,30 +8223,61 @@ class ServiceContainer {
8209
8223
  service.produces?.forEach((contract) => {
8210
8224
  this._serviceDefinitions.set(contract, service);
8211
8225
  });
8212
- const dependencies = service.consumes?.map((dependency) => {
8213
- const dependencyDefinition = this._serviceDefinitions.get(dependency);
8214
- if (!dependencyDefinition) {
8215
- throw new Error(`Service '${dependency.toString()}' has not been registered in the '${this._friendlyName}' container.`);
8216
- }
8217
- let dependentDefinitions = this._serviceDependents.get(dependencyDefinition);
8226
+ const dependencies = service.consumes?.map((contract) => this._resolveDependency(contract, service)) ?? [];
8227
+ this._serviceInstances.set(service, await service.factory(...dependencies, abortSignal));
8228
+ }
8229
+ /**
8230
+ * Resolves a dependency by contract identity for a consuming service.
8231
+ * Checks local services first, then walks up the parent chain.
8232
+ * Registers the consumer as a dependent in whichever container owns the dependency.
8233
+ * @param contract The contract identity to resolve.
8234
+ * @param consumer The service definition that consumes this dependency.
8235
+ * @returns The resolved service instance.
8236
+ */
8237
+ _resolveDependency(contract, consumer) {
8238
+ const definition = this._serviceDefinitions.get(contract);
8239
+ if (definition) {
8240
+ let dependentDefinitions = this._serviceDependents.get(definition);
8218
8241
  if (!dependentDefinitions) {
8219
- this._serviceDependents.set(dependencyDefinition, (dependentDefinitions = new Set()));
8242
+ this._serviceDependents.set(definition, (dependentDefinitions = new Set()));
8220
8243
  }
8221
- dependentDefinitions.add(service);
8222
- const dependencyInstance = this._serviceInstances.get(dependencyDefinition);
8223
- if (!dependencyInstance) {
8224
- throw new Error(`Service '${dependency.toString()}' has not been instantiated in the '${this._friendlyName}' container.`);
8244
+ dependentDefinitions.add(consumer);
8245
+ const instance = this._serviceInstances.get(definition);
8246
+ if (!instance) {
8247
+ throw new Error(`Service '${contract.toString()}' has not been instantiated in the '${this._friendlyName}' container.`);
8225
8248
  }
8226
- return dependencyInstance;
8227
- }) ?? [];
8228
- this._serviceInstances.set(service, await service.factory(...dependencies, abortSignal));
8249
+ return instance;
8250
+ }
8251
+ if (this._parent) {
8252
+ return this._parent._resolveDependency(contract, consumer);
8253
+ }
8254
+ throw new Error(`Service '${contract.toString()}' has not been registered in the '${this._friendlyName}' container.`);
8255
+ }
8256
+ /**
8257
+ * Removes a consumer from the dependent set for a given contract, checking locally first then the parent chain.
8258
+ * @param contract The contract identity.
8259
+ * @param consumer The service definition to remove as a dependent.
8260
+ */
8261
+ _removeDependentFromChain(contract, consumer) {
8262
+ const definition = this._serviceDefinitions.get(contract);
8263
+ if (definition) {
8264
+ const dependentDefinitions = this._serviceDependents.get(definition);
8265
+ if (dependentDefinitions) {
8266
+ dependentDefinitions.delete(consumer);
8267
+ if (dependentDefinitions.size === 0) {
8268
+ this._serviceDependents.delete(definition);
8269
+ }
8270
+ }
8271
+ return;
8272
+ }
8273
+ this._parent?._removeDependentFromChain(contract, consumer);
8229
8274
  }
8230
8275
  _removeService(service) {
8231
8276
  if (this._isDisposed) {
8232
8277
  throw new Error(`'${this._friendlyName}' container is disposed.`);
8233
8278
  }
8234
8279
  const serviceDependents = this._serviceDependents.get(service);
8235
- if (serviceDependents) {
8280
+ if (serviceDependents && serviceDependents.size > 0) {
8236
8281
  throw new Error(`Service '${service.friendlyName}' has dependents: ${Array.from(serviceDependents)
8237
8282
  .map((dependent) => dependent.friendlyName)
8238
8283
  .join(", ")}`);
@@ -8244,27 +8289,24 @@ class ServiceContainer {
8244
8289
  service.produces?.forEach((contract) => {
8245
8290
  this._serviceDefinitions.delete(contract);
8246
8291
  });
8247
- service.consumes?.forEach((dependency) => {
8248
- const dependencyDefinition = this._serviceDefinitions.get(dependency);
8249
- if (dependencyDefinition) {
8250
- const dependentDefinitions = this._serviceDependents.get(dependencyDefinition);
8251
- if (dependentDefinitions) {
8252
- dependentDefinitions.delete(service);
8253
- if (dependentDefinitions.size === 0) {
8254
- this._serviceDependents.delete(dependencyDefinition);
8255
- }
8256
- }
8257
- }
8292
+ // Remove this service as a dependent from each of its consumed dependencies (local or in parent chain).
8293
+ service.consumes?.forEach((contract) => {
8294
+ this._removeDependentFromChain(contract, service);
8258
8295
  });
8259
8296
  }
8260
8297
  /**
8261
8298
  * Disposes the service container and all contained services.
8299
+ * Throws if this container is still a parent of any live child containers.
8262
8300
  */
8263
8301
  dispose() {
8302
+ if (this._children.size > 0) {
8303
+ throw new Error(`'${this._friendlyName}' container cannot be disposed because it has ${this._children.size} active child container(s).`);
8304
+ }
8264
8305
  Array.from(this._serviceInstances.keys()).reverse().forEach(this._removeService.bind(this));
8265
8306
  this._serviceInstances.clear();
8266
8307
  this._serviceDependents.clear();
8267
8308
  this._serviceDefinitions.clear();
8309
+ this._parent?._children.delete(this);
8268
8310
  this._isDisposed = true;
8269
8311
  }
8270
8312
  }
@@ -8345,7 +8387,7 @@ const ReactContextsWrapper = ({ contexts, children }) => {
8345
8387
  * @returns A token that can be used to dispose of the tool.
8346
8388
  */
8347
8389
  function MakeModularTool(options) {
8348
- const { namespace, containerElement, serviceDefinitions, themeMode, showThemeSelector = true, extensionFeeds = [] } = options;
8390
+ const { namespace, containerElement, serviceDefinitions, themeMode, showThemeSelector = true, extensionFeeds = [], parentContainer } = options;
8349
8391
  // Create the settings store immediately as it will be exposed to services and through React context.
8350
8392
  const settingsStore = new SettingsStore(namespace);
8351
8393
  // If a theme mode is provided, just write the setting so it is the active theme.
@@ -8372,7 +8414,7 @@ function MakeModularTool(options) {
8372
8414
  // This is the main async initialization.
8373
8415
  useEffect(() => {
8374
8416
  const initializeExtensionManagerAsync = async () => {
8375
- const serviceContainer = new ServiceContainer("ModularToolContainer");
8417
+ const serviceContainer = new ServiceContainer("ModularToolContainer", parentContainer);
8376
8418
  // Expose the settings store as a service so other services can read/write settings.
8377
8419
  await serviceContainer.addServiceAsync({
8378
8420
  friendlyName: "Settings Store",
@@ -8420,7 +8462,7 @@ function MakeModularTool(options) {
8420
8462
  }
8421
8463
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
8422
8464
  if (extensionFeeds.length > 0) {
8423
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-BmiNjZiw.js');
8465
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-eRZtqcfj.js');
8424
8466
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
8425
8467
  }
8426
8468
  // Register all external services (that make up a unique tool).
@@ -8536,17 +8578,934 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
8536
8578
  description: "Adds a new panel for easy creation of various Babylon assets. This is a WIP extension...expect changes!",
8537
8579
  keywords: ["creation", "tools"],
8538
8580
  ...BabylonWebResources,
8539
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-BoqrJqoM.js'),
8581
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-MzZbVrvr.js'),
8540
8582
  },
8541
8583
  {
8542
8584
  name: "Reflector",
8543
8585
  description: "Connects to the Reflector Bridge for real-time scene synchronization with the Babylon.js Sandbox.",
8544
8586
  keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
8545
8587
  ...BabylonWebResources,
8546
- getExtensionModuleAsync: async () => await import('./reflectorService-CFTva2GG.js'),
8588
+ getExtensionModuleAsync: async () => await import('./reflectorService-DdPEZLjO.js'),
8547
8589
  },
8548
8590
  ]);
8549
8591
 
8592
+ /**
8593
+ * The service identity for the inspectable command registry.
8594
+ */
8595
+ const InspectableCommandRegistryIdentity = Symbol("InspectableCommandRegistry");
8596
+
8597
+ const UniqueIdArg = {
8598
+ name: "uniqueId",
8599
+ description: "The uniqueId of the entity to query. Omit to list all entities of this type.",
8600
+ required: false,
8601
+ };
8602
+ const SyntheticUniqueIds = new WeakMap();
8603
+ function GetEntityId(entity) {
8604
+ if ("uniqueId" in entity && typeof entity.uniqueId === "number") {
8605
+ return entity.uniqueId;
8606
+ }
8607
+ let id = SyntheticUniqueIds.get(entity);
8608
+ if (!id) {
8609
+ SyntheticUniqueIds.set(entity, (id = UniqueIdGenerator.UniqueId));
8610
+ }
8611
+ return id;
8612
+ }
8613
+ function NodeSummary(entity) {
8614
+ return {
8615
+ uniqueId: entity.uniqueId,
8616
+ name: entity.name,
8617
+ className: entity.getClassName(),
8618
+ parentId: entity.parent?.uniqueId,
8619
+ };
8620
+ }
8621
+ function NamedSummary(entity) {
8622
+ return {
8623
+ uniqueId: entity.uniqueId,
8624
+ name: entity.name,
8625
+ className: entity.getClassName(),
8626
+ };
8627
+ }
8628
+ function MinimalSummary(entity) {
8629
+ return {
8630
+ uniqueId: entity.uniqueId,
8631
+ name: entity.name,
8632
+ };
8633
+ }
8634
+ function MakeQueryCommand(collection, sceneContext) {
8635
+ return {
8636
+ id: collection.id,
8637
+ description: collection.description,
8638
+ args: [UniqueIdArg],
8639
+ executeAsync: async (args) => {
8640
+ const scene = sceneContext.currentScene;
8641
+ if (!scene) {
8642
+ throw new Error("No active scene.");
8643
+ }
8644
+ const entities = collection.getEntities(scene);
8645
+ if (!entities) {
8646
+ return JSON.stringify([], null, 2);
8647
+ }
8648
+ if (!args.uniqueId) {
8649
+ return JSON.stringify(entities.map((e) => collection.getSummary(e)), null, 2);
8650
+ }
8651
+ const id = parseInt(args.uniqueId, 10);
8652
+ if (isNaN(id)) {
8653
+ throw new Error("uniqueId must be a number.");
8654
+ }
8655
+ const entity = entities.find((e) => collection.getUniqueId(e) === id);
8656
+ if (!entity) {
8657
+ throw new Error(`No ${collection.id.replace("query-", "")} found with uniqueId ${id}.`);
8658
+ }
8659
+ return JSON.stringify(collection.serialize ? collection.serialize(entity) : collection.getSummary(entity), null, 2);
8660
+ },
8661
+ };
8662
+ }
8663
+ /**
8664
+ * Service that registers CLI commands for querying scene entities by uniqueId.
8665
+ * When uniqueId is omitted, returns a summary list of all entities of that type.
8666
+ */
8667
+ const EntityQueryServiceDefinition = {
8668
+ friendlyName: "Entity Query Service",
8669
+ consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
8670
+ factory: (commandRegistry, sceneContext) => {
8671
+ const collections = [
8672
+ {
8673
+ id: "query-mesh",
8674
+ description: "List meshes, or query a specific mesh by uniqueId.",
8675
+ getEntities: (scene) => scene.meshes,
8676
+ getUniqueId: (e) => e.uniqueId,
8677
+ getSummary: NodeSummary,
8678
+ serialize: (e) => e.serialize(),
8679
+ },
8680
+ {
8681
+ id: "query-light",
8682
+ description: "List lights, or query a specific light by uniqueId.",
8683
+ getEntities: (scene) => scene.lights,
8684
+ getUniqueId: (e) => e.uniqueId,
8685
+ getSummary: NodeSummary,
8686
+ serialize: (e) => e.serialize(),
8687
+ },
8688
+ {
8689
+ id: "query-camera",
8690
+ description: "List cameras, or query a specific camera by uniqueId.",
8691
+ getEntities: (scene) => scene.cameras,
8692
+ getUniqueId: (e) => e.uniqueId,
8693
+ getSummary: NodeSummary,
8694
+ serialize: (e) => e.serialize(),
8695
+ },
8696
+ {
8697
+ id: "query-transformNode",
8698
+ description: "List transform nodes, or query a specific transform node by uniqueId.",
8699
+ getEntities: (scene) => scene.transformNodes,
8700
+ getUniqueId: (e) => e.uniqueId,
8701
+ getSummary: NodeSummary,
8702
+ serialize: (e) => e.serialize(),
8703
+ },
8704
+ {
8705
+ id: "query-material",
8706
+ description: "List materials, or query a specific material by uniqueId.",
8707
+ getEntities: (scene) => scene.materials,
8708
+ getUniqueId: (e) => e.uniqueId,
8709
+ getSummary: NamedSummary,
8710
+ serialize: (e) => e.serialize(),
8711
+ },
8712
+ {
8713
+ id: "query-texture",
8714
+ description: "List textures, or query a specific texture by uniqueId.",
8715
+ getEntities: (scene) => scene.textures,
8716
+ getUniqueId: (e) => e.uniqueId,
8717
+ getSummary: NamedSummary,
8718
+ serialize: (e) => e.serialize(),
8719
+ },
8720
+ {
8721
+ id: "query-skeleton",
8722
+ description: "List skeletons, or query a specific skeleton by uniqueId.",
8723
+ getEntities: (scene) => scene.skeletons,
8724
+ getUniqueId: (e) => e.uniqueId,
8725
+ getSummary: NamedSummary,
8726
+ serialize: (e) => e.serialize(),
8727
+ },
8728
+ {
8729
+ id: "query-geometry",
8730
+ description: "List geometries, or query a specific geometry by uniqueId.",
8731
+ getEntities: (scene) => scene.geometries,
8732
+ getUniqueId: (e) => e.uniqueId,
8733
+ getSummary: MinimalSummary,
8734
+ serialize: (e) => e.serialize(),
8735
+ },
8736
+ {
8737
+ id: "query-animation",
8738
+ description: "List animations, or query a specific animation by uniqueId.",
8739
+ getEntities: (scene) => scene.animations,
8740
+ getUniqueId: (e) => e.uniqueId,
8741
+ getSummary: MinimalSummary,
8742
+ serialize: (e) => e.serialize(),
8743
+ },
8744
+ {
8745
+ id: "query-animationGroup",
8746
+ description: "List animation groups, or query a specific animation group by uniqueId.",
8747
+ getEntities: (scene) => scene.animationGroups,
8748
+ getUniqueId: (e) => e.uniqueId,
8749
+ getSummary: NamedSummary,
8750
+ serialize: (e) => e.serialize(),
8751
+ },
8752
+ {
8753
+ id: "query-particleSystem",
8754
+ description: "List particle systems, or query a specific particle system by uniqueId.",
8755
+ getEntities: (scene) => scene.particleSystems,
8756
+ getUniqueId: (e) => e.uniqueId,
8757
+ getSummary: NamedSummary,
8758
+ serialize: (e) => e.serialize(false),
8759
+ },
8760
+ {
8761
+ id: "query-morphTargetManager",
8762
+ description: "List morph target managers, or query a specific morph target manager by uniqueId.",
8763
+ getEntities: (scene) => scene.morphTargetManagers,
8764
+ getUniqueId: (e) => e.uniqueId,
8765
+ getSummary: MinimalSummary,
8766
+ serialize: (e) => e.serialize(),
8767
+ },
8768
+ {
8769
+ id: "query-multiMaterial",
8770
+ description: "List multi-materials, or query a specific multi-material by uniqueId.",
8771
+ getEntities: (scene) => scene.multiMaterials,
8772
+ getUniqueId: (e) => e.uniqueId,
8773
+ getSummary: NamedSummary,
8774
+ serialize: (e) => e.serialize(),
8775
+ },
8776
+ {
8777
+ id: "query-postProcess",
8778
+ description: "List post-processes, or query a specific post-process by uniqueId.",
8779
+ getEntities: (scene) => scene.postProcesses,
8780
+ getUniqueId: (e) => e.uniqueId,
8781
+ getSummary: NamedSummary,
8782
+ serialize: (e) => e.serialize(),
8783
+ },
8784
+ {
8785
+ id: "query-frameGraph",
8786
+ description: "List frame graphs, or query a specific frame graph by uniqueId.",
8787
+ getEntities: (scene) => scene.frameGraphs,
8788
+ getUniqueId: (e) => e.uniqueId,
8789
+ getSummary: NamedSummary,
8790
+ },
8791
+ {
8792
+ id: "query-effectLayer",
8793
+ description: "List effect layers, or query a specific effect layer by uniqueId.",
8794
+ getEntities: (scene) => scene.effectLayers,
8795
+ getUniqueId: (e) => e.uniqueId,
8796
+ getSummary: NamedSummary,
8797
+ serialize: (e) => e.serialize?.(),
8798
+ },
8799
+ {
8800
+ id: "query-spriteManager",
8801
+ description: "List sprite managers, or query a specific sprite manager by uniqueId.",
8802
+ getEntities: (scene) => scene.spriteManagers,
8803
+ getUniqueId: (e) => e.uniqueId,
8804
+ getSummary: MinimalSummary,
8805
+ serialize: (e) => e.serialize(false),
8806
+ },
8807
+ {
8808
+ id: "query-sound",
8809
+ description: "List sounds in the main sound track, or query a specific sound by uniqueId.",
8810
+ getEntities: (scene) => scene.mainSoundTrack?.soundCollection ?? [],
8811
+ getUniqueId: (e) => GetEntityId(e),
8812
+ getSummary: (e) => ({ uniqueId: GetEntityId(e), name: e.name, className: e.getClassName() }),
8813
+ serialize: (e) => e.serialize(),
8814
+ },
8815
+ {
8816
+ id: "query-renderingPipeline",
8817
+ description: "List rendering pipelines, or query a specific rendering pipeline by uniqueId.",
8818
+ getEntities: (scene) => scene.postProcessRenderPipelineManager?.supportedPipelines ?? [],
8819
+ getUniqueId: (e) => e.uniqueId,
8820
+ getSummary: NamedSummary,
8821
+ },
8822
+ ];
8823
+ const registrations = collections.map((col) => commandRegistry.addCommand(MakeQueryCommand(col, sceneContext)));
8824
+ return {
8825
+ dispose: () => {
8826
+ for (const reg of registrations) {
8827
+ reg.dispose();
8828
+ }
8829
+ },
8830
+ };
8831
+ },
8832
+ };
8833
+
8834
+ /**
8835
+ * The service identity for the CLI connection status.
8836
+ */
8837
+ const CliConnectionStatusIdentity = Symbol("CliConnectionStatus");
8838
+
8839
+ /**
8840
+ * Creates the service definition for the InspectableBridgeService.
8841
+ * @param options The options for connecting to the bridge.
8842
+ * @returns A service definition that produces an IInspectableCommandRegistry.
8843
+ */
8844
+ function MakeInspectableBridgeServiceDefinition(options) {
8845
+ return {
8846
+ friendlyName: "Inspectable Bridge Service",
8847
+ produces: [InspectableCommandRegistryIdentity, CliConnectionStatusIdentity],
8848
+ factory: () => {
8849
+ const commands = new Map();
8850
+ let ws = null;
8851
+ let reconnectTimer = null;
8852
+ let disposed = false;
8853
+ let connected = false;
8854
+ const onConnectionStatusChanged = new Observable();
8855
+ function setConnected(value) {
8856
+ if (connected !== value) {
8857
+ connected = value;
8858
+ onConnectionStatusChanged.notifyObservers(value);
8859
+ }
8860
+ }
8861
+ function sendToBridge(message) {
8862
+ ws?.send(JSON.stringify(message));
8863
+ }
8864
+ function connect() {
8865
+ if (disposed) {
8866
+ return;
8867
+ }
8868
+ try {
8869
+ ws = new WebSocket(`ws://127.0.0.1:${options.port}`);
8870
+ }
8871
+ catch {
8872
+ scheduleReconnect();
8873
+ return;
8874
+ }
8875
+ ws.onopen = () => {
8876
+ setConnected(true);
8877
+ sendToBridge({ type: "register", name: options.name });
8878
+ };
8879
+ ws.onmessage = (event) => {
8880
+ try {
8881
+ const message = JSON.parse(event.data);
8882
+ void handleMessage(message);
8883
+ }
8884
+ catch {
8885
+ Logger.Warn("InspectableBridgeService: Failed to parse message from bridge.");
8886
+ }
8887
+ };
8888
+ ws.onclose = () => {
8889
+ ws = null;
8890
+ setConnected(false);
8891
+ scheduleReconnect();
8892
+ };
8893
+ ws.onerror = () => {
8894
+ // onclose will fire after onerror, which handles reconnection.
8895
+ };
8896
+ }
8897
+ function scheduleReconnect() {
8898
+ if (disposed || reconnectTimer !== null) {
8899
+ return;
8900
+ }
8901
+ reconnectTimer = setTimeout(() => {
8902
+ reconnectTimer = null;
8903
+ connect();
8904
+ }, 3000);
8905
+ }
8906
+ async function handleMessage(message) {
8907
+ switch (message.type) {
8908
+ case "listCommands": {
8909
+ const commandList = Array.from(commands.values()).map((cmd) => ({
8910
+ id: cmd.id,
8911
+ description: cmd.description,
8912
+ args: cmd.args,
8913
+ }));
8914
+ sendToBridge({
8915
+ type: "commandListResponse",
8916
+ requestId: message.requestId,
8917
+ commands: commandList,
8918
+ });
8919
+ break;
8920
+ }
8921
+ case "execCommand": {
8922
+ const command = commands.get(message.commandId);
8923
+ if (!command) {
8924
+ sendToBridge({
8925
+ type: "commandResponse",
8926
+ requestId: message.requestId,
8927
+ error: `Unknown command: ${message.commandId}`,
8928
+ });
8929
+ break;
8930
+ }
8931
+ try {
8932
+ const result = await command.executeAsync(message.args);
8933
+ sendToBridge({
8934
+ type: "commandResponse",
8935
+ requestId: message.requestId,
8936
+ result,
8937
+ });
8938
+ }
8939
+ catch (error) {
8940
+ sendToBridge({
8941
+ type: "commandResponse",
8942
+ requestId: message.requestId,
8943
+ error: String(error),
8944
+ });
8945
+ }
8946
+ break;
8947
+ }
8948
+ }
8949
+ }
8950
+ // Initiate connection.
8951
+ connect();
8952
+ const registry = {
8953
+ addCommand(descriptor) {
8954
+ if (commands.has(descriptor.id)) {
8955
+ throw new Error(`Command '${descriptor.id}' is already registered.`);
8956
+ }
8957
+ commands.set(descriptor.id, descriptor);
8958
+ return {
8959
+ dispose: () => {
8960
+ commands.delete(descriptor.id);
8961
+ },
8962
+ };
8963
+ },
8964
+ get isConnected() {
8965
+ return connected;
8966
+ },
8967
+ onConnectionStatusChanged,
8968
+ dispose: () => {
8969
+ disposed = true;
8970
+ if (reconnectTimer !== null) {
8971
+ clearTimeout(reconnectTimer);
8972
+ reconnectTimer = null;
8973
+ }
8974
+ commands.clear();
8975
+ setConnected(false);
8976
+ onConnectionStatusChanged.clear();
8977
+ if (ws) {
8978
+ ws.onclose = null;
8979
+ ws.close();
8980
+ ws = null;
8981
+ }
8982
+ },
8983
+ };
8984
+ return registry;
8985
+ },
8986
+ };
8987
+ }
8988
+
8989
+ /**
8990
+ * Service that registers CLI commands for performance tracing using the PerformanceViewerCollector.
8991
+ * start-perf-trace begins collecting data, stop-perf-trace stops and returns the collected data as JSON.
8992
+ */
8993
+ const PerfTraceCommandServiceDefinition = {
8994
+ friendlyName: "Perf Trace Command Service",
8995
+ consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
8996
+ factory: (commandRegistry, sceneContext) => {
8997
+ let perfCollector;
8998
+ const startReg = commandRegistry.addCommand({
8999
+ id: "start-perf-trace",
9000
+ description: "Start collecting performance trace data.",
9001
+ executeAsync: async () => {
9002
+ const scene = sceneContext.currentScene;
9003
+ if (!scene) {
9004
+ throw new Error("No active scene.");
9005
+ }
9006
+ if (perfCollector?.isStarted) {
9007
+ return "Performance trace is already running.";
9008
+ }
9009
+ perfCollector = scene.getPerfCollector();
9010
+ perfCollector.stop();
9011
+ perfCollector.clear(false);
9012
+ perfCollector.addCollectionStrategies(...DefaultPerfStrategies);
9013
+ perfCollector.start(true);
9014
+ return "Performance trace started.";
9015
+ },
9016
+ });
9017
+ const stopReg = commandRegistry.addCommand({
9018
+ id: "stop-perf-trace",
9019
+ description: "Stop collecting performance trace data and return the results as JSON.",
9020
+ executeAsync: async () => {
9021
+ if (!perfCollector || !perfCollector.isStarted) {
9022
+ throw new Error("Performance trace is not running. Run start-perf-trace first.");
9023
+ }
9024
+ perfCollector.stop();
9025
+ const datasets = perfCollector.datasets;
9026
+ const ids = datasets.ids;
9027
+ const rawData = datasets.data.subarray(0, datasets.data.itemLength);
9028
+ const sliceSize = ids.length + PerformanceViewerCollector.SliceDataOffset;
9029
+ const samples = [];
9030
+ for (let i = 0; i < rawData.length; i += sliceSize) {
9031
+ const timestamp = rawData[i];
9032
+ const sample = { timestamp };
9033
+ for (let j = 0; j < ids.length; j++) {
9034
+ sample[ids[j]] = rawData[i + PerformanceViewerCollector.SliceDataOffset + j];
9035
+ }
9036
+ samples.push(sample);
9037
+ }
9038
+ perfCollector.clear(false);
9039
+ perfCollector = undefined;
9040
+ return JSON.stringify({ strategies: ids, sampleCount: samples.length, samples }, null, 2);
9041
+ },
9042
+ });
9043
+ return {
9044
+ dispose: () => {
9045
+ startReg.dispose();
9046
+ stopReg.dispose();
9047
+ if (perfCollector?.isStarted) {
9048
+ perfCollector.stop();
9049
+ }
9050
+ perfCollector = undefined;
9051
+ },
9052
+ };
9053
+ },
9054
+ };
9055
+
9056
+ /**
9057
+ * Service that registers a CLI command for capturing a screenshot of the scene.
9058
+ * Returns the image as a base64 data string, suitable for consumption by AI agents.
9059
+ */
9060
+ const ScreenshotCommandServiceDefinition = {
9061
+ friendlyName: "Screenshot Command Service",
9062
+ consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
9063
+ factory: (commandRegistry, sceneContext) => {
9064
+ const registration = commandRegistry.addCommand({
9065
+ id: "take-screenshot",
9066
+ description: "Capture a screenshot of the scene. Returns base64-encoded PNG data.",
9067
+ args: [
9068
+ {
9069
+ name: "cameraUniqueId",
9070
+ description: "The uniqueId of the camera to use. Defaults to the active camera.",
9071
+ required: false,
9072
+ },
9073
+ {
9074
+ name: "width",
9075
+ description: "Screenshot width in pixels. When set, uses custom size mode.",
9076
+ required: false,
9077
+ },
9078
+ {
9079
+ name: "height",
9080
+ description: "Screenshot height in pixels. When set, uses custom size mode.",
9081
+ required: false,
9082
+ },
9083
+ {
9084
+ name: "precision",
9085
+ description: "Resolution multiplier (e.g. 2 for double resolution). Defaults to 1.",
9086
+ required: false,
9087
+ },
9088
+ ],
9089
+ executeAsync: async (args) => {
9090
+ const scene = sceneContext.currentScene;
9091
+ if (!scene) {
9092
+ throw new Error("No active scene.");
9093
+ }
9094
+ const engine = scene.getEngine();
9095
+ // Resolve camera: explicit uniqueId, or active/frame-graph camera.
9096
+ let camera;
9097
+ if (args.cameraUniqueId) {
9098
+ const cameraId = parseInt(args.cameraUniqueId, 10);
9099
+ if (isNaN(cameraId)) {
9100
+ throw new Error("cameraUniqueId must be a number.");
9101
+ }
9102
+ camera = scene.cameras.find((c) => c.uniqueId === cameraId);
9103
+ if (!camera) {
9104
+ throw new Error(`No camera found with uniqueId ${cameraId}.`);
9105
+ }
9106
+ }
9107
+ else {
9108
+ camera = scene.frameGraph ? FrameGraphUtils.FindMainCamera(scene.frameGraph) : scene.activeCamera;
9109
+ }
9110
+ if (!camera) {
9111
+ throw new Error("No camera available for screenshot.");
9112
+ }
9113
+ const precision = args.precision !== undefined ? Number(args.precision) : 1;
9114
+ if (!Number.isFinite(precision) || precision <= 0) {
9115
+ throw new Error("precision must be a finite number greater than 0.");
9116
+ }
9117
+ let width;
9118
+ if (args.width !== undefined) {
9119
+ width = Number(args.width);
9120
+ if (!Number.isFinite(width) || width <= 0 || !Number.isInteger(width)) {
9121
+ throw new Error("width must be a finite positive integer.");
9122
+ }
9123
+ }
9124
+ let height;
9125
+ if (args.height !== undefined) {
9126
+ height = Number(args.height);
9127
+ if (!Number.isFinite(height) || height <= 0 || !Number.isInteger(height)) {
9128
+ throw new Error("height must be a finite positive integer.");
9129
+ }
9130
+ }
9131
+ const screenshotSize = width !== undefined && height !== undefined ? { width, height, precision } : { precision };
9132
+ // Omit fileName to get data URL back without triggering a download.
9133
+ const dataUrl = await CreateScreenshotUsingRenderTargetAsync(engine, camera, screenshotSize, "image/png");
9134
+ // Strip the data URI prefix to return raw base64, which is what AI agent APIs expect.
9135
+ const commaIndex = dataUrl.indexOf(",");
9136
+ return commaIndex !== -1 ? dataUrl.substring(commaIndex + 1) : dataUrl;
9137
+ },
9138
+ });
9139
+ return {
9140
+ dispose: () => {
9141
+ registration.dispose();
9142
+ },
9143
+ };
9144
+ },
9145
+ };
9146
+
9147
+ /**
9148
+ * Service that registers a CLI command for retrieving compiled shader code from a material.
9149
+ */
9150
+ const ShaderCommandServiceDefinition = {
9151
+ friendlyName: "Shader Command Service",
9152
+ consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
9153
+ factory: (commandRegistry, sceneContext) => {
9154
+ const registration = commandRegistry.addCommand({
9155
+ id: "get-shader-code",
9156
+ description: "Get the shader code for a material by uniqueId.",
9157
+ args: [
9158
+ {
9159
+ name: "uniqueId",
9160
+ description: "The uniqueId of the material.",
9161
+ required: true,
9162
+ },
9163
+ {
9164
+ name: "variant",
9165
+ description: "Which shader variant to return: compiled (default), raw, or beforeMigration.",
9166
+ required: false,
9167
+ },
9168
+ ],
9169
+ executeAsync: async (args) => {
9170
+ const scene = sceneContext.currentScene;
9171
+ if (!scene) {
9172
+ throw new Error("No active scene.");
9173
+ }
9174
+ const id = parseInt(args.uniqueId, 10);
9175
+ if (isNaN(id)) {
9176
+ throw new Error("uniqueId must be a number.");
9177
+ }
9178
+ const material = scene.materials.find((m) => m.uniqueId === id);
9179
+ if (!material) {
9180
+ throw new Error(`No material found with uniqueId ${id}.`);
9181
+ }
9182
+ const effect = material.getEffect();
9183
+ if (!effect) {
9184
+ throw new Error(`Material "${material.name}" has no effect. It may not have been rendered yet.`);
9185
+ }
9186
+ if (!effect.isReady()) {
9187
+ throw new Error(`Material "${material.name}" effect is not ready. Wait for it to be rendered.`);
9188
+ }
9189
+ const variant = args.variant ?? "compiled";
9190
+ let vertexShader;
9191
+ let fragmentShader;
9192
+ switch (variant) {
9193
+ case "compiled":
9194
+ vertexShader = effect.vertexSourceCode;
9195
+ fragmentShader = effect.fragmentSourceCode;
9196
+ break;
9197
+ case "raw":
9198
+ vertexShader = effect.rawVertexSourceCode;
9199
+ fragmentShader = effect.rawFragmentSourceCode;
9200
+ break;
9201
+ case "beforeMigration":
9202
+ vertexShader = effect.vertexSourceCodeBeforeMigration;
9203
+ fragmentShader = effect.fragmentSourceCodeBeforeMigration;
9204
+ break;
9205
+ default:
9206
+ throw new Error(`Unknown variant "${variant}". Use: compiled, raw, or beforeMigration.`);
9207
+ }
9208
+ return JSON.stringify({ vertexShader, fragmentShader }, null, 2);
9209
+ },
9210
+ });
9211
+ return {
9212
+ dispose: () => {
9213
+ registration.dispose();
9214
+ },
9215
+ };
9216
+ },
9217
+ };
9218
+
9219
+ /**
9220
+ * Service that registers CLI commands for querying scene and engine statistics.
9221
+ */
9222
+ const StatsCommandServiceDefinition = {
9223
+ friendlyName: "Stats Command Service",
9224
+ consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
9225
+ factory: (commandRegistry, sceneContext) => {
9226
+ let sceneInstrumentation;
9227
+ let engineInstrumentation;
9228
+ function disposeInstrumentation() {
9229
+ sceneInstrumentation?.dispose();
9230
+ sceneInstrumentation = undefined;
9231
+ engineInstrumentation?.dispose();
9232
+ engineInstrumentation = undefined;
9233
+ }
9234
+ const startPerfReg = commandRegistry.addCommand({
9235
+ id: "start-perf-instrumentation",
9236
+ description: "Start scene and engine performance instrumentation for frame stats collection.",
9237
+ executeAsync: async () => {
9238
+ const scene = sceneContext.currentScene;
9239
+ if (!scene) {
9240
+ throw new Error("No active scene.");
9241
+ }
9242
+ // Dispose any stale instrumentation (e.g. scene changed).
9243
+ if (sceneInstrumentation && sceneInstrumentation.scene !== scene) {
9244
+ disposeInstrumentation();
9245
+ }
9246
+ if (sceneInstrumentation) {
9247
+ return "Performance instrumentation is already running.";
9248
+ }
9249
+ sceneInstrumentation = new SceneInstrumentation(scene);
9250
+ sceneInstrumentation.captureActiveMeshesEvaluationTime = true;
9251
+ sceneInstrumentation.captureRenderTargetsRenderTime = true;
9252
+ sceneInstrumentation.captureFrameTime = true;
9253
+ sceneInstrumentation.captureRenderTime = true;
9254
+ sceneInstrumentation.captureInterFrameTime = true;
9255
+ sceneInstrumentation.captureParticlesRenderTime = true;
9256
+ sceneInstrumentation.captureSpritesRenderTime = true;
9257
+ sceneInstrumentation.capturePhysicsTime = true;
9258
+ sceneInstrumentation.captureAnimationsTime = true;
9259
+ engineInstrumentation = new EngineInstrumentation(scene.getEngine());
9260
+ engineInstrumentation.captureGPUFrameTime = true;
9261
+ return "Performance instrumentation started.";
9262
+ },
9263
+ });
9264
+ const stopPerfReg = commandRegistry.addCommand({
9265
+ id: "stop-perf-instrumentation",
9266
+ description: "Stop scene and engine performance instrumentation.",
9267
+ executeAsync: async () => {
9268
+ if (!sceneInstrumentation && !engineInstrumentation) {
9269
+ return "Performance instrumentation is not running.";
9270
+ }
9271
+ disposeInstrumentation();
9272
+ return "Performance instrumentation stopped.";
9273
+ },
9274
+ });
9275
+ const countStatsReg = commandRegistry.addCommand({
9276
+ id: "get-count-stats",
9277
+ description: "Get scene entity counts (meshes, lights, vertices, draw calls, etc.).",
9278
+ executeAsync: async () => {
9279
+ const scene = sceneContext.currentScene;
9280
+ if (!scene) {
9281
+ throw new Error("No active scene.");
9282
+ }
9283
+ let activeMeshesCount = scene.getActiveMeshes().length;
9284
+ for (const objectRenderer of scene.objectRenderers) {
9285
+ activeMeshesCount += objectRenderer.getActiveMeshes().length;
9286
+ }
9287
+ const activeIndices = scene.getActiveIndices();
9288
+ return JSON.stringify({
9289
+ totalMeshes: scene.meshes.length,
9290
+ activeMeshes: activeMeshesCount,
9291
+ activeIndices,
9292
+ activeFaces: Math.floor(activeIndices / 3),
9293
+ activeBones: scene.getActiveBones(),
9294
+ activeParticles: scene.getActiveParticles(),
9295
+ drawCalls: scene.getEngine()._drawCalls.current,
9296
+ totalLights: scene.lights.length,
9297
+ totalVertices: scene.getTotalVertices(),
9298
+ totalMaterials: scene.materials.length,
9299
+ totalTextures: scene.textures.length,
9300
+ }, null, 2);
9301
+ },
9302
+ });
9303
+ const frameStatsReg = commandRegistry.addCommand({
9304
+ id: "get-frame-stats",
9305
+ description: "Get frame timing statistics. Requires start-perf-instrumentation to be run first.",
9306
+ executeAsync: async () => {
9307
+ if (!sceneInstrumentation || !engineInstrumentation) {
9308
+ throw new Error("Performance instrumentation is not running. Run start-perf-instrumentation first.");
9309
+ }
9310
+ const si = sceneInstrumentation;
9311
+ const ei = engineInstrumentation;
9312
+ const round = (v) => Math.round(v * 100) / 100;
9313
+ return JSON.stringify({
9314
+ absoluteFPS: Math.floor(1000.0 / si.frameTimeCounter.lastSecAverage),
9315
+ meshesSelectionMs: round(si.activeMeshesEvaluationTimeCounter.lastSecAverage),
9316
+ renderTargetsMs: round(si.renderTargetsRenderTimeCounter.lastSecAverage),
9317
+ particlesMs: round(si.particlesRenderTimeCounter.lastSecAverage),
9318
+ spritesMs: round(si.spritesRenderTimeCounter.lastSecAverage),
9319
+ animationsMs: round(si.animationsTimeCounter.lastSecAverage),
9320
+ physicsMs: round(si.physicsTimeCounter.lastSecAverage),
9321
+ renderMs: round(si.renderTimeCounter.lastSecAverage),
9322
+ frameMs: round(si.frameTimeCounter.lastSecAverage),
9323
+ interFrameMs: round(si.interFrameTimeCounter.lastSecAverage),
9324
+ gpuFrameMs: round(ei.gpuFrameTimeCounter.lastSecAverage * 0.000001),
9325
+ gpuFrameAverageMs: round(ei.gpuFrameTimeCounter.average * 0.000001),
9326
+ }, null, 2);
9327
+ },
9328
+ });
9329
+ const systemStatsReg = commandRegistry.addCommand({
9330
+ id: "get-system-stats",
9331
+ description: "Get engine capabilities and system information.",
9332
+ executeAsync: async () => {
9333
+ const scene = sceneContext.currentScene;
9334
+ if (!scene) {
9335
+ throw new Error("No active scene.");
9336
+ }
9337
+ const engine = scene.getEngine();
9338
+ const caps = engine.getCaps();
9339
+ return JSON.stringify({
9340
+ resolution: `${engine.getRenderWidth()} x ${engine.getRenderHeight()}`,
9341
+ hardwareScalingLevel: engine.getHardwareScalingLevel(),
9342
+ engine: engine.description,
9343
+ driver: engine.extractDriverInfo(),
9344
+ capabilities: {
9345
+ stdDerivatives: caps.standardDerivatives,
9346
+ compressedTextures: caps.s3tc !== undefined,
9347
+ hardwareInstances: caps.instancedArrays,
9348
+ textureFloat: caps.textureFloat,
9349
+ textureHalfFloat: caps.textureHalfFloat,
9350
+ renderToTextureFloat: caps.textureFloatRender,
9351
+ renderToTextureHalfFloat: caps.textureHalfFloatRender,
9352
+ indices32Bit: caps.uintIndices,
9353
+ fragmentDepth: caps.fragmentDepthSupported,
9354
+ highPrecisionShaders: caps.highPrecisionShaderSupported,
9355
+ drawBuffers: caps.drawBuffersExtension,
9356
+ vertexArrayObject: caps.vertexArrayObject,
9357
+ timerQuery: caps.timerQuery !== undefined,
9358
+ stencil: engine.isStencilEnable,
9359
+ parallelShaderCompilation: caps.parallelShaderCompile != null,
9360
+ maxTexturesUnits: caps.maxTexturesImageUnits,
9361
+ maxTexturesSize: caps.maxTextureSize,
9362
+ maxAnisotropy: caps.maxAnisotropy,
9363
+ },
9364
+ }, null, 2);
9365
+ },
9366
+ });
9367
+ return {
9368
+ dispose: () => {
9369
+ startPerfReg.dispose();
9370
+ stopPerfReg.dispose();
9371
+ countStatsReg.dispose();
9372
+ frameStatsReg.dispose();
9373
+ systemStatsReg.dispose();
9374
+ disposeInstrumentation();
9375
+ },
9376
+ };
9377
+ },
9378
+ };
9379
+
9380
+ const DefaultPort = 4400;
9381
+ const InspectableStates = new Map();
9382
+ /**
9383
+ * @internal
9384
+ * Internal implementation that returns an {@link InternalInspectableToken} with access
9385
+ * to the underlying ServiceContainer. Used by ShowInspector to set up a parent container relationship.
9386
+ */
9387
+ function _StartInspectable(scene, options) {
9388
+ let state = InspectableStates.get(scene);
9389
+ if (!state) {
9390
+ const port = options?.port ?? DefaultPort;
9391
+ const name = options?.name ?? (typeof document !== "undefined" ? document.title : "Babylon.js Scene");
9392
+ const serviceContainer = new ServiceContainer("InspectableContainer");
9393
+ const fullyDispose = () => {
9394
+ InspectableStates.delete(scene);
9395
+ serviceContainer.dispose();
9396
+ sceneDisposeObserver.remove();
9397
+ };
9398
+ // Initialize the service container asynchronously.
9399
+ const sceneContextServiceDefinition = {
9400
+ friendlyName: "Inspectable Scene Context",
9401
+ produces: [SceneContextIdentity],
9402
+ factory: () => ({
9403
+ currentScene: scene,
9404
+ currentSceneObservable: new Observable(),
9405
+ }),
9406
+ };
9407
+ const readyPromise = (async () => {
9408
+ await serviceContainer.addServicesAsync(sceneContextServiceDefinition, MakeInspectableBridgeServiceDefinition({
9409
+ port,
9410
+ name,
9411
+ }), EntityQueryServiceDefinition, ScreenshotCommandServiceDefinition, ShaderCommandServiceDefinition, StatsCommandServiceDefinition, PerfTraceCommandServiceDefinition);
9412
+ })();
9413
+ state = {
9414
+ refCount: 0,
9415
+ serviceContainer,
9416
+ sceneDisposeObserver: { remove: () => { } },
9417
+ fullyDispose,
9418
+ readyPromise,
9419
+ };
9420
+ const capturedState = state;
9421
+ InspectableStates.set(scene, state);
9422
+ // Auto-dispose when the scene is disposed.
9423
+ const sceneDisposeObserver = scene.onDisposeObservable.addOnce(() => {
9424
+ capturedState.refCount = 0;
9425
+ capturedState.fullyDispose();
9426
+ });
9427
+ state.sceneDisposeObserver = sceneDisposeObserver;
9428
+ // Handle initialization failure (guard against already-disposed state).
9429
+ void (async () => {
9430
+ try {
9431
+ await readyPromise;
9432
+ }
9433
+ catch (error) {
9434
+ if (InspectableStates.has(scene)) {
9435
+ Logger.Error(`Failed to initialize Inspectable: ${error}`);
9436
+ capturedState.refCount = 0;
9437
+ capturedState.fullyDispose();
9438
+ }
9439
+ }
9440
+ })();
9441
+ }
9442
+ state.refCount++;
9443
+ const { serviceContainer } = state;
9444
+ const owningState = state;
9445
+ // If additional service definitions were provided, add them in a separate call
9446
+ // so they can be independently removed when this token is disposed.
9447
+ let extraServicesDisposable;
9448
+ const extraAbortController = new AbortController();
9449
+ const extraServiceDefinitions = options?.serviceDefinitions;
9450
+ if (extraServiceDefinitions && extraServiceDefinitions.length > 0) {
9451
+ // Wait for the built-in services to be ready, then add the extra ones.
9452
+ void (async () => {
9453
+ try {
9454
+ await owningState.readyPromise;
9455
+ extraServicesDisposable = await serviceContainer.addServicesAsync(...extraServiceDefinitions, extraAbortController.signal);
9456
+ }
9457
+ catch (error) {
9458
+ if (!extraAbortController.signal.aborted) {
9459
+ Logger.Error(`Failed to add extra inspectable services: ${error}`);
9460
+ }
9461
+ }
9462
+ })();
9463
+ }
9464
+ let disposed = false;
9465
+ const token = {
9466
+ get isDisposed() {
9467
+ return disposed;
9468
+ },
9469
+ get serviceContainer() {
9470
+ return serviceContainer;
9471
+ },
9472
+ dispose() {
9473
+ if (disposed) {
9474
+ return;
9475
+ }
9476
+ disposed = true;
9477
+ // Abort any in-flight extra service initialization and remove already-added extra services.
9478
+ extraAbortController.abort();
9479
+ extraServicesDisposable?.dispose();
9480
+ owningState.refCount--;
9481
+ if (owningState.refCount <= 0) {
9482
+ owningState.fullyDispose();
9483
+ }
9484
+ },
9485
+ };
9486
+ return token;
9487
+ }
9488
+ /**
9489
+ * Makes a scene inspectable by connecting it to the Inspector CLI bridge.
9490
+ * This creates a headless {@link ServiceContainer} (no UI) and registers the
9491
+ * {@link InspectableBridgeService} which opens a WebSocket to the bridge and
9492
+ * exposes a command registry for CLI-invocable commands.
9493
+ *
9494
+ * Multiple callers may call this for the same scene. Each returned token is
9495
+ * ref-counted — the underlying connection is only torn down when all tokens
9496
+ * have been disposed. Additional {@link InspectableOptions.serviceDefinitions}
9497
+ * passed by each caller are added to the shared container and removed when
9498
+ * that caller's token is disposed.
9499
+ *
9500
+ * @param scene The scene to make inspectable.
9501
+ * @param options Optional configuration.
9502
+ * @returns An {@link InspectableToken} that can be disposed to disconnect.
9503
+ * @experimental
9504
+ */
9505
+ function StartInspectable(scene, options) {
9506
+ return _StartInspectable(scene, options);
9507
+ }
9508
+
8550
9509
  /**
8551
9510
  * Reusable component which renders a color property line containing a label, colorPicker popout, and expandable RGBA values
8552
9511
  * The expandable RGBA values are synced sliders that allow the user to modify the color's RGBA values directly
@@ -8735,6 +9694,39 @@ const LegacyInspectableObjectPropertiesServiceDefinition = {
8735
9694
  },
8736
9695
  };
8737
9696
 
9697
+ const CliConnectionStatusServiceDefinition = {
9698
+ friendlyName: "CLI Connection Status",
9699
+ consumes: [ShellServiceIdentity, CliConnectionStatusIdentity],
9700
+ factory: (shellService, cliConnectionStatus) => {
9701
+ shellService.addToolbarItem({
9702
+ key: "CLI Connection Status",
9703
+ verticalLocation: "bottom",
9704
+ horizontalLocation: "right",
9705
+ teachingMoment: false,
9706
+ order: 0 /* DefaultToolbarItemOrder.CliStatus */,
9707
+ component: () => {
9708
+ const isConnected = useObservableState(() => cliConnectionStatus.isConnected, cliConnectionStatus.onConnectionStatusChanged);
9709
+ const { showToast } = useToast();
9710
+ const isFirstRender = useRef(true);
9711
+ useEffect(() => {
9712
+ if (isFirstRender.current) {
9713
+ isFirstRender.current = false;
9714
+ return;
9715
+ }
9716
+ if (isConnected) {
9717
+ showToast("Inspector bridge connected.", { intent: "success" });
9718
+ }
9719
+ else {
9720
+ showToast("Inspector bridge disconnected.", { intent: "warning" });
9721
+ }
9722
+ }, [isConnected, showToast]);
9723
+ // Using raw Fluent Button to pass color directly to the icon.
9724
+ return (jsx(Tooltip, { content: isConnected ? "Connected to Inspector CLI Bridge" : "Disconnected from Inspector CLI Bridge", children: jsx(Button$1, { appearance: "subtle", icon: isConnected ? (jsx(PlugConnectedRegular, { color: tokens.colorPaletteGreenForeground2 })) : (jsx(PlugDisconnectedRegular, { color: tokens.colorPaletteRedForeground2 })), onClick: () => window.open("https://www.npmjs.com/package/@babylonjs/inspector", "_blank") }) }));
9725
+ },
9726
+ });
9727
+ },
9728
+ };
9729
+
8738
9730
  const MeshIcon = createFluentIcon("Mesh", "1em", '<g transform="scale(1.25)"><path d="M14.03,3.54l-5.11-2.07c-.61-.25-1.27-.25-1.88,0L1.93,3.54c-.57.23-.94.78-.94,1.39v6.15c0,.61.37,1.16.94,1.39l5.11,2.07c.3.12.62.18.94.18s.64-.06.94-.18l5.12-2.07c.57-.23.94-.78.94-1.39v-6.15c0-.61-.37-1.16-.94-1.39ZM13.97,7.71l-2.11.86v-2.71l2.11-.86v2.71ZM1.99,5l2.11.86v2.71l-2.11-.86v-2.71ZM11.35,4.98l-2.04-.83,1.78-.72,2.04.83-1.78.72ZM10.02,5.52l-2.04.83-2.04-.83,2.04-.83,2.04.83ZM4.6,4.98l-1.78-.72,2.04-.83,1.78.72-2.04.83ZM5.1,6.26l2.38.96v2.71l-2.38-.96v-2.71ZM8.48,7.22l2.38-.96v2.71l-2.38.96v-2.71ZM7.41,2.39c.18-.07.37-.11.56-.11s.38.04.56.11l1.22.49-1.78.72-1.79-.72,1.22-.49ZM1.99,11.07v-2.29l2.11.86v2.62l-1.8-.73c-.19-.08-.31-.26-.31-.46ZM5.1,12.67v-2.62l2.38.96v2.61s-.04,0-.06-.01l-2.31-.94ZM8.54,13.61s-.04,0-.06.01v-2.61l2.38-.96v2.62l-2.31.94ZM13.66,11.54l-1.8.73v-2.62l2.11-.86v2.29c0,.2-.12.39-.31.46Z"/></g>');
8739
9731
  const TranslateIcon = createFluentIcon("Translate", "1em", '<g transform="scale(0.833)"><path d="M20.16,12.98l-2.75-2.75c-.29-.29-.77-.29-1.06,0-.29.29-.29.77,0,1.06l1.47,1.47h-6.69v-6.69l1.47,1.47c.29.29.77.29,1.06,0,.29-.29.29-.77,0-1.06l-2.75-2.75c-.14-.14-.33-.22-.53-.22s-.39.08-.53.22l-2.75,2.75c-.29.29-.29.77,0,1.06.29.29.77.29,1.06,0l1.47-1.47v7.13l-3.52,3.52v-2.08c0-.41-.34-.75-.75-.75s-.75.34-.75.75v3.89c0,.2.08.39.22.53.14.14.33.22.53.22h3.89c.41,0,.75-.34.75-.75s-.34-.75-.75-.75h-2.08s3.52-3.52,3.52-3.52h7.13l-1.47,1.47c-.29.29-.29.77,0,1.06s.77.29,1.06,0l2.75-2.75c.14-.14.22-.33.22-.53s-.08-.39-.22-.53Z" /></g>');
8740
9732
  const MaterialIcon = createFluentIcon("Material", "1em", '<g transform="scale(1.25)"><path d="M14.74,6.3c-.09-.36-.38-.64-.75-.72-.04-.09-.08-.18-.12-.27.1-.15.16-.32.16-.51,0-.18-.05-.34-.13-.48-1.23-1.97-3.41-3.28-5.9-3.28C4.16,1.04,1.04,4.16,1.04,7.99c0,.39.23.72.57.88.02.12.03.25.06.37-.18.18-.3.42-.3.7,0,.11.02.21.06.31.94,2.74,3.53,4.71,6.58,4.71,3.84,0,6.96-3.12,6.96-6.96,0-.59-.08-1.16-.22-1.7ZM2.07,8.58c-.02-.19-.03-.39-.03-.58,0-3.29,2.67-5.96,5.96-5.96,2.23,0,4.17,1.23,5.2,3.05.05.18-.07.45-.3.75-.57-.73-1.45-1.21-2.45-1.21-1.72,0-3.12,1.4-3.12,3.11,0,.33.07.65.16.95-3.05.82-5.17.52-5.42-.11ZM12.56,7.75c0,1.17-.95,2.11-2.11,2.11s-2.12-.95-2.12-2.11.95-2.11,2.12-2.11,2.11.95,2.11,2.11ZM8,13.96c-2.6,0-4.81-1.68-5.62-4.01.5.16,1.11.24,1.79.24,1.15,0,2.49-.22,3.79-.59.57.76,1.47,1.26,2.49,1.26,1.72,0,3.11-1.4,3.11-3.11,0-.34-.07-.65-.17-.96.13-.13.24-.26.34-.39.14.51.22,1.04.22,1.6,0,3.29-2.67,5.96-5.96,5.96Z"/></g>');
@@ -14547,14 +15539,42 @@ const NormalMapProperties = (props) => {
14547
15539
  return (jsx(Fragment, { children: IsMaterialWithPublicNormalMaps(material) ? (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Invert X Axis", target: material, propertyKey: "invertNormalMapX" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Invert Y Axis", target: material, propertyKey: "invertNormalMapY" })] })) : (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Invert X Axis", target: material, propertyKey: "_invertNormalMapX" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Invert Y Axis", target: material, propertyKey: "_invertNormalMapY" })] })) }));
14548
15540
  };
14549
15541
 
14550
- // TODO: ryamtrem / gehalper This function is temporal until there is a line control to handle texture links (similar to the old TextureLinkLineComponent)
14551
- const UpdateTexture = (file, material, textureSetter) => {
14552
- ReadFile(file, (data) => {
14553
- const blob = new Blob([data], { type: "octet/stream" });
14554
- const url = URL.createObjectURL(blob);
14555
- textureSetter(new Texture(url, material.getScene(), false, false));
14556
- }, undefined, true);
15542
+ const useCheckboxStyles = makeStyles({
15543
+ indicator: {
15544
+ margin: 0,
15545
+ },
15546
+ });
15547
+ /**
15548
+ * This is a primitive fluent checkbox that can both read and write checked state
15549
+ * @param props
15550
+ * @returns Checkbox component
15551
+ */
15552
+ const Checkbox = (props) => {
15553
+ Checkbox.displayName = "Checkbox";
15554
+ const [checked, setChecked] = useState(() => props.value ?? false);
15555
+ const classes = useCheckboxStyles();
15556
+ useEffect(() => {
15557
+ if (props.value != undefined) {
15558
+ setChecked(props.value); // Update local state when props.checked changes
15559
+ }
15560
+ }, [props.value]);
15561
+ const onChange = (ev, _) => {
15562
+ props.onChange(ev.target.checked);
15563
+ setChecked(ev.target.checked);
15564
+ };
15565
+ return jsx(Checkbox$1, { checked: checked, onChange: onChange, indicator: { className: classes.indicator } });
15566
+ };
15567
+
15568
+ /**
15569
+ * Wraps a checkbox in a property line
15570
+ * @param props - PropertyLineProps and CheckboxProps
15571
+ * @returns property-line wrapped checkbox
15572
+ */
15573
+ const CheckboxPropertyLine = (props) => {
15574
+ CheckboxPropertyLine.displayName = "CheckboxPropertyLine";
15575
+ return (jsx(PropertyLine, { ...props, children: jsx(Checkbox, { ...props }) }));
14557
15576
  };
15577
+
14558
15578
  /**
14559
15579
  * Displays the base layer properties of an OpenPBR material.
14560
15580
  * @param props - The required properties
@@ -14562,23 +15582,7 @@ const UpdateTexture = (file, material, textureSetter) => {
14562
15582
  */
14563
15583
  const OpenPBRMaterialBaseProperties = (props) => {
14564
15584
  const { material } = props;
14565
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Base Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14566
- if (files.length > 0) {
14567
- UpdateTexture(files[0], material, (texture) => (material.baseWeightTexture = texture));
14568
- }
14569
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Base Color", target: material, propertyKey: "baseColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Base Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14570
- if (files.length > 0) {
14571
- UpdateTexture(files[0], material, (texture) => (material.baseColorTexture = texture));
14572
- }
14573
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Metalness", target: material, propertyKey: "baseMetalness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Base Metalness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14574
- if (files.length > 0) {
14575
- UpdateTexture(files[0], material, (texture) => (material.baseMetalnessTexture = texture));
14576
- }
14577
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Base Diffuse Roughness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14578
- if (files.length > 0) {
14579
- UpdateTexture(files[0], material, (texture) => (material.baseDiffuseRoughnessTexture = texture));
14580
- }
14581
- } })] }));
15585
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeight", min: 0, max: 1, step: 0.01, description: "Controls how strong or visible the base aspect appears.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Base Color", target: material, propertyKey: "baseColor", isLinearMode: true, description: "Sets the primary surface color of the material.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Color", target: material, propertyKey: "baseColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Metalness", target: material, propertyKey: "baseMetalness", min: 0, max: 1, step: 0.01, description: "Controls whether the material behaves as metal or non-metal. The parameter supersedes transmission_weight and subsurface_weight.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Metalness", target: material, propertyKey: "baseMetalnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughness", min: 0, max: 1, step: 0.01, description: "Softens the surface's base appearance. Higher values create matte or porous looks. Lower values are smoother.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Ambient Occlusion", target: material, propertyKey: "ambientOcclusionTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 })] }));
14582
15586
  };
14583
15587
  /**
14584
15588
  * Displays the specular layer properties of an OpenPBR material.
@@ -14587,47 +15591,15 @@ const OpenPBRMaterialBaseProperties = (props) => {
14587
15591
  */
14588
15592
  const OpenPBRMaterialSpecularProperties = (props) => {
14589
15593
  const { material } = props;
14590
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Weight", target: material, propertyKey: "specularWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Specular Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14591
- if (files.length > 0) {
14592
- UpdateTexture(files[0], material, (texture) => (material.specularWeightTexture = texture));
14593
- }
14594
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Specular Color", target: material, propertyKey: "specularColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Specular Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14595
- if (files.length > 0) {
14596
- UpdateTexture(files[0], material, (texture) => (material.specularColorTexture = texture));
14597
- }
14598
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness", target: material, propertyKey: "specularRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Specular Roughness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14599
- if (files.length > 0) {
14600
- UpdateTexture(files[0], material, (texture) => (material.specularRoughnessTexture = texture));
14601
- }
14602
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropy", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Specular Roughness Anisotropy", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14603
- if (files.length > 0) {
14604
- UpdateTexture(files[0], material, (texture) => (material.specularRoughnessAnisotropyTexture = texture));
14605
- }
14606
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular IOR", target: material, propertyKey: "specularIor", min: 1, max: 3, step: 0.01 })] }));
15594
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Weight", target: material, propertyKey: "specularWeight", min: 0, max: 1, step: 0.01, description: "Controls how strong the reflections appear.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Specular Weight", target: material, propertyKey: "specularWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Specular Color", target: material, propertyKey: "specularColor", isLinearMode: true, description: "Tints the color of reflections.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Specular Color", target: material, propertyKey: "specularColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness", target: material, propertyKey: "specularRoughness", min: 0, max: 1, step: 0.01, description: "Controls how sharp or blurry reflections are.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Specular Roughness", target: material, propertyKey: "specularRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropy", min: 0, max: 1, step: 0.01, description: "Stretches reflections in one direction for brushed or streaked looks. Requires specular_roughness > 0.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/microfacetmodel" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropyTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular IOR", target: material, propertyKey: "specularIor", min: 1, max: 3, step: 0.01, description: "Index of refraction is a physical value controlling the reflective intensity and refraction. The parameter has no effect on metals.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate" })] }));
14607
15595
  };
14608
15596
  const OpenPBRMaterialTransmissionProperties = (props) => {
14609
15597
  const { material } = props;
14610
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Weight", target: material, propertyKey: "transmissionWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Transmission Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14611
- if (files.length > 0) {
14612
- UpdateTexture(files[0], material, (texture) => (material.transmissionWeightTexture = texture));
14613
- }
14614
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Color", target: material, propertyKey: "transmissionColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Transmission Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14615
- if (files.length > 0) {
14616
- UpdateTexture(files[0], material, (texture) => (material.transmissionColorTexture = texture));
14617
- }
14618
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Depth", target: material, propertyKey: "transmissionDepth", min: 0, step: 0.01 }), jsx(FileUploadLine, { label: "Transmission Depth", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14619
- if (files.length > 0) {
14620
- UpdateTexture(files[0], material, (texture) => (material.transmissionDepthTexture = texture));
14621
- }
14622
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Scatter", target: material, propertyKey: "transmissionScatter", isLinearMode: true }), jsx(FileUploadLine, { label: "Transmission Scatter", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14623
- if (files.length > 0) {
14624
- UpdateTexture(files[0], material, (texture) => (material.transmissionScatterTexture = texture));
14625
- }
14626
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Scatter Anisotropy", target: material, propertyKey: "transmissionScatterAnisotropy", min: -1, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Abbe Number", target: material, propertyKey: "transmissionDispersionAbbeNumber", min: 1, max: 100, step: 1 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Scale", target: material, propertyKey: "transmissionDispersionScale", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Transmission Dispersion Scale", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14627
- if (files.length > 0) {
14628
- UpdateTexture(files[0], material, (texture) => (material.transmissionDispersionScaleTexture = texture));
14629
- }
14630
- } })] }));
15598
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Weight", target: material, propertyKey: "transmissionWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the transparency effect. The parameter is superseded by base_metalness.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Weight", target: material, propertyKey: "transmissionWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Color", target: material, propertyKey: "transmissionColor", isLinearMode: true, description: "Tints light passing through the material. Works with transmission_depth for realistic thickness-based coloring.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Color", target: material, propertyKey: "transmissionColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Depth (cm)", target: material, propertyKey: "transmissionDepth", min: 0, max: 5, step: 0.001, convertTo: (value) => value * 100, convertFrom: (value) => value / 100, description: "Controls how quickly light is absorbed with thickness. Distance is in scene units.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Depth", target: material, propertyKey: "transmissionDepthTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Transmission Scatter", target: material, propertyKey: "transmissionScatter", isLinearMode: true, description: "Adds internal cloudiness to create materials like juice, honey, etc. Requires transmission_depth > 0.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Scatter", target: material, propertyKey: "transmissionScatterTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Scatter Anisotropy", target: material, propertyKey: "transmissionScatterAnisotropy", min: -1, max: 1, step: 0.01, description: "Shifts scattering forward/backward for clearer or hazier appearance depending on viewing angle.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Abbe Number", target: material, propertyKey: "transmissionDispersionAbbeNumber", min: 1, max: 100, step: 1, description: "Physical value for the rainbow color separation in refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Transmission Dispersion Scale", target: material, propertyKey: "transmissionDispersionScale", min: 0, max: 1, step: 0.01, description: "Strength of rainbow color separation in refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/translucentbase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Transmission Dispersion Scale", target: material, propertyKey: "transmissionDispersionScaleTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 })] }));
15599
+ };
15600
+ const OpenPBRMaterialSubsurfaceProperties = (props) => {
15601
+ const { material } = props;
15602
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Subsurface Weight", target: material, propertyKey: "subsurfaceWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the subsurface effect. The parameter is superseded by base_metalness and transmission_weight.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Subsurface Weight", target: material, propertyKey: "subsurfaceWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Subsurface Color", target: material, propertyKey: "subsurfaceColor", isLinearMode: true, description: "Colors the light that scatters under the surface.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Subsurface Color", target: material, propertyKey: "subsurfaceColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Subsurface Radius (cm)", target: material, propertyKey: "subsurfaceRadius", min: 0, max: 4, step: 0.001, convertTo: (value) => value * 100, convertFrom: (value) => value / 100, description: "Controls how soft and spread-out the subsurface look appears.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Subsurface Radius Scale", target: material, propertyKey: "subsurfaceRadiusScale", isLinearMode: true, description: "Tints thin areas with light shining through, like warm glow on ears or leaves.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Subsurface Radius Scale", target: material, propertyKey: "subsurfaceRadiusScaleTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Subsurface Scatter Anisotropy", target: material, propertyKey: "subsurfaceScatterAnisotropy", min: -1, max: 1, step: 0.01, description: "Shifts scattering forward/backward for a softer glow or a sharper one.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/basesubstrate/subsurface" })] }));
14631
15603
  };
14632
15604
  /**
14633
15605
  * Displays the coat layer properties of an OpenPBR material.
@@ -14636,27 +15608,7 @@ const OpenPBRMaterialTransmissionProperties = (props) => {
14636
15608
  */
14637
15609
  const OpenPBRMaterialCoatProperties = (props) => {
14638
15610
  const { material } = props;
14639
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14640
- if (files.length > 0) {
14641
- UpdateTexture(files[0], material, (texture) => (material.coatWeightTexture = texture));
14642
- }
14643
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Coat Color", target: material, propertyKey: "coatColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Coat Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14644
- if (files.length > 0) {
14645
- UpdateTexture(files[0], material, (texture) => (material.coatColorTexture = texture));
14646
- }
14647
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Roughness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14648
- if (files.length > 0) {
14649
- UpdateTexture(files[0], material, (texture) => (material.coatRoughnessTexture = texture));
14650
- }
14651
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropy", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Roughness Anisotropy", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14652
- if (files.length > 0) {
14653
- UpdateTexture(files[0], material, (texture) => (material.coatRoughnessAnisotropyTexture = texture));
14654
- }
14655
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat IOR", target: material, propertyKey: "coatIor", min: 1, max: 3, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkening", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Darkening", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14656
- if (files.length > 0) {
14657
- UpdateTexture(files[0], material, (texture) => (material.coatDarkeningTexture = texture));
14658
- }
14659
- } })] }));
15611
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the coat.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Coat Color", target: material, propertyKey: "coatColor", isLinearMode: true, description: "Tints the coat, for tinted varnish or paint.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Color", target: material, propertyKey: "coatColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughness", min: 0, max: 1, step: 0.01, description: "Controls how sharp or blurry the coat reflections appear.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat/roughening" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropy", min: 0, max: 1, step: 0.01, description: "Stretches coat reflections in one direction for brushed or streaked looks. Requires coat_roughness > 0.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropyTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat IOR", target: material, propertyKey: "coatIor", min: 1, max: 3, step: 0.01, description: "Index of refraction is a physical value controlling the reflective intensity of the coat.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkening", min: 0, max: 1, step: 0.01, description: "Darkens the base under the coat, similar to how real varnish deepens color.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat/darkening" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkeningTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 })] }));
14660
15612
  };
14661
15613
  /**
14662
15614
  * Displays the fuzz layer properties of an OpenPBR material.
@@ -14665,19 +15617,7 @@ const OpenPBRMaterialCoatProperties = (props) => {
14665
15617
  */
14666
15618
  const OpenPBRMaterialFuzzProperties = (props) => {
14667
15619
  const { material } = props;
14668
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Fuzz Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14669
- if (files.length > 0) {
14670
- UpdateTexture(files[0], material, (texture) => (material.fuzzWeightTexture = texture));
14671
- }
14672
- } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Fuzz Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14673
- if (files.length > 0) {
14674
- UpdateTexture(files[0], material, (texture) => (material.fuzzColorTexture = texture));
14675
- }
14676
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Fuzz Roughness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14677
- if (files.length > 0) {
14678
- UpdateTexture(files[0], material, (texture) => (material.fuzzRoughnessTexture = texture));
14679
- }
14680
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Number of Samples", target: material, propertyKey: "fuzzSampleNumber", min: 4, max: 64, step: 1 })] }));
15620
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the fuzz.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/fuzz" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColor", isLinearMode: true, description: "Controls the color of the fuzz.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/fuzz" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughness", min: 0, max: 1, step: 0.01, description: "Controls the roughness of the fuzz.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/fuzz" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughnessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Number of Samples", target: material, propertyKey: "fuzzSampleNumber", min: 4, max: 64, step: 1 })] }));
14681
15621
  };
14682
15622
  /**
14683
15623
  * Displays the emission properties of an OpenPBR material.
@@ -14686,11 +15626,7 @@ const OpenPBRMaterialFuzzProperties = (props) => {
14686
15626
  */
14687
15627
  const OpenPBRMaterialEmissionProperties = (props) => {
14688
15628
  const { material } = props;
14689
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color3PropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Emission Color", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14690
- if (files.length > 0) {
14691
- UpdateTexture(files[0], material, (texture) => (material.emissionColorTexture = texture));
14692
- }
14693
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Emission Luminance", target: material, propertyKey: "emissionLuminance", min: 0, max: 10, step: 0.01 })] }));
15629
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color3PropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColor", isLinearMode: true, description: "Controls the color of the glow.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/emission" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColorTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Emission Luminance", target: material, propertyKey: "emissionLuminance", min: 0, max: 10, step: 0.01, description: "Controls how bright the glow is.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/emission" })] }));
14694
15630
  };
14695
15631
  /**
14696
15632
  * Displays the thin film properties of an OpenPBR material.
@@ -14699,15 +15635,7 @@ const OpenPBRMaterialEmissionProperties = (props) => {
14699
15635
  */
14700
15636
  const OpenPBRMaterialThinFilmProperties = (props) => {
14701
15637
  const { material } = props;
14702
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Thin Film Weight", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14703
- if (files.length > 0) {
14704
- UpdateTexture(files[0], material, (texture) => (material.thinFilmWeightTexture = texture));
14705
- }
14706
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThickness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Thin Film Thickness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14707
- if (files.length > 0) {
14708
- UpdateTexture(files[0], material, (texture) => (material.thinFilmThicknessTexture = texture));
14709
- }
14710
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film IOR", target: material, propertyKey: "thinFilmIor", min: 1, max: 3, step: 0.01 })] }));
15638
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeight", min: 0, max: 1, step: 0.01, description: "Controls the presence of the thin-film.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-filmiridescence" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeightTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThickness", min: 0, max: 1, step: 0.01, description: "Changes the color pattern of the iridescence.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-filmiridescence" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThicknessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film IOR", target: material, propertyKey: "thinFilmIor", min: 1, max: 3, step: 0.01, description: "Alters the strength and contrast of the color shift based in the index of refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-filmiridescence" })] }));
14711
15639
  };
14712
15640
  /**
14713
15641
  * Displays the geometry properties of an OpenPBR material.
@@ -14716,31 +15644,7 @@ const OpenPBRMaterialThinFilmProperties = (props) => {
14716
15644
  */
14717
15645
  const OpenPBRMaterialGeometryProperties = (props) => {
14718
15646
  const { material } = props;
14719
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Opacity", target: material, propertyKey: "geometryOpacity", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Opacity", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14720
- if (files.length > 0) {
14721
- UpdateTexture(files[0], material, (texture) => (material.geometryOpacityTexture = texture));
14722
- }
14723
- } }), jsx(FileUploadLine, { label: "Geometry Normal", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14724
- if (files.length > 0) {
14725
- UpdateTexture(files[0], material, (texture) => (material.geometryNormalTexture = texture));
14726
- }
14727
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Tangent Angle", target: material, propertyKey: "geometryTangentAngle", min: 0, max: Math.PI, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Tangent", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14728
- if (files.length > 0) {
14729
- UpdateTexture(files[0], material, (texture) => (material.geometryTangentTexture = texture));
14730
- }
14731
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Tangent Angle", target: material, propertyKey: "geometryCoatTangentAngle", min: 0, max: Math.PI, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Coat Normal", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14732
- if (files.length > 0) {
14733
- UpdateTexture(files[0], material, (texture) => (material.geometryCoatNormalTexture = texture));
14734
- }
14735
- } }), jsx(FileUploadLine, { label: "Geometry Coat Tangent", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14736
- if (files.length > 0) {
14737
- UpdateTexture(files[0], material, (texture) => (material.geometryCoatTangentTexture = texture));
14738
- }
14739
- } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Geometry Thickness", target: material, propertyKey: "geometryThickness", min: 0, step: 0.1 }), jsx(FileUploadLine, { label: "Geometry Thickness", accept: ".jpg, .png, .webp, .tga, .dds, .env, .exr", onClick: (files) => {
14740
- if (files.length > 0) {
14741
- UpdateTexture(files[0], material, (texture) => (material.geometryThicknessTexture = texture));
14742
- }
14743
- } })] }));
15647
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Geometry Opacity", target: material, propertyKey: "geometryOpacity", min: 0, max: 1, step: 0.01, description: "Controls material presence and transparency cutout.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/opacity/transparency" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Opacity", target: material, propertyKey: "geometryOpacityTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: CheckboxPropertyLine, label: "Thin-Walled", target: material, propertyKey: "geometryThinWalled", description: "When enabled, treats material as a thin shell (like leaves, paper sheets or windows). Disables ray bending in refraction.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thin-walledcase" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Normal", target: material, propertyKey: "geometryNormalTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Tangent Angle", target: material, propertyKey: "geometryTangentAngle", min: 0, max: Math.PI, step: 0.01, description: "Tangent vector controlling anisotropic reflection direction for the base (metal and non-metal). Works with specular_roughness_anisotropy.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/geometry/tangent" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Tangent", target: material, propertyKey: "geometryTangentTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Tangent Angle", target: material, propertyKey: "geometryCoatTangentAngle", min: 0, max: Math.PI, step: 0.01, description: "Tangent vector controlling anisotropic reflection direction for the coat. Works with coat_roughness_anisotropy.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/geometry/coat-tangent" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Coat Normal", target: material, propertyKey: "geometryCoatNormalTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Coat Tangent", target: material, propertyKey: "geometryCoatTangentTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Geometry Thickness", target: material, propertyKey: "geometryThickness", min: 0, step: 0.001, description: "Controls the thickness of the geometry for volume approximations.", docLink: "https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/thickness" }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Geometry Thickness", target: material, propertyKey: "geometryThicknessTexture", scene: material.getScene(), defaultValue: null, onLink: (texture) => void 0 })] }));
14744
15648
  };
14745
15649
 
14746
15650
  const LightFalloffOptions = [
@@ -15101,6 +16005,10 @@ const MaterialPropertiesServiceDefinition = {
15101
16005
  section: "OpenPBR",
15102
16006
  component: ({ context }) => jsx(OpenPBRMaterialTransmissionProperties, { material: context }),
15103
16007
  },
16008
+ {
16009
+ section: "OpenPBR",
16010
+ component: ({ context }) => jsx(OpenPBRMaterialSubsurfaceProperties, { material: context }),
16011
+ },
15104
16012
  {
15105
16013
  section: "OpenPBR",
15106
16014
  component: ({ context }) => jsx(OpenPBRMaterialCoatProperties, { material: context }),
@@ -17172,42 +18080,6 @@ const PhysicsPropertiesServiceDefinition = {
17172
18080
  },
17173
18081
  };
17174
18082
 
17175
- const useCheckboxStyles = makeStyles({
17176
- indicator: {
17177
- margin: 0,
17178
- },
17179
- });
17180
- /**
17181
- * This is a primitive fluent checkbox that can both read and write checked state
17182
- * @param props
17183
- * @returns Checkbox component
17184
- */
17185
- const Checkbox = (props) => {
17186
- Checkbox.displayName = "Checkbox";
17187
- const [checked, setChecked] = useState(() => props.value ?? false);
17188
- const classes = useCheckboxStyles();
17189
- useEffect(() => {
17190
- if (props.value != undefined) {
17191
- setChecked(props.value); // Update local state when props.checked changes
17192
- }
17193
- }, [props.value]);
17194
- const onChange = (ev, _) => {
17195
- props.onChange(ev.target.checked);
17196
- setChecked(ev.target.checked);
17197
- };
17198
- return jsx(Checkbox$1, { checked: checked, onChange: onChange, indicator: { className: classes.indicator } });
17199
- };
17200
-
17201
- /**
17202
- * Wraps a checkbox in a property line
17203
- * @param props - PropertyLineProps and CheckboxProps
17204
- * @returns property-line wrapped checkbox
17205
- */
17206
- const CheckboxPropertyLine = (props) => {
17207
- CheckboxPropertyLine.displayName = "CheckboxPropertyLine";
17208
- return (jsx(PropertyLine, { ...props, children: jsx(Checkbox, { ...props }) }));
17209
- };
17210
-
17211
18083
  /**
17212
18084
  * The properties component for a post process.
17213
18085
  * @param props - The properties component props containing the post process.
@@ -19846,7 +20718,7 @@ const AdvancedDynamicTextureGeneralProperties = MakeLazyComponent(async () => {
19846
20718
  const AdvancedDynamicTexturePreviewProperties = (props) => {
19847
20719
  const { texture } = props;
19848
20720
  return (jsx(Fragment, { children: jsx(ButtonLine, { label: "Edit GUI", icon: EditRegular, onClick: async () => {
19849
- const { GUIEditor } = await import('@babylonjs/gui-editor/guiEditor.js');
20721
+ const { GUIEditor } = await import('@babylonjs/gui-editor');
19850
20722
  await GUIEditor.Show({ liveGuiTexture: texture });
19851
20723
  } }) }));
19852
20724
  };
@@ -20551,7 +21423,7 @@ const GuiExplorerServiceDefinition = {
20551
21423
  icon: () => jsx(EditRegular, {}),
20552
21424
  // eslint-disable-next-line @typescript-eslint/naming-convention
20553
21425
  execute: async () => {
20554
- const { GUIEditor } = await import('@babylonjs/gui-editor/guiEditor.js');
21426
+ const { GUIEditor } = await import('@babylonjs/gui-editor');
20555
21427
  await GUIEditor.Show({ liveGuiTexture: texture });
20556
21428
  },
20557
21429
  };
@@ -22117,6 +22989,11 @@ function ShowInspector(scene, options = {}) {
22117
22989
  });
22118
22990
  // This array will contain all the default Inspector service definitions.
22119
22991
  const serviceDefinitions = [];
22992
+ // Ensure the inspectable bridge is running for this scene. The inspector's
22993
+ // ServiceContainer will use the inspectable container as a parent, inheriting
22994
+ // services like ISceneContext and IInspectableCommandRegistry.
22995
+ const inspectableToken = _StartInspectable(scene);
22996
+ disposeActions.push(() => inspectableToken.dispose());
22120
22997
  // Create a container element for the inspector UI.
22121
22998
  // This element will become the root React node, so it must be a new empty node
22122
22999
  // since React will completely take over its contents.
@@ -22166,18 +23043,6 @@ function ShowInspector(scene, options = {}) {
22166
23043
  disposeActions.push(() => {
22167
23044
  parentElement.removeChild(containerElement);
22168
23045
  });
22169
- // This service exposes the scene that was passed into Inspector through ISceneContext, which is used by other services that may be used in other contexts outside of Inspector.
22170
- const sceneContextServiceDefinition = {
22171
- friendlyName: "Inspector Scene Context",
22172
- produces: [SceneContextIdentity],
22173
- factory: () => {
22174
- return {
22175
- currentScene: scene,
22176
- currentSceneObservable: new Observable(),
22177
- };
22178
- },
22179
- };
22180
- serviceDefinitions.push(sceneContextServiceDefinition);
22181
23046
  if (options.autoResizeEngine) {
22182
23047
  const observer = scene.onBeforeRenderObservable.add(() => scene.getEngine().resize());
22183
23048
  disposeActions.push(() => observer.remove());
@@ -22213,6 +23078,8 @@ function ShowInspector(scene, options = {}) {
22213
23078
  HighlightServiceDefinition,
22214
23079
  // Adds entry points for user feedback on Inspector v2 (probably eventually will be removed).
22215
23080
  UserFeedbackServiceDefinition,
23081
+ // Shows CLI bridge connection status in the toolbar.
23082
+ CliConnectionStatusServiceDefinition,
22216
23083
  // Adds always present "mini stats" (like fps) to the toolbar, etc.
22217
23084
  MiniStatsServiceDefinition,
22218
23085
  // Legacy service to support custom inspectable properties on objects.
@@ -22220,6 +23087,7 @@ function ShowInspector(scene, options = {}) {
22220
23087
  const modularTool = MakeModularTool({
22221
23088
  namespace: "Inspector",
22222
23089
  containerElement,
23090
+ parentContainer: inspectableToken.serviceContainer,
22223
23091
  serviceDefinitions: [
22224
23092
  // Default Inspector services.
22225
23093
  ...serviceDefinitions,
@@ -23054,5 +23922,5 @@ const TextAreaPropertyLine = (props) => {
23054
23922
  // Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
23055
23923
  AttachDebugLayer();
23056
23924
 
23057
- export { GizmoServiceIdentity as $, Accordion as A, Button as B, CheckboxPropertyLine as C, ColorPickerPopup as D, ColorStepGradientComponent as E, ComboBox as F, ComboBoxPropertyLine as G, ConstructorFactory as H, ConvertOptions as I, DebugServiceIdentity as J, DetachDebugLayer as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, DraggableLine as O, Popover as P, Dropdown as Q, EntitySelector as R, ShellServiceIdentity as S, TextInputPropertyLine as T, ErrorBoundary as U, Vector3PropertyLine as V, ExtensibleAccordion as W, FactorGradientComponent as X, FactorGradientList as Y, FileUploadLine as Z, GetPropertyDescriptor as _, useToast as a, Vector2PropertyLine as a$, HexPropertyLine as a0, InfoLabel as a1, InputHexField as a2, InputHsvField as a3, Inspector as a4, InterceptFunction as a5, InterceptProperty as a6, IsPropertyReadonly as a7, LineContainer as a8, LinkPropertyLine as a9, SettingsStoreIdentity as aA, ShowInspector as aB, SidePaneContainer as aC, SkeletonSelector as aD, Slider as aE, SpinButton as aF, StatsServiceIdentity as aG, StringDropdown as aH, StringDropdownPropertyLine as aI, StringifiedPropertyLine as aJ, Switch as aK, SwitchPropertyLine as aL, SyncedSliderInput as aM, SyncedSliderPropertyLine as aN, TeachingMoment as aO, TextAreaPropertyLine as aP, TextInput as aQ, TextPropertyLine as aR, Textarea as aS, TextureSelector as aT, TextureUpload as aU, Theme as aV, ThemeServiceIdentity as aW, ToastProvider as aX, ToggleButton as aY, Tooltip as aZ, UploadButton as a_, LinkToEntityPropertyLine as aa, List as ab, MakeDialogTeachingMoment as ac, MakeLazyComponent as ad, MakePopoverTeachingMoment as ae, MakePropertyHook as af, MakeTeachingMoment as ag, MaterialSelector as ah, NodeSelector as ai, NumberDropdown as aj, NumberDropdownPropertyLine as ak, ObservableCollection as al, Pane as am, PlaceholderPropertyLine as an, PositionedPopover as ao, PropertiesServiceIdentity as ap, Property as aq, PropertyContext as ar, PropertyLine as as, QuaternionPropertyLine as at, RotationVectorPropertyLine as au, SceneExplorerServiceIdentity as av, SearchBar as aw, SearchBox as ax, SelectionServiceDefinition as ay, SettingsServiceIdentity as az, useInterceptObservable as b, Vector4PropertyLine as b0, WatcherServiceIdentity as b1, useAngleConverters as b2, useAsyncResource as b3, useColor3Property as b4, useColor4Property as b5, useEventListener as b6, useEventfulState as b7, useKeyListener as b8, useKeyState as b9, useObservableCollection as ba, useOrderedObservableCollection as bb, usePollingObservable as bc, usePropertyChangedNotifier as bd, useQuaternionProperty as be, useResource as bf, useSetting as bg, useTheme as bh, useThemeMode as bi, useVector3Property as bj, 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, BuiltInsExtensionFeed as p, Checkbox as q, ChildWindow as r, Collapse as s, Color3GradientComponent as t, useExtensionManager as u, Color3GradientList as v, Color3PropertyLine as w, Color4GradientComponent as x, Color4GradientList as y, Color4PropertyLine as z };
23058
- //# sourceMappingURL=index-BCbXjPTn.js.map
23925
+ export { GizmoServiceIdentity as $, Accordion as A, Button as B, CheckboxPropertyLine as C, ColorPickerPopup as D, ColorStepGradientComponent as E, ComboBox as F, ComboBoxPropertyLine as G, ConstructorFactory as H, ConvertOptions as I, DebugServiceIdentity as J, DetachDebugLayer as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, DraggableLine as O, Popover as P, Dropdown as Q, EntitySelector as R, ShellServiceIdentity as S, TextInputPropertyLine as T, ErrorBoundary as U, Vector3PropertyLine as V, ExtensibleAccordion as W, FactorGradientComponent as X, FactorGradientList as Y, FileUploadLine as Z, GetPropertyDescriptor as _, useToast as a, Tooltip as a$, HexPropertyLine as a0, InfoLabel as a1, InputHexField as a2, InputHsvField as a3, InspectableCommandRegistryIdentity as a4, Inspector as a5, InterceptFunction as a6, InterceptProperty as a7, IsPropertyReadonly as a8, LineContainer as a9, SettingsServiceIdentity as aA, SettingsStoreIdentity as aB, ShowInspector as aC, SidePaneContainer as aD, SkeletonSelector as aE, Slider as aF, SpinButton as aG, StartInspectable as aH, StatsServiceIdentity as aI, StringDropdown as aJ, StringDropdownPropertyLine as aK, StringifiedPropertyLine as aL, Switch as aM, SwitchPropertyLine as aN, SyncedSliderInput as aO, SyncedSliderPropertyLine as aP, TeachingMoment as aQ, TextAreaPropertyLine as aR, TextInput as aS, TextPropertyLine as aT, Textarea as aU, TextureSelector as aV, TextureUpload as aW, Theme as aX, ThemeServiceIdentity as aY, ToastProvider as aZ, ToggleButton as a_, LinkPropertyLine as aa, LinkToEntityPropertyLine as ab, List as ac, MakeDialogTeachingMoment as ad, MakeLazyComponent as ae, MakePopoverTeachingMoment as af, MakePropertyHook as ag, MakeTeachingMoment as ah, MaterialSelector as ai, NodeSelector as aj, NumberDropdown as ak, NumberDropdownPropertyLine as al, ObservableCollection as am, Pane as an, PlaceholderPropertyLine as ao, PositionedPopover as ap, PropertiesServiceIdentity as aq, Property as ar, PropertyContext as as, PropertyLine as at, QuaternionPropertyLine as au, RotationVectorPropertyLine as av, SceneExplorerServiceIdentity as aw, SearchBar as ax, SearchBox as ay, SelectionServiceDefinition as az, useInterceptObservable as b, UploadButton as b0, Vector2PropertyLine as b1, Vector4PropertyLine as b2, WatcherServiceIdentity as b3, useAngleConverters as b4, useAsyncResource as b5, useColor3Property as b6, useColor4Property as b7, useEventListener as b8, useEventfulState as b9, useKeyListener as ba, useKeyState as bb, useObservableCollection as bc, useOrderedObservableCollection as bd, usePollingObservable as be, usePropertyChangedNotifier as bf, useQuaternionProperty as bg, useResource as bh, useSetting as bi, useTheme as bj, useThemeMode as bk, useVector3Property as bl, 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, BuiltInsExtensionFeed as p, Checkbox as q, ChildWindow as r, Collapse as s, Color3GradientComponent as t, useExtensionManager as u, Color3GradientList as v, Color3PropertyLine as w, Color4GradientComponent as x, Color4GradientList as y, Color4PropertyLine as z };
23926
+ //# sourceMappingURL=index-FWuITINA.js.map