Improve render efficiency: merged geometry, leaner aromatic dashes, fixed hollow caps - #17
Merged
NCCU-Schultz-Lab merged 7 commits intoAug 26, 2026
Conversation
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
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
Mesh3dtraces.draw_atoms/draw_bondspreviously 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 lightweightScatter3doverlay.alphahullatom 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._cylinder_meshnow 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.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 withoutdraw_atoms.format_lighting'supdate_tracescall totype="mesh3d"so it no longer errors on the newScatter3dhover-overlay trace.examples/render_preview.py, a render harness for viewing plotlyMol output in headless/cloud sessions via kaleido (auto-detects a local Chromium viaBROWSER_PATH), with a--comparemode 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 --checkclean on all changed filesexamples/render_preview.pyrenders: benzene, naphthalene (fused aromatic rings), cholesterol, and a triple/double-bond molecule, at both default and close-up camera angles🤖 Generated with Claude Code
https://claude.ai/code/session_015pw5723CL7SaUDUhhXfVSf
Generated by Claude Code