react-cheminfo 0.63.1 → 0.64.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/lib/molecule3d/core/highlight.d.ts +53 -0
  2. package/lib/molecule3d/core/highlight.d.ts.map +1 -0
  3. package/lib/molecule3d/core/highlight.js +96 -0
  4. package/lib/molecule3d/core/highlight.js.map +1 -0
  5. package/lib/molecule3d/core/index.d.ts +3 -1
  6. package/lib/molecule3d/core/index.d.ts.map +1 -1
  7. package/lib/molecule3d/core/index.js +2 -1
  8. package/lib/molecule3d/core/index.js.map +1 -1
  9. package/lib/molecule3d/core/measurement.d.ts +17 -0
  10. package/lib/molecule3d/core/measurement.d.ts.map +1 -1
  11. package/lib/molecule3d/core/measurement.js +43 -0
  12. package/lib/molecule3d/core/measurement.js.map +1 -1
  13. package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
  14. package/lib/molecule3d/ui/MoleculeCanvas3D.js +7 -1
  15. package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
  16. package/lib/molecule3d/ui/drawScene.d.ts +4 -2
  17. package/lib/molecule3d/ui/drawScene.d.ts.map +1 -1
  18. package/lib/molecule3d/ui/drawScene.js +1 -1
  19. package/lib/molecule3d/ui/drawScene.js.map +1 -1
  20. package/lib/molecule3d/ui/highlightColor.d.ts +18 -0
  21. package/lib/molecule3d/ui/highlightColor.d.ts.map +1 -0
  22. package/lib/molecule3d/ui/highlightColor.js +31 -0
  23. package/lib/molecule3d/ui/highlightColor.js.map +1 -0
  24. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +16 -1
  25. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
  26. package/lib/molecule3d/ui/renderMolecule.d.ts +2 -0
  27. package/lib/molecule3d/ui/renderMolecule.d.ts.map +1 -1
  28. package/lib/molecule3d/ui/renderMolecule.js +1 -1
  29. package/lib/molecule3d/ui/renderMolecule.js.map +1 -1
  30. package/lib/molecule3d/ui/sceneDrawer.d.ts +47 -0
  31. package/lib/molecule3d/ui/sceneDrawer.d.ts.map +1 -0
  32. package/lib/molecule3d/ui/sceneDrawer.js +103 -0
  33. package/lib/molecule3d/ui/sceneDrawer.js.map +1 -0
  34. package/lib/molecule3d/ui/useMoleculeScene.d.ts +2 -1
  35. package/lib/molecule3d/ui/useMoleculeScene.d.ts.map +1 -1
  36. package/lib/molecule3d/ui/useMoleculeScene.js +13 -22
  37. package/lib/molecule3d/ui/useMoleculeScene.js.map +1 -1
  38. package/lib/molecule3d/ui/useSharedCamera.d.ts +1 -3
  39. package/lib/molecule3d/ui/useSharedCamera.d.ts.map +1 -1
  40. package/lib/molecule3d/ui/useSharedCamera.js.map +1 -1
  41. package/lib/molecule3d/ui/viewer.d.ts +9 -0
  42. package/lib/molecule3d/ui/viewer.d.ts.map +1 -1
  43. package/lib/molecule3d/ui/viewer.js +18 -2
  44. package/lib/molecule3d/ui/viewer.js.map +1 -1
  45. package/lib/molstar/core/highlights.d.ts +57 -0
  46. package/lib/molstar/core/highlights.d.ts.map +1 -0
  47. package/lib/molstar/core/highlights.js +178 -0
  48. package/lib/molstar/core/highlights.js.map +1 -0
  49. package/lib/molstar/core/index.d.ts +2 -1
  50. package/lib/molstar/core/index.d.ts.map +1 -1
  51. package/lib/molstar/core/index.js +2 -1
  52. package/lib/molstar/core/index.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/molecule3d/core/highlight.ts +127 -0
  55. package/src/molecule3d/core/index.ts +7 -0
  56. package/src/molecule3d/core/measurement.ts +47 -0
  57. package/src/molecule3d/ui/MoleculeCanvas3D.tsx +13 -0
  58. package/src/molecule3d/ui/drawScene.ts +13 -2
  59. package/src/molecule3d/ui/highlightColor.ts +34 -0
  60. package/src/molecule3d/ui/moleculeViewer3DProps.ts +16 -1
  61. package/src/molecule3d/ui/renderMolecule.ts +1 -1
  62. package/src/molecule3d/ui/sceneDrawer.ts +132 -0
  63. package/src/molecule3d/ui/useMoleculeScene.ts +16 -26
  64. package/src/molecule3d/ui/useSharedCamera.ts +1 -3
  65. package/src/molecule3d/ui/viewer.ts +23 -0
  66. package/src/molstar/core/highlights.ts +236 -0
  67. package/src/molstar/core/index.ts +2 -1
