diff --git a/packages/dev/inspector-v2/src/components/tools/importTools.tsx b/packages/dev/inspector-v2/src/components/tools/import/gltfAnimationImportTool.tsx similarity index 95% rename from packages/dev/inspector-v2/src/components/tools/importTools.tsx rename to packages/dev/inspector-v2/src/components/tools/import/gltfAnimationImportTool.tsx index 69708ba9aba..967e7a15548 100644 --- a/packages/dev/inspector-v2/src/components/tools/importTools.tsx +++ b/packages/dev/inspector-v2/src/components/tools/import/gltfAnimationImportTool.tsx @@ -17,7 +17,7 @@ const AnimationGroupLoadingModes = [ { label: "NoSync", value: SceneLoaderAnimationGroupLoadingMode.NoSync }, ] as const satisfies DropdownOption[]; -export const ImportAnimationsTools: FunctionComponent<{ scene: Scene }> = ({ scene }) => { +export const GLTFAnimationImportTool: FunctionComponent<{ scene: Scene }> = ({ scene }) => { const [importDefaults, setImportDefaults] = useState({ overwriteAnimations: true, animationGroupLoadingMode: SceneLoaderAnimationGroupLoadingMode.Clean, diff --git a/packages/dev/inspector-v2/src/components/tools/import/gltfLoaderOptionsTool.tsx b/packages/dev/inspector-v2/src/components/tools/import/gltfLoaderOptionsTool.tsx new file mode 100644 index 00000000000..9c7600480a8 --- /dev/null +++ b/packages/dev/inspector-v2/src/components/tools/import/gltfLoaderOptionsTool.tsx @@ -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[] = [ + { label: "None", value: GLTFLoaderAnimationStartMode.NONE }, + { label: "First", value: GLTFLoaderAnimationStartMode.FIRST }, + { label: "All", value: GLTFLoaderAnimationStartMode.ALL }, +]; + +const CoordinateSystemModeOptions: DropdownOption[] = [ + { label: "Auto", value: GLTFLoaderCoordinateSystemMode.AUTO }, + { label: "Right Handed", value: GLTFLoaderCoordinateSystemMode.FORCE_RIGHT_HANDED }, +]; + +export const GLTFLoaderOptionsTool: FunctionComponent<{ + loaderOptions: GLTFLoaderOptionsType; +}> = ({ loaderOptions }) => { + return ( + + + + + + + + + + + + + + + + + } + /> + ); +}; + +export const GLTFExtensionOptionsTool: FunctionComponent<{ + extensionOptions: GLTFExtensionOptionsType; +}> = ({ extensionOptions }) => { + return ( + + {Object.entries(extensionOptions).map(([extensionName, options]) => { + return ( + + )) || + undefined + } + /> + ); + })} + + } + /> + ); +}; diff --git a/packages/dev/inspector-v2/src/components/tools/import/gltfValidationTool.tsx b/packages/dev/inspector-v2/src/components/tools/import/gltfValidationTool.tsx new file mode 100644 index 00000000000..b65b9c7276f --- /dev/null +++ b/packages/dev/inspector-v2/src/components/tools/import/gltfValidationTool.tsx @@ -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(null); + + const issues = validationResults.issues; + const hasErrors = issues.numErrors > 0; + + return ( + <> + + + + + + childWindow.current?.open()} /> + +
+                    {JSON.stringify(validationResults, null, 2)}
+                
+
+ + ); +}; diff --git a/packages/dev/inspector-v2/src/extensibility/defaultInspectorExtensionFeed.ts b/packages/dev/inspector-v2/src/extensibility/defaultInspectorExtensionFeed.ts index 4fbf78aa5c4..0adcd818a83 100644 --- a/packages/dev/inspector-v2/src/extensibility/defaultInspectorExtensionFeed.ts +++ b/packages/dev/inspector-v2/src/extensibility/defaultInspectorExtensionFeed.ts @@ -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", diff --git a/packages/dev/inspector-v2/src/services/panes/tools/import/gltfAnimationImportService.tsx b/packages/dev/inspector-v2/src/services/panes/tools/import/gltfAnimationImportService.tsx new file mode 100644 index 00000000000..59a688bffb7 --- /dev/null +++ b/packages/dev/inspector-v2/src/services/panes/tools/import/gltfAnimationImportService.tsx @@ -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 }) => , + }); + + return { + dispose: () => { + contentRegistration.dispose(); + }, + }; + }, +}; diff --git a/packages/dev/inspector-v2/src/services/panes/tools/import/gltfLoaderOptionsService.tsx b/packages/dev/inspector-v2/src/services/panes/tools/import/gltfLoaderOptionsService.tsx new file mode 100644 index 00000000000..9295a2229c7 --- /dev/null +++ b/packages/dev/inspector-v2/src/services/panes/tools/import/gltfLoaderOptionsService.tsx @@ -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, + 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 ( + <> + + + + + ); + }, + }); + + return { + dispose: () => { + pluginObserver.remove(); + loaderToolsRegistration.dispose(); + }, + }; + }, +}; diff --git a/packages/dev/inspector-v2/src/services/panes/tools/import/gltfValidationService.tsx b/packages/dev/inspector-v2/src/services/panes/tools/import/gltfValidationService.tsx new file mode 100644 index 00000000000..8b4979b53ea --- /dev/null +++ b/packages/dev/inspector-v2/src/services/panes/tools/import/gltfValidationService.tsx @@ -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 ; + } + + return ; + }, + }); + + return { + dispose: () => { + sectionRegistration.dispose(); + pluginObserver.remove(); + }, + }; + }, +}; diff --git a/packages/dev/inspector-v2/src/services/panes/tools/import/importService.tsx b/packages/dev/inspector-v2/src/services/panes/tools/import/importService.tsx new file mode 100644 index 00000000000..f140c3976a5 --- /dev/null +++ b/packages/dev/inspector-v2/src/services/panes/tools/import/importService.tsx @@ -0,0 +1,7 @@ +import { GLTFLoaderOptionsServiceDefinition } from "./gltfLoaderOptionsService"; +import { GLTFValidationServiceDefinition } from "./gltfValidationService"; +import { GLTFAnimationImportServiceDefinition } from "./gltfAnimationImportService"; + +export default { + serviceDefinitions: [GLTFAnimationImportServiceDefinition, GLTFLoaderOptionsServiceDefinition, GLTFValidationServiceDefinition], +} as const; diff --git a/packages/dev/inspector-v2/src/services/panes/tools/importService.tsx b/packages/dev/inspector-v2/src/services/panes/tools/importService.tsx deleted file mode 100644 index e474c3440c3..00000000000 --- a/packages/dev/inspector-v2/src/services/panes/tools/importService.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { ServiceDefinition } from "../../../modularity/serviceDefinition"; -import { ToolsServiceIdentity } from "../toolsService"; -import type { IToolsService } from "../toolsService"; -import { ImportAnimationsTools } from "../../../components/tools/importTools"; - -export const SceneImportServiceDefinition: ServiceDefinition<[], [IToolsService]> = { - friendlyName: "Import Tool", - consumes: [ToolsServiceIdentity], - factory: (toolsService) => { - const contentRegistration = toolsService.addSectionContent({ - key: "AnimationImport", - section: "Animation Import", - component: ({ context }) => , - }); - - return { - dispose: () => { - contentRegistration.dispose(); - }, - }; - }, -}; - -export default { - serviceDefinitions: [SceneImportServiceDefinition], -} as const; diff --git a/packages/dev/inspector-v2/src/services/panes/toolsService.tsx b/packages/dev/inspector-v2/src/services/panes/toolsService.tsx index 95e97411270..4bd2fe2b25a 100644 --- a/packages/dev/inspector-v2/src/services/panes/toolsService.tsx +++ b/packages/dev/inspector-v2/src/services/panes/toolsService.tsx @@ -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) */ diff --git a/packages/dev/loaders/src/glTF/glTFValidation.ts b/packages/dev/loaders/src/glTF/glTFValidation.ts index 3c0d1f95359..017b5b12d23 100644 --- a/packages/dev/loaders/src/glTF/glTFValidation.ts +++ b/packages/dev/loaders/src/glTF/glTFValidation.ts @@ -2,6 +2,7 @@ /* eslint-disable no-restricted-syntax */ /* eslint-disable @typescript-eslint/promise-function-async */ import type * as GLTF2 from "babylonjs-gltf2interface"; +import type { Nullable } from "core/types"; import { Tools } from "core/Misc/tools"; // eslint-disable-next-line @typescript-eslint/naming-convention @@ -99,6 +100,12 @@ export class GLTFValidation { private static _LoadScriptPromise: Promise; + /** + * The most recent validation results. + * @internal - Used for back-compat in Sandbox with Inspector V2. + */ + public static _LastResults: Nullable = null; + /** * Validate a glTF asset using the glTF-Validator. * @param data The JSON of a glTF or the array buffer of a binary glTF @@ -143,6 +150,7 @@ export class GLTFValidation { case "validate.resolve": { worker.removeEventListener("error", onError); worker.removeEventListener("message", onMessage); + GLTFValidation._LastResults = data.value; resolve(data.value); worker.terminate(); break; diff --git a/packages/dev/sharedUiComponents/src/fluent/hoc/childWindow.tsx b/packages/dev/sharedUiComponents/src/fluent/hoc/childWindow.tsx index 2ce52b5d3e5..e273fa12441 100644 --- a/packages/dev/sharedUiComponents/src/fluent/hoc/childWindow.tsx +++ b/packages/dev/sharedUiComponents/src/fluent/hoc/childWindow.tsx @@ -1,7 +1,7 @@ import type { GriffelRenderer } from "@fluentui/react-components"; import type { FunctionComponent, PropsWithChildren, Ref } from "react"; -import { createDOMRenderer, FluentProvider, makeStyles, Portal, RendererProvider } from "@fluentui/react-components"; +import { createDOMRenderer, FluentProvider, Portal, RendererProvider } from "@fluentui/react-components"; import { useCallback, useEffect, useImperativeHandle, useState } from "react"; import { Logger } from "core/Misc/logger"; @@ -28,15 +28,6 @@ function ToFeaturesString(options: ChildWindowOptions) { return features.map((feature) => `${feature.key}=${feature.value}`).join(","); } -const useStyles = makeStyles({ - container: { - display: "flex", - flexGrow: 1, - flexDirection: "column", - overflow: "hidden", - }, -}); - export type ChildWindowOptions = { /** * The default width of the child window in pixels. @@ -108,7 +99,6 @@ export type ChildWindowProps = { */ export const ChildWindow: FunctionComponent> = (props) => { const { id, children, onOpenChange, imperativeRef: imperativeRef } = props; - const classes = useStyles(); const [windowState, setWindowState] = useState<{ mountNode: HTMLElement; renderer: GriffelRenderer }>(); const [childWindow, setChildWindow] = useState(); @@ -268,7 +258,16 @@ export const ChildWindow: FunctionComponent> {/* RenderProvider manages Fluent style/class state. */} {/* Fluent Provider is needed for managing other Fluent state and applying the current theme mode. */} - + {children} diff --git a/packages/dev/sharedUiComponents/src/fluent/hoc/propertyLines/propertyLine.tsx b/packages/dev/sharedUiComponents/src/fluent/hoc/propertyLines/propertyLine.tsx index d37498bad2e..dabd3f98fad 100644 --- a/packages/dev/sharedUiComponents/src/fluent/hoc/propertyLines/propertyLine.tsx +++ b/packages/dev/sharedUiComponents/src/fluent/hoc/propertyLines/propertyLine.tsx @@ -56,6 +56,9 @@ const usePropertyLineStyles = makeStyles({ expandedContentDiv: { overflow: "hidden", }, + expandedContentDivIndented: { + paddingLeft: tokens.spacingHorizontalM, + }, checkbox: { display: "flex", alignItems: "center", @@ -109,7 +112,7 @@ type NonNullableProperty = { ignoreNullable?: false; }; -// Only expect optional expandByDefault prop if expandedContent is defined +// Only expect optional expandByDefault or indentExpandedContent prop if expandedContent is defined type ExpandableProperty = { /** * If supplied, an 'expand' icon will be shown which, when clicked, renders this component within the property line. @@ -120,6 +123,11 @@ type ExpandableProperty = { * If true, the expanded content will be shown by default. */ expandByDefault?: boolean; + + /** + * If true, the expanded content will be indented to the right. + */ + indentExpandedContent?: boolean; }; // If expanded content is undefined, don't expect expandByDefault prop @@ -210,7 +218,7 @@ export const PropertyLine = forwardRef {expandedContent && ( -
{expandedContent}
+
{expandedContent}
)} diff --git a/packages/dev/sharedUiComponents/src/fluent/primitives/messageBar.tsx b/packages/dev/sharedUiComponents/src/fluent/primitives/messageBar.tsx index 0d7547d2abb..c8159b53aa9 100644 --- a/packages/dev/sharedUiComponents/src/fluent/primitives/messageBar.tsx +++ b/packages/dev/sharedUiComponents/src/fluent/primitives/messageBar.tsx @@ -12,7 +12,7 @@ const useClasses = makeStyles({ type MessageBarProps = { message: string; - title: string; + title?: string; docLink?: string; intent: "info" | "success" | "warning" | "error"; }; @@ -25,7 +25,7 @@ export const MessageBar: FunctionComponent = (props) => {
- {header} + {header && {header}} {message} {docLink && ( <>