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/__init__.py +63 -0
- vimol/__main__.py +4 -0
- vimol/app.py +218 -0
- vimol/arrows.py +91 -0
- vimol/bonds.py +79 -0
- vimol/camera.py +77 -0
- vimol/editor.py +646 -0
- vimol/elements.py +123 -0
- vimol/gl_adapter.py +114 -0
- vimol/gl_render.py +742 -0
- vimol/input.py +252 -0
- vimol/kitty.py +297 -0
- vimol/molecule.py +159 -0
- vimol/parsers/__init__.py +88 -0
- vimol/parsers/mol.py +57 -0
- vimol/parsers/pdb.py +97 -0
- vimol/parsers/xyz.py +68 -0
- vimol/periodic_table.py +97 -0
- vimol/py.typed +0 -0
- vimol/render.py +600 -0
- vimol/scene.py +239 -0
- vimol/templates.py +205 -0
- vimol/viewer.py +1159 -0
- vimol/widget.py +611 -0
- vimol-0.1.0.dist-info/METADATA +302 -0
- vimol-0.1.0.dist-info/RECORD +30 -0
- vimol-0.1.0.dist-info/WHEEL +5 -0
- vimol-0.1.0.dist-info/entry_points.txt +2 -0
- vimol-0.1.0.dist-info/licenses/LICENSE +20 -0
- vimol-0.1.0.dist-info/top_level.txt +1 -0
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
|