Namespace sceneModel

xeokit Model Inspector


IDE-style inspect / quick-fix toolkit for SceneModel.


Catches data-integrity errors and performance / correctness warnings, surfaces them as a structured report, and dispatches each finding to a pluggable Fix that knows how to remediate it. Mental model is eslint's rule registry + IntelliJ's "fix all problems" — bring your own rules and your own remediations; the framework wires them together.


%%{init:{"theme":"dark"}}%% classDiagram direction TB class inspectSceneModel { +(params) InspectionReport } class applyFixes { +(params) SDKResult~ApplyFixesResult~ } class InspectionReport { +issues : Issue[] +errors / warnings / info : Issue[] +byCode : Map~code, Issue[]~ } class Issue { +code : string +severity : error | warning | info +message : string +resourceId? / context? } class ApplyFixesResult { +fixed : Issue[] +skipped : Issue[] +errors : Issue[] } class Inspection { +codes : string[] +run(sceneModel, push) } class Fix { +codes : string[] +apply(issue, sceneModel) } class InspectionRegistry { +register(inspection) } class FixRegistry { +register(fix) +unregister(code) } inspectSceneModel ..> InspectionRegistry : walks inspectSceneModel ..> InspectionReport : produces InspectionRegistry "1" *-- "*" Inspection InspectionReport "1" *-- "*" Issue applyFixes ..> InspectionReport : consumes applyFixes ..> FixRegistry : dispatches applyFixes ..> ApplyFixesResult : returns FixRegistry "1" *-- "*" Fix
%%{init:{"theme":"default"}}%% classDiagram direction TB class inspectSceneModel { +(params) InspectionReport } class applyFixes { +(params) SDKResult~ApplyFixesResult~ } class InspectionReport { +issues : Issue[] +errors / warnings / info : Issue[] +byCode : Map~code, Issue[]~ } class Issue { +code : string +severity : error | warning | info +message : string +resourceId? / context? } class ApplyFixesResult { +fixed : Issue[] +skipped : Issue[] +errors : Issue[] } class Inspection { +codes : string[] +run(sceneModel, push) } class Fix { +codes : string[] +apply(issue, sceneModel) } class InspectionRegistry { +register(inspection) } class FixRegistry { +register(fix) +unregister(code) } inspectSceneModel ..> InspectionRegistry : walks inspectSceneModel ..> InspectionReport : produces InspectionRegistry "1" *-- "*" Inspection InspectionReport "1" *-- "*" Issue applyFixes ..> InspectionReport : consumes applyFixes ..> FixRegistry : dispatches applyFixes ..> ApplyFixesResult : returns FixRegistry "1" *-- "*" Fix
classDiagram
    direction TB
    class inspectSceneModel {
      +(params) InspectionReport
    }
    class applyFixes {
      +(params) SDKResult~ApplyFixesResult~
    }
    class InspectionReport {
      +issues   : Issue[]
      +errors / warnings / info : Issue[]
      +byCode   : Map~code, Issue[]~
    }
    class Issue {
      +code      : string
      +severity  : error | warning | info
      +message   : string
      +resourceId? / context?
    }
    class ApplyFixesResult {
      +fixed    : Issue[]
      +skipped  : Issue[]
      +errors   : Issue[]
    }
    class Inspection {
      +codes    : string[]
      +run(sceneModel, push)
    }
    class Fix {
      +codes    : string[]
      +apply(issue, sceneModel)
    }
    class InspectionRegistry {
      +register(inspection)
    }
    class FixRegistry {
      +register(fix)
      +unregister(code)
    }
    inspectSceneModel ..> InspectionRegistry : walks
    inspectSceneModel ..> InspectionReport : produces
    InspectionRegistry "1" *-- "*" Inspection
    InspectionReport "1" *-- "*" Issue
    applyFixes ..> InspectionReport : consumes
    applyFixes ..> FixRegistry : dispatches
    applyFixes ..> ApplyFixesResult : returns
    FixRegistry "1" *-- "*" Fix

