UV and texture review built exclusively for AI agents: a mesh and its maps in, exact measurements + evidence renders out.
Project description
texturesight
UV and texture review built exclusively for AI agents. A mesh and its maps in; exact measurements, located findings, and evidence renders out.
Texture work is judged by putting a checker on the model and looking, or tiling a plane and squinting. An agent can do neither — and it turns out none of it needs eyes. Texel density is UV area against 3D area. Stretch is the singular values of the per-face UV Jacobian. A seam is an edge whose two faces land in different UV islands. A normal map is either unit-length vectors or it is broken.
model.obj + maps -> texturesight inspect -> report.json + uv_layout.png -> fix -> diff
What it measures
| texel density | px per world unit, area-weighted, with the spread across the mesh |
| distortion | per-face anisotropy from the UV Jacobian's SVD; flipped winding |
| islands & seams | UV islands vs mesh shells; seams counted from the topology, with their 3D length |
| packing | utilization of the 0..1 square, overlap between islands, out-of-bounds faces |
| tiling | the wrap jump judged against the texture's own pixel-to-pixel variation |
| repetition | autocorrelation peak and offset (reported, not moralised about) |
| normal maps | unit length, Z channel validity, green convention (OpenGL/DirectX) |
| data maps | range used, distinct levels, constant/quantised detection, grayscale check |
| compression | codec blocking, required on both axes because a codec's grid is square |
Findings carry where and a try:. Everything is deterministic.
Quickstart
pip install texturesight
texturesight inspect --mesh model.obj --texture-px 2048
# -> out/report.json
# -> out/uv_layout.png (islands; red = flipped, orange = stretched)
# -> out/uv_density.png (texel density painted per face)
texturesight inspect --texture rock_normal.png # kind guessed from the name
texturesight inspect --texture rough.png --kind roughness
Or as a Claude Code plugin:
/plugin marketplace add VortexJer/AISight
/plugin install texturesight@aisight
(the plugin carries the skill; the CLI it drives still comes from pip)
Exit codes: 0 ok, 1 bad input, 2 a FAIL-level finding.
Never unwrapped a mesh? Two renders explain the whole domain
A UV layout is unreadable if you have never made one, so every mesh inspect also writes the two pictures that explain it without words:
correspondence.png — same colour = same piece of surface. The flat shapes on the right ARE the 3D faces on the left, peeled onto the texture like a cardboard box unfolded into its die-cut template. Where a piece lands decides which pixels paint it.
checker_preview.png — the broken cube wearing a checker through its own UVs, front and back. Every defect the report measures is now visible to any eye: the giant L = the density-starved island (its cells are 3x the size), the squashed cells = the 4:1 stretch, and the mirrored L marks on the back = the flipped faces.
Proof it works: known ground truth
examples/01-cube is synthetic on purpose — every defect is injected by
make_assets.py with a known magnitude, so the tests can assert the
right answer rather than merely an answer.
| injected | measured |
|---|---|
| one face's UVs squashed to 1/4 in u | anisotropy max 4.0:1 (exact) |
| one island scaled to 1/3 | density spread 3.0x, min 109.23 of 327.68 px/unit (exact) |
| one face's UV winding reversed | [FAIL] 2 face(s) have inverted UV winding |
| one island moved outside 0..1 | 2 face(s) fall outside, uv bbox [0.01, 0.01, 1.99, 0.66] |
| a normal map gamma-encoded (sRGB'd) | [FAIL] 88.2% of texels are not unit-length (mean 1.1612) |
| a texture's edges darkened | does not tile: the wrap jump is 34.74x the texture's own variation |
| a roughness map quantised to 6 levels | only 4 distinct levels: it was quantised somewhere |
And the clean cube reports anisotropy exactly 1.0, density spread exactly 1.0x, zero findings, exit 0.
the broken cube: red = flipped winding, orange = the 4:1 stretch, the small island = the density hole (the sixth is off-canvas, which is the out-of-bounds evidence)
Three bugs the clean reference caught
The clean cube exists to fail loudly when the maths is wrong, and it did — all three of these were found before shipping, by the reference, not by a user:
- A transposed matrix in the UV Jacobian reported a conformal map
(square UVs on square faces) as 2.618:1 stretched. Every asset would
have been accused of stretch.
(
test_uniform_scale_is_conformal) - Codec blocking was tested on one axis, so a posterised roughness
map — contour bands that happen to correlate with x but not y — was
reported as JPEG artifacts. A codec's grid is square; both axes must
agree. (
test_banding_is_not_reported_as_codec_blocking) - Repetition warned on every tiling texture. A texture that tiles is periodic by definition. It is now reported as a measurement, and only warned about for a tight repeat inside a single tile.
False positives are how a tool loses the right to be believed, so they are treated as bugs of the same severity as false negatives.
Before / after: one turn of the loop
examples/02-crate is the classic layout edit gone
wrong: the lid's island packed at 1/3 scale. Invisible on the model
until the texture is painted — then the lid is blurry forever. The
finding names the island, its place, and the factor:
islands: #4 is the sparsest (2.05 px/unit, 2 face(s)) vs #0 (6.14)
[WARN] texel density varies 3.0x across the mesh
where: island #4 at uv (0.35, 0.35)-(0.45, 0.45): 2.05 px/unit vs 5.46 mesh mean
try: scale island #4 up ~2.7x in the UV editor
Apply the try: line, re-inspect into a new out dir, and prove it:
texturesight diff out_starved out_fixed
density: mean 5.46 -> 6.14 px/unit; spread 3.0x -> 1.0x
GONE [texel-density-uneven] texel density varies 3.0x across the mesh
before: island #4 dark (starved) · after: uniform density
Blind vs measured: the hero crate
examples/03-crate-hero is the full study: a
576-triangle sci-fi supply crate, unwrapped and textured twice — once
by a cold-context agent with no tools at all (numpy + PIL, one
shot), once through this tool's loop. The blind build's painted maps
survive the audit untouched; its little UV mapper hides three real
bugs: 148 flipped faces (FAIL), 7.35:1 stretch, 54x texel density
spread. The after build imports the blind generator, rewrites only
the mapper, and lands at 0 flips, 1.005 p95 anisotropy, 3.35x spread —
with the two remaining warnings explained as intent (trim-sheet
overlap, downres'd hidden faces).
left: the blind unwrap — 148 flipped faces in red, islands stacked 336 deep · right: the rewritten mapper — 0 flips, welded shells, 92% packing
texel density painted per face — blind: a 54x spread between the best- and worst-fed faces · after: 3.35x, with the hidden faces' downres declared as intent
And the same pair through the human-eye renders — read them knowing what they CAN'T show:
the crate and its unfolded template, blind vs after — in the blind one the island labels pile into smudges: 336 pieces stacked on the same texels. Same box outside; the template underneath is a heap vs a layout.
the checker test is the honest one: blind and after look almost the same (the flips live on the trims — 9.9% of the surface — and the stacking shares texels invisibly). A checker catches what it can see; the 148 flips, 54x spread and 336-deep stacking were measured, not spotted. That is the whole point of this tool.
Full numbers in the example's README.
Reading the results honestly
Several findings are only defects in context, and the tool says so rather than pretending:
- Out-of-bounds UVs are correct for tiling and UDIMs.
- Overlap is correct for mirrored geometry sharing texture.
- Repetition is correct for a brick or a weave.
- The green convention is a guess from the map's own statistics — no format records it. Confirm against what the engine expects.
Scope
Reads OBJ (with UVs) and standard image formats.
Does not do: unwrapping, packing, baking, painting, or fixing anything — it audits; you fix it in the tool that made it. It does not judge whether a texture is beautiful, only whether it is correct.
Siblings, same philosophy: solidsight (3D geometry), animationsight (motion clips).
Leaving: texturesight uninstall removes this tool's skill and package.
pip install aisight && aisight uninstall removes the whole family —
every skill, every package and the plugin marketplace. See
aisight.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file texturesight-0.5.2.tar.gz.
File metadata
- Download URL: texturesight-0.5.2.tar.gz
- Upload date:
- Size: 42.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d2ce41f54bd000d796f5eae901d0b8c4f499586e3b49f9751e349cc9b30dde9
|
|
| MD5 |
e192f8d73161d04ed4ab1a6f5557a788
|
|
| BLAKE2b-256 |
b9170bd6f86c14a95da43956058c683f59db57272ee9ee1cc1770db3ba015a69
|
Provenance
The following attestation bundles were made for texturesight-0.5.2.tar.gz:
Publisher:
release.yml on VortexJer/AISight
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
texturesight-0.5.2.tar.gz -
Subject digest:
8d2ce41f54bd000d796f5eae901d0b8c4f499586e3b49f9751e349cc9b30dde9 - Sigstore transparency entry: 2225122534
- Sigstore integration time:
-
Permalink:
VortexJer/AISight@4843ac23d42b95b2ebbbd41cf7b7b30cf8769fa2 -
Branch / Tag:
refs/tags/texturesight-v0.5.2 - Owner: https://github.com/VortexJer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4843ac23d42b95b2ebbbd41cf7b7b30cf8769fa2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file texturesight-0.5.2-py3-none-any.whl.
File metadata
- Download URL: texturesight-0.5.2-py3-none-any.whl
- Upload date:
- Size: 39.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
284e2978da8d60158b6ff9f741b053dc03e18ad83209e14ab9925e798cd8815d
|
|
| MD5 |
e9b00eead7668539f019a35b68d5a98c
|
|
| BLAKE2b-256 |
e7acb7db8890011ec977d58447c47f01266275bbfa3bcebdbd4410bd810b604a
|
Provenance
The following attestation bundles were made for texturesight-0.5.2-py3-none-any.whl:
Publisher:
release.yml on VortexJer/AISight
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
texturesight-0.5.2-py3-none-any.whl -
Subject digest:
284e2978da8d60158b6ff9f741b053dc03e18ad83209e14ab9925e798cd8815d - Sigstore transparency entry: 2225122927
- Sigstore integration time:
-
Permalink:
VortexJer/AISight@4843ac23d42b95b2ebbbd41cf7b7b30cf8769fa2 -
Branch / Tag:
refs/tags/texturesight-v0.5.2 - Owner: https://github.com/VortexJer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4843ac23d42b95b2ebbbd41cf7b7b30cf8769fa2 -
Trigger Event:
push
-
Statement type: