@ccp-nc/crystvis-js 0.4.13
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/.eslintrc.json +16 -0
- package/.github/workflows/test-mocha.yml +30 -0
- package/.vscode/settings.json +4 -0
- package/LICENSE +21 -0
- package/README.html +1127 -0
- package/README.md +76 -0
- package/demo/demo.css +30 -0
- package/demo/index.html +76 -0
- package/demo/main.js +143 -0
- package/docs/.nojekyll +0 -0
- package/docs-tutorials/Events.md +57 -0
- package/docs-tutorials/Queries.md +50 -0
- package/fonts/Rubik/OFL.txt +93 -0
- package/fonts/Rubik/README.txt +77 -0
- package/fonts/Rubik/Rubik-Italic-VariableFont_wght.ttf +0 -0
- package/fonts/Rubik/Rubik-VariableFont_wght.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Black.ttf +0 -0
- package/fonts/Rubik/static/Rubik-BlackItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Bold.ttf +0 -0
- package/fonts/Rubik/static/Rubik-BoldItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-ExtraBold.ttf +0 -0
- package/fonts/Rubik/static/Rubik-ExtraBoldItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Italic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Light.ttf +0 -0
- package/fonts/Rubik/static/Rubik-LightItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Medium.ttf +0 -0
- package/fonts/Rubik/static/Rubik-MediumItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Regular.ttf +0 -0
- package/fonts/Rubik/static/Rubik-SemiBold.ttf +0 -0
- package/fonts/Rubik/static/Rubik-SemiBoldItalic.ttf +0 -0
- package/index.html +25 -0
- package/index.js +11 -0
- package/jsconf.json +14 -0
- package/lib/assets/fonts/Rubik-Medium.fnt +297 -0
- package/lib/assets/fonts/Rubik-Medium.png +0 -0
- package/lib/assets/fonts/bmpfonts.in.js +16 -0
- package/lib/assets/fonts/bmpfonts.js +9 -0
- package/lib/assets/fonts/font.js +82 -0
- package/lib/assets/fonts/index.js +14 -0
- package/lib/assets/fonts/threebmfont.js +28 -0
- package/lib/data.js +125 -0
- package/lib/formats/cell.js +114 -0
- package/lib/formats/cif.js +22 -0
- package/lib/formats/magres.js +337 -0
- package/lib/formats/xyz.js +124 -0
- package/lib/loader.js +87 -0
- package/lib/model.js +2076 -0
- package/lib/modelview.js +382 -0
- package/lib/nmrdata.js +2898 -0
- package/lib/orbit.js +1233 -0
- package/lib/primitives/atoms.js +261 -0
- package/lib/primitives/cell.js +160 -0
- package/lib/primitives/dither.js +156 -0
- package/lib/primitives/ellipsoid.js +183 -0
- package/lib/primitives/geometries.js +20 -0
- package/lib/primitives/index.js +48 -0
- package/lib/primitives/isosurface.js +171 -0
- package/lib/primitives/shapes.js +100 -0
- package/lib/primitives/sprites.js +172 -0
- package/lib/query.js +158 -0
- package/lib/render.js +440 -0
- package/lib/selbox.js +361 -0
- package/lib/shaders/aura.frag +26 -0
- package/lib/shaders/aura.vert +37 -0
- package/lib/shaders/dither.frag +42 -0
- package/lib/shaders/dither.vert +8 -0
- package/lib/shaders/index.in.js +17 -0
- package/lib/shaders/index.js +25 -0
- package/lib/shaders/msdf300.frag +25 -0
- package/lib/shaders/msdf300.vert +45 -0
- package/lib/tensor.js +227 -0
- package/lib/utils.js +168 -0
- package/lib/visualizer.js +480 -0
- package/package.json +106 -0
- package/scripts/build-bundle.js +17 -0
- package/scripts/build-fonts.js +43 -0
- package/scripts/build-resources.js +46 -0
- package/scripts/plugins-shim.js +10 -0
- package/test/chemdata.js +69 -0
- package/test/data/CHA.cif +74 -0
- package/test/data/H2O.xyz +8 -0
- package/test/data/H2_bound.xyz +4 -0
- package/test/data/ethanol.cell +25 -0
- package/test/data/ethanol.magres +238 -0
- package/test/data/example_single.cif +789 -0
- package/test/data/frac.cell +8 -0
- package/test/data/org.cif +427 -0
- package/test/data/pyridine.xyz +13 -0
- package/test/data/si8.xyz +10 -0
- package/test/loader.js +107 -0
- package/test/model.js +368 -0
- package/test/query.js +135 -0
- package/test/tensor.js +133 -0
- package/test/test-html/examples.js +1485 -0
- package/test/test-html/index.html +33 -0
- package/test/test-html/index.js +279 -0
- package/tools/compile_colors.py +120 -0
- package/tools/compile_periodic.py +96 -0
- package/tools/ptable.json +497 -0
- package/tools/test +5844 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Class constituting the main object that plots crystals in the webpage
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as _ from 'lodash';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Renderer as Renderer
|
|
12
|
+
} from './render.js';
|
|
13
|
+
import {
|
|
14
|
+
Loader as Loader
|
|
15
|
+
} from './loader.js';
|
|
16
|
+
import {
|
|
17
|
+
Model as Model
|
|
18
|
+
} from './model.js';
|
|
19
|
+
import {
|
|
20
|
+
ModelView as ModelView
|
|
21
|
+
} from './modelview.js';
|
|
22
|
+
import {
|
|
23
|
+
AtomMesh
|
|
24
|
+
} from './primitives/index.js';
|
|
25
|
+
import {
|
|
26
|
+
addStaticVar
|
|
27
|
+
} from './utils.js';
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
const model_parameter_defaults = {
|
|
31
|
+
supercell: [1, 1, 1],
|
|
32
|
+
molecularCrystal: false
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** An object providing a full interface to a renderer for crystallographic models */
|
|
36
|
+
class CrystVis {
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* An object providing a full interface to a renderer for crystallographic
|
|
40
|
+
* models
|
|
41
|
+
* @class
|
|
42
|
+
* @param {string} element CSS-style identifier for the HTML element to
|
|
43
|
+
* put the renderer in
|
|
44
|
+
* @param {int} width Window width
|
|
45
|
+
* @param {int} height Window height. If both this and width are
|
|
46
|
+
* set to 0, the window fits its context and
|
|
47
|
+
* automatically resizes with it
|
|
48
|
+
*/
|
|
49
|
+
constructor(element, width = 0, height = 0) {
|
|
50
|
+
|
|
51
|
+
// Create a renderer
|
|
52
|
+
this._renderer = new Renderer(element, width, height);
|
|
53
|
+
this._loader = new Loader();
|
|
54
|
+
|
|
55
|
+
this._models = {};
|
|
56
|
+
|
|
57
|
+
this._current_model = null;
|
|
58
|
+
this._current_mname = null;
|
|
59
|
+
this._displayed = null;
|
|
60
|
+
this._selected = null;
|
|
61
|
+
|
|
62
|
+
// Handling events
|
|
63
|
+
this._atom_click_events = {};
|
|
64
|
+
this._atom_click_events[CrystVis.LEFT_CLICK] = this._defaultAtomLeftClick.bind(this);
|
|
65
|
+
this._atom_click_events[CrystVis.LEFT_CLICK + CrystVis.SHIFT_BUTTON] = this._defaultAtomShiftLeftClick.bind(this);
|
|
66
|
+
this._atom_click_events[CrystVis.LEFT_CLICK + CrystVis.CTRL_BUTTON] = this._defaultAtomCtrlLeftClick.bind(this);
|
|
67
|
+
|
|
68
|
+
this._atom_click_defaults = _.cloneDeep(this._atom_click_events);
|
|
69
|
+
|
|
70
|
+
this._atom_box_event = this._defaultAtomBox.bind(this);
|
|
71
|
+
|
|
72
|
+
this._renderer.addClickListener(this._handleAtomClick.bind(this),
|
|
73
|
+
this._renderer._groups.model, AtomMesh);
|
|
74
|
+
this._renderer.addSelBoxListener(this._handleAtomBox.bind(this),
|
|
75
|
+
this._renderer._groups.model, AtomMesh);
|
|
76
|
+
|
|
77
|
+
// Additional options
|
|
78
|
+
// Hidden (need dedicated setters)
|
|
79
|
+
this._hsel = false; // If true, highlight the selected atoms
|
|
80
|
+
|
|
81
|
+
// Vanilla (no get/set needed)
|
|
82
|
+
this.cifsymtol = 1e-2; // Parameter controlling the tolerance to symmetry when loading CIF files
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* List of loaded models
|
|
88
|
+
* @readonly
|
|
89
|
+
* @type {Array}
|
|
90
|
+
*/
|
|
91
|
+
get modelList() {
|
|
92
|
+
return Object.keys(this._models);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Currently loaded model
|
|
97
|
+
* @readonly
|
|
98
|
+
* @type {Model}
|
|
99
|
+
*/
|
|
100
|
+
get model() {
|
|
101
|
+
return this._current_model;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Name of the currently loaded model
|
|
106
|
+
* @readonly
|
|
107
|
+
* @type {String}
|
|
108
|
+
*/
|
|
109
|
+
get modelName() {
|
|
110
|
+
return this._current_mname;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Displayed atoms
|
|
115
|
+
* @type {ModelView}
|
|
116
|
+
*/
|
|
117
|
+
get displayed() {
|
|
118
|
+
return this._displayed;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
set displayed(d) {
|
|
122
|
+
if (!(d instanceof ModelView)) {
|
|
123
|
+
throw new Error('.displayed must be set with a ModelView');
|
|
124
|
+
}
|
|
125
|
+
this._displayed.hide();
|
|
126
|
+
this._displayed = d;
|
|
127
|
+
this._displayed.show();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Selected atoms
|
|
132
|
+
* @type {ModelView}
|
|
133
|
+
*/
|
|
134
|
+
get selected() {
|
|
135
|
+
return this._selected;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
set selected(s) {
|
|
139
|
+
if (!(s instanceof ModelView)) {
|
|
140
|
+
throw new Error('.selected must be set with a ModelView');
|
|
141
|
+
}
|
|
142
|
+
this._selected.setProperty('highlighted', false);
|
|
143
|
+
this._selected = s;
|
|
144
|
+
this._selected.setProperty('highlighted', this._hsel);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Whether the selected atoms should be highlighted with auras
|
|
148
|
+
* @type {bool}
|
|
149
|
+
*/
|
|
150
|
+
get highlightSelected() {
|
|
151
|
+
return this._hsel;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
set highlightSelected(hs) {
|
|
155
|
+
this._hsel = hs;
|
|
156
|
+
if (this._selected) {
|
|
157
|
+
this._selected.setProperty('highlighted', this._hsel);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Set a callback function for an event where a user clicks on an atom. The
|
|
163
|
+
* function should take as arguments the atom image for the clicked atom and
|
|
164
|
+
* the event object:
|
|
165
|
+
*
|
|
166
|
+
* function callback(atom, event) {
|
|
167
|
+
* ...
|
|
168
|
+
* }
|
|
169
|
+
*
|
|
170
|
+
* @param {Function} callback Callback function for the event. Passing "null" restores default behaviour
|
|
171
|
+
* @param {int} modifiers Click event. Use the following flags to define it:
|
|
172
|
+
*
|
|
173
|
+
* * CrystVis.LEFT_CLICK
|
|
174
|
+
* * CrystVis.RIGHT_CLICK
|
|
175
|
+
* * CrystVis.MIDDLE_CLICK
|
|
176
|
+
* * CrystVis.CTRL_BUTTON
|
|
177
|
+
* * CrystVis.ALT_BUTTON
|
|
178
|
+
* * CrystVis.SHIFT_BUTTON
|
|
179
|
+
* * CrystVis.CMD_BUTTON
|
|
180
|
+
*
|
|
181
|
+
* For example, CrystVis.LEFT_CLICK + CrystVis.SHIFT_BUTTON
|
|
182
|
+
* defines the event for a click while the Shift key is pressed.
|
|
183
|
+
*
|
|
184
|
+
*/
|
|
185
|
+
onAtomClick(callback = null, modifiers = CrystVis.LEFT_CLICK) {
|
|
186
|
+
|
|
187
|
+
// Check that event makes sense
|
|
188
|
+
var lc = modifiers & CrystVis.LEFT_CLICK;
|
|
189
|
+
var mc = modifiers & CrystVis.MIDDLE_CLICK;
|
|
190
|
+
var rc = modifiers & CrystVis.RIGHT_CLICK;
|
|
191
|
+
|
|
192
|
+
if (lc + mc + rc == 0) {
|
|
193
|
+
throw 'Can not set event without any click type';
|
|
194
|
+
}
|
|
195
|
+
if ((lc && mc) || (lc && rc) || (mc && rc)) {
|
|
196
|
+
throw 'Can not set event with two or more click types';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (callback)
|
|
200
|
+
this._atom_click_events[modifiers] = callback.bind(this);
|
|
201
|
+
else
|
|
202
|
+
this._atom_click_events[modifiers] = this._atom_click_defaults[modifiers];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Set a callback function for an event where a user drags a box around multiple atoms.
|
|
207
|
+
* The function should take as arguments a ModelView including the atoms in the box:
|
|
208
|
+
*
|
|
209
|
+
* function callback(view) {
|
|
210
|
+
* ...
|
|
211
|
+
* }
|
|
212
|
+
*
|
|
213
|
+
* @param {Function} callback Callback function for the event. Passing "null" restores default behaviour
|
|
214
|
+
*/
|
|
215
|
+
onAtomBox(callback = null) {
|
|
216
|
+
if (callback)
|
|
217
|
+
this._atom_box_event = callback;
|
|
218
|
+
else
|
|
219
|
+
this._atom_box_event = this._defaultAtomBox.bind(this);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_defaultAtomLeftClick(atom, event) {
|
|
223
|
+
var i = atom.imgIndex;
|
|
224
|
+
this.selected = new ModelView(this._current_model, [i]);
|
|
225
|
+
}
|
|
226
|
+
_defaultAtomShiftLeftClick(atom, event) {
|
|
227
|
+
var i = atom.imgIndex;
|
|
228
|
+
this.selected = this.selected.or(new ModelView(this._current_model, [i]));
|
|
229
|
+
}
|
|
230
|
+
_defaultAtomCtrlLeftClick(atom, event) {
|
|
231
|
+
var i = atom.imgIndex;
|
|
232
|
+
this.selected = this.selected.xor(new ModelView(this._current_model, [i]));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_defaultAtomBox(view) {
|
|
236
|
+
this.selected = this.selected.xor(view);
|
|
237
|
+
console.log(view);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Callback for when atoms are clicked
|
|
241
|
+
_handleAtomClick(alist, event) {
|
|
242
|
+
|
|
243
|
+
if (alist.length == 0) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let clicked = alist[0].image;
|
|
248
|
+
|
|
249
|
+
let modifiers = [CrystVis.LEFT_CLICK, CrystVis.MIDDLE_CLICK, CrystVis.RIGHT_CLICK][event.button];
|
|
250
|
+
|
|
251
|
+
modifiers += event.shiftKey * CrystVis.SHIFT_BUTTON;
|
|
252
|
+
modifiers += (event.ctrlKey || event.metaKey) * CrystVis.CTRL_BUTTON;
|
|
253
|
+
modifiers += event.altKey * CrystVis.ALT_BUTTON;
|
|
254
|
+
|
|
255
|
+
var callback = this._atom_click_events[modifiers];
|
|
256
|
+
|
|
257
|
+
if (callback)
|
|
258
|
+
callback(clicked, event);
|
|
259
|
+
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Callback for a whole box dragged over atoms
|
|
263
|
+
_handleAtomBox(alist) {
|
|
264
|
+
|
|
265
|
+
var indices = alist.map(function(a) {
|
|
266
|
+
return a.image.imgIndex;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
var callback = this._atom_box_event;
|
|
270
|
+
|
|
271
|
+
if (callback)
|
|
272
|
+
callback(new ModelView(this._current_model, indices));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Center the camera on a given point
|
|
277
|
+
*
|
|
278
|
+
* @param {float[]} center Point in model space that the orbiting camera
|
|
279
|
+
* should be centred on and look at
|
|
280
|
+
* @param {float[]} shift Shift (in units of width/height of the canvas) with
|
|
281
|
+
* which the center of the camera should be rendered with
|
|
282
|
+
* respect to the center of the canvas
|
|
283
|
+
*/
|
|
284
|
+
centerCamera(center = [0, 0, 0], shift = [0, 0]) {
|
|
285
|
+
const renderer = this._renderer;
|
|
286
|
+
|
|
287
|
+
renderer.resetOrbitCenter(center[0], center[1], center[2]);
|
|
288
|
+
renderer.resetCameraCenter(shift[0], shift[1]);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Load one or more atomic models from a file's contents
|
|
293
|
+
*
|
|
294
|
+
* @param {String} contents The contents of the structure file
|
|
295
|
+
* @param {String} format The file's format (cif, xyz, etc.). Default is cif.
|
|
296
|
+
* @param {String} prefix Prefix to use when naming the models. Default is empty.
|
|
297
|
+
* @param {Object} parameters Loading parameters:
|
|
298
|
+
*
|
|
299
|
+
* - `supercell`: supercell size (only used if the structure is periodic)
|
|
300
|
+
* - `molecularCrystal`: if true, try to make the model load completing molecules across periodic boundaries
|
|
301
|
+
* - `useNMRActiveIsotopes`: if true, all isotopes are set by default to the most common one with non-zero spin
|
|
302
|
+
* - `vdwScaling`: scale van der Waals radii by a constant factor
|
|
303
|
+
* - `vdwElementScaling`: table of per-element factors to scale VdW radii by
|
|
304
|
+
*
|
|
305
|
+
* @return {Object} Names of the models we tried to load, and values of true/false for successful loading or not
|
|
306
|
+
*/
|
|
307
|
+
loadModels(contents, format = 'cif', prefix = null, parameters = {}) {
|
|
308
|
+
|
|
309
|
+
parameters = _.merge(model_parameter_defaults, parameters);
|
|
310
|
+
|
|
311
|
+
// By default, it's cif
|
|
312
|
+
format = format.toLowerCase();
|
|
313
|
+
|
|
314
|
+
// By default, same as the format
|
|
315
|
+
prefix = prefix || format;
|
|
316
|
+
|
|
317
|
+
var structs = this._loader.load(contents, format, prefix);
|
|
318
|
+
|
|
319
|
+
var status = {};
|
|
320
|
+
|
|
321
|
+
if (this._loader.status == Loader.STATUS_ERROR) {
|
|
322
|
+
status[prefix] = this._loader.error_message;
|
|
323
|
+
return status;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Now make unique names
|
|
327
|
+
for (var n in structs) {
|
|
328
|
+
var iter = 0;
|
|
329
|
+
var coll = true;
|
|
330
|
+
var nn = n;
|
|
331
|
+
while (coll) {
|
|
332
|
+
nn = n + (iter > 0 ? '_' + iter : '');
|
|
333
|
+
coll = nn in this._models;
|
|
334
|
+
iter++;
|
|
335
|
+
}
|
|
336
|
+
var s = structs[n];
|
|
337
|
+
if (!s) {
|
|
338
|
+
status[nn] = 'Model could not load properly';
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
this._models[nn] = new Model(s, parameters);
|
|
342
|
+
status[nn] = 0; // Success
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return status;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Reload a model, possibly with new parameters
|
|
350
|
+
*
|
|
351
|
+
* @param {String} name Name of the model to reload.
|
|
352
|
+
* @param {Object} parameters Loading parameters as in .loadModels()
|
|
353
|
+
*/
|
|
354
|
+
reloadModel(name, parameters = {}) {
|
|
355
|
+
|
|
356
|
+
if (!(name in this._models)) {
|
|
357
|
+
throw 'The requested model does not exist';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
var current = (this._current_mname == name);
|
|
361
|
+
if (current) {
|
|
362
|
+
// Hide the model to reload it later
|
|
363
|
+
this.displayModel();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
var s = this._models[name]._atoms_base;
|
|
367
|
+
parameters = _.merge(model_parameter_defaults, parameters);
|
|
368
|
+
|
|
369
|
+
this._models[name] = new Model(s, parameters);
|
|
370
|
+
|
|
371
|
+
if (current) {
|
|
372
|
+
this.displayModel(name);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Render a model
|
|
378
|
+
*
|
|
379
|
+
* @param {String} name Name of the model to display. If empty, just
|
|
380
|
+
* clear the renderer window.
|
|
381
|
+
*/
|
|
382
|
+
displayModel(name = null) {
|
|
383
|
+
|
|
384
|
+
if (this._current_model) {
|
|
385
|
+
this.selected = this._current_model.view([]);
|
|
386
|
+
this._current_model.renderer = null;
|
|
387
|
+
this._current_model = null;
|
|
388
|
+
this._current_mname = null;
|
|
389
|
+
}
|
|
390
|
+
this._renderer.clear();
|
|
391
|
+
|
|
392
|
+
if (!name) {
|
|
393
|
+
// If called with nothing, just quit here
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!(name in this._models)) {
|
|
398
|
+
throw 'The requested model does not exist';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
var m = this._models[name];
|
|
402
|
+
m.renderer = this._renderer;
|
|
403
|
+
|
|
404
|
+
this._current_model = m;
|
|
405
|
+
this._current_mname = name;
|
|
406
|
+
|
|
407
|
+
this._displayed = m.find({
|
|
408
|
+
'cell': [
|
|
409
|
+
[0, 0, 0]
|
|
410
|
+
]
|
|
411
|
+
});
|
|
412
|
+
this._selected = new ModelView(m, []); // Empty
|
|
413
|
+
|
|
414
|
+
// Set the camera in a way that will center the model
|
|
415
|
+
var c = m.fracToAbs([0.5, 0.5, 0.5]);
|
|
416
|
+
this._renderer.resetOrbitCenter(c[0], c[1], c[2]);
|
|
417
|
+
|
|
418
|
+
this._displayed.show();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Erase a model from the recorded ones
|
|
423
|
+
*
|
|
424
|
+
* @param {String} name Name of the model to delete
|
|
425
|
+
*/
|
|
426
|
+
deleteModel(name) {
|
|
427
|
+
|
|
428
|
+
if (!(name in this._models)) {
|
|
429
|
+
throw 'The requested model does not exist';
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (this._current_mname == name) {
|
|
433
|
+
this.displayModel();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
delete this._models[name];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Add a primitive shape to the drawing
|
|
441
|
+
*
|
|
442
|
+
* @param {THREE.Object3D} p Primitive to add
|
|
443
|
+
*/
|
|
444
|
+
addPrimitive(p) {
|
|
445
|
+
this._renderer.add(p);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Remove a primitive shape from the drawing
|
|
450
|
+
*
|
|
451
|
+
* @param {THREE.Object3D} p Primitive to remove
|
|
452
|
+
*/
|
|
453
|
+
removePrimitive(p) {
|
|
454
|
+
this._renderer.remove(p);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Recover a data URL of a PNG screenshot of the current scene
|
|
459
|
+
*
|
|
460
|
+
* @return {String} A data URL of the PNG screenshot
|
|
461
|
+
*/
|
|
462
|
+
getScreenshotData() {
|
|
463
|
+
// Force a render
|
|
464
|
+
this._renderer._render();
|
|
465
|
+
// Grab the data from the canvas
|
|
466
|
+
return this._renderer._r.domElement.toDataURL();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
addStaticVar(CrystVis, 'LEFT_CLICK', 1);
|
|
471
|
+
addStaticVar(CrystVis, 'MIDDLE_CLICK', 2);
|
|
472
|
+
addStaticVar(CrystVis, 'RIGHT_CLICK', 4);
|
|
473
|
+
addStaticVar(CrystVis, 'ALT_BUTTON', 8);
|
|
474
|
+
addStaticVar(CrystVis, 'CTRL_BUTTON', 16);
|
|
475
|
+
addStaticVar(CrystVis, 'CMD_BUTTON', 16); // Alias for Mac users
|
|
476
|
+
addStaticVar(CrystVis, 'SHIFT_BUTTON', 32);
|
|
477
|
+
|
|
478
|
+
export {
|
|
479
|
+
CrystVis
|
|
480
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ccp-nc/crystvis-js",
|
|
3
|
+
"version": "0.4.13",
|
|
4
|
+
"description": "A Three.js based crystallographic visualisation tool",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"watch": {
|
|
8
|
+
"build-demo": {
|
|
9
|
+
"patterns": [
|
|
10
|
+
"demo/main.js",
|
|
11
|
+
"lib/*",
|
|
12
|
+
"lib/*/*",
|
|
13
|
+
"lib/*/*/*"
|
|
14
|
+
],
|
|
15
|
+
"extensions": "js,json"
|
|
16
|
+
},
|
|
17
|
+
"build-html-test": {
|
|
18
|
+
"patterns": [
|
|
19
|
+
"test/test-html/index.js",
|
|
20
|
+
"lib/*",
|
|
21
|
+
"lib/*/*",
|
|
22
|
+
"lib/*/*/*"
|
|
23
|
+
],
|
|
24
|
+
"extensions": "js,json"
|
|
25
|
+
},
|
|
26
|
+
"build-resources": {
|
|
27
|
+
"patterns": [
|
|
28
|
+
"lib/assets/fonts/*",
|
|
29
|
+
"lib/shaders/*"
|
|
30
|
+
],
|
|
31
|
+
"extensions": "png,fnt,vert,frag"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "mocha --reporter spec",
|
|
36
|
+
"start": "npm run watch-testserver",
|
|
37
|
+
"clean-docs": "rm -rf docs/* || true",
|
|
38
|
+
"predocs": "npm run clean-docs",
|
|
39
|
+
"docs": "jsdoc -d docs -u docs-tutorials --configure jsconf.json index.js; touch docs/.nojekyll",
|
|
40
|
+
"predeploy-docs": "npm run docs",
|
|
41
|
+
"deploy-docs": "gh-pages -d docs",
|
|
42
|
+
"prepare": "npm run build-resources",
|
|
43
|
+
"build-fonts": "node scripts/build-fonts.js",
|
|
44
|
+
"build-demo": "node scripts/build-bundle.js demo/main.js demo/demo.js",
|
|
45
|
+
"build-resources": "node scripts/build-resources.js",
|
|
46
|
+
"build-html-test": "node scripts/build-bundle.js test/test-html/index.js test/test-html/testbuild.js",
|
|
47
|
+
"watch-testserver": "npx serve & npm-watch build-demo & npm-watch build-html-test & npm-watch build-resources"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/ccp-nc/crystvis-js.git"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"Three.js",
|
|
55
|
+
"crystallography",
|
|
56
|
+
"visualisation",
|
|
57
|
+
"webgl"
|
|
58
|
+
],
|
|
59
|
+
"author": "Simone Sturniolo",
|
|
60
|
+
"contributors": [
|
|
61
|
+
{
|
|
62
|
+
"name": "Kane Shenton",
|
|
63
|
+
"email": "jkshenton@gmail.com"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"license": "MIT",
|
|
67
|
+
"bugs": {
|
|
68
|
+
"url": "https://github.com/ccp-nc/crystvis-js/issues"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/ccp-nc/crystvis-js#readme",
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"@jkshenton/three-bmfont-text": ">=3.0.4",
|
|
73
|
+
"buffer": "^6.0.3",
|
|
74
|
+
"chroma-js": "^2.4.2",
|
|
75
|
+
"crystcif-parse": "^0.2.9",
|
|
76
|
+
"isosurface": "^1.0.0",
|
|
77
|
+
"jquery": "^3.6.0",
|
|
78
|
+
"load-bmfont": "^1.4.1",
|
|
79
|
+
"lodash": "^4.17.21",
|
|
80
|
+
"mathjs": "^8.0.1",
|
|
81
|
+
"three": "^0.143.0",
|
|
82
|
+
"yargs-parser": "^21.1.1"
|
|
83
|
+
},
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@babel/eslint-parser": "^7.18.9",
|
|
86
|
+
"chai": "^4.3.6",
|
|
87
|
+
"chai-almost": "^1.0.1",
|
|
88
|
+
"clean-jsdoc-theme": "^4.1.7",
|
|
89
|
+
"datauri": "^4.1.0",
|
|
90
|
+
"elliptic": ">=6.5.4",
|
|
91
|
+
"esbuild": "^0.15.5",
|
|
92
|
+
"eslint": "^8.22.0",
|
|
93
|
+
"gh-pages": "^4.0.0",
|
|
94
|
+
"glob": "^8.0.3",
|
|
95
|
+
"jpeg-js": ">=0.4.4",
|
|
96
|
+
"jsdoc": "^3.6.11",
|
|
97
|
+
"minimist": "^1.2.6",
|
|
98
|
+
"mocha": "^10.0.0",
|
|
99
|
+
"msdf-bmfont-xml": "^2.5.4",
|
|
100
|
+
"npm-watch": "^0.11.0",
|
|
101
|
+
"serve": "^14.0.1"
|
|
102
|
+
},
|
|
103
|
+
"overrides": {
|
|
104
|
+
"minimist": "$minimist"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import esbuild from 'esbuild';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
esbuild.build({
|
|
8
|
+
entryPoints: [process.argv[2]],
|
|
9
|
+
bundle: true,
|
|
10
|
+
outfile: process.argv[3],
|
|
11
|
+
target: [
|
|
12
|
+
'chrome58',
|
|
13
|
+
'firefox57',
|
|
14
|
+
'safari11',
|
|
15
|
+
'edge18',
|
|
16
|
+
]
|
|
17
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import generateBMFont from 'msdf-bmfont-xml';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import glob from 'glob';
|
|
5
|
+
|
|
6
|
+
// We update by hand whenever we want to build a new font
|
|
7
|
+
const fontList = [
|
|
8
|
+
'fonts/Rubik/static/Rubik-Medium.ttf'
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const savePath = 'lib/assets/fonts';
|
|
12
|
+
|
|
13
|
+
// Clean up the destination path!
|
|
14
|
+
var old_files = glob.sync(path.join(savePath, '*.+(png|fnt)'));
|
|
15
|
+
old_files.forEach((fname, index) => {
|
|
16
|
+
fs.unlinkSync(fname);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
for (var i in fontList) {
|
|
20
|
+
const fname = fontList[i];
|
|
21
|
+
|
|
22
|
+
generateBMFont(fname, (error, textures, font) => {
|
|
23
|
+
|
|
24
|
+
textures.forEach((texture, index) => {
|
|
25
|
+
|
|
26
|
+
var save = path.basename(texture.filename) + '.png';
|
|
27
|
+
save = path.join(savePath, save);
|
|
28
|
+
|
|
29
|
+
fs.writeFile(save, texture.texture, (err) => {
|
|
30
|
+
if (err) throw err;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
var save = path.basename(font.filename);
|
|
36
|
+
save = path.join(savePath, save);
|
|
37
|
+
|
|
38
|
+
fs.writeFile(save, font.data, (err) => {
|
|
39
|
+
if (err) throw err;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import esbuild from 'esbuild';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import {
|
|
4
|
+
fileURLToPath
|
|
5
|
+
} from 'url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(
|
|
8
|
+
import.meta.url));
|
|
9
|
+
|
|
10
|
+
const toBuild = [
|
|
11
|
+
'../lib/assets/fonts/bmpfonts.in.js',
|
|
12
|
+
'../lib/shaders/index.in.js'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
toBuild.forEach((inputFile) => {
|
|
16
|
+
|
|
17
|
+
var outputFile = inputFile.replace('.in.js', '.js');
|
|
18
|
+
|
|
19
|
+
esbuild.build({
|
|
20
|
+
entryPoints: [path.join(__dirname, inputFile)],
|
|
21
|
+
bundle: true,
|
|
22
|
+
platform: 'node',
|
|
23
|
+
format: 'esm',
|
|
24
|
+
outfile: path.join(__dirname, outputFile),
|
|
25
|
+
loader: {
|
|
26
|
+
'.png': 'dataurl',
|
|
27
|
+
'.fnt': 'dataurl',
|
|
28
|
+
'.frag': 'text',
|
|
29
|
+
'.vert': 'text'
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// // A special treatment for fonts.js. Needs some definitions injected
|
|
35
|
+
// esbuild.build({
|
|
36
|
+
// entryPoints: [path.join(__dirname, '../lib/assets/fonts/threebmfont.in.js')],
|
|
37
|
+
// bundle: true,
|
|
38
|
+
// inject: [path.join(__dirname, 'plugins-shim.js')], // Crude, but a necessary fix for load-bmfont
|
|
39
|
+
// define: {
|
|
40
|
+
// Buffer: 'Buffer',
|
|
41
|
+
// THREE: 'THREE',
|
|
42
|
+
// },
|
|
43
|
+
// platform: 'node',
|
|
44
|
+
// outfile: path.join(__dirname, '../lib/assets/fonts/threebmfont.js')
|
|
45
|
+
// });
|
|
46
|
+
|