%%{init:{"theme":"dark"}}%% flowchart LR A[SceneModel] --> B[inspectSceneModel] B --> C[InspectionReport] C --> D[applyFixes] D --> E[ApplyFixesResult] E -- re-inspect --> B
%%{init:{"theme":"default"}}%% flowchart LR A[SceneModel] --> B[inspectSceneModel] B --> C[InspectionReport] C --> D[applyFixes] D --> E[ApplyFixesResult] E -- re-inspect --> B
flowchart LR
    A[SceneModel] --> B[inspectSceneModel]
    B --> C[InspectionReport]
    C --> D[applyFixes]
    D --> E[ApplyFixesResult]
    E -- re-inspect --> B

  • Pluggable rulesInspections registered into an InspectionRegistry produce typed Issues; the shipped DEFAULT_INSPECTION_REGISTRY covers structural integrity, dangling references, geometry quality, transform cycles, texture sanity, and more.
  • Pluggable fixesFixes registered into a FixRegistry claim issue codes and apply remediation in place. DEFAULT_FIX_REGISTRY ships ~20 built-ins; last-registration-wins lets plugins override built-ins by re-registering for the same code.
  • Structured byCode payloadsIssue.context carries the strategy-readable payload (e.g. {geometryId}, {duplicates: [ids]}) so fix code never has to parse a human-readable message.
  • Severity tiers — issues split into errors, warnings, and info. Errors block downstream optimisation (optimizeSceneModel refuses to run with any errors present) because auto-fixing them would mask data corruption.
  • Opt-in expensive walks — geometry-quality / duplicate / similarity / dense / oversize checks are off by default and enabled via checkX flags so a default inspection stays cheap.
  • Re-inspection loop — typical pipeline is inspect → fix → re-inspect to see the post-fix state.
  • Async variantsinspectSceneModelAsync + applyFixesAsync yield to the host periodically so very large models don't block the main thread.
  • Orchestrator facadeoptimizeSceneModel wraps the "inspect → split oversized geometries → prune orphans" path when callers want a one-call convenience.

npm install @xeokit/sdk

Three core artefacts:

  • Issue — one finding (severity, code, message, resourceId, structured context). The context field carries strategy-readable payload ({geometryId}, {duplicates: [ids]}, …) so fix code never has to parse the message.

  • InspectionReport{issues, errors, warnings, info, byCode}. The byCode Map<code, Issue[]> is the canonical "set of issue lists" view that fix dispatch iterates, and that the demo example (see examples/ValidateSceneModel_Duplex) groups by in its UI.

  • Fix{codes[], description, apply}. The apply function is the fix itself; codes is the list of issue codes the strategy claims.

Both halves of the pipeline are pluggable through symmetric registries:

Tests / one-off pipelines build fresh registries instead and pass them via the matching registry field on the params object.

// Plug a strategy into the singleton — every applyFixes() call
// sees it from now on.
import * as sceneModelInspector from "@xeokit/sdk/inspect/sceneModel";

sceneModelInspector.DEFAULT_FIX_REGISTRY.register({
codes: ["MyApp/STALE_PROPERTY_SET"],
description: "Prune deprecated property sets",
apply(issue, sceneModel) {
// … remediation in place; return {fixed: true} on success
return {ok: true, value: {fixed: true}};
},
});
// Or build a one-off registry — leaves the default untouched.
import {
FixRegistry,
mergeDuplicateGeometries,
applyFixes,
} from "@xeokit/sdk/inspect/sceneModel";

const registry = new FixRegistry([
mergeDuplicateGeometries, // pick the built-ins you want
myFix,
]);
applyFixes({sceneModel, report, registry});

Last registration wins for a given code, so plugins can override built-ins by registering after them. Use unregister(code) to opt out.

One file per inspection under studio/sceneModelInspector/inspections, all pre-registered into DEFAULT_INSPECTION_REGISTRY. Each inspection groups codes by topical concern — one walk, multiple codes — so the file count stays low and the registration order is meaningful.

