Skip to main content

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:

  1. 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)
  2. 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)
  3. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

texturesight-0.5.2.tar.gz (42.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

texturesight-0.5.2-py3-none-any.whl (39.0 kB view details)

Uploaded Python 3

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

Hashes for texturesight-0.5.2.tar.gz
Algorithm Hash digest
SHA256 8d2ce41f54bd000d796f5eae901d0b8c4f499586e3b49f9751e349cc9b30dde9
MD5 e192f8d73161d04ed4ab1a6f5557a788
BLAKE2b-256 b9170bd6f86c14a95da43956058c683f59db57272ee9ee1cc1770db3ba015a69

See more details on using hashes here.

Provenance

The following attestation bundles were made for texturesight-0.5.2.tar.gz:

Publisher: release.yml on VortexJer/AISight

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for texturesight-0.5.2-py3-none-any.whl
Algorithm Hash digest
SHA256 284e2978da8d60158b6ff9f741b053dc03e18ad83209e14ab9925e798cd8815d
MD5 e9b00eead7668539f019a35b68d5a98c
BLAKE2b-256 e7acb7db8890011ec977d58447c47f01266275bbfa3bcebdbd4410bd810b604a

See more details on using hashes here.

Provenance

The following attestation bundles were made for texturesight-0.5.2-py3-none-any.whl:

Publisher: release.yml on VortexJer/AISight

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page