@motionscript/molecule 0.0.0-stage → 0.1.0-alpha.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +201 -0
  3. package/dist/browser/index.js +4 -0
  4. package/dist/browser/index.js.map +7 -0
  5. package/dist/browser/manifest.json +11 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/nodes.d.ts +18 -0
  11. package/dist/nodes.d.ts.map +1 -0
  12. package/dist/nodes.js +18 -0
  13. package/dist/nodes.js.map +1 -0
  14. package/dist/protein/chemistry.d.ts +52 -0
  15. package/dist/protein/chemistry.d.ts.map +1 -0
  16. package/dist/protein/chemistry.js +208 -0
  17. package/dist/protein/chemistry.js.map +1 -0
  18. package/dist/protein/index.d.ts +35 -0
  19. package/dist/protein/index.d.ts.map +1 -0
  20. package/dist/protein/index.js +35 -0
  21. package/dist/protein/index.js.map +1 -0
  22. package/dist/protein/parse.d.ts +32 -0
  23. package/dist/protein/parse.d.ts.map +1 -0
  24. package/dist/protein/parse.js +387 -0
  25. package/dist/protein/parse.js.map +1 -0
  26. package/dist/protein/protein.d.ts +265 -0
  27. package/dist/protein/protein.d.ts.map +1 -0
  28. package/dist/protein/protein.js +645 -0
  29. package/dist/protein/protein.js.map +1 -0
  30. package/dist/protein/ribbon.d.ts +83 -0
  31. package/dist/protein/ribbon.d.ts.map +1 -0
  32. package/dist/protein/ribbon.js +468 -0
  33. package/dist/protein/ribbon.js.map +1 -0
  34. package/dist/protein/shared.d.ts +221 -0
  35. package/dist/protein/shared.d.ts.map +1 -0
  36. package/dist/protein/shared.js +478 -0
  37. package/dist/protein/shared.js.map +1 -0
  38. package/dist/protein/structure.d.ts +184 -0
  39. package/dist/protein/structure.d.ts.map +1 -0
  40. package/dist/protein/structure.js +324 -0
  41. package/dist/protein/structure.js.map +1 -0
  42. package/package.json +64 -3
  43. package/registry.json +22 -0
  44. package/src/index.ts +2 -0
  45. package/src/nodes.ts +18 -0
  46. package/src/protein/chemistry.ts +223 -0
  47. package/src/protein/index.ts +34 -0
  48. package/src/protein/parse.ts +427 -0
  49. package/src/protein/protein.ts +897 -0
  50. package/src/protein/ribbon.ts +658 -0
  51. package/src/protein/shared.ts +622 -0
  52. package/src/protein/structure.ts +491 -0
  53. package/README.md +0 -4
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./protein";
2
+ export { NODES } from "./nodes";
package/src/nodes.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { Protein } from "./protein";
2
+
3
+ /**
4
+ * Every node type this package publishes.
5
+ *
6
+ * A host registers these by handing the array to an engine —
7
+ * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
8
+ * key. The decorator only *declares* that key: nothing is registered by the mere
9
+ * act of importing a module, so a registry holds exactly what its owner asked
10
+ * for and two engines in one process can differ about it.
11
+ *
12
+ * Listing class **values** is also what survives bundling. This package declares
13
+ * `sideEffects: false`, which licenses a bundler to drop a module nothing
14
+ * imports a value from, and a document names a node type by string — so
15
+ * `import "./protein"` for its side effect is exactly what gets shaken out, where an
16
+ * array of bindings is a data dependency that cannot be.
17
+ */
18
+ export const NODES = [Protein];
@@ -0,0 +1,223 @@
1
+ /**
2
+ * The chemistry the {@link Protein} node draws from: how big an atom is, what
3
+ * colour it conventionally takes, what counts as a bond, and which residues are
4
+ * part of a polymer chain rather than sitting beside it.
5
+ *
6
+ * Tables rather than a dependency, for the same reason `graph3d/expression.ts`
7
+ * is a parser rather than a maths library: this package has to stay loadable in
8
+ * a bare Node process with no DOM, and what a molecular-graphics library would
9
+ * bring with it — a renderer, a fetch layer, a DOM — is everything the scene
10
+ * builder was split apart to avoid.
11
+ *
12
+ * The numbers are the standard ones. Van der Waals radii are Bondi's, covalent
13
+ * radii are Cordero's, and the colours are the CPK scheme every molecular viewer
14
+ * has used since Corey and Pauling built theirs out of painted wood — which is
15
+ * the point of using them: a biologist reads oxygen as red before they have
16
+ * read the legend.
17
+ */
18
+
19
+ /** Ångströms. What an atom's electron cloud actually occupies. */
20
+ const VAN_DER_WAALS: Readonly<Record<string, number>> = {
21
+ H: 1.1,
22
+ C: 1.7,
23
+ N: 1.55,
24
+ O: 1.52,
25
+ F: 1.47,
26
+ NA: 2.27,
27
+ MG: 1.73,
28
+ P: 1.8,
29
+ S: 1.8,
30
+ CL: 1.75,
31
+ K: 2.75,
32
+ CA: 2.31,
33
+ MN: 2.05,
34
+ FE: 2.04,
35
+ CO: 2.0,
36
+ NI: 1.97,
37
+ CU: 1.96,
38
+ ZN: 2.01,
39
+ SE: 1.9,
40
+ BR: 1.85,
41
+ I: 1.98,
42
+ }
43
+
44
+ /** The fallback radius, carbon's — the element most of a protein is made of. */
45
+ const DEFAULT_VAN_DER_WAALS = 1.7
46
+
47
+ /** Ångströms. Half of how far two of these sit apart when bonded. */
48
+ const COVALENT: Readonly<Record<string, number>> = {
49
+ H: 0.31,
50
+ C: 0.76,
51
+ N: 0.71,
52
+ O: 0.66,
53
+ F: 0.57,
54
+ NA: 1.66,
55
+ MG: 1.41,
56
+ P: 1.07,
57
+ S: 1.05,
58
+ CL: 1.02,
59
+ K: 2.03,
60
+ CA: 1.76,
61
+ MN: 1.39,
62
+ FE: 1.32,
63
+ CO: 1.26,
64
+ NI: 1.24,
65
+ CU: 1.32,
66
+ ZN: 1.22,
67
+ SE: 1.2,
68
+ BR: 1.2,
69
+ I: 1.39,
70
+ }
71
+
72
+ const DEFAULT_COVALENT = 0.77
73
+
74
+ /**
75
+ * The CPK colours, as Jmol renders them.
76
+ *
77
+ * Only the elements a biological structure actually contains, plus the metals
78
+ * that turn up in cofactors. Anything else takes {@link UNKNOWN_ELEMENT_COLOR},
79
+ * which is deliberately garish — an unrecognised element should look like a
80
+ * question rather than blend into the carbons.
81
+ */
82
+ const CPK: Readonly<Record<string, string>> = {
83
+ H: "#ffffff",
84
+ C: "#909090",
85
+ N: "#3050f8",
86
+ O: "#ff0d0d",
87
+ F: "#90e050",
88
+ NA: "#ab5cf2",
89
+ MG: "#8aff00",
90
+ P: "#ff8000",
91
+ S: "#ffff30",
92
+ CL: "#1ff01f",
93
+ K: "#8f40d4",
94
+ CA: "#3dff00",
95
+ MN: "#9c7ac7",
96
+ FE: "#e06633",
97
+ CO: "#f090a0",
98
+ NI: "#50d050",
99
+ CU: "#c88033",
100
+ ZN: "#7d80b0",
101
+ SE: "#ffa100",
102
+ BR: "#a62929",
103
+ I: "#940094",
104
+ }
105
+
106
+ /** What an element nobody has a colour for is drawn in. */
107
+ export const UNKNOWN_ELEMENT_COLOR = "#ff1493"
108
+
109
+ /** An element's van der Waals radius in Ångströms. */
110
+ export function vanDerWaalsRadius(element: string): number {
111
+ return VAN_DER_WAALS[element] ?? DEFAULT_VAN_DER_WAALS
112
+ }
113
+
114
+ /** An element's covalent radius in Ångströms — half of a bond it makes. */
115
+ export function covalentRadius(element: string): number {
116
+ return COVALENT[element] ?? DEFAULT_COVALENT
117
+ }
118
+
119
+ /** The CPK colour for an element, as a `#rrggbb` string. */
120
+ export function elementColor(element: string): string {
121
+ return CPK[element] ?? UNKNOWN_ELEMENT_COLOR
122
+ }
123
+
124
+ /**
125
+ * The standard amino acids, plus the two modified residues common enough that
126
+ * treating them as foreign would put a hole in the middle of a chain:
127
+ * selenomethionine (`MSE`, how a crystallographer phases a new structure) and
128
+ * pyroglutamate (`PCA`).
129
+ *
130
+ * Both arrive as `HETATM` records, which is exactly why the set is consulted
131
+ * rather than the record type — "is this part of the chain" is a question about
132
+ * the residue, and the file answers a different one.
133
+ */
134
+ const AMINO_ACIDS = new Set([
135
+ "ALA", "ARG", "ASN", "ASP", "CYS", "GLN", "GLU", "GLY", "HIS", "ILE",
136
+ "LEU", "LYS", "MET", "PHE", "PRO", "SER", "THR", "TRP", "TYR", "VAL",
137
+ "MSE", "PCA", "SEC", "PYL",
138
+ ])
139
+
140
+ /** The nucleotides, RNA and DNA. */
141
+ const NUCLEOTIDES = new Set([
142
+ "A", "C", "G", "U", "I",
143
+ "DA", "DC", "DG", "DT", "DI",
144
+ ])
145
+
146
+ /**
147
+ * The waters. Named separately from the rest of the hetero atoms because they
148
+ * are the one kind a viewer hides by default: a crystal structure carries
149
+ * hundreds of them, they are chemically uninteresting to the picture being
150
+ * drawn, and left on they bury the molecule in red dots.
151
+ */
152
+ const WATERS = new Set(["HOH", "DOD", "WAT", "H2O", "TIP"])
153
+
154
+ /** What a residue is, which decides whether it joins a backbone trace. */
155
+ export type ResidueKind = "amino" | "nucleic" | "water" | "other"
156
+
157
+ /** Classifies a residue by its three-letter code. */
158
+ export function residueKind(name: string): ResidueKind {
159
+ const code = name.trim().toUpperCase()
160
+ if (AMINO_ACIDS.has(code)) return "amino"
161
+ if (NUCLEOTIDES.has(code)) return "nucleic"
162
+ if (WATERS.has(code)) return "water"
163
+ return "other"
164
+ }
165
+
166
+ /**
167
+ * The atom a residue's backbone trace passes through: the α-carbon of an amino
168
+ * acid, the phosphorus of a nucleotide.
169
+ *
170
+ * One atom per residue rather than the whole backbone, because what the trace is
171
+ * *for* is the smooth curve a ribbon is swept along, and the N–CA–C zig-zag
172
+ * would put a kink at every residue. Every molecular viewer traces α-carbons for
173
+ * the same reason.
174
+ */
175
+ export function traceAtomName(kind: ResidueKind): string | null {
176
+ if (kind === "amino") return "CA"
177
+ if (kind === "nucleic") return "P"
178
+ return null
179
+ }
180
+
181
+ /**
182
+ * The element an atom belongs to, from the file's element column when it has
183
+ * one and from the atom's *name* when it doesn't.
184
+ *
185
+ * The fallback matters more than it looks: the element column is right-justified
186
+ * in columns 77–78 of a PDB record and plenty of older files, and most files
187
+ * written by hand, simply stop before it. The name is then all there is, and
188
+ * reading it is not quite trimming the digits off — an amino acid's `CA` is a
189
+ * carbon and an ion's `CA` is calcium, and the two are told apart only by what
190
+ * residue they sit in. Hence {@link kind}.
191
+ */
192
+ export function elementOf(
193
+ declared: string,
194
+ atomName: string,
195
+ kind: ResidueKind
196
+ ): string {
197
+ const stated = declared.trim().toUpperCase()
198
+ if (stated !== "") return stated
199
+
200
+ // PDB atom names are padded so that the element starts in column 14 for a
201
+ // two-letter element and column 15 for a one-letter one — but only in files
202
+ // that respect the alignment, which is why this reads the characters instead:
203
+ // strip the leading digit an alternate hydrogen carries (`1HB`), then take the
204
+ // letters.
205
+ const name = atomName.trim().toUpperCase().replace(/^\d+/, "")
206
+ const letters = name.replace(/[^A-Z]/g, "")
207
+ if (letters === "") return ""
208
+
209
+ // Inside a polymer residue the only two-letter elements are the ones that
210
+ // can't be confused: everything else is C, N, O, S or P followed by the
211
+ // position letters that name the atom (`CB`, `NZ`, `OG1`). A `CA` there is the
212
+ // α-carbon, never calcium.
213
+ if (kind === "amino" || kind === "nucleic") {
214
+ if (letters.startsWith("SE")) return "SE"
215
+ return letters.slice(0, 1)
216
+ }
217
+
218
+ // Outside one — a ligand, an ion, a cofactor — a two-letter element is real,
219
+ // so prefer the longest match the tables know before falling back to one.
220
+ const pair = letters.slice(0, 2)
221
+ if (pair in COVALENT) return pair
222
+ return letters.slice(0, 1)
223
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The Protein node — a **native** node type (see `node-types.ts` /
3
+ * `NODE_CLASS_BY_KEY`) like the chart family and the 3D graph beside it: built,
4
+ * themed and animated from the inspector rather than loaded as a code package.
5
+ *
6
+ * It draws a molecular structure — a protein, a nucleic acid, a complex of both
7
+ * with whatever is bound to them — in one of four representations, from a
8
+ * coordinate file the Protein Data Bank publishes.
9
+ *
10
+ * Three things about it are worth knowing before reading the parts:
11
+ *
12
+ * **The file is parsed outside the render.** `buildScene` is synchronous and a
13
+ * coordinate file arrives over a network, so the fetch and the parse happen
14
+ * ahead of the build and what reaches the node is a finished
15
+ * {@link ProteinStructure} — the same arrangement a chart has with its rows.
16
+ * That is why `parse.ts` is exported: the app's structure store is its caller,
17
+ * not this node.
18
+ *
19
+ * **The chemistry is a table, not a dependency.** Radii, colours and what counts
20
+ * as a bond live in `chemistry.ts` for the same reason `graph3d/expression.ts`
21
+ * is a parser rather than a maths library: this package has to stay loadable in
22
+ * a bare Node process, and a molecular-graphics library brings a renderer, a
23
+ * fetch layer and a DOM with it.
24
+ *
25
+ * **There is no pointer.** The camera is three tweenable numbers rather than a
26
+ * drag, because the studio renders a timeline and every frame has to be
27
+ * reproducible under scrubbing and export. See {@link Protein}.
28
+ */
29
+ export * from "./chemistry";
30
+ export * from "./parse";
31
+ export * from "./protein";
32
+ export * from "./ribbon";
33
+ export * from "./shared";
34
+ export * from "./structure";