Skip to content

Commit 10440bc

Browse files
authored
Inspector v2: GLTF Import tools (#17580)
This PR adds the remaining glTF configuration tools (Extensions config, loader plugin config, and validator) to the Inspector v2 Tools pane.| Default view: <img width="347" height="481" alt="image" src="https://github.com/user-attachments/assets/eabbdf23-85ce-422a-8ac6-229ac9ca3a46" /> After loading new file and editing loader settings: <img width="351" height="897" alt="image" src="https://github.com/user-attachments/assets/96d28b41-f3e7-4960-af2a-59e540f50a48" /> **Features:** - Configure all loader settings (bounding boxes, animations, materials, etc.) - Enable/disable glTF extensions and configure extension-specific properties - Real-time validation feedback with detailed report viewer - Animation import with merge mode configuration
1 parent d5cd422 commit 10440bc

File tree

14 files changed

+353
-46
lines changed

14 files changed

+353
-46
lines changed

packages/dev/inspector-v2/src/components/tools/importTools.tsx renamed to packages/dev/inspector-v2/src/components/tools/import/gltfAnimationImportTool.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const AnimationGroupLoadingModes = [
1717
{ label: "NoSync", value: SceneLoaderAnimationGroupLoadingMode.NoSync },
1818
] as const satisfies DropdownOption<number>[];
1919

20-
export const ImportAnimationsTools: FunctionComponent<{ scene: Scene }> = ({ scene }) => {
20+
export const GLTFAnimationImportTool: FunctionComponent<{ scene: Scene }> = ({ scene }) => {
2121
const [importDefaults, setImportDefaults] = useState({
2222
overwriteAnimations: true,
2323
animationGroupLoadingMode: SceneLoaderAnimationGroupLoadingMode.Clean,
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import type { FunctionComponent } from "react";
2+
import { GLTFLoaderCoordinateSystemMode, GLTFLoaderAnimationStartMode } from "loaders/glTF/glTFFileLoader";
3+
import type { DropdownOption } from "shared-ui-components/fluent/primitives/dropdown";
4+
import { SwitchPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/switchPropertyLine";
5+
import { NumberDropdownPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/dropdownPropertyLine";
6+
import { SyncedSliderPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/syncedSliderPropertyLine";
7+
import type { GLTFExtensionOptionsType, GLTFLoaderOptionsType } from "../../../services/panes/tools/import/gltfLoaderOptionsService";
8+
import { PropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/propertyLine";
9+
import { BoundProperty } from "../../properties/boundProperty";
10+
11+
const AnimationStartModeOptions: DropdownOption<number>[] = [
12+
{ label: "None", value: GLTFLoaderAnimationStartMode.NONE },
13+
{ label: "First", value: GLTFLoaderAnimationStartMode.FIRST },
14+
{ label: "All", value: GLTFLoaderAnimationStartMode.ALL },
15+
];
16+
17+
const CoordinateSystemModeOptions: DropdownOption<number>[] = [
18+
{ label: "Auto", value: GLTFLoaderCoordinateSystemMode.AUTO },
19+
{ label: "Right Handed", value: GLTFLoaderCoordinateSystemMode.FORCE_RIGHT_HANDED },
20+
];
21+
22+
export const GLTFLoaderOptionsTool: FunctionComponent<{
23+
loaderOptions: GLTFLoaderOptionsType;
24+
}> = ({ loaderOptions }) => {
25+
return (
26+
<PropertyLine
27+
label="Loader Options"
28+
expandByDefault={false}
29+
indentExpandedContent={true}
30+
expandedContent={
31+
<>
32+
<BoundProperty component={SwitchPropertyLine} label="Always compute bounding box" target={loaderOptions} propertyKey="alwaysComputeBoundingBox" />
33+
<BoundProperty component={SwitchPropertyLine} label="Always compute skeleton root node" target={loaderOptions} propertyKey="alwaysComputeSkeletonRootNode" />
34+
<BoundProperty
35+
component={NumberDropdownPropertyLine}
36+
label="Animation start mode"
37+
options={AnimationStartModeOptions}
38+
target={loaderOptions}
39+
propertyKey="animationStartMode"
40+
/>
41+
<BoundProperty component={SwitchPropertyLine} label="Capture performance counters" target={loaderOptions} propertyKey="capturePerformanceCounters" />
42+
<BoundProperty component={SwitchPropertyLine} label="Compile materials" target={loaderOptions} propertyKey="compileMaterials" />
43+
<BoundProperty component={SwitchPropertyLine} label="Compile shadow generators" target={loaderOptions} propertyKey="compileShadowGenerators" />
44+
<BoundProperty
45+
component={NumberDropdownPropertyLine}
46+
label="Coordinate system"
47+
options={CoordinateSystemModeOptions}
48+
target={loaderOptions}
49+
propertyKey="coordinateSystemMode"
50+
/>
51+
<BoundProperty component={SwitchPropertyLine} label="Create instances" target={loaderOptions} propertyKey="createInstances" />
52+
<BoundProperty component={SwitchPropertyLine} label="Enable logging" target={loaderOptions} propertyKey="loggingEnabled" />
53+
<BoundProperty component={SwitchPropertyLine} label="Load all materials" target={loaderOptions} propertyKey="loadAllMaterials" />
54+
<BoundProperty component={SyncedSliderPropertyLine} label="Target FPS" target={loaderOptions} propertyKey="targetFps" min={1} max={120} step={1} />
55+
<BoundProperty component={SwitchPropertyLine} label="Transparency as coverage" target={loaderOptions} propertyKey="transparencyAsCoverage" />
56+
<BoundProperty component={SwitchPropertyLine} label="Use clip plane" target={loaderOptions} propertyKey="useClipPlane" />
57+
<BoundProperty component={SwitchPropertyLine} label="Use sRGB buffers" target={loaderOptions} propertyKey="useSRGBBuffers" />
58+
</>
59+
}
60+
/>
61+
);
62+
};
63+
64+
export const GLTFExtensionOptionsTool: FunctionComponent<{
65+
extensionOptions: GLTFExtensionOptionsType;
66+
}> = ({ extensionOptions }) => {
67+
return (
68+
<PropertyLine
69+
label="Extension Options"
70+
expandByDefault={false}
71+
indentExpandedContent={true}
72+
expandedContent={
73+
<>
74+
{Object.entries(extensionOptions).map(([extensionName, options]) => {
75+
return (
76+
<BoundProperty
77+
key={extensionName}
78+
component={SwitchPropertyLine}
79+
label={extensionName}
80+
target={options}
81+
propertyKey="enabled"
82+
expandedContent={
83+
(extensionName === "MSFT_lod" && (
84+
<BoundProperty
85+
key={extensionName + "_maxLODsToLoad"}
86+
component={SyncedSliderPropertyLine}
87+
label="Maximum LODs"
88+
target={extensionOptions[extensionName]} // TS can't infer that value ~ extensionOptions[extensionName]
89+
propertyKey="maxLODsToLoad"
90+
min={1}
91+
max={10}
92+
step={1}
93+
/>
94+
)) ||
95+
undefined
96+
}
97+
/>
98+
);
99+
})}
100+
</>
101+
}
102+
/>
103+
);
104+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { FunctionComponent } from "react";
2+
3+
import type { IGLTFValidationResults } from "babylonjs-gltf2interface";
4+
5+
import { useRef } from "react";
6+
7+
import { ButtonLine } from "shared-ui-components/fluent/hoc/buttonLine";
8+
import { ChildWindow } from "shared-ui-components/fluent/hoc/childWindow";
9+
import { StringifiedPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/stringifiedPropertyLine";
10+
import { MessageBar } from "shared-ui-components/fluent/primitives/messageBar";
11+
12+
export const GLTFValidationTool: FunctionComponent<{ validationResults: IGLTFValidationResults }> = ({ validationResults }) => {
13+
const childWindow = useRef<ChildWindow>(null);
14+
15+
const issues = validationResults.issues;
16+
const hasErrors = issues.numErrors > 0;
17+
18+
return (
19+
<>
20+
<MessageBar intent={hasErrors ? "error" : "success"} message={hasErrors ? "Your file has validation issues" : "Your file is a valid glTF file"} />
21+
<StringifiedPropertyLine key="NumErrors" label="Errors" value={issues.numErrors} />
22+
<StringifiedPropertyLine key="NumWarnings" label="Warnings" value={issues.numWarnings} />
23+
<StringifiedPropertyLine key="NumInfos" label="Infos" value={issues.numInfos} />
24+
<StringifiedPropertyLine key="NumHints" label="Hints" value={issues.numHints} />
25+
<ButtonLine label="View Report Details" onClick={() => childWindow.current?.open()} />
26+
<ChildWindow id="gltfValidationResults" imperativeRef={childWindow}>
27+
<pre style={{ margin: 0, overflow: "auto" }}>
28+
<code>{JSON.stringify(validationResults, null, 2)}</code>
29+
</pre>
30+
</ChildWindow>
31+
</>
32+
);
33+
};

packages/dev/inspector-v2/src/extensibility/defaultInspectorExtensionFeed.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ export const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspecto
4141
description: "Adds new features related to importing Babylon assets.",
4242
keywords: ["import", "tools"],
4343
...BabylonWebResources,
44-
author: { name: "Alex Chuber", forumUserName: "alexchuber" },
45-
getExtensionModuleAsync: async () => await import("../services/panes/tools/importService"),
44+
author: { name: "Babylon.js", forumUserName: "" },
45+
getExtensionModuleAsync: async () => await import("../services/panes/tools/import/importService"),
4646
},
4747
{
4848
name: "Reflector",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ServiceDefinition } from "../../../../modularity/serviceDefinition";
2+
import { ToolsServiceIdentity } from "../../toolsService";
3+
import type { IToolsService } from "../../toolsService";
4+
import { GLTFAnimationImportTool } from "../../../../components/tools/import/gltfAnimationImportTool";
5+
6+
export const GLTFAnimationImportServiceDefinition: ServiceDefinition<[], [IToolsService]> = {
7+
friendlyName: "GLTF Animation Import",
8+
consumes: [ToolsServiceIdentity],
9+
factory: (toolsService) => {
10+
const contentRegistration = toolsService.addSectionContent({
11+
key: "AnimationImport",
12+
order: 40,
13+
section: "GLTF Animation Import",
14+
component: ({ context }) => <GLTFAnimationImportTool scene={context} />,
15+
});
16+
17+
return {
18+
dispose: () => {
19+
contentRegistration.dispose();
20+
},
21+
};
22+
},
23+
};
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { ISceneLoaderPlugin, ISceneLoaderPluginAsync, SceneLoaderPluginOptions } from "core/Loading/sceneLoader";
2+
import type { GLTFFileLoader, IGLTFLoaderExtension } from "loaders/glTF/glTFFileLoader";
3+
import type { ServiceDefinition } from "../../../../modularity/serviceDefinition";
4+
import type { IToolsService } from "../../toolsService";
5+
6+
import { SceneLoader } from "core/Loading/sceneLoader";
7+
import { GLTFLoaderDefaultOptions } from "loaders/glTF/glTFFileLoader";
8+
import { MessageBar } from "shared-ui-components/fluent/primitives/messageBar";
9+
import { GLTFExtensionOptionsTool, GLTFLoaderOptionsTool } from "../../../../components/tools/import/gltfLoaderOptionsTool";
10+
import { ToolsServiceIdentity } from "../../toolsService";
11+
12+
export const GLTFLoaderServiceIdentity = Symbol("GLTFLoaderService");
13+
14+
// Options exposed in Inspector includes all the properties from the default loader options (GLTFLoaderDefaultOptions)
15+
// plus some options that only exist directly on the GLTFFileLoader class itself.
16+
const CurrentLoaderOptions = Object.assign(
17+
{
18+
capturePerformanceCounters: false,
19+
loggingEnabled: false,
20+
} satisfies Pick<GLTFFileLoader, "capturePerformanceCounters" | "loggingEnabled">,
21+
GLTFLoaderDefaultOptions
22+
);
23+
24+
export type GLTFLoaderOptionsType = typeof CurrentLoaderOptions;
25+
26+
const CurrentExtensionOptions = {
27+
/* eslint-disable @typescript-eslint/naming-convention */
28+
EXT_lights_image_based: { enabled: true },
29+
EXT_mesh_gpu_instancing: { enabled: true },
30+
EXT_texture_webp: { enabled: true },
31+
EXT_texture_avif: { enabled: true },
32+
KHR_draco_mesh_compression: { enabled: true },
33+
KHR_materials_pbrSpecularGlossiness: { enabled: true },
34+
KHR_materials_clearcoat: { enabled: true },
35+
KHR_materials_iridescence: { enabled: true },
36+
KHR_materials_anisotropy: { enabled: true },
37+
KHR_materials_emissive_strength: { enabled: true },
38+
KHR_materials_ior: { enabled: true },
39+
KHR_materials_sheen: { enabled: true },
40+
KHR_materials_specular: { enabled: true },
41+
KHR_materials_unlit: { enabled: true },
42+
KHR_materials_variants: { enabled: true },
43+
KHR_materials_transmission: { enabled: true },
44+
KHR_materials_diffuse_transmission: { enabled: true },
45+
KHR_materials_volume: { enabled: true },
46+
KHR_materials_dispersion: { enabled: true },
47+
KHR_materials_diffuse_roughness: { enabled: true },
48+
KHR_mesh_quantization: { enabled: true },
49+
KHR_lights_punctual: { enabled: true },
50+
EXT_lights_area: { enabled: true },
51+
KHR_texture_basisu: { enabled: true },
52+
KHR_texture_transform: { enabled: true },
53+
KHR_xmp_json_ld: { enabled: true },
54+
MSFT_lod: { enabled: true, maxLODsToLoad: 10 },
55+
MSFT_minecraftMesh: { enabled: true },
56+
MSFT_sRGBFactors: { enabled: true },
57+
MSFT_audio_emitter: { enabled: true },
58+
} satisfies SceneLoaderPluginOptions["gltf"]["extensionOptions"];
59+
60+
export type GLTFExtensionOptionsType = typeof CurrentExtensionOptions;
61+
62+
export const GLTFLoaderOptionsServiceDefinition: ServiceDefinition<[], [IToolsService]> = {
63+
friendlyName: "GLTF Loader Options",
64+
consumes: [ToolsServiceIdentity],
65+
factory: (toolsService) => {
66+
// Subscribe to plugin activation
67+
const pluginObserver = SceneLoader.OnPluginActivatedObservable.add((plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync) => {
68+
if (plugin.name === "gltf") {
69+
const loader = plugin as GLTFFileLoader;
70+
71+
// Apply loader settings
72+
Object.assign(loader, CurrentLoaderOptions);
73+
74+
// Subscribe to extension loading
75+
loader.onExtensionLoadedObservable.add((extension: IGLTFLoaderExtension) => {
76+
const extensionOptions = CurrentExtensionOptions[extension.name as keyof GLTFExtensionOptionsType];
77+
if (extensionOptions) {
78+
// Apply extension settings
79+
Object.assign(extension, extensionOptions);
80+
}
81+
});
82+
}
83+
});
84+
85+
const loaderToolsRegistration = toolsService.addSectionContent({
86+
key: "GLTFLoaderOptions",
87+
section: "GLTF Loader",
88+
order: 50,
89+
component: () => {
90+
return (
91+
<>
92+
<MessageBar intent="info" message="Reload the file for changes to take effect" />
93+
<GLTFLoaderOptionsTool loaderOptions={CurrentLoaderOptions} />
94+
<GLTFExtensionOptionsTool extensionOptions={CurrentExtensionOptions} />
95+
</>
96+
);
97+
},
98+
});
99+
100+
return {
101+
dispose: () => {
102+
pluginObserver.remove();
103+
loaderToolsRegistration.dispose();
104+
},
105+
};
106+
},
107+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { ServiceDefinition } from "../../../../modularity/serviceDefinition";
2+
import { SceneLoader } from "core/Loading/sceneLoader";
3+
import type { ISceneLoaderPlugin, ISceneLoaderPluginAsync } from "core/Loading/sceneLoader";
4+
import type { GLTFFileLoader } from "loaders/glTF/glTFFileLoader";
5+
import { GLTFValidationTool } from "../../../../components/tools/import/gltfValidationTool";
6+
import type { IToolsService } from "../../toolsService";
7+
import { ToolsServiceIdentity } from "../../toolsService";
8+
import { MessageBar } from "shared-ui-components/fluent/primitives/messageBar";
9+
import { GLTFValidation } from "loaders/glTF/glTFValidation";
10+
import { useProperty } from "../../../../hooks/compoundPropertyHooks";
11+
12+
export const GLTFValidationServiceDefinition: ServiceDefinition<[], [IToolsService]> = {
13+
friendlyName: "GLTF Validation",
14+
consumes: [ToolsServiceIdentity],
15+
factory: (toolsService) => {
16+
const pluginObserver = SceneLoader.OnPluginActivatedObservable.add((plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync) => {
17+
if (plugin.name === "gltf") {
18+
const loader = plugin as GLTFFileLoader;
19+
loader.validate = true;
20+
}
21+
});
22+
23+
const sectionRegistration = toolsService.addSectionContent({
24+
key: "GLTFValidation",
25+
section: "GLTF Validation",
26+
order: 60,
27+
component: () => {
28+
const validationState = useProperty(GLTFValidation, "_LastResults");
29+
30+
if (!validationState) {
31+
return <MessageBar intent="info" message="Reload the file to see validation results" />;
32+
}
33+
34+
return <GLTFValidationTool validationResults={validationState} />;
35+
},
36+
});
37+
38+
return {
39+
dispose: () => {
40+
sectionRegistration.dispose();
41+
pluginObserver.remove();
42+
},
43+
};
44+
},
45+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { GLTFLoaderOptionsServiceDefinition } from "./gltfLoaderOptionsService";
2+
import { GLTFValidationServiceDefinition } from "./gltfValidationService";
3+
import { GLTFAnimationImportServiceDefinition } from "./gltfAnimationImportService";
4+
5+
export default {
6+
serviceDefinitions: [GLTFAnimationImportServiceDefinition, GLTFLoaderOptionsServiceDefinition, GLTFValidationServiceDefinition],
7+
} as const;

packages/dev/inspector-v2/src/services/panes/tools/importService.tsx

Lines changed: 0 additions & 26 deletions
This file was deleted.

packages/dev/inspector-v2/src/services/panes/toolsService.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ export const ToolsServiceDefinition: ServiceDefinition<[IToolsService], [IShellS
7070

7171
/**
7272
* Left TODO: Implement the following sections from toolsTabComponent.tsx
73-
* - GLTF Validator (see glTFComponent.tsx) (consider putting in Import tools)
7473
* - GIF (consider putting in Capture Tools)
7574
* - Replay (consider putting in Capture Tools)
7675
*/

0 commit comments

Comments
 (0)