Skip to content

Improve render efficiency: merged geometry, leaner aromatic dashes, fixed hollow caps - #17

Merged
NCCU-Schultz-Lab merged 7 commits into
mainfrom
claude/plotlymol-render-efficiency-dxnsfo
Aug 26, 2026
Merged

Improve render efficiency: merged geometry, leaner aromatic dashes, fixed hollow caps#17
NCCU-Schultz-Lab merged 7 commits into
mainfrom
claude/plotlymol-render-efficiency-dxnsfo

Conversation

@NCCU-Schultz-Lab

Copy link
Copy Markdown
Collaborator

Summary

  • Merge atom/bond geometry into per-color Mesh3d traces. draw_atoms/draw_bonds previously emitted one trace per atom and two per bond half (a 74-atom molecule produced 232 separate WebGL draw calls). They now accumulate geometry into a handful of merged traces grouped by element color instead — 20-75x fewer traces with no visual change. Per-atom hover is preserved via a single lightweight Scatter3d overlay.
  • Replace the Fibonacci-sphere + alphahull atom mesh with a precomputed icosphere with explicit faces, removing the per-atom convex-hull triangulation the browser previously had to do at render time, and right-size the default tessellation.
  • Rework aromatic (dashed) bonds: trimmed back to the atom's sphere surface (an exact sphere-line intersection, no geometry wasted on the hidden portion), redesigned as one continuous 3-dash sequence with wide gaps instead of two symmetric halves, and a dedicated lower cylinder resolution for these thin decorative segments. Benzene drops ~35-40% in vertex/triangle count and JSON payload from this alone; non-aromatic molecules are unaffected.
  • Fix hollow-looking cylinder end caps. Cap fan triangles and the wall's end triangles were sharing rim vertices, so smooth shading blended the wall's radial normal into the cap's, tilting it enough to catch light wrong and render as a pale, hollow-looking disc from some angles. _cylinder_mesh now gives each cap its own copy of the rim vertices, so the wall stays smoothly round and the cap renders as a solid, correctly-lit flat end — no shading trade-off needed.
  • Fix create_heatmap_colored_figure's atom matching (vibration heatmap coloring), which previously matched whole traces to atoms by centroid — this breaks once several atoms share one merged trace. Replaced with an exact per-vertex atom index into a compact per-trace lookup table (meta), with the old centroid heuristic kept as a fallback for figures built without draw_atoms.
  • Scope format_lighting's update_traces call to type="mesh3d" so it no longer errors on the new Scatter3d hover-overlay trace.
  • Add examples/render_preview.py, a render harness for viewing plotlyMol output in headless/cloud sessions via kaleido (auto-detects a local Chromium via BROWSER_PATH), with a --compare mode for side-by-side before/after or parameter-sweep PNGs.

Test plan

  • pytest tests/ -q — all 55 tests pass (two updated since they hard-coded the old one-trace-per-atom/two-per-bond counts as "correct" behavior; now assert on distinct-color trace counts instead)
  • ruff check / black --check clean on all changed files
  • Visually verified via examples/render_preview.py renders: benzene, naphthalene (fused aromatic rings), cholesterol, and a triple/double-bond molecule, at both default and close-up camera angles
  • Confirmed non-aromatic molecules (ethanol, cholesterol) are byte-for-byte unaffected by the dash rework
  • Confirmed the hollow-cap fix and smooth cylinder walls hold simultaneously at the exact close-up angle that originally exposed the bug

🤖 Generated with Claude Code

https://claude.ai/code/session_015pw5723CL7SaUDUhhXfVSf


Generated by Claude Code

claude and others added 7 commits August 26, 2026 02:47
Atoms and bonds were each emitted as their own Mesh3d trace (one per
atom, two per bond half), so a 74-atom molecule produced 232 separate
WebGL draw calls. draw_atoms/draw_bonds now accumulate geometry into a
handful of merged traces grouped by element color instead, cutting
trace counts by 20-75x with no visual change. Atom spheres also switch
from an unstructured point cloud + alphahull (which forces a per-atom
convex-hull triangulation in the browser) to a precomputed icosphere
with explicit faces.