Inspection Codes
geometryDataIntegrity GEOMETRY_NO_POSITIONS, GEOMETRY_POSITIONS_LENGTH, GEOMETRY_NORMALS_LENGTH, GEOMETRY_UVS_LENGTH, GEOMETRY_AABB_NONFINITE, GEOMETRY_AABB_INVERTED, GEOMETRY_INDICES_LENGTH, GEOMETRY_INDEX_OUT_OF_RANGE (errors)
meshReferences MESH_DANGLING_GEOMETRY, MESH_DANGLING_MATERIAL, MESH_DANGLING_TRANSFORM, MESH_NONFINITE_MATRIX (errors)
objectMeshReferences OBJECT_DANGLING_MESH (error)
transformParentCycles TRANSFORM_CYCLE (error)
unusedResources MATERIAL_UNUSED, TEXTURE_UNUSED, TRANSFORM_UNUSED (warnings)
identityTransforms TRANSFORM_IDENTITY (warning)
duplicateGeometries GEOMETRY_DUPLICATE (warning, opt-in via checkDuplicateGeometries)
similarGeometries GEOMETRY_SIMILAR (warning, opt-in via checkSimilarGeometries)
denseGeometries GEOMETRY_OVER_BUDGET (warning, opt-in via checkDenseGeometries)
largeGeometries GEOMETRY_OVER_EXTENT (warning, opt-in via checkLargeGeometries)
geometryQuality GEOMETRY_ZERO_VOLUME_AABB, GEOMETRY_DEGENERATE_TRIANGLES, GEOMETRY_UNUSED_VERTICES, GEOMETRY_DUPLICATE_VERTICES, GEOMETRY_NON_WATERTIGHT, GEOMETRY_INCONSISTENT_WINDING, GEOMETRY_AABB_NOT_TIGHT, GEOMETRY_DUPLICATE_INDICES (warnings, opt-in via checkGeometryQuality)
objectPlacement OBJECT_FAR_FROM_ORIGIN, OBJECT_DUPLICATE_AABB (warnings, opt-in via checkObjectStructure)
textureDimensions TEXTURE_NPOT, TEXTURE_OVERSIZED (warnings, opt-in via checkTextureSanity)
farFromOriginGeometries GEOMETRY_FAR_FROM_ORIGIN (warning, opt-in via checkGeometryFarFromOrigin)

One file per fix under studio/sceneModelInspector/fixes, all pre-registered into DEFAULT_FIX_REGISTRY.

