-
Notifications
You must be signed in to change notification settings - Fork 0
Validation Reference
← Home
The nine validation checks Solarxy runs against every loaded model, the
severity each can raise, what trips it, and how to surface results in the
GUI, the analyze TUI, and CI. For the solarxy.toml schema that toggles and
tunes these checks, see Configuration. For the CI pipeline
recipes that publish findings to GitHub Actions / GitLab / Jenkins /
Perforce, see CI/CD Integration.
- The nine checks
- Severity and thresholds
- Where the results appear
- The in-viewport validation overlay
- The Analyze TUI's Validation tab
- Per-check reference
flowchart TD
classDef warn fill:#1F2430,stroke:#FFC44C,color:#FFC44C
classDef err fill:#1F2430,stroke:#FF3333,color:#FF3333
classDef both fill:#1F2430,stroke:#A06DFF,color:#CCCAC2
classDef mod fill:#1F2430,stroke:#5C6773,color:#5C6773
classDef io fill:#33415E,stroke:#FFC44C,color:#FFC44C
L[Load model<br/>validate_raw_model_with_config]:::io
L --> A[triangle_budget<br/>Warning or Error]:::both
A --> B[normal_mismatch<br/>Error]:::err
B --> C[flipped_normals<br/>Warning]:::warn
C --> D{file is<br/>obj/gltf/glb?}
D -->|yes| E[uv_presence<br/>Warning]:::warn
D -->|no| F[index_buffer<br/>Error]:::err
E --> F
F --> G[material_refs<br/>Error]:::err
G --> H[non_manifold_edges<br/>Error or Warning]:::both
H --> I{allow_open_mesh?}:::mod
I -->|true| J[suppress boundary warning]:::mod
I -->|false| K[keep all warnings]:::both
J --> M[degenerate_triangles<br/>Warning]:::warn
K --> M
M --> R[ValidationReport]:::io
Dispatch order, severity, and the allow_open_mesh modifier. UV checks only run for formats that carry UVs (OBJ, glTF, GLB).
The [validation] table in solarxy.toml toggles nine checks. The
analyzer, the batch validator (--paths), and the GUI's Shift+V overlay
all share this set. All are on by default except allow_open_mesh. Each
row notes the issue severity it raises - Error or Warning.
| Toggle | Default | What it flags |
|---|---|---|
normal_mismatch |
on | Error - a mesh declares per-vertex normals but the normal count does not match the vertex count. |
flipped_normals |
on |
Warning - a triangle's averaged vertex normal points away from its geometric (winding-derived) normal, below the thresholds.flipped_normal_dot cutoff. Catches reversed normals left after a mirror modifier. |
non_manifold_edges |
on |
Error / Warning - edges shared by 3+ triangles (error), and - unless allow_open_mesh is set - boundary/open edges with a single adjacent face (warning). |
triangle_budget |
on |
Warning / Error - the model's total triangle count exceeds the per-category [budgets] value: a warning over budget, an error past the tolerance band (thresholds.triangle_budget_tolerance_percent). |
allow_open_mesh |
off |
Modifier (not a check) - when true, suppresses the boundary/open-edge warning from non_manifold_edges so deliberately unsealed assets don't trip CI gates. |
degenerate_triangles |
on | Warning - triangles with near-zero area (collinear or coincident vertices). |
material_refs |
on | Error - a mesh's material index points to a slot beyond the model's material list. |
uv_presence |
on | Warning - a UV-coordinate count that doesn't match the vertex count (any format), or missing UVs entirely. The missing-UV check runs for OBJ / glTF / GLB only - STL and PLY are exempt. |
index_buffer |
on | Error - an empty index buffer, or an index count not divisible by three (a non-triangulated mesh). |
Per-check causes, fixes, and overlay appearance live in Per-check reference below.
Two numeric knobs live in [thresholds] of solarxy.toml:
| Threshold | Default | What it controls |
|---|---|---|
triangle_budget_tolerance_percent |
20.0 |
The warning-to-error boundary for triangle_budget. Any triangle count over budget warns; a count above budget * (1 + tolerance%) is an error. |
flipped_normal_dot |
-0.5 |
The dot-product cutoff for flipped_normals. A triangle is flagged when its averaged vertex normal dots below this value against the geometric (winding-derived) normal. The default -0.5 corresponds to roughly 120° between the two normals - a clear reversal, not a noisy small-angle case. |
Consider a prop_barrel_01.glb classified by [[filenames.rules]] as
prop, with the default budgets.prop = 20000 and the default
thresholds.triangle_budget_tolerance_percent = 20.0. The grace band is
budget * (1 + tolerance / 100) = 20000 * 1.2 = 24000:
| Triangle count | Result | Why |
|---|---|---|
| 19,500 | Clean | Under budget. |
| 20,001 | Warning | Over 20,000 but within the 24,000 grace band. |
| 24,001 | Error | Past the grace band. |
The grace band is your buffer for iteration. Tighten the tolerance percent
toward 10.0 for stricter gates; loosen toward 30.0+ to let artists hit
budget with less precision.
The same ValidationReport data drives three surfaces:
-
The GUI -
Shift+Vpaints colour-coded issue highlights on the mesh, and the Properties panel's Validation section lists each issue (click a row to fly the active camera to it). - The Analyze TUI - the Validation tab.
-
CI - batch validation publishes findings to PR annotations, JUnit
reports, SARIF, or plain JSON via the
--adapterand--adapter-formatflags. See CI/CD Integration.
Press Shift+V to toggle the overlay. The overlay paints each affected
mesh with a colour that maps to the highest-severity issue category on that
mesh. When a mesh has both an error and a warning, the error category wins.
| Category | Issues that map to it | Overlay colour |
|---|---|---|
| Error (catch-all) |
EmptyIndices, NonTriangulated, MissingTexture
|
Red |
| InvalidMaterial | InvalidMaterialRef |
Orange |
| NormalMismatch |
NormalMismatch, FlippedNormals
|
Cyan |
| MissingUvs |
MissingUvs, UvMismatch
|
Magenta |
| DegenerateTriangles | DegenerateTriangles |
Yellow |
| NonManifoldEdge | NonManifoldEdge |
Orange-red, painted on edges (not faces) |
Face highlights render at 40% alpha so the underlying shading stays
readable; the NonManifoldEdge edge highlight is 60% alpha because edges
are thinner and need extra contrast.
The triangle_budget check has Model scope (not Mesh or Edge), so it does
not paint the overlay. The finding still appears in the Properties panel's
Validation section and in the Analyze TUI.
The overlay renders inside the MSAA main pass, so it antialiases like the rest of the geometry. It respects mesh visibility - hide a mesh in the Outliner and its overlay vanishes with it.
solarxy-cli --mode analyze -m model.glb opens a four-tab TUI. The fourth
tab lists every validation issue with severity tag, scope, and message.
Navigate with:
| Key | Action |
|---|---|
Tab / Shift+Tab
|
Cycle tabs |
1-4
|
Jump to a tab directly |
j / k, ↓ / ↑ |
Scroll one line |
g / G
|
Top / bottom |
PgDn / PgUp
|
Scroll 20 lines |
e |
Export a text report (prompts for a filename) |
J |
Export a JSON report (prompts for a filename) |
q / Esc
|
Quit |
Each row shows the severity tag, the scope (Mesh N, or Edge in Mesh N),
and a descriptive message.
To compose the GUI and TUI views: load the model in solarxy, toggle the
in-viewport overlay with Shift+V, and read the Properties panel's
Validation section for the same finding list. Clicking a row in the
Properties panel flies the active camera to the affected mesh.

