@babylonjs/inspector 9.8.0 → 9.9.1
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.
- package/bin/inspector-bridge.mjs +10 -4
- package/bin/inspector-cli.mjs +9 -4
- package/lib/browser-CANgtOiM.js +1277 -0
- package/lib/browser-CANgtOiM.js.map +1 -0
- package/lib/{extensionsListService-DTrjNf_v.js → extensionsListService-Drj94Pma.js} +4 -2
- package/lib/{extensionsListService-DTrjNf_v.js.map → extensionsListService-Drj94Pma.js.map} +1 -1
- package/lib/{index-PYblOaAV.js → index-UoPnMkyH.js} +1700 -216
- package/lib/index-UoPnMkyH.js.map +1 -0
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/projects/overrideEntry.d.ts +36 -0
- package/lib/projects/overrideManager.d.ts +176 -0
- package/lib/projects/projectFile.d.ts +143 -0
- package/lib/{quickCreateToolsService-8d6rBO-A.js → quickCreateToolsService-CXUBvaDf.js} +4 -2
- package/lib/{quickCreateToolsService-8d6rBO-A.js.map → quickCreateToolsService-CXUBvaDf.js.map} +1 -1
- package/lib/{reflectorService-B7LcD1Sn.js → reflectorService-D3JRLq4p.js} +4 -2
- package/lib/{reflectorService-B7LcD1Sn.js.map → reflectorService-D3JRLq4p.js.map} +1 -1
- package/lib/services/overrideCaptureService.d.ts +16 -0
- package/lib/services/panes/{smartAssetsService.d.ts → babylonProjectAuthoringService.d.ts} +4 -2
- package/package.json +1 -1
- package/lib/index-PYblOaAV.js.map +0 -1
- package/lib/services/panes/tools/smartAssetToolsService.d.ts +0 -10
|
@@ -144,7 +144,9 @@ import { SceneRecorder } from '@babylonjs/core/Misc/sceneRecorder.js';
|
|
|
144
144
|
import { VideoRecorder } from '@babylonjs/core/Misc/videoRecorder.js';
|
|
145
145
|
import { SceneSerializer } from '@babylonjs/core/Misc/sceneSerializer.js';
|
|
146
146
|
import { EnvironmentTextureTools } from '@babylonjs/core/Misc/environmentTextureTools.js';
|
|
147
|
-
import { SerializeSmartAssetManagerMap, GetAllSmartAssets, RemoveSmartAssetAsync,
|
|
147
|
+
import { FindSmartAssetKeyForObject, SerializeSmartAssetManagerMap, GetSmartAssetTextureExtensions, GetAllSmartAssets, RemoveSmartAssetAsync, RegisterSmartAsset, LoadAllSmartAssetsAsync, LoadSmartAssetAsync, GetSmartAssetManager, LoadSmartAssetTextureAsync, ReloadSmartAssetAsync, UnloadSmartAssetAsync } from '@babylonjs/core/SmartAssets/smartAssetManager.js';
|
|
148
|
+
import '@babylonjs/core/Loading/Plugins/babylonFileLoader.js';
|
|
149
|
+
import { ReadJsonSourceAsync, ResolveAssetUrl, DeserializeSmartAssetMap } from '@babylonjs/core/SmartAssets/smartAssetSerializer.js';
|
|
148
150
|
import { ImportAnimationsAsync, SceneLoader } from '@babylonjs/core/Loading/sceneLoader.js';
|
|
149
151
|
import { FilesInput } from '@babylonjs/core/Misc/filesInput.js';
|
|
150
152
|
import { registeredGLTFExtensions } from '@babylonjs/loaders/glTF/2.0/glTFLoaderExtensionRegistry.js';
|
|
@@ -285,7 +287,7 @@ const Button = forwardRef((props, ref) => {
|
|
|
285
287
|
});
|
|
286
288
|
Button.displayName = "Button";
|
|
287
289
|
|
|
288
|
-
const useStyles$
|
|
290
|
+
const useStyles$10 = makeStyles({
|
|
289
291
|
root: {
|
|
290
292
|
display: "flex",
|
|
291
293
|
flexDirection: "column",
|
|
@@ -366,7 +368,7 @@ class ErrorBoundary extends Component {
|
|
|
366
368
|
}
|
|
367
369
|
}
|
|
368
370
|
function ErrorFallback({ error, onRetry }) {
|
|
369
|
-
const styles = useStyles$
|
|
371
|
+
const styles = useStyles$10();
|
|
370
372
|
return (jsxs("div", { className: styles.root, children: [jsx(ErrorCircleRegular, { className: styles.icon }), jsx("div", { className: styles.title, children: "Something went wrong" }), jsx("div", { className: styles.message, children: "An error occurred in this component. You can try again or continue using other parts of the tool." }), jsx(Button, { label: "Try Again", appearance: "primary", onClick: onRetry }), error && jsx("div", { className: styles.details, children: error.message })] }));
|
|
371
373
|
}
|
|
372
374
|
|
|
@@ -1367,7 +1369,7 @@ function useIsSectionEmpty(sectionId) {
|
|
|
1367
1369
|
return hasItems;
|
|
1368
1370
|
}
|
|
1369
1371
|
|
|
1370
|
-
const useStyles
|
|
1372
|
+
const useStyles$$ = makeStyles({
|
|
1371
1373
|
accordion: {
|
|
1372
1374
|
display: "flex",
|
|
1373
1375
|
flexDirection: "column",
|
|
@@ -1459,7 +1461,7 @@ const useStyles$10 = makeStyles({
|
|
|
1459
1461
|
*/
|
|
1460
1462
|
const AccordionMenuBar = () => {
|
|
1461
1463
|
AccordionMenuBar.displayName = "AccordionMenuBar";
|
|
1462
|
-
const classes = useStyles
|
|
1464
|
+
const classes = useStyles$$();
|
|
1463
1465
|
const accordionCtx = useContext(AccordionContext);
|
|
1464
1466
|
if (!accordionCtx) {
|
|
1465
1467
|
return null;
|
|
@@ -1483,7 +1485,7 @@ const AccordionMenuBar = () => {
|
|
|
1483
1485
|
const AccordionSectionBlock = (props) => {
|
|
1484
1486
|
AccordionSectionBlock.displayName = "AccordionSectionBlock";
|
|
1485
1487
|
const { children, sectionId } = props;
|
|
1486
|
-
const classes = useStyles
|
|
1488
|
+
const classes = useStyles$$();
|
|
1487
1489
|
const accordionCtx = useContext(AccordionContext);
|
|
1488
1490
|
const { context: sectionContext, isEmpty } = useAccordionSectionBlockContext(props);
|
|
1489
1491
|
if (accordionCtx) {
|
|
@@ -1503,7 +1505,7 @@ const AccordionSectionBlock = (props) => {
|
|
|
1503
1505
|
const AccordionSectionItem = (props) => {
|
|
1504
1506
|
AccordionSectionItem.displayName = "AccordionSectionItem";
|
|
1505
1507
|
const { children, staticItem } = props;
|
|
1506
|
-
const classes = useStyles
|
|
1508
|
+
const classes = useStyles$$();
|
|
1507
1509
|
const accordionCtx = useContext(AccordionContext);
|
|
1508
1510
|
const itemState = useAccordionSectionItemState(props);
|
|
1509
1511
|
const [ctrlMode, setCtrlMode] = useState(false);
|
|
@@ -1543,7 +1545,7 @@ const AccordionSectionItem = (props) => {
|
|
|
1543
1545
|
*/
|
|
1544
1546
|
const AccordionPinnedContainer = () => {
|
|
1545
1547
|
AccordionPinnedContainer.displayName = "AccordionPinnedContainer";
|
|
1546
|
-
const classes = useStyles
|
|
1548
|
+
const classes = useStyles$$();
|
|
1547
1549
|
const accordionCtx = useContext(AccordionContext);
|
|
1548
1550
|
return (jsx("div", { ref: accordionCtx?.pinnedContainerRef, className: classes.pinnedContainer, children: jsx(MessageBar$1, { className: classes.pinnedContainerEmpty, children: jsx(MessageBarBody, { children: "No pinned items" }) }) }));
|
|
1549
1551
|
};
|
|
@@ -1554,7 +1556,7 @@ const AccordionPinnedContainer = () => {
|
|
|
1554
1556
|
*/
|
|
1555
1557
|
const AccordionSearchBox = () => {
|
|
1556
1558
|
AccordionSearchBox.displayName = "AccordionSearchBox";
|
|
1557
|
-
const classes = useStyles
|
|
1559
|
+
const classes = useStyles$$();
|
|
1558
1560
|
const accordionCtx = useContext(AccordionContext);
|
|
1559
1561
|
if (!accordionCtx?.features.search) {
|
|
1560
1562
|
return null;
|
|
@@ -1570,7 +1572,7 @@ const AccordionSearchBox = () => {
|
|
|
1570
1572
|
*/
|
|
1571
1573
|
const AccordionSection = (props) => {
|
|
1572
1574
|
AccordionSection.displayName = "AccordionSection";
|
|
1573
|
-
const classes = useStyles
|
|
1575
|
+
const classes = useStyles$$();
|
|
1574
1576
|
return jsx("div", { className: classes.panelDiv, children: props.children });
|
|
1575
1577
|
};
|
|
1576
1578
|
const StringAccordion = Accordion$1;
|
|
@@ -1578,7 +1580,7 @@ const Accordion = forwardRef((props, ref) => {
|
|
|
1578
1580
|
Accordion.displayName = "Accordion";
|
|
1579
1581
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1580
1582
|
const { children, highlightSections, uniqueId, enablePinnedItems, enableHiddenItems, enableSearchItems, ...rest } = props;
|
|
1581
|
-
const classes = useStyles
|
|
1583
|
+
const classes = useStyles$$();
|
|
1582
1584
|
const { size } = useContext(ToolContext);
|
|
1583
1585
|
const accordionCtx = useAccordionContext(props);
|
|
1584
1586
|
const hasPinning = accordionCtx?.features.pinning ?? false;
|
|
@@ -1675,7 +1677,7 @@ const Collapse = (props) => {
|
|
|
1675
1677
|
return (jsx(Collapse$1, { visible: props.visible, orientation: props.orientation, unmountOnExit: true, children: jsx("div", { className: `${classes.collapseContent} ${props.orientation === "horizontal" ? classes.horizontal : classes.vertical}`, children: props.children }) }));
|
|
1676
1678
|
};
|
|
1677
1679
|
|
|
1678
|
-
const useStyles
|
|
1680
|
+
const useStyles$_ = makeStyles({
|
|
1679
1681
|
button: {
|
|
1680
1682
|
display: "flex",
|
|
1681
1683
|
alignItems: "center",
|
|
@@ -1693,7 +1695,7 @@ const ToggleButton = (props) => {
|
|
|
1693
1695
|
ToggleButton.displayName = "ToggleButton";
|
|
1694
1696
|
const { value, onChange, title, appearance = "subtle" } = props;
|
|
1695
1697
|
const { size } = useContext(ToolContext);
|
|
1696
|
-
const classes = useStyles
|
|
1698
|
+
const classes = useStyles$_();
|
|
1697
1699
|
const [checked, setChecked] = useState(value);
|
|
1698
1700
|
const toggle = useCallback(() => {
|
|
1699
1701
|
setChecked((prevChecked) => {
|
|
@@ -2002,7 +2004,7 @@ const UXContextProvider = (props) => {
|
|
|
2002
2004
|
function AsReadonlyArray(array) {
|
|
2003
2005
|
return array;
|
|
2004
2006
|
}
|
|
2005
|
-
const useStyles$
|
|
2007
|
+
const useStyles$Z = makeStyles({
|
|
2006
2008
|
rootDiv: {
|
|
2007
2009
|
flex: 1,
|
|
2008
2010
|
overflow: "hidden",
|
|
@@ -2017,7 +2019,7 @@ const useStyles$_ = makeStyles({
|
|
|
2017
2019
|
* @returns The extensible accordion component.
|
|
2018
2020
|
*/
|
|
2019
2021
|
function ExtensibleAccordion(props) {
|
|
2020
|
-
const classes = useStyles$
|
|
2022
|
+
const classes = useStyles$Z();
|
|
2021
2023
|
const { children, sections, sectionContent, context, sectionsRef, ...rest } = props;
|
|
2022
2024
|
const defaultSections = useMemo(() => {
|
|
2023
2025
|
const defaultSections = [];
|
|
@@ -2142,7 +2144,7 @@ function ExtensibleAccordion(props) {
|
|
|
2142
2144
|
})] }) })) }));
|
|
2143
2145
|
}
|
|
2144
2146
|
|
|
2145
|
-
const useStyles$
|
|
2147
|
+
const useStyles$Y = makeStyles({
|
|
2146
2148
|
paneRootDiv: {
|
|
2147
2149
|
display: "flex",
|
|
2148
2150
|
flex: 1,
|
|
@@ -2155,7 +2157,7 @@ const useStyles$Z = makeStyles({
|
|
|
2155
2157
|
*/
|
|
2156
2158
|
const SidePaneContainer = forwardRef((props, ref) => {
|
|
2157
2159
|
const { className, ...rest } = props;
|
|
2158
|
-
const classes = useStyles$
|
|
2160
|
+
const classes = useStyles$Y();
|
|
2159
2161
|
return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
|
|
2160
2162
|
});
|
|
2161
2163
|
|
|
@@ -2426,7 +2428,7 @@ function useTheme(invert = false) {
|
|
|
2426
2428
|
}
|
|
2427
2429
|
|
|
2428
2430
|
// Fluent doesn't apply styling to scrollbars by default, so provide our own reasonable default.
|
|
2429
|
-
const useStyles$
|
|
2431
|
+
const useStyles$X = makeStyles({
|
|
2430
2432
|
root: {
|
|
2431
2433
|
scrollbarColor: `${tokens.colorNeutralForeground3} ${tokens.colorTransparentBackground}`,
|
|
2432
2434
|
},
|
|
@@ -2442,11 +2444,11 @@ const Theme = (props) => {
|
|
|
2442
2444
|
// break any UI within the portal. Therefore, default to false.
|
|
2443
2445
|
const { invert = false, applyStylesToPortals = false, className, ...rest } = props;
|
|
2444
2446
|
const theme = useTheme(invert);
|
|
2445
|
-
const classes = useStyles$
|
|
2447
|
+
const classes = useStyles$X();
|
|
2446
2448
|
return (jsx(FluentProvider, { theme: theme, className: mergeClasses(classes.root, className), applyStylesToPortals: applyStylesToPortals, ...rest, children: props.children }));
|
|
2447
2449
|
};
|
|
2448
2450
|
|
|
2449
|
-
const useStyles$
|
|
2451
|
+
const useStyles$W = makeStyles({
|
|
2450
2452
|
extensionTeachingPopover: {
|
|
2451
2453
|
maxWidth: "320px",
|
|
2452
2454
|
},
|
|
@@ -2457,7 +2459,7 @@ const useStyles$X = makeStyles({
|
|
|
2457
2459
|
* @returns The teaching moment popover.
|
|
2458
2460
|
*/
|
|
2459
2461
|
const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
|
|
2460
|
-
const classes = useStyles$
|
|
2462
|
+
const classes = useStyles$W();
|
|
2461
2463
|
return (jsx(TeachingPopover, { appearance: "brand", open: shouldDisplay, positioning: { positioningRef }, onOpenChange: onOpenChange, children: jsxs(TeachingPopoverSurface, { className: classes.extensionTeachingPopover, children: [jsx(TeachingPopoverHeader, { children: title }), jsx(TeachingPopoverBody, { children: description })] }) }));
|
|
2462
2464
|
};
|
|
2463
2465
|
|
|
@@ -2890,7 +2892,7 @@ const RootComponentServiceIdentity = Symbol("RootComponent");
|
|
|
2890
2892
|
* The unique identity symbol for the shell service.
|
|
2891
2893
|
*/
|
|
2892
2894
|
const ShellServiceIdentity = Symbol("ShellService");
|
|
2893
|
-
const useStyles$
|
|
2895
|
+
const useStyles$V = makeStyles({
|
|
2894
2896
|
mainView: {
|
|
2895
2897
|
flex: 1,
|
|
2896
2898
|
display: "flex",
|
|
@@ -3103,14 +3105,14 @@ const DockMenu = (props) => {
|
|
|
3103
3105
|
};
|
|
3104
3106
|
const PaneHeader = (props) => {
|
|
3105
3107
|
const { id, title, dockOptions } = props;
|
|
3106
|
-
const classes = useStyles$
|
|
3108
|
+
const classes = useStyles$V();
|
|
3107
3109
|
return (jsxs("div", { className: classes.paneHeaderDiv, children: [props.icon && (jsx("div", { className: classes.paneHeaderIcon, children: jsx(props.icon, {}) })), jsx(Subtitle2Stronger, { className: mergeClasses(classes.paneHeaderText, !props.icon && classes.paneHeaderTextNoIcon), children: title }), jsx(DockMenu, { sidePaneId: id, dockOptions: dockOptions, children: jsx(Button$1, { className: classes.paneHeaderButton, appearance: "transparent", icon: jsx(MoreHorizontalRegular, {}) }) })] }));
|
|
3108
3110
|
};
|
|
3109
3111
|
// This is a wrapper for an item in a toolbar that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
|
|
3110
3112
|
const ToolbarItem = (props) => {
|
|
3111
3113
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
3112
3114
|
const { verticalLocation, horizontalLocation, id, component: Component, displayName } = props;
|
|
3113
|
-
const classes = useStyles$
|
|
3115
|
+
const classes = useStyles$V();
|
|
3114
3116
|
const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
|
|
3115
3117
|
const teachingMoment = useTeachingMoment(props.teachingMoment === false);
|
|
3116
3118
|
const title = typeof props.teachingMoment === "object" ? props.teachingMoment.title : (displayName ?? id);
|
|
@@ -3120,7 +3122,7 @@ const ToolbarItem = (props) => {
|
|
|
3120
3122
|
// TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
|
|
3121
3123
|
// This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
|
|
3122
3124
|
const Toolbar = ({ location, components }) => {
|
|
3123
|
-
const classes = useStyles$
|
|
3125
|
+
const classes = useStyles$V();
|
|
3124
3126
|
const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
|
|
3125
3127
|
const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
|
|
3126
3128
|
return (jsx(Fragment, { children: components.length > 0 && (jsxs("div", { className: `${classes.bar} ${location === "top" ? classes.barTop : classes.barBottom}`, children: [jsx("div", { className: classes.barLeft, children: leftComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) }), jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) })] })) }));
|
|
@@ -3130,7 +3132,7 @@ const SidePaneTab = (props) => {
|
|
|
3130
3132
|
const { location, id, isSelected, isFirst, isLast, dockOptions,
|
|
3131
3133
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
3132
3134
|
icon: Icon, title, } = props;
|
|
3133
|
-
const classes = useStyles$
|
|
3135
|
+
const classes = useStyles$V();
|
|
3134
3136
|
const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
|
|
3135
3137
|
const teachingMoment = useTeachingMoment(props.teachingMoment === false);
|
|
3136
3138
|
const tabClass = mergeClasses(classes.tab, isSelected ? classes.selectedTab : classes.unselectedTab, isFirst ? classes.firstTab : undefined, isLast ? classes.lastTab : undefined);
|
|
@@ -3142,7 +3144,7 @@ const SidePaneTab = (props) => {
|
|
|
3142
3144
|
// In "compact" mode, the tab list is integrated into the pane itself.
|
|
3143
3145
|
// In "full" mode, the returned tab list is later injected into the toolbar.
|
|
3144
3146
|
function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
|
|
3145
|
-
const classes = useStyles$
|
|
3147
|
+
const classes = useStyles$V();
|
|
3146
3148
|
const [topSelectedTab, setTopSelectedTab] = useState();
|
|
3147
3149
|
const [bottomSelectedTab, setBottomSelectedTab] = useState();
|
|
3148
3150
|
const [collapsed, setCollapsed] = useState(initialCollapsed);
|
|
@@ -3371,7 +3373,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
|
|
|
3371
3373
|
expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
|
|
3372
3374
|
};
|
|
3373
3375
|
const rootComponent = () => {
|
|
3374
|
-
const classes = useStyles$
|
|
3376
|
+
const classes = useStyles$V();
|
|
3375
3377
|
const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
|
|
3376
3378
|
// This function returns a promise that resolves after the dock change takes effect so that
|
|
3377
3379
|
// we can then select the re-docked pane.
|
|
@@ -3758,13 +3760,13 @@ function useImpulse() {
|
|
|
3758
3760
|
return [value, pulse];
|
|
3759
3761
|
}
|
|
3760
3762
|
|
|
3761
|
-
const useStyles$
|
|
3763
|
+
const useStyles$U = makeStyles({
|
|
3762
3764
|
placeholderDiv: {
|
|
3763
3765
|
padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
|
|
3764
3766
|
},
|
|
3765
3767
|
});
|
|
3766
3768
|
const PropertiesPane = (props) => {
|
|
3767
|
-
const classes = useStyles$
|
|
3769
|
+
const classes = useStyles$U();
|
|
3768
3770
|
const entity = props.context;
|
|
3769
3771
|
return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
|
|
3770
3772
|
};
|
|
@@ -4204,7 +4206,7 @@ function CoerceEntityArray(entities, sort) {
|
|
|
4204
4206
|
}
|
|
4205
4207
|
return entities;
|
|
4206
4208
|
}
|
|
4207
|
-
const useStyles$
|
|
4209
|
+
const useStyles$T = makeStyles({
|
|
4208
4210
|
rootDiv: {
|
|
4209
4211
|
flex: 1,
|
|
4210
4212
|
overflow: "hidden",
|
|
@@ -4313,14 +4315,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
|
|
|
4313
4315
|
}
|
|
4314
4316
|
const SceneTreeItem = (props) => {
|
|
4315
4317
|
const { isSelected, select } = props;
|
|
4316
|
-
const classes = useStyles$
|
|
4318
|
+
const classes = useStyles$T();
|
|
4317
4319
|
const [compactMode] = useSetting(CompactModeSettingDescriptor);
|
|
4318
4320
|
const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
|
|
4319
4321
|
return (jsx(FlatTreeItem, { className: classes.treeItem, value: "scene", itemType: "leaf", parentValue: undefined, "aria-level": 1, "aria-setsize": 1, "aria-posinset": 1, onClick: select, children: jsx(TreeItemLayout, { iconBefore: jsx(GlobeRegular, {}), className: treeItemLayoutClass, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, children: jsx(Body1Strong, { wrap: false, truncate: true, children: "Scene" }) }) }, "scene"));
|
|
4320
4322
|
};
|
|
4321
4323
|
const SectionTreeItem = (props) => {
|
|
4322
4324
|
const { section, isFiltering, commandProviders, expandAll, collapseAll, isDropTarget, ...dropProps } = props;
|
|
4323
|
-
const classes = useStyles$
|
|
4325
|
+
const classes = useStyles$T();
|
|
4324
4326
|
const [compactMode] = useSetting(CompactModeSettingDescriptor);
|
|
4325
4327
|
// Get the commands that apply to this section.
|
|
4326
4328
|
const commands = useResource(useCallback(() => {
|
|
@@ -4337,7 +4339,7 @@ const SectionTreeItem = (props) => {
|
|
|
4337
4339
|
};
|
|
4338
4340
|
const EntityTreeItem = (props) => {
|
|
4339
4341
|
const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll, isDragging, isDropTarget, ...dragProps } = props;
|
|
4340
|
-
const classes = useStyles$
|
|
4342
|
+
const classes = useStyles$T();
|
|
4341
4343
|
const [compactMode] = useSetting(CompactModeSettingDescriptor);
|
|
4342
4344
|
const hasChildren = !!entityItem.children?.length;
|
|
4343
4345
|
const displayInfo = useResource(useCallback(() => {
|
|
@@ -4453,7 +4455,7 @@ const EntityTreeItem = (props) => {
|
|
|
4453
4455
|
}, 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] }) })] }));
|
|
4454
4456
|
};
|
|
4455
4457
|
const SceneExplorer = (props) => {
|
|
4456
|
-
const classes = useStyles$
|
|
4458
|
+
const classes = useStyles$T();
|
|
4457
4459
|
const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity = null } = props;
|
|
4458
4460
|
const [openItems, setOpenItems] = useState(new Set());
|
|
4459
4461
|
const [sceneVersion, setSceneVersion] = useState(0);
|
|
@@ -6079,7 +6081,7 @@ class CanvasGraphService {
|
|
|
6079
6081
|
}
|
|
6080
6082
|
}
|
|
6081
6083
|
|
|
6082
|
-
const useStyles$
|
|
6084
|
+
const useStyles$S = makeStyles({
|
|
6083
6085
|
canvas: {
|
|
6084
6086
|
flexGrow: 1,
|
|
6085
6087
|
width: "100%",
|
|
@@ -6088,7 +6090,7 @@ const useStyles$T = makeStyles({
|
|
|
6088
6090
|
});
|
|
6089
6091
|
const CanvasGraph = (props) => {
|
|
6090
6092
|
const { collector, scene, layoutObservable, returnToPlayheadObservable, onVisibleRangeChangedObservable, initialGraphSize } = props;
|
|
6091
|
-
const classes = useStyles$
|
|
6093
|
+
const classes = useStyles$S();
|
|
6092
6094
|
const canvasRef = useRef(null);
|
|
6093
6095
|
useEffect(() => {
|
|
6094
6096
|
if (!canvasRef.current) {
|
|
@@ -6153,19 +6155,31 @@ function CoerceStepValue(step, isFineKeyPressed, isCourseKeyPressed) {
|
|
|
6153
6155
|
}
|
|
6154
6156
|
return step;
|
|
6155
6157
|
}
|
|
6156
|
-
//
|
|
6157
|
-
//
|
|
6158
|
-
//
|
|
6158
|
+
// Parse the raw input into a number, supporting plain numeric values and arbitrary math expressions
|
|
6159
|
+
// (e.g. "10*60" for 10 minutes in seconds).
|
|
6160
|
+
// First, try Number() so plain numeric input works even under a strict Content-Security-Policy that
|
|
6161
|
+
// disallows eval/Function. Only fall back to the Function constructor for non-numeric inputs that may
|
|
6162
|
+
// be expressions. Empty/whitespace input returns NaN so validateValue rejects it rather than committing
|
|
6163
|
+
// 0 (which is what Number("") would otherwise return).
|
|
6164
|
+
// Non-finite results (NaN, +/-Infinity) are rejected from both paths so callers don't have to handle them.
|
|
6159
6165
|
function EvaluateExpression(rawValue) {
|
|
6160
|
-
|
|
6166
|
+
rawValue = rawValue.trim();
|
|
6167
|
+
if (rawValue.length === 0) {
|
|
6168
|
+
return NaN;
|
|
6169
|
+
}
|
|
6170
|
+
const value = Number(rawValue);
|
|
6171
|
+
if (Number.isFinite(value)) {
|
|
6172
|
+
return value;
|
|
6173
|
+
}
|
|
6161
6174
|
try {
|
|
6162
|
-
|
|
6175
|
+
const result = Number(Function(`"use strict";return (${rawValue})`)());
|
|
6176
|
+
return Number.isFinite(result) ? result : NaN;
|
|
6163
6177
|
}
|
|
6164
6178
|
catch {
|
|
6165
6179
|
return NaN;
|
|
6166
6180
|
}
|
|
6167
6181
|
}
|
|
6168
|
-
const useStyles$
|
|
6182
|
+
const useStyles$R = makeStyles({
|
|
6169
6183
|
icon: {
|
|
6170
6184
|
"&:hover": {
|
|
6171
6185
|
color: tokens.colorBrandForeground1,
|
|
@@ -6179,7 +6193,7 @@ const useStyles$S = makeStyles({
|
|
|
6179
6193
|
const SpinButton = forwardRef((props, ref) => {
|
|
6180
6194
|
SpinButton.displayName = "SpinButton2";
|
|
6181
6195
|
const inputClasses = useInputStyles$1();
|
|
6182
|
-
const classes = useStyles$
|
|
6196
|
+
const classes = useStyles$R();
|
|
6183
6197
|
const { size } = useContext(ToolContext);
|
|
6184
6198
|
const { min, max } = props;
|
|
6185
6199
|
const baseStep = props.step ?? 1;
|
|
@@ -6446,7 +6460,7 @@ const Dropdown = (props) => {
|
|
|
6446
6460
|
const NumberDropdown = Dropdown;
|
|
6447
6461
|
const StringDropdown = Dropdown;
|
|
6448
6462
|
|
|
6449
|
-
const useStyles$
|
|
6463
|
+
const useStyles$Q = makeStyles({
|
|
6450
6464
|
surface: {
|
|
6451
6465
|
maxWidth: "400px",
|
|
6452
6466
|
},
|
|
@@ -6461,7 +6475,7 @@ const useStyles$R = makeStyles({
|
|
|
6461
6475
|
const Popover = forwardRef((props, ref) => {
|
|
6462
6476
|
const { children, open: controlledOpen, onOpenChange, positioning, surfaceClassName } = props;
|
|
6463
6477
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
6464
|
-
const classes = useStyles$
|
|
6478
|
+
const classes = useStyles$Q();
|
|
6465
6479
|
const isControlled = controlledOpen !== undefined;
|
|
6466
6480
|
const popoverOpen = isControlled ? controlledOpen : internalOpen;
|
|
6467
6481
|
const handleOpenChange = (_, data) => {
|
|
@@ -6705,7 +6719,7 @@ const InputAlphaField = (props) => {
|
|
|
6705
6719
|
} }));
|
|
6706
6720
|
};
|
|
6707
6721
|
|
|
6708
|
-
const useStyles$
|
|
6722
|
+
const useStyles$P = makeStyles({
|
|
6709
6723
|
sidebar: {
|
|
6710
6724
|
display: "flex",
|
|
6711
6725
|
flexDirection: "column",
|
|
@@ -6769,7 +6783,7 @@ const useStyles$Q = makeStyles({
|
|
|
6769
6783
|
});
|
|
6770
6784
|
const PerformanceSidebar = (props) => {
|
|
6771
6785
|
const { collector, onVisibleRangeChangedObservable } = props;
|
|
6772
|
-
const classes = useStyles$
|
|
6786
|
+
const classes = useStyles$P();
|
|
6773
6787
|
// Map from id to IPerfMetadata information
|
|
6774
6788
|
const [metadataMap, setMetadataMap] = useState();
|
|
6775
6789
|
// Map from category to all the ids belonging to that category
|
|
@@ -6842,7 +6856,7 @@ const PerformanceSidebar = (props) => {
|
|
|
6842
6856
|
})] }, `category-${category || "version"}`))) }));
|
|
6843
6857
|
};
|
|
6844
6858
|
|
|
6845
|
-
const useStyles$
|
|
6859
|
+
const useStyles$O = makeStyles({
|
|
6846
6860
|
container: {
|
|
6847
6861
|
display: "flex",
|
|
6848
6862
|
flexDirection: "row",
|
|
@@ -6871,7 +6885,7 @@ const useStyles$P = makeStyles({
|
|
|
6871
6885
|
});
|
|
6872
6886
|
const PerformanceViewer = (props) => {
|
|
6873
6887
|
const { scene, layoutObservable, returnToLiveObservable, performanceCollector, initialGraphSize } = props;
|
|
6874
|
-
const classes = useStyles$
|
|
6888
|
+
const classes = useStyles$O();
|
|
6875
6889
|
const [onVisibleRangeChangedObservable] = useState(() => new Observable());
|
|
6876
6890
|
const onReturnToPlayheadClick = () => {
|
|
6877
6891
|
returnToLiveObservable.notifyObservers();
|
|
@@ -7038,14 +7052,14 @@ const TextPropertyLine = (props) => {
|
|
|
7038
7052
|
return (jsx(PropertyLine, { ...props, children: jsx(Body1, { title: title, children: value ?? "" }) }));
|
|
7039
7053
|
};
|
|
7040
7054
|
|
|
7041
|
-
const useStyles$
|
|
7055
|
+
const useStyles$N = makeStyles({
|
|
7042
7056
|
pinnedStatsPane: {
|
|
7043
7057
|
flex: "0 1 auto",
|
|
7044
7058
|
paddingBottom: tokens.spacingHorizontalM,
|
|
7045
7059
|
},
|
|
7046
7060
|
});
|
|
7047
7061
|
const StatsPane = (props) => {
|
|
7048
|
-
const classes = useStyles$
|
|
7062
|
+
const classes = useStyles$N();
|
|
7049
7063
|
const scene = props.context;
|
|
7050
7064
|
const engine = scene.getEngine();
|
|
7051
7065
|
const pollingObservable = usePollingObservable(250);
|
|
@@ -7208,7 +7222,7 @@ const ToolsServiceDefinition = {
|
|
|
7208
7222
|
*/
|
|
7209
7223
|
const ReactContextServiceIdentity = Symbol("ReactContextService");
|
|
7210
7224
|
|
|
7211
|
-
const useStyles$
|
|
7225
|
+
const useStyles$M = makeStyles({
|
|
7212
7226
|
dropdown: {
|
|
7213
7227
|
...UniformWidthStyling,
|
|
7214
7228
|
},
|
|
@@ -7220,7 +7234,7 @@ const useStyles$N = makeStyles({
|
|
|
7220
7234
|
*/
|
|
7221
7235
|
const DropdownPropertyLine = forwardRef((props, ref) => {
|
|
7222
7236
|
DropdownPropertyLine.displayName = "DropdownPropertyLine";
|
|
7223
|
-
const classes = useStyles$
|
|
7237
|
+
const classes = useStyles$M();
|
|
7224
7238
|
return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
|
|
7225
7239
|
});
|
|
7226
7240
|
/**
|
|
@@ -7378,7 +7392,7 @@ const SyncedSliderInput = (props) => {
|
|
|
7378
7392
|
return (jsxs("div", { className: mergeClasses(classes.container, props.className), children: [infoLabel && jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [hasSlider && (jsx(Slider, { className: getSliderClassName(), value: value, onChange: handleSliderChange, min: props.min, max: props.max, step: props.step, disabled: props.disabled, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...passthroughProps, className: useCompactSizing ? classes.compactSpinButton : classes.spinButton, inputClassName: useCompactSizing ? classes.compactSpinButtonInput : classes.spinButtonInput, value: value, onChange: handleInputChange, step: props.step, disabled: props.disabled, disableDragButton: true })] })] }));
|
|
7379
7393
|
};
|
|
7380
7394
|
|
|
7381
|
-
const useStyles$
|
|
7395
|
+
const useStyles$L = makeStyles({
|
|
7382
7396
|
uniformWidth: {
|
|
7383
7397
|
...UniformWidthStyling,
|
|
7384
7398
|
},
|
|
@@ -7390,7 +7404,7 @@ const useStyles$M = makeStyles({
|
|
|
7390
7404
|
*/
|
|
7391
7405
|
const SyncedSliderPropertyLine = forwardRef((props, ref) => {
|
|
7392
7406
|
SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
|
|
7393
|
-
const classes = useStyles$
|
|
7407
|
+
const classes = useStyles$L();
|
|
7394
7408
|
const { label, description, ...sliderProps } = props;
|
|
7395
7409
|
return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps, className: mergeClasses(classes.uniformWidth, props.className) }) }));
|
|
7396
7410
|
});
|
|
@@ -8350,7 +8364,7 @@ function useExtensionManager() {
|
|
|
8350
8364
|
return useContext(ExtensionManagerContext)?.extensionManager;
|
|
8351
8365
|
}
|
|
8352
8366
|
|
|
8353
|
-
const useStyles$
|
|
8367
|
+
const useStyles$K = makeStyles({
|
|
8354
8368
|
themeButton: {
|
|
8355
8369
|
margin: 0,
|
|
8356
8370
|
},
|
|
@@ -8369,7 +8383,7 @@ const ThemeSelectorServiceDefinition = {
|
|
|
8369
8383
|
teachingMoment: false,
|
|
8370
8384
|
order: -300,
|
|
8371
8385
|
component: () => {
|
|
8372
|
-
const classes = useStyles$
|
|
8386
|
+
const classes = useStyles$K();
|
|
8373
8387
|
const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
|
|
8374
8388
|
const onSelectedThemeChange = useCallback((e, data) => {
|
|
8375
8389
|
setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
|
|
@@ -8386,7 +8400,7 @@ const ThemeSelectorServiceDefinition = {
|
|
|
8386
8400
|
},
|
|
8387
8401
|
};
|
|
8388
8402
|
|
|
8389
|
-
const useStyles$
|
|
8403
|
+
const useStyles$J = makeStyles({
|
|
8390
8404
|
app: {
|
|
8391
8405
|
colorScheme: "light dark",
|
|
8392
8406
|
flexGrow: 1,
|
|
@@ -8431,7 +8445,7 @@ function MakeModularTool(options) {
|
|
|
8431
8445
|
// This deferred resolves once the React effect cleanup (which disposes the ServiceContainer) is complete.
|
|
8432
8446
|
const disposeDeferred = new Deferred();
|
|
8433
8447
|
const modularToolRootComponent = () => {
|
|
8434
|
-
const classes = useStyles$
|
|
8448
|
+
const classes = useStyles$J();
|
|
8435
8449
|
const [extensionManagerContext, setExtensionManagerContext] = useState();
|
|
8436
8450
|
const [requiredExtensions, setRequiredExtensions] = useState();
|
|
8437
8451
|
const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
|
|
@@ -8518,7 +8532,7 @@ function MakeModularTool(options) {
|
|
|
8518
8532
|
}
|
|
8519
8533
|
// Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
|
|
8520
8534
|
if (extensionFeeds.length > 0) {
|
|
8521
|
-
const { ExtensionListServiceDefinition } = await import('./extensionsListService-
|
|
8535
|
+
const { ExtensionListServiceDefinition } = await import('./extensionsListService-Drj94Pma.js');
|
|
8522
8536
|
serviceContainer.addService(ExtensionListServiceDefinition);
|
|
8523
8537
|
}
|
|
8524
8538
|
// Register all external services (that make up a unique tool).
|
|
@@ -8639,14 +8653,14 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
|
|
|
8639
8653
|
description: "Adds a new panel for easy creation of various Babylon assets. This is a WIP extension...expect changes!",
|
|
8640
8654
|
keywords: ["creation", "tools"],
|
|
8641
8655
|
...BabylonWebResources,
|
|
8642
|
-
getExtensionModuleAsync: async () => await import('./quickCreateToolsService-
|
|
8656
|
+
getExtensionModuleAsync: async () => await import('./quickCreateToolsService-CXUBvaDf.js'),
|
|
8643
8657
|
},
|
|
8644
8658
|
{
|
|
8645
8659
|
name: "Reflector",
|
|
8646
8660
|
description: "Connects to the Reflector Bridge for real-time scene synchronization with the Babylon.js Sandbox.",
|
|
8647
8661
|
keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
|
|
8648
8662
|
...BabylonWebResources,
|
|
8649
|
-
getExtensionModuleAsync: async () => await import('./reflectorService-
|
|
8663
|
+
getExtensionModuleAsync: async () => await import('./reflectorService-D3JRLq4p.js'),
|
|
8650
8664
|
},
|
|
8651
8665
|
]);
|
|
8652
8666
|
|
|
@@ -9687,7 +9701,7 @@ const ColorSliders = ({ color, onSliderChange }) => (jsxs(Fragment, { children:
|
|
|
9687
9701
|
const Color3PropertyLine = ColorPropertyLine;
|
|
9688
9702
|
const Color4PropertyLine = ColorPropertyLine;
|
|
9689
9703
|
|
|
9690
|
-
const useStyles$
|
|
9704
|
+
const useStyles$I = makeStyles({
|
|
9691
9705
|
uniformWidth: {
|
|
9692
9706
|
...UniformWidthStyling,
|
|
9693
9707
|
},
|
|
@@ -9699,7 +9713,7 @@ const useStyles$J = makeStyles({
|
|
|
9699
9713
|
*/
|
|
9700
9714
|
const TextInputPropertyLine = (props) => {
|
|
9701
9715
|
TextInputPropertyLine.displayName = "TextInputPropertyLine";
|
|
9702
|
-
const classes = useStyles$
|
|
9716
|
+
const classes = useStyles$I();
|
|
9703
9717
|
return (jsx(PropertyLine, { ...props, children: jsx(TextInput, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
|
|
9704
9718
|
};
|
|
9705
9719
|
/**
|
|
@@ -9710,7 +9724,7 @@ const TextInputPropertyLine = (props) => {
|
|
|
9710
9724
|
*/
|
|
9711
9725
|
const NumberInputPropertyLine = (props) => {
|
|
9712
9726
|
NumberInputPropertyLine.displayName = "NumberInputPropertyLine";
|
|
9713
|
-
const classes = useStyles$
|
|
9727
|
+
const classes = useStyles$I();
|
|
9714
9728
|
return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
|
|
9715
9729
|
};
|
|
9716
9730
|
|
|
@@ -9842,7 +9856,7 @@ const LegacyInspectableObjectPropertiesServiceDefinition = {
|
|
|
9842
9856
|
};
|
|
9843
9857
|
|
|
9844
9858
|
const DocUrl = "https://www.npmjs.com/package/@babylonjs/inspector#inspector-cli";
|
|
9845
|
-
const useStyles$
|
|
9859
|
+
const useStyles$H = makeStyles({
|
|
9846
9860
|
tooltipContent: {
|
|
9847
9861
|
display: "flex",
|
|
9848
9862
|
flexDirection: "column",
|
|
@@ -9860,7 +9874,7 @@ const CliConnectionStatusServiceDefinition = {
|
|
|
9860
9874
|
teachingMoment: false,
|
|
9861
9875
|
order: 0 /* DefaultToolbarItemOrder.CliStatus */,
|
|
9862
9876
|
component: () => {
|
|
9863
|
-
const classes = useStyles$
|
|
9877
|
+
const classes = useStyles$H();
|
|
9864
9878
|
const isEnabled = useObservableState(() => cliConnectionStatus.isEnabled, cliConnectionStatus.onConnectionStatusChanged);
|
|
9865
9879
|
const isConnected = useObservableState(() => cliConnectionStatus.isConnected, cliConnectionStatus.onConnectionStatusChanged);
|
|
9866
9880
|
const { showToast } = useToast();
|
|
@@ -9921,7 +9935,7 @@ const BreakTangentIcon = createFluentIcon("BreakTangent", "20", '<g transform="s
|
|
|
9921
9935
|
const UnifyTangentIcon = createFluentIcon("UnifyTangent", "20", '<g transform="scale(0.5)"><path d="M27.94,18.28a1.49,1.49,0,0,0-1.41,1h-5l-1.62-1.63-1.62,1.63h-5a1.5,1.5,0,1,0,0,1h5l1.62,1.62,1.62-1.62h5a1.5,1.5,0,1,0,1.41-2Z"/></g>');
|
|
9922
9936
|
const StepTangentIcon = createFluentIcon("StepTangent", "20", '<g transform="scale(0.5)"><path d="M29,16.71a1.5,1.5,0,1,0-2,1.41v5.67H11v1H28V18.12A1.51,1.51,0,0,0,29,16.71Z"/></g>');
|
|
9923
9937
|
|
|
9924
|
-
const useStyles$
|
|
9938
|
+
const useStyles$G = makeStyles({
|
|
9925
9939
|
coordinatesModeButton: {
|
|
9926
9940
|
margin: `0 0 0 ${tokens.spacingHorizontalXS}`,
|
|
9927
9941
|
},
|
|
@@ -9937,7 +9951,7 @@ const useStyles$H = makeStyles({
|
|
|
9937
9951
|
});
|
|
9938
9952
|
const GizmoToolbar = (props) => {
|
|
9939
9953
|
const { gizmoService, sceneContext } = props;
|
|
9940
|
-
const classes = useStyles$
|
|
9954
|
+
const classes = useStyles$G();
|
|
9941
9955
|
const gizmoMode = useObservableState(() => gizmoService.gizmoMode, gizmoService.onGizmoModeChanged);
|
|
9942
9956
|
const coordinatesMode = useObservableState(() => gizmoService.coordinatesMode, gizmoService.onCoordinatesModeChanged);
|
|
9943
9957
|
const cameraGizmo = useObservableState(() => gizmoService.gizmoCamera, gizmoService.onCameraGizmoChanged);
|
|
@@ -10066,7 +10080,7 @@ const HighlightServiceDefinition = {
|
|
|
10066
10080
|
},
|
|
10067
10081
|
};
|
|
10068
10082
|
|
|
10069
|
-
const useStyles$
|
|
10083
|
+
const useStyles$F = makeStyles({
|
|
10070
10084
|
badge: {
|
|
10071
10085
|
margin: tokens.spacingHorizontalXXS,
|
|
10072
10086
|
fontFamily: "monospace",
|
|
@@ -10083,7 +10097,7 @@ const MiniStatsServiceDefinition = {
|
|
|
10083
10097
|
order: 300 /* DefaultToolbarItemOrder.FrameRate */,
|
|
10084
10098
|
teachingMoment: false,
|
|
10085
10099
|
component: () => {
|
|
10086
|
-
const classes = useStyles$
|
|
10100
|
+
const classes = useStyles$F();
|
|
10087
10101
|
const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
|
|
10088
10102
|
const engine = scene?.getEngine();
|
|
10089
10103
|
const pollingObservable = usePollingObservable(250);
|
|
@@ -10412,7 +10426,7 @@ function useCurveEditor() {
|
|
|
10412
10426
|
return context;
|
|
10413
10427
|
}
|
|
10414
10428
|
|
|
10415
|
-
const useStyles$
|
|
10429
|
+
const useStyles$E = makeStyles({
|
|
10416
10430
|
root: {
|
|
10417
10431
|
display: "flex",
|
|
10418
10432
|
flexDirection: "row",
|
|
@@ -10456,7 +10470,7 @@ const useStyles$F = makeStyles({
|
|
|
10456
10470
|
* @returns The top bar component
|
|
10457
10471
|
*/
|
|
10458
10472
|
const TopBar = () => {
|
|
10459
|
-
const styles = useStyles$
|
|
10473
|
+
const styles = useStyles$E();
|
|
10460
10474
|
const { state, observables } = useCurveEditor();
|
|
10461
10475
|
const [keyFrameValue, setKeyFrameValue] = useState(null);
|
|
10462
10476
|
const [keyValue, setKeyValue] = useState(null);
|
|
@@ -10549,7 +10563,7 @@ const ColorChannelColors = {
|
|
|
10549
10563
|
*/
|
|
10550
10564
|
const DefaultCurveColor = "#ffffff";
|
|
10551
10565
|
|
|
10552
|
-
const useStyles$
|
|
10566
|
+
const useStyles$D = makeStyles({
|
|
10553
10567
|
root: {
|
|
10554
10568
|
display: "flex",
|
|
10555
10569
|
flexDirection: "column",
|
|
@@ -10591,7 +10605,7 @@ const LOOP_MODES$1 = [
|
|
|
10591
10605
|
* @returns The edit animation panel component
|
|
10592
10606
|
*/
|
|
10593
10607
|
const EditAnimationPanel = ({ animation, onClose }) => {
|
|
10594
|
-
const styles = useStyles$
|
|
10608
|
+
const styles = useStyles$D();
|
|
10595
10609
|
const { observables } = useCurveEditor();
|
|
10596
10610
|
const [name, setName] = useState(animation.name);
|
|
10597
10611
|
const [property, setProperty] = useState(animation.targetProperty);
|
|
@@ -10622,7 +10636,7 @@ const EditAnimationPanel = ({ animation, onClose }) => {
|
|
|
10622
10636
|
return (jsxs("div", { className: styles.root, children: [jsxs("div", { className: styles.form, children: [jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Display Name" }), jsx(Input, { value: name, onChange: (_, data) => setName(data.value), placeholder: "Animation name" })] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Property" }), jsx(Input, { value: property, onChange: (_, data) => setProperty(data.value), placeholder: "e.g., position, rotation, scaling" })] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Loop Mode" }), jsx(Dropdown$1, { value: getLoopModeLabel(loopMode), selectedOptions: [loopMode.toString()], onOptionSelect: (_, data) => setLoopMode(Number(data.optionValue)), positioning: "below", inlinePopup: true, children: LOOP_MODES$1.map((mode) => (jsx(Option, { value: mode.value.toString(), children: mode.label }, mode.value))) })] })] }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: saveChanges, label: "Save", disabled: !isValid }), jsx(Button, { appearance: "subtle", onClick: onClose, label: "Cancel" })] })] }));
|
|
10623
10637
|
};
|
|
10624
10638
|
|
|
10625
|
-
const useStyles$
|
|
10639
|
+
const useStyles$C = makeStyles({
|
|
10626
10640
|
root: {
|
|
10627
10641
|
display: "flex",
|
|
10628
10642
|
flexDirection: "column",
|
|
@@ -10688,7 +10702,7 @@ const useStyles$D = makeStyles({
|
|
|
10688
10702
|
* @returns Animation entry component
|
|
10689
10703
|
*/
|
|
10690
10704
|
const AnimationEntry = ({ animation }) => {
|
|
10691
|
-
const styles = useStyles$
|
|
10705
|
+
const styles = useStyles$C();
|
|
10692
10706
|
const { state, actions, observables } = useCurveEditor();
|
|
10693
10707
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
10694
10708
|
const [isHovered, setIsHovered] = useState(false);
|
|
@@ -10767,7 +10781,7 @@ const AnimationEntry = ({ animation }) => {
|
|
|
10767
10781
|
* @returns Animation sub-entry component
|
|
10768
10782
|
*/
|
|
10769
10783
|
const AnimationSubEntry = ({ animation, subName, color }) => {
|
|
10770
|
-
const styles = useStyles$
|
|
10784
|
+
const styles = useStyles$C();
|
|
10771
10785
|
const { actions, observables } = useCurveEditor();
|
|
10772
10786
|
const activeChannel = actions.getActiveChannel(animation);
|
|
10773
10787
|
const isThisChannelActive = activeChannel === color;
|
|
@@ -10790,7 +10804,7 @@ const AnimationSubEntry = ({ animation, subName, color }) => {
|
|
|
10790
10804
|
* @returns Animation list component
|
|
10791
10805
|
*/
|
|
10792
10806
|
const AnimationList = () => {
|
|
10793
|
-
const styles = useStyles$
|
|
10807
|
+
const styles = useStyles$C();
|
|
10794
10808
|
const { state, observables } = useCurveEditor();
|
|
10795
10809
|
// Re-render when animations are loaded or changed (e.g. animation deleted)
|
|
10796
10810
|
// useCallback stabilizes the accessor to prevent infinite re-render loops
|
|
@@ -10803,7 +10817,7 @@ const AnimationList = () => {
|
|
|
10803
10817
|
}) }));
|
|
10804
10818
|
};
|
|
10805
10819
|
|
|
10806
|
-
const useStyles$
|
|
10820
|
+
const useStyles$B = makeStyles({
|
|
10807
10821
|
root: {
|
|
10808
10822
|
display: "flex",
|
|
10809
10823
|
flexDirection: "column",
|
|
@@ -10852,7 +10866,7 @@ const LoopModeOptions = LOOP_MODES.map((lm) => ({ label: lm, value: lm }));
|
|
|
10852
10866
|
* @returns The add animation panel component
|
|
10853
10867
|
*/
|
|
10854
10868
|
const AddAnimationPanel = ({ onClose }) => {
|
|
10855
|
-
const styles = useStyles$
|
|
10869
|
+
const styles = useStyles$B();
|
|
10856
10870
|
const { state, actions, observables } = useCurveEditor();
|
|
10857
10871
|
const [name, setName] = useState("");
|
|
10858
10872
|
const [mode, setMode] = useState("List");
|
|
@@ -11069,7 +11083,7 @@ const AddAnimationPanel = ({ onClose }) => {
|
|
|
11069
11083
|
return (jsxs("div", { className: styles.root, children: [jsx("div", { className: styles.header, children: jsx("div", { className: styles.title, children: "Add Animation" }) }), jsxs("div", { className: styles.form, children: [jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Display Name" }), jsx(TextInput, { value: name, onChange: setName })] }), jsx("div", { className: styles.row, children: jsx(StringDropdown, { value: mode, onChange: (val) => setMode(val), options: ModeOptions, disabled: properties.length === 0, infoLabel: { label: "Mode" } }) }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Property" }), isCustomMode ? (jsx(TextInput, { value: customProperty, onChange: setCustomProperty })) : (jsx(StringDropdown, { value: selectedProperty, onChange: (val) => setSelectedProperty(val), options: properties.map((p) => ({ label: p, value: p })) }))] }), jsxs("div", { className: styles.row, children: [jsx(Label, { children: "Type" }), isCustomMode ? (jsx(StringDropdown, { value: animationType, onChange: (val) => setAnimationType(val), options: AnimationTypeOptions })) : (jsx("div", { className: styles.typeDisplay, children: inferredType }))] }), jsx("div", { className: styles.row, children: jsx(StringDropdown, { value: loopMode, onChange: (val) => setLoopMode(val), options: LoopModeOptions, infoLabel: { label: "Loop Mode" } }) })] }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: createAnimation, disabled: !isValid, label: "Create" }), jsx(Button, { appearance: "subtle", onClick: onClose, label: "Cancel" })] })] }));
|
|
11070
11084
|
};
|
|
11071
11085
|
|
|
11072
|
-
const useStyles$
|
|
11086
|
+
const useStyles$A = makeStyles({
|
|
11073
11087
|
root: {
|
|
11074
11088
|
display: "flex",
|
|
11075
11089
|
flexDirection: "column",
|
|
@@ -11112,7 +11126,7 @@ const useStyles$B = makeStyles({
|
|
|
11112
11126
|
* @returns The load animation panel component
|
|
11113
11127
|
*/
|
|
11114
11128
|
const LoadAnimationPanel = ({ onClose }) => {
|
|
11115
|
-
const styles = useStyles$
|
|
11129
|
+
const styles = useStyles$A();
|
|
11116
11130
|
const { state, actions, observables } = useCurveEditor();
|
|
11117
11131
|
const [snippetIdInput, setSnippetIdInput] = useState("");
|
|
11118
11132
|
const [loadedSnippetId, setLoadedSnippetId] = useState(null);
|
|
@@ -11251,7 +11265,7 @@ class StringTools {
|
|
|
11251
11265
|
}
|
|
11252
11266
|
}
|
|
11253
11267
|
|
|
11254
|
-
const useStyles$
|
|
11268
|
+
const useStyles$z = makeStyles({
|
|
11255
11269
|
root: {
|
|
11256
11270
|
display: "flex",
|
|
11257
11271
|
flexDirection: "column",
|
|
@@ -11296,7 +11310,7 @@ const useStyles$A = makeStyles({
|
|
|
11296
11310
|
* @returns The save animation panel component
|
|
11297
11311
|
*/
|
|
11298
11312
|
const SaveAnimationPanel = ({ onClose: _onClose }) => {
|
|
11299
|
-
const styles = useStyles$
|
|
11313
|
+
const styles = useStyles$z();
|
|
11300
11314
|
const { state } = useCurveEditor();
|
|
11301
11315
|
const [selectedAnimations, setSelectedAnimations] = useState(() => {
|
|
11302
11316
|
if (!state.animations) {
|
|
@@ -11369,7 +11383,7 @@ const SaveAnimationPanel = ({ onClose: _onClose }) => {
|
|
|
11369
11383
|
}) }), jsxs("div", { className: styles.buttons, children: [jsx(Button, { appearance: "primary", onClick: saveToSnippetServer, disabled: selectedAnimations.length === 0 || isSaving, label: isSaving ? "Saving..." : "Save to Snippet Server" }), jsx(Button, { appearance: "secondary", onClick: saveToFile, disabled: selectedAnimations.length === 0, label: "Save to File" })] }), saveError && jsx("div", { className: styles.errorText, children: saveError }), snippetId && jsxs("div", { className: styles.snippetId, children: ["Saved! Snippet ID: ", snippetId] })] }));
|
|
11370
11384
|
};
|
|
11371
11385
|
|
|
11372
|
-
const useStyles$
|
|
11386
|
+
const useStyles$y = makeStyles({
|
|
11373
11387
|
root: {
|
|
11374
11388
|
display: "flex",
|
|
11375
11389
|
flexDirection: "column",
|
|
@@ -11423,7 +11437,7 @@ const useStyles$z = makeStyles({
|
|
|
11423
11437
|
* @returns The sidebar component
|
|
11424
11438
|
*/
|
|
11425
11439
|
const SideBar = () => {
|
|
11426
|
-
const styles = useStyles$
|
|
11440
|
+
const styles = useStyles$y();
|
|
11427
11441
|
const { state, actions, observables } = useCurveEditor();
|
|
11428
11442
|
const [openPopover, setOpenPopover] = useState(null);
|
|
11429
11443
|
const [fps, setFps] = useState(60);
|
|
@@ -12411,7 +12425,7 @@ const KeyPointComponent = (props) => {
|
|
|
12411
12425
|
} })] }))] }))] }));
|
|
12412
12426
|
};
|
|
12413
12427
|
|
|
12414
|
-
const useStyles$
|
|
12428
|
+
const useStyles$x = makeStyles({
|
|
12415
12429
|
root: {
|
|
12416
12430
|
position: "absolute",
|
|
12417
12431
|
top: 0,
|
|
@@ -12595,7 +12609,7 @@ function ExtractValuesFromKeys(keys, curves) {
|
|
|
12595
12609
|
* @returns The graph component
|
|
12596
12610
|
*/
|
|
12597
12611
|
const Graph = ({ width, height }) => {
|
|
12598
|
-
const styles = useStyles$
|
|
12612
|
+
const styles = useStyles$x();
|
|
12599
12613
|
const { state, actions, observables } = useCurveEditor();
|
|
12600
12614
|
const svgRef = useRef(null);
|
|
12601
12615
|
const [scale, setScale] = useState(1);
|
|
@@ -12958,7 +12972,7 @@ const Graph = ({ width, height }) => {
|
|
|
12958
12972
|
}), renderValueAxis()] })] }));
|
|
12959
12973
|
};
|
|
12960
12974
|
|
|
12961
|
-
const useStyles$
|
|
12975
|
+
const useStyles$w = makeStyles({
|
|
12962
12976
|
root: {
|
|
12963
12977
|
position: "absolute",
|
|
12964
12978
|
top: 0,
|
|
@@ -13001,7 +13015,7 @@ const useStyles$x = makeStyles({
|
|
|
13001
13015
|
* @returns The playhead component
|
|
13002
13016
|
*/
|
|
13003
13017
|
const PlayHead = ({ width, height: _height }) => {
|
|
13004
|
-
const styles = useStyles$
|
|
13018
|
+
const styles = useStyles$w();
|
|
13005
13019
|
const { state, actions, observables } = useCurveEditor();
|
|
13006
13020
|
const [isDragging, setIsDragging] = useState(false);
|
|
13007
13021
|
// Use refs for all mutable values to avoid render cycles
|
|
@@ -13152,7 +13166,7 @@ const PlayHead = ({ width, height: _height }) => {
|
|
|
13152
13166
|
return (jsxs("div", { className: styles.root, children: [jsx("div", { ref: lineRef, className: styles.line, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp }), jsx("div", { ref: handleRef, className: styles.handle, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp })] }));
|
|
13153
13167
|
};
|
|
13154
13168
|
|
|
13155
|
-
const useStyles$
|
|
13169
|
+
const useStyles$v = makeStyles({
|
|
13156
13170
|
root: {
|
|
13157
13171
|
display: "flex",
|
|
13158
13172
|
flexDirection: "row",
|
|
@@ -13184,7 +13198,7 @@ const useStyles$w = makeStyles({
|
|
|
13184
13198
|
* @returns The frame bar component
|
|
13185
13199
|
*/
|
|
13186
13200
|
const FrameBar = ({ width }) => {
|
|
13187
|
-
const styles = useStyles$
|
|
13201
|
+
const styles = useStyles$v();
|
|
13188
13202
|
const { state, observables } = useCurveEditor();
|
|
13189
13203
|
const containerRef = useRef(null);
|
|
13190
13204
|
const [scale, setScale] = useState(1);
|
|
@@ -13242,7 +13256,7 @@ const FrameBar = ({ width }) => {
|
|
|
13242
13256
|
return (jsx("div", { className: styles.root, ref: containerRef, children: renderTicks() }));
|
|
13243
13257
|
};
|
|
13244
13258
|
|
|
13245
|
-
const useStyles$
|
|
13259
|
+
const useStyles$u = makeStyles({
|
|
13246
13260
|
root: {
|
|
13247
13261
|
display: "flex",
|
|
13248
13262
|
flexDirection: "column",
|
|
@@ -13283,7 +13297,7 @@ const OFFSET_X = 10;
|
|
|
13283
13297
|
* @returns The range frame bar component
|
|
13284
13298
|
*/
|
|
13285
13299
|
const RangeFrameBar = ({ width }) => {
|
|
13286
|
-
const styles = useStyles$
|
|
13300
|
+
const styles = useStyles$u();
|
|
13287
13301
|
const { state, actions, observables } = useCurveEditor();
|
|
13288
13302
|
const svgRef = useRef(null);
|
|
13289
13303
|
const [viewWidth, setViewWidth] = useState(width);
|
|
@@ -13394,7 +13408,7 @@ const RangeFrameBar = ({ width }) => {
|
|
|
13394
13408
|
}), renderKeyframes, renderActiveFrame] }) }));
|
|
13395
13409
|
};
|
|
13396
13410
|
|
|
13397
|
-
const useStyles$
|
|
13411
|
+
const useStyles$t = makeStyles({
|
|
13398
13412
|
root: {
|
|
13399
13413
|
display: "flex",
|
|
13400
13414
|
flexDirection: "column",
|
|
@@ -13426,7 +13440,7 @@ const useStyles$u = makeStyles({
|
|
|
13426
13440
|
* @returns The canvas component
|
|
13427
13441
|
*/
|
|
13428
13442
|
const Canvas = () => {
|
|
13429
|
-
const styles = useStyles$
|
|
13443
|
+
const styles = useStyles$t();
|
|
13430
13444
|
const { observables } = useCurveEditor();
|
|
13431
13445
|
const containerRef = useRef(null);
|
|
13432
13446
|
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
|
@@ -13456,7 +13470,7 @@ const Canvas = () => {
|
|
|
13456
13470
|
return (jsxs("div", { className: styles.root, ref: containerRef, children: [jsx("div", { className: styles.frameBar, children: jsx(FrameBar, { width: dimensions.width }) }), jsxs("div", { className: styles.canvasArea, children: [jsx(Graph, { width: dimensions.width, height: dimensions.height - 70 }), jsx(PlayHead, { width: dimensions.width, height: dimensions.height - 70 })] }), jsx("div", { className: styles.rangeFrameBar, children: jsx(RangeFrameBar, { width: dimensions.width }) })] }));
|
|
13457
13471
|
};
|
|
13458
13472
|
|
|
13459
|
-
const useStyles$
|
|
13473
|
+
const useStyles$s = makeStyles({
|
|
13460
13474
|
root: {
|
|
13461
13475
|
flex: 1,
|
|
13462
13476
|
height: "25px",
|
|
@@ -13518,7 +13532,7 @@ const useStyles$t = makeStyles({
|
|
|
13518
13532
|
* @returns The range selector component
|
|
13519
13533
|
*/
|
|
13520
13534
|
const RangeSelector = () => {
|
|
13521
|
-
const styles = useStyles$
|
|
13535
|
+
const styles = useStyles$s();
|
|
13522
13536
|
const { state, actions, observables } = useCurveEditor();
|
|
13523
13537
|
const containerRef = useRef(null);
|
|
13524
13538
|
const scrollbarRef = useRef(null);
|
|
@@ -13663,7 +13677,7 @@ function GetKeyAtAnyFrameIndex(animations, frame) {
|
|
|
13663
13677
|
}
|
|
13664
13678
|
return false;
|
|
13665
13679
|
}
|
|
13666
|
-
const useStyles$
|
|
13680
|
+
const useStyles$r = makeStyles({
|
|
13667
13681
|
root: {
|
|
13668
13682
|
display: "flex",
|
|
13669
13683
|
flexDirection: "row",
|
|
@@ -13720,7 +13734,7 @@ MediaControls.displayName = "MediaControls";
|
|
|
13720
13734
|
* @returns The BottomBar component.
|
|
13721
13735
|
*/
|
|
13722
13736
|
const BottomBar = () => {
|
|
13723
|
-
const styles = useStyles$
|
|
13737
|
+
const styles = useStyles$r();
|
|
13724
13738
|
const { state, actions, observables } = useCurveEditor();
|
|
13725
13739
|
// Track display frame separately for smooth updates during playback
|
|
13726
13740
|
const [displayFrame, setDisplayFrame] = useState(state.activeFrame);
|
|
@@ -13835,7 +13849,7 @@ const BottomBar = () => {
|
|
|
13835
13849
|
return (jsxs("div", { className: styles.root, children: [jsx("div", { className: styles.mediaControls, children: jsx(MediaControls, { hasActiveAnimations: hasActiveAnimations, isPlaying: state.isPlaying, forwardAnimation: state.forwardAnimation, onPlayForward: handlePlayForward, onPlayBackward: handlePlayBackward, onStop: handleStop, onPrevKey: handlePrevKey, onNextKey: handleNextKey, onFirstFrame: handleFirstFrame, onLastFrame: handleLastFrame }) }), jsxs("div", { className: styles.frameDisplay, children: [jsx("div", { className: styles.frameLabel, children: "Frame:" }), jsx(SpinButton, { className: styles.spinButton, value: displayFrame, onChange: handleFrameChange, min: state.fromKey, max: state.toKey, disabled: !hasActiveAnimations })] }), jsx(RangeSelector, {}), jsxs("div", { className: styles.clipLengthSection, children: [jsx("div", { className: styles.frameLabel, children: "Clip Length:" }), jsx(SpinButton, { className: styles.spinButton, value: clipLength, onChange: handleClipLengthChange, min: 1, disabled: !hasActiveAnimations })] })] }));
|
|
13836
13850
|
};
|
|
13837
13851
|
|
|
13838
|
-
const useStyles$
|
|
13852
|
+
const useStyles$q = makeStyles({
|
|
13839
13853
|
root: {
|
|
13840
13854
|
display: "flex",
|
|
13841
13855
|
flexDirection: "column",
|
|
@@ -13884,7 +13898,7 @@ const useStyles$r = makeStyles({
|
|
|
13884
13898
|
* @returns The curve editor content
|
|
13885
13899
|
*/
|
|
13886
13900
|
const CurveEditorContent = () => {
|
|
13887
|
-
const styles = useStyles$
|
|
13901
|
+
const styles = useStyles$q();
|
|
13888
13902
|
const { state, actions, observables } = useCurveEditor();
|
|
13889
13903
|
const rootRef = useRef(null);
|
|
13890
13904
|
const prepareRef = useRef(() => actions.prepare());
|
|
@@ -14726,7 +14740,7 @@ function useObservableArray(target, getItems, addFn, removeFn, changeFn) {
|
|
|
14726
14740
|
}, [getItems]), useInterceptObservable("function", target, addFn), useInterceptObservable("function", target, removeFn), changeFn ? useInterceptObservable("function", target, changeFn) : undefined);
|
|
14727
14741
|
}
|
|
14728
14742
|
|
|
14729
|
-
const useStyles$
|
|
14743
|
+
const useStyles$p = makeStyles({
|
|
14730
14744
|
lightsListDiv: {
|
|
14731
14745
|
display: "flex",
|
|
14732
14746
|
flexDirection: "column",
|
|
@@ -14736,7 +14750,7 @@ const ClusteredLightContainerSetupProperties = ({ context: container }) => {
|
|
|
14736
14750
|
return (jsxs(Fragment, { children: [jsx(BooleanBadgePropertyLine, { label: "Is Supported", value: container.isSupported }), jsx(BoundProperty, { label: "Horizontal Tiles", component: NumberInputPropertyLine, target: container, propertyKey: "horizontalTiles", step: 1, min: 1, forceInt: true }), jsx(BoundProperty, { label: "Vertical Tiles", component: NumberInputPropertyLine, target: container, propertyKey: "verticalTiles", step: 1, min: 1, forceInt: true }), jsx(BoundProperty, { label: "Depth Slices", component: NumberInputPropertyLine, target: container, propertyKey: "depthSlices", step: 1, min: 1, forceInt: true }), jsx(BoundProperty, { label: "Max Range", component: NumberInputPropertyLine, target: container, propertyKey: "maxRange", min: 1 })] }));
|
|
14737
14751
|
};
|
|
14738
14752
|
const ClusteredLightContainerLightsProperties = ({ container, selectionService, }) => {
|
|
14739
|
-
const classes = useStyles$
|
|
14753
|
+
const classes = useStyles$p();
|
|
14740
14754
|
const lights = useObservableArray(container, useCallback(() => container.lights, [container]), "addLight", "removeLight");
|
|
14741
14755
|
return (jsx(PropertyLine, { label: "Lights", expandedContent: jsx("div", { className: classes.lightsListDiv, children: lights.map((light) => (jsx(LinkToEntityPropertyLine, { label: light.getClassName(), entity: light, selectionService: selectionService }, light.uniqueId))) }), children: jsx(Badge, { appearance: "filled", children: lights.length }) }));
|
|
14742
14756
|
};
|
|
@@ -14786,7 +14800,7 @@ const HemisphericLightSetupProperties = ({ context: hemisphericLight }) => {
|
|
|
14786
14800
|
return (jsxs(Fragment, { children: [jsx(BoundProperty, { label: "Direction", component: Vector3PropertyLine, target: hemisphericLight, propertyKey: "direction" }), jsx(BoundProperty, { label: "Diffuse", component: Color3PropertyLine, target: hemisphericLight, propertyKey: "diffuse" }), jsx(BoundProperty, { label: "Ground", component: Color3PropertyLine, target: hemisphericLight, propertyKey: "groundColor" }), jsx(BoundProperty, { label: "Intensity", component: NumberInputPropertyLine, target: hemisphericLight, propertyKey: "intensity" })] }));
|
|
14787
14801
|
};
|
|
14788
14802
|
|
|
14789
|
-
const useStyles$
|
|
14803
|
+
const useStyles$o = makeStyles({
|
|
14790
14804
|
root: {
|
|
14791
14805
|
display: "grid",
|
|
14792
14806
|
gridTemplateRows: "repeat(1fr)",
|
|
@@ -14816,7 +14830,7 @@ const useStyles$p = makeStyles({
|
|
|
14816
14830
|
const ComboBox = forwardRef((props, ref) => {
|
|
14817
14831
|
ComboBox.displayName = "ComboBox";
|
|
14818
14832
|
const comboId = useId();
|
|
14819
|
-
const styles = useStyles$
|
|
14833
|
+
const styles = useStyles$o();
|
|
14820
14834
|
const { size } = useContext(ToolContext);
|
|
14821
14835
|
// Find the label for the current value
|
|
14822
14836
|
const getLabel = (value) => props.options.find((opt) => opt.value === value)?.label ?? "";
|
|
@@ -14839,7 +14853,7 @@ const ComboBox = forwardRef((props, ref) => {
|
|
|
14839
14853
|
return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { ref: ref, defaultOpen: props.defaultOpen, size: size, root: { className: styles.comboBox }, input: { className: styles.input }, listbox: { className: styles.listbox }, onOptionSelect: onOptionSelect, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
|
|
14840
14854
|
});
|
|
14841
14855
|
|
|
14842
|
-
const useStyles$
|
|
14856
|
+
const useStyles$n = makeStyles({
|
|
14843
14857
|
linkDiv: {
|
|
14844
14858
|
display: "flex",
|
|
14845
14859
|
flexDirection: "row",
|
|
@@ -14864,7 +14878,7 @@ const useStyles$o = makeStyles({
|
|
|
14864
14878
|
function EntitySelector(props) {
|
|
14865
14879
|
const { value, onLink, getEntities, getName, filter, defaultValue } = props;
|
|
14866
14880
|
const onChange = props.onChange;
|
|
14867
|
-
const classes = useStyles$
|
|
14881
|
+
const classes = useStyles$n();
|
|
14868
14882
|
const comboBoxRef = useRef(null);
|
|
14869
14883
|
// Build options with uniqueId as key
|
|
14870
14884
|
const options = useMemo(() => {
|
|
@@ -15018,7 +15032,7 @@ const TextureUpload = (props) => {
|
|
|
15018
15032
|
return jsx(UploadButton, { onUpload: handleUpload, accept: accept, title: "Upload Texture", label: label });
|
|
15019
15033
|
};
|
|
15020
15034
|
|
|
15021
|
-
const useStyles$
|
|
15035
|
+
const useStyles$m = makeStyles({
|
|
15022
15036
|
container: {
|
|
15023
15037
|
display: "flex",
|
|
15024
15038
|
flexDirection: "row",
|
|
@@ -15035,7 +15049,7 @@ const useStyles$n = makeStyles({
|
|
|
15035
15049
|
const TextureSelector = (props) => {
|
|
15036
15050
|
TextureSelector.displayName = "TextureSelector";
|
|
15037
15051
|
const { scene, cubeOnly, value, onChange, onLink, defaultValue } = props;
|
|
15038
|
-
const classes = useStyles$
|
|
15052
|
+
const classes = useStyles$m();
|
|
15039
15053
|
const getTextures = useCallback(() => scene.textures, [scene.textures]);
|
|
15040
15054
|
const getName = useCallback((texture) => texture.displayName || texture.name || `${texture.getClassName() || "Unnamed Texture"} (${texture.uniqueId})`, []);
|
|
15041
15055
|
const filter = useCallback((texture) => !cubeOnly || texture.isCube, [cubeOnly]);
|
|
@@ -15640,7 +15654,7 @@ async function EditNodeMaterial(material) {
|
|
|
15640
15654
|
await material.edit({ nodeEditorConfig: { backgroundColor: material.getScene().clearColor } });
|
|
15641
15655
|
}
|
|
15642
15656
|
|
|
15643
|
-
const useStyles$
|
|
15657
|
+
const useStyles$l = makeStyles({
|
|
15644
15658
|
subsection: {
|
|
15645
15659
|
marginTop: tokens.spacingVerticalM,
|
|
15646
15660
|
},
|
|
@@ -15714,7 +15728,7 @@ const GradientBlockPropertyLine = (props) => {
|
|
|
15714
15728
|
};
|
|
15715
15729
|
const NodeMaterialInputProperties = (props) => {
|
|
15716
15730
|
const { material } = props;
|
|
15717
|
-
const classes = useStyles$
|
|
15731
|
+
const classes = useStyles$l();
|
|
15718
15732
|
const inputBlocks = useObservableState(useCallback(() => {
|
|
15719
15733
|
const inspectorVisibleInputBlocks = material
|
|
15720
15734
|
.getInputBlocks()
|
|
@@ -16440,7 +16454,7 @@ function SaveMetadata(entity, metadata) {
|
|
|
16440
16454
|
entity.metadata = metadata;
|
|
16441
16455
|
}
|
|
16442
16456
|
}
|
|
16443
|
-
const useStyles$
|
|
16457
|
+
const useStyles$k = makeStyles({
|
|
16444
16458
|
mainDiv: {
|
|
16445
16459
|
display: "flex",
|
|
16446
16460
|
flexDirection: "column",
|
|
@@ -16460,7 +16474,7 @@ const useStyles$l = makeStyles({
|
|
|
16460
16474
|
*/
|
|
16461
16475
|
const MetadataProperties = (props) => {
|
|
16462
16476
|
const { entity } = props;
|
|
16463
|
-
const classes = useStyles$
|
|
16477
|
+
const classes = useStyles$k();
|
|
16464
16478
|
const { size } = useContext(ToolContext);
|
|
16465
16479
|
const metadata = useProperty(entity, "metadata");
|
|
16466
16480
|
const stringifiedMetadata = useMemo(() => StringifyMetadata(metadata, false) ?? "", [metadata]);
|
|
@@ -17395,7 +17409,7 @@ const ParticleSystemEmitterProperties = (props) => {
|
|
|
17395
17409
|
} })) : (jsx(Property, { component: TextPropertyLine, propertyPath: "source", label: "Source", value: "No meshes in scene." })), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use normals for direction", target: particleEmitterType, propertyKey: "useMeshNormalsForDirection" }), !useMeshNormalsForDirection && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" })] }))] })), particleEmitterType instanceof BoxParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Min emit box", target: particleEmitterType, propertyKey: "minEmitBox" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Max emit box", target: particleEmitterType, propertyKey: "maxEmitBox" })] })), particleEmitterType instanceof ConeParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height range", target: particleEmitterType, propertyKey: "heightRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Emit from spawn point only", target: particleEmitterType, propertyKey: "emitFromSpawnPointOnly" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof SphereParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof CylinderParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height", target: particleEmitterType, propertyKey: "height", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof HemisphericParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), particleEmitterType instanceof PointParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" })] }))] })), !scene && jsx(TextPropertyLine, { label: "Emitter", value: "No scene available." })] }));
|
|
17396
17410
|
};
|
|
17397
17411
|
|
|
17398
|
-
const useStyles$
|
|
17412
|
+
const useStyles$j = makeStyles({
|
|
17399
17413
|
subsection: {
|
|
17400
17414
|
marginTop: tokens.spacingVerticalM,
|
|
17401
17415
|
},
|
|
@@ -17415,7 +17429,7 @@ const ParticleSystemSizeProperties = (props) => {
|
|
|
17415
17429
|
const sizeGradientGetter = useCallback(() => system.getSizeGradients(), [system]);
|
|
17416
17430
|
const sizeGradient = useObservableArray(system, sizeGradientGetter, "addSizeGradient", "removeSizeGradient", "forceRefreshGradients");
|
|
17417
17431
|
const useSizeGradients = (sizeGradient?.length ?? 0) > 0;
|
|
17418
|
-
const classes = useStyles$
|
|
17432
|
+
const classes = useStyles$j();
|
|
17419
17433
|
return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min size", target: system, propertyKey: "minSize", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max size", target: system, propertyKey: "maxSize", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min scale x", target: system, propertyKey: "minScaleX", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max scale x", target: system, propertyKey: "maxScaleX", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min scale y", target: system, propertyKey: "minScaleY", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max scale y", target: system, propertyKey: "maxScaleY", min: 0, step: 0.1 }), isCpuParticleSystem && !useStartSizeGradients && (jsx(ButtonLine, { label: "Use Start Size gradients", onClick: () => {
|
|
17420
17434
|
system.addStartSizeGradient(0, system.minSize, system.maxSize);
|
|
17421
17435
|
system.forceRefreshGradients();
|
|
@@ -17451,7 +17465,7 @@ const ParticleSystemSizeProperties = (props) => {
|
|
|
17451
17465
|
} })] }))] }));
|
|
17452
17466
|
};
|
|
17453
17467
|
|
|
17454
|
-
const useStyles$
|
|
17468
|
+
const useStyles$i = makeStyles({
|
|
17455
17469
|
subsection: {
|
|
17456
17470
|
marginTop: tokens.spacingVerticalM,
|
|
17457
17471
|
},
|
|
@@ -17477,7 +17491,7 @@ const ParticleSystemEmissionProperties = (props) => {
|
|
|
17477
17491
|
const useVelocityGradients = (velocityGradients?.length ?? 0) > 0;
|
|
17478
17492
|
const useLimitVelocityGradients = (limitVelocityGradients?.length ?? 0) > 0;
|
|
17479
17493
|
const useDragGradients = (dragGradients?.length ?? 0) > 0;
|
|
17480
|
-
const classes = useStyles$
|
|
17494
|
+
const classes = useStyles$i();
|
|
17481
17495
|
return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Emit rate", target: system, propertyKey: "emitRate", min: 0, step: 1 }), isCpuParticleSystem && !useEmitRateGradients && (jsx(ButtonLine, { label: "Use Emit rate gradients", onClick: () => {
|
|
17482
17496
|
system.addEmitRateGradient(0, system.emitRate, system.emitRate);
|
|
17483
17497
|
system.forceRefreshGradients();
|
|
@@ -17545,7 +17559,7 @@ const ParticleSystemEmissionProperties = (props) => {
|
|
|
17545
17559
|
} })] }))] }));
|
|
17546
17560
|
};
|
|
17547
17561
|
|
|
17548
|
-
const useStyles$
|
|
17562
|
+
const useStyles$h = makeStyles({
|
|
17549
17563
|
subsection: {
|
|
17550
17564
|
marginTop: tokens.spacingVerticalM,
|
|
17551
17565
|
},
|
|
@@ -17562,7 +17576,7 @@ const ParticleSystemLifetimeProperties = (props) => {
|
|
|
17562
17576
|
const lifeTimeGradientsGetter = useCallback(() => system.getLifeTimeGradients(), [system]);
|
|
17563
17577
|
const lifeTimeGradients = useObservableArray(system, lifeTimeGradientsGetter, "addLifeTimeGradient", "removeLifeTimeGradient", "forceRefreshGradients");
|
|
17564
17578
|
const useLifeTimeGradients = (lifeTimeGradients?.length ?? 0) > 0;
|
|
17565
|
-
const classes = useStyles$
|
|
17579
|
+
const classes = useStyles$h();
|
|
17566
17580
|
return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min lifetime", target: system, propertyKey: "minLifeTime", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max lifetime", target: system, propertyKey: "maxLifeTime", min: 0, step: 0.1 }), isCpuParticleSystem && !useLifeTimeGradients && (jsx(ButtonLine, { label: "Use Lifetime gradients", onClick: () => {
|
|
17567
17581
|
system.addLifeTimeGradient(0, system.minLifeTime, system.maxLifeTime);
|
|
17568
17582
|
system.forceRefreshGradients();
|
|
@@ -17582,7 +17596,7 @@ const ParticleSystemLifetimeProperties = (props) => {
|
|
|
17582
17596
|
} })] })), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Target stop duration", target: system, propertyKey: "targetStopDuration", min: 0, step: 0.1 })] }));
|
|
17583
17597
|
};
|
|
17584
17598
|
|
|
17585
|
-
const useStyles$
|
|
17599
|
+
const useStyles$g = makeStyles({
|
|
17586
17600
|
subsection: {
|
|
17587
17601
|
marginTop: tokens.spacingVerticalM,
|
|
17588
17602
|
},
|
|
@@ -17609,7 +17623,7 @@ const ParticleSystemColorProperties = (props) => {
|
|
|
17609
17623
|
const hasRampGradients = (rampGradients?.length ?? 0) > 0;
|
|
17610
17624
|
const hasColorRemapGradients = (colorRemapGradients?.length ?? 0) > 0;
|
|
17611
17625
|
const hasAlphaRemapGradients = (alphaRemapGradients?.length ?? 0) > 0;
|
|
17612
|
-
const classes = useStyles$
|
|
17626
|
+
const classes = useStyles$g();
|
|
17613
17627
|
return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color4PropertyLine, label: "Color 1", target: system, propertyKey: "color1" }), jsx(BoundProperty, { component: Color4PropertyLine, label: "Color 2", target: system, propertyKey: "color2" }), jsx(BoundProperty, { component: Color4PropertyLine, label: "Color dead", target: system, propertyKey: "colorDead" }), !hasColorGradients && (jsx(ButtonLine, { label: "Use Color gradients", onClick: () => {
|
|
17614
17628
|
system.addColorGradient(0, system.color1, system.color1);
|
|
17615
17629
|
system.addColorGradient(1, system.color2, system.color2);
|
|
@@ -17681,7 +17695,7 @@ const ParticleSystemColorProperties = (props) => {
|
|
|
17681
17695
|
} })] }))] }))] }));
|
|
17682
17696
|
};
|
|
17683
17697
|
|
|
17684
|
-
const useStyles$
|
|
17698
|
+
const useStyles$f = makeStyles({
|
|
17685
17699
|
subsection: {
|
|
17686
17700
|
marginTop: tokens.spacingVerticalM,
|
|
17687
17701
|
},
|
|
@@ -17696,7 +17710,7 @@ const ParticleSystemRotationProperties = (props) => {
|
|
|
17696
17710
|
const angularSpeedGradientsGetter = useCallback(() => system.getAngularSpeedGradients(), [system]);
|
|
17697
17711
|
const angularSpeedGradients = useObservableArray(system, angularSpeedGradientsGetter, "addAngularSpeedGradient", "removeAngularSpeedGradient", "forceRefreshGradients");
|
|
17698
17712
|
const useAngularSpeedGradients = (angularSpeedGradients?.length ?? 0) > 0;
|
|
17699
|
-
const classes = useStyles$
|
|
17713
|
+
const classes = useStyles$f();
|
|
17700
17714
|
return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min Angular speed", target: system, propertyKey: "minAngularSpeed", step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max Angular speed", target: system, propertyKey: "maxAngularSpeed", step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min initial rotation", target: system, propertyKey: "minInitialRotation", step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max initial rotation", target: system, propertyKey: "maxInitialRotation", step: 0.01 }), !useAngularSpeedGradients && (jsx(ButtonLine, { label: "Use Angular speed gradients", onClick: () => {
|
|
17701
17715
|
system.addAngularSpeedGradient(0, system.minAngularSpeed, system.maxAngularSpeed);
|
|
17702
17716
|
system.forceRefreshGradients();
|
|
@@ -17802,7 +17816,7 @@ const AttractorComponent = (props) => {
|
|
|
17802
17816
|
} }), !attractorData.isReadOnly && (jsx(ToggleButton, { title: "Add / remove position gizmo from particle attractor", checkedIcon: ArrowMoveFilled, value: isControlled(impostor), onChange: (control) => onControl(control ? impostor : undefined) }))] }));
|
|
17803
17817
|
};
|
|
17804
17818
|
|
|
17805
|
-
const useStyles$
|
|
17819
|
+
const useStyles$e = makeStyles({
|
|
17806
17820
|
subsection: {
|
|
17807
17821
|
marginTop: tokens.spacingVerticalM,
|
|
17808
17822
|
},
|
|
@@ -17859,7 +17873,7 @@ const AttractorList = (props) => {
|
|
|
17859
17873
|
gizmoManager.attachToMesh(attached);
|
|
17860
17874
|
setControlledImpostor(attached);
|
|
17861
17875
|
};
|
|
17862
|
-
const classes = useStyles$
|
|
17876
|
+
const classes = useStyles$e();
|
|
17863
17877
|
return (jsxs(Fragment, { children: [items.length > 0 && (jsxs(Fragment, { children: [jsx(Color3PropertyLine, { label: "Attractor Debug Color", value: impostorColor, onChange: setImpostorColor }), jsx(SyncedSliderPropertyLine, { label: "Attractor Debug Size", value: impostorScale, onChange: setImpostorScale, min: 0, max: 10, step: 0.1 }), jsx(Subtitle2, { className: classes.subsection, children: "Attractors list" })] })), jsx(List, { addButtonLabel: `Add New Attractor`, items: items, onDelete: attractorSource.removeAttractor
|
|
17864
17878
|
? (item, _index) => {
|
|
17865
17879
|
// Only CPU attractors (Attractor instances) can be removed
|
|
@@ -17969,7 +17983,7 @@ const ParticleSystemAttractorProperties = (props) => {
|
|
|
17969
17983
|
return (jsx(Fragment, { children: scene ? (jsx(AttractorList, { attractorSource: attractorSource, scene: scene })) : (jsx(MessageBar, { intent: "info", title: "No Scene Available", message: "Cannot display attractors without a scene" })) }));
|
|
17970
17984
|
};
|
|
17971
17985
|
|
|
17972
|
-
const useStyles$
|
|
17986
|
+
const useStyles$d = makeStyles({
|
|
17973
17987
|
subsection: {
|
|
17974
17988
|
marginTop: tokens.spacingVerticalM,
|
|
17975
17989
|
},
|
|
@@ -18023,7 +18037,7 @@ const InputBlockPropertyLine = (props) => {
|
|
|
18023
18037
|
*/
|
|
18024
18038
|
const ParticleSystemNodeEditorProperties = (props) => {
|
|
18025
18039
|
const { particleSystem: system } = props;
|
|
18026
|
-
const classes = useStyles$
|
|
18040
|
+
const classes = useStyles$d();
|
|
18027
18041
|
const source = system.source;
|
|
18028
18042
|
const inputBlocks = useObservableState(useCallback(() => {
|
|
18029
18043
|
if (!source) {
|
|
@@ -18884,14 +18898,14 @@ const SkeletonPropertiesServiceDefinition = {
|
|
|
18884
18898
|
},
|
|
18885
18899
|
};
|
|
18886
18900
|
|
|
18887
|
-
const useStyles$
|
|
18901
|
+
const useStyles$c = makeStyles({
|
|
18888
18902
|
uniformWidth: {
|
|
18889
18903
|
...UniformWidthStyling,
|
|
18890
18904
|
},
|
|
18891
18905
|
});
|
|
18892
18906
|
const SpinButtonPropertyLine = (props) => {
|
|
18893
18907
|
SpinButtonPropertyLine.displayName = "SpinButtonPropertyLine";
|
|
18894
|
-
const classes = useStyles$
|
|
18908
|
+
const classes = useStyles$c();
|
|
18895
18909
|
return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
|
|
18896
18910
|
};
|
|
18897
18911
|
|
|
@@ -19139,7 +19153,7 @@ function _TextureFormatHasNoAlpha(format) {
|
|
|
19139
19153
|
}
|
|
19140
19154
|
}
|
|
19141
19155
|
|
|
19142
|
-
const useStyles$
|
|
19156
|
+
const useStyles$b = makeStyles({
|
|
19143
19157
|
root: {
|
|
19144
19158
|
display: "flex",
|
|
19145
19159
|
flexDirection: "column",
|
|
@@ -19191,7 +19205,7 @@ const TextureChannelStates = {
|
|
|
19191
19205
|
*/
|
|
19192
19206
|
const TexturePreview = (props) => {
|
|
19193
19207
|
const { texture, disableToolbar = false, maxWidth = "100%", maxHeight = "384px", offsetX = 0, offsetY = 0, width, height, imperativeRef } = props;
|
|
19194
|
-
const classes = useStyles$
|
|
19208
|
+
const classes = useStyles$b();
|
|
19195
19209
|
const canvasRef = useRef(null);
|
|
19196
19210
|
const [channels, setChannels] = useState(TextureChannelStates.ALL);
|
|
19197
19211
|
const [face, setFace] = useState(0);
|
|
@@ -20101,7 +20115,7 @@ class TextureCanvasManager {
|
|
|
20101
20115
|
}
|
|
20102
20116
|
}
|
|
20103
20117
|
|
|
20104
|
-
const useStyles$
|
|
20118
|
+
const useStyles$a = makeStyles({
|
|
20105
20119
|
channelsBar: {
|
|
20106
20120
|
display: "flex",
|
|
20107
20121
|
flexDirection: "column",
|
|
@@ -20146,7 +20160,7 @@ const useStyles$b = makeStyles({
|
|
|
20146
20160
|
*/
|
|
20147
20161
|
const ChannelsBar = (props) => {
|
|
20148
20162
|
const { channels, setChannels } = props;
|
|
20149
|
-
const classes = useStyles$
|
|
20163
|
+
const classes = useStyles$a();
|
|
20150
20164
|
const toggleVisibility = useCallback((index) => {
|
|
20151
20165
|
const newChannels = [...channels];
|
|
20152
20166
|
newChannels[index] = { ...newChannels[index], visible: !newChannels[index].visible };
|
|
@@ -20176,7 +20190,7 @@ const ChannelsBar = (props) => {
|
|
|
20176
20190
|
}) }));
|
|
20177
20191
|
};
|
|
20178
20192
|
|
|
20179
|
-
const useStyles$
|
|
20193
|
+
const useStyles$9 = makeStyles({
|
|
20180
20194
|
propertiesBar: {
|
|
20181
20195
|
display: "flex",
|
|
20182
20196
|
backgroundColor: tokens.colorNeutralBackground1,
|
|
@@ -20226,7 +20240,7 @@ const useStyles$a = makeStyles({
|
|
|
20226
20240
|
},
|
|
20227
20241
|
});
|
|
20228
20242
|
const PixelDataDisplay = ({ label, value }) => {
|
|
20229
|
-
const classes = useStyles$
|
|
20243
|
+
const classes = useStyles$9();
|
|
20230
20244
|
return (jsxs("span", { className: classes.pixelData, children: [jsxs(Label, { className: classes.pixelDataLabel, children: [label, ":"] }), jsx(Label, { className: classes.pixelDataValue, children: value !== undefined ? value : "-" })] }));
|
|
20231
20245
|
};
|
|
20232
20246
|
const CubeFaces = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
|
|
@@ -20237,7 +20251,7 @@ const CubeFaces = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
|
|
|
20237
20251
|
*/
|
|
20238
20252
|
const PropertiesBar = (props) => {
|
|
20239
20253
|
const { texture, size, saveTexture, pixelData, face, setFace, resetTexture, resizeTexture, uploadTexture, mipLevel, setMipLevel } = props;
|
|
20240
|
-
const classes = useStyles$
|
|
20254
|
+
const classes = useStyles$9();
|
|
20241
20255
|
const uploadInputRef = useRef(null);
|
|
20242
20256
|
const [width, setWidth] = useState(size.width);
|
|
20243
20257
|
const [height, setHeight] = useState(size.height);
|
|
@@ -20272,7 +20286,7 @@ const PropertiesBar = (props) => {
|
|
|
20272
20286
|
return (jsxs("div", { className: classes.propertiesBar, children: [jsxs("div", { className: classes.section, children: [jsx(Label, { children: "W:" }), jsx(Input, { className: classes.dimensionInput, size: "small", type: "text", value: width.toString(), readOnly: texture.isCube, onChange: (_, data) => setWidth(getNewDimension(width, data.value)) }), jsx(Label, { children: "H:" }), jsx(Input, { className: classes.dimensionInput, size: "small", type: "text", value: height.toString(), readOnly: texture.isCube, onChange: (_, data) => setHeight(getNewDimension(height, data.value)) }), !texture.isCube && (jsx(Tooltip$1, { content: "Resize", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ResizeRegular, {}), onClick: handleResize }) }))] }), jsx(ToolbarDivider, {}), jsxs("div", { className: classes.section, children: [jsx(PixelDataDisplay, { label: "X", value: pixelData.x }), jsx(PixelDataDisplay, { label: "Y", value: pixelData.y })] }), jsx(ToolbarDivider, {}), jsxs("div", { className: classes.section, children: [jsx(PixelDataDisplay, { label: "R", value: pixelData.r }), jsx(PixelDataDisplay, { label: "G", value: pixelData.g }), jsx(PixelDataDisplay, { label: "B", value: pixelData.b }), jsx(PixelDataDisplay, { label: "A", value: pixelData.a })] }), texture.isCube && (jsxs(Fragment, { children: [jsx(ToolbarDivider, {}), jsx(Toolbar$1, { size: "small", children: CubeFaces.map((label, index) => (jsx(ToolbarButton, { className: classes.faceButton, appearance: face === index ? "primary" : "subtle", onClick: () => setFace(index), children: label }, label))) })] })), mipsEnabled && (jsxs(Fragment, { children: [jsx(ToolbarDivider, {}), jsxs("div", { className: classes.section, children: [jsx(Label, { children: "MIP:" }), jsx(Tooltip$1, { content: "Mip Preview Up", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ChevronUpRegular, {}), disabled: mipLevel <= 0, onClick: () => setMipLevel(mipLevel - 1) }) }), jsx(Label, { children: mipLevel }), jsx(Tooltip$1, { content: "Mip Preview Down", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ChevronDownRegular, {}), disabled: mipLevel >= maxLevels, onClick: () => setMipLevel(mipLevel + 1) }) })] })] })), jsx("div", { className: classes.spacer }), jsxs(Toolbar$1, { size: "small", children: [jsx(Tooltip$1, { content: "Reset", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ArrowResetRegular, {}), onClick: resetTexture }) }), jsx(Tooltip$1, { content: "Upload", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(ArrowUploadRegular, {}), onClick: handleUploadClick }) }), jsx("input", { ref: uploadInputRef, className: classes.uploadInput, type: "file", accept: ".jpg, .png, .tga, .dds, .env, .exr", onChange: handleFileChange }), jsx(Tooltip$1, { content: "Save", relationship: "label", children: jsx(ToolbarButton, { icon: jsx(SaveRegular, {}), onClick: saveTexture }) })] })] }));
|
|
20273
20287
|
};
|
|
20274
20288
|
|
|
20275
|
-
const useStyles$
|
|
20289
|
+
const useStyles$8 = makeStyles({
|
|
20276
20290
|
statusBar: {
|
|
20277
20291
|
display: "flex",
|
|
20278
20292
|
backgroundColor: tokens.colorNeutralBackground1,
|
|
@@ -20300,14 +20314,14 @@ const useStyles$9 = makeStyles({
|
|
|
20300
20314
|
*/
|
|
20301
20315
|
const StatusBar = (props) => {
|
|
20302
20316
|
const { texture, mipLevel } = props;
|
|
20303
|
-
const classes = useStyles$
|
|
20317
|
+
const classes = useStyles$8();
|
|
20304
20318
|
const factor = Math.pow(2, mipLevel);
|
|
20305
20319
|
const width = Math.ceil(texture.getSize().width / factor);
|
|
20306
20320
|
const height = Math.ceil(texture.getSize().height / factor);
|
|
20307
20321
|
return (jsxs("div", { className: classes.statusBar, children: [jsx("span", { className: classes.fileName, children: texture.name }), !texture.noMipmap && (jsxs("span", { className: classes.mipInfo, children: ["MIP Preview: ", mipLevel, " (", width, "\u00D7", height, ")"] }))] }));
|
|
20308
20322
|
};
|
|
20309
20323
|
|
|
20310
|
-
const useStyles$
|
|
20324
|
+
const useStyles$7 = makeStyles({
|
|
20311
20325
|
toolbar: {
|
|
20312
20326
|
display: "flex",
|
|
20313
20327
|
flexDirection: "column",
|
|
@@ -20344,7 +20358,7 @@ const useStyles$8 = makeStyles({
|
|
|
20344
20358
|
*/
|
|
20345
20359
|
const ToolBar = (props) => {
|
|
20346
20360
|
const { tools, changeTool, activeToolIndex, metadata, setMetadata, hasAlpha } = props;
|
|
20347
|
-
const classes = useStyles$
|
|
20361
|
+
const classes = useStyles$7();
|
|
20348
20362
|
const computeRGBAColor = useCallback(() => {
|
|
20349
20363
|
const opacityInt = Math.floor(metadata.alpha * 255);
|
|
20350
20364
|
const opacityHex = opacityInt.toString(16).padStart(2, "0");
|
|
@@ -20373,7 +20387,7 @@ const ToolBar = (props) => {
|
|
|
20373
20387
|
}) })] }));
|
|
20374
20388
|
};
|
|
20375
20389
|
|
|
20376
|
-
const useStyles$
|
|
20390
|
+
const useStyles$6 = makeStyles({
|
|
20377
20391
|
textureEditor: {
|
|
20378
20392
|
display: "flex",
|
|
20379
20393
|
flexDirection: "column",
|
|
@@ -20437,7 +20451,7 @@ const PREVIEW_UPDATE_DELAY_MS = 160;
|
|
|
20437
20451
|
*/
|
|
20438
20452
|
const TextureEditor = (props) => {
|
|
20439
20453
|
const { texture, toolProviders = [], window: editorWindow, onUpdate } = props;
|
|
20440
|
-
const classes = useStyles$
|
|
20454
|
+
const classes = useStyles$6();
|
|
20441
20455
|
// Canvas refs
|
|
20442
20456
|
const uiCanvasRef = useRef(null);
|
|
20443
20457
|
const canvas2DRef = useRef(null);
|
|
@@ -20585,7 +20599,7 @@ const TextureEditor = (props) => {
|
|
|
20585
20599
|
return (jsxs("div", { className: classes.textureEditor, children: [jsx(PropertiesBar, { texture: texture, saveTexture: saveTexture, pixelData: pixelData, face: face, setFace: setFace, resetTexture: resetTexture, resizeTexture: resizeTexture, uploadTexture: uploadTexture, mipLevel: mipLevel, setMipLevel: setMipLevel, size: canvasManagerRef.current?.size || size }), jsxs("div", { className: classes.mainContent, children: [jsxs("div", { className: classes.canvasContainer, style: { cursor }, children: [jsx("canvas", { ref: uiCanvasRef, className: classes.canvasUI, tabIndex: 1 }), jsx("canvas", { ref: canvas2DRef, className: classes.canvas2D }), jsx("canvas", { ref: canvas3DRef, className: classes.canvas3D })] }), CurrentToolSettings && (jsx("div", { className: classes.toolSettingsContainer, children: jsx(CurrentToolSettings, {}) })), !texture.isCube && (jsx("div", { className: classes.sidebarLeft, children: jsx(ToolBar, { tools: toolProviders, activeToolIndex: activeToolIndex, changeTool: changeTool, metadata: metadata, setMetadata: setMetadata, hasAlpha: hasAlpha }) })), jsx("div", { className: classes.sidebarRight, children: jsx(ChannelsBar, { channels: channels, setChannels: setChannels }) })] }), jsx(StatusBar, { texture: texture, mipLevel: mipLevel })] }));
|
|
20586
20600
|
};
|
|
20587
20601
|
|
|
20588
|
-
const useStyles$
|
|
20602
|
+
const useStyles$5 = makeStyles({
|
|
20589
20603
|
settingsContainer: {
|
|
20590
20604
|
display: "flex",
|
|
20591
20605
|
flexDirection: "column",
|
|
@@ -20605,7 +20619,7 @@ const Contrast = {
|
|
|
20605
20619
|
name: "Contrast/Exposure",
|
|
20606
20620
|
order: 500,
|
|
20607
20621
|
icon: () => {
|
|
20608
|
-
const classes = useStyles$
|
|
20622
|
+
const classes = useStyles$5();
|
|
20609
20623
|
return jsx(CircleHalfFillRegular, { className: classes.icon });
|
|
20610
20624
|
},
|
|
20611
20625
|
is3D: true,
|
|
@@ -20670,7 +20684,7 @@ const Contrast = {
|
|
|
20670
20684
|
setContrast(0);
|
|
20671
20685
|
},
|
|
20672
20686
|
settingsComponent: () => {
|
|
20673
|
-
const classes = useStyles$
|
|
20687
|
+
const classes = useStyles$5();
|
|
20674
20688
|
const [contrast, exposure] = useObservableState(useCallback(() => [_contrast, _exposure], []), stateChangedObservable);
|
|
20675
20689
|
const handleContrastChange = (_, data) => {
|
|
20676
20690
|
setContrast(data.value);
|
|
@@ -20771,7 +20785,7 @@ const Floodfill = {
|
|
|
20771
20785
|
},
|
|
20772
20786
|
};
|
|
20773
20787
|
|
|
20774
|
-
const useStyles$
|
|
20788
|
+
const useStyles$4 = makeStyles({
|
|
20775
20789
|
settingsContainer: {
|
|
20776
20790
|
display: "flex",
|
|
20777
20791
|
flexDirection: "column",
|
|
@@ -20895,7 +20909,7 @@ const Paintbrush = {
|
|
|
20895
20909
|
pointerObserver?.remove();
|
|
20896
20910
|
},
|
|
20897
20911
|
settingsComponent: () => {
|
|
20898
|
-
const classes = useStyles$
|
|
20912
|
+
const classes = useStyles$4();
|
|
20899
20913
|
const width = useObservableState(useCallback(() => _width, []), stateChangedObservable);
|
|
20900
20914
|
const handleWidthChange = (_, data) => {
|
|
20901
20915
|
setWidth(data.value);
|
|
@@ -22889,74 +22903,1428 @@ const ExportServiceDefinition = {
|
|
|
22889
22903
|
},
|
|
22890
22904
|
};
|
|
22891
22905
|
|
|
22892
|
-
const
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
opacity: 0.7,
|
|
22896
|
-
},
|
|
22897
|
-
busyMessage: {
|
|
22898
|
-
display: "flex",
|
|
22899
|
-
alignItems: "center",
|
|
22900
|
-
gap: tokens.spacingHorizontalXS,
|
|
22901
|
-
padding: `${tokens.spacingVerticalXXS} ${tokens.spacingHorizontalS}`,
|
|
22902
|
-
},
|
|
22903
|
-
});
|
|
22906
|
+
const OverrideManagerKey = Symbol("babylonjs:overrideManager");
|
|
22907
|
+
const OverrideManagerInternals = new WeakMap();
|
|
22908
|
+
const OnOverrideManagerCreatedObservable = new Observable();
|
|
22904
22909
|
/**
|
|
22905
|
-
*
|
|
22906
|
-
*
|
|
22907
|
-
* @
|
|
22910
|
+
* Creates a new OverrideManager state object and attaches it to the scene.
|
|
22911
|
+
*
|
|
22912
|
+
* Internal: callers should use {@link GetOverrideManager} which returns the
|
|
22913
|
+
* existing manager when one is already attached.
|
|
22914
|
+
* @param scene - The scene this manager operates on.
|
|
22915
|
+
* @returns The created override manager state.
|
|
22908
22916
|
*/
|
|
22909
|
-
|
|
22910
|
-
const
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
|
|
22914
|
-
const
|
|
22915
|
-
|
|
22916
|
-
|
|
22917
|
-
|
|
22917
|
+
function CreateOverrideManager(scene) {
|
|
22918
|
+
const manager = {
|
|
22919
|
+
scene,
|
|
22920
|
+
onChangedObservable: new Observable(),
|
|
22921
|
+
};
|
|
22922
|
+
const internal = {
|
|
22923
|
+
overrides: [],
|
|
22924
|
+
originalValues: new Map(),
|
|
22925
|
+
sceneDisposeObserver: null,
|
|
22926
|
+
};
|
|
22927
|
+
OverrideManagerInternals.set(manager, internal);
|
|
22928
|
+
if (!scene.metadata) {
|
|
22929
|
+
scene.metadata = {};
|
|
22930
|
+
}
|
|
22931
|
+
scene.metadata[OverrideManagerKey] = manager;
|
|
22932
|
+
// Auto-dispose when the scene is disposed so the manager doesn't outlive it.
|
|
22933
|
+
internal.sceneDisposeObserver = scene.onDisposeObservable.add(() => DisposeOverrideManager(manager));
|
|
22934
|
+
OnOverrideManagerCreatedObservable.notifyObservers(manager);
|
|
22935
|
+
return manager;
|
|
22936
|
+
}
|
|
22937
|
+
/**
|
|
22938
|
+
* Returns the OverrideManager attached to the given scene, creating and
|
|
22939
|
+
* attaching one if none exists.
|
|
22940
|
+
* @param scene - The scene to look up or attach a manager to.
|
|
22941
|
+
* @returns The existing or newly created OverrideManager.
|
|
22942
|
+
*/
|
|
22943
|
+
function GetOverrideManager(scene) {
|
|
22944
|
+
const existing = scene.metadata?.[OverrideManagerKey];
|
|
22945
|
+
if (existing) {
|
|
22946
|
+
return existing;
|
|
22947
|
+
}
|
|
22948
|
+
return CreateOverrideManager(scene);
|
|
22949
|
+
}
|
|
22950
|
+
/**
|
|
22951
|
+
* Adds an override entry and immediately applies it.
|
|
22952
|
+
* If an override with the same target coordinates already exists, it is replaced.
|
|
22953
|
+
*
|
|
22954
|
+
* When the caller has already mutated the target (e.g. an Inspector edit),
|
|
22955
|
+
* pass `{ originalValue }` containing the property's prior value — this seeds
|
|
22956
|
+
* the original-value map (so {@link RemoveOverride} can restore it) and skips
|
|
22957
|
+
* the redundant apply step.
|
|
22958
|
+
* @param scene - The scene whose override registry to update.
|
|
22959
|
+
* @param entry - The override to add.
|
|
22960
|
+
* @param options - Optional behavior modifiers; see {@link AddOverrideOptions}.
|
|
22961
|
+
*/
|
|
22962
|
+
function AddOverride(scene, entry, options) {
|
|
22963
|
+
const manager = GetOverrideManager(scene);
|
|
22964
|
+
const internal = GetOverrideInternals(manager);
|
|
22965
|
+
RemoveMatchingOverride(internal, entry.targetType, entry.targetName, entry.targetIndex, entry.propertyPath);
|
|
22966
|
+
internal.overrides.push(entry);
|
|
22967
|
+
if (options && "originalValue" in options) {
|
|
22968
|
+
// Caller already applied the new value. Seed the captured original from
|
|
22969
|
+
// their pre-edit snapshot — otherwise ApplyOverrideEntry would capture
|
|
22970
|
+
// the post-edit value and RemoveOverride would have nothing to restore.
|
|
22971
|
+
const origKey = MakeOriginalValueKey(entry.targetType, entry.targetName, entry.targetIndex, entry.propertyPath);
|
|
22972
|
+
if (!internal.originalValues.has(origKey)) {
|
|
22973
|
+
internal.originalValues.set(origKey, CloneValue(options.originalValue));
|
|
22918
22974
|
}
|
|
22919
|
-
|
|
22920
|
-
|
|
22921
|
-
|
|
22922
|
-
|
|
22923
|
-
|
|
22924
|
-
|
|
22925
|
-
|
|
22975
|
+
}
|
|
22976
|
+
else {
|
|
22977
|
+
ApplyOverrideEntry(manager, internal, entry);
|
|
22978
|
+
}
|
|
22979
|
+
manager.onChangedObservable.notifyObservers();
|
|
22980
|
+
}
|
|
22981
|
+
/**
|
|
22982
|
+
* Returns all overrides currently registered with the scene.
|
|
22983
|
+
* @param scene - The scene whose override registry to read.
|
|
22984
|
+
* @returns A read-only array of override entries.
|
|
22985
|
+
*/
|
|
22986
|
+
function GetOverrides(scene) {
|
|
22987
|
+
return GetOverrideInternals(GetOverrideManager(scene)).overrides;
|
|
22988
|
+
}
|
|
22989
|
+
/**
|
|
22990
|
+
* Removes all overrides, optionally restoring original values.
|
|
22991
|
+
* @param scene - The scene whose override registry to clear.
|
|
22992
|
+
* @param restoreOriginals - If true, restores all captured original values.
|
|
22993
|
+
*/
|
|
22994
|
+
function ClearOverrides(scene, restoreOriginals = false) {
|
|
22995
|
+
const manager = GetOverrideManager(scene);
|
|
22996
|
+
const internal = GetOverrideInternals(manager);
|
|
22997
|
+
if (restoreOriginals) {
|
|
22998
|
+
// Snapshot the entries so we can restore each original without firing
|
|
22999
|
+
// an onChangedObservable notification per entry; consumers only need
|
|
23000
|
+
// one signal that the registry was emptied.
|
|
23001
|
+
const entries = [...internal.overrides];
|
|
23002
|
+
internal.overrides.length = 0;
|
|
23003
|
+
for (const entry of entries) {
|
|
23004
|
+
const origKey = MakeOriginalValueKey(entry.targetType, entry.targetName, entry.targetIndex, entry.propertyPath);
|
|
23005
|
+
const original = internal.originalValues.get(origKey);
|
|
23006
|
+
if (original !== undefined) {
|
|
23007
|
+
const target = ResolveTarget(manager.scene, entry.targetType, entry.targetName, entry.targetIndex);
|
|
23008
|
+
if (target) {
|
|
23009
|
+
SetNestedProperty(target, entry.propertyPath, original);
|
|
23010
|
+
}
|
|
23011
|
+
}
|
|
22926
23012
|
}
|
|
22927
|
-
|
|
22928
|
-
|
|
23013
|
+
internal.originalValues.clear();
|
|
23014
|
+
manager.onChangedObservable.notifyObservers();
|
|
23015
|
+
return;
|
|
23016
|
+
}
|
|
23017
|
+
internal.overrides.length = 0;
|
|
23018
|
+
internal.originalValues.clear();
|
|
23019
|
+
manager.onChangedObservable.notifyObservers();
|
|
23020
|
+
}
|
|
23021
|
+
/**
|
|
23022
|
+
* Updates the target coordinates on the override matching a specific (type,
|
|
23023
|
+
* old-name, old-index) so it follows an entity rename. Used by capture services
|
|
23024
|
+
* to keep overrides attached to a specific object after the user renames it.
|
|
23025
|
+
*
|
|
23026
|
+
* Only the override at the exact `(targetType, oldName, oldIndex)` slot is
|
|
23027
|
+
* updated, so other same-named siblings keep their own overrides untouched.
|
|
23028
|
+
*
|
|
23029
|
+
* @param scene - The scene whose override registry to update.
|
|
23030
|
+
* @param targetType - The target type.
|
|
23031
|
+
* @param oldName - The previous name of the renamed entity.
|
|
23032
|
+
* @param oldIndex - The previous index of the renamed entity among same-named siblings.
|
|
23033
|
+
* @param newName - The new name of the renamed entity.
|
|
23034
|
+
* @param newIndex - The new index of the renamed entity among same-named siblings.
|
|
23035
|
+
*/
|
|
23036
|
+
function RenameOverrideTarget(scene, targetType, oldName, oldIndex, newName, newIndex) {
|
|
23037
|
+
const manager = GetOverrideManager(scene);
|
|
23038
|
+
const internal = GetOverrideInternals(manager);
|
|
23039
|
+
let changed = false;
|
|
23040
|
+
for (let i = 0; i < internal.overrides.length; i++) {
|
|
23041
|
+
const entry = internal.overrides[i];
|
|
23042
|
+
if (entry.targetType === targetType && entry.targetName === oldName && entry.targetIndex === oldIndex) {
|
|
23043
|
+
internal.overrides[i] = { ...entry, targetName: newName, targetIndex: newIndex };
|
|
23044
|
+
changed = true;
|
|
23045
|
+
}
|
|
23046
|
+
}
|
|
23047
|
+
// Update original-value keys to match the new identity
|
|
23048
|
+
const oldPrefix = `${targetType}::${oldName}::${oldIndex}::`;
|
|
23049
|
+
const newPrefix = `${targetType}::${newName}::${newIndex}::`;
|
|
23050
|
+
for (const [origKey, value] of Array.from(internal.originalValues.entries())) {
|
|
23051
|
+
if (origKey.startsWith(oldPrefix)) {
|
|
23052
|
+
const propertyPath = origKey.substring(oldPrefix.length);
|
|
23053
|
+
internal.originalValues.set(newPrefix + propertyPath, value);
|
|
23054
|
+
internal.originalValues.delete(origKey);
|
|
23055
|
+
}
|
|
23056
|
+
}
|
|
23057
|
+
if (changed) {
|
|
23058
|
+
manager.onChangedObservable.notifyObservers();
|
|
23059
|
+
}
|
|
23060
|
+
}
|
|
23061
|
+
/**
|
|
23062
|
+
* Rewrites override *values* that reference an entity by name when that entity
|
|
23063
|
+
* has been renamed. Mirrors {@link RenameOverrideTarget} but operates on the
|
|
23064
|
+
* `value` field rather than the `targetName` field, so overrides whose value
|
|
23065
|
+
* is `"ref:oldName"` (material/light/camera references) or `"texture:oldName"`
|
|
23066
|
+
* (non-SmartAsset texture references) follow the rename instead of silently
|
|
23067
|
+
* pointing at a non-existent entity.
|
|
23068
|
+
*
|
|
23069
|
+
* SmartAsset texture references (`"samTexture:<key>"`) are unaffected because
|
|
23070
|
+
* the SmartAsset key is decoupled from the texture's runtime `name` field.
|
|
23071
|
+
*
|
|
23072
|
+
* @param scene - The scene whose override registry to update.
|
|
23073
|
+
* @param valueScheme - Which encoded-reference prefix to rewrite: `"ref"` for
|
|
23074
|
+
* material/light/camera references, `"texture"` for non-SAM textures.
|
|
23075
|
+
* @param oldName - The previous name embedded in the reference.
|
|
23076
|
+
* @param newName - The new name embedded in the reference.
|
|
23077
|
+
*/
|
|
23078
|
+
function RenameOverrideValueReferences(scene, valueScheme, oldName, newName) {
|
|
23079
|
+
if (oldName === newName) {
|
|
23080
|
+
return;
|
|
23081
|
+
}
|
|
23082
|
+
const manager = GetOverrideManager(scene);
|
|
23083
|
+
const internal = GetOverrideInternals(manager);
|
|
23084
|
+
const oldValue = `${valueScheme}:${oldName}`;
|
|
23085
|
+
const newValue = `${valueScheme}:${newName}`;
|
|
23086
|
+
let changed = false;
|
|
23087
|
+
for (let i = 0; i < internal.overrides.length; i++) {
|
|
23088
|
+
const entry = internal.overrides[i];
|
|
23089
|
+
if (entry.value === oldValue) {
|
|
23090
|
+
internal.overrides[i] = { ...entry, value: newValue };
|
|
23091
|
+
changed = true;
|
|
22929
23092
|
}
|
|
22930
|
-
|
|
22931
|
-
|
|
23093
|
+
}
|
|
23094
|
+
if (changed) {
|
|
23095
|
+
manager.onChangedObservable.notifyObservers();
|
|
23096
|
+
}
|
|
23097
|
+
}
|
|
23098
|
+
// ── Application ──
|
|
23099
|
+
/**
|
|
23100
|
+
* Applies all overrides to their current targets in the scene.
|
|
23101
|
+
*
|
|
23102
|
+
* Call this after any scene mutation that might have invalidated previously
|
|
23103
|
+
* applied state (asset reload, object recreation, project load). The override
|
|
23104
|
+
* manager does not auto-subscribe to other scene subsystems — coordination is
|
|
23105
|
+
* the caller's responsibility, which keeps the override system independent.
|
|
23106
|
+
* @param scene - The scene whose overrides to apply.
|
|
23107
|
+
*/
|
|
23108
|
+
function ApplyAllOverrides(scene) {
|
|
23109
|
+
const manager = GetOverrideManager(scene);
|
|
23110
|
+
const internal = GetOverrideInternals(manager);
|
|
23111
|
+
for (const entry of internal.overrides) {
|
|
23112
|
+
ApplyOverrideEntry(manager, internal, entry);
|
|
23113
|
+
}
|
|
23114
|
+
}
|
|
23115
|
+
// ── Serialization ──
|
|
23116
|
+
/**
|
|
23117
|
+
* Serializes all overrides to a JSON-compatible array.
|
|
23118
|
+
* The on-disk shape is identical to the in-memory `IOverrideEntry`.
|
|
23119
|
+
* @param scene - The scene whose overrides to serialize.
|
|
23120
|
+
* @returns An array of override entries (shallow copies).
|
|
23121
|
+
*/
|
|
23122
|
+
function SerializeOverrides(scene) {
|
|
23123
|
+
const internal = GetOverrideInternals(GetOverrideManager(scene));
|
|
23124
|
+
return internal.overrides.map((o) => ({ ...o }));
|
|
23125
|
+
}
|
|
23126
|
+
/**
|
|
23127
|
+
* Loads overrides from a serialized array and applies them.
|
|
23128
|
+
* @param scene - The scene whose override registry to populate.
|
|
23129
|
+
* @param data - Array of override entries.
|
|
23130
|
+
*/
|
|
23131
|
+
function DeserializeAndApplyOverrides(scene, data) {
|
|
23132
|
+
if (!Array.isArray(data)) {
|
|
23133
|
+
throw new Error("OverrideManager: Expected an array of override entries.");
|
|
23134
|
+
}
|
|
23135
|
+
for (const entry of data) {
|
|
23136
|
+
if (!entry.targetType || entry.targetName === undefined || typeof entry.targetIndex !== "number" || !entry.propertyPath || entry.value === undefined) {
|
|
23137
|
+
Logger.Warn("OverrideManager: Skipping invalid override entry.");
|
|
23138
|
+
continue;
|
|
22932
23139
|
}
|
|
22933
|
-
|
|
22934
|
-
|
|
22935
|
-
|
|
22936
|
-
|
|
22937
|
-
|
|
23140
|
+
AddOverride(scene, entry);
|
|
23141
|
+
}
|
|
23142
|
+
}
|
|
23143
|
+
// ── Lifecycle ──
|
|
23144
|
+
/**
|
|
23145
|
+
* Disposes the manager, clearing all overrides and detaching it from its scene.
|
|
23146
|
+
* Safe to call multiple times; subsequent calls are no-ops. Automatically invoked when the
|
|
23147
|
+
* owning scene is disposed.
|
|
23148
|
+
* @param manager - The override manager state.
|
|
23149
|
+
*/
|
|
23150
|
+
function DisposeOverrideManager(manager) {
|
|
23151
|
+
const internal = OverrideManagerInternals.get(manager);
|
|
23152
|
+
if (!internal) {
|
|
23153
|
+
return;
|
|
23154
|
+
}
|
|
23155
|
+
OverrideManagerInternals.delete(manager);
|
|
23156
|
+
if (internal.sceneDisposeObserver) {
|
|
23157
|
+
manager.scene.onDisposeObservable.remove(internal.sceneDisposeObserver);
|
|
23158
|
+
internal.sceneDisposeObserver = null;
|
|
23159
|
+
}
|
|
23160
|
+
internal.overrides.length = 0;
|
|
23161
|
+
internal.originalValues.clear();
|
|
23162
|
+
manager.onChangedObservable.clear();
|
|
23163
|
+
if (manager.scene.metadata) {
|
|
23164
|
+
delete manager.scene.metadata[OverrideManagerKey];
|
|
23165
|
+
}
|
|
23166
|
+
}
|
|
23167
|
+
// ── Private ──
|
|
23168
|
+
function GetOverrideInternals(manager) {
|
|
23169
|
+
const internal = OverrideManagerInternals.get(manager);
|
|
23170
|
+
if (!internal) {
|
|
23171
|
+
throw new Error("OverrideManager: Unknown manager state.");
|
|
23172
|
+
}
|
|
23173
|
+
return internal;
|
|
23174
|
+
}
|
|
23175
|
+
/**
|
|
23176
|
+
* Applies a single override entry to its target, capturing the original value
|
|
23177
|
+
* on the first application so {@link RemoveOverride} can restore it later.
|
|
23178
|
+
* @param manager - The override manager owning the entry.
|
|
23179
|
+
* @param internal - The manager's internal state.
|
|
23180
|
+
* @param entry - The override to apply.
|
|
23181
|
+
*/
|
|
23182
|
+
function ApplyOverrideEntry(manager, internal, entry) {
|
|
23183
|
+
const target = ResolveTarget(manager.scene, entry.targetType, entry.targetName, entry.targetIndex);
|
|
23184
|
+
if (!target) {
|
|
23185
|
+
Logger.Warn(`OverrideManager: target not found for type="${entry.targetType}" name="${entry.targetName}" index=${entry.targetIndex} prop="${entry.propertyPath}"`);
|
|
23186
|
+
return; // Target not loaded yet — override will be applied on next ApplyAllOverrides
|
|
23187
|
+
}
|
|
23188
|
+
// Capture original value before first override
|
|
23189
|
+
const origKey = MakeOriginalValueKey(entry.targetType, entry.targetName, entry.targetIndex, entry.propertyPath);
|
|
23190
|
+
if (!internal.originalValues.has(origKey)) {
|
|
23191
|
+
const currentValue = GetNestedProperty(target, entry.propertyPath);
|
|
23192
|
+
if (currentValue !== undefined) {
|
|
23193
|
+
internal.originalValues.set(origKey, CloneValue(currentValue));
|
|
22938
23194
|
}
|
|
22939
|
-
|
|
23195
|
+
}
|
|
23196
|
+
const resolvedValue = ResolveOverrideValue(manager.scene, entry.value);
|
|
23197
|
+
SetNestedProperty(target, entry.propertyPath, resolvedValue);
|
|
23198
|
+
}
|
|
23199
|
+
/**
|
|
23200
|
+
* Locates a scene object by (targetType, targetName, targetIndex). The scene
|
|
23201
|
+
* collection is filtered to objects matching `targetName`; the N-th survivor
|
|
23202
|
+
* (per `targetIndex`) is returned. Falls back to the first match if the index
|
|
23203
|
+
* is out of range — useful when the scene shape has changed since capture.
|
|
23204
|
+
* @param scene - The scene to search.
|
|
23205
|
+
* @param targetType - The override target type.
|
|
23206
|
+
* @param targetName - The target object name (or "" for scene-level).
|
|
23207
|
+
* @param targetIndex - The target's position among same-named siblings.
|
|
23208
|
+
* @returns The matching scene object, or null if not found.
|
|
23209
|
+
*/
|
|
23210
|
+
function ResolveTarget(scene, targetType, targetName, targetIndex) {
|
|
23211
|
+
// Scene-level overrides target the scene itself
|
|
23212
|
+
if (targetType === "scene") {
|
|
23213
|
+
return scene;
|
|
23214
|
+
}
|
|
23215
|
+
const collection = GetCollection$1(scene, targetType);
|
|
23216
|
+
if (!collection) {
|
|
23217
|
+
return null;
|
|
23218
|
+
}
|
|
23219
|
+
const matches = collection.filter((obj) => obj.name === targetName);
|
|
23220
|
+
if (matches.length === 0) {
|
|
23221
|
+
return null;
|
|
23222
|
+
}
|
|
23223
|
+
return (matches[targetIndex] ?? matches[0]);
|
|
23224
|
+
}
|
|
23225
|
+
/**
|
|
23226
|
+
* Returns the scene collection corresponding to an override target type.
|
|
23227
|
+
* @param scene - The scene to inspect.
|
|
23228
|
+
* @param targetType - The target type.
|
|
23229
|
+
* @returns The collection, or null if the type has no collection.
|
|
23230
|
+
*/
|
|
23231
|
+
function GetCollection$1(scene, targetType) {
|
|
23232
|
+
switch (targetType) {
|
|
23233
|
+
case "meshes":
|
|
23234
|
+
return scene.meshes;
|
|
23235
|
+
case "materials":
|
|
23236
|
+
return scene.materials;
|
|
23237
|
+
case "textures":
|
|
23238
|
+
return scene.textures;
|
|
23239
|
+
case "lights":
|
|
23240
|
+
return scene.lights;
|
|
23241
|
+
case "cameras":
|
|
23242
|
+
return scene.cameras;
|
|
23243
|
+
case "animationGroups":
|
|
23244
|
+
return scene.animationGroups;
|
|
23245
|
+
default:
|
|
23246
|
+
return null;
|
|
23247
|
+
}
|
|
23248
|
+
}
|
|
23249
|
+
/**
|
|
23250
|
+
* Resolves an override value, expanding string references like "ref:name",
|
|
23251
|
+
* "samTexture:key", or "texture:name" into the actual scene object they refer to.
|
|
23252
|
+
* @param scene - The scene used to look up references.
|
|
23253
|
+
* @param value - The serialized override value.
|
|
23254
|
+
* @returns The runtime value to assign to the target property.
|
|
23255
|
+
*/
|
|
23256
|
+
function ResolveOverrideValue(scene, value) {
|
|
23257
|
+
if (typeof value === "string") {
|
|
23258
|
+
if (value.startsWith("ref:")) {
|
|
23259
|
+
return ResolveObjectReference(scene, value.substring(4));
|
|
23260
|
+
}
|
|
23261
|
+
if (value.startsWith("samTexture:")) {
|
|
23262
|
+
return ResolveSamTextureReference(scene, value.substring(11));
|
|
23263
|
+
}
|
|
23264
|
+
if (value.startsWith("texture:")) {
|
|
23265
|
+
return ResolveTextureReference(scene, value.substring(8));
|
|
23266
|
+
}
|
|
23267
|
+
}
|
|
23268
|
+
// Number arrays are passed through as-is. SetNestedProperty will use
|
|
23269
|
+
// the live target's `fromArray` method (Vector3, Color3, etc.) to push
|
|
23270
|
+
// values in-place, preserving the math instance identity.
|
|
23271
|
+
return value;
|
|
23272
|
+
}
|
|
23273
|
+
/**
|
|
23274
|
+
* Resolves a "ref:name" value by looking up a material, light, or camera
|
|
23275
|
+
* in the scene by name.
|
|
23276
|
+
* @param scene - The scene to search.
|
|
23277
|
+
* @param name - The object name to resolve.
|
|
23278
|
+
* @returns The matching material, light, or camera, or undefined if not found.
|
|
23279
|
+
*/
|
|
23280
|
+
function ResolveObjectReference(scene, name) {
|
|
23281
|
+
const mat = scene.materials.find((m) => m.name === name);
|
|
23282
|
+
if (mat) {
|
|
23283
|
+
return mat;
|
|
23284
|
+
}
|
|
23285
|
+
const light = scene.lights.find((l) => l.name === name);
|
|
23286
|
+
if (light) {
|
|
23287
|
+
return light;
|
|
23288
|
+
}
|
|
23289
|
+
const camera = scene.cameras.find((c) => c.name === name);
|
|
23290
|
+
if (camera) {
|
|
23291
|
+
return camera;
|
|
23292
|
+
}
|
|
23293
|
+
Logger.Warn(`OverrideManager: Object reference "${name}" not found in scene.`);
|
|
23294
|
+
return undefined;
|
|
23295
|
+
}
|
|
23296
|
+
/**
|
|
23297
|
+
* Resolves a "texture:name" value by looking up a texture in the scene by name.
|
|
23298
|
+
* @param scene - The scene to search.
|
|
23299
|
+
* @param name - The texture name to resolve.
|
|
23300
|
+
* @returns The matching texture, or undefined if not found.
|
|
23301
|
+
*/
|
|
23302
|
+
function ResolveTextureReference(scene, name) {
|
|
23303
|
+
const tex = scene.textures.find((t) => t.name === name);
|
|
23304
|
+
if (tex) {
|
|
23305
|
+
return tex;
|
|
23306
|
+
}
|
|
23307
|
+
Logger.Warn(`OverrideManager: Texture reference "${name}" not found.`);
|
|
23308
|
+
return undefined;
|
|
23309
|
+
}
|
|
23310
|
+
/**
|
|
23311
|
+
* Resolves a "samTexture:key" value by looking up a SmartAsset-tracked texture
|
|
23312
|
+
* by its registry key. The SAM key is stable across save/load whereas the
|
|
23313
|
+
* texture's `name` (for SAM textures, the blob URL) changes on every reload,
|
|
23314
|
+
* so this is the only reliable way to round-trip texture references on
|
|
23315
|
+
* user-uploaded SmartAsset textures.
|
|
23316
|
+
* @param scene - The scene to search.
|
|
23317
|
+
* @param key - The SmartAsset key to resolve.
|
|
23318
|
+
* @returns The matching texture, or undefined if not found.
|
|
23319
|
+
*/
|
|
23320
|
+
function ResolveSamTextureReference(scene, key) {
|
|
23321
|
+
const tex = scene.textures.find((t) => FindSmartAssetKeyForObject(scene, t) === key);
|
|
23322
|
+
if (tex) {
|
|
23323
|
+
return tex;
|
|
23324
|
+
}
|
|
23325
|
+
Logger.Warn(`OverrideManager: SmartAsset texture "${key}" not found.`);
|
|
23326
|
+
return undefined;
|
|
23327
|
+
}
|
|
23328
|
+
/**
|
|
23329
|
+
* Finds the index of an override matching the given coordinates.
|
|
23330
|
+
* @param internal - The manager's internal state.
|
|
23331
|
+
* @param targetType - The target type.
|
|
23332
|
+
* @param targetName - The target object name.
|
|
23333
|
+
* @param targetIndex - The target index among same-named siblings.
|
|
23334
|
+
* @param propertyPath - The property path.
|
|
23335
|
+
* @returns The matching index, or -1 if none found.
|
|
23336
|
+
*/
|
|
23337
|
+
function FindOverrideIndex(internal, targetType, targetName, targetIndex, propertyPath) {
|
|
23338
|
+
return internal.overrides.findIndex((o) => o.targetType === targetType && o.targetName === targetName && o.targetIndex === targetIndex && o.propertyPath === propertyPath);
|
|
23339
|
+
}
|
|
23340
|
+
/**
|
|
23341
|
+
* Removes any existing override that matches the given coordinates. Used by
|
|
23342
|
+
* {@link AddOverride} to enforce one entry per (type, name, index, property).
|
|
23343
|
+
* @param internal - The manager's internal state.
|
|
23344
|
+
* @param targetType - The target type.
|
|
23345
|
+
* @param targetName - The target object name.
|
|
23346
|
+
* @param targetIndex - The target index among same-named siblings.
|
|
23347
|
+
* @param propertyPath - The property path.
|
|
23348
|
+
*/
|
|
23349
|
+
function RemoveMatchingOverride(internal, targetType, targetName, targetIndex, propertyPath) {
|
|
23350
|
+
const idx = FindOverrideIndex(internal, targetType, targetName, targetIndex, propertyPath);
|
|
23351
|
+
if (idx >= 0) {
|
|
23352
|
+
internal.overrides.splice(idx, 1);
|
|
23353
|
+
}
|
|
23354
|
+
}
|
|
23355
|
+
/**
|
|
23356
|
+
* Creates a unique key for storing original values.
|
|
23357
|
+
* @param targetType - The override target type.
|
|
23358
|
+
* @param targetName - The target object name.
|
|
23359
|
+
* @param targetIndex - The target index among same-named siblings.
|
|
23360
|
+
* @param propertyPath - The property path.
|
|
23361
|
+
* @returns A composite string key uniquely identifying the original value slot.
|
|
23362
|
+
*/
|
|
23363
|
+
function MakeOriginalValueKey(targetType, targetName, targetIndex, propertyPath) {
|
|
23364
|
+
return `${targetType}::${targetName}::${targetIndex}::${propertyPath}`;
|
|
23365
|
+
}
|
|
23366
|
+
/**
|
|
23367
|
+
* Gets a nested property from an object using a dot-separated path.
|
|
23368
|
+
* @param obj - The root object to traverse.
|
|
23369
|
+
* @param path - The dot-separated property path.
|
|
23370
|
+
* @returns The value at the path, or undefined if any segment is missing.
|
|
23371
|
+
*/
|
|
23372
|
+
function GetNestedProperty(obj, path) {
|
|
23373
|
+
const parts = path.split(".");
|
|
23374
|
+
let current = obj;
|
|
23375
|
+
for (const part of parts) {
|
|
23376
|
+
if (current === null || current === undefined || typeof current !== "object") {
|
|
23377
|
+
return undefined;
|
|
23378
|
+
}
|
|
23379
|
+
current = current[part];
|
|
23380
|
+
}
|
|
23381
|
+
return current;
|
|
23382
|
+
}
|
|
23383
|
+
/**
|
|
23384
|
+
* Sets a nested property on an object using a dot-separated path.
|
|
23385
|
+
*
|
|
23386
|
+
* When the value is a number array and the existing property is a Babylon
|
|
23387
|
+
* math type (Vector*, Quaternion, Color3/4, Matrix), uses the math type's
|
|
23388
|
+
* `fromArray` method to mutate it in place — preserving the live instance
|
|
23389
|
+
* identity that consumers may already hold references to. Otherwise falls
|
|
23390
|
+
* back to direct property replacement.
|
|
23391
|
+
* @param obj - The root object to mutate.
|
|
23392
|
+
* @param path - The dot-separated property path.
|
|
23393
|
+
* @param value - The new value to assign.
|
|
23394
|
+
*/
|
|
23395
|
+
function SetNestedProperty(obj, path, value) {
|
|
23396
|
+
const parts = path.split(".");
|
|
23397
|
+
let current = obj;
|
|
23398
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
23399
|
+
if (current === null || current === undefined || typeof current !== "object") {
|
|
22940
23400
|
return;
|
|
22941
23401
|
}
|
|
22942
|
-
|
|
22943
|
-
|
|
23402
|
+
current = current[parts[i]];
|
|
23403
|
+
}
|
|
23404
|
+
if (current === null || current === undefined || typeof current !== "object") {
|
|
23405
|
+
return;
|
|
23406
|
+
}
|
|
23407
|
+
const lastPart = parts[parts.length - 1];
|
|
23408
|
+
const existing = current[lastPart];
|
|
23409
|
+
if (Array.isArray(value) && existing && typeof existing === "object" && typeof existing.fromArray === "function") {
|
|
23410
|
+
existing.fromArray(value);
|
|
23411
|
+
return;
|
|
23412
|
+
}
|
|
23413
|
+
current[lastPart] = value;
|
|
23414
|
+
}
|
|
23415
|
+
/**
|
|
23416
|
+
* Snapshots a value for original-value tracking.
|
|
23417
|
+
*
|
|
23418
|
+
* Scene entities (textures, materials, meshes, etc.) are stored by reference
|
|
23419
|
+
* because cloning them would register unwanted duplicates in the scene.
|
|
23420
|
+
* Plain math types (Vector3, Color3, etc.) are cloned so mutations to the
|
|
23421
|
+
* live object don't corrupt the saved original.
|
|
23422
|
+
* @param value - The value to snapshot.
|
|
23423
|
+
* @returns The snapshot value (cloned for plain math types, by reference for entities).
|
|
23424
|
+
*/
|
|
23425
|
+
function CloneValue(value) {
|
|
23426
|
+
if (value === null || value === undefined) {
|
|
23427
|
+
return value;
|
|
23428
|
+
}
|
|
23429
|
+
if (typeof value !== "object") {
|
|
23430
|
+
return value;
|
|
23431
|
+
}
|
|
23432
|
+
if (typeof value.getScene === "function") {
|
|
23433
|
+
return value;
|
|
23434
|
+
}
|
|
23435
|
+
if ("clone" in value && typeof value.clone === "function") {
|
|
23436
|
+
return value.clone();
|
|
23437
|
+
}
|
|
23438
|
+
return { ...value };
|
|
23439
|
+
}
|
|
23440
|
+
|
|
23441
|
+
/**
|
|
23442
|
+
* Inspector service that captures property edits made through Inspector and
|
|
23443
|
+
* feeds them to the OverrideManager as persistent overrides.
|
|
23444
|
+
*
|
|
23445
|
+
* Works on any scene object — overrides have no concept of "which asset"
|
|
23446
|
+
* owns an object. When multiple objects share a name, an entity's position
|
|
23447
|
+
* among same-named siblings (`targetIndex`) is captured so the override
|
|
23448
|
+
* re-applies to the same object after reload.
|
|
23449
|
+
*
|
|
23450
|
+
* The service re-attaches to the current scene whenever it changes, so
|
|
23451
|
+
* overrides are captured against the active scene even after loads/swaps.
|
|
23452
|
+
*/
|
|
23453
|
+
const OverrideCaptureServiceDefinition = {
|
|
23454
|
+
friendlyName: "Override Capture",
|
|
23455
|
+
consumes: [SceneContextIdentity, PropertiesServiceIdentity],
|
|
23456
|
+
factory: (sceneContext, propertiesService) => {
|
|
23457
|
+
// Track each entity's name + index at first contact so we can update
|
|
23458
|
+
// existing overrides when the user renames the entity in Inspector.
|
|
23459
|
+
// Re-created on each scene attach so identities don't leak across scenes.
|
|
23460
|
+
let previousIdentity = new WeakMap();
|
|
23461
|
+
let changeObserver = null;
|
|
23462
|
+
function attachToScene(scene) {
|
|
23463
|
+
if (changeObserver) {
|
|
23464
|
+
changeObserver.remove();
|
|
23465
|
+
changeObserver = null;
|
|
23466
|
+
}
|
|
23467
|
+
previousIdentity = new WeakMap();
|
|
23468
|
+
if (!scene) {
|
|
23469
|
+
return;
|
|
23470
|
+
}
|
|
23471
|
+
changeObserver = propertiesService.onPropertyChanged.add((changeInfo) => {
|
|
23472
|
+
const { entity, propertyKey, oldValue, newValue } = changeInfo;
|
|
23473
|
+
// When "name" changes, update the matching override so it follows the rename
|
|
23474
|
+
// instead of creating a new (orphaned) one. Also rewrite any overrides
|
|
23475
|
+
// whose *value* referenced this entity by name so cross-references
|
|
23476
|
+
// (e.g. `mesh.material = ref:redMat`) survive the rename too.
|
|
23477
|
+
if (propertyKey === "name" && typeof newValue === "string") {
|
|
23478
|
+
const targetType = ClassifyEntity(entity, scene);
|
|
23479
|
+
if (targetType !== null && targetType !== "scene") {
|
|
23480
|
+
const previous = previousIdentity.get(entity);
|
|
23481
|
+
if (previous && previous.name !== newValue) {
|
|
23482
|
+
// The entity already has the new name at this point, so compute its new index among same-named siblings.
|
|
23483
|
+
const newIndex = ComputeTargetIndex(scene, targetType, entity, newValue);
|
|
23484
|
+
RenameOverrideTarget(scene, targetType, previous.name, previous.index, newValue, newIndex);
|
|
23485
|
+
// Mirror the rename into override values that reference
|
|
23486
|
+
// this entity by name. SmartAsset textures use the
|
|
23487
|
+
// `samTexture:<key>` form and stay decoupled from the
|
|
23488
|
+
// texture's runtime name, so they don't need rewriting.
|
|
23489
|
+
const valueScheme = targetType === "textures" ? "texture" : "ref";
|
|
23490
|
+
RenameOverrideValueReferences(scene, valueScheme, previous.name, newValue);
|
|
23491
|
+
}
|
|
23492
|
+
previousIdentity.set(entity, { name: newValue, index: ComputeTargetIndex(scene, targetType, entity, newValue) });
|
|
23493
|
+
}
|
|
23494
|
+
return;
|
|
23495
|
+
}
|
|
23496
|
+
if (propertyKey === "id") {
|
|
23497
|
+
return;
|
|
23498
|
+
}
|
|
23499
|
+
let targetType = ClassifyEntity(entity, scene);
|
|
23500
|
+
let targetName;
|
|
23501
|
+
let targetIndex;
|
|
23502
|
+
let propertyPath = String(propertyKey);
|
|
23503
|
+
let targetEntity;
|
|
23504
|
+
if (targetType !== null) {
|
|
23505
|
+
if (targetType === "scene") {
|
|
23506
|
+
targetName = "";
|
|
23507
|
+
targetIndex = 0;
|
|
23508
|
+
targetEntity = scene;
|
|
23509
|
+
}
|
|
23510
|
+
else {
|
|
23511
|
+
targetName = GetEntityName(entity);
|
|
23512
|
+
targetIndex = ComputeTargetIndex(scene, targetType, entity, targetName);
|
|
23513
|
+
targetEntity = entity;
|
|
23514
|
+
}
|
|
23515
|
+
// Seed identity on first contact so rename tracking works
|
|
23516
|
+
if (!previousIdentity.has(targetEntity) && targetName) {
|
|
23517
|
+
previousIdentity.set(targetEntity, { name: targetName, index: targetIndex });
|
|
23518
|
+
}
|
|
23519
|
+
}
|
|
23520
|
+
else {
|
|
23521
|
+
// Sub-object: check if this is a property of a known parent
|
|
23522
|
+
const parentInfo = FindParentEntity(entity, scene);
|
|
23523
|
+
if (!parentInfo) {
|
|
23524
|
+
return;
|
|
23525
|
+
}
|
|
23526
|
+
targetType = parentInfo.targetType;
|
|
23527
|
+
targetName = parentInfo.targetName;
|
|
23528
|
+
targetIndex = parentInfo.targetIndex;
|
|
23529
|
+
propertyPath = `${parentInfo.parentProperty}.${propertyPath}`;
|
|
23530
|
+
}
|
|
23531
|
+
const serializedValue = SerializeOverrideValueForCapture(newValue, scene);
|
|
23532
|
+
if (serializedValue === undefined) {
|
|
23533
|
+
return;
|
|
23534
|
+
}
|
|
23535
|
+
// The Inspector binding has already written `newValue` to the
|
|
23536
|
+
// entity, so pass `oldValue` so the manager can record the
|
|
23537
|
+
// true pre-edit value (without this, RemoveOverride would have
|
|
23538
|
+
// no record of the original and could not restore it).
|
|
23539
|
+
AddOverride(scene, {
|
|
23540
|
+
targetType,
|
|
23541
|
+
targetName,
|
|
23542
|
+
targetIndex,
|
|
23543
|
+
propertyPath,
|
|
23544
|
+
value: serializedValue,
|
|
23545
|
+
}, { originalValue: oldValue });
|
|
23546
|
+
});
|
|
23547
|
+
}
|
|
23548
|
+
attachToScene(sceneContext.currentScene);
|
|
23549
|
+
const sceneSubObserver = sceneContext.currentSceneObservable.add((scene) => attachToScene(scene));
|
|
23550
|
+
return {
|
|
23551
|
+
dispose: () => {
|
|
23552
|
+
sceneSubObserver.remove();
|
|
23553
|
+
if (changeObserver) {
|
|
23554
|
+
changeObserver.remove();
|
|
23555
|
+
changeObserver = null;
|
|
23556
|
+
}
|
|
23557
|
+
},
|
|
23558
|
+
};
|
|
23559
|
+
},
|
|
23560
|
+
};
|
|
23561
|
+
/**
|
|
23562
|
+
* Classifies an entity into an OverrideTargetType by membership in the
|
|
23563
|
+
* scene's standard collections (or by being the scene itself).
|
|
23564
|
+
* @param entity - The entity to classify.
|
|
23565
|
+
* @param scene - The scene to check collections against.
|
|
23566
|
+
* @returns The target type, or null if unrecognized.
|
|
23567
|
+
*/
|
|
23568
|
+
function ClassifyEntity(entity, scene) {
|
|
23569
|
+
if (entity === scene) {
|
|
23570
|
+
return "scene";
|
|
23571
|
+
}
|
|
23572
|
+
const obj = entity;
|
|
23573
|
+
if (!obj || typeof obj !== "object") {
|
|
23574
|
+
return null;
|
|
23575
|
+
}
|
|
23576
|
+
if (scene.materials.includes(obj)) {
|
|
23577
|
+
return "materials";
|
|
23578
|
+
}
|
|
23579
|
+
if (scene.meshes.includes(obj)) {
|
|
23580
|
+
return "meshes";
|
|
23581
|
+
}
|
|
23582
|
+
if (scene.lights.includes(obj)) {
|
|
23583
|
+
return "lights";
|
|
23584
|
+
}
|
|
23585
|
+
if (scene.cameras.includes(obj)) {
|
|
23586
|
+
return "cameras";
|
|
23587
|
+
}
|
|
23588
|
+
if (scene.textures.includes(obj)) {
|
|
23589
|
+
return "textures";
|
|
23590
|
+
}
|
|
23591
|
+
if (scene.animationGroups.includes(obj)) {
|
|
23592
|
+
return "animationGroups";
|
|
23593
|
+
}
|
|
23594
|
+
return null;
|
|
23595
|
+
}
|
|
23596
|
+
/**
|
|
23597
|
+
* Gets the name of a scene entity.
|
|
23598
|
+
* @param entity - The entity to get the name from.
|
|
23599
|
+
* @returns The entity name, or an empty string if unavailable.
|
|
23600
|
+
*/
|
|
23601
|
+
function GetEntityName(entity) {
|
|
23602
|
+
const obj = entity;
|
|
23603
|
+
return obj?.name ?? "";
|
|
23604
|
+
}
|
|
23605
|
+
/**
|
|
23606
|
+
* Returns the position of `entity` among scene[targetType] objects with the
|
|
23607
|
+
* same name. Used so overrides can disambiguate same-named siblings.
|
|
23608
|
+
* @param scene - The scene to inspect.
|
|
23609
|
+
* @param targetType - The target type / collection name.
|
|
23610
|
+
* @param entity - The entity to locate.
|
|
23611
|
+
* @param name - The name to filter by.
|
|
23612
|
+
* @returns The index within the same-name filter, or 0 if not found.
|
|
23613
|
+
*/
|
|
23614
|
+
function ComputeTargetIndex(scene, targetType, entity, name) {
|
|
23615
|
+
const collection = GetCollection(scene, targetType);
|
|
23616
|
+
if (!collection) {
|
|
23617
|
+
return 0;
|
|
23618
|
+
}
|
|
23619
|
+
const sameName = collection.filter((obj) => obj.name === name);
|
|
23620
|
+
const idx = sameName.indexOf(entity);
|
|
23621
|
+
return idx >= 0 ? idx : 0;
|
|
23622
|
+
}
|
|
23623
|
+
/**
|
|
23624
|
+
* Returns the scene collection matching a target type.
|
|
23625
|
+
* @param scene - The scene to inspect.
|
|
23626
|
+
* @param targetType - The target type.
|
|
23627
|
+
* @returns The collection, or null if `targetType` doesn't map to one.
|
|
23628
|
+
*/
|
|
23629
|
+
function GetCollection(scene, targetType) {
|
|
23630
|
+
switch (targetType) {
|
|
23631
|
+
case "meshes":
|
|
23632
|
+
return scene.meshes;
|
|
23633
|
+
case "materials":
|
|
23634
|
+
return scene.materials;
|
|
23635
|
+
case "textures":
|
|
23636
|
+
return scene.textures;
|
|
23637
|
+
case "lights":
|
|
23638
|
+
return scene.lights;
|
|
23639
|
+
case "cameras":
|
|
23640
|
+
return scene.cameras;
|
|
23641
|
+
case "animationGroups":
|
|
23642
|
+
return scene.animationGroups;
|
|
23643
|
+
default:
|
|
23644
|
+
return null;
|
|
23645
|
+
}
|
|
23646
|
+
}
|
|
23647
|
+
/**
|
|
23648
|
+
* Serializes a property value into an OverrideValue.
|
|
23649
|
+
* Returns undefined for unsupported types.
|
|
23650
|
+
* @param value - The value to serialize.
|
|
23651
|
+
* @param scene - Optional scene for resolving object references.
|
|
23652
|
+
* @returns The serialized value, or undefined if unsupported.
|
|
23653
|
+
*/
|
|
23654
|
+
function SerializeOverrideValueForCapture(value, scene) {
|
|
23655
|
+
// null is a legitimate override value (e.g. clearing a material slot) and
|
|
23656
|
+
// must round-trip as null — substituting "" here would silently corrupt
|
|
23657
|
+
// object-typed slots with an empty string on reload.
|
|
23658
|
+
if (value === null) {
|
|
23659
|
+
return null;
|
|
23660
|
+
}
|
|
23661
|
+
if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") {
|
|
23662
|
+
return value;
|
|
23663
|
+
}
|
|
23664
|
+
// Material reference → "ref:materialName"
|
|
23665
|
+
if (value && typeof value === "object" && "getClassName" in value && typeof value.getClassName === "function") {
|
|
23666
|
+
const className = value.getClassName();
|
|
23667
|
+
if (className.includes("Material") || className.includes("material")) {
|
|
23668
|
+
return `ref:${value.name}`;
|
|
23669
|
+
}
|
|
23670
|
+
}
|
|
23671
|
+
// Texture reference → "samTexture:<key>" if SmartAsset-tracked, else "texture:<name>".
|
|
23672
|
+
// The SAM key is stable across save/load; `texture.name` for a SAM-tracked
|
|
23673
|
+
// texture is the blob URL, which dies on page reload — using it as the
|
|
23674
|
+
// override identifier would break the override after every reload.
|
|
23675
|
+
if (value && typeof value === "object" && "getClassName" in value && scene) {
|
|
23676
|
+
const className = value.getClassName();
|
|
23677
|
+
if (className.includes("Texture") || className.includes("texture")) {
|
|
23678
|
+
const samKey = FindSmartAssetKeyForObject(scene, value);
|
|
23679
|
+
if (samKey !== undefined) {
|
|
23680
|
+
return `samTexture:${samKey}`;
|
|
23681
|
+
}
|
|
23682
|
+
return `texture:${value.name}`;
|
|
23683
|
+
}
|
|
23684
|
+
}
|
|
23685
|
+
// Color3 / Color4
|
|
23686
|
+
if (value && typeof value === "object" && "r" in value && "g" in value && "b" in value) {
|
|
23687
|
+
const color = value;
|
|
23688
|
+
if ("a" in color && color.a !== undefined) {
|
|
23689
|
+
return [color.r, color.g, color.b, color.a];
|
|
23690
|
+
}
|
|
23691
|
+
return [color.r, color.g, color.b];
|
|
23692
|
+
}
|
|
23693
|
+
// Vector3 / Vector4
|
|
23694
|
+
if (value && typeof value === "object" && "x" in value && "y" in value && "z" in value) {
|
|
23695
|
+
const vec = value;
|
|
23696
|
+
if ("w" in vec && vec.w !== undefined) {
|
|
23697
|
+
return [vec.x, vec.y, vec.z, vec.w];
|
|
23698
|
+
}
|
|
23699
|
+
return [vec.x, vec.y, vec.z];
|
|
23700
|
+
}
|
|
23701
|
+
// Vector2
|
|
23702
|
+
if (value && typeof value === "object" && "x" in value && "y" in value && !("z" in value)) {
|
|
23703
|
+
const vec2 = value;
|
|
23704
|
+
return [vec2.x, vec2.y];
|
|
23705
|
+
}
|
|
23706
|
+
return undefined;
|
|
23707
|
+
}
|
|
23708
|
+
/**
|
|
23709
|
+
* Checks if an entity is a sub-object of a known scene entity by scanning
|
|
23710
|
+
* well-known sub-object properties on the scene and its collections.
|
|
23711
|
+
* Returns the parent entity info with the property path prefix.
|
|
23712
|
+
* @param entity - The entity to search for.
|
|
23713
|
+
* @param scene - The scene to search in.
|
|
23714
|
+
* @returns The parent entity info, or null if not found.
|
|
23715
|
+
*/
|
|
23716
|
+
function FindParentEntity(entity, scene) {
|
|
23717
|
+
// Check scene sub-objects (imageProcessingConfiguration, fogSettings, etc.)
|
|
23718
|
+
const sceneSubProps = ["imageProcessingConfiguration", "postProcessRenderPipelineManager", "ambientColor", "gravity"];
|
|
23719
|
+
for (const prop of sceneSubProps) {
|
|
23720
|
+
if (scene[prop] === entity) {
|
|
23721
|
+
return { targetType: "scene", targetName: "", targetIndex: 0, parentProperty: prop };
|
|
23722
|
+
}
|
|
23723
|
+
}
|
|
23724
|
+
const collections = [
|
|
23725
|
+
{ type: "materials", items: scene.materials },
|
|
23726
|
+
{ type: "cameras", items: scene.cameras },
|
|
23727
|
+
{ type: "meshes", items: scene.meshes },
|
|
23728
|
+
{ type: "lights", items: scene.lights },
|
|
23729
|
+
];
|
|
23730
|
+
for (const { type, items } of collections) {
|
|
23731
|
+
for (const parent of items) {
|
|
23732
|
+
for (const prop of Object.keys(parent)) {
|
|
23733
|
+
if (prop.startsWith("_")) {
|
|
23734
|
+
continue;
|
|
23735
|
+
}
|
|
23736
|
+
try {
|
|
23737
|
+
if (parent[prop] === entity) {
|
|
23738
|
+
const targetIndex = items.filter((p) => p.name === parent.name).indexOf(parent);
|
|
23739
|
+
return { targetType: type, targetName: parent.name, targetIndex: Math.max(targetIndex, 0), parentProperty: prop };
|
|
23740
|
+
}
|
|
23741
|
+
}
|
|
23742
|
+
catch {
|
|
23743
|
+
// Skip properties that throw on access
|
|
23744
|
+
}
|
|
23745
|
+
}
|
|
23746
|
+
}
|
|
23747
|
+
}
|
|
23748
|
+
return null;
|
|
23749
|
+
}
|
|
23750
|
+
|
|
23751
|
+
// Side-effect import: registers the `.babylon` SceneLoader plugin so the
|
|
23752
|
+
// companion `.babylon` file produced by SerializeProject can be loaded back.
|
|
23753
|
+
// Without this, LoadAssetContainerAsync logs "Unable to find a plugin to
|
|
23754
|
+
// load .babylon files" and the companion load fails.
|
|
23755
|
+
/**
|
|
23756
|
+
* ## `.babylonproj` project file format
|
|
23757
|
+
*
|
|
23758
|
+
* The `.babylonproj` zip on disk packages three layers:
|
|
23759
|
+
* 1. **SmartAsset registry** — URL references to external assets (glb/gltf/textures
|
|
23760
|
+
* loaded via SAM). Local blob/data assets are bundled inside the zip and
|
|
23761
|
+
* extracted to fresh blob URLs on load.
|
|
23762
|
+
* 2. **OverrideManager state** — declarative property overrides applied after load.
|
|
23763
|
+
* 3. **Companion `.babylon`** — meshes, lights, cameras, transform nodes, and
|
|
23764
|
+
* materials that are *not* tracked by SAM (i.e. user-created scene content).
|
|
23765
|
+
* Plus a `companionBindings` side table mapping material texture slots back
|
|
23766
|
+
* to SAM-tracked textures so re-attachment works without embedding texture
|
|
23767
|
+
* bytes in the companion.
|
|
23768
|
+
*
|
|
23769
|
+
* ### What round-trips cleanly
|
|
23770
|
+
* - SAM-tracked assets (re-fetched from their URLs or extracted from the zip)
|
|
23771
|
+
* - User-created `Mesh` geometry, `Material`s (Standard/PBR/Multi/Node), and
|
|
23772
|
+
* `*Texture` slot bindings to SAM textures
|
|
23773
|
+
* - `Light`s, `Camera`s, `TransformNode`s, scene/material image processing,
|
|
23774
|
+
* clear color, fog, environment intensity
|
|
23775
|
+
* - Property overrides on any of the above
|
|
23776
|
+
*
|
|
23777
|
+
* ### Known gaps (not preserved on save/load)
|
|
23778
|
+
* - `PostProcess` attachments to cameras (a post-process attaches to a *specific*
|
|
23779
|
+
* camera instance; we dispose+recreate cameras, leaving post-processes orphaned).
|
|
23780
|
+
* - `AdvancedDynamicTexture` GUI controls — not in `.babylon` format.
|
|
23781
|
+
* - Audio (`Sound` / `AudioEngine` state).
|
|
23782
|
+
* - Particle systems with runtime state, baked vertex animations.
|
|
23783
|
+
* - Complex shader-driven content like GaussianSplatting: the mesh round-trips
|
|
23784
|
+
* but its companion utility materials (`gaussianSplattingDepth`, `ProxyMaterial`)
|
|
23785
|
+
* get duplicated on each load cycle.
|
|
23786
|
+
* - Skeleton animation playback state.
|
|
23787
|
+
*
|
|
23788
|
+
* If you hit a "the scene looks different after load" issue, it's almost
|
|
23789
|
+
* certainly one of the gaps above rather than camera or mesh state drift.
|
|
23790
|
+
*/
|
|
23791
|
+
/**
|
|
23792
|
+
* Reserved smart asset key for user-created objects (materials, lights, cameras)
|
|
23793
|
+
* that are persisted as a companion `.babylon` file alongside the project JSON.
|
|
23794
|
+
*/
|
|
23795
|
+
const ProjectLocalsKey = "__project_locals__";
|
|
23796
|
+
// ── JSON layer (scene ↔ ISerializedProject) ──
|
|
23797
|
+
/**
|
|
23798
|
+
* Serializes a scene's smart asset map and override registry into a project
|
|
23799
|
+
* bundle. User-created objects (materials, lights, cameras not owned by any
|
|
23800
|
+
* smart asset) are serialized into a companion `.babylon` file rather than
|
|
23801
|
+
* embedded in the project JSON.
|
|
23802
|
+
*
|
|
23803
|
+
* Both managers are looked up (and created if missing) via their respective
|
|
23804
|
+
* `Get…Manager(scene)` accessors, so this function can be called on any scene.
|
|
23805
|
+
*
|
|
23806
|
+
* @param scene - The scene to serialize.
|
|
23807
|
+
* @param baseUrl - Optional base URL for making asset paths relative.
|
|
23808
|
+
* @returns A project bundle containing the JSON document and optional companion file.
|
|
23809
|
+
*/
|
|
23810
|
+
function SerializeProject(scene, baseUrl) {
|
|
23811
|
+
const assetMap = SerializeSmartAssetManagerMap(scene, baseUrl);
|
|
23812
|
+
const overrides = SerializeOverrides(scene);
|
|
23813
|
+
// Build a minimal .babylon JSON with only user-created objects, plus the
|
|
23814
|
+
// texture-binding side table that records which SmartAsset textures should
|
|
23815
|
+
// be re-attached to which material slots after load.
|
|
23816
|
+
const companionResult = SerializeCompanionBabylon(scene);
|
|
23817
|
+
let companionBabylon;
|
|
23818
|
+
const assets = { ...assetMap.assets };
|
|
23819
|
+
if (companionResult) {
|
|
23820
|
+
companionBabylon = new Blob([JSON.stringify(companionResult.companion)], { type: "application/json" });
|
|
23821
|
+
assets[ProjectLocalsKey] = { url: ProjectLocalsKey + ".babylon" };
|
|
23822
|
+
}
|
|
23823
|
+
else {
|
|
23824
|
+
// Remove stale companion entry if no locals exist
|
|
23825
|
+
delete assets[ProjectLocalsKey];
|
|
23826
|
+
}
|
|
23827
|
+
const hasBindings = companionResult && Object.keys(companionResult.bindings).length > 0;
|
|
23828
|
+
const project = {
|
|
23829
|
+
version: 2,
|
|
23830
|
+
assets,
|
|
23831
|
+
overrides,
|
|
23832
|
+
...(hasBindings ? { companionBindings: companionResult.bindings } : {}),
|
|
23833
|
+
};
|
|
23834
|
+
return { project, companionBabylon };
|
|
23835
|
+
}
|
|
23836
|
+
/**
|
|
23837
|
+
* Loads a project file from a URL, File, or pre-parsed object.
|
|
23838
|
+
* Registers all asset entries on the scene's SmartAssetManager, loads all
|
|
23839
|
+
* assets (including the companion `.babylon` for user-created objects), then
|
|
23840
|
+
* applies all overrides via the OverrideManager.
|
|
23841
|
+
*
|
|
23842
|
+
* For loading the `.babylonproj` zip on-disk format, use {@link LoadProjectFileAsync}
|
|
23843
|
+
* instead — it extracts the zip and then calls this function with the embedded
|
|
23844
|
+
* JSON document.
|
|
23845
|
+
*
|
|
23846
|
+
* @param scene - The scene to populate.
|
|
23847
|
+
* @param source - A URL string, File object, or pre-parsed ISerializedProject.
|
|
23848
|
+
* @param rootUrl - Optional root URL for resolving relative asset paths.
|
|
23849
|
+
*/
|
|
23850
|
+
async function LoadProjectAsync(scene, source, rootUrl) {
|
|
23851
|
+
let resolvedRootUrl = "";
|
|
23852
|
+
if (typeof source === "string" && true) {
|
|
23853
|
+
const { Tools } = await import('@babylonjs/core/Misc/tools.js');
|
|
23854
|
+
resolvedRootUrl = Tools.GetFolderPath(source);
|
|
23855
|
+
}
|
|
23856
|
+
const raw = await ReadJsonSourceAsync(source);
|
|
23857
|
+
const doc = DeserializeProject(raw);
|
|
23858
|
+
// Pause the engine's render loops for the duration of the swap. Disposing
|
|
23859
|
+
// cameras mid-frame would throw "No camera defined" out of `scene.render`,
|
|
23860
|
+
// which kills the render loop entirely (it is not re-queued after an
|
|
23861
|
+
// uncaught exception). Snapshot the active loops first so we can restore
|
|
23862
|
+
// exactly what was running, even if multiple callbacks were registered.
|
|
23863
|
+
const engine = scene.getEngine();
|
|
23864
|
+
const savedRenderLoops = [...engine.activeRenderLoops];
|
|
23865
|
+
engine.stopRenderLoop();
|
|
23866
|
+
try {
|
|
23867
|
+
// Clear existing state so we load fresh from the project file.
|
|
23868
|
+
// The companion `.babylon` (when present) is the source of truth for all
|
|
23869
|
+
// user-created scene content, so dispose user-owned meshes, lights,
|
|
23870
|
+
// cameras, materials, and animation groups before reloading.
|
|
23871
|
+
await Promise.all(Array.from(GetAllSmartAssets(scene).keys()).map(async (existingKey) => await RemoveSmartAssetAsync(scene, existingKey)));
|
|
23872
|
+
ClearOverrides(scene);
|
|
23873
|
+
for (const mesh of [...scene.meshes]) {
|
|
23874
|
+
mesh.dispose();
|
|
23875
|
+
}
|
|
23876
|
+
for (const tn of [...scene.transformNodes]) {
|
|
23877
|
+
tn.dispose();
|
|
23878
|
+
}
|
|
23879
|
+
for (const ag of [...scene.animationGroups]) {
|
|
23880
|
+
ag.dispose();
|
|
23881
|
+
}
|
|
23882
|
+
for (const mat of [...scene.materials]) {
|
|
23883
|
+
mat.dispose();
|
|
23884
|
+
}
|
|
23885
|
+
for (const light of [...scene.lights]) {
|
|
23886
|
+
light.dispose();
|
|
23887
|
+
}
|
|
23888
|
+
for (const camera of [...scene.cameras]) {
|
|
23889
|
+
camera.dispose();
|
|
23890
|
+
}
|
|
23891
|
+
// Register all assets. Defer the companion .babylon — it must load after
|
|
23892
|
+
// textures are available so binding re-attachment can find them.
|
|
23893
|
+
let hasCompanion = false;
|
|
23894
|
+
for (const [key, entry] of Object.entries(doc.assets)) {
|
|
23895
|
+
if (key === ProjectLocalsKey) {
|
|
23896
|
+
hasCompanion = true;
|
|
23897
|
+
continue;
|
|
23898
|
+
}
|
|
23899
|
+
const resolved = resolvedRootUrl ? ResolveAssetUrl(entry.url, resolvedRootUrl) : entry.url;
|
|
23900
|
+
RegisterSmartAsset(scene, key, resolved, { type: entry.type, extension: entry.extension, metadata: entry.metadata });
|
|
23901
|
+
}
|
|
23902
|
+
await LoadAllSmartAssetsAsync(scene);
|
|
23903
|
+
// Now load the companion .babylon. Its materials were saved with texture
|
|
23904
|
+
// slots stripped (the binding side table records which SmartAsset texture
|
|
23905
|
+
// each slot should be re-attached to), so the loader never sees a broken
|
|
23906
|
+
// texture URL. Pass the .babylon extension hint because blob URLs have
|
|
23907
|
+
// no file extension.
|
|
23908
|
+
if (hasCompanion) {
|
|
23909
|
+
const companionEntry = doc.assets[ProjectLocalsKey];
|
|
23910
|
+
const companionUrl = resolvedRootUrl ? ResolveAssetUrl(companionEntry.url, resolvedRootUrl) : companionEntry.url;
|
|
23911
|
+
await LoadSmartAssetAsync(scene, ProjectLocalsKey, companionUrl, { extension: ".babylon" });
|
|
23912
|
+
if (doc.companionBindings) {
|
|
23913
|
+
ApplyCompanionBindings(scene, doc.companionBindings);
|
|
23914
|
+
}
|
|
23915
|
+
}
|
|
23916
|
+
// Apply overrides
|
|
23917
|
+
if (doc.overrides.length > 0) {
|
|
23918
|
+
DeserializeAndApplyOverrides(scene, doc.overrides);
|
|
23919
|
+
}
|
|
23920
|
+
// Re-assign the active camera if the companion brought in fresh cameras.
|
|
23921
|
+
// The .babylon scene loader populates scene.cameras but does not set
|
|
23922
|
+
// scene.activeCamera, so render would otherwise throw "No camera defined".
|
|
23923
|
+
if (!scene.activeCamera && scene.cameras.length > 0) {
|
|
23924
|
+
scene.activeCamera = scene.cameras[0];
|
|
23925
|
+
}
|
|
23926
|
+
// Attach controls so the user can rotate/zoom/pan after load. New
|
|
23927
|
+
// camera instances from the companion .babylon are not attached to
|
|
23928
|
+
// the canvas by the loader — without this, the camera renders but
|
|
23929
|
+
// ignores mouse/touch input.
|
|
23930
|
+
const canvas = engine.getRenderingCanvas();
|
|
23931
|
+
if (scene.activeCamera && canvas) {
|
|
23932
|
+
scene.activeCamera.attachControl(canvas, true);
|
|
23933
|
+
}
|
|
23934
|
+
}
|
|
23935
|
+
finally {
|
|
23936
|
+
// Always restore the render loops, even if loading threw — otherwise
|
|
23937
|
+
// the canvas stays frozen forever and the user has no way to recover.
|
|
23938
|
+
for (const loop of savedRenderLoops) {
|
|
23939
|
+
engine.runRenderLoop(loop);
|
|
23940
|
+
}
|
|
23941
|
+
}
|
|
23942
|
+
}
|
|
23943
|
+
/**
|
|
23944
|
+
* Validates and parses a serialized project document.
|
|
23945
|
+
* @param data - The raw data to validate (typically parsed JSON).
|
|
23946
|
+
* @returns The validated project document.
|
|
23947
|
+
* @throws If the data does not conform to the expected schema.
|
|
23948
|
+
*/
|
|
23949
|
+
function DeserializeProject(data) {
|
|
23950
|
+
if (!data || typeof data !== "object") {
|
|
23951
|
+
throw new Error("ProjectFile: Invalid project file — expected an object.");
|
|
23952
|
+
}
|
|
23953
|
+
const doc = data;
|
|
23954
|
+
if (doc.version !== 2) {
|
|
23955
|
+
throw new Error(`ProjectFile: Unsupported project version "${doc.version}". Expected version 2.`);
|
|
23956
|
+
}
|
|
23957
|
+
// Validate the asset map portion
|
|
23958
|
+
DeserializeSmartAssetMap({ version: 1, assets: doc.assets });
|
|
23959
|
+
// Validate overrides array
|
|
23960
|
+
if (!Array.isArray(doc.overrides)) {
|
|
23961
|
+
throw new Error("ProjectFile: Invalid project file — 'overrides' must be an array.");
|
|
23962
|
+
}
|
|
23963
|
+
// Validate optional companion bindings (shape-only check)
|
|
23964
|
+
if (doc.companionBindings !== undefined) {
|
|
23965
|
+
if (typeof doc.companionBindings !== "object" || doc.companionBindings === null || Array.isArray(doc.companionBindings)) {
|
|
23966
|
+
throw new Error("ProjectFile: Invalid project file — 'companionBindings' must be an object.");
|
|
23967
|
+
}
|
|
23968
|
+
}
|
|
23969
|
+
return data;
|
|
23970
|
+
}
|
|
23971
|
+
// ── Zip layer (.babylonproj on disk) ──
|
|
23972
|
+
/**
|
|
23973
|
+
* Serializes a scene's project (smart assets + overrides) into a `.babylonproj`
|
|
23974
|
+
* zip bundle.
|
|
23975
|
+
*
|
|
23976
|
+
* The zip contains:
|
|
23977
|
+
* - `project.json` — the project document (assets + overrides)
|
|
23978
|
+
* - `__project_locals__.babylon` — companion file for user-created objects (if any)
|
|
23979
|
+
* - Bundled local asset files (blobs the user dragged in from disk)
|
|
23980
|
+
*
|
|
23981
|
+
* Remote URLs (http/https) are left as references and not bundled.
|
|
23982
|
+
*
|
|
23983
|
+
* @param scene - The scene to serialize.
|
|
23984
|
+
* @returns A Blob containing the zip bundle.
|
|
23985
|
+
*/
|
|
23986
|
+
async function SaveProjectFileAsync(scene) {
|
|
23987
|
+
const bundle = SerializeProject(scene);
|
|
23988
|
+
const files = {};
|
|
23989
|
+
// Collect local (blob/data URI) assets to bundle inside the zip.
|
|
23990
|
+
// Rewrite their URLs in the project JSON to relative paths.
|
|
23991
|
+
const projectAssets = { ...bundle.project.assets };
|
|
23992
|
+
// Fetch all blob/data URIs in parallel (avoid serial awaits in a loop).
|
|
23993
|
+
const blobEntries = Object.entries(projectAssets).filter(([key, entry]) => key !== ProjectLocalsKey && (entry.url.startsWith("blob:") || entry.url.startsWith("data:")));
|
|
23994
|
+
const fetched = await Promise.all(blobEntries.map(async ([key, entry]) => {
|
|
22944
23995
|
try {
|
|
22945
|
-
|
|
22946
|
-
await
|
|
22947
|
-
|
|
23996
|
+
const response = await fetch(entry.url);
|
|
23997
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
23998
|
+
return { key, entry, arrayBuffer };
|
|
22948
23999
|
}
|
|
22949
|
-
catch
|
|
22950
|
-
|
|
24000
|
+
catch {
|
|
24001
|
+
// Can't fetch blob — leave the URL as-is (will break on reload,
|
|
24002
|
+
// but at least the project structure is preserved).
|
|
24003
|
+
return null;
|
|
22951
24004
|
}
|
|
22952
|
-
|
|
22953
|
-
|
|
24005
|
+
}));
|
|
24006
|
+
for (const result of fetched) {
|
|
24007
|
+
if (!result) {
|
|
24008
|
+
continue;
|
|
22954
24009
|
}
|
|
22955
|
-
|
|
22956
|
-
|
|
22957
|
-
}
|
|
24010
|
+
const { key, entry, arrayBuffer } = result;
|
|
24011
|
+
const ext = GuessExtension(entry.url, IsTextureEntry(entry.url, entry.extension, entry.type));
|
|
24012
|
+
const filename = `assets/${key}${ext}`;
|
|
24013
|
+
files[filename] = new Uint8Array(arrayBuffer);
|
|
24014
|
+
projectAssets[key] = { ...entry, url: filename };
|
|
24015
|
+
}
|
|
24016
|
+
// Add companion .babylon if it exists
|
|
24017
|
+
if (bundle.companionBabylon) {
|
|
24018
|
+
const companionBuffer = await bundle.companionBabylon.arrayBuffer();
|
|
24019
|
+
const companionFilename = ProjectLocalsKey + ".babylon";
|
|
24020
|
+
files[companionFilename] = new Uint8Array(companionBuffer);
|
|
24021
|
+
projectAssets[ProjectLocalsKey] = { url: companionFilename };
|
|
24022
|
+
}
|
|
24023
|
+
// Write the project JSON with updated asset paths
|
|
24024
|
+
const projectWithBundledPaths = {
|
|
24025
|
+
...bundle.project,
|
|
24026
|
+
assets: projectAssets,
|
|
24027
|
+
};
|
|
24028
|
+
const { zip, strToU8 } = await import('./browser-CANgtOiM.js');
|
|
24029
|
+
files["project.json"] = strToU8(JSON.stringify(projectWithBundledPaths, null, 2));
|
|
24030
|
+
// Create the zip (async — runs in a Web Worker to avoid blocking the UI thread)
|
|
24031
|
+
const zipped = await new Promise((resolve, reject) => {
|
|
24032
|
+
zip(files, { level: 6 }, (err, data) => (err ? reject(err) : resolve(data)));
|
|
24033
|
+
});
|
|
24034
|
+
return new Blob([zipped], { type: "application/zip" });
|
|
24035
|
+
}
|
|
24036
|
+
/**
|
|
24037
|
+
* Loads a `.babylonproj` zip bundle into a scene. Extracts all files, creates
|
|
24038
|
+
* blob URLs for bundled assets, and loads the project through SAM.
|
|
24039
|
+
*
|
|
24040
|
+
* @param scene - The scene to load the project into.
|
|
24041
|
+
* @param zipFile - The `.babylonproj` zip file to load.
|
|
24042
|
+
*/
|
|
24043
|
+
async function LoadProjectFileAsync(scene, zipFile) {
|
|
24044
|
+
const arrayBuffer = await zipFile.arrayBuffer();
|
|
24045
|
+
const { unzip, strFromU8 } = await import('./browser-CANgtOiM.js');
|
|
24046
|
+
const extracted = await new Promise((resolve, reject) => {
|
|
24047
|
+
unzip(new Uint8Array(arrayBuffer), (err, data) => (err ? reject(err) : resolve(data)));
|
|
24048
|
+
});
|
|
24049
|
+
// Parse project.json
|
|
24050
|
+
const projectJsonBytes = extracted["project.json"];
|
|
24051
|
+
if (!projectJsonBytes) {
|
|
24052
|
+
throw new Error("ProjectFile: Invalid project bundle — missing project.json");
|
|
24053
|
+
}
|
|
24054
|
+
const projectJson = JSON.parse(strFromU8(projectJsonBytes));
|
|
24055
|
+
// Create blob URLs for all bundled files and rewrite asset URLs
|
|
24056
|
+
for (const [, entry] of Object.entries(projectJson.assets)) {
|
|
24057
|
+
const filename = entry.url;
|
|
24058
|
+
const fileBytes = extracted[filename];
|
|
24059
|
+
if (fileBytes) {
|
|
24060
|
+
const mimeType = GuessMimeType(filename);
|
|
24061
|
+
// Use a named File so LoadAssetContainerAsync can detect the
|
|
24062
|
+
// format from the filename (blob URLs alone have no extension).
|
|
24063
|
+
const file = new File([fileBytes], filename, { type: mimeType });
|
|
24064
|
+
const blobUrl = URL.createObjectURL(file);
|
|
24065
|
+
entry.url = blobUrl;
|
|
24066
|
+
}
|
|
24067
|
+
// If no file found in zip, assume the URL is a remote reference — leave it as-is
|
|
24068
|
+
}
|
|
24069
|
+
// Load through the standard JSON path
|
|
24070
|
+
await LoadProjectAsync(scene, projectJson);
|
|
24071
|
+
// Note: textures and scene files may still reference the blob URLs created
|
|
24072
|
+
// above, so we do NOT revoke them here. They'll be cleaned up when SAM disposes.
|
|
24073
|
+
}
|
|
24074
|
+
// ── Private ──
|
|
24075
|
+
/**
|
|
24076
|
+
* Returns true if a scene object is a "local" — not owned by any external
|
|
24077
|
+
* smart asset, or owned by the reserved `__project_locals__` key.
|
|
24078
|
+
* @param scene - The scene that owns the object.
|
|
24079
|
+
* @param obj - The scene object to check.
|
|
24080
|
+
* @returns True if the object should be included in the companion file.
|
|
24081
|
+
*/
|
|
24082
|
+
function IsLocalObject(scene, obj) {
|
|
24083
|
+
const key = FindSmartAssetKeyForObject(scene, obj);
|
|
24084
|
+
return key === undefined || key === ProjectLocalsKey;
|
|
24085
|
+
}
|
|
24086
|
+
/**
|
|
24087
|
+
* Builds a `.babylon`-compatible JSON containing all user-created scene
|
|
24088
|
+
* content (meshes, lights, cameras, transform nodes, and materials not owned
|
|
24089
|
+
* by any external smart asset), plus a side table recording which `*Texture`
|
|
24090
|
+
* slots on each material should be re-attached to which SmartAsset textures
|
|
24091
|
+
* after load.
|
|
24092
|
+
*
|
|
24093
|
+
* Mesh, light, camera, and standalone material serialization is delegated to
|
|
24094
|
+
* `SceneSerializer.SerializeMesh`, which auto-handles geometries, sub-materials,
|
|
24095
|
+
* and skeletons. Texture slots that map to a SmartAsset-tracked texture are
|
|
24096
|
+
* stripped from the serialized material so the `.babylon` loader never sees a
|
|
24097
|
+
* broken URL; the binding table is the sole source of truth for re-attachment.
|
|
24098
|
+
*
|
|
24099
|
+
* @param scene - The scene to extract locals from.
|
|
24100
|
+
* @returns The companion document and binding table, or null if there are no local objects.
|
|
24101
|
+
*/
|
|
24102
|
+
function SerializeCompanionBabylon(scene) {
|
|
24103
|
+
const meshes = scene.meshes.filter((m) => m instanceof Mesh && m.name !== "__root__" && IsLocalObject(scene, m));
|
|
24104
|
+
const lights = scene.lights.filter((l) => IsLocalObject(scene, l));
|
|
24105
|
+
const cameras = scene.cameras.filter((c) => IsLocalObject(scene, c));
|
|
24106
|
+
const transformNodes = scene.transformNodes.filter((t) => IsLocalObject(scene, t));
|
|
24107
|
+
// Standalone materials (not attached to any included mesh) need to be
|
|
24108
|
+
// added explicitly — SerializeMesh only picks up materials reachable from
|
|
24109
|
+
// the supplied meshes.
|
|
24110
|
+
const meshMaterialIds = new Set(meshes.map((m) => m.material?.uniqueId).filter((id) => id !== undefined));
|
|
24111
|
+
const standaloneMaterials = scene.materials.filter((mat) => mat.name !== "default material" && IsLocalObject(scene, mat) && !meshMaterialIds.has(mat.uniqueId));
|
|
24112
|
+
if (meshes.length === 0 && lights.length === 0 && cameras.length === 0 && transformNodes.length === 0 && standaloneMaterials.length === 0) {
|
|
24113
|
+
return null;
|
|
24114
|
+
}
|
|
24115
|
+
const companion = SceneSerializer.SerializeMesh([...meshes, ...lights, ...cameras, ...transformNodes], false, false);
|
|
24116
|
+
const allMaterials = companion.materials ?? [];
|
|
24117
|
+
companion.materials = allMaterials;
|
|
24118
|
+
for (const mat of standaloneMaterials) {
|
|
24119
|
+
const serialized = mat.serialize();
|
|
24120
|
+
if (serialized && !allMaterials.some((m) => m.uniqueId === serialized.uniqueId)) {
|
|
24121
|
+
allMaterials.push(serialized);
|
|
24122
|
+
}
|
|
24123
|
+
}
|
|
24124
|
+
// Strip non-JSON-serializable metadata (e.g. metadata that references
|
|
24125
|
+
// another scene object) to avoid `Converting circular structure to JSON`
|
|
24126
|
+
// when the companion is stringified. Simple JSON metadata is preserved.
|
|
24127
|
+
SanitizeMetadataInPlace(companion);
|
|
24128
|
+
// Walk every serialized material (mesh-attached + standalone + multi-material
|
|
24129
|
+
// children) and extract SmartAsset texture bindings, stripping those slots
|
|
24130
|
+
// from the serialized data.
|
|
24131
|
+
const bindings = {};
|
|
24132
|
+
for (const serializedMat of allMaterials) {
|
|
24133
|
+
const matBindings = ExtractTextureBindings(scene, serializedMat);
|
|
24134
|
+
if (Object.keys(matBindings).length > 0 && typeof serializedMat.name === "string") {
|
|
24135
|
+
bindings[serializedMat.name] = matBindings;
|
|
24136
|
+
}
|
|
24137
|
+
}
|
|
24138
|
+
const multiMaterials = companion.multiMaterials ?? [];
|
|
24139
|
+
for (const serializedMat of multiMaterials) {
|
|
24140
|
+
const matBindings = ExtractTextureBindings(scene, serializedMat);
|
|
24141
|
+
if (Object.keys(matBindings).length > 0 && typeof serializedMat.name === "string") {
|
|
24142
|
+
bindings[serializedMat.name] = matBindings;
|
|
24143
|
+
}
|
|
24144
|
+
}
|
|
24145
|
+
return { companion, bindings };
|
|
24146
|
+
}
|
|
24147
|
+
/**
|
|
24148
|
+
* Walks the top-level entity arrays in a serialized companion document and
|
|
24149
|
+
* strips `metadata` fields that cannot be JSON-stringified (typically because
|
|
24150
|
+
* the user put a reference to another scene object in metadata). Simple
|
|
24151
|
+
* JSON-serializable metadata is preserved.
|
|
24152
|
+
* @param companion - The serialized companion document to mutate in-place.
|
|
24153
|
+
*/
|
|
24154
|
+
function SanitizeMetadataInPlace(companion) {
|
|
24155
|
+
const arrays = ["meshes", "transformNodes", "lights", "cameras", "materials", "multiMaterials"];
|
|
24156
|
+
for (const arrayKey of arrays) {
|
|
24157
|
+
const arr = companion[arrayKey];
|
|
24158
|
+
if (!Array.isArray(arr)) {
|
|
24159
|
+
continue;
|
|
24160
|
+
}
|
|
24161
|
+
for (const item of arr) {
|
|
24162
|
+
if (item && typeof item === "object" && "metadata" in item && item.metadata !== undefined) {
|
|
24163
|
+
try {
|
|
24164
|
+
item.metadata = JSON.parse(JSON.stringify(item.metadata));
|
|
24165
|
+
}
|
|
24166
|
+
catch {
|
|
24167
|
+
delete item.metadata;
|
|
24168
|
+
}
|
|
24169
|
+
}
|
|
24170
|
+
}
|
|
24171
|
+
}
|
|
24172
|
+
}
|
|
24173
|
+
/**
|
|
24174
|
+
* Walks a serialized material's `*Texture` slots and, for any that reference a
|
|
24175
|
+
* SmartAsset-tracked texture, records a `{slot: samKey}` binding and removes
|
|
24176
|
+
* the slot from the serialized data so the `.babylon` loader does not try to
|
|
24177
|
+
* fetch the (now-dead) original URL.
|
|
24178
|
+
* @param scene - The scene that owns the textures.
|
|
24179
|
+
* @param serializedMaterial - The serialized material data to rewrite in-place.
|
|
24180
|
+
* @returns A map of stripped slot names to their SmartAsset keys.
|
|
24181
|
+
*/
|
|
24182
|
+
function ExtractTextureBindings(scene, serializedMaterial) {
|
|
24183
|
+
const bindings = {};
|
|
24184
|
+
for (const [propName, propValue] of Object.entries(serializedMaterial)) {
|
|
24185
|
+
if (!propName.endsWith("Texture") || typeof propValue !== "object" || propValue === null) {
|
|
24186
|
+
continue;
|
|
24187
|
+
}
|
|
24188
|
+
const texData = propValue;
|
|
24189
|
+
const texName = typeof texData.name === "string" ? texData.name : undefined;
|
|
24190
|
+
const texUrl = typeof texData.url === "string" ? texData.url : undefined;
|
|
24191
|
+
if (!texName && !texUrl) {
|
|
24192
|
+
continue;
|
|
24193
|
+
}
|
|
24194
|
+
for (const tex of scene.textures) {
|
|
24195
|
+
const key = FindSmartAssetKeyForObject(scene, tex);
|
|
24196
|
+
if (key && (tex.name === texName || tex.url === texName || tex.name === texUrl || tex.url === texUrl)) {
|
|
24197
|
+
bindings[propName] = key;
|
|
24198
|
+
delete serializedMaterial[propName];
|
|
24199
|
+
break;
|
|
24200
|
+
}
|
|
24201
|
+
}
|
|
24202
|
+
}
|
|
24203
|
+
return bindings;
|
|
24204
|
+
}
|
|
24205
|
+
/**
|
|
24206
|
+
* Re-attaches SmartAsset-tracked textures to user-created material slots
|
|
24207
|
+
* after the companion `.babylon` has loaded. Silently skips bindings whose
|
|
24208
|
+
* material or texture is no longer present (e.g. the underlying SmartAsset
|
|
24209
|
+
* was removed before reload).
|
|
24210
|
+
* @param scene - The scene that owns the materials and textures.
|
|
24211
|
+
* @param bindings - The binding table from the project document.
|
|
24212
|
+
*/
|
|
24213
|
+
function ApplyCompanionBindings(scene, bindings) {
|
|
24214
|
+
for (const [materialName, slots] of Object.entries(bindings)) {
|
|
24215
|
+
const mat = scene.materials.find((m) => m.name === materialName);
|
|
24216
|
+
if (!mat) {
|
|
24217
|
+
continue;
|
|
24218
|
+
}
|
|
24219
|
+
for (const [slotName, samKey] of Object.entries(slots)) {
|
|
24220
|
+
const texture = scene.textures.find((tex) => FindSmartAssetKeyForObject(scene, tex) === samKey);
|
|
24221
|
+
if (texture) {
|
|
24222
|
+
mat[slotName] = texture;
|
|
24223
|
+
}
|
|
24224
|
+
}
|
|
24225
|
+
}
|
|
24226
|
+
}
|
|
24227
|
+
/**
|
|
24228
|
+
* Returns true if a serialized asset entry refers to a standalone texture,
|
|
24229
|
+
* based on the registered options or the URL extension.
|
|
24230
|
+
* @param url - The asset URL.
|
|
24231
|
+
* @param extension - Optional explicit extension hint from the registration options.
|
|
24232
|
+
* @param type - Optional explicit type from the registration options.
|
|
24233
|
+
* @returns True if the entry should be treated as a texture.
|
|
24234
|
+
*/
|
|
24235
|
+
function IsTextureEntry(url, extension, type) {
|
|
24236
|
+
if (type === "texture") {
|
|
24237
|
+
return true;
|
|
24238
|
+
}
|
|
24239
|
+
const textureExts = GetSmartAssetTextureExtensions();
|
|
24240
|
+
if (extension && textureExts.has(extension.toLowerCase())) {
|
|
24241
|
+
return true;
|
|
24242
|
+
}
|
|
24243
|
+
const ext = ExtractExtension(url);
|
|
24244
|
+
return ext !== "" && textureExts.has(ext);
|
|
24245
|
+
}
|
|
24246
|
+
/**
|
|
24247
|
+
* Extracts the file extension (with leading dot, lowercased) from a URL,
|
|
24248
|
+
* stripping query/hash and ignoring blob/data prefixes.
|
|
24249
|
+
* @param url - The URL to inspect.
|
|
24250
|
+
* @returns The extension including the leading dot, or "" if none found.
|
|
24251
|
+
*/
|
|
24252
|
+
function ExtractExtension(url) {
|
|
24253
|
+
if (url.startsWith("blob:") || url.startsWith("data:")) {
|
|
24254
|
+
return "";
|
|
24255
|
+
}
|
|
24256
|
+
const clean = url.split("?")[0].split("#")[0];
|
|
24257
|
+
const lastDot = clean.lastIndexOf(".");
|
|
24258
|
+
const lastSlash = Math.max(clean.lastIndexOf("/"), clean.lastIndexOf("\\"));
|
|
24259
|
+
if (lastDot > lastSlash && lastDot >= 0) {
|
|
24260
|
+
return clean.substring(lastDot).toLowerCase();
|
|
24261
|
+
}
|
|
24262
|
+
return "";
|
|
24263
|
+
}
|
|
24264
|
+
/**
|
|
24265
|
+
* Guesses a file extension for a blob/data URL when bundling into the zip.
|
|
24266
|
+
* @param url - The original URL.
|
|
24267
|
+
* @param isTexture - Whether the key is known to be a texture.
|
|
24268
|
+
* @returns A file extension including the dot (e.g. ".glb", ".png").
|
|
24269
|
+
*/
|
|
24270
|
+
function GuessExtension(url, isTexture) {
|
|
24271
|
+
// Try to extract from data URI mime type
|
|
24272
|
+
if (url.startsWith("data:")) {
|
|
24273
|
+
const mimeMatch = url.match(/^data:([^;,]+)/);
|
|
24274
|
+
if (mimeMatch) {
|
|
24275
|
+
const ext = MimeToExtension(mimeMatch[1]);
|
|
24276
|
+
if (ext) {
|
|
24277
|
+
return ext;
|
|
24278
|
+
}
|
|
24279
|
+
}
|
|
24280
|
+
}
|
|
24281
|
+
return isTexture ? ".png" : ".glb";
|
|
24282
|
+
}
|
|
24283
|
+
// Tuple-array `Map`s rather than object literals so the MIME and extension
|
|
24284
|
+
// keys (e.g. "model/gltf-binary", ".glb") don't trigger the naming-convention
|
|
24285
|
+
// rule that runs on object-literal property names.
|
|
24286
|
+
const MimeToExtensionMap = new Map([
|
|
24287
|
+
["model/gltf-binary", ".glb"],
|
|
24288
|
+
["model/gltf+json", ".gltf"],
|
|
24289
|
+
["image/png", ".png"],
|
|
24290
|
+
["image/jpeg", ".jpg"],
|
|
24291
|
+
["image/webp", ".webp"],
|
|
24292
|
+
["application/octet-stream", ".glb"],
|
|
24293
|
+
["application/json", ".babylon"],
|
|
24294
|
+
]);
|
|
24295
|
+
const ExtensionToMimeMap = new Map([
|
|
24296
|
+
[".glb", "model/gltf-binary"],
|
|
24297
|
+
[".gltf", "model/gltf+json"],
|
|
24298
|
+
[".babylon", "application/json"],
|
|
24299
|
+
[".png", "image/png"],
|
|
24300
|
+
[".jpg", "image/jpeg"],
|
|
24301
|
+
[".jpeg", "image/jpeg"],
|
|
24302
|
+
[".env", "application/octet-stream"],
|
|
24303
|
+
[".hdr", "application/octet-stream"],
|
|
24304
|
+
[".dds", "application/octet-stream"],
|
|
24305
|
+
[".ktx", "application/octet-stream"],
|
|
24306
|
+
[".ktx2", "application/octet-stream"],
|
|
24307
|
+
[".json", "application/json"],
|
|
24308
|
+
]);
|
|
24309
|
+
/**
|
|
24310
|
+
* Maps a MIME type to a file extension.
|
|
24311
|
+
* @param mime - The MIME type string.
|
|
24312
|
+
* @returns The file extension including the dot, or empty string if unknown.
|
|
24313
|
+
*/
|
|
24314
|
+
function MimeToExtension(mime) {
|
|
24315
|
+
return MimeToExtensionMap.get(mime) ?? "";
|
|
24316
|
+
}
|
|
24317
|
+
/**
|
|
24318
|
+
* Guesses a MIME type from a filename.
|
|
24319
|
+
* @param filename - The filename to check.
|
|
24320
|
+
* @returns The guessed MIME type string.
|
|
24321
|
+
*/
|
|
24322
|
+
function GuessMimeType(filename) {
|
|
24323
|
+
const ext = filename.substring(filename.lastIndexOf(".")).toLowerCase();
|
|
24324
|
+
return ExtensionToMimeMap.get(ext) ?? "application/octet-stream";
|
|
24325
|
+
}
|
|
22958
24326
|
|
|
22959
|
-
const
|
|
24327
|
+
const ProjectAuthoringPaneKey = "Project Authoring";
|
|
22960
24328
|
const SceneFileAccept = [".glb", ".gltf", ".babylon", ".obj"];
|
|
22961
24329
|
const TextureFileAccept = Array.from(GetSmartAssetTextureExtensions());
|
|
22962
24330
|
const AllAcceptString = [...SceneFileAccept, ...TextureFileAccept].join(",");
|
|
@@ -22965,15 +24333,17 @@ function _isTextureExtension(ext) {
|
|
|
22965
24333
|
return GetSmartAssetTextureExtensions().has(ext);
|
|
22966
24334
|
}
|
|
22967
24335
|
/**
|
|
22968
|
-
* Inspector pane service that hosts the
|
|
24336
|
+
* Inspector pane service that hosts the Project Authoring pane. Combines
|
|
24337
|
+
* smart-asset management (assets list, asset map I/O) with override-driven
|
|
24338
|
+
* scene authoring (material assignment, override summary).
|
|
22969
24339
|
*/
|
|
22970
|
-
const
|
|
22971
|
-
friendlyName: "
|
|
24340
|
+
const BabylonProjectAuthoringServiceDefinition = {
|
|
24341
|
+
friendlyName: "Project Authoring",
|
|
22972
24342
|
consumes: [ShellServiceIdentity, SceneContextIdentity, SelectionServiceIdentity],
|
|
22973
24343
|
factory: (shellService, sceneContext, selectionService) => {
|
|
22974
24344
|
const paneRegistration = shellService.addSidePane({
|
|
22975
|
-
key:
|
|
22976
|
-
title:
|
|
24345
|
+
key: ProjectAuthoringPaneKey,
|
|
24346
|
+
title: ProjectAuthoringPaneKey,
|
|
22977
24347
|
icon: CubeRegular,
|
|
22978
24348
|
horizontalLocation: "right",
|
|
22979
24349
|
verticalLocation: "top",
|
|
@@ -22981,7 +24351,7 @@ const SmartAssetsServiceDefinition = {
|
|
|
22981
24351
|
teachingMoment: false,
|
|
22982
24352
|
content: () => {
|
|
22983
24353
|
const scene = useObservableState(() => sceneContext.currentScene, sceneContext.currentSceneObservable);
|
|
22984
|
-
return scene ? jsx(
|
|
24354
|
+
return scene ? jsx(BabylonProjectAuthoringPane, { scene: scene, selectionService: selectionService }) : null;
|
|
22985
24355
|
},
|
|
22986
24356
|
});
|
|
22987
24357
|
return {
|
|
@@ -23036,11 +24406,81 @@ const useStyles$3 = makeStyles({
|
|
|
23036
24406
|
hiddenInput: {
|
|
23037
24407
|
display: "none",
|
|
23038
24408
|
},
|
|
24409
|
+
overrideRow: {
|
|
24410
|
+
display: "flex",
|
|
24411
|
+
gap: tokens.spacingHorizontalXS,
|
|
24412
|
+
padding: `${tokens.spacingVerticalXXS} ${tokens.spacingHorizontalS}`,
|
|
24413
|
+
fontSize: "10px",
|
|
24414
|
+
fontFamily: "monospace",
|
|
24415
|
+
},
|
|
24416
|
+
dimSeparator: {
|
|
24417
|
+
opacity: 0.5,
|
|
24418
|
+
},
|
|
24419
|
+
overrideValue: {
|
|
24420
|
+
color: tokens.colorPaletteGreenForeground1,
|
|
24421
|
+
},
|
|
24422
|
+
busyMessage: {
|
|
24423
|
+
display: "flex",
|
|
24424
|
+
alignItems: "center",
|
|
24425
|
+
gap: tokens.spacingHorizontalXS,
|
|
24426
|
+
padding: `${tokens.spacingVerticalXXS} ${tokens.spacingHorizontalS}`,
|
|
24427
|
+
},
|
|
23039
24428
|
});
|
|
23040
|
-
// ──
|
|
23041
|
-
const
|
|
24429
|
+
// ── Project Authoring Pane ──
|
|
24430
|
+
const BabylonProjectAuthoringPane = (props) => {
|
|
23042
24431
|
const { scene, selectionService } = props;
|
|
23043
|
-
return (jsxs(Accordion, { uniqueId: "
|
|
24432
|
+
return (jsxs(Accordion, { uniqueId: "ProjectAuthoring", enablePinnedItems: true, enableHiddenItems: true, enableSearchItems: true, children: [jsx(AccordionSection, { title: "Project File", children: jsx(ProjectFileTools, { scene: scene }) }), jsx(AccordionSection, { title: "Assets", children: jsx(SmartAssetList, { scene: scene, selectionService: selectionService }) }), jsx(AccordionSection, { title: "Override Summary", children: jsx(OverrideSummary, { scene: scene }) })] }));
|
|
24433
|
+
};
|
|
24434
|
+
// ── Project File ──
|
|
24435
|
+
/**
|
|
24436
|
+
* Save/load controls for the `.babylonproj` zip bundle that captures the
|
|
24437
|
+
* scene's smart assets and overrides as a single project file.
|
|
24438
|
+
* @param props - Component props.
|
|
24439
|
+
* @returns The project file controls.
|
|
24440
|
+
*/
|
|
24441
|
+
const ProjectFileTools = (props) => {
|
|
24442
|
+
const { scene } = props;
|
|
24443
|
+
const styles = useStyles$3();
|
|
24444
|
+
const [status, setStatus] = useState("");
|
|
24445
|
+
const [busy, setBusy] = useState("");
|
|
24446
|
+
const isBusy = busy !== "";
|
|
24447
|
+
const onSaveProject = useCallback(async () => {
|
|
24448
|
+
if (isBusy) {
|
|
24449
|
+
return;
|
|
24450
|
+
}
|
|
24451
|
+
setBusy("Saving project...");
|
|
24452
|
+
setStatus("");
|
|
24453
|
+
try {
|
|
24454
|
+
const blob = await SaveProjectFileAsync(scene);
|
|
24455
|
+
Tools.Download(blob, "scene.babylonproj");
|
|
24456
|
+
setStatus("Saved scene.babylonproj");
|
|
24457
|
+
}
|
|
24458
|
+
catch (err) {
|
|
24459
|
+
setStatus(`Save error: ${err}`);
|
|
24460
|
+
}
|
|
24461
|
+
finally {
|
|
24462
|
+
setBusy("");
|
|
24463
|
+
}
|
|
24464
|
+
}, [scene, isBusy]);
|
|
24465
|
+
const onLoadProject = useCallback(async (files) => {
|
|
24466
|
+
const file = files[0];
|
|
24467
|
+
if (!file || isBusy) {
|
|
24468
|
+
return;
|
|
24469
|
+
}
|
|
24470
|
+
setBusy("Loading project...");
|
|
24471
|
+
setStatus("");
|
|
24472
|
+
try {
|
|
24473
|
+
await LoadProjectFileAsync(scene, file);
|
|
24474
|
+
setStatus(`Loaded ${file.name}`);
|
|
24475
|
+
}
|
|
24476
|
+
catch (err) {
|
|
24477
|
+
setStatus(`Load error: ${err}`);
|
|
24478
|
+
}
|
|
24479
|
+
finally {
|
|
24480
|
+
setBusy("");
|
|
24481
|
+
}
|
|
24482
|
+
}, [scene, isBusy]);
|
|
24483
|
+
return (jsxs(Fragment, { children: [jsx(ButtonLine, { label: "Save Project (.babylonproj)", icon: SaveRegular, onClick: onSaveProject, disabled: isBusy }), jsx(FileUploadLine, { label: "Load Project (.babylonproj)", accept: ".babylonproj", onClick: onLoadProject, disabled: isBusy }), isBusy && (jsxs("div", { className: styles.busyMessage, children: [jsx(Spinner, { size: "extra-small" }), jsx(Caption1, { children: busy })] })), status && jsx("div", { className: styles.statusMessage, children: status })] }));
|
|
23044
24484
|
};
|
|
23045
24485
|
// ── Smart Asset List ──
|
|
23046
24486
|
const SmartAssetList = (props) => {
|
|
@@ -23055,8 +24495,10 @@ const SmartAssetList = (props) => {
|
|
|
23055
24495
|
const compactToolContext = useMemo(() => ({ ...toolContext, size: "small" }), [toolContext]);
|
|
23056
24496
|
// Subscribe reactively to changes — re-renders the asset list whenever
|
|
23057
24497
|
// RegisterSmartAsset / Load / Remove / Reload fire onChangedObservable.
|
|
24498
|
+
// Filter out the reserved companion key so it does not appear as a user
|
|
24499
|
+
// asset alongside dragged-in textures/models.
|
|
23058
24500
|
const sam = GetSmartAssetManager(scene);
|
|
23059
|
-
const assets = useObservableState(useCallback(() => Array.from(GetAllSmartAssets(scene), ([key, url]) => ({ key, url })), [scene]), sam.onChangedObservable);
|
|
24501
|
+
const assets = useObservableState(useCallback(() => Array.from(GetAllSmartAssets(scene), ([key, url]) => ({ key, url })).filter((a) => a.key !== ProjectLocalsKey), [scene]), sam.onChangedObservable);
|
|
23060
24502
|
const onAddAsset = useCallback(() => {
|
|
23061
24503
|
fileInputRef.current?.click();
|
|
23062
24504
|
}, []);
|
|
@@ -23107,6 +24549,12 @@ const SmartAssetList = (props) => {
|
|
|
23107
24549
|
}, [scene]);
|
|
23108
24550
|
const onReloadAsset = useCallback(async (key) => {
|
|
23109
24551
|
await ReloadSmartAssetAsync(scene, key);
|
|
24552
|
+
// ReloadSmartAssetAsync disposes the old asset and loads fresh
|
|
24553
|
+
// instances — those new objects have no knowledge of previously
|
|
24554
|
+
// applied overrides, so we must re-apply them. (The swap path
|
|
24555
|
+
// does the same.) Without this, overrides on a smart asset
|
|
24556
|
+
// appear to silently revert whenever the user hits Reload.
|
|
24557
|
+
ApplyAllOverrides(scene);
|
|
23110
24558
|
setStatus(`Reloaded: ${key}`);
|
|
23111
24559
|
}, [scene]);
|
|
23112
24560
|
const onSwapAsset = useCallback((key) => {
|
|
@@ -23234,6 +24682,34 @@ const SmartAssetList = (props) => {
|
|
|
23234
24682
|
}), jsx(ButtonLine, { label: "Add Asset", icon: AddRegular, onClick: onAddAsset }), jsx("input", { ref: fileInputRef, type: "file", accept: AllAcceptString, multiple: true, className: styles.hiddenInput, onChange: onFileSelected }), status && jsx(Caption1, { className: styles.statusMessage, children: status })] }));
|
|
23235
24683
|
};
|
|
23236
24684
|
// ── Utilities ──
|
|
24685
|
+
// ── Override Summary ──
|
|
24686
|
+
/**
|
|
24687
|
+
* Pane content that lists all registered overrides for the scene. Subscribes
|
|
24688
|
+
* directly to the manager's change observable so loads, deletes, and
|
|
24689
|
+
* Inspector-driven edits update the view instantly.
|
|
24690
|
+
* @param props - Component props.
|
|
24691
|
+
* @returns The override list view.
|
|
24692
|
+
*/
|
|
24693
|
+
const OverrideSummary = (props) => {
|
|
24694
|
+
const { scene } = props;
|
|
24695
|
+
const styles = useStyles$3();
|
|
24696
|
+
const overrideManager = GetOverrideManager(scene);
|
|
24697
|
+
const overrideList = useObservableState(useCallback(() => {
|
|
24698
|
+
return GetOverrides(scene).map((o) => {
|
|
24699
|
+
const nameLabel = o.targetName === "" ? "(scene)" : o.targetIndex > 0 ? `${o.targetName}[${o.targetIndex}]` : o.targetName;
|
|
24700
|
+
return {
|
|
24701
|
+
target: `${o.targetType}.${nameLabel}`,
|
|
24702
|
+
prop: o.propertyPath,
|
|
24703
|
+
value: String(o.value),
|
|
24704
|
+
};
|
|
24705
|
+
});
|
|
24706
|
+
}, [scene]), overrideManager.onChangedObservable);
|
|
24707
|
+
if (overrideList.length === 0) {
|
|
24708
|
+
return jsx("div", { className: styles.emptyMessage, children: "No overrides tracked. Edit properties in Inspector to create overrides." });
|
|
24709
|
+
}
|
|
24710
|
+
return (jsx(Fragment, { children: overrideList.map((o, i) => (jsxs("div", { className: styles.overrideRow, children: [jsx("span", { children: o.target }), jsx("span", { className: styles.dimSeparator, children: "." }), jsx("span", { children: o.prop }), jsx("span", { className: styles.dimSeparator, children: "=" }), jsx("span", { className: styles.overrideValue, children: ShortenValue(o.value) })] }, i))) }));
|
|
24711
|
+
};
|
|
24712
|
+
// ── Utilities ──
|
|
23237
24713
|
/**
|
|
23238
24714
|
* Finds the first scene entity produced by a smart asset key, for click-to-select.
|
|
23239
24715
|
* Prefers non-root meshes, then materials, then textures.
|
|
@@ -23293,6 +24769,14 @@ function _getExtension(url) {
|
|
|
23293
24769
|
}
|
|
23294
24770
|
return "";
|
|
23295
24771
|
}
|
|
24772
|
+
/**
|
|
24773
|
+
* Truncates a value string to a maximum display length.
|
|
24774
|
+
* @param value - The value string to shorten.
|
|
24775
|
+
* @returns The truncated string, with an ellipsis if it was shortened.
|
|
24776
|
+
*/
|
|
24777
|
+
function ShortenValue(value) {
|
|
24778
|
+
return value.length > 30 ? value.substring(0, 27) + "…" : value;
|
|
24779
|
+
}
|
|
23296
24780
|
|
|
23297
24781
|
const AnimationGroupLoadingModes = [
|
|
23298
24782
|
{ label: "Clean", value: 0 /* SceneLoaderAnimationGroupLoadingMode.Clean */ },
|
|
@@ -24096,7 +25580,7 @@ function ShowInspector(scene, options = {}) {
|
|
|
24096
25580
|
// Stats pane tab and related services.
|
|
24097
25581
|
StatsServiceDefinition,
|
|
24098
25582
|
// Tools pane tab and related services.
|
|
24099
|
-
ToolsServiceDefinition, ExportServiceDefinition, SmartAssetPromptServiceDefinition,
|
|
25583
|
+
ToolsServiceDefinition, ExportServiceDefinition, SmartAssetPromptServiceDefinition, BabylonProjectAuthoringServiceDefinition, OverrideCaptureServiceDefinition, GLTFAnimationImportServiceDefinition, GLTFLoaderOptionsServiceDefinition, GLTFValidationServiceDefinition, CaptureToolsDefinition,
|
|
24100
25584
|
// Settings pane tab and related services.
|
|
24101
25585
|
SettingsServiceDefinition, InspectorSettingsServiceDefinition, WatcherSettingsServiceDefinition, ShellSettingsServiceDefinition,
|
|
24102
25586
|
// Adds a button to refresh all properties manually (when watcher is in "manual" mode).
|
|
@@ -24958,4 +26442,4 @@ const TextAreaPropertyLine = (props) => {
|
|
|
24958
26442
|
AttachDebugLayer();
|
|
24959
26443
|
|
|
24960
26444
|
export { GetPropertyDescriptor as $, Accordion as A, Button as B, CheckboxPropertyLine as C, Color4PropertyLine as D, ColorPickerPopup as E, ColorStepGradientComponent as F, ComboBox as G, ComboBoxPropertyLine as H, ConstructorFactory as I, ConvertOptions as J, DebugServiceIdentity as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, DetachDebugLayer as O, Popover as P, DraggableLine as Q, Dropdown as R, ShellServiceIdentity as S, TextInputPropertyLine as T, EntitySelector as U, Vector3PropertyLine as V, ErrorBoundary as W, ExtensibleAccordion as X, FactorGradientComponent as Y, FactorGradientList as Z, FileUploadLine as _, useToast as a, ThemeServiceIdentity as a$, GizmoServiceIdentity as a0, HexPropertyLine as a1, InfoLabel as a2, InputHexField as a3, InputHsvField as a4, Inspector as a5, InterceptFunction as a6, InterceptProperty as a7, IsPropertyReadonly as a8, LineContainer as a9, SearchBox as aA, SelectionServiceDefinition as aB, SettingsServiceIdentity as aC, SettingsStore as aD, SettingsStoreIdentity as aE, ShowInspector as aF, SidePaneContainer as aG, SkeletonSelector as aH, Slider as aI, SpinButton as aJ, StartInspectable as aK, StatsServiceIdentity as aL, StringDropdown as aM, StringDropdownPropertyLine as aN, StringifiedPropertyLine as aO, Switch as aP, SwitchPropertyLine as aQ, SyncedSliderInput as aR, SyncedSliderPropertyLine as aS, TeachingMoment as aT, TextAreaPropertyLine as aU, TextInput as aV, TextPropertyLine as aW, Textarea as aX, TextureSelector as aY, TextureUpload as aZ, Theme as a_, LinkPropertyLine as aa, LinkToEntityPropertyLine as ab, List as ac, MakeDialogTeachingMoment as ad, MakeLazyComponent as ae, MakeModularBridge as af, MakeModularTool as ag, MakePopoverTeachingMoment as ah, MakePropertyHook as ai, MakeTeachingMoment as aj, MaterialSelector as ak, NodeSelector as al, NumberDropdown as am, NumberDropdownPropertyLine as an, ObservableCollection as ao, Pane as ap, PlaceholderPropertyLine as aq, PositionedPopover as ar, PropertiesServiceIdentity as as, Property as at, PropertyContext as au, PropertyLine as av, QuaternionPropertyLine as aw, RotationVectorPropertyLine as ax, SceneExplorerServiceIdentity as ay, SearchBar as az, useInterceptObservable as b, ToastProvider as b0, ToggleButton as b1, Tooltip as b2, UploadButton as b3, Vector2PropertyLine as b4, Vector4PropertyLine as b5, WatcherServiceIdentity as b6, inspectorAssetNotFoundHandler as b7, useAngleConverters as b8, useAsyncResource as b9, useColor3Property as ba, useColor4Property as bb, useEventListener as bc, useEventfulState as bd, useKeyListener as be, useKeyState as bf, useObservableCollection as bg, useOrderedObservableCollection as bh, usePollingObservable as bi, usePropertyChangedNotifier as bj, useQuaternionProperty as bk, useResource as bl, useTheme as bm, useThemeMode as bn, useVector3Property as bo, LinkToEntity as c, SpinButtonPropertyLine as d, useProperty as e, SceneContextIdentity as f, SelectionServiceIdentity as g, useObservableState as h, AccordionSection as i, ButtonLine as j, ToolsServiceIdentity as k, AccordionSectionItem as l, AttachDebugLayer as m, BooleanBadgePropertyLine as n, BoundProperty as o, BridgeCommandRegistryIdentity as p, BuiltInsExtensionFeed as q, Checkbox as r, ChildWindow as s, Collapse as t, useExtensionManager as u, Color3GradientComponent as v, Color3GradientList as w, Color3PropertyLine as x, Color4GradientComponent as y, Color4GradientList as z };
|
|
24961
|
-
//# sourceMappingURL=index-
|
|
26445
|
+
//# sourceMappingURL=index-UoPnMkyH.js.map
|