Slice 14 — Heatmap HM-1 (per-center viridis coloring) - #13
Merged
Conversation
Phase 2B Slice 14 (HM-1) groundwork. Mirror of Slice 13's centers() getter: const reference to FitResult::weights, noexcept, returns a 0x0 matrix when !is_fitted(). Exposes the (N x M) weight matrix to the Viewport 2.0 heatmap path so it can compute per-center L2 norms without re-parsing the saved JSON or re-running the solver. Phase 1 test count: 138 -> 139. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… + F-group tests) Phase 2B Slice 14 (HM-1) — pure-C++14, Maya-free color-mapping module. Two surfaces: * map_scalar_to_color(Scalar v01) -> 4-tuple float RGBA. 11-stop piecewise-linear viridis LUT with anchors at v=0/0.5/1 matching matplotlib reference values exactly. Spec originally called for a quartic polynomial fit; pivoted to LUT after polynomial form failed F1/F2/F3 by ~0.4 absolute (see DEVLOG "F-stop #5"). * compute_center_colors(weights, out, n) — row L2 norm per center, per-frame min/max normalize, map through LUT. NaN/Inf weight in any row falls back to white for that single center (others use finite-only min/max). Degenerate "all weights equal" maps to the viridis start. 8 F-group tests cover both surfaces: F1/F2/F3 — viridis anchors at v=0/0.5/1 F4/F5 — clamping below 0 / above 1 F6 — alpha invariance across in/out-of-range inputs F7 — ascending norms map min->start max->end F8 — degenerate all-equal -> all colors equal & = viridis(0) Random seed kSeedS14 = 0xF5BFADu reserved for future randomised additions. Adapter test count: 25 -> 33 (E + F merged into the same target). Total adapter+Phase 1 ctest: 172/172 green. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s / PredictionField)
Phase 2B Slice 14 (HM-1) — adds the third (and currently last)
attribute on mRBFShape:
heatmapMode (enum, default 0=Off, channel-box visible)
0 = Off (Slice 13 default behaviour)
1 = Center Weights (Slice 14 HM-1)
2 = Prediction Field (reserved for Slice 15; falls back to Off)
Field indices match the rbfmax::maya::HeatmapMode enum class so the
DrawOverride can cast the short to the enum type directly. Slice 13
mRBFShape attributes (sourceNode, drawEnabled, sphereRadius) and
its Path B classification + initialize() flow are untouched; the new
block sits between addAttribute(aSphereRadius) and the trailing
return MS::kSuccess as the spec requires.
Strict invariant from Slice 13 Path A retrospective preserved:
mRBFShape MUST NOT carry any MFnTypedAttribute(MFnData::kString) —
the new attribute uses MFnEnumAttribute, which is unaffected by the
empirical kLocatorNode-typed-string failure mode (DEVLOG R-44).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…aching Phase 2B Slice 14 (HM-1) — wires the heatmap end to end. Changes: - mRBFNode: additive const weights() returning the interpolator's N x M weight matrix (or a static empty 0x0 when !is_loaded()). Phase 2A compute() / try_load() / attribute set zero-touch; this is purely a read accessor for the DrawOverride's color path. - mRBFDrawOverride RbfDrawData: extended with center_colors vector, current heatmap_mode, and a 4-tuple cache key (weights buffer pointer + rows + cols + last cached mode). - prepareForDraw: reads heatmapMode plug; for kCenterWeights computes (or reuses cached) per-center colors via compute_center_colors; kPredictionField gracefully degrades to kOff. Cache key compares the underlying weights buffer pointer against the prior frame; pointer change (or shape change, or mode switch) triggers recompute. Slice 13 supportedDrawAPIs / boundingBox / isBounded / classification untouched. - addUIDrawables: switches to per-sphere setColor + sphere when center_colors is populated; otherwise keeps the Slice 13 single- color batch path. Per Maya 2022/2025 docs, calling setColor inside the begin/endDrawable block before each sphere() is the documented idiom for per-primitive coloring. - maya_node/CMakeLists.txt: src/color_mapping.cpp added to the plugin sources alongside the Slice 13 sources. - smoke_viewport.py: round-trips heatmapMode through Off (0) and CenterWeights (1) after the Slice 13 drawEnabled / sphereRadius asserts. Validation (8 smokes total, both Maya versions all PASS): Maya 2022 hellonode/predict/train/viewport: PASS Maya 2025 hellonode/predict/train/viewport: PASS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DEVLOG.md: ~140-line Slice 14 entry covering scope, deliverables, 12 design decisions, R-09 self-check (LUT precision, cache key soundness), validation outcomes (172 / 0 / 8 / 139), the F-stop #5 record (polynomial -> LUT pivot rationale + recovery), and tech- debt R-45/T-19/T-20. maya_node/README.md: new "Heatmap mode (Slice 14 - HM-1)" subsection inside the Slice 13 Viewport 2.0 section: enum table, workflow snippet, implementation notes (LUT precision contract, NaN fallback, per-frame normalization, cache key). Visual review screenshots (4: Maya 2022 kOff + kCenterWeights; Maya 2025 kOff + kCenterWeights) pending user-side GUI session and will be attached to PR description on capture. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…+ 2025)
Captured during user-side GUI verification:
- maya2022_Off.png — Maya 2022.5.1, heatmapMode=0 (4 white spheres)
- maya2022_CenterWts.png — Maya 2022.5.1, heatmapMode=1 (3 deep-purple + 1 yellow)
- maya2025_Off.png — Maya 2025.3, heatmapMode=0 (4 white spheres)
- maya2025_CenterWts.png — Maya 2025.3, heatmapMode=1 (3 deep-purple + 1 yellow)
Color distribution observation: tiny_rbf fixture's 4 weight-norm
values cluster as {3 near min, 1 outlier}, which the per-frame
min/max normalization maps to viridis(0)=purple (3 centers) and
viridis(1)=yellow (1 center). No teal-band centers because the
fixture lacks intermediate L2 norms. Production rigs (50+ centers)
will exhibit the full purple->teal->yellow ramp.
Cross-version invariant verified visually: both Maya 2022 and Maya
2025 produce identical color assignments at identical positions.
This complements the deterministic adapter F-group tests by
confirming no Maya-side color management drift between versions.
8 tasks
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.
Summary
Phase 2B second slice. Adds the first viewport heatmap mode — HM-1: per-center weight visualization — so artists can see at a glance which trained centers carry the largest weights for the current rig.
Deliverables
RBFInterpolator::weights()const getter + 1 D-group test (138 → 139)color_mappingmodule (Maya-free) —HeatmapModeenum,map_scalar_to_color,compute_center_colors— + 8 F-group testsweights()accessor (Phase 2A logic untouched)aHeatmapModeenum attribute (3 fields; Slice 13 attrs untouched)F-stop #5 — polynomial → LUT pivot
The original spec called for a 4th-order polynomial fit of viridis. The candidate coefficients failed F1/F2/F3 by 0.038–0.486 absolute per channel against the spec's own reference values (the polynomial fits a different curve, not viridis). Per the spec's prescribed recovery path, pivoted to an 11-stop piecewise-linear LUT with anchors at v=0 / v=0.5 / v=1 matching F1/F2/F3 reference values exactly. Single rebuild → 172/172.
Validation
build-adapter, ctest)C:/SDK/Maya2022/devkitBase)C:/SDK/Maya2025/devkitBase)build, ctest)Constraints respected
compute()/try_load()/ train-cmd touches (only additiveweights()accessor)supportedDrawAPIs/boundingBox/isBounded/ classification changesreturn MS::kSuccessline touches (new attribute appended at end ofinitialize())MRenderItem/ custom shaders / playblast infra introducedMFnTypedAttribute(kString), preserving the Slice 13 R-44 invariantTech-debt added
HeatmapMode::kPredictionFieldregistered as enum field 2 but degrades to kOff in Slice 14; Slice 15 (HM-2) activatesTest plan
Pre-flight Maya API grep (FitResult.weights, addField signatures across 2022/2025)
Adapter + Phase 1 ctest 172/172 (after F-stop Slice 08 — JSON I/O with schema v1 (v0.8.0) #5 LUT pivot)
Maya 2022 configure + build clean
Maya 2025 configure + build clean
Maya 2022 × 4 smokes exit 0 (incl. heatmapMode round-trip)
Maya 2025 × 4 smokes exit 0 (incl. heatmapMode round-trip)
Phase 1 pure regression ctest 139/139
Visual review — Maya 2022 kOff screenshot — 4 white spheres
Visual review — Maya 2022 kCenterWeights screenshot — 3 deep-purple + 1 yellow (per-frame min/max maps tiny_rbf weight cluster)
Visual review — Maya 2025 kOff screenshot — 4 white spheres
Visual review — Maya 2025 kCenterWeights screenshot — 3 deep-purple + 1 yellow (cross-version match with Maya 2022)