🦄feat(web,desktop): check a material setup against its export - #850
Draft
polynaut wants to merge 2 commits into
Draft
🦄feat(web,desktop): check a material setup against its export#850polynaut wants to merge 2 commits into
polynaut wants to merge 2 commits into
Conversation
…ts export Every DTH Export already writes a Materials list into the .dth the HDA imports — one entry per Daz surface, with the asset that owns it, the Daz content type and every channel's texture path. Nothing in the studio read it. Measured on a real export (LaraCroft_G81, DTH 2.0.2): Material Name is verbatim the surface a DazToHueMaterial slot claims, 26 of 26 exact matches against that character's own scanned .hiplc. The new Export check tab answers two questions nothing else in the pipeline can. Does this setup still match the scene — slot claims and baker LAYER groups both bind by plain text, so removing a graft leaves both pointing at surfaces that no longer exist, and a baker in that state finishes normally and bakes nothing. And what would a setup built from this export look like — the Daz content type is vendor-authored, so the grouping needs no heuristic. Read-only: no hython, no .hip opened, nothing written. The two inputs are the stored scan and a JSON file the studio's own pipeline wrote. The scan now records the geometry groups each baker layer names; it already computed them and _node_info threw them away. SCAN_ANSWER_VERSION 7 retires entries that would otherwise answer the new question with an empty list. Two scoping rules the diff keeps rather than guessing: a project importing several scenes is refused (the scan records imports per project, not per node, so a node cannot be attributed), and unclaimed surfaces are grouped by content type and never flagged — a "naked" variant is supposed to leave its wardrobe unclaimed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JHYR4PVb5KgaknwGSmL6wH
The tab shipped in the previous commit with no guide page and two rough edges, both found by writing the documentation and looking at the result. - **The read-only tab was offering a Transfer button.** The drawer footer branched only on `tab === 'general'`, so Export check fell through to the TRANSFER footer: a Transfer button over "0 target nodes selected", which is the material tab's (empty) selection, on a tab that cannot transfer anything. It gets no footer now — it has no action of its own. - **`material-plan.ts` was invisible to ripgrep.** The composite Map key joined slot and channel with a literal NUL BYTE, which makes the whole file `binary` to rg: every repo-wide grep silently skipped it, and the file read as absent rather than as a non-match. Same string, written as the escape, plus a comment saying why it must stay one. The guide section covers what the tab answers and what it deliberately will not: the four per-node verdict rows and why "Surfaces with no material" is never flagged (a "naked" variant is supposed to leave its wardrobe unclaimed), the proposed setup and its wardrobe toggle, that a proposal read from textures is partial by construction, and the four ways it says it cannot answer rather than showing a clean result. Plus its screenshot, driven by a new test in guide.screenshots.ts. That shot reuses the behaviour spec's fixture rather than copying it — houdini-export-check.smoke.ts and the screenshot now share EXPORT_CHECK_* in smoke/fixtures.ts, so the picture in the guide cannot drift away from the state that is actually asserted. Gates: build:guide, screenshots 46/46 incl. coverage, drawer smoke 10/10, typecheck, lint within baseline. Not verified: still not run against a real Houdini project — that is the 2026-08-17 test on LaraCroft_G81, and nothing here changes what it will show. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JHYR4PVb5KgaknwGSmL6wH
Contributor
🔍 Site preview deployed
Redeploys on every push to this PR. Production stays on GitHub Pages. |
polynaut
marked this pull request as draft
August 18, 2026 06:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A DazToHue material slot claims Daz surfaces by plain text, and so does every
texture baker layer. Change the scene — swap a graft out, drop an outfit — and
those claims keep naming surfaces that no longer exist: measured on DazToHue
2.5, a baker in that state finishes normally and bakes nothing, and Houdini
reports neither half. Nothing in the pipeline could see it.
Where the answer was
The
.dththe HDA imports already carries aMaterialslist — one entry perDaz surface, with the asset that owns it, the Daz content type and every
channel's texture path. Measured on a real export (LaraCroft_G81, DTH 2.0.2):
Material Nameis verbatim the surface a slot claims — 26 of 26 exact matchesagainst that character's own scanned
.hiplc. So the new Export check tabneeds no hython, opens no
.hipand writes nothing; its two inputs are thestored scan and a JSON file the studio itself wrote.
It reports claims the export does not back, baker layer groups it does not
back, and what a setup built from that export alone would look like — grouped
by the Daz content type, which is vendor-authored, so no heuristic is involved
(the eye stack is the one name match, and is marked as such).
variant node is supposed to leave its wardrobe unclaimed.
scan records
importsper project, not per node.constant cannot be read out of an export. Said in the tab, not implied away.
The scan now records baker layer groups (
bakerGroups) — it already computedthem and threw them away.
SCAN_ANSWER_VERSION→ 7 so existing entries re-earntheir verdict instead of answering the new question with
[].Not verified
The tab has never rendered against real data — the 6 new smoke specs drive the
real SPA end-to-end but against the fake native layer. The Python change is
unexercised for the same reason the rest of
material_utilsis (no harness; itonly runs inside hython).
bakerGroupsstays empty until the first rescan.The three most likely to be wrong, in the order I would bet on them: the
baker-groups row (the only unexercised path — if it reads
noneon a projectwith 6 bakers after the rescan, that is the bug); the proposal's slot/baker
naming and where the eye stack lands (judgement calls made from two projects);
and the unclaimed list reading as alarming on a "naked" variant, where it is
correct and the fix would be presentational.
Generation — the write half — is #851, which carries the design and the one
measurement it is blocked on.
Test plan
The first open re-runs the background scan (version bump), so give it a
moment before the baker-groups row means anything.
LaraCroft_G81it should name the fiveGP*claims left behind by theGolden Palace removal and the
metal1/metal_1mismatch — and saywhether the skin bakers' layers still carry the GP groups too.
proposal against what you would have built by hand.
once, not show an empty setup.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JHYR4PVb5KgaknwGSmL6wH