Collapse the repeated best-effort try/except into _opt/_put helpers, flatten
the visual/metadata handling, and compress the docstring. 117 -> 86 lines
(85 -> 57 code lines). Verified behaviorally identical to the prior version
by round-tripping real trimesh meshes (plain / textured uv+material+metadata /
no-faces) through both old and new serialize+deserialize with identity recurse.
The load-bearing comments are kept verbatim in intent: the id()-reuse GC
hazard (pass mesh arrays directly, never np.asarray) and the decompose-visuals-
by-kind rationale (avoid PIL + the mesh back-reference re-serializing geometry).
Material without uv is now nested under uv -- deserialize ignores it anyway, so
the round-trip is unchanged and the wire gets slightly smaller.