Skip to content

fix: trplot/trplot2 silently swallowed unknown keyword arguments - #230

Open
petercorke wants to merge 1 commit into
rai-opensource:masterfrom
petercorke:fix/trplot-silent-unknown-kwargs
Open

petercorke wants to merge 1 commit into
rai-opensource:masterfrom
petercorke:fix/trplot-silent-unknown-kwargs

Conversation

@petercorke

Copy link
Copy Markdown
Collaborator

Summary

trplot() and trplot2() both ended with a bare **kwargs that was never read anywhere in their bodies, so a typo'd keyword (e.g. framelabel= instead of frame=) was silently accepted and ignored instead of raising — it drew the frame with no error and no label at all. This surfaced downstream in petercorke/RVC3-python#50, where a notebook had exactly this typo for years with no test ever catching it.

  • Removed the dead **kwargs sink from both functions so an unrecognized keyword now raises TypeError, same as any normal Python call.
  • trplot's anaglyph branch only forwarded a small fixed subset of parameters to each eye's recursive call, silently dropping textcolor, labels, originsize, origincolor, axislabel, axissubscript, width and projection. It also never returned ax and never actually called plt.show(block=...), so anaglyph mode ignored block entirely. Fixed to forward the full applicable parameter set, return ax, and call plt.show() once after both eyes are drawn.
  • trplot's "T is an iterable of transforms" branch forwarded every parameter except axissubscript, and also never returned ax. Fixed.
  • tranimate/tranimate2 were splatting the same unfiltered kwargs dict into Animate()/Animate2()'s constructor, the drawing call, and run()'s animation-control call, relying on each one's own dead **kwargs sink to drop what it didn't need. Now that trplot/trplot2 validate strictly, run()-only parameters (movie, repeat, interval, nframes, wait) are split off before the drawing call.
  • Fixed a long-standing typo in test_pose2d.py::test_graphics (T0=T2, which was never a real parameter — animate()'s documented one is start=) that this same silent-swallow bug had been quietly masking since the test was written.

New test coverage

Added tests/base/test_animate.py. While chasing the tranimate/tranimate2 regression above, I found that constructing a FuncAnimation under a non-interactive backend (the Agg backend this repo's whole test suite runs under) doesn't actually run its per-frame update() callback — confirmed directly by checking Animate2's internal frame counter, which never advances past its initial value unless something drives the animation. So the existing animate()/tranimate() tests only ever proved construction didn't raise, not that interpolation/drawing works.

The new tests force every frame through the real callback via FuncAnimation.save() with PillowWriter (pure Python, no external ffmpeg dependency) and assert trinterp/trinterp2 actually ran across a real s: 0→1 sweep. This is what caught the tranimate/tranimate2 kwargs-splitting bug above in the first place.

Test plan

  • Full test suite passes: 353 passed, 3 skipped (skips are pre-existing, display-dependent)
  • Fail/fix/pass verified for every behavioral change (confirmed each new/modified test fails against the pre-fix code for the intended reason, and passes after)
  • SE3/SE2 .plot()/.animate() wrapper methods (which forward *args, **kwargs straight through) verified to propagate the stricter validation correctly

🤖 Generated with Claude Code

Both trplot() and trplot2() ended with a bare **kwargs that was never
read anywhere in their bodies, so a typo'd keyword (e.g. framelabel=
instead of frame=) was silently accepted and ignored instead of raising
-- it drew the frame with no error and no label at all. Removing the
dead sink lets Python's own unexpected-keyword-argument check do its
job.

While rewriting the two places that used to lean on this catch-all:

- trplot's anaglyph branch only forwarded a small fixed subset of
  parameters to each eye's recursive call, silently dropping textcolor,
  labels, originsize, origincolor, axislabel, axissubscript, width and
  projection; it also never returned ax and never actually called
  plt.show(block=...), so anaglyph mode ignored block entirely.
- trplot's "T is an iterable of transforms" branch forwarded every
  parameter except axissubscript, and also never returned ax.
- tranimate/tranimate2 were splatting the same unfiltered kwargs dict
  into Animate()/Animate2()'s constructor, the drawing call, and
  run()'s animation-control call, relying on each one's own dead
  **kwargs sink to drop what it didn't need. Once trplot/trplot2
  validate strictly, run()-only parameters (movie, repeat, interval,
  nframes, wait) needed to be split off before the drawing call.

Also fixes a long-standing typo in test_pose2d.py::test_graphics
(T0=T2, which was never a real parameter -- animate()'s documented one
is start=) that this same silent-swallow bug had been quietly masking.

Adds tests/base/test_animate.py: constructing a FuncAnimation under a
non-interactive backend doesn't actually run its per-frame update()
callback (confirmed directly -- Animate2's frame counter never
advances past its initial value), so existing animate() tests only
proved construction didn't raise. These tests force every frame
through the real callback via FuncAnimation.save() (PillowWriter, no
external ffmpeg dependency) and assert trinterp/trinterp2 actually ran
across a real s: 0->1 sweep.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants