vimol 0.1.0__py3-none-any.whl

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.
vimol/elements.py ADDED
@@ -0,0 +1,123 @@
1
+ """Periodic-table reference data: symbols, covalent/van-der-Waals radii and CPK colors.
2
+
3
+ All radii are in angstrom. Colors are (r, g, b) floats in [0, 1].
4
+ The tables are deliberately self-contained (no external data files) so that the
5
+ library stays dependency-light and trivially embeddable.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ # Atomic number -> symbol (1..118). Index 0 is a dummy so Z indexes directly.
10
+ SYMBOLS = [
11
+ "X",
12
+ "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne",
13
+ "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca",
14
+ "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn",
15
+ "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr",
16
+ "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn",
17
+ "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd",
18
+ "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb",
19
+ "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg",
20
+ "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th",
21
+ "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm",
22
+ "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds",
23
+ "Rg", "Cn", "Nh", "Fl", "Mc", "Lv", "Ts", "Og",
24
+ ]
25
+
26
+ SYMBOL_TO_Z = {s.upper(): z for z, s in enumerate(SYMBOLS)}
27
+
28
+ # Full element names, aligned with SYMBOLS (index 0 is the same dummy).
29
+ NAMES = [
30
+ "",
31
+ "Hydrogen", "Helium", "Lithium", "Beryllium", "Boron", "Carbon", "Nitrogen",
32
+ "Oxygen", "Fluorine", "Neon", "Sodium", "Magnesium", "Aluminium", "Silicon",
33
+ "Phosphorus", "Sulfur", "Chlorine", "Argon", "Potassium", "Calcium",
34
+ "Scandium", "Titanium", "Vanadium", "Chromium", "Manganese", "Iron",
35
+ "Cobalt", "Nickel", "Copper", "Zinc", "Gallium", "Germanium", "Arsenic",
36
+ "Selenium", "Bromine", "Krypton", "Rubidium", "Strontium", "Yttrium",
37
+ "Zirconium", "Niobium", "Molybdenum", "Technetium", "Ruthenium", "Rhodium",
38
+ "Palladium", "Silver", "Cadmium", "Indium", "Tin", "Antimony", "Tellurium",
39
+ "Iodine", "Xenon", "Caesium", "Barium", "Lanthanum", "Cerium",
40
+ "Praseodymium", "Neodymium", "Promethium", "Samarium", "Europium",
41
+ "Gadolinium", "Terbium", "Dysprosium", "Holmium", "Erbium", "Thulium",
42
+ "Ytterbium", "Lutetium", "Hafnium", "Tantalum", "Tungsten", "Rhenium",
43
+ "Osmium", "Iridium", "Platinum", "Gold", "Mercury", "Thallium", "Lead",
44
+ "Bismuth", "Polonium", "Astatine", "Radon", "Francium", "Radium",
45
+ "Actinium", "Thorium", "Protactinium", "Uranium", "Neptunium",
46
+ "Plutonium", "Americium", "Curium", "Berkelium", "Californium",
47
+ "Einsteinium", "Fermium", "Mendelevium", "Nobelium", "Lawrencium",
48
+ "Rutherfordium", "Dubnium", "Seaborgium", "Bohrium", "Hassium",
49
+ "Meitnerium", "Darmstadtium", "Roentgenium", "Copernicium", "Nihonium",
50
+ "Flerovium", "Moscovium", "Livermorium", "Tennessine", "Oganesson",
51
+ ]
52
+
53
+ # Covalent radii (Cordero 2008), angstrom. Fallback 0.77 (carbon-ish).
54
+ _COVALENT = {
55
+ "H": 0.31, "He": 0.28, "Li": 1.28, "Be": 0.96, "B": 0.84, "C": 0.76,
56
+ "N": 0.71, "O": 0.66, "F": 0.57, "Ne": 0.58, "Na": 1.66, "Mg": 1.41,
57
+ "Al": 1.21, "Si": 1.11, "P": 1.07, "S": 1.05, "Cl": 1.02, "Ar": 1.06,
58
+ "K": 2.03, "Ca": 1.76, "Sc": 1.70, "Ti": 1.60, "V": 1.53, "Cr": 1.39,
59
+ "Mn": 1.39, "Fe": 1.32, "Co": 1.26, "Ni": 1.24, "Cu": 1.32, "Zn": 1.22,
60
+ "Ga": 1.22, "Ge": 1.20, "As": 1.19, "Se": 1.20, "Br": 1.20, "Kr": 1.16,
61
+ "Rb": 2.20, "Sr": 1.95, "Y": 1.90, "Zr": 1.75, "Nb": 1.64, "Mo": 1.54,
62
+ "Tc": 1.47, "Ru": 1.46, "Rh": 1.42, "Pd": 1.39, "Ag": 1.45, "Cd": 1.44,
63
+ "In": 1.42, "Sn": 1.39, "Sb": 1.39, "Te": 1.38, "I": 1.39, "Xe": 1.40,
64
+ "Cs": 2.44, "Ba": 2.15, "Pt": 1.36, "Au": 1.36, "Hg": 1.32, "Pb": 1.46,
65
+ }
66
+
67
+ # Van der Waals radii (Bondi + extensions), angstrom. Fallback 1.7.
68
+ _VDW = {
69
+ "H": 1.20, "He": 1.40, "Li": 1.82, "Be": 1.53, "B": 1.92, "C": 1.70,
70
+ "N": 1.55, "O": 1.52, "F": 1.47, "Ne": 1.54, "Na": 2.27, "Mg": 1.73,
71
+ "Al": 1.84, "Si": 2.10, "P": 1.80, "S": 1.80, "Cl": 1.75, "Ar": 1.88,
72
+ "K": 2.75, "Ca": 2.31, "Ni": 1.63, "Cu": 1.40, "Zn": 1.39, "Ga": 1.87,
73
+ "Ge": 2.11, "As": 1.85, "Se": 1.90, "Br": 1.85, "Kr": 2.02, "I": 1.98,
74
+ "Xe": 2.16, "Fe": 2.05, "Pd": 1.63, "Ag": 1.72, "Cd": 1.58, "Sn": 2.17,
75
+ "Pt": 1.75, "Au": 1.66, "Hg": 1.55, "Pb": 2.02,
76
+ }
77
+
78
+ # CPK / Jmol-inspired colors, (r, g, b) in [0, 1]. Fallback pink for unknowns.
79
+ _COLORS = {
80
+ "H": (1.00, 1.00, 1.00), "He": (0.85, 1.00, 1.00), "Li": (0.80, 0.50, 1.00),
81
+ "Be": (0.76, 1.00, 0.00), "B": (1.00, 0.71, 0.71), "C": (0.35, 0.35, 0.38),
82
+ "N": (0.19, 0.31, 0.97), "O": (1.00, 0.05, 0.05), "F": (0.56, 0.88, 0.31),
83
+ "Ne": (0.70, 0.89, 0.96), "Na": (0.67, 0.36, 0.95), "Mg": (0.54, 1.00, 0.00),
84
+ "Al": (0.75, 0.65, 0.65), "Si": (0.94, 0.78, 0.63), "P": (1.00, 0.50, 0.00),
85
+ "S": (1.00, 0.86, 0.19), "Cl": (0.12, 0.94, 0.12), "Ar": (0.50, 0.82, 0.89),
86
+ "K": (0.56, 0.25, 0.83), "Ca": (0.24, 1.00, 0.00), "Fe": (0.88, 0.40, 0.20),
87
+ "Cu": (0.78, 0.50, 0.20), "Zn": (0.49, 0.50, 0.69), "Br": (0.65, 0.16, 0.16),
88
+ "I": (0.58, 0.00, 0.58), "Au": (1.00, 0.82, 0.14), "Ag": (0.75, 0.75, 0.75),
89
+ "Pt": (0.82, 0.82, 0.88), "Hg": (0.72, 0.72, 0.82), "Ni": (0.31, 0.82, 0.31),
90
+ "Mn": (0.61, 0.48, 0.78), "Se": (1.00, 0.63, 0.00), "Pb": (0.34, 0.35, 0.38),
91
+ }
92
+
93
+ _DEFAULT_COLOR = (1.00, 0.41, 0.71) # hot pink for unknown elements
94
+
95
+
96
+ def normalize_symbol(sym: str) -> str:
97
+ """Normalize an element token to canonical capitalization (e.g. 'FE' -> 'Fe')."""
98
+ sym = sym.strip()
99
+ if not sym:
100
+ return "X"
101
+ return sym[0].upper() + sym[1:].lower()
102
+
103
+
104
+ def symbol_to_z(sym: str) -> int:
105
+ return SYMBOL_TO_Z.get(normalize_symbol(sym).upper(), 0)
106
+
107
+
108
+ def covalent_radius(sym: str) -> float:
109
+ return _COVALENT.get(normalize_symbol(sym), 0.77)
110
+
111
+
112
+ def vdw_radius(sym: str) -> float:
113
+ return _VDW.get(normalize_symbol(sym), 1.70)
114
+
115
+
116
+ def element_color(sym: str) -> tuple:
117
+ return _COLORS.get(normalize_symbol(sym), _DEFAULT_COLOR)
118
+
119
+
120
+ def element_name(sym: str) -> str:
121
+ """Full element name, e.g. 'Carbon' for 'C'. Falls back to the symbol itself."""
122
+ z = symbol_to_z(sym)
123
+ return NAMES[z] if z else normalize_symbol(sym)
vimol/gl_adapter.py ADDED
@@ -0,0 +1,114 @@
1
+ """Bridges vimol's molecule-aware types to the generic GL renderer.
2
+
3
+ This is the only module that knows about both ``Molecule``/``Camera``/
4
+ ``Style`` (vimol) and ``SphereBatch``/``CylinderBatch``/``ConeBatch``/
5
+ ``ShadingParams`` (``gl_render.py``, which otherwise has zero vimol imports).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Tuple
10
+
11
+ import numpy as np
12
+
13
+ from .arrows import build_arrow_geometry
14
+ from .camera import Camera
15
+ from .molecule import Molecule
16
+ from .render import Style, _atom_radii
17
+ from .gl_render import SphereBatch, CylinderBatch, ConeBatch, ShadingParams
18
+
19
+
20
+ def _build_projection(zoom: float, pan: np.ndarray, width: int, height: int, extent: float) -> np.ndarray:
21
+ """Orthographic projection matching ``Camera``'s pixel-space screen
22
+ mapping (``sx = W/2 + pan.x + vx*zoom``, ``sy = H/2 - pan.y - vy*zoom``,
23
+ Y-down pixel space), reworked into a clip-space (Y-up) 4x4 matrix.
24
+
25
+ Returned in the usual mathematical convention (row-major indexing,
26
+ ``[nx,ny,nz,1] = M @ [vx,vy,vz,1]``) — ``GLRenderer.render`` transposes
27
+ internally for GLSL's column-major upload, and also reads ``M[2,2]``/
28
+ ``M[2,3]`` directly to reproject each fragment's own analytic depth.
29
+
30
+ The Z mapping ``nz = -vz / (4*extent)`` needs no separate near/far
31
+ values: view-space ``+z`` is toward the viewer (``Camera``'s
32
+ convention), so larger ``vz`` (nearer) maps to smaller/negative ``nz``
33
+ (GL's near plane, since the default ``GL_LESS`` test + clear depth
34
+ ``1.0`` means smaller wins). ``4*extent`` is a generous symmetric bound
35
+ around the camera's look-at center — no clipping in practice.
36
+ """
37
+ ext = max(float(extent), 1e-6)
38
+ m = np.zeros((4, 4), dtype=np.float64)
39
+ m[0, 0] = 2.0 * zoom / width
40
+ m[0, 3] = 2.0 * pan[0] / width
41
+ m[1, 1] = 2.0 * zoom / height
42
+ m[1, 3] = 2.0 * pan[1] / height
43
+ m[2, 2] = -1.0 / (4.0 * ext)
44
+ m[3, 3] = 1.0
45
+ return m
46
+
47
+
48
+ def molecule_to_gl_inputs(molecule: Molecule, camera: Camera, style: Style,
49
+ width: int, height: int
50
+ ) -> Tuple[SphereBatch, CylinderBatch, ConeBatch, np.ndarray, ShadingParams]:
51
+ """Convert a molecule + camera + style into generic GL renderer inputs."""
52
+ if molecule.n_atoms == 0:
53
+ vpos = np.zeros((0, 3), np.float32)
54
+ colors = np.zeros((0, 3), np.float32)
55
+ else:
56
+ vpos = camera.view_positions(molecule.positions).astype(np.float32)
57
+ colors = np.asarray(
58
+ style.color_override if style.color_override is not None else molecule.element_colors(),
59
+ dtype=np.float32,
60
+ )
61
+
62
+ if molecule.n_atoms == 0:
63
+ spheres = SphereBatch.empty()
64
+ else:
65
+ radii = _atom_radii(molecule, style).astype(np.float32)
66
+ spheres = SphereBatch(centers=vpos, radii=radii, colors=colors)
67
+
68
+ draw_bonds = style.representation in ("ball_and_stick", "wireframe", "licorice") and molecule.bonds
69
+ if draw_bonds:
70
+ rb = style.bond_radius * (1.6 if style.representation == "licorice" else 1.0)
71
+ idx_i = np.array([i for i, j, _order in molecule.bonds], dtype=np.int64)
72
+ idx_j = np.array([j for i, j, _order in molecule.bonds], dtype=np.int64)
73
+ bond_a = vpos[idx_i]
74
+ bond_b = vpos[idx_j]
75
+ bond_r = np.full(len(molecule.bonds), rb, dtype=np.float32)
76
+ bond_ca = colors[idx_i]
77
+ bond_cb = colors[idx_j]
78
+ else:
79
+ bond_a = bond_b = bond_ca = bond_cb = np.zeros((0, 3), np.float32)
80
+ bond_r = np.zeros((0,), np.float32)
81
+
82
+ # Arrow shafts are literally cylinders -- fold them into the same batch
83
+ # as bonds. Arrow heads (cones) need the new GL primitive. Reuse the
84
+ # atom view positions already computed above rather than transforming
85
+ # them again.
86
+ geom = build_arrow_geometry(molecule, camera, view_pos=vpos)
87
+ cylinders = CylinderBatch(
88
+ a=np.concatenate([bond_a, geom.shaft_a.astype(np.float32)]),
89
+ b=np.concatenate([bond_b, geom.shaft_b.astype(np.float32)]),
90
+ radii=np.concatenate([bond_r, geom.shaft_radius.astype(np.float32)]),
91
+ colors_a=np.concatenate([bond_ca, geom.shaft_color.astype(np.float32)]),
92
+ colors_b=np.concatenate([bond_cb, geom.shaft_color.astype(np.float32)]),
93
+ )
94
+ cones = ConeBatch(
95
+ base=geom.head_base.astype(np.float32),
96
+ apex=geom.head_apex.astype(np.float32),
97
+ radius=geom.head_radius.astype(np.float32),
98
+ color=geom.head_color.astype(np.float32),
99
+ )
100
+
101
+ proj = _build_projection(camera.zoom, camera.pan, width, height, camera.extent)
102
+
103
+ shading = ShadingParams(
104
+ ambient=style.ambient,
105
+ specular_strength=style.specular_strength,
106
+ shininess=style.shininess,
107
+ light_dir=tuple(style.light_dir),
108
+ fill_light=style.fill_light,
109
+ depth_cue=style.depth_cue,
110
+ background=tuple(style.background),
111
+ transparent=style.transparent,
112
+ )
113
+
114
+ return spheres, cylinders, cones, proj, shading