Per-atom hover is preserved via a single lightweight Scatter3d overlay,
and the vibration heatmap coloring (which used to match whole traces to
atoms by centroid) now uses a compact per-vertex atom index into a
per-trace lookup table, so it stays exact even when many atoms share a
trace.

Aromatic bond dashes are reduced from 5 to 4 segments per half-bond and
moved further from the solid line (they previously hugged it).

Also adds examples/render_preview.py, a render harness for viewing
plotlyMol output in headless/cloud sessions via kaleido, with a
--compare mode for side-by-side before/after or parameter-sweep PNGs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pw5723CL7SaUDUhhXfVSf
Dashed (aromatic) bond segments previously ran from atom center to atom
center, so a large fraction of each end dash rendered fully hidden
inside the atom sphere. Since the dash line sits at a fixed
perpendicular offset from the true bond axis, the point where it enters
the sphere is an exact sphere-line intersection
(sqrt(atom_radius^2 - offset^2)), so each dash is now trimmed back to
where it actually becomes visible.

Combined with fewer, more separated dashes per half-bond (3 instead of
4, wider gaps) and a dedicated lower cylinder resolution for these thin
decorative segments (they don't need the full bond resolution), this
cuts benzene's vertex/triangle count by ~35% and its figure JSON by
~34%, with no change to non-aromatic molecules.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pw5723CL7SaUDUhhXfVSf
The dashed line was drawn as two symmetric per-half sequences (3 dashes
each), so a single aromatic bond rendered 6 total dash segments packed
fairly close together. Replaced this with one continuous dash-gap
sequence spanning the whole trimmed line, colored by whichever atom
each dash is nearer to (split at the bond midpoint) -- this makes an
odd total dash count possible and gives direct control over the total
rather than only the per-half count.

Dropped to 3 dashes total (was 6) with a much lower duty cycle (0.6 ->
0.4) for noticeably wider gaps between them. Benzene drops another ~9%
in vertex/triangle count and JSON size on top of the previous trimming
work; non-aromatic molecules are unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pw5723CL7SaUDUhhXfVSf
Bond/dash cylinder end caps were rendering as pale, hollow-looking
discs from some angles instead of solid ends. Cause: Plotly's default
smooth shading averages vertex normals across the rim vertices shared
between a flat cap and the curved wall it closes off, which can point
the blended normal enough off-axis to catch stray light incorrectly.
Fixed by setting flatshading=True on cylinder/cap traces (draw_bonds,
make_bond_mesh_trace, _make_oval_cap) so each facet uses its own true
normal instead.

flatshading=True does the wrong thing on atom spheres, though: it turns
them into visibly faceted low-poly gemstones instead of a smooth round
surface, since a sphere approximation is *meant* to have its facet
normals blended for the illusion of curvature. Left draw_atoms and
make_atom_mesh_trace on the default smooth shading.

Also increased AROMATIC_DASH_DUTY_CYCLE from 0.4 to 0.48 (~20% longer
dashes, eating into the gap) per request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pw5723CL7SaUDUhhXfVSf
flatshading=True fixed the hollow-looking end caps but at a real cost:
it also flattened the cylinder side walls into a hard-edged prism look,
since flat shading disables the per-vertex normal averaging that was
making the polygonal tube approximation read as smoothly round.

The actual bug was vertex sharing, not the shading mode: cap fan
triangles and the wall's end triangles were both indexing the same rim
vertices, so smooth shading blended the wall's radial normal into the
cap's, tilting it enough to catch light wrong. _cylinder_mesh now gives
each cap its own copy of the rim vertices instead of reusing the
wall's, so the wall's per-vertex normals stay purely radial (smooth,
round tube) and the cap's stay purely axial (solid, correctly lit flat
end) -- both fixed at once, with ordinary smooth shading restored
everywhere. All flatshading=True additions from the previous commit are
reverted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pw5723CL7SaUDUhhXfVSf
@NCCU-Schultz-Lab
NCCU-Schultz-Lab merged commit 7f861a2 into main Aug 26, 2026
16 checks passed
@NCCU-Schultz-Lab
NCCU-Schultz-Lab deleted the claude/plotlymol-render-efficiency-dxnsfo branch August 26, 2026 21:54
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