@@ -9,10 +9,12 @@
9
9
  */
10
10
 
11
11
  import type { PluginContext } from 'molstar/lib/mol-plugin/context.js';
12
+ import { Color } from 'molstar/lib/mol-util/color/color.js';
12
13
 
13
14
  import {
14
15
  DEFAULT_CAMERA_DURATION,
15
16
  DEFAULT_SPIN_SPEED,
17
+ HighlightPainter,
16
18
  MeasurementPicker,
17
19
  MolstarPlugin,
18
20
  captureScene,
@@ -22,11 +24,13 @@ import {
22
24
  } from '../../molstar/core/index.ts';
23
25
  import type { Molecule3DCamera } from '../core/camera.ts';
24
26
  import type { ImageSize } from '../core/exportImage.ts';
27
+ import type { Molecule3DHighlight } from '../core/highlight.ts';
25
28
  import type { Measurement, MeasurementKind } from '../core/measurement.ts';
26
29
  import type { Molecule3DFile } from '../core/settings.ts';
27
30
 
28
31
  import { applyCamera, resetCamera, watchCamera } from './camera.ts';
29
32
  import {
33
+ MODEL_REPRESENTATION_REF,
30
34
  clearMolecule,
31
35
  moleculeStructure,
32
36
  renderMolecule,
@@ -45,6 +49,7 @@ import type {
45
49
  export class Molecule3DViewer {
46
50
  readonly #plugin: MolstarPlugin;
47
51
  #picker: MeasurementPicker | undefined;
52
+ #painter: HighlightPainter | undefined;
48
53
 
49
54
  /** Resolves once the canvas exists; every method awaits it internally. */
50
55
  readonly ready: Promise<void>;
@@ -66,6 +71,7 @@ export class Molecule3DViewer {
66
71
  onMeasure,
67
72
  moleculeStructure,
68
73
  );
74
+ this.#painter = new HighlightPainter(plugin, MODEL_REPRESENTATION_REF);
69
75
  })
70
76
  .catch(ignore);
71
77
  }
@@ -123,6 +129,22 @@ export class Molecule3DViewer {
123
129
  );
124
130
  }
125
131
 
132
+ /**
133
+ * Emphasise exactly these paths of atoms on the model — this one and every
134
+ * one drawn after it — without redrawing the scene or moving the camera.
135
+ * @param highlights - Replaces what is emphasised; empty restores the model.
136
+ * @param color - What they are painted in, as `#rrggbb`.
137
+ * @returns Nothing; resolves once the model is repainted.
138
+ */
139
+ setHighlights(
140
+ highlights: readonly Molecule3DHighlight[],
141
+ color: string,
142
+ ): Promise<void> {
143
+ return this.#run(() => {
144
+ this.#painter?.set(highlights, Color.fromHexStyle(color));
145
+ });
146
+ }
147
+
126
148
  /**
127
149
  * Make clicks on atoms build a measurement, or give them back to the camera.
128
150
  * @param kind - The measurement to build, or `null`.
@@ -202,6 +224,7 @@ export class Molecule3DViewer {
202
224
  /** Tear the viewer down and release its WebGL context. Idempotent. */
