fastmolwidget 0.5.1__tar.gz
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.
- fastmolwidget-0.5.1/LICENSE +24 -0
- fastmolwidget-0.5.1/PKG-INFO +285 -0
- fastmolwidget-0.5.1/README.md +232 -0
- fastmolwidget-0.5.1/pyproject.toml +60 -0
- fastmolwidget-0.5.1/src/fastmolwidget/__init__.py +9 -0
- fastmolwidget-0.5.1/src/fastmolwidget/atoms.py +831 -0
- fastmolwidget-0.5.1/src/fastmolwidget/cif/__init__.py +0 -0
- fastmolwidget-0.5.1/src/fastmolwidget/cif/cif_file_io.py +844 -0
- fastmolwidget-0.5.1/src/fastmolwidget/dsrmath.py +1311 -0
- fastmolwidget-0.5.1/src/fastmolwidget/loader.py +329 -0
- fastmolwidget-0.5.1/src/fastmolwidget/molecule2D.py +1131 -0
- fastmolwidget-0.5.1/src/fastmolwidget/sdm.py +425 -0
- fastmolwidget-0.5.1/src/fastmolwidget/tools.py +58 -0
- fastmolwidget-0.5.1/src/fastmolwidget/viewer_widget.py +117 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Daniel Kratzert
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: fastmolwidget
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: A PyQt widget to display crystal structures
|
|
5
|
+
Keywords: chemistry,crystallography,qt,widget,molecule
|
|
6
|
+
Author: Daniel Kratzert
|
|
7
|
+
Author-email: Daniel Kratzert <dkratzert@gmx.de>
|
|
8
|
+
License: BSD 2-Clause License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026, Daniel Kratzert
|
|
11
|
+
|
|
12
|
+
Redistribution and use in source and binary forms, with or without
|
|
13
|
+
modification, are permitted provided that the following conditions are met:
|
|
14
|
+
|
|
15
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
16
|
+
list of conditions and the following disclaimer.
|
|
17
|
+
|
|
18
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
19
|
+
this list of conditions and the following disclaimer in the documentation
|
|
20
|
+
and/or other materials provided with the distribution.
|
|
21
|
+
|
|
22
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
23
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
24
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
25
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
26
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
27
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
28
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
29
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
30
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
31
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Intended Audience :: Science/Research
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
39
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
40
|
+
Requires-Dist: gemmi>=0.7.5
|
|
41
|
+
Requires-Dist: numpy>=2.2.0
|
|
42
|
+
Requires-Dist: qtpy>=2.4.3
|
|
43
|
+
Requires-Dist: shelxfile>=22
|
|
44
|
+
Requires-Dist: pyqt6>=6.9.0 ; extra == 'pyqt6'
|
|
45
|
+
Requires-Dist: pyside6-essentials>=6.11.0 ; extra == 'pyside6'
|
|
46
|
+
Requires-Python: >=3.12
|
|
47
|
+
Project-URL: Homepage, https://github.com/dkratzert/Fastmolwidget
|
|
48
|
+
Project-URL: Repository, https://github.com/dkratzert/Fastmolwidget
|
|
49
|
+
Project-URL: Issues, https://github.com/dkratzert/Fastmolwidget/issues
|
|
50
|
+
Provides-Extra: pyqt6
|
|
51
|
+
Provides-Extra: pyside6
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# Fastmolwidget
|
|
55
|
+
|
|
56
|
+
**A PyQt/PySide6 widget to display crystal structures**
|
|
57
|
+
|
|
58
|
+
Fastmolwidget is a lightweight, embeddable Qt widget that renders molecular and crystal structures in 2D projection with interactive mouse controls.
|
|
59
|
+
It supports anisotropic displacement parameter (ADP) ellipsoids, ball-and-stick diagrams, and plain sphere representations —
|
|
60
|
+
all drawn with a pure-Python QPainter backend (no OpenGL required).
|
|
61
|
+
|
|
62
|
+
## Screenshot
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
*View of a crystal structure from a CIF file with ADP ellipsoids. The control bar allows toggling display options interactively.*
|
|
67
|
+
|
|
68
|
+
## Features
|
|
69
|
+
|
|
70
|
+
- **ADP ellipsoids** at the 50 % probability level, rendered from anisotropic displacement parameters
|
|
71
|
+
- **Ball-and-stick** and **isotropic sphere** representations as fallbacks or when speed is more important than detail
|
|
72
|
+
- **Interactive mouse controls**: rotate (left-drag), zoom (right-drag), pan (middle-drag), scroll wheel to resize labels
|
|
73
|
+
- **Atom and bond selection**: single click or Ctrl+click for multi-selection; emits `atomClicked` / `bondClicked` Qt signals
|
|
74
|
+
- **Hydrogen visibility toggle**
|
|
75
|
+
- **Atom label display toggle** with adjustable font size
|
|
76
|
+
- **Bond width** adjustment via spin box
|
|
77
|
+
- **Multiple file formats**: CIF, SHELX `.res`/`.ins`, and plain XYZ. More to come...
|
|
78
|
+
- **Embeddable** — `MoleculeWidget` is a plain `QWidget` subclass; drop it into any layout
|
|
79
|
+
- **Ready-to-use** `MoleculeViewerWidget` bundles the renderer with a full control bar
|
|
80
|
+
|
|
81
|
+
## Supported File Formats
|
|
82
|
+
|
|
83
|
+
| Extension | Format | Notes |
|
|
84
|
+
|-----------|--------|-------|
|
|
85
|
+
| `.cif` | Crystallographic Information File | Reads atoms, unit cell, and ADPs |
|
|
86
|
+
| `.res` / `.ins` | SHELXL instruction file | Reads atoms and unit cell via [shelxfile](https://github.com/dkratzert/ShelXFile) |
|
|
87
|
+
| `.xyz` | Standard XYZ coordinate file | Cartesian coordinates, no cell or ADPs |
|
|
88
|
+
|
|
89
|
+
## Installation
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pip install fastmolwidget
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
By default, `fastmolwidget` installs **without a concrete Qt binding**.
|
|
96
|
+
Install one binding explicitly via extras:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install "fastmolwidget[pyside6]"
|
|
100
|
+
pip install "fastmolwidget[pyqt6]"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Requirements**: Python >= 3.12, NumPy, gemmi, shelxfile, qtpy, and either PySide6 or PyQt6.
|
|
104
|
+
|
|
105
|
+
## Quick Start
|
|
106
|
+
|
|
107
|
+
### Standalone viewer
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from qtpy.QtWidgets import QApplication
|
|
111
|
+
from fastmolwidget import MoleculeViewerWidget
|
|
112
|
+
|
|
113
|
+
app = QApplication([])
|
|
114
|
+
viewer = MoleculeViewerWidget()
|
|
115
|
+
viewer.load_file("structure.cif")
|
|
116
|
+
viewer.show()
|
|
117
|
+
app.exec()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Embedding in your own layout
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from fastmolwidget import MoleculeWidget, MoleculeLoader
|
|
124
|
+
|
|
125
|
+
mol = MoleculeWidget(parent=self)
|
|
126
|
+
loader = MoleculeLoader(mol)
|
|
127
|
+
# The loader recognizes the file format from the extension and populates `mol` accordingly
|
|
128
|
+
loader.load_file("structure.cif")
|
|
129
|
+
|
|
130
|
+
# drop `mol` into any QLayout
|
|
131
|
+
layout.addWidget(mol)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Loading a different file at runtime
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
viewer.load_file("new_structure.res")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Reacting to atom / bond clicks
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
mol.atomClicked.connect(lambda label: print(f"Clicked atom: {label}"))
|
|
144
|
+
mol.bondClicked.connect(lambda a, b: print(f"Clicked bond: {a}–{b}"))
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Mouse Controls
|
|
148
|
+
|
|
149
|
+
| Action | Effect |
|
|
150
|
+
|--------|--------|
|
|
151
|
+
| Left-drag | Rotate the molecule |
|
|
152
|
+
| Right-drag | Zoom in / out |
|
|
153
|
+
| Middle-drag | Pan the view |
|
|
154
|
+
| Scroll wheel | Increase / decrease label font size |
|
|
155
|
+
| Left-click | Select a single atom or bond |
|
|
156
|
+
| Ctrl + Left-click | Toggle multi-selection |
|
|
157
|
+
|
|
158
|
+
## Control Bar Options (`MoleculeViewerWidget`)
|
|
159
|
+
|
|
160
|
+
| Control | Default | Description |
|
|
161
|
+
|---------|---------|-------------|
|
|
162
|
+
| Show ADP | ✓ | Toggle ORTEP ellipsoid / isotropic sphere rendering |
|
|
163
|
+
| Show Labels | ✗ | Toggle non-hydrogen atom labels |
|
|
164
|
+
| Round Bonds | ✓ | Switch between 3D-shaded and flat bond drawing |
|
|
165
|
+
| Show Hydrogens | ✓ | Show or hide hydrogen atoms and their bonds |
|
|
166
|
+
| Bond Width | 3 | Stroke width for bonds (1–15 px) |
|
|
167
|
+
|
|
168
|
+
## API Overview
|
|
169
|
+
|
|
170
|
+
### `MoleculeViewerWidget(parent=None)`
|
|
171
|
+
|
|
172
|
+
A self-contained widget combining `MoleculeWidget` with the control bar described above.
|
|
173
|
+
|
|
174
|
+
- `load_file(path)` — load a structure file (format auto-detected from extension)
|
|
175
|
+
- `render_widget` — read-only property exposing the underlying `MoleculeWidget`
|
|
176
|
+
|
|
177
|
+
### `MoleculeWidget(parent=None)`
|
|
178
|
+
|
|
179
|
+
The low-level renderer widget. It is a plain `QWidget` subclass that you can drop into any layout.
|
|
180
|
+
Provide atom data directly via `open_molecule()` instead of loading a file through `MoleculeLoader`.
|
|
181
|
+
|
|
182
|
+
#### Qt Signals
|
|
183
|
+
|
|
184
|
+
| Signal | Signature | Emitted when |
|
|
185
|
+
|--------|-----------|--------------|
|
|
186
|
+
| `atomClicked` | `(label: str)` | The user clicks on an atom; `label` is the atom name (e.g. `"C1"`) |
|
|
187
|
+
| `bondClicked` | `(label1: str, label2: str)` | The user clicks on a bond; both atom labels are passed |
|
|
188
|
+
|
|
189
|
+
#### Data Methods
|
|
190
|
+
|
|
191
|
+
- **`open_molecule(atoms, cell=None, adps=None, keep_view=False)`**
|
|
192
|
+
Load a new set of atoms and reset (or optionally preserve) the view.
|
|
193
|
+
- `atoms` — list of `Atomtuple(label, type, x, y, z, part)` in Cartesian coordinates (Å)
|
|
194
|
+
- `cell` — optional `(a, b, c, α, β, γ)` tuple of unit-cell parameters (Å / °); required for ADP rendering
|
|
195
|
+
- `adps` — optional `dict` mapping atom labels to `(U11, U22, U33, U23, U13, U12)` ADP tensors
|
|
196
|
+
- `keep_view` — when `True`, the current zoom, pan, and rotation are preserved (useful for live updates)
|
|
197
|
+
|
|
198
|
+
- **`grow_molecule(atoms, cell=None, adps=None)`**
|
|
199
|
+
Replace the atom set while always preserving the current view.
|
|
200
|
+
Equivalent to calling `open_molecule(..., keep_view=True)`.
|
|
201
|
+
|
|
202
|
+
- **`clear()`**
|
|
203
|
+
Remove all atoms and bonds from the display.
|
|
204
|
+
|
|
205
|
+
#### Display Methods
|
|
206
|
+
|
|
207
|
+
- **`show_adps(value: bool)`**
|
|
208
|
+
Toggle ORTEP-style ADP ellipsoid rendering. When `False`, atoms are drawn as isotropic spheres.
|
|
209
|
+
|
|
210
|
+
- **`show_labels(value: bool)`**
|
|
211
|
+
Show or hide non-hydrogen atom labels.
|
|
212
|
+
|
|
213
|
+
- **`show_hydrogens(value: bool)`**
|
|
214
|
+
Show or hide hydrogen / deuterium atoms and their bonds.
|
|
215
|
+
|
|
216
|
+
- **`show_round_bonds(value: bool)`**
|
|
217
|
+
Switch between 3D-shaded cylinder-style bonds (`True`, default) and flat single-colour bonds (`False`).
|
|
218
|
+
|
|
219
|
+
- **`set_bond_width(width: int)`**
|
|
220
|
+
Set the stroke width for bonds in pixels (valid range: 1–15).
|
|
221
|
+
|
|
222
|
+
- **`setLabelFont(font_size: int)`**
|
|
223
|
+
Set the pixel size used for atom labels.
|
|
224
|
+
|
|
225
|
+
- **`set_background_color(color: QColor)`**
|
|
226
|
+
Change the widget background colour.
|
|
227
|
+
|
|
228
|
+
- **`reset_view()`**
|
|
229
|
+
Reset zoom, pan, and rotation to their defaults.
|
|
230
|
+
|
|
231
|
+
#### Example — feeding atom data directly
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from fastmolwidget import MoleculeWidget
|
|
235
|
+
from fastmolwidget.sdm import Atomtuple
|
|
236
|
+
|
|
237
|
+
mol = MoleculeWidget(parent=self)
|
|
238
|
+
|
|
239
|
+
atoms = [
|
|
240
|
+
Atomtuple(label="C1", type="C", x=0.0, y=0.0, z=0.0, part=0),
|
|
241
|
+
Atomtuple(label="O1", type="O", x=1.22, y=0.0, z=0.0, part=0),
|
|
242
|
+
Atomtuple(label="H1", type="H", x=-0.5, y=0.94, z=0.0, part=0),
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
# ADP tensors: {atom_label: (U11, U22, U33, U23, U13, U12)}
|
|
246
|
+
adps = {
|
|
247
|
+
"C1": (0.02, 0.02, 0.02, 0.0, 0.0, 0.0),
|
|
248
|
+
"O1": (0.03, 0.03, 0.03, 0.0, 0.0, 0.0),
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
cell = (5.0, 5.0, 5.0, 90.0, 90.0, 90.0) # optional
|
|
252
|
+
|
|
253
|
+
mol.open_molecule(atoms=atoms, cell=cell, adps=adps)
|
|
254
|
+
mol.atomClicked.connect(lambda label: print(f"Selected: {label}"))
|
|
255
|
+
|
|
256
|
+
layout.addWidget(mol)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `MoleculeLoader(widget)`
|
|
260
|
+
|
|
261
|
+
Format-aware loader that populates a `MoleculeWidget`.
|
|
262
|
+
|
|
263
|
+
- `load_file(path, keep_view=False)` — parse file and call `open_molecule` / `grow_molecule`
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
BSD 2-Clause License — see [LICENSE](LICENSE) for details.
|
|
268
|
+
|
|
269
|
+
© 2026 Daniel Kratzert
|
|
270
|
+
|
|
271
|
+
## Maintainer Release Workflow
|
|
272
|
+
|
|
273
|
+
The release workflow is tag-driven and currently publishes to **TestPyPI** only.
|
|
274
|
+
|
|
275
|
+
1. Ensure `project.version` in `pyproject.toml` is the version to publish.
|
|
276
|
+
2. Create and push a matching tag in the format `version-X.Y.Z`.
|
|
277
|
+
3. GitHub Actions builds sdist/wheel and uploads to TestPyPI.
|
|
278
|
+
|
|
279
|
+
Example:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
git tag version-0.1.0
|
|
283
|
+
git push origin version-0.1.0
|
|
284
|
+
```
|
|
285
|
+
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Fastmolwidget
|
|
2
|
+
|
|
3
|
+
**A PyQt/PySide6 widget to display crystal structures**
|
|
4
|
+
|
|
5
|
+
Fastmolwidget is a lightweight, embeddable Qt widget that renders molecular and crystal structures in 2D projection with interactive mouse controls.
|
|
6
|
+
It supports anisotropic displacement parameter (ADP) ellipsoids, ball-and-stick diagrams, and plain sphere representations —
|
|
7
|
+
all drawn with a pure-Python QPainter backend (no OpenGL required).
|
|
8
|
+
|
|
9
|
+
## Screenshot
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
*View of a crystal structure from a CIF file with ADP ellipsoids. The control bar allows toggling display options interactively.*
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **ADP ellipsoids** at the 50 % probability level, rendered from anisotropic displacement parameters
|
|
18
|
+
- **Ball-and-stick** and **isotropic sphere** representations as fallbacks or when speed is more important than detail
|
|
19
|
+
- **Interactive mouse controls**: rotate (left-drag), zoom (right-drag), pan (middle-drag), scroll wheel to resize labels
|
|
20
|
+
- **Atom and bond selection**: single click or Ctrl+click for multi-selection; emits `atomClicked` / `bondClicked` Qt signals
|
|
21
|
+
- **Hydrogen visibility toggle**
|
|
22
|
+
- **Atom label display toggle** with adjustable font size
|
|
23
|
+
- **Bond width** adjustment via spin box
|
|
24
|
+
- **Multiple file formats**: CIF, SHELX `.res`/`.ins`, and plain XYZ. More to come...
|
|
25
|
+
- **Embeddable** — `MoleculeWidget` is a plain `QWidget` subclass; drop it into any layout
|
|
26
|
+
- **Ready-to-use** `MoleculeViewerWidget` bundles the renderer with a full control bar
|
|
27
|
+
|
|
28
|
+
## Supported File Formats
|
|
29
|
+
|
|
30
|
+
| Extension | Format | Notes |
|
|
31
|
+
|-----------|--------|-------|
|
|
32
|
+
| `.cif` | Crystallographic Information File | Reads atoms, unit cell, and ADPs |
|
|
33
|
+
| `.res` / `.ins` | SHELXL instruction file | Reads atoms and unit cell via [shelxfile](https://github.com/dkratzert/ShelXFile) |
|
|
34
|
+
| `.xyz` | Standard XYZ coordinate file | Cartesian coordinates, no cell or ADPs |
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install fastmolwidget
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
By default, `fastmolwidget` installs **without a concrete Qt binding**.
|
|
43
|
+
Install one binding explicitly via extras:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install "fastmolwidget[pyside6]"
|
|
47
|
+
pip install "fastmolwidget[pyqt6]"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Requirements**: Python >= 3.12, NumPy, gemmi, shelxfile, qtpy, and either PySide6 or PyQt6.
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
### Standalone viewer
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from qtpy.QtWidgets import QApplication
|
|
58
|
+
from fastmolwidget import MoleculeViewerWidget
|
|
59
|
+
|
|
60
|
+
app = QApplication([])
|
|
61
|
+
viewer = MoleculeViewerWidget()
|
|
62
|
+
viewer.load_file("structure.cif")
|
|
63
|
+
viewer.show()
|
|
64
|
+
app.exec()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Embedding in your own layout
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from fastmolwidget import MoleculeWidget, MoleculeLoader
|
|
71
|
+
|
|
72
|
+
mol = MoleculeWidget(parent=self)
|
|
73
|
+
loader = MoleculeLoader(mol)
|
|
74
|
+
# The loader recognizes the file format from the extension and populates `mol` accordingly
|
|
75
|
+
loader.load_file("structure.cif")
|
|
76
|
+
|
|
77
|
+
# drop `mol` into any QLayout
|
|
78
|
+
layout.addWidget(mol)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Loading a different file at runtime
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
viewer.load_file("new_structure.res")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Reacting to atom / bond clicks
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
mol.atomClicked.connect(lambda label: print(f"Clicked atom: {label}"))
|
|
91
|
+
mol.bondClicked.connect(lambda a, b: print(f"Clicked bond: {a}–{b}"))
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Mouse Controls
|
|
95
|
+
|
|
96
|
+
| Action | Effect |
|
|
97
|
+
|--------|--------|
|
|
98
|
+
| Left-drag | Rotate the molecule |
|
|
99
|
+
| Right-drag | Zoom in / out |
|
|
100
|
+
| Middle-drag | Pan the view |
|
|
101
|
+
| Scroll wheel | Increase / decrease label font size |
|
|
102
|
+
| Left-click | Select a single atom or bond |
|
|
103
|
+
| Ctrl + Left-click | Toggle multi-selection |
|
|
104
|
+
|
|
105
|
+
## Control Bar Options (`MoleculeViewerWidget`)
|
|
106
|
+
|
|
107
|
+
| Control | Default | Description |
|
|
108
|
+
|---------|---------|-------------|
|
|
109
|
+
| Show ADP | ✓ | Toggle ORTEP ellipsoid / isotropic sphere rendering |
|
|
110
|
+
| Show Labels | ✗ | Toggle non-hydrogen atom labels |
|
|
111
|
+
| Round Bonds | ✓ | Switch between 3D-shaded and flat bond drawing |
|
|
112
|
+
| Show Hydrogens | ✓ | Show or hide hydrogen atoms and their bonds |
|
|
113
|
+
| Bond Width | 3 | Stroke width for bonds (1–15 px) |
|
|
114
|
+
|
|
115
|
+
## API Overview
|
|
116
|
+
|
|
117
|
+
### `MoleculeViewerWidget(parent=None)`
|
|
118
|
+
|
|
119
|
+
A self-contained widget combining `MoleculeWidget` with the control bar described above.
|
|
120
|
+
|
|
121
|
+
- `load_file(path)` — load a structure file (format auto-detected from extension)
|
|
122
|
+
- `render_widget` — read-only property exposing the underlying `MoleculeWidget`
|
|
123
|
+
|
|
124
|
+
### `MoleculeWidget(parent=None)`
|
|
125
|
+
|
|
126
|
+
The low-level renderer widget. It is a plain `QWidget` subclass that you can drop into any layout.
|
|
127
|
+
Provide atom data directly via `open_molecule()` instead of loading a file through `MoleculeLoader`.
|
|
128
|
+
|
|
129
|
+
#### Qt Signals
|
|
130
|
+
|
|
131
|
+
| Signal | Signature | Emitted when |
|
|
132
|
+
|--------|-----------|--------------|
|
|
133
|
+
| `atomClicked` | `(label: str)` | The user clicks on an atom; `label` is the atom name (e.g. `"C1"`) |
|
|
134
|
+
| `bondClicked` | `(label1: str, label2: str)` | The user clicks on a bond; both atom labels are passed |
|
|
135
|
+
|
|
136
|
+
#### Data Methods
|
|
137
|
+
|
|
138
|
+
- **`open_molecule(atoms, cell=None, adps=None, keep_view=False)`**
|
|
139
|
+
Load a new set of atoms and reset (or optionally preserve) the view.
|
|
140
|
+
- `atoms` — list of `Atomtuple(label, type, x, y, z, part)` in Cartesian coordinates (Å)
|
|
141
|
+
- `cell` — optional `(a, b, c, α, β, γ)` tuple of unit-cell parameters (Å / °); required for ADP rendering
|
|
142
|
+
- `adps` — optional `dict` mapping atom labels to `(U11, U22, U33, U23, U13, U12)` ADP tensors
|
|
143
|
+
- `keep_view` — when `True`, the current zoom, pan, and rotation are preserved (useful for live updates)
|
|
144
|
+
|
|
145
|
+
- **`grow_molecule(atoms, cell=None, adps=None)`**
|
|
146
|
+
Replace the atom set while always preserving the current view.
|
|
147
|
+
Equivalent to calling `open_molecule(..., keep_view=True)`.
|
|
148
|
+
|
|
149
|
+
- **`clear()`**
|
|
150
|
+
Remove all atoms and bonds from the display.
|
|
151
|
+
|
|
152
|
+
#### Display Methods
|
|
153
|
+
|
|
154
|
+
- **`show_adps(value: bool)`**
|
|
155
|
+
Toggle ORTEP-style ADP ellipsoid rendering. When `False`, atoms are drawn as isotropic spheres.
|
|
156
|
+
|
|
157
|
+
- **`show_labels(value: bool)`**
|
|
158
|
+
Show or hide non-hydrogen atom labels.
|
|
159
|
+
|
|
160
|
+
- **`show_hydrogens(value: bool)`**
|
|
161
|
+
Show or hide hydrogen / deuterium atoms and their bonds.
|
|
162
|
+
|
|
163
|
+
- **`show_round_bonds(value: bool)`**
|
|
164
|
+
Switch between 3D-shaded cylinder-style bonds (`True`, default) and flat single-colour bonds (`False`).
|
|
165
|
+
|
|
166
|
+
- **`set_bond_width(width: int)`**
|
|
167
|
+
Set the stroke width for bonds in pixels (valid range: 1–15).
|
|
168
|
+
|
|
169
|
+
- **`setLabelFont(font_size: int)`**
|
|
170
|
+
Set the pixel size used for atom labels.
|
|
171
|
+
|
|
172
|
+
- **`set_background_color(color: QColor)`**
|
|
173
|
+
Change the widget background colour.
|
|
174
|
+
|
|
175
|
+
- **`reset_view()`**
|
|
176
|
+
Reset zoom, pan, and rotation to their defaults.
|
|
177
|
+
|
|
178
|
+
#### Example — feeding atom data directly
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from fastmolwidget import MoleculeWidget
|
|
182
|
+
from fastmolwidget.sdm import Atomtuple
|
|
183
|
+
|
|
184
|
+
mol = MoleculeWidget(parent=self)
|
|
185
|
+
|
|
186
|
+
atoms = [
|
|
187
|
+
Atomtuple(label="C1", type="C", x=0.0, y=0.0, z=0.0, part=0),
|
|
188
|
+
Atomtuple(label="O1", type="O", x=1.22, y=0.0, z=0.0, part=0),
|
|
189
|
+
Atomtuple(label="H1", type="H", x=-0.5, y=0.94, z=0.0, part=0),
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
# ADP tensors: {atom_label: (U11, U22, U33, U23, U13, U12)}
|
|
193
|
+
adps = {
|
|
194
|
+
"C1": (0.02, 0.02, 0.02, 0.0, 0.0, 0.0),
|
|
195
|
+
"O1": (0.03, 0.03, 0.03, 0.0, 0.0, 0.0),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
cell = (5.0, 5.0, 5.0, 90.0, 90.0, 90.0) # optional
|
|
199
|
+
|
|
200
|
+
mol.open_molecule(atoms=atoms, cell=cell, adps=adps)
|
|
201
|
+
mol.atomClicked.connect(lambda label: print(f"Selected: {label}"))
|
|
202
|
+
|
|
203
|
+
layout.addWidget(mol)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### `MoleculeLoader(widget)`
|
|
207
|
+
|
|
208
|
+
Format-aware loader that populates a `MoleculeWidget`.
|
|
209
|
+
|
|
210
|
+
- `load_file(path, keep_view=False)` — parse file and call `open_molecule` / `grow_molecule`
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
BSD 2-Clause License — see [LICENSE](LICENSE) for details.
|
|
215
|
+
|
|
216
|
+
© 2026 Daniel Kratzert
|
|
217
|
+
|
|
218
|
+
## Maintainer Release Workflow
|
|
219
|
+
|
|
220
|
+
The release workflow is tag-driven and currently publishes to **TestPyPI** only.
|
|
221
|
+
|
|
222
|
+
1. Ensure `project.version` in `pyproject.toml` is the version to publish.
|
|
223
|
+
2. Create and push a matching tag in the format `version-X.Y.Z`.
|
|
224
|
+
3. GitHub Actions builds sdist/wheel and uploads to TestPyPI.
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
git tag version-0.1.0
|
|
230
|
+
git push origin version-0.1.0
|
|
231
|
+
```
|
|
232
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fastmolwidget"
|
|
3
|
+
version = "0.5.1"
|
|
4
|
+
description = "A PyQt widget to display crystal structures"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { file = "LICENSE" }
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Daniel Kratzert", email = "dkratzert@gmx.de" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
keywords = ["chemistry", "crystallography", "qt", "widget", "molecule"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Intended Audience :: Science/Research",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Chemistry",
|
|
20
|
+
"Topic :: Software Development :: User Interfaces",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"gemmi>=0.7.5",
|
|
24
|
+
"numpy>=2.2.0",
|
|
25
|
+
"qtpy>=2.4.3",
|
|
26
|
+
"shelxfile>=22",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
pyside6 = [
|
|
31
|
+
"pyside6-essentials>=6.11.0",
|
|
32
|
+
]
|
|
33
|
+
pyqt6 = [
|
|
34
|
+
"pyqt6>=6.9.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/dkratzert/Fastmolwidget"
|
|
39
|
+
Repository = "https://github.com/dkratzert/Fastmolwidget"
|
|
40
|
+
Issues = "https://github.com/dkratzert/Fastmolwidget/issues"
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest",
|
|
45
|
+
"ruff",
|
|
46
|
+
"ty",
|
|
47
|
+
"pyside6-stubs",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
doc = [
|
|
51
|
+
"sphinx>=8.1.3",
|
|
52
|
+
"sphinx-rtd-theme",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[project.scripts]
|
|
56
|
+
fastmolwidget = "fastmolwidget:main"
|
|
57
|
+
|
|
58
|
+
[build-system]
|
|
59
|
+
requires = ["uv_build>=0.11.6,<0.12.0"]
|
|
60
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from fastmolwidget.viewer_widget import MoleculeViewerWidget
|
|
2
|
+
from fastmolwidget.molecule2D import MoleculeWidget
|
|
3
|
+
from fastmolwidget.loader import MoleculeLoader
|
|
4
|
+
|
|
5
|
+
__all__ = ["MoleculeViewerWidget", "MoleculeWidget", "MoleculeLoader"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
print("Hello from fastmolwidget!")
|