react-cheminfo 0.63.1 → 0.64.0
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.
- package/lib/molecule3d/core/highlight.d.ts +53 -0
- package/lib/molecule3d/core/highlight.d.ts.map +1 -0
- package/lib/molecule3d/core/highlight.js +102 -0
- package/lib/molecule3d/core/highlight.js.map +1 -0
- package/lib/molecule3d/core/index.d.ts +2 -0
- package/lib/molecule3d/core/index.d.ts.map +1 -1
- package/lib/molecule3d/core/index.js +1 -0
- package/lib/molecule3d/core/index.js.map +1 -1
- package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
- package/lib/molecule3d/ui/MoleculeCanvas3D.js +7 -1
- package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
- package/lib/molecule3d/ui/highlightColor.d.ts +18 -0
- package/lib/molecule3d/ui/highlightColor.d.ts.map +1 -0
- package/lib/molecule3d/ui/highlightColor.js +31 -0
- package/lib/molecule3d/ui/highlightColor.js.map +1 -0
- package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +13 -0
- package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
- package/lib/molecule3d/ui/renderMolecule.d.ts +2 -0
- package/lib/molecule3d/ui/renderMolecule.d.ts.map +1 -1
- package/lib/molecule3d/ui/renderMolecule.js +1 -1
- package/lib/molecule3d/ui/renderMolecule.js.map +1 -1
- package/lib/molecule3d/ui/viewer.d.ts +9 -0
- package/lib/molecule3d/ui/viewer.d.ts.map +1 -1
- package/lib/molecule3d/ui/viewer.js +18 -2
- package/lib/molecule3d/ui/viewer.js.map +1 -1
- package/lib/molstar/core/highlights.d.ts +57 -0
- package/lib/molstar/core/highlights.d.ts.map +1 -0
- package/lib/molstar/core/highlights.js +178 -0
- package/lib/molstar/core/highlights.js.map +1 -0
- package/lib/molstar/core/index.d.ts +2 -1
- package/lib/molstar/core/index.d.ts.map +1 -1
- package/lib/molstar/core/index.js +2 -1
- package/lib/molstar/core/index.js.map +1 -1
- package/package.json +1 -1
- package/src/molecule3d/core/highlight.ts +133 -0
- package/src/molecule3d/core/index.ts +6 -0
- package/src/molecule3d/ui/MoleculeCanvas3D.tsx +13 -0
- package/src/molecule3d/ui/highlightColor.ts +34 -0
- package/src/molecule3d/ui/moleculeViewer3DProps.ts +13 -0
- package/src/molecule3d/ui/renderMolecule.ts +1 -1
- package/src/molecule3d/ui/viewer.ts +23 -0
- package/src/molstar/core/highlights.ts +236 -0
- package/src/molstar/core/index.ts +2 -1
|
@@ -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';
|