Each subsection has the same five blocks: What it flags / Severity + Toggle / Common causes / How to fix / Overlay appearance. Fix guidance stays generic across DCC tools - the underlying operations (recompute normals, weld vertices, triangulate) exist in every DCC under different menu names.
What it flags: A mesh declares per-vertex normals, but the normal-array length does not match the vertex-array length.
Severity: Error. Toggle: validation.normal_mismatch (on by default).
Common causes: An exporter that dropped some normals; a custom geometry assembly that built a partial normal buffer; a file converter that stripped trailing data.
How to fix: Re-export the mesh with normals fully populated, or strip normals entirely so they are face-derived (legal for STL and PLY; OBJ and glTF expect normals when the format permits them).
Overlay appearance: Cyan face tint on the affected mesh, shared with
flipped_normals.
What it flags: A triangle's averaged vertex normal dots below
thresholds.flipped_normal_dot (default -0.5, roughly 120°) against its
geometric, winding-derived normal.
Severity: Warning. Toggle: validation.flipped_normals (on by default).
Common causes: A mirror modifier left without "flip normals" applied; an export pass that reversed face winding without flipping normals; manual vertex-normal edits that drifted from the face plane.
How to fix: Recompute normals outside (the standard DCC operation), or
manually flip the affected face's normal or winding. For mirror-symmetric
meshes that legitimately have opposing normals (mirrored hair cards, for
example), loosen flipped_normal_dot toward -1.0 to suppress noisy
flagging.
Overlay appearance: Cyan face tint on the affected mesh, shared with
normal_mismatch.
What it flags: Edges shared by 3 or more triangles, and - unless
allow_open_mesh is set - boundary edges with exactly one adjacent face.
Severity: Error (3+-face edges); Warning (boundary edges).
Toggle: validation.non_manifold_edges (on by default).
Common causes: Manual modeling that left T-junctions or unwelded vertices; boolean operations that did not clean up shared edges; mesh combines that overlapped at edges instead of stitching them.
How to fix: Weld duplicate vertices. For 3+-face edges, separate the
overlapping faces or remove the extras. For boundary edges that are
deliberate (foliage cards, cloth, hair planes), set
allow_open_mesh = true in solarxy.toml so the warning does not fire.
Overlay appearance: Orange-red edges on the affected mesh - the only check that paints edges instead of faces, drawn at 60% alpha for readability against the shaded surface.
What it flags: The model's total triangle count exceeds the budget for
its category. The grace band before the error threshold is
budget * (1 + thresholds.triangle_budget_tolerance_percent / 100). See
Severity and thresholds for the worked example.
Severity: Warning (over budget); Error (past the grace band).
Toggle: validation.triangle_budget (on by default).
Common causes: A vendor delivered a higher-detail mesh than the budget
allows; a subdivision pass was not applied in reverse before export; a
hero asset was misclassified into the prop category by
[[filenames.rules]].
How to fix: Decimate the mesh (LOD pass, manual retopology, or a
remesh modifier), or re-categorize the asset via [[filenames.rules]] if
it really belongs in a higher-budget category. The tolerance band is your
buffer for iteration - artists see warnings; only the error band blocks
CI.
Overlay appearance: None. The check has Model scope, so it does not paint the in-viewport overlay. The finding appears in the Properties panel's Validation section and in the Analyze TUI.
What it does: Modifier, not a check. When true, suppresses the
boundary-edge warning from non_manifold_edges.
Severity: N/A. Toggle: validation.allow_open_mesh (off by default -
the only check off by default).
When to flip on: Your pipeline ships deliberately unsealed assets - foliage cards, hair planes, cloth ribbons, anything single-sided. Without this, every leaf card fires a boundary-edge warning.
How to apply: Flip on in solarxy.toml when assets are deliberately
open-mesh; leave off when assets should all be watertight. Do not
blanket-enable just to silence noise on solid assets that happen to have
unintended boundary edges - that hides genuine problems on the rest of
your library.
Overlay appearance: No overlay. It is a modifier on
non_manifold_edges.
What it flags: Triangles with near-zero area - collinear vertices, two coincident vertices, or all three coincident.
Severity: Warning. Toggle: validation.degenerate_triangles (on by
default).
Common causes: Modeling that left zero-area faces after a vertex weld; subdivision that created near-degenerate triangles at poles; importer artifacts at format boundaries.
How to fix: Run a "remove degenerate faces" or "delete zero-area faces" command in your DCC. For Solarxy's purposes, a triangle is degenerate when its cross-product magnitude approaches zero.
Overlay appearance: Yellow face tint on the affected mesh.
What it flags: A mesh's material index points to a slot beyond the model's material list - a stale index pointing nowhere.
Severity: Error. Toggle: validation.material_refs (on by default).
Common causes: A mesh assignment to a material that was later deleted;
an exporter bug that wrote material indices without updating the material
list; a manual .mtl edit that removed a material a mesh still
references.
How to fix: Re-assign the mesh to a valid material, or restore the
missing material. For OBJ files, check that usemtl directives line up
with newmtl declarations in the .mtl.
Overlay appearance: Orange face tint on the affected mesh.
What it flags: A UV-coordinate count that does not match the vertex count, or missing UVs entirely. Runs only for OBJ, glTF, and GLB - STL and PLY are exempt because those formats have no UVs by definition.
Severity: Warning. Toggle: validation.uv_presence (on by
default).
Common causes: An export pass that dropped UVs; a mesh assembled without unwrapping; a custom geometry-build pipeline that did not propagate UVs.
How to fix: Unwrap the mesh in your DCC and re-export. Even a trivial planar projection is better than no UVs for assets that will receive textures downstream.
Overlay appearance: Magenta face tint on the affected mesh.
What it flags: An empty index buffer (vertices but no triangles), or an index count not divisible by three (a non-triangulated mesh).
Severity: Error. Toggle: validation.index_buffer (on by default).
Common causes: An export pass that wrote the vertex buffer but skipped the index buffer; a mesh containing quads or n-gons in a format that requires triangles; a generated mesh that was not triangulated.
How to fix: Triangulate the mesh in your DCC before exporting. Most tools have a "Triangulate" modifier or export option. The glTF spec specifically requires triangles - enable triangulation at export time when targeting glTF / GLB.
Overlay appearance: Red face tint on the affected mesh.
See also: Configuration · CI/CD Integration · CLI Reference · Tutorial: Build a custom validation policy · Troubleshooting
Solarxy - A lightweight, cross-platform 3D model viewer and validator built with Rust and wgpu.
GitHub Repository · Releases & Downloads
© 2026 Marko Koljancic · MIT License
Getting Started
Tutorials
Using Solarxy
Reference
Help
Project