203
225
  dispose(): void {
204
226
  this.#picker?.dispose();
227
+ this.#painter?.dispose();
205
228
  this.#plugin.dispose();
206
229
  }
207
230
 
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Atoms and bonds of the displayed molecule emphasised in one colour, for as
3
+ * long as a site keeps pointing at them.
4
+ *
5
+ * Neither of molstar's own markings fits: its hover highlight is erased by the
6
+ * next move of the pointer, and its selection belongs to the measuring tools.
7
+ * This is an overpaint of the model's own representation instead, so it
8
+ * follows the model in every style, survives the hover, and leaves the camera
9
+ * and every other drawing alone.
10
+ *
11
+ * molstar paints an atom and every half-bond leaving it from one element loci,
12
+ * which would tint the C–C bonds of a row that names only C–H ones. So the
13
+ * overpaint has three layers: the atoms, then those half-bonds cleared again,
14
+ * then exactly the bonds the paths name, as bond loci.
15
+ */
16
+
17
+ import { OrderedSet, SortedArray } from 'molstar/lib/mol-data/int.js';
18
+ import type { UnitIndex } from 'molstar/lib/mol-model/structure/structure/element/util.js';
19
+ import type { Structure } from 'molstar/lib/mol-model/structure.js';
20
+ import {
21
+ Bond,
22
+ StructureElement,
23
+ Unit,
24
+ } from 'molstar/lib/mol-model/structure.js';
25
+ import type { PluginContext } from 'molstar/lib/mol-plugin/context.js';
26
+ import { PluginStateObject } from 'molstar/lib/mol-plugin-state/objects.js';
27
+ import type { StateObject } from 'molstar/lib/mol-state/index.js';
28
+ import { Overpaint } from 'molstar/lib/mol-theme/overpaint.js';
29
+ import type { Color } from 'molstar/lib/mol-util/color/color.js';
30
+
31
+ import type {
32
+ HighlightedBond,
33
+ Molecule3DHighlight,
34
+ } from '../../molecule3d/core/highlight.ts';
35
+ import {
36
+ highlightedParts,
37
+ sameHighlights,
38
+ } from '../../molecule3d/core/highlight.ts';
39
+ import type { AtomReference } from '../../molecule3d/core/measurement.ts';
40
+
41
+ // Lowercased: they are factories, not constructors.
42
+ const elementLoci = StructureElement.Loci;
43
+ const bondLoci = Bond.Loci;
44
+ const bondLocation = Bond.Location;
45
+
46
+ /** The loci an overpaint layer of {@link highlightOverpaint} holds. */
47
+ type HighlightLoci = StructureElement.Loci | Bond.Loci;
48
+
49
+ /**
50
+ * The overpaint that emphasises `highlights` on a representation of
51
+ * `structure`: their atoms, and exactly the bonds between consecutive atoms of
52
+ * each path.
53
+ * @param structure - The structure the representation is drawn from.
54
+ * @param highlights - The paths; an atom the structure lacks is skipped.
55
+ * @param color - What they are painted in.
56
+ * @returns The overpaint, empty when no atom is left to paint.
57
+ */
58
+ export function highlightOverpaint(
59
+ structure: Structure,
60
+ highlights: readonly Molecule3DHighlight[],
61
+ color: Color,
62
+ ): Overpaint<HighlightLoci> {
63
+ const { atoms, bonds } = highlightedParts(
64
+ highlights,
65
+ (atom) => unitOf(structure, atom) !== undefined,
66
+ );
67
+ if (atoms.length === 0) return { kind: 'bond-loci', layers: [] };
68
+ const layers: Array<Overpaint.Layer<HighlightLoci>> = [
69
+ { loci: atomsLoci(structure, atoms), color, clear: false },
70
+ { loci: leavingBondsLoci(structure, atoms), color, clear: true },
71
+ ];
72
+ if (bonds.length > 0) {
73
+ layers.push({ loci: pathBondsLoci(structure, bonds), color, clear: false });
74
+ }
75
+ // Not `element-loci`, which molstar would remap layer by layer as atoms, and
76
+ // a bond loci read as atoms is a crash.
77
+ return { kind: 'bond-loci', layers };
78
+ }
79
+
80
+ /**
81
+ * Keeps one representation of the state tree painted with the highlights a
82
+ * site asks for: the one there now, and every one built under the same ref
83
+ * afterwards, so a restyled model or a new conformer comes up already painted.
84
+ */
85
+ export class HighlightPainter {
86
+ readonly #plugin: PluginContext;
87
+ readonly #ref: string;
88
+ readonly #unsubscribe: () => void;
89
+ #highlights: readonly Molecule3DHighlight[] = [];
90
+ #color: Color | undefined;
91
+
92
+ /**
93
+ * Start following the representation at `representationRef`.
94
+ * @param plugin - The molstar context.
95
+ * @param representationRef - State-tree ref of the representation to paint.
96
+ */
97
+ constructor(plugin: PluginContext, representationRef: string) {
98
+ this.#plugin = plugin;
99
+ this.#ref = representationRef;
100
+ const repaint = (event: { ref: string; obj: StateObject }) => {
101
+ if (event.ref === this.#ref) this.#paint(event.obj);
102
+ };
103
+ const { created, updated } = plugin.state.data.events.object;
104
+ const subscriptions = [
105
+ created.subscribe(repaint),
106
+ updated.subscribe(repaint),
107
+ ];
108
+ this.#unsubscribe = () => {
109
+ for (const subscription of subscriptions) subscription.unsubscribe();
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Paint exactly these highlights, in place of the ones painted.
115
+ * @param highlights - The paths; an empty list restores the plain model.
116
+ * @param color - What they are painted in.
117
+ */
118
+ set(highlights: readonly Molecule3DHighlight[], color: Color): void {
119
+ if (color === this.#color && sameHighlights(highlights, this.#highlights)) {
120
+ return;
121
+ }
122
+ this.#highlights = highlights;
123
+ this.#color = color;
124
+ this.#paint(this.#plugin.state.data.cells.get(this.#ref)?.obj);
125
+ }
126
+
127
+ /** Stop following the representation. */
128
+ dispose(): void {
129
+ this.#unsubscribe();
130
+ }
131
+
132
+ #paint(object: StateObject | undefined): void {
133
+ if (!PluginStateObject.Molecule.Structure.Representation3D.is(object)) {
134
+ return;
135
+ }
136
+ const { repr, sourceData } = object.data;
137
+ const color = this.#color;
138
+ const overpaint =
139
+ color === undefined
140
+ ? Overpaint.Empty
141
+ : highlightOverpaint(sourceData, this.#highlights, color);
142
+ if (
143
+ Overpaint.isEmpty(overpaint) &&
144
+ Overpaint.isEmpty(repr.state.overpaint)
145
+ ) {
146
+ return;
147
+ }
148
+ repr.setState({ overpaint });
149
+ // No commit: the scene keeps its bounds, so the camera is never reframed.
150
+ this.#plugin.canvas3d?.update(repr, true);
151
+ }
152
+ }
153
+
154
+ function unitOf(structure: Structure, atom: AtomReference): Unit | undefined {
155
+ const unit = structure.units[atom.unit];
156
+ if (unit === undefined || atom.element >= unit.elements.length) {
157
+ return undefined;
158
+ }
159
+ return unit;
160
+ }
161
+
162
+ function atomsLoci(
163
+ structure: Structure,
164
+ atoms: readonly AtomReference[],
165
+ ): StructureElement.Loci {
166
+ const elements: Array<StructureElement.Loci['elements'][number]> = [];
167
+ for (let index = 0; index < structure.units.length; index++) {
168
+ const unit = structure.units[index];
169
+ const indices: number[] = [];
170
+ for (const atom of atoms) {
171
+ if (atom.unit === index) indices.push(atom.element);
172
+ }
173
+ if (unit === undefined || indices.length === 0) continue;
174
+ const sorted = SortedArray.ofUnsortedArray<UnitIndex>(indices);
175
+ elements.push({ unit, indices: OrderedSet.ofSortedArray(sorted) });
176
+ }
177
+ return elementLoci(structure, elements);
178
+ }
179
+
180
+ // Every half-bond leaving the atoms, which an element loci paints as well.
181
+ function leavingBondsLoci(
182
+ structure: Structure,
183
+ atoms: readonly AtomReference[],
184
+ ): Bond.Loci {
185
+ const locations: Bond.Location[] = [];
186
+ const iterator = new Bond.ElementBondIterator();
187
+ for (const atom of atoms) {
188
+ const unit = unitOf(structure, atom);
189
+ if (unit === undefined || !Unit.isAtomic(unit)) continue;
190
+ const index = atom.element as UnitIndex;
191
+ iterator.setElement(structure, unit, index);
192
+ while (iterator.hasNext) {
193
+ const { otherUnit, otherIndex } = iterator.move();
194
+ locations.push(
195
+ bondLocation(structure, unit, index, structure, otherUnit, otherIndex),
196
+ );
197
+ }
198
+ }
199
+ return bondLoci(structure, locations);
200
+ }
201
+
202
+ // molstar draws a bond as two halves, one from each atom.
203
+ function pathBondsLoci(
204
+ structure: Structure,
205
+ bonds: readonly HighlightedBond[],
206
+ ): Bond.Loci {
207
+ const locations: Bond.Location[] = [];
208
+ for (const [first, second] of bonds) {
209
+ const forward = halfBond(structure, first, second);
210
+ const backward = halfBond(structure, second, first);
211
+ if (forward !== undefined && backward !== undefined) {
212
+ locations.push(forward, backward);
213
+ }
214
+ }
215
+ return bondLoci(structure, locations);
216
+ }
217
+
218
+ function halfBond(
219
+ structure: Structure,
220
+ from: AtomReference,
221
+ to: AtomReference,
222
+ ): Bond.Location | undefined {
223
+ const fromUnit = unitOf(structure, from);
224
+ const toUnit = unitOf(structure, to);
225
+ if (fromUnit === undefined || toUnit === undefined) return undefined;
226
+ const fromIndex = from.element as UnitIndex;
227
+ const toIndex = to.element as UnitIndex;
228
+ return bondLocation(
229
+ structure,
230
+ fromUnit,
231
+ fromIndex,
232
+ structure,
233
+ toUnit,
234
+ toIndex,
235
+ );
236
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The molstar layer under the 3D views: the plugin lifecycle, the camera, the
3
- * hover labels, the measurements and the picture of a scene.
3
+ * hover labels, the measurements, the highlights and the picture of a scene.
4
4
  *
5
5
  * Three of our sites mount a headless molstar canvas — the molecule viewer
6
6
  * here, lcao's orbitals and symmetry's crystals — and each of them had its own
@@ -25,6 +25,7 @@ export {
25
25
  framedRadius,
26
26
  setSpin,
27
27
  } from './camera.ts';
28
+ export { HighlightPainter, highlightOverpaint } from './highlights.ts';
28
29
  export { atomName, atomText, lociText, subscribeHover } from './hover.ts';
29
30
  export type { MolstarPluginOptions } from './plugin.ts';
30
31
  export { MolstarPlugin } from './plugin.ts';