@judah-silva/rnacanvas 0.0.5 → 0.0.7

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/README.md CHANGED
@@ -129,8 +129,3 @@ const props: MotifProps = {
129
129
  locked: false,
130
130
  };
131
131
  ```
132
-
133
- TALK ABOUT THE DATAMANAGER NEXT, AND THEN THAT SHOULD BE GOOD
134
-
135
- ACTUALLY, I NEED TO TALK ABOUT RMSD AND ITS INTERFACES AND STUFF TOO :(
136
-
package/dist/index.d.mts CHANGED
@@ -112,6 +112,7 @@ declare class Motif extends Group<Residue> {
112
112
  rotateByQuaternion(quat: Quat): void;
113
113
  setQuaternion(quat: Quat): void;
114
114
  multiplyScalar(scalar: number): void;
115
+ setScale(scale: number): void;
115
116
  get uuid(): string;
116
117
  get quat(): Quat;
117
118
  get scale(): number;
@@ -334,7 +335,7 @@ declare class RenderScene {
334
335
  * @author Judah Silva <jusilva@csumb.edu>
335
336
  */
336
337
 
337
- declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
338
+ declare function parseAtomCoords(meshObject: MotifMesh): Promise<Vec3[]>;
338
339
 
339
340
  /**
340
341
  * Copyright (c) 2025 RNA3DS Lab CSUMB.
@@ -342,6 +343,7 @@ declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
342
343
  * @author Judah Silva
343
344
  */
344
345
 
346
+ type MotifMesh = Record<string, any>;
345
347
  /**
346
348
  * ________________________________________________________________________________________________
347
349
  */
@@ -356,7 +358,7 @@ declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
356
358
  * @returns {Promise<Motif>}
357
359
  * @async
358
360
  */
359
- declare function getMotif(motifJSONFileName: string, motifColorHex?: string): Promise<Motif>;
361
+ declare function getMotif(motifName: string, motifMesh: MotifMesh, motifColorHex?: string, includeAtoms?: boolean): Promise<Motif>;
360
362
 
361
363
  /**
362
364
  * Copyright (c) 2025 RNA3DS Lab CSUMB.
@@ -370,7 +372,7 @@ declare function getMotif(motifJSONFileName: string, motifColorHex?: string): Pr
370
372
  * @param nucleotideData {number[][]}
371
373
  * @returns Object of { vertices: number[][], indices: number[][] }
372
374
  */
373
- declare function getPoints(nucleotideData: number[][]): {
375
+ declare function getPoints(nucleotideData: number[][], includeAtoms: boolean): {
374
376
  vertices: number[][];
375
377
  indices: number[][];
376
378
  };
@@ -411,6 +413,7 @@ interface CanvasProps {
411
413
  rendererBackgroundColor?: string;
412
414
  rendererSizeIsWindow?: boolean;
413
415
  cameraPositionZ?: number;
416
+ showRMSD?: boolean;
414
417
  motifProps: MotifProps[];
415
418
  customEventProps?: AnyEventProps[];
416
419
  }
@@ -430,7 +433,7 @@ interface CanvasProps {
430
433
  * @function Canvas {JSX.Element}
431
434
  * @returns {JSX.Element}
432
435
  */
433
- declare function Canvas({ rendererHeight, rendererWidth, rendererBackgroundColor, rendererSizeIsWindow, cameraPositionZ, motifProps, customEventProps, }: CanvasProps): JSX.Element;
436
+ declare function Canvas({ rendererHeight, rendererWidth, rendererBackgroundColor, rendererSizeIsWindow, cameraPositionZ, showRMSD, motifProps, customEventProps, }: CanvasProps): JSX.Element;
434
437
 
435
438
  /**
436
439
  * Copyright (c) 2025 RNA3DS Lab CSUMB.
@@ -521,4 +524,4 @@ declare function calculateRMSDSlide(coordinates1: Vec3[], coordinates2: Vec3[]):
521
524
 
522
525
  declare function rotateAllPoints(atomCoords: Vec3[], quat: Quat): Vec3[];
523
526
 
524
- export { Canvas, CanvasAttributeTypes, CanvasDataManager, type CanvasProps, type CustomEventProps, EventManager, Events, Group, Matrix4, MeshObject, Motif, type MotifProps, Quat, RenderScene, Residue, type ScoreInfo, Vec3, calculateAllKabschRMSD, calculateRMSD, calculateRMSDSlide, getMotif, getPoints, getRMSD, kabschSlidingWindow, rotateAllPoints, updateAllMotifs };
527
+ export { Canvas, CanvasAttributeTypes, CanvasDataManager, type CanvasProps, type CustomEventProps, EventManager, Events, Group, Matrix4, MeshObject, Motif, type MotifMesh, type MotifProps, Quat, RenderScene, Residue, type ScoreInfo, Vec3, calculateAllKabschRMSD, calculateRMSD, calculateRMSDSlide, getMotif, getPoints, getRMSD, kabschSlidingWindow, parseAtomCoords, rotateAllPoints };
package/dist/index.d.ts CHANGED
@@ -112,6 +112,7 @@ declare class Motif extends Group<Residue> {
112
112
  rotateByQuaternion(quat: Quat): void;
113
113
  setQuaternion(quat: Quat): void;
114
114
  multiplyScalar(scalar: number): void;
115
+ setScale(scale: number): void;
115
116
  get uuid(): string;
116
117
  get quat(): Quat;
117
118
  get scale(): number;
@@ -334,7 +335,7 @@ declare class RenderScene {
334
335
  * @author Judah Silva <jusilva@csumb.edu>
335
336
  */
336
337
 
337
- declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
338
+ declare function parseAtomCoords(meshObject: MotifMesh): Promise<Vec3[]>;
338
339
 
339
340
  /**
340
341
  * Copyright (c) 2025 RNA3DS Lab CSUMB.
@@ -342,6 +343,7 @@ declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
342
343
  * @author Judah Silva
343
344
  */
344
345
 
346
+ type MotifMesh = Record<string, any>;
345
347
  /**
346
348
  * ________________________________________________________________________________________________
347
349
  */
@@ -356,7 +358,7 @@ declare function updateAllMotifs(motifMeshArray: Motif[]): Promise<void>;
356
358
  * @returns {Promise<Motif>}
357
359
  * @async
358
360
  */
359
- declare function getMotif(motifJSONFileName: string, motifColorHex?: string): Promise<Motif>;
361
+ declare function getMotif(motifName: string, motifMesh: MotifMesh, motifColorHex?: string, includeAtoms?: boolean): Promise<Motif>;
360
362
 
361
363
  /**
362
364
  * Copyright (c) 2025 RNA3DS Lab CSUMB.
@@ -370,7 +372,7 @@ declare function getMotif(motifJSONFileName: string, motifColorHex?: string): Pr
370
372
  * @param nucleotideData {number[][]}
371
373
  * @returns Object of { vertices: number[][], indices: number[][] }
372
374
  */
373
- declare function getPoints(nucleotideData: number[][]): {
375
+ declare function getPoints(nucleotideData: number[][], includeAtoms: boolean): {
374
376
  vertices: number[][];
375
377
  indices: number[][];
376
378
  };
@@ -411,6 +413,7 @@ interface CanvasProps {
411
413
  rendererBackgroundColor?: string;
412
414
  rendererSizeIsWindow?: boolean;
413
415
  cameraPositionZ?: number;
416
+ showRMSD?: boolean;
414
417
  motifProps: MotifProps[];
415
418
  customEventProps?: AnyEventProps[];
416
419
  }
@@ -430,7 +433,7 @@ interface CanvasProps {
430
433
  * @function Canvas {JSX.Element}
431
434
  * @returns {JSX.Element}
432
435
  */
433
- declare function Canvas({ rendererHeight, rendererWidth, rendererBackgroundColor, rendererSizeIsWindow, cameraPositionZ, motifProps, customEventProps, }: CanvasProps): JSX.Element;
436
+ declare function Canvas({ rendererHeight, rendererWidth, rendererBackgroundColor, rendererSizeIsWindow, cameraPositionZ, showRMSD, motifProps, customEventProps, }: CanvasProps): JSX.Element;
434
437
 
435
438
  /**
436
439
  * Copyright (c) 2025 RNA3DS Lab CSUMB.
@@ -521,4 +524,4 @@ declare function calculateRMSDSlide(coordinates1: Vec3[], coordinates2: Vec3[]):
521
524
 
522
525
  declare function rotateAllPoints(atomCoords: Vec3[], quat: Quat): Vec3[];
523
526
 
524
- export { Canvas, CanvasAttributeTypes, CanvasDataManager, type CanvasProps, type CustomEventProps, EventManager, Events, Group, Matrix4, MeshObject, Motif, type MotifProps, Quat, RenderScene, Residue, type ScoreInfo, Vec3, calculateAllKabschRMSD, calculateRMSD, calculateRMSDSlide, getMotif, getPoints, getRMSD, kabschSlidingWindow, rotateAllPoints, updateAllMotifs };
527
+ export { Canvas, CanvasAttributeTypes, CanvasDataManager, type CanvasProps, type CustomEventProps, EventManager, Events, Group, Matrix4, MeshObject, Motif, type MotifMesh, type MotifProps, Quat, RenderScene, Residue, type ScoreInfo, Vec3, calculateAllKabschRMSD, calculateRMSD, calculateRMSDSlide, getMotif, getPoints, getRMSD, kabschSlidingWindow, parseAtomCoords, rotateAllPoints };
package/dist/index.js CHANGED
@@ -50,8 +50,8 @@ __export(index_exports, {
50
50
  getPoints: () => getPoints,
51
51
  getRMSD: () => getRMSD,
52
52
  kabschSlidingWindow: () => kabschSlidingWindow,
53
- rotateAllPoints: () => rotateAllPoints,
54
- updateAllMotifs: () => updateAllMotifs
53
+ parseAtomCoords: () => parseAtomCoords,
54
+ rotateAllPoints: () => rotateAllPoints
55
55
  });
56
56
  module.exports = __toCommonJS(index_exports);
57
57
 
@@ -269,6 +269,13 @@ var Motif = class extends Group {
269
269
  this._node.scaling.z * scalar
270
270
  );
271
271
  }
272
+ setScale(scale) {
273
+ this._node.scaling = new import_core5.Vector3(
274
+ scale,
275
+ scale,
276
+ scale
277
+ );
278
+ }
272
279
  get uuid() {
273
280
  return this._node.uniqueId.toString();
274
281
  }
@@ -813,12 +820,10 @@ var RenderScene = class {
813
820
  };
814
821
 
815
822
  // src/3D/utils/GetAtomInfo.ts
816
- async function parseAtomCoords(fileName) {
817
- const response = await fetch(`/${fileName}`);
818
- const jsonData = await response.json();
823
+ async function parseAtomCoords(meshObject) {
819
824
  const coordinates = [];
820
- for (const [key] of Object.entries(jsonData)) {
821
- const atomMap = jsonData[key][0];
825
+ for (const [key] of Object.entries(meshObject)) {
826
+ const atomMap = meshObject[key][0];
822
827
  if (atomMap[`"C1'"`]) {
823
828
  coordinates.push(
824
829
  new Vec3(
@@ -831,26 +836,12 @@ async function parseAtomCoords(fileName) {
831
836
  }
832
837
  return coordinates;
833
838
  }
834
- async function getAtomCoords(fileName) {
835
- const coordinates = await parseAtomCoords(fileName);
836
- return coordinates;
837
- }
838
- async function updateAllMotifs(motifMeshArray) {
839
- const fileNames = [];
840
- for (const motif of motifMeshArray) {
841
- fileNames.push(motif.userData.fileName);
842
- }
843
- const atomInfoLists = await Promise.all(fileNames.map((o) => getAtomCoords(o)));
844
- for (let i = 0; i < motifMeshArray.length; i += 1) {
845
- motifMeshArray[i].userData.atomInfo = atomInfoLists[i];
846
- }
847
- }
848
839
 
849
840
  // src/3D/utils/GetPoints.ts
850
- function getPoints(nucleotideData) {
841
+ function getPoints(nucleotideData, includeAtoms) {
851
842
  const vertices = [];
852
843
  const indices = [];
853
- for (let i = 1; i < nucleotideData.length; i += 2) {
844
+ for (let i = includeAtoms ? 1 : 0; i < nucleotideData.length; i += 2) {
854
845
  vertices.push(nucleotideData[i]);
855
846
  indices.push(nucleotideData[i + 1]);
856
847
  }
@@ -858,12 +849,10 @@ function getPoints(nucleotideData) {
858
849
  }
859
850
 
860
851
  // src/3D/utils/GetMotif.ts
861
- async function getMotif(motifJSONFileName, motifColorHex = "0xcc2900") {
862
- const motif = new Motif(`${motifJSONFileName}_motif`);
863
- const motifJSONFileData = await fetch(`/${motifJSONFileName}`);
864
- const jsonObject = await motifJSONFileData.json();
865
- for (const [key] of Object.entries(jsonObject)) {
866
- const { vertices, indices } = getPoints(jsonObject[key]);
852
+ async function getMotif(motifName, motifMesh, motifColorHex = "0xcc2900", includeAtoms = true) {
853
+ const motif = new Motif(`${motifName}_motif`);
854
+ for (const [key] of Object.entries(motifMesh)) {
855
+ const { vertices, indices } = getPoints(motifMesh[key], includeAtoms);
867
856
  const residue = new Residue("residue");
868
857
  const backboneMesh = new MeshObject(`backbone_${key}`);
869
858
  backboneMesh.applyVertexData(vertices[0], indices[0]);
@@ -875,7 +864,8 @@ async function getMotif(motifJSONFileName, motifColorHex = "0xcc2900") {
875
864
  residue.addChild(ringMesh);
876
865
  motif.addChild(residue);
877
866
  }
878
- motif.userData.fileName = motifJSONFileName;
867
+ motif.userData.fileName = motifName;
868
+ if (includeAtoms) motif.userData.atomInfo = await parseAtomCoords(motifMesh);
879
869
  return motif;
880
870
  }
881
871
 
@@ -970,6 +960,7 @@ function getRMSD(a, b) {
970
960
  if (a instanceof Motif && b instanceof Motif) {
971
961
  newCoords1 = rotateAllPoints(a.userData.atomInfo, a.quat);
972
962
  newCoords2 = rotateAllPoints(b.userData.atomInfo, b.quat);
963
+ return calculateRMSDSlide(newCoords1, newCoords2);
973
964
  } else {
974
965
  return -1;
975
966
  }
@@ -1143,6 +1134,7 @@ function Canvas({
1143
1134
  rendererBackgroundColor = "#040a20",
1144
1135
  rendererSizeIsWindow = false,
1145
1136
  cameraPositionZ = 1e3,
1137
+ showRMSD = true,
1146
1138
  motifProps,
1147
1139
  customEventProps
1148
1140
  }) {
@@ -1179,7 +1171,6 @@ function Canvas({
1179
1171
  if (!selectedMotifMeshState.current.has(motif)) {
1180
1172
  return;
1181
1173
  }
1182
- console.log("removing motif: ", motif);
1183
1174
  selectedMotifMeshState.current.delete(motif);
1184
1175
  setSelectedmotifIds((prevState) => {
1185
1176
  const newState = new Set(prevState);
@@ -1254,7 +1245,7 @@ function Canvas({
1254
1245
  element.rotate(axisVec, angle);
1255
1246
  }
1256
1247
  });
1257
- setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1248
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1258
1249
  }
1259
1250
  }
1260
1251
  }
@@ -1267,10 +1258,12 @@ function Canvas({
1267
1258
  const zoomSpeed = 0.1;
1268
1259
  const zoomDirection = event.originalEvent.deltaY > 0 ? -1 : 1;
1269
1260
  selectedMotifMeshState.current.forEach((element) => {
1270
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1261
+ if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
1271
1262
  const scaleFactor = 1 + zoomDirection * zoomSpeed;
1272
1263
  element.multiplyScalar(scaleFactor);
1273
1264
  }
1265
+ if (element.scale < 1) element.setScale(1);
1266
+ if (element.scale > 30) element.setScale(30);
1274
1267
  });
1275
1268
  }
1276
1269
  }
@@ -1297,7 +1290,7 @@ function Canvas({
1297
1290
  element.rotate(event.rotationAxis, angle);
1298
1291
  }
1299
1292
  });
1300
- setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1293
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1301
1294
  }
1302
1295
  function onKeyboardTranslate(event) {
1303
1296
  if (event.translationDirection.equals(Vec3.Zero)) {
@@ -1323,10 +1316,8 @@ function Canvas({
1323
1316
  }
1324
1317
  const motif = motifs[Number(event.key) - 1];
1325
1318
  if (selectedMotifMeshState.current.has(motif)) {
1326
- console.log("removing motif");
1327
1319
  removeMotif(motif);
1328
1320
  } else {
1329
- console.log("adding motif");
1330
1321
  addMotif(motif);
1331
1322
  }
1332
1323
  }
@@ -1345,6 +1336,21 @@ function Canvas({
1345
1336
  }
1346
1337
  return positions;
1347
1338
  };
1339
+ const updateMotifs = () => {
1340
+ const positions = calculatePositions(motifs.length);
1341
+ motifs.forEach((motifMesh, index) => {
1342
+ if (!scene.current?.children.has(motifMesh.uuid)) return;
1343
+ if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1344
+ motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
1345
+ if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
1346
+ let scale = canvasRef.current.width / 250;
1347
+ if (motifProps[index].scale) scale = motifProps[index].scale;
1348
+ motifMesh.setScale(scale);
1349
+ });
1350
+ };
1351
+ (0, import_react.useEffect)(() => {
1352
+ updateMotifs();
1353
+ }, [motifProps]);
1348
1354
  (0, import_react.useEffect)(() => {
1349
1355
  const unsubscribe = CanvasDataManager.subscribe("selectedMotifs" /* SELECTED_MOTIFS */, () => {
1350
1356
  setSelectedmotifIds(CanvasDataManager.selectedMotifIds);
@@ -1362,7 +1368,7 @@ function Canvas({
1362
1368
  }
1363
1369
  });
1364
1370
  updateGlow();
1365
- setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1371
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1366
1372
  }, [selectedMotifIds]);
1367
1373
  (0, import_react.useEffect)(() => {
1368
1374
  const unsubscribe = CanvasDataManager.subscribe("lockedMotifIds" /* LOCKED_MOTIF_IDS */, () => {
@@ -1380,25 +1386,31 @@ function Canvas({
1380
1386
  hardLockedMotifIds = CanvasDataManager.hardLockedMotifIds;
1381
1387
  }, [CanvasDataManager.hardLockedMotifIds]);
1382
1388
  (0, import_react.useEffect)(() => {
1389
+ if (!showRMSD) return;
1383
1390
  const unsubscribe = CanvasDataManager.subscribe("scoreRMSD" /* SCORE_RMSD */, () => {
1384
1391
  setScoreRMSD(CanvasDataManager.scoreRMSD);
1385
1392
  });
1386
1393
  return () => unsubscribe();
1387
1394
  }, []);
1388
1395
  (0, import_react.useEffect)(() => {
1396
+ if (!showRMSD) return;
1389
1397
  if (CanvasDataManager.scoreRMSD !== scoreRMSD) {
1390
1398
  CanvasDataManager.setScoreRMSD(scoreRMSD);
1391
1399
  }
1392
1400
  }, [scoreRMSD]);
1393
1401
  (0, import_react.useEffect)(() => {
1402
+ if (!showRMSD) return;
1394
1403
  const unsubscribe = CanvasDataManager.subscribe("kabschRMSD" /* KABSCH_RMSD */, () => {
1395
1404
  setKabschRMSD(CanvasDataManager.kabschRMSD);
1396
1405
  });
1397
1406
  return () => unsubscribe();
1398
1407
  }, []);
1399
1408
  (0, import_react.useEffect)(() => {
1409
+ if (!showRMSD) return;
1400
1410
  if (CanvasDataManager.kabschRMSD !== kabschRMSD) {
1401
1411
  CanvasDataManager.setKabschRMSD(kabschRMSD);
1412
+ } else if (CanvasDataManager.kabschRMSD.length !== motifs.length) {
1413
+ CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs));
1402
1414
  }
1403
1415
  }, [kabschRMSD]);
1404
1416
  (0, import_react.useEffect)(() => {
@@ -1418,12 +1430,10 @@ function Canvas({
1418
1430
  rendererSizeIsWindow ? window.innerHeight : rendererHeight
1419
1431
  );
1420
1432
  }
1421
- const positions = calculatePositions(motifs.length);
1422
1433
  if (motifs.length > 0) {
1423
- updateAllMotifs(motifs).then(() => {
1424
- setKabschRMSD(calculateAllKabschRMSD(motifs));
1425
- });
1434
+ if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs));
1426
1435
  if (scene.current.children.size !== motifs.length) {
1436
+ const positions = calculatePositions(motifs.length);
1427
1437
  motifs.forEach((motifMesh, index) => {
1428
1438
  scene.current?.add(motifMesh);
1429
1439
  if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
@@ -1494,10 +1504,6 @@ function Canvas({
1494
1504
  }
1495
1505
  });
1496
1506
  }
1497
- } else {
1498
- motifs.forEach((motifMesh) => {
1499
- scene.current?.add(motifMesh);
1500
- });
1501
1507
  }
1502
1508
  }
1503
1509
  scene.current?.start();
@@ -1526,6 +1532,6 @@ function Canvas({
1526
1532
  getPoints,
1527
1533
  getRMSD,
1528
1534
  kabschSlidingWindow,
1529
- rotateAllPoints,
1530
- updateAllMotifs
1535
+ parseAtomCoords,
1536
+ rotateAllPoints
1531
1537
  });
package/dist/index.mjs CHANGED
@@ -212,6 +212,13 @@ var Motif = class extends Group {
212
212
  this._node.scaling.z * scalar
213
213
  );
214
214
  }
215
+ setScale(scale) {
216
+ this._node.scaling = new Vector32(
217
+ scale,
218
+ scale,
219
+ scale
220
+ );
221
+ }
215
222
  get uuid() {
216
223
  return this._node.uniqueId.toString();
217
224
  }
@@ -756,12 +763,10 @@ var RenderScene = class {
756
763
  };
757
764
 
758
765
  // src/3D/utils/GetAtomInfo.ts
759
- async function parseAtomCoords(fileName) {
760
- const response = await fetch(`/${fileName}`);
761
- const jsonData = await response.json();
766
+ async function parseAtomCoords(meshObject) {
762
767
  const coordinates = [];
763
- for (const [key] of Object.entries(jsonData)) {
764
- const atomMap = jsonData[key][0];
768
+ for (const [key] of Object.entries(meshObject)) {
769
+ const atomMap = meshObject[key][0];
765
770
  if (atomMap[`"C1'"`]) {
766
771
  coordinates.push(
767
772
  new Vec3(
@@ -774,26 +779,12 @@ async function parseAtomCoords(fileName) {
774
779
  }
775
780
  return coordinates;
776
781
  }
777
- async function getAtomCoords(fileName) {
778
- const coordinates = await parseAtomCoords(fileName);
779
- return coordinates;
780
- }
781
- async function updateAllMotifs(motifMeshArray) {
782
- const fileNames = [];
783
- for (const motif of motifMeshArray) {
784
- fileNames.push(motif.userData.fileName);
785
- }
786
- const atomInfoLists = await Promise.all(fileNames.map((o) => getAtomCoords(o)));
787
- for (let i = 0; i < motifMeshArray.length; i += 1) {
788
- motifMeshArray[i].userData.atomInfo = atomInfoLists[i];
789
- }
790
- }
791
782
 
792
783
  // src/3D/utils/GetPoints.ts
793
- function getPoints(nucleotideData) {
784
+ function getPoints(nucleotideData, includeAtoms) {
794
785
  const vertices = [];
795
786
  const indices = [];
796
- for (let i = 1; i < nucleotideData.length; i += 2) {
787
+ for (let i = includeAtoms ? 1 : 0; i < nucleotideData.length; i += 2) {
797
788
  vertices.push(nucleotideData[i]);
798
789
  indices.push(nucleotideData[i + 1]);
799
790
  }
@@ -801,12 +792,10 @@ function getPoints(nucleotideData) {
801
792
  }
802
793
 
803
794
  // src/3D/utils/GetMotif.ts
804
- async function getMotif(motifJSONFileName, motifColorHex = "0xcc2900") {
805
- const motif = new Motif(`${motifJSONFileName}_motif`);
806
- const motifJSONFileData = await fetch(`/${motifJSONFileName}`);
807
- const jsonObject = await motifJSONFileData.json();
808
- for (const [key] of Object.entries(jsonObject)) {
809
- const { vertices, indices } = getPoints(jsonObject[key]);
795
+ async function getMotif(motifName, motifMesh, motifColorHex = "0xcc2900", includeAtoms = true) {
796
+ const motif = new Motif(`${motifName}_motif`);
797
+ for (const [key] of Object.entries(motifMesh)) {
798
+ const { vertices, indices } = getPoints(motifMesh[key], includeAtoms);
810
799
  const residue = new Residue("residue");
811
800
  const backboneMesh = new MeshObject(`backbone_${key}`);
812
801
  backboneMesh.applyVertexData(vertices[0], indices[0]);
@@ -818,7 +807,8 @@ async function getMotif(motifJSONFileName, motifColorHex = "0xcc2900") {
818
807
  residue.addChild(ringMesh);
819
808
  motif.addChild(residue);
820
809
  }
821
- motif.userData.fileName = motifJSONFileName;
810
+ motif.userData.fileName = motifName;
811
+ if (includeAtoms) motif.userData.atomInfo = await parseAtomCoords(motifMesh);
822
812
  return motif;
823
813
  }
824
814
 
@@ -913,6 +903,7 @@ function getRMSD(a, b) {
913
903
  if (a instanceof Motif && b instanceof Motif) {
914
904
  newCoords1 = rotateAllPoints(a.userData.atomInfo, a.quat);
915
905
  newCoords2 = rotateAllPoints(b.userData.atomInfo, b.quat);
906
+ return calculateRMSDSlide(newCoords1, newCoords2);
916
907
  } else {
917
908
  return -1;
918
909
  }
@@ -1086,6 +1077,7 @@ function Canvas({
1086
1077
  rendererBackgroundColor = "#040a20",
1087
1078
  rendererSizeIsWindow = false,
1088
1079
  cameraPositionZ = 1e3,
1080
+ showRMSD = true,
1089
1081
  motifProps,
1090
1082
  customEventProps
1091
1083
  }) {
@@ -1122,7 +1114,6 @@ function Canvas({
1122
1114
  if (!selectedMotifMeshState.current.has(motif)) {
1123
1115
  return;
1124
1116
  }
1125
- console.log("removing motif: ", motif);
1126
1117
  selectedMotifMeshState.current.delete(motif);
1127
1118
  setSelectedmotifIds((prevState) => {
1128
1119
  const newState = new Set(prevState);
@@ -1197,7 +1188,7 @@ function Canvas({
1197
1188
  element.rotate(axisVec, angle);
1198
1189
  }
1199
1190
  });
1200
- setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1191
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1201
1192
  }
1202
1193
  }
1203
1194
  }
@@ -1210,10 +1201,12 @@ function Canvas({
1210
1201
  const zoomSpeed = 0.1;
1211
1202
  const zoomDirection = event.originalEvent.deltaY > 0 ? -1 : 1;
1212
1203
  selectedMotifMeshState.current.forEach((element) => {
1213
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1204
+ if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
1214
1205
  const scaleFactor = 1 + zoomDirection * zoomSpeed;
1215
1206
  element.multiplyScalar(scaleFactor);
1216
1207
  }
1208
+ if (element.scale < 1) element.setScale(1);
1209
+ if (element.scale > 30) element.setScale(30);
1217
1210
  });
1218
1211
  }
1219
1212
  }
@@ -1240,7 +1233,7 @@ function Canvas({
1240
1233
  element.rotate(event.rotationAxis, angle);
1241
1234
  }
1242
1235
  });
1243
- setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1236
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1244
1237
  }
1245
1238
  function onKeyboardTranslate(event) {
1246
1239
  if (event.translationDirection.equals(Vec3.Zero)) {
@@ -1266,10 +1259,8 @@ function Canvas({
1266
1259
  }
1267
1260
  const motif = motifs[Number(event.key) - 1];
1268
1261
  if (selectedMotifMeshState.current.has(motif)) {
1269
- console.log("removing motif");
1270
1262
  removeMotif(motif);
1271
1263
  } else {
1272
- console.log("adding motif");
1273
1264
  addMotif(motif);
1274
1265
  }
1275
1266
  }
@@ -1288,6 +1279,21 @@ function Canvas({
1288
1279
  }
1289
1280
  return positions;
1290
1281
  };
1282
+ const updateMotifs = () => {
1283
+ const positions = calculatePositions(motifs.length);
1284
+ motifs.forEach((motifMesh, index) => {
1285
+ if (!scene.current?.children.has(motifMesh.uuid)) return;
1286
+ if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1287
+ motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
1288
+ if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
1289
+ let scale = canvasRef.current.width / 250;
1290
+ if (motifProps[index].scale) scale = motifProps[index].scale;
1291
+ motifMesh.setScale(scale);
1292
+ });
1293
+ };
1294
+ useEffect(() => {
1295
+ updateMotifs();
1296
+ }, [motifProps]);
1291
1297
  useEffect(() => {
1292
1298
  const unsubscribe = CanvasDataManager.subscribe("selectedMotifs" /* SELECTED_MOTIFS */, () => {
1293
1299
  setSelectedmotifIds(CanvasDataManager.selectedMotifIds);
@@ -1305,7 +1311,7 @@ function Canvas({
1305
1311
  }
1306
1312
  });
1307
1313
  updateGlow();
1308
- setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1314
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1309
1315
  }, [selectedMotifIds]);
1310
1316
  useEffect(() => {
1311
1317
  const unsubscribe = CanvasDataManager.subscribe("lockedMotifIds" /* LOCKED_MOTIF_IDS */, () => {
@@ -1323,25 +1329,31 @@ function Canvas({
1323
1329
  hardLockedMotifIds = CanvasDataManager.hardLockedMotifIds;
1324
1330
  }, [CanvasDataManager.hardLockedMotifIds]);
1325
1331
  useEffect(() => {
1332
+ if (!showRMSD) return;
1326
1333
  const unsubscribe = CanvasDataManager.subscribe("scoreRMSD" /* SCORE_RMSD */, () => {
1327
1334
  setScoreRMSD(CanvasDataManager.scoreRMSD);
1328
1335
  });
1329
1336
  return () => unsubscribe();
1330
1337
  }, []);
1331
1338
  useEffect(() => {
1339
+ if (!showRMSD) return;
1332
1340
  if (CanvasDataManager.scoreRMSD !== scoreRMSD) {
1333
1341
  CanvasDataManager.setScoreRMSD(scoreRMSD);
1334
1342
  }
1335
1343
  }, [scoreRMSD]);
1336
1344
  useEffect(() => {
1345
+ if (!showRMSD) return;
1337
1346
  const unsubscribe = CanvasDataManager.subscribe("kabschRMSD" /* KABSCH_RMSD */, () => {
1338
1347
  setKabschRMSD(CanvasDataManager.kabschRMSD);
1339
1348
  });
1340
1349
  return () => unsubscribe();
1341
1350
  }, []);
1342
1351
  useEffect(() => {
1352
+ if (!showRMSD) return;
1343
1353
  if (CanvasDataManager.kabschRMSD !== kabschRMSD) {
1344
1354
  CanvasDataManager.setKabschRMSD(kabschRMSD);
1355
+ } else if (CanvasDataManager.kabschRMSD.length !== motifs.length) {
1356
+ CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs));
1345
1357
  }
1346
1358
  }, [kabschRMSD]);
1347
1359
  useEffect(() => {
@@ -1361,12 +1373,10 @@ function Canvas({
1361
1373
  rendererSizeIsWindow ? window.innerHeight : rendererHeight
1362
1374
  );
1363
1375
  }
1364
- const positions = calculatePositions(motifs.length);
1365
1376
  if (motifs.length > 0) {
1366
- updateAllMotifs(motifs).then(() => {
1367
- setKabschRMSD(calculateAllKabschRMSD(motifs));
1368
- });
1377
+ if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs));
1369
1378
  if (scene.current.children.size !== motifs.length) {
1379
+ const positions = calculatePositions(motifs.length);
1370
1380
  motifs.forEach((motifMesh, index) => {
1371
1381
  scene.current?.add(motifMesh);
1372
1382
  if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
@@ -1437,10 +1447,6 @@ function Canvas({
1437
1447
  }
1438
1448
  });
1439
1449
  }
1440
- } else {
1441
- motifs.forEach((motifMesh) => {
1442
- scene.current?.add(motifMesh);
1443
- });
1444
1450
  }
1445
1451
  }
1446
1452
  scene.current?.start();
@@ -1468,6 +1474,6 @@ export {
1468
1474
  getPoints,
1469
1475
  getRMSD,
1470
1476
  kabschSlidingWindow,
1471
- rotateAllPoints,
1472
- updateAllMotifs
1477
+ parseAtomCoords,
1478
+ rotateAllPoints
1473
1479
  };
Binary file
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@judah-silva/rnacanvas",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "A 3D Canvas for displaying and interacting with custom RNA models. Powered by BabylonJS.",
5
+ "license": "MIT",
5
6
  "main": "./dist/index.js",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/RNAStructureAnalysisLab/RNACanvas"
10
+ },
6
11
  "module": "./dist/index.mjs",
7
12
  "scripts": {
8
13
  "build": "tsup"
@@ -14,7 +19,6 @@
14
19
  "test"
15
20
  ],
16
21
  "author": "Judah Silva",
17
- "license": "MIT",
18
22
  "dependencies": {
19
23
  "@babylonjs/core": "^7.50.0",
20
24
  "numeric": "^1.2.6",