Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
604286e
Make MessageBar title/message optional
alexchuber Dec 18, 2025
cf332d7
Update author and leave note
alexchuber Dec 18, 2025
ea859ad
Add GLTF tools (settings, validator) to Import Tools
alexchuber Dec 18, 2025
5f7abe7
Refactor
alexchuber Jan 12, 2026
70004e5
Fix validation results being discarded whenever Inspector is recreated
alexchuber Jan 15, 2026
d937a5e
Nits for validation tool
alexchuber Jan 15, 2026
30e0d55
Move validationResult state to GLTFValidation
alexchuber Jan 15, 2026
8a0a1c6
Lazy initialize results history
alexchuber Jan 15, 2026
5cb4f79
Simplify loader options state mgmt
alexchuber Jan 15, 2026
e6880ea
Naming & comment nits
alexchuber Jan 15, 2026
d56f476
Add "indentExpandedContent" option to ExpandableProperty props
alexchuber Jan 24, 2026
8fc86ac
Missed a spot for indentExpandedContent
alexchuber Jan 24, 2026
12f2d6e
Loop through extensionOptions to create components
alexchuber Jan 24, 2026
05fc2f8
Use child window for glTF validation results
alexchuber Jan 24, 2026
27da23d
Fix MessageBar optional props
alexchuber Jan 24, 2026
ebca3fa
Keep all weird GLTFValidation statics internal
alexchuber Jan 24, 2026
c7f4fee
Simplify lo-fi GLTFValidation solution
alexchuber Jan 26, 2026
36f03d4
Forgot to remove import
alexchuber Jan 26, 2026
a95bcae
Merge branch 'master' of https://github.com/BabylonJS/Babylon.js into…
alexchuber Jan 26, 2026
0523353
Merge remote-tracking branch 'origin/master' into inspector-v2-import…
ryantrem Jan 27, 2026
9fb6e8b
Use ChildWindow
ryantrem Jan 27, 2026
68d853e
Use GLTFLoaderDefaultOptions
ryantrem Jan 27, 2026
6192840
Merge remote-tracking branch 'origin/master' into inspector-v2-import…
ryantrem Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const AnimationGroupLoadingModes = [
{ label: "NoSync", value: SceneLoaderAnimationGroupLoadingMode.NoSync },
] as const satisfies DropdownOption<number>[];