Fix Codes handled
addMissingUVs MATERIAL_TEXTURED_GEOMETRY_NO_UVS — synthesises planar UVs
addMissingNormals MATERIAL_PBR_GEOMETRY_NO_NORMALS — synthesises smooth normals
pruneDanglingMeshRefs OBJECT_DANGLING_MESH
dropUnusedMaterial MATERIAL_UNUSED — destroys the orphan SceneMaterial
dropUnusedTexture TEXTURE_UNUSED — destroys the orphan SceneTexture
dropUnusedTransform TRANSFORM_UNUSED — destroys the orphan SceneTransform
dropIdentityTransform TRANSFORM_IDENTITY — re-parents referencers and destroys the identity transform
mergeDuplicateGeometries GEOMETRY_DUPLICATE
mergeSimilarGeometries GEOMETRY_SIMILAR — fits a rigid transform via Kabsch (Horn's quaternion method) and instances each similar geometry through its referencing meshes' matrices
splitDenseGeometry GEOMETRY_OVER_BUDGET — splits dense / over-budget geometries (one split per apply)
splitLargeGeometry GEOMETRY_OVER_EXTENT — splits large / over-extent geometries (one split per apply)
dropDegenerateTriangles GEOMETRY_DEGENERATE_TRIANGLES — drops zero-area triangles from geom.indices
compactUnusedVertices GEOMETRY_UNUSED_VERTICES — compacts unused vertex slots, remaps indices
mergeDuplicateVertices GEOMETRY_DUPLICATE_VERTICES — coalesces byte-identical vertex slots, remaps indices
downgradeNonWatertight GEOMETRY_NON_WATERTIGHT — flips SolidPrimitiveSurfacePrimitive
dropDuplicateObject OBJECT_DUPLICATE_AABB — destroys duplicate SceneObjects (detach + destroy meshes, then destroy object)
recenterGeometry GEOMETRY_FAR_FROM_ORIGIN — shifts the AABB to origin and composes the inverse offset into each referencing mesh's matrix
unifyTriangleWinding GEOMETRY_INCONSISTENT_WINDING — flood-fill flip wrongly-wound triangles to match the seed
tightenAabb GEOMETRY_AABB_NOT_TIGHT — re-quantises positions into a tight AABB to recover precision
dropDuplicateTriangles GEOMETRY_DUPLICATE_INDICES — drops duplicate triangles (same vertex set, any rotation / winding) from indices

Codes deliberately without a built-in fix (MESH_DANGLING_*, MESH_NONFINITE_MATRIX, every malformed GEOMETRY_*, TRANSFORM_CYCLE) need user judgement — auto-fixing them would mask data corruption or silently change semantics.

Typical pipeline: load → inspect → applyFixes → re-inspect → render.

import * as sceneModelInspector from "@xeokit/sdk/inspect/sceneModel";

const report = sceneModelInspector.inspectSceneModel({
sceneModel,
checkDuplicateGeometries: true, // opt-in: byte-identical detection
checkSimilarGeometries: true, // opt-in: pose-invariant shape match
});

if (report.errors.length > 0) {
// Errors block downstream optimisation — auto-fixing them
// would mask data corruption. Surface to the user instead.
for (const e of report.errors) {
console.error(`[${e.code}] ${e.message}`);
}
return;
}

const fixResult = sceneModelInspector.applyFixes({sceneModel, report});
if (!fixResult.ok) throw new Error(fixResult.error);

const {fixed, skipped, errors} = fixResult.value;
console.log(
`fixed ${fixed.length}, skipped ${skipped.length}, ` +
`errors ${errors.length}`,
);

// Re-inspect to see the post-fix state.
const after = sceneModelInspector.inspectSceneModel({sceneModel});

optimizeSceneModel is the broader orchestrator — runs inspection up-front (refusing to run if any errors are present), splits oversized geometries, and prunes orphan resources. Use it when you want the convenience facade; reach for inspectSceneModel / applyFixes directly when you want IDE-style granularity (per-rule remediation, custom strategies, partial application).

Classes

FixRegistry
InspectionRegistry

Interfaces

ApplyFixesIssueOutcome
ApplyFixesIssueOutcomeJson
ApplyFixesParams
ApplyFixesProgress
ApplyFixesResult
ApplyFixesResultJson
CodeAggregateJson
ConfigSchema
Fix
Inspection
InspectionReport
InspectionReportJson
InspectionReportToJsonParams
InspectProgress
InspectSceneModelParams
Issue
IssueHighlight
IssueJson
OptimizeSceneModelParams
ResolvedConfig
ResourceLabel
SceneModelInspectionIndex
VertexCanonicalSlots

Type Aliases

ConfigField
FixApplyResult
FixSkipReason
IssueSeverity
ResourceKind

Variables

compactUnusedVertices
DEFAULT_FIX_REGISTRY
DEFAULT_INSPECTION_REGISTRY
denseGeometries
downgradeNonWatertight
dropDegenerateTriangles
dropDuplicateObject
dropDuplicateTriangles
dropIdentityTransform
dropUnusedMaterial
dropUnusedTexture
dropUnusedTransform
duplicateGeometries
farFromOriginGeometries
geometryArrayLengths
geometryDataIntegrity
geometryQuality
identityTransforms
largeGeometries
mergeDuplicateGeometries
mergeDuplicateVertices
mergeSimilarGeometries
meshReferences
objectMeshReferences
objectPlacement
pruneDanglingMeshRefs
recenterGeometry
similarGeometries
splitDenseGeometry
splitLargeGeometry
splitOversizedGeometry
textureDimensions
tightenAabb
transformParentCycles
unifyTriangleWinding
unusedResources

Functions

applyFixes
applyFixesAsync
applyFixesResultToJson
createSceneModelInspectionIndex
descriptionForCode
findResourceLabel
findSceneObjectsForGeometry
getInspectionIndex
inspectionReportToJson
inspectSceneModel
inspectSceneModelAsync
issueToJson
labelForCode
optimizeSceneModel
outcomeToJson
resolveConfig
splitGeometryAndRebuildMeshes