@ccp-nc/crystvis-js 0.6.1 → 0.7.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/.mocharc.yml +4 -0
- package/.vscode/settings.json +5 -1
- package/CHANGELOG.md +54 -16
- package/README.md +75 -2
- package/audit.txt +12 -25
- package/changes.txt +1 -26
- package/demo/demo.css +15 -10
- package/demo/index.html +32 -5
- package/demo/main.js +149 -42
- package/docs/data/search.json +1 -1
- package/docs/index.html +51 -2
- package/docs/lib_model.module_js-Model.html +1 -1
- package/docs/lib_modelview.module_js-ModelView.html +1 -1
- package/docs/lib_visualizer.module_js-CrystVis.html +1 -1
- package/docs/model.js.html +31 -0
- package/docs/modelview.js.html +24 -0
- package/docs/visualizer.js.html +237 -1
- package/lib/formats/magres.js +156 -1
- package/lib/model.js +31 -0
- package/lib/modelview.js +24 -0
- package/lib/render.js +97 -2
- package/lib/selbox.js +18 -4
- package/lib/visualizer.js +237 -1
- package/outdated.txt +9 -12
- package/package.json +5 -4
- package/test/data/hf_mu_test.magres +62 -0
- package/test/data/hf_test.magres +61 -0
- package/test/data/optimized_muon_65-hf.magres +1424 -0
- package/test/loader.js +115 -1
- package/test/model.js +118 -0
- package/test/setup-dom.cjs +147 -0
- package/test/visualizer.js +475 -0
package/.mocharc.yml
ADDED
package/.vscode/settings.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -5,26 +5,44 @@ All notable changes to crystvis-js will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.
|
|
9
|
-
|
|
10
|
-
### Changed
|
|
11
|
-
- Updated Three.js from version 0.137 to 0.178
|
|
12
|
-
- Fixed color handling across all primitives to work with new Three.js color management
|
|
13
|
-
- Fixed shader handling for GLSL 3.0 compatibility
|
|
14
|
-
- Improved text rendering with better color handling and transparency
|
|
15
|
-
- Refined material properties for better appearance in the new renderer
|
|
8
|
+
## [0.7.0] - 2026-03-11
|
|
16
9
|
|
|
17
10
|
### Added
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
- `CrystVis.dispose()` — full teardown of the WebGL context, animation loop, orbit
|
|
12
|
+
controls, resize observer and all event listeners. `isDisposed` getter reflects state.
|
|
13
|
+
Methods throw if called on a disposed instance. (#17)
|
|
14
|
+
- `CrystVis.getCameraState()` / `setCameraState(state)` — snapshot and restore the
|
|
15
|
+
camera position, target and zoom level as a plain serialisable object. (#20)
|
|
16
|
+
- `CrystVis.onCameraChange(callback)` — subscribe to live camera-change events
|
|
17
|
+
(rotate / pan / zoom); returns an unsubscribe function. (#20)
|
|
18
|
+
- `CrystVis.getModelSource(name)` — retrieve the raw file text and extension originally
|
|
19
|
+
passed to `loadModels()`. (#20)
|
|
20
|
+
- `CrystVis.getModelParameters(name)` — retrieve the merged loading parameters used when
|
|
21
|
+
a model was last loaded or reloaded. (#20)
|
|
22
|
+
- `CrystVis.getModelMeta(name)` — retrieve `{ prefix, originalName }` metadata stored at
|
|
23
|
+
load time. (#20)
|
|
24
|
+
- `CrystVis.onModelListChange(callback)` — subscribe to events fired whenever the set of
|
|
25
|
+
loaded models changes (load, delete, unloadAll); returns an unsubscribe function. (#20)
|
|
26
|
+
- `CrystVis.onDisplayChange(callback)` — subscribe to events fired whenever the displayed
|
|
27
|
+
model changes; callback receives the new model name or `null`. (#20)
|
|
28
|
+
- `CrystVis.unloadAll()` — remove all loaded models atomically (one renderer clear, one
|
|
29
|
+
set of change events). (#20)
|
|
30
|
+
- `ModelView.toIndices()` — serialise a selection to a plain index array. (#20)
|
|
31
|
+
- `ModelView.toLabels()` — serialise a selection to an array of crystallographic site
|
|
32
|
+
labels (`crystLabel`), resilient to atom-index reordering. (#20)
|
|
33
|
+
- `Model.viewFromIndices(indices)` — reconstruct a `ModelView` from a saved index array. (#20)
|
|
34
|
+
- `Model.viewFromLabels(labels)` — reconstruct a `ModelView` from a saved label array. (#20)
|
|
35
|
+
- `Renderer.getCameraState()` / `setCameraState(state)` / `onCameraChange(callback)` —
|
|
36
|
+
lower-level camera state API used by `CrystVis`. (#20)
|
|
37
|
+
- Visualisation of hyperfine tensors from `magres_old` blocks in Magres files as
|
|
38
|
+
ellipsoids. (#19)
|
|
21
39
|
|
|
22
40
|
### Fixed
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
41
|
+
- `loadModels()` return value corrected in README and JSDoc: it returns a status object
|
|
42
|
+
(keys = model names, values = `0` or error string), not an array. (#18)
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- Updated several dependencies. (#12, #13, #16)
|
|
28
46
|
|
|
29
47
|
## [0.6.1] - 2026-01-22
|
|
30
48
|
|
|
@@ -34,3 +52,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
34
52
|
- Improved Extended XYZ parsing: stricter handling of atom lines and properties to avoid accidental token merging when input contains stray newlines.
|
|
35
53
|
- Parser now reports clearer errors for malformed Extended XYZ files.
|
|
36
54
|
|
|
55
|
+
## [0.6.0] - 2025-07-18
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
- Documentation on Three.js migration.
|
|
59
|
+
- Improved error handling for shader compilation.
|
|
60
|
+
- Better color space handling for all primitives.
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
- Updated Three.js from version 0.137 to 0.178.
|
|
64
|
+
- Fixed color handling across all primitives to work with new Three.js color management.
|
|
65
|
+
- Fixed shader handling for GLSL 3.0 compatibility.
|
|
66
|
+
- Improved text rendering with better color handling and transparency.
|
|
67
|
+
- Refined material properties for better appearance in the new renderer.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
- Color issues when upgrading to Three.js 0.178.
|
|
71
|
+
- Text rendering and transparency problems.
|
|
72
|
+
- Shader compilation errors with GLSL 3.0.
|
|
73
|
+
- EllipsoidMesh color handling with different material types.
|
|
74
|
+
- Proper handling of atoms and bond materials.
|
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ You can then create a visualizer for your webpage by simply importing and instan
|
|
|
36
36
|
```js
|
|
37
37
|
import CrystVis from 'crystvis-js';
|
|
38
38
|
|
|
39
|
-
const visualizer = CrystVis('#target-id', 800, 600)
|
|
39
|
+
const visualizer = new CrystVis('#target-id', 800, 600)
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
will create an 800x600 canvas with the visualizer inside the element specified by the given selector. To load a model, simply load the contents of your file as a text string and then pass them to the visualizer's `loadModels` method:
|
|
@@ -44,7 +44,80 @@ will create an 800x600 canvas with the visualizer inside the element specified b
|
|
|
44
44
|
```js
|
|
45
45
|
var loaded = visualizer.loadModels(contents);
|
|
46
46
|
console.log('Models loaded: ', loaded);
|
|
47
|
-
|
|
47
|
+
// loaded is an object: keys are model names, values are 0 (success) or an error string
|
|
48
|
+
var modelName = Object.keys(loaded)[0];
|
|
49
|
+
if (loaded[modelName] !== 0) {
|
|
50
|
+
console.error('Failed to load model:', loaded[modelName]);
|
|
51
|
+
} else {
|
|
52
|
+
visualizer.displayModel(modelName);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### API highlights
|
|
57
|
+
|
|
58
|
+
Full JSDoc documentation is available at [ccp-nc.github.io/crystvis-js](https://ccp-nc.github.io/crystvis-js/).
|
|
59
|
+
|
|
60
|
+
#### Camera state — save, restore and react to view changes
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// Snapshot the current camera (position, target, zoom) — plain JSON-serialisable object
|
|
64
|
+
const snap = visualizer.getCameraState();
|
|
65
|
+
// { position: {x,y,z}, target: {x,y,z}, zoom: 1 }
|
|
66
|
+
|
|
67
|
+
// Restore a previously saved snapshot
|
|
68
|
+
visualizer.setCameraState(snap);
|
|
69
|
+
|
|
70
|
+
// React to every rotate/pan/zoom (returns an unsubscribe function)
|
|
71
|
+
const unsub = visualizer.onCameraChange(state => {
|
|
72
|
+
console.log('Camera moved:', state);
|
|
73
|
+
});
|
|
74
|
+
unsub(); // stop listening
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Lifecycle events — react to model and display changes
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
// Fired whenever models are loaded, deleted, or all cleared
|
|
81
|
+
const unsubList = visualizer.onModelListChange(names => {
|
|
82
|
+
console.log('Loaded models:', names);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Fired whenever displayModel() completes; receives model name or null
|
|
86
|
+
const unsubDisplay = visualizer.onDisplayChange(name => {
|
|
87
|
+
console.log('Now displaying:', name);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Remove all loaded models in one atomic operation
|
|
91
|
+
visualizer.unloadAll();
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Model metadata — access source and parameters after loading
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
// Retrieve the raw file text and extension originally passed to loadModels()
|
|
98
|
+
const src = visualizer.getModelSource(modelName);
|
|
99
|
+
// { text: '...', extension: 'cif' }
|
|
100
|
+
|
|
101
|
+
// Retrieve the merged loading parameters (supercell, molecularCrystal, …)
|
|
102
|
+
const params = visualizer.getModelParameters(modelName);
|
|
103
|
+
|
|
104
|
+
// Retrieve prefix and original structure name
|
|
105
|
+
const meta = visualizer.getModelMeta(modelName);
|
|
106
|
+
// { prefix: 'cif', originalName: 'struct' }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Selection serialisation — save and reconstruct atom subsets
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
// Serialise a selection to plain data
|
|
113
|
+
const indices = visualizer.selected.toIndices(); // number[]
|
|
114
|
+
const labels = visualizer.selected.toLabels(); // string[] (crystLabel per atom)
|
|
115
|
+
|
|
116
|
+
// Reconstruct from indices later
|
|
117
|
+
visualizer.selected = visualizer.model.viewFromIndices(indices);
|
|
118
|
+
|
|
119
|
+
// Reconstruct from labels — resilient to atom-index reordering on reload
|
|
120
|
+
visualizer.selected = visualizer.model.viewFromLabels(labels);
|
|
48
121
|
```
|
|
49
122
|
|
|
50
123
|
### Preparing for development
|
package/audit.txt
CHANGED
|
@@ -1,37 +1,24 @@
|
|
|
1
1
|
## Security Audit
|
|
2
2
|
# npm audit report
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
Elliptic Uses a Cryptographic Primitive with a Risky Implementation - https://github.com/advisories/GHSA-848j-6mx2-7j84
|
|
6
|
-
No fix available
|
|
7
|
-
node_modules/elliptic
|
|
8
|
-
|
|
9
|
-
1 low severity vulnerability
|
|
10
|
-
|
|
11
|
-
Some issues need review, and may require choosing
|
|
12
|
-
a different dependency.
|
|
13
|
-
|
|
14
|
-
## Security Audit
|
|
15
|
-
# npm audit report
|
|
16
|
-
|
|
17
|
-
diff <8.0.3
|
|
4
|
+
diff 6.0.0 - 8.0.2
|
|
18
5
|
jsdiff has a Denial of Service vulnerability in parsePatch and applyPatch - https://github.com/advisories/GHSA-73rr-hh4g-fpgx
|
|
19
6
|
fix available via `npm audit fix --force`
|
|
20
|
-
Will install mocha@
|
|
7
|
+
Will install mocha@11.3.0, which is a breaking change
|
|
21
8
|
node_modules/diff
|
|
22
|
-
mocha 0.
|
|
9
|
+
mocha 8.0.0 - 12.0.0-beta-3
|
|
23
10
|
Depends on vulnerable versions of diff
|
|
11
|
+
Depends on vulnerable versions of serialize-javascript
|
|
24
12
|
node_modules/mocha
|
|
25
13
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
serialize-javascript <=7.0.2
|
|
15
|
+
Severity: high
|
|
16
|
+
Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString() - https://github.com/advisories/GHSA-5c6j-r48x-rmvq
|
|
17
|
+
fix available via `npm audit fix --force`
|
|
18
|
+
Will install mocha@11.3.0, which is a breaking change
|
|
19
|
+
node_modules/serialize-javascript
|
|
30
20
|
|
|
31
|
-
3 low
|
|
21
|
+
3 vulnerabilities (1 low, 2 high)
|
|
32
22
|
|
|
33
|
-
To address all issues
|
|
23
|
+
To address all issues (including breaking changes), run:
|
|
34
24
|
npm audit fix --force
|
|
35
|
-
|
|
36
|
-
Some issues need review, and may require choosing
|
|
37
|
-
a different dependency.
|
package/changes.txt
CHANGED
|
@@ -1,27 +1,2 @@
|
|
|
1
1
|
## Updated Packages
|
|
2
|
-
|
|
3
|
-
+ "chroma-js": "^3.2.0",
|
|
4
|
-
- "mathjs": "^14.5.3",
|
|
5
|
-
+ "mathjs": "^14.9.1",
|
|
6
|
-
- "@babel/eslint-parser": "^7.28.0",
|
|
7
|
-
- "chai": "^5.2.1",
|
|
8
|
-
+ "@babel/eslint-parser": "^7.28.6",
|
|
9
|
-
+ "chai": "^5.3.3",
|
|
10
|
-
- "elliptic": ">=6.6.1",
|
|
11
|
-
- "esbuild": "^0.25.6",
|
|
12
|
-
- "eslint": "^9.31.0",
|
|
13
|
-
+ "elliptic": "^6.6.1",
|
|
14
|
-
+ "esbuild": "^0.25.12",
|
|
15
|
-
+ "eslint": "^9.39.2",
|
|
16
|
-
- "glob": "^11.0.3",
|
|
17
|
-
- "jpeg-js": ">=0.4.4",
|
|
18
|
-
- "jsdoc": "^4.0.4",
|
|
19
|
-
+ "glob": "^11.1.0",
|
|
20
|
-
+ "jpeg-js": "^0.4.4",
|
|
21
|
-
+ "jsdoc": "^4.0.5",
|
|
22
|
-
- "mocha": "^11.7.1",
|
|
23
|
-
- "msdf-bmfont-xml": "^2.7.0",
|
|
24
|
-
+ "mocha": "^11.7.5",
|
|
25
|
-
+ "msdf-bmfont-xml": "^2.8.0",
|
|
26
|
-
- "serve": "^14.2.4"
|
|
27
|
-
+ "serve": "^14.2.5"
|
|
2
|
+
See package.json diff for details
|
package/demo/demo.css
CHANGED
|
@@ -18,13 +18,18 @@
|
|
|
18
18
|
left: 0;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
#error-banner {
|
|
22
|
+
position: fixed;
|
|
23
|
+
top: 0;
|
|
24
|
+
left: 0;
|
|
25
|
+
width: 100%;
|
|
26
|
+
padding: 10px 16px;
|
|
27
|
+
background-color: #b00020;
|
|
28
|
+
color: #fff;
|
|
29
|
+
font-family: sans-serif;
|
|
30
|
+
font-size: 14px;
|
|
31
|
+
font-weight: bold;
|
|
32
|
+
z-index: 9999;
|
|
33
|
+
box-sizing: border-box;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
}
|
package/demo/index.html
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
Loading...
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
+
<div id="error-banner" style="display:none"></div>
|
|
20
|
+
|
|
19
21
|
<div style="float: right; width: 30%">
|
|
20
22
|
<table>
|
|
21
23
|
<caption>Input controls</caption>
|
|
@@ -52,12 +54,19 @@
|
|
|
52
54
|
<input id="label-check" type="checkbox" name="" value="false" onchange="changeLabels()">Show labels
|
|
53
55
|
</td>
|
|
54
56
|
<td colspan="" rowspan="" headers="">
|
|
55
|
-
<input id="ellipsoid-check" type="checkbox" name="" value="false" onchange="changeEllipsoids()">Show ellipsoids
|
|
57
|
+
<input id="ellipsoid-check" type="checkbox" name="" value="false" onchange="changeEllipsoids()" disabled>Show MS ellipsoids<br>
|
|
58
|
+
Scale: <input id="ms-scale" type="range" min="0.001" max="0.5" step="0.001" value="0.05" oninput="changeMsScale()" disabled> <span id="ms-scale-val">0.050</span>
|
|
59
|
+
</td>
|
|
60
|
+
</tr>
|
|
61
|
+
<tr>
|
|
62
|
+
<td colspan="" rowspan="" headers="">
|
|
63
|
+
<input id="hf-ellipsoid-check" type="checkbox" name="" value="false" onchange="changeHFEllipsoids()" disabled>Show HF ellipsoids<br>
|
|
64
|
+
Scale: <input id="hf-scale" type="range" min="0.001" max="1.0" step="0.001" value="0.1" oninput="changeHfScale()" disabled> <span id="hf-scale-val">0.100</span>
|
|
56
65
|
</td>
|
|
57
66
|
</tr>
|
|
58
67
|
<tr>
|
|
59
68
|
<td>
|
|
60
|
-
<input id="isosurf-check" type="checkbox" name="" value="false" onchange="changeIsosurface()">Show isosurface
|
|
69
|
+
<input id="isosurf-check" type="checkbox" name="" value="false" onchange="changeIsosurface()" disabled>Show isosurface
|
|
61
70
|
</td>
|
|
62
71
|
<td>
|
|
63
72
|
<input type="text" id="vdw-f" size="5" value="1.0"> Van der Waals scaling
|
|
@@ -77,11 +86,29 @@
|
|
|
77
86
|
</td>
|
|
78
87
|
|
|
79
88
|
</tr>
|
|
89
|
+
<tr>
|
|
90
|
+
<td colspan="2"><hr style="margin:6px 0"><strong>Camera state</strong></td>
|
|
91
|
+
</tr>
|
|
92
|
+
<tr>
|
|
93
|
+
<td>
|
|
94
|
+
<input type="button" value="Save view" onclick="saveCamera()">
|
|
95
|
+
<input type="button" value="Restore view" onclick="restoreCamera()">
|
|
96
|
+
</td>
|
|
97
|
+
<td>
|
|
98
|
+
<input type="button" value="Copy JSON" onclick="copyCameraJSON()">
|
|
99
|
+
<input type="button" value="Apply JSON" onclick="applyPastedJSON()">
|
|
100
|
+
</td>
|
|
101
|
+
</tr>
|
|
102
|
+
<tr>
|
|
103
|
+
<td colspan="2">
|
|
104
|
+
<textarea id="camera-json" rows="5" style="width:100%;font-family:monospace;font-size:11px;box-sizing:border-box" placeholder="Camera state will appear here…" spellcheck="false"></textarea>
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
<tr>
|
|
108
|
+
<td colspan="2" id="camera-status" style="font-size:11px;color:#555"></td>
|
|
109
|
+
</tr>
|
|
80
110
|
</tbody>
|
|
81
111
|
</table>
|
|
82
|
-
<div id='colorgrid'>
|
|
83
|
-
|
|
84
|
-
</div>
|
|
85
112
|
</div>
|
|
86
113
|
|
|
87
114
|
</div>
|
package/demo/main.js
CHANGED
|
@@ -3,43 +3,15 @@
|
|
|
3
3
|
const CrystVis = require('../lib/visualizer.js').CrystVis;
|
|
4
4
|
const Primitives = require('../lib/primitives/index.js');
|
|
5
5
|
|
|
6
|
-
const shiftCpkColor = require('../lib/utils').shiftCpkColor;
|
|
7
|
-
|
|
8
6
|
var visualizer = new CrystVis('#main-app', 0, 0);
|
|
9
7
|
visualizer.highlight_selected = true;
|
|
10
8
|
visualizer.theme = 'dark';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function
|
|
17
|
-
c = c.toString(16);
|
|
18
|
-
return '0'.repeat(6-c.length) + c;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
for (let i = 0; i < gridSize; ++i) {
|
|
22
|
-
for (let j = 0; j < gridSize; ++j) {
|
|
23
|
-
|
|
24
|
-
const hue = parseInt(j/gridSize*360);
|
|
25
|
-
const light = parseInt(i/(gridSize-1)*100);
|
|
26
|
-
const cbase = `hsl(${hue}, 100%, ${light}%)`;
|
|
27
|
-
const cplus = shiftCpkColor(cbase, 1.0);
|
|
28
|
-
const cminus = shiftCpkColor(cbase, -1.0);
|
|
29
|
-
|
|
30
|
-
let el = document.createElement('div');
|
|
31
|
-
el.style['background-color'] = '#' + int2hex(cminus);
|
|
32
|
-
gridEl.append(el);
|
|
33
|
-
|
|
34
|
-
el = document.createElement('div');
|
|
35
|
-
el.style['background-color'] = cbase;
|
|
36
|
-
gridEl.append(el);
|
|
37
|
-
|
|
38
|
-
el = document.createElement('div');
|
|
39
|
-
el.style['background-color'] = '#' + int2hex(cplus);
|
|
40
|
-
gridEl.append(el);
|
|
41
|
-
|
|
42
|
-
}
|
|
10
|
+
function showError(msg) {
|
|
11
|
+
var banner = document.getElementById('error-banner');
|
|
12
|
+
banner.textContent = msg + ' (click to dismiss)';
|
|
13
|
+
banner.style.display = 'block';
|
|
14
|
+
banner.onclick = function() { banner.style.display = 'none'; };
|
|
43
15
|
}
|
|
44
16
|
|
|
45
17
|
window.loadFile = function() {
|
|
@@ -57,17 +29,54 @@ window.loadFile = function() {
|
|
|
57
29
|
reader.onload = function() {
|
|
58
30
|
var mcryst = document.getElementById('molcryst-check').checked;
|
|
59
31
|
var name = file.name.split('.')[0];
|
|
60
|
-
var loaded
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
32
|
+
var loaded;
|
|
33
|
+
try {
|
|
34
|
+
loaded = visualizer.loadModels(reader.result, extension, name, {
|
|
35
|
+
supercell: [sx, sy, sz],
|
|
36
|
+
molecularCrystal: mcryst,
|
|
37
|
+
vdwScaling: vdwf
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
showError('Could not load file: ' + e.message);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
65
43
|
|
|
66
|
-
|
|
44
|
+
var modelName = Object.keys(loaded)[0];
|
|
45
|
+
if (loaded[modelName] !== 0) {
|
|
46
|
+
showError('Failed to load "' + modelName + '": ' + loaded[modelName]);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
visualizer.displayModel(modelName);
|
|
67
50
|
visualizer.displayed = visualizer.model.find({
|
|
68
51
|
'all': []
|
|
69
52
|
});
|
|
70
53
|
|
|
54
|
+
// Update checkbox/slider states based on available data in the loaded model
|
|
55
|
+
var model = visualizer.model;
|
|
56
|
+
|
|
57
|
+
var ellipsoidCheck = document.getElementById('ellipsoid-check');
|
|
58
|
+
var msSlider = document.getElementById('ms-scale');
|
|
59
|
+
var hasMs = model.hasArray('ms');
|
|
60
|
+
ellipsoidCheck.disabled = !hasMs;
|
|
61
|
+
msSlider.disabled = !hasMs;
|
|
62
|
+
if (!hasMs && ellipsoidCheck.checked) {
|
|
63
|
+
ellipsoidCheck.checked = false;
|
|
64
|
+
visualizer.displayed.removeEllipsoids('ms');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var hfEllipsoidCheck = document.getElementById('hf-ellipsoid-check');
|
|
68
|
+
var hfSlider = document.getElementById('hf-scale');
|
|
69
|
+
var hasHf = model.hasArray('hf');
|
|
70
|
+
hfEllipsoidCheck.disabled = !hasHf;
|
|
71
|
+
hfSlider.disabled = !hasHf;
|
|
72
|
+
if (!hasHf && hfEllipsoidCheck.checked) {
|
|
73
|
+
hfEllipsoidCheck.checked = false;
|
|
74
|
+
visualizer.displayed.removeEllipsoids('hf');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Isosurface only makes sense once a model (with a cell) is loaded
|
|
78
|
+
document.getElementById('isosurf-check').disabled = false;
|
|
79
|
+
|
|
71
80
|
};
|
|
72
81
|
}
|
|
73
82
|
|
|
@@ -89,21 +98,55 @@ window.changeLabels = function() {
|
|
|
89
98
|
|
|
90
99
|
window.changeEllipsoids = function() {
|
|
91
100
|
var val = document.getElementById('ellipsoid-check').checked;
|
|
101
|
+
var scale = parseFloat(document.getElementById('ms-scale').value);
|
|
92
102
|
if (val) {
|
|
93
103
|
visualizer.displayed.find({
|
|
94
104
|
'elements': 'H'
|
|
95
105
|
}).addEllipsoids((a) => {
|
|
96
106
|
return a.getArrayValue('ms');
|
|
97
107
|
}, 'ms', {
|
|
98
|
-
scalingFactor:
|
|
108
|
+
scalingFactor: scale,
|
|
99
109
|
opacity: 0.2
|
|
100
110
|
});
|
|
101
|
-
|
|
102
111
|
} else {
|
|
103
112
|
visualizer.displayed.removeEllipsoids('ms');
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
115
|
|
|
116
|
+
window.changeMsScale = function() {
|
|
117
|
+
document.getElementById('ms-scale-val').textContent = parseFloat(document.getElementById('ms-scale').value).toFixed(3);
|
|
118
|
+
if (document.getElementById('ellipsoid-check').checked) {
|
|
119
|
+
window.changeEllipsoids();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
window.changeHFEllipsoids = function() {
|
|
124
|
+
var val = document.getElementById('hf-ellipsoid-check').checked;
|
|
125
|
+
var scale = parseFloat(document.getElementById('hf-scale').value);
|
|
126
|
+
if (val) {
|
|
127
|
+
// Show HF (hyperfine) tensor ellipsoids for all atoms that have data
|
|
128
|
+
visualizer.displayed.addEllipsoids((a) => {
|
|
129
|
+
try {
|
|
130
|
+
return a.getArrayValue('hf');
|
|
131
|
+
} catch(e) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}, 'hf', {
|
|
135
|
+
scalingFactor: scale,
|
|
136
|
+
opacity: 0.3
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
visualizer.displayed.removeEllipsoids('hf');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
window.changeHfScale = function() {
|
|
144
|
+
document.getElementById('hf-scale-val').textContent = parseFloat(document.getElementById('hf-scale').value).toFixed(3);
|
|
145
|
+
if (document.getElementById('hf-ellipsoid-check').checked) {
|
|
146
|
+
window.changeHFEllipsoids();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
107
150
|
var isosurface = null;
|
|
108
151
|
window.changeIsosurface = function() {
|
|
109
152
|
var val = document.getElementById('isosurf-check').checked;
|
|
@@ -152,4 +195,68 @@ window.displayMessage = function() {
|
|
|
152
195
|
// clear messages
|
|
153
196
|
window.clearMessages = function() {
|
|
154
197
|
visualizer.clearNotifications();
|
|
155
|
-
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── Camera state demo ───────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
// Keep a snapshot of the last saved camera state so Restore can use it.
|
|
203
|
+
var _savedCameraState = null;
|
|
204
|
+
|
|
205
|
+
/** Pretty-print a camera state snapshot into the textarea. */
|
|
206
|
+
function updateCameraTextarea(state) {
|
|
207
|
+
var ta = document.getElementById('camera-json');
|
|
208
|
+
if (ta) ta.value = JSON.stringify(state, null, 2);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Mark the status line briefly then clear it. */
|
|
212
|
+
function setCameraStatus(msg) {
|
|
213
|
+
var el = document.getElementById('camera-status');
|
|
214
|
+
if (!el) return;
|
|
215
|
+
el.textContent = msg;
|
|
216
|
+
clearTimeout(el._timer);
|
|
217
|
+
el._timer = setTimeout(() => { el.textContent = ''; }, 2500);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Live-update the textarea whenever the user rotates / pans / zooms.
|
|
221
|
+
visualizer.onCameraChange(function(state) {
|
|
222
|
+
updateCameraTextarea(state);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
/** Save the current camera position so it can be restored later. */
|
|
226
|
+
window.saveCamera = function() {
|
|
227
|
+
_savedCameraState = visualizer.getCameraState();
|
|
228
|
+
updateCameraTextarea(_savedCameraState);
|
|
229
|
+
setCameraStatus('View saved.');
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/** Restore the most recently saved camera snapshot. */
|
|
233
|
+
window.restoreCamera = function() {
|
|
234
|
+
if (!_savedCameraState) {
|
|
235
|
+
setCameraStatus('Nothing saved yet — click Save view first.');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
visualizer.setCameraState(_savedCameraState);
|
|
239
|
+
setCameraStatus('View restored.');
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/** Copy the current textarea JSON to the clipboard. */
|
|
243
|
+
window.copyCameraJSON = function() {
|
|
244
|
+
var ta = document.getElementById('camera-json');
|
|
245
|
+
if (!ta || !ta.value) { setCameraStatus('Nothing to copy yet.'); return; }
|
|
246
|
+
navigator.clipboard.writeText(ta.value)
|
|
247
|
+
.then(() => setCameraStatus('Copied to clipboard.'))
|
|
248
|
+
.catch(() => { ta.select(); document.execCommand('copy'); setCameraStatus('Copied (fallback).'); });
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/** Parse whatever is in the textarea and apply it as the camera state. */
|
|
252
|
+
window.applyPastedJSON = function() {
|
|
253
|
+
var ta = document.getElementById('camera-json');
|
|
254
|
+
if (!ta || !ta.value) { setCameraStatus('Textarea is empty.'); return; }
|
|
255
|
+
try {
|
|
256
|
+
var state = JSON.parse(ta.value);
|
|
257
|
+
visualizer.setCameraState(state);
|
|
258
|
+
setCameraStatus('Camera state applied.');
|
|
259
|
+
} catch (e) {
|
|
260
|
+
setCameraStatus('Invalid JSON: ' + e.message);
|
|
261
|
+
}
|
|
262
|
+
};
|