export const ImportAnimationsTools: FunctionComponent<{ scene: Scene }> = ({ scene }) => {
export const GLTFAnimationImportTool: FunctionComponent<{ scene: Scene }> = ({ scene }) => {
const [importDefaults, setImportDefaults] = useState({
overwriteAnimations: true,
animationGroupLoadingMode: SceneLoaderAnimationGroupLoadingMode.Clean,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { FunctionComponent } from "react";
import { GLTFLoaderCoordinateSystemMode, GLTFLoaderAnimationStartMode } from "loaders/glTF/glTFFileLoader";
import type { DropdownOption } from "shared-ui-components/fluent/primitives/dropdown";
import { SwitchPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/switchPropertyLine";
import { NumberDropdownPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/dropdownPropertyLine";
import { SyncedSliderPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/syncedSliderPropertyLine";
import type { GLTFExtensionOptionsType, GLTFLoaderOptionsType } from "../../../services/panes/tools/import/gltfLoaderOptionsService";
import { PropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/propertyLine";
import { BoundProperty } from "../../properties/boundProperty";

const AnimationStartModeOptions: DropdownOption<number>[] = [
{ label: "None", value: GLTFLoaderAnimationStartMode.NONE },
{ label: "First", value: GLTFLoaderAnimationStartMode.FIRST },
{ label: "All", value: GLTFLoaderAnimationStartMode.ALL },
];

const CoordinateSystemModeOptions: DropdownOption<number>[] = [
{ label: "Auto", value: GLTFLoaderCoordinateSystemMode.AUTO },
{ label: "Right Handed", value: GLTFLoaderCoordinateSystemMode.FORCE_RIGHT_HANDED },
];

export const GLTFLoaderOptionsTool: FunctionComponent<{
loaderOptions: GLTFLoaderOptionsType;
}> = ({ loaderOptions }) => {
return (
<PropertyLine
label="Loader Options"
expandByDefault={false}
indentExpandedContent={true}
expandedContent={
<>
<BoundProperty component={SwitchPropertyLine} label="Always compute bounding box" target={loaderOptions} propertyKey="alwaysComputeBoundingBox" />
<BoundProperty component={SwitchPropertyLine} label="Always compute skeleton root node" target={loaderOptions} propertyKey="alwaysComputeSkeletonRootNode" />
<BoundProperty
component={NumberDropdownPropertyLine}
label="Animation start mode"
options={AnimationStartModeOptions}
target={loaderOptions}
propertyKey="animationStartMode"
/>
<BoundProperty component={SwitchPropertyLine} label="Capture performance counters" target={loaderOptions} propertyKey="capturePerformanceCounters" />
<BoundProperty component={SwitchPropertyLine} label="Compile materials" target={loaderOptions} propertyKey="compileMaterials" />
<BoundProperty component={SwitchPropertyLine} label="Compile shadow generators" target={loaderOptions} propertyKey="compileShadowGenerators" />
<BoundProperty
component={NumberDropdownPropertyLine}
label="Coordinate system"
options={CoordinateSystemModeOptions}
target={loaderOptions}
propertyKey="coordinateSystemMode"
/>
<BoundProperty component={SwitchPropertyLine} label="Create instances" target={loaderOptions} propertyKey="createInstances" />
<BoundProperty component={SwitchPropertyLine} label="Enable logging" target={loaderOptions} propertyKey="loggingEnabled" />
<BoundProperty component={SwitchPropertyLine} label="Load all materials" target={loaderOptions} propertyKey="loadAllMaterials" />
<BoundProperty component={SyncedSliderPropertyLine} label="Target FPS" target={loaderOptions} propertyKey="targetFps" min={1} max={120} step={1} />
<BoundProperty component={SwitchPropertyLine} label="Transparency as coverage" target={loaderOptions} propertyKey="transparencyAsCoverage" />
<BoundProperty component={SwitchPropertyLine} label="Use clip plane" target={loaderOptions} propertyKey="useClipPlane" />
<BoundProperty component={SwitchPropertyLine} label="Use sRGB buffers" target={loaderOptions} propertyKey="useSRGBBuffers" />
</>
}
/>
);
};

export const GLTFExtensionOptionsTool: FunctionComponent<{
extensionOptions: GLTFExtensionOptionsType;
}> = ({ extensionOptions }) => {
return (
<PropertyLine
label="Extension Options"
expandByDefault={false}
indentExpandedContent={true}
expandedContent={
<>
{Object.entries(extensionOptions).map(([extensionName, options]) => {
return (
<BoundProperty
key={extensionName}
component={SwitchPropertyLine}
label={extensionName}
target={options}
propertyKey="enabled"
expandedContent={
(extensionName === "MSFT_lod" && (
<BoundProperty
key={extensionName + "_maxLODsToLoad"}
component={SyncedSliderPropertyLine}
label="Maximum LODs"
target={extensionOptions[extensionName]} // TS can't infer that value ~ extensionOptions[extensionName]
propertyKey="maxLODsToLoad"
min={1}
max={10}
step={1}
/>
)) ||
undefined
}
/>
);
})}
</>
}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { FunctionComponent } from "react";

import type { IGLTFValidationResults } from "babylonjs-gltf2interface";

import { useRef } from "react";

import { ButtonLine } from "shared-ui-components/fluent/hoc/buttonLine";
import { ChildWindow } from "shared-ui-components/fluent/hoc/childWindow";
import { StringifiedPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/stringifiedPropertyLine";
import { MessageBar } from "shared-ui-components/fluent/primitives/messageBar";

export const GLTFValidationTool: FunctionComponent<{ validationResults: IGLTFValidationResults }> = ({ validationResults }) => {
const childWindow = useRef<ChildWindow>(null);

const issues = validationResults.issues;
const hasErrors = issues.numErrors > 0;

return (
<>
<MessageBar intent={hasErrors ? "error" : "success"} message={hasErrors ? "Your file has validation issues" : "Your file is a valid glTF file"} />
<StringifiedPropertyLine key="NumErrors" label="Errors" value={issues.numErrors} />
<StringifiedPropertyLine key="NumWarnings" label="Warnings" value={issues.numWarnings} />
<StringifiedPropertyLine key="NumInfos" label="Infos" value={issues.numInfos} />
<StringifiedPropertyLine key="NumHints" label="Hints" value={issues.numHints} />
<ButtonLine label="View Report Details" onClick={() => childWindow.current?.open()} />
<ChildWindow id="gltfValidationResults" imperativeRef={childWindow}>
<pre style={{ margin: 0, overflow: "auto" }}>
<code>{JSON.stringify(validationResults, null, 2)}</code>
</pre>
</ChildWindow>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspecto
description: "Adds new features related to importing Babylon assets.",
keywords: ["import", "tools"],
...BabylonWebResources,
author: { name: "Alex Chuber", forumUserName: "alexchuber" },
getExtensionModuleAsync: async () => await import("../services/panes/tools/importService"),
author: { name: "Babylon.js", forumUserName: "" },
getExtensionModuleAsync: async () => await import("../services/panes/tools/import/importService"),
},
{
name: "Reflector",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { ServiceDefinition } from "../../../../modularity/serviceDefinition";
import { ToolsServiceIdentity } from "../../toolsService";
import type { IToolsService } from "../../toolsService";
import { GLTFAnimationImportTool } from "../../../../components/tools/import/gltfAnimationImportTool";

export const GLTFAnimationImportServiceDefinition: ServiceDefinition<[], [IToolsService]> = {
friendlyName: "GLTF Animation Import",
consumes: [ToolsServiceIdentity],
factory: (toolsService) => {
const contentRegistration = toolsService.addSectionContent({
key: "AnimationImport",
order: 40,
section: "GLTF Animation Import",
component: ({ context }) => <GLTFAnimationImportTool scene={context} />,
});

return {
dispose: () => {
contentRegistration.dispose();
},
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { ISceneLoaderPlugin, ISceneLoaderPluginAsync, SceneLoaderPluginOptions } from "core/Loading/sceneLoader";
import type { GLTFFileLoader, IGLTFLoaderExtension } from "loaders/glTF/glTFFileLoader";
import type { ServiceDefinition } from "../../../../modularity/serviceDefinition";
import type { IToolsService } from "../../toolsService";

import { SceneLoader } from "core/Loading/sceneLoader";
import { GLTFLoaderDefaultOptions } from "loaders/glTF/glTFFileLoader";
import { MessageBar } from "shared-ui-components/fluent/primitives/messageBar";
import { GLTFExtensionOptionsTool, GLTFLoaderOptionsTool } from "../../../../components/tools/import/gltfLoaderOptionsTool";
import { ToolsServiceIdentity } from "../../toolsService";

export const GLTFLoaderServiceIdentity = Symbol("GLTFLoaderService");

// Options exposed in Inspector includes all the properties from the default loader options (GLTFLoaderDefaultOptions)
// plus some options that only exist directly on the GLTFFileLoader class itself.
const CurrentLoaderOptions = Object.assign(
{
capturePerformanceCounters: false,
loggingEnabled: false,
} satisfies Pick<GLTFFileLoader, "capturePerformanceCounters" | "loggingEnabled">,
GLTFLoaderDefaultOptions
);

export type GLTFLoaderOptionsType = typeof CurrentLoaderOptions;

const CurrentExtensionOptions = {
/* eslint-disable @typescript-eslint/naming-convention */
EXT_lights_image_based: { enabled: true },
EXT_mesh_gpu_instancing: { enabled: true },
EXT_texture_webp: { enabled: true },
EXT_texture_avif: { enabled: true },
KHR_draco_mesh_compression: { enabled: true },
KHR_materials_pbrSpecularGlossiness: { enabled: true },
KHR_materials_clearcoat: { enabled: true },
KHR_materials_iridescence: { enabled: true },
KHR_materials_anisotropy: { enabled: true },
KHR_materials_emissive_strength: { enabled: true },
KHR_materials_ior: { enabled: true },
KHR_materials_sheen: { enabled: true },
KHR_materials_specular: { enabled: true },
KHR_materials_unlit: { enabled: true },
KHR_materials_variants: { enabled: true },
KHR_materials_transmission: { enabled: true },
KHR_materials_diffuse_transmission: { enabled: true },
KHR_materials_volume: { enabled: true },
KHR_materials_dispersion: { enabled: true },
KHR_materials_diffuse_roughness: { enabled: true },
KHR_mesh_quantization: { enabled: true },
KHR_lights_punctual: { enabled: true },
EXT_lights_area: { enabled: true },
KHR_texture_basisu: { enabled: true },
KHR_texture_transform: { enabled: true },
KHR_xmp_json_ld: { enabled: true },
MSFT_lod: { enabled: true, maxLODsToLoad: 10 },
MSFT_minecraftMesh: { enabled: true },
MSFT_sRGBFactors: { enabled: true },
MSFT_audio_emitter: { enabled: true },
} satisfies SceneLoaderPluginOptions["gltf"]["extensionOptions"];

export type GLTFExtensionOptionsType = typeof CurrentExtensionOptions;

export const GLTFLoaderOptionsServiceDefinition: ServiceDefinition<[], [IToolsService]> = {
friendlyName: "GLTF Loader Options",
consumes: [ToolsServiceIdentity],
factory: (toolsService) => {
// Subscribe to plugin activation
const pluginObserver = SceneLoader.OnPluginActivatedObservable.add((plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync) => {
if (plugin.name === "gltf") {
const loader = plugin as GLTFFileLoader;

// Apply loader settings
Object.assign(loader, CurrentLoaderOptions);

// Subscribe to extension loading
loader.onExtensionLoadedObservable.add((extension: IGLTFLoaderExtension) => {
const extensionOptions = CurrentExtensionOptions[extension.name as keyof GLTFExtensionOptionsType];
if (extensionOptions) {
// Apply extension settings
Object.assign(extension, extensionOptions);
}
});
}
});

const loaderToolsRegistration = toolsService.addSectionContent({
key: "GLTFLoaderOptions",
section: "GLTF Loader",
order: 50,
component: () => {
return (
<>
<MessageBar intent="info" message="Reload the file for changes to take effect" />
<GLTFLoaderOptionsTool loaderOptions={CurrentLoaderOptions} />
<GLTFExtensionOptionsTool extensionOptions={CurrentExtensionOptions} />
</>
);
},
});

return {
dispose: () => {
pluginObserver.remove();
loaderToolsRegistration.dispose();
},
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { ServiceDefinition } from "../../../../modularity/serviceDefinition";
import { SceneLoader } from "core/Loading/sceneLoader";
import type { ISceneLoaderPlugin, ISceneLoaderPluginAsync } from "core/Loading/sceneLoader";
import type { GLTFFileLoader } from "loaders/glTF/glTFFileLoader";
import { GLTFValidationTool } from "../../../../components/tools/import/gltfValidationTool";
import type { IToolsService } from "../../toolsService";
import { ToolsServiceIdentity } from "../../toolsService";
import { MessageBar } from "shared-ui-components/fluent/primitives/messageBar";
import { GLTFValidation } from "loaders/glTF/glTFValidation";
import { useProperty } from "../../../../hooks/compoundPropertyHooks";

export const GLTFValidationServiceDefinition: ServiceDefinition<[], [IToolsService]> = {
friendlyName: "GLTF Validation",
consumes: [ToolsServiceIdentity],
factory: (toolsService) => {
const pluginObserver = SceneLoader.OnPluginActivatedObservable.add((plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync) => {
if (plugin.name === "gltf") {
const loader = plugin as GLTFFileLoader;
loader.validate = true;
}
});

const sectionRegistration = toolsService.addSectionContent({
key: "GLTFValidation",
section: "GLTF Validation",
order: 60,
component: () => {
const validationState = useProperty(GLTFValidation, "_LastResults");

if (!validationState) {
return <MessageBar intent="info" message="Reload the file to see validation results" />;
}

return <GLTFValidationTool validationResults={validationState} />;
},
});

return {
dispose: () => {
sectionRegistration.dispose();
pluginObserver.remove();
},
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { GLTFLoaderOptionsServiceDefinition } from "./gltfLoaderOptionsService";
import { GLTFValidationServiceDefinition } from "./gltfValidationService";
import { GLTFAnimationImportServiceDefinition } from "./gltfAnimationImportService";

export default {
serviceDefinitions: [GLTFAnimationImportServiceDefinition, GLTFLoaderOptionsServiceDefinition, GLTFValidationServiceDefinition],
} as const;

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export const ToolsServiceDefinition: ServiceDefinition<[IToolsService], [IShellS

/**
* Left TODO: Implement the following sections from toolsTabComponent.tsx
* - GLTF Validator (see glTFComponent.tsx) (consider putting in Import tools)
* - GIF (consider putting in Capture Tools)
* - Replay (consider putting in Capture Tools)
*/
Expand Down
Loading
Loading