Skip to content

feat(ui): solid mode-displacement arrows + CIF/mCIF export from the 3D card#6

Merged
drthyang merged 1 commit into
mainfrom
feat/viewer-arrows-and-cif-export
Jul 21, 2026
Merged

feat(ui): solid mode-displacement arrows + CIF/mCIF export from the 3D card#6
drthyang merged 1 commit into
mainfrom
feat/viewer-arrows-and-cif-export

Conversation

@drthyang

Copy link
Copy Markdown
Owner

Two UI requests, plus a data-integrity fix found while wiring the second.

1. Distortion-mode arrows (PDF page, irreps)

Two independent problems with the green arrows:

Too long. The scale was span * 0.34 where span is the bounding-box body diagonal — on GaTa₄Se₈ (10.36 Å cubic) that is 17.9 Å, so the longest arrow ran ~6 Å, well past the neighbouring site. The pattern read as a vector field over the cell rather than a displacement of each atom. Now min(shortestCellEdge · 0.26, span · 0.12) ≈ 1.8 Å, about one bond. Tying it to the cell edge rather than the diagonal also stops arrows growing when a supercell is displayed.

Stems too thin. The real culprit: THREE.ArrowHelper draws its shaft with LineBasicMaterial, which WebGL rasterizes at a single device pixel however close the camera gets — the stem was a hairline that disappeared against the lit spheres. New arrowBuilder emits a real cylinder shaft + cone head in MeshPhongMaterial, lit by the same lights as the atoms and bonds. Shaft and head sizes are constant across a set, so length alone carries the amplitude and the smallest arrow of a pattern stays legible; unit geometries are built once per overlay and scaled per arrow.

Verified in the running app on the GaTa₄Se₈ demo, mode 1 @ Γ (Ta1): the ⟨111⟩ arrows on the 16 Ta copies now read as solid local displacements.

2. CIF export from the 3D model

StructureView gains exports — a list of host-supplied {label, title, run} descriptors rendered as download buttons in its toolbar. Descriptors rather than a boolean so the viewer never learns which flavour it is writing and the label tracks the content; that is what leaves room for the mCIF work ahead (moments today, mode displacements once the writer carries them).

Host Button Writes
PDF card ↓ CIF the phase on screen
Rietveld card ↓ CIF / ↓ mCIF the phase the picker selects
Magnetic analysis card ↓ CIF↓ mCIF once a group is previewed moment loop at the current amplitudes

The magnetic-analysis card is beyond the two pages originally asked for, but it is the view that actually holds the moments, so it is where mCIF means the most — and its export uses appliedMagnetic, the very model drawn as arrows, so file and picture agree.

Fix: every CIF export wrote the starting structure

Found while tracing the export path. structureToCif reads coordinates and cell straight off the model and consults the parameters only for their esds — but all three workbenches passed the pre-refinement structure. Exported files therefore carried starting coordinates annotated with refined uncertainties and refined R-factors.

All three now pass the model with parameters applied. The powder path additionally routes bindings by phase (as refinedPhases does), so a multi-phase session cannot attach one phase's esds to another phase's identically-labelled site. cif.test.ts pins the contract in both directions.

Verification

  • Full suite: 1126 passed, 0 failed
  • tsc -b --noEmit clean
  • Both features exercised in the running dev server (GaTa₄Se₈ PDF demo for the arrows; Mn₃Ga + MnO for the CIF→mCIF label switch)

Not touched

The red magnetic moment arrows still use ArrowHelper, so they share the hairline-stem issue and sit at span * 0.42. Left alone deliberately — out of scope, and that rendering has a validated look plus test coverage. Routing them through the same builder is a two-line follow-up.

🤖 Generated with Claude Code

…D card

Arrows (requirement 1). Two independent problems with the green
distortion-mode arrows on the PDF page:

  Too long — the scale was span·0.34 with span the bounding-box body
  DIAGONAL, so on GaTa4Se8 (10.36 A cubic) the longest arrow ran ~6 A,
  well past the neighbouring site; the pattern read as a vector field
  over the cell instead of a displacement of each atom. Now
  min(shortestCellEdge·0.26, span·0.12) ~ 1.8 A, about one bond. Tying
  it to the cell edge rather than the diagonal also stops the arrows
  growing when a supercell is displayed.

  Stems too thin — THREE.ArrowHelper draws its shaft with
  LineBasicMaterial, which WebGL rasterizes at a single device pixel
  however close the camera gets, so the stem was a hairline that
  disappeared against the lit spheres. New `arrowBuilder` emits a real
  cylinder shaft + cone head in MeshPhongMaterial, lit by the same
  lights as the atoms and bonds. Shaft/head sizes are constant across a
  set so length alone carries the amplitude and the smallest arrow of a
  pattern stays legible; unit geometries are built once per overlay and
  scaled per arrow. Head length is capped at a fraction of a short arrow
  so a small one keeps a shaft instead of collapsing to a bare cone.

  Verified on the GaTa4Se8 demo, mode 1 @ Gamma (Ta1): the <111> arrows
  on the 16 Ta copies now read as solid local displacements.

Export (requirement 2). StructureView gains `exports` — a list of
host-supplied {label, title, run} descriptors rendered as download
buttons in its toolbar. Descriptors rather than a flag so the viewer
never learns which flavour it is writing: the label tracks the content,
which is what makes room for the mCIF work ahead (moments today, mode
displacements once the writer carries them). Wired into three hosts:
the PDF card (phase on screen), the Rietveld card (phase the picker
selects, mCIF when the primary phase carries moments) and the magnetic
analysis card (mCIF at the current amplitudes as soon as a group is
previewed, so file and arrows agree).

Fix found while wiring: EVERY CIF export wrote the STARTING structure.
structureToCif reads coordinates and cell straight off the model and
consults the parameters only for their esds, but all three workbenches
passed the pre-refinement `structure` — so exported files carried
starting coordinates annotated with refined uncertainties and refined
R-factors. All three now pass the model with parameters applied, and
the powder path routes bindings by phase (as refinedPhases does) so a
multi-phase session cannot attach one phase's esds to another's
identically-labelled site. cif.test pins the contract both ways.

Full suite green (1126 passed); typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@drthyang
drthyang merged commit ac1f79a into main Jul 21, 2026
2 checks passed
@drthyang
drthyang deleted the feat/viewer-arrows-and-cif-export branch July 21, 2026 15:44
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.

1 participant