MoleditPy 2.2.0__py3-none-any.whl → 2.2.0a1__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.
- moleditpy/modules/constants.py +1 -1
- moleditpy/modules/main_window_main_init.py +13 -31
- moleditpy/modules/main_window_ui_manager.py +2 -21
- moleditpy/modules/plugin_interface.py +10 -1
- moleditpy/modules/plugin_manager.py +3 -0
- moleditpy/plugins/Analysis/ms_spectrum_neo.py +919 -0
- moleditpy/plugins/File/animated_xyz_giffer.py +583 -0
- moleditpy/plugins/File/cube_viewer.py +689 -0
- moleditpy/plugins/File/gaussian_fchk_freq_analyzer.py +1148 -0
- moleditpy/plugins/File/mapped_cube_viewer.py +552 -0
- moleditpy/plugins/File/orca_out_freq_analyzer.py +1226 -0
- moleditpy/plugins/File/paste_xyz.py +336 -0
- moleditpy/plugins/Input Generator/gaussian_input_generator_neo.py +930 -0
- moleditpy/plugins/Input Generator/orca_input_generator_neo.py +1028 -0
- moleditpy/plugins/Input Generator/orca_xyz2inp_gui.py +286 -0
- moleditpy/plugins/Optimization/all-trans_optimizer.py +65 -0
- moleditpy/plugins/Optimization/complex_molecule_untangler.py +268 -0
- moleditpy/plugins/Optimization/conf_search.py +224 -0
- moleditpy/plugins/Utility/atom_colorizer.py +547 -0
- moleditpy/plugins/Utility/console.py +163 -0
- moleditpy/plugins/Utility/pubchem_ressolver.py +244 -0
- moleditpy/plugins/Utility/vdw_radii_overlay.py +303 -0
- {moleditpy-2.2.0.dist-info → moleditpy-2.2.0a1.dist-info}/METADATA +1 -1
- {moleditpy-2.2.0.dist-info → moleditpy-2.2.0a1.dist-info}/RECORD +28 -11
- {moleditpy-2.2.0.dist-info → moleditpy-2.2.0a1.dist-info}/WHEEL +0 -0
- {moleditpy-2.2.0.dist-info → moleditpy-2.2.0a1.dist-info}/entry_points.txt +0 -0
- {moleditpy-2.2.0.dist-info → moleditpy-2.2.0a1.dist-info}/licenses/LICENSE +0 -0
- {moleditpy-2.2.0.dist-info → moleditpy-2.2.0a1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pyvista as pv
|
|
4
|
+
from PyQt6.QtWidgets import (QFileDialog, QDockWidget, QWidget, QVBoxLayout,
|
|
5
|
+
QSlider, QLabel, QHBoxLayout, QPushButton, QMessageBox, QDoubleSpinBox, QProgressBar, QComboBox, QCheckBox, QDialog, QLineEdit, QFormLayout, QDialogButtonBox)
|
|
6
|
+
from PyQt6.QtCore import Qt
|
|
7
|
+
|
|
8
|
+
# --- Dependency Management ---
|
|
9
|
+
try:
|
|
10
|
+
from rdkit import Chem
|
|
11
|
+
from rdkit import Geometry
|
|
12
|
+
except ImportError:
|
|
13
|
+
Chem = None
|
|
14
|
+
Geometry = None
|
|
15
|
+
|
|
16
|
+
# Periodic Table for Radii
|
|
17
|
+
try:
|
|
18
|
+
from moleditpy.modules.constants import pt
|
|
19
|
+
except ImportError:
|
|
20
|
+
try:
|
|
21
|
+
from modules.constants import pt
|
|
22
|
+
except ImportError:
|
|
23
|
+
try:
|
|
24
|
+
from rdkit import Chem
|
|
25
|
+
pt = Chem.GetPeriodicTable()
|
|
26
|
+
except:
|
|
27
|
+
pt = None
|
|
28
|
+
|
|
29
|
+
__version__="2025.12.25" # Fixed version
|
|
30
|
+
__author__="HiroYokoyama"
|
|
31
|
+
PLUGIN_NAME = "Mapped Cube Viewer"
|
|
32
|
+
|
|
33
|
+
# --- Core Logic: Robust Parser from cube_viewer.py ---
|
|
34
|
+
|
|
35
|
+
def parse_cube_data(filename):
|
|
36
|
+
"""
|
|
37
|
+
Robust Cube file parser copied from cube_viewer.py
|
|
38
|
+
"""
|
|
39
|
+
with open(filename, 'r') as f:
|
|
40
|
+
lines = f.readlines()
|
|
41
|
+
|
|
42
|
+
if len(lines) < 6:
|
|
43
|
+
raise ValueError("File too short to be a Cube file.")
|
|
44
|
+
|
|
45
|
+
# --- Header Parsing ---
|
|
46
|
+
tokens = lines[2].split()
|
|
47
|
+
n_atoms_raw = int(tokens[0])
|
|
48
|
+
n_atoms = abs(n_atoms_raw)
|
|
49
|
+
origin_raw = np.array([float(tokens[1]), float(tokens[2]), float(tokens[3])])
|
|
50
|
+
|
|
51
|
+
def parse_vec(line):
|
|
52
|
+
t = line.split()
|
|
53
|
+
return int(t[0]), np.array([float(t[1]), float(t[2]), float(t[3])])
|
|
54
|
+
|
|
55
|
+
nx, x_vec_raw = parse_vec(lines[3])
|
|
56
|
+
ny, y_vec_raw = parse_vec(lines[4])
|
|
57
|
+
nz, z_vec_raw = parse_vec(lines[5])
|
|
58
|
+
|
|
59
|
+
is_angstrom_header = (nx < 0 or ny < 0 or nz < 0)
|
|
60
|
+
nx, ny, nz = abs(nx), abs(ny), abs(nz)
|
|
61
|
+
|
|
62
|
+
# --- Atoms Parsing ---
|
|
63
|
+
atoms = []
|
|
64
|
+
current_line = 6
|
|
65
|
+
if n_atoms_raw < 0:
|
|
66
|
+
try:
|
|
67
|
+
parts = lines[current_line].split()
|
|
68
|
+
if len(parts) != 5:
|
|
69
|
+
current_line += 1
|
|
70
|
+
except:
|
|
71
|
+
current_line += 1
|
|
72
|
+
|
|
73
|
+
for _ in range(n_atoms):
|
|
74
|
+
line = lines[current_line].split()
|
|
75
|
+
current_line += 1
|
|
76
|
+
atomic_num = int(line[0])
|
|
77
|
+
try:
|
|
78
|
+
x, y, z = float(line[2]), float(line[3]), float(line[4])
|
|
79
|
+
except:
|
|
80
|
+
x, y, z = 0.0, 0.0, 0.0
|
|
81
|
+
atoms.append((atomic_num, np.array([x, y, z])))
|
|
82
|
+
|
|
83
|
+
# --- Volumetric Data Parsing (Skip Metadata) ---
|
|
84
|
+
while current_line < len(lines):
|
|
85
|
+
line_content = lines[current_line].strip()
|
|
86
|
+
parts = line_content.split()
|
|
87
|
+
if not parts:
|
|
88
|
+
current_line += 1
|
|
89
|
+
continue
|
|
90
|
+
if len(parts) < 6: # Heuristic: Data lines usually have 6 cols
|
|
91
|
+
current_line += 1
|
|
92
|
+
continue
|
|
93
|
+
try:
|
|
94
|
+
float(parts[0])
|
|
95
|
+
except ValueError:
|
|
96
|
+
current_line += 1
|
|
97
|
+
continue
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
full_str = " ".join(lines[current_line:])
|
|
101
|
+
try:
|
|
102
|
+
data_values = np.fromstring(full_str, sep=' ')
|
|
103
|
+
except:
|
|
104
|
+
data_values = np.array([])
|
|
105
|
+
|
|
106
|
+
expected_size = nx * ny * nz
|
|
107
|
+
actual_size = len(data_values)
|
|
108
|
+
|
|
109
|
+
if actual_size > expected_size:
|
|
110
|
+
excess = actual_size - expected_size
|
|
111
|
+
data_values = data_values[excess:] # Trim from start if header noise included
|
|
112
|
+
elif actual_size < expected_size:
|
|
113
|
+
pad = np.zeros(expected_size - actual_size)
|
|
114
|
+
data_values = np.concatenate((data_values, pad))
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"atoms": atoms,
|
|
118
|
+
"origin": origin_raw,
|
|
119
|
+
"x_vec": x_vec_raw,
|
|
120
|
+
"y_vec": y_vec_raw,
|
|
121
|
+
"z_vec": z_vec_raw,
|
|
122
|
+
"dims": (nx, ny, nz),
|
|
123
|
+
"data_flat": data_values,
|
|
124
|
+
"is_angstrom_header": is_angstrom_header
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
def build_grid_from_meta(meta):
|
|
128
|
+
"""
|
|
129
|
+
Reconstructs the PyVista grid.
|
|
130
|
+
Correctly handles standard Cube (Z-fast) mapping.
|
|
131
|
+
"""
|
|
132
|
+
nx, ny, nz = meta['dims']
|
|
133
|
+
origin = meta['origin'].copy()
|
|
134
|
+
x_vec = meta['x_vec'].copy()
|
|
135
|
+
y_vec = meta['y_vec'].copy()
|
|
136
|
+
z_vec = meta['z_vec'].copy()
|
|
137
|
+
atoms = []
|
|
138
|
+
|
|
139
|
+
BOHR_TO_ANGSTROM = 0.529177210903
|
|
140
|
+
convert_to_ang = True
|
|
141
|
+
if meta['is_angstrom_header']:
|
|
142
|
+
convert_to_ang = False
|
|
143
|
+
|
|
144
|
+
if convert_to_ang:
|
|
145
|
+
origin *= BOHR_TO_ANGSTROM
|
|
146
|
+
x_vec *= BOHR_TO_ANGSTROM
|
|
147
|
+
y_vec *= BOHR_TO_ANGSTROM
|
|
148
|
+
z_vec *= BOHR_TO_ANGSTROM
|
|
149
|
+
|
|
150
|
+
for anum, pos in meta['atoms']:
|
|
151
|
+
p = pos.copy()
|
|
152
|
+
if convert_to_ang:
|
|
153
|
+
p *= BOHR_TO_ANGSTROM
|
|
154
|
+
atoms.append((anum, p))
|
|
155
|
+
|
|
156
|
+
# Grid Points Generation
|
|
157
|
+
x_range = np.arange(nx)
|
|
158
|
+
y_range = np.arange(ny)
|
|
159
|
+
z_range = np.arange(nz)
|
|
160
|
+
|
|
161
|
+
gx, gy, gz = np.meshgrid(x_range, y_range, z_range, indexing='ij')
|
|
162
|
+
|
|
163
|
+
# Flatten using Fortran order (X-fast) for VTK Structure
|
|
164
|
+
# !!! IMPORTANT FIX: Must match Flatten order of data !!!
|
|
165
|
+
gx_f = gx.flatten(order='F')
|
|
166
|
+
gy_f = gy.flatten(order='F')
|
|
167
|
+
gz_f = gz.flatten(order='F')
|
|
168
|
+
|
|
169
|
+
points = (origin +
|
|
170
|
+
np.outer(gx_f, x_vec) +
|
|
171
|
+
np.outer(gy_f, y_vec) +
|
|
172
|
+
np.outer(gz_f, z_vec))
|
|
173
|
+
|
|
174
|
+
grid = pv.StructuredGrid()
|
|
175
|
+
grid.points = points
|
|
176
|
+
grid.dimensions = [nx, ny, nz]
|
|
177
|
+
|
|
178
|
+
# Data Mapping
|
|
179
|
+
raw_data = meta['data_flat']
|
|
180
|
+
|
|
181
|
+
# Standard Cube: Z-Fast (C-order reshape)
|
|
182
|
+
# Then flatten F-order to match the X-fast points we just generated
|
|
183
|
+
vol_3d = raw_data.reshape((nx, ny, nz), order='C')
|
|
184
|
+
|
|
185
|
+
# Key name "property_values" for Mapped Viewer
|
|
186
|
+
grid.point_data["property_values"] = vol_3d.flatten(order='F')
|
|
187
|
+
|
|
188
|
+
return {"atoms": atoms}, grid
|
|
189
|
+
|
|
190
|
+
def read_cube(filename):
|
|
191
|
+
meta = parse_cube_data(filename)
|
|
192
|
+
return build_grid_from_meta(meta)
|
|
193
|
+
|
|
194
|
+
# --- Dialog & Widget (Identical Logic, just ensured imports) ---
|
|
195
|
+
|
|
196
|
+
class MappedCubeSetupDialog(QDialog):
|
|
197
|
+
def __init__(self, parent=None):
|
|
198
|
+
super().__init__(parent)
|
|
199
|
+
self.setWindowTitle("Mapped Cube Setup")
|
|
200
|
+
self.surface_file = None
|
|
201
|
+
self.property_file = None
|
|
202
|
+
|
|
203
|
+
layout = QVBoxLayout()
|
|
204
|
+
self.setLayout(layout)
|
|
205
|
+
|
|
206
|
+
form = QFormLayout()
|
|
207
|
+
|
|
208
|
+
# File 1: Surface
|
|
209
|
+
self.le_surf = QLineEdit()
|
|
210
|
+
btn_surf = QPushButton("Browse")
|
|
211
|
+
btn_surf.clicked.connect(self.browse_surf)
|
|
212
|
+
h1 = QHBoxLayout()
|
|
213
|
+
h1.addWidget(self.le_surf)
|
|
214
|
+
h1.addWidget(btn_surf)
|
|
215
|
+
form.addRow("Surface (Density):", h1)
|
|
216
|
+
|
|
217
|
+
# File 2: Property
|
|
218
|
+
self.le_prop = QLineEdit()
|
|
219
|
+
btn_prop = QPushButton("Browse")
|
|
220
|
+
btn_prop.clicked.connect(self.browse_prop)
|
|
221
|
+
h2 = QHBoxLayout()
|
|
222
|
+
h2.addWidget(self.le_prop)
|
|
223
|
+
h2.addWidget(btn_prop)
|
|
224
|
+
form.addRow("Property (Color):", h2)
|
|
225
|
+
|
|
226
|
+
layout.addLayout(form)
|
|
227
|
+
|
|
228
|
+
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
|
229
|
+
buttons.accepted.connect(self.accept)
|
|
230
|
+
buttons.rejected.connect(self.reject)
|
|
231
|
+
layout.addWidget(buttons)
|
|
232
|
+
|
|
233
|
+
def browse_surf(self):
|
|
234
|
+
f, _ = QFileDialog.getOpenFileName(self, "Select Surface Cube", "", "Cube (*.cube *.cub)")
|
|
235
|
+
if f: self.le_surf.setText(f)
|
|
236
|
+
|
|
237
|
+
def browse_prop(self):
|
|
238
|
+
f, _ = QFileDialog.getOpenFileName(self, "Select Property Cube", "", "Cube (*.cube *.cub)")
|
|
239
|
+
if f: self.le_prop.setText(f)
|
|
240
|
+
|
|
241
|
+
def accept(self):
|
|
242
|
+
self.surface_file = self.le_surf.text()
|
|
243
|
+
self.property_file = self.le_prop.text()
|
|
244
|
+
if not os.path.exists(self.surface_file):
|
|
245
|
+
QMessageBox.warning(self, "Error", "Surface file not found.")
|
|
246
|
+
return
|
|
247
|
+
if not os.path.exists(self.property_file):
|
|
248
|
+
QMessageBox.warning(self, "Error", "Property file not found.")
|
|
249
|
+
return
|
|
250
|
+
super().accept()
|
|
251
|
+
|
|
252
|
+
class MappedWidget(QWidget):
|
|
253
|
+
def __init__(self, mw, dock, grid_surf, grid_prop):
|
|
254
|
+
super().__init__()
|
|
255
|
+
self.mw = mw
|
|
256
|
+
self.dock = dock
|
|
257
|
+
self.grid_surf = grid_surf
|
|
258
|
+
self.grid_prop = grid_prop
|
|
259
|
+
self.actor = None
|
|
260
|
+
|
|
261
|
+
self.layout = QVBoxLayout()
|
|
262
|
+
self.setLayout(self.layout)
|
|
263
|
+
|
|
264
|
+
# Controls
|
|
265
|
+
self.layout.addWidget(QLabel("<b>Surface Isovalue:</b>"))
|
|
266
|
+
self.iso_spin = QDoubleSpinBox()
|
|
267
|
+
self.iso_spin.setRange(-1000, 1000)
|
|
268
|
+
self.iso_spin.setDecimals(5)
|
|
269
|
+
self.iso_spin.setSingleStep(0.001)
|
|
270
|
+
|
|
271
|
+
# Default ISO
|
|
272
|
+
vals = self.grid_surf.point_data.get("property_values", np.array([0]))
|
|
273
|
+
# Robust default calculation
|
|
274
|
+
if len(vals) > 0:
|
|
275
|
+
if vals.max() > 0.1:
|
|
276
|
+
default_iso = 0.00200
|
|
277
|
+
else:
|
|
278
|
+
default_iso = float(np.mean(vals))
|
|
279
|
+
else:
|
|
280
|
+
default_iso = 0.002
|
|
281
|
+
|
|
282
|
+
self.iso_spin.setValue(default_iso)
|
|
283
|
+
self.iso_spin.setKeyboardTracking(False)
|
|
284
|
+
self.iso_spin.valueChanged.connect(lambda: self.update_mesh(auto_fit=False))
|
|
285
|
+
self.layout.addWidget(self.iso_spin)
|
|
286
|
+
|
|
287
|
+
# Color Range
|
|
288
|
+
self.layout.addWidget(QLabel("<b>Property Color Range:</b>"))
|
|
289
|
+
|
|
290
|
+
cbox = QHBoxLayout()
|
|
291
|
+
cbox.addWidget(QLabel("Style:"))
|
|
292
|
+
self.cmap_combo = QComboBox()
|
|
293
|
+
self.cmap_combo.addItems([
|
|
294
|
+
'jet_r', 'jet',
|
|
295
|
+
'rainbow_r', 'rainbow',
|
|
296
|
+
'bwr_r', 'bwr',
|
|
297
|
+
'seismic_r', 'seismic',
|
|
298
|
+
'coolwarm_r', 'coolwarm',
|
|
299
|
+
'viridis_r', 'viridis',
|
|
300
|
+
'plasma_r', 'plasma',
|
|
301
|
+
'magma_r', 'magma',
|
|
302
|
+
'inferno_r', 'inferno'
|
|
303
|
+
])
|
|
304
|
+
self.cmap_combo.setCurrentText('jet_r')
|
|
305
|
+
self.cmap_combo.currentTextChanged.connect(lambda: self.update_mesh(auto_fit=False))
|
|
306
|
+
cbox.addWidget(self.cmap_combo)
|
|
307
|
+
self.layout.addLayout(cbox)
|
|
308
|
+
|
|
309
|
+
rbox = QHBoxLayout()
|
|
310
|
+
|
|
311
|
+
pvals = self.grid_prop.point_data.get("property_values", np.array([-0.1, 0.1]))
|
|
312
|
+
if len(pvals) > 0:
|
|
313
|
+
vmin, vmax = pvals.min(), pvals.max()
|
|
314
|
+
else:
|
|
315
|
+
vmin, vmax = -0.1, 0.1
|
|
316
|
+
|
|
317
|
+
self.min_spin = QDoubleSpinBox()
|
|
318
|
+
self.min_spin.setRange(-1e20, 1e20)
|
|
319
|
+
self.min_spin.setDecimals(6)
|
|
320
|
+
self.min_spin.setSingleStep(0.01)
|
|
321
|
+
self.min_spin.setKeyboardTracking(False)
|
|
322
|
+
self.min_spin.setValue(vmin)
|
|
323
|
+
self.min_spin.valueChanged.connect(lambda: self.update_mesh(auto_fit=False))
|
|
324
|
+
|
|
325
|
+
self.max_spin = QDoubleSpinBox()
|
|
326
|
+
self.max_spin.setRange(-1e20, 1e20)
|
|
327
|
+
self.max_spin.setDecimals(6)
|
|
328
|
+
self.max_spin.setSingleStep(0.01)
|
|
329
|
+
self.max_spin.setKeyboardTracking(False)
|
|
330
|
+
self.max_spin.setValue(vmax)
|
|
331
|
+
self.max_spin.valueChanged.connect(lambda: self.update_mesh(auto_fit=False))
|
|
332
|
+
|
|
333
|
+
rbox.addWidget(QLabel("Min:"))
|
|
334
|
+
rbox.addWidget(self.min_spin)
|
|
335
|
+
rbox.addWidget(QLabel("Max:"))
|
|
336
|
+
rbox.addWidget(self.max_spin)
|
|
337
|
+
self.layout.addLayout(rbox)
|
|
338
|
+
|
|
339
|
+
btn_fit = QPushButton("Fit Range to Surface")
|
|
340
|
+
btn_fit.clicked.connect(lambda: self.update_mesh(auto_fit=True))
|
|
341
|
+
self.layout.addWidget(btn_fit)
|
|
342
|
+
|
|
343
|
+
# Opacity
|
|
344
|
+
self.layout.addWidget(QLabel("<b>Opacity:</b>"))
|
|
345
|
+
self.opacity_spin = QDoubleSpinBox()
|
|
346
|
+
self.opacity_spin.setRange(0, 1)
|
|
347
|
+
self.opacity_spin.setSingleStep(0.1)
|
|
348
|
+
self.opacity_spin.setValue(0.4)
|
|
349
|
+
self.opacity_spin.valueChanged.connect(lambda: self.update_mesh(auto_fit=False))
|
|
350
|
+
self.layout.addWidget(self.opacity_spin)
|
|
351
|
+
|
|
352
|
+
# Exports
|
|
353
|
+
ebox = QHBoxLayout()
|
|
354
|
+
btn_view = QPushButton("Export View")
|
|
355
|
+
btn_view.clicked.connect(self.export_view)
|
|
356
|
+
ebox.addWidget(btn_view)
|
|
357
|
+
|
|
358
|
+
btn_cbar = QPushButton("Export Color Bar")
|
|
359
|
+
btn_cbar.clicked.connect(self.export_colorbar)
|
|
360
|
+
ebox.addWidget(btn_cbar)
|
|
361
|
+
self.layout.addLayout(ebox)
|
|
362
|
+
|
|
363
|
+
# Options
|
|
364
|
+
self.check_transparent = QCheckBox("Transparent Background")
|
|
365
|
+
self.check_transparent.setChecked(True)
|
|
366
|
+
self.layout.addWidget(self.check_transparent)
|
|
367
|
+
|
|
368
|
+
self.layout.addStretch()
|
|
369
|
+
btn = QPushButton("Close")
|
|
370
|
+
btn.clicked.connect(self.close_plugin)
|
|
371
|
+
self.layout.addWidget(btn)
|
|
372
|
+
|
|
373
|
+
self.update_mesh(auto_fit=True)
|
|
374
|
+
|
|
375
|
+
def export_view(self):
|
|
376
|
+
f, _ = QFileDialog.getSaveFileName(self, "Save View", "mapped_view.png", "PNG Image (*.png)")
|
|
377
|
+
if f:
|
|
378
|
+
try:
|
|
379
|
+
self.mw.plotter.screenshot(f, transparent_background=self.check_transparent.isChecked())
|
|
380
|
+
QMessageBox.information(self, "Success", f"Saved View to {f}")
|
|
381
|
+
except Exception as e:
|
|
382
|
+
QMessageBox.critical(self, "Error", f"Save failed: {e}")
|
|
383
|
+
|
|
384
|
+
def export_colorbar(self):
|
|
385
|
+
f, _ = QFileDialog.getSaveFileName(self, "Save Color Bar", "mapped_colorbar.png", "PNG Image (*.png)")
|
|
386
|
+
if f:
|
|
387
|
+
try:
|
|
388
|
+
pl = pv.Plotter(off_screen=True, window_size=(2000, 400))
|
|
389
|
+
if self.check_transparent.isChecked():
|
|
390
|
+
pl.set_background(None)
|
|
391
|
+
else:
|
|
392
|
+
pl.set_background('white')
|
|
393
|
+
|
|
394
|
+
vmin = self.min_spin.value()
|
|
395
|
+
vmax = self.max_spin.value()
|
|
396
|
+
cmap = self.cmap_combo.currentText()
|
|
397
|
+
|
|
398
|
+
mesh = pv.Box()
|
|
399
|
+
mesh.point_data['scalars'] = np.linspace(vmin, vmax, mesh.n_points)
|
|
400
|
+
|
|
401
|
+
pl.add_mesh(mesh,
|
|
402
|
+
scalars='scalars',
|
|
403
|
+
cmap=cmap,
|
|
404
|
+
clim=[vmin, vmax],
|
|
405
|
+
show_scalar_bar=True,
|
|
406
|
+
opacity=0.0,
|
|
407
|
+
scalar_bar_args={
|
|
408
|
+
"title": "",
|
|
409
|
+
"vertical": False,
|
|
410
|
+
"n_labels": 5,
|
|
411
|
+
"italic": False,
|
|
412
|
+
"bold": False,
|
|
413
|
+
"title_font_size": 1,
|
|
414
|
+
"label_font_size": 50,
|
|
415
|
+
"color": "black",
|
|
416
|
+
"height": 0.4,
|
|
417
|
+
"width": 0.8,
|
|
418
|
+
"position_x": 0.1,
|
|
419
|
+
"position_y": 0.3
|
|
420
|
+
}
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
if hasattr(pl, 'scalar_bar'):
|
|
424
|
+
try:
|
|
425
|
+
sb = pl.scalar_bar
|
|
426
|
+
sb.GetLabelTextProperty().SetJustificationToCentered()
|
|
427
|
+
except: pass
|
|
428
|
+
|
|
429
|
+
pl.screenshot(f, transparent_background=self.check_transparent.isChecked())
|
|
430
|
+
pl.close()
|
|
431
|
+
QMessageBox.information(self, "Success", f"Saved Color Bar to {f}")
|
|
432
|
+
except Exception as e:
|
|
433
|
+
import traceback
|
|
434
|
+
traceback.print_exc()
|
|
435
|
+
QMessageBox.critical(self, "Error", f"Save failed: {e}")
|
|
436
|
+
|
|
437
|
+
def update_mesh(self, auto_fit=False):
|
|
438
|
+
try:
|
|
439
|
+
iso_val = self.iso_spin.value()
|
|
440
|
+
opacity = self.opacity_spin.value()
|
|
441
|
+
|
|
442
|
+
iso = self.grid_surf.contour([iso_val], scalars="property_values")
|
|
443
|
+
if iso.n_points == 0:
|
|
444
|
+
return
|
|
445
|
+
|
|
446
|
+
mapped = iso.sample(self.grid_prop)
|
|
447
|
+
|
|
448
|
+
clim = [self.min_spin.value(), self.max_spin.value()]
|
|
449
|
+
if auto_fit:
|
|
450
|
+
mvals = mapped.point_data.get("property_values")
|
|
451
|
+
if mvals is not None and len(mvals) > 0:
|
|
452
|
+
vmin, vmax = mvals.min(), mvals.max()
|
|
453
|
+
if vmax - vmin < 1e-9: vmax += 0.001
|
|
454
|
+
clim = [vmin, vmax]
|
|
455
|
+
|
|
456
|
+
self.min_spin.blockSignals(True); self.min_spin.setValue(vmin); self.min_spin.blockSignals(False)
|
|
457
|
+
self.max_spin.blockSignals(True); self.max_spin.setValue(vmax); self.max_spin.blockSignals(False)
|
|
458
|
+
|
|
459
|
+
if self.actor:
|
|
460
|
+
self.mw.plotter.remove_actor(self.actor)
|
|
461
|
+
|
|
462
|
+
self.actor = self.mw.plotter.add_mesh(
|
|
463
|
+
mapped,
|
|
464
|
+
scalars="property_values",
|
|
465
|
+
cmap=self.cmap_combo.currentText(),
|
|
466
|
+
clim=clim,
|
|
467
|
+
smooth_shading=True,
|
|
468
|
+
opacity=opacity,
|
|
469
|
+
name="mapped_mesh_dock",
|
|
470
|
+
scalar_bar_args={'title': ''}
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
self.mw.plotter.render()
|
|
474
|
+
|
|
475
|
+
except Exception as e:
|
|
476
|
+
print(f"Update Error: {e}")
|
|
477
|
+
|
|
478
|
+
def close_plugin(self):
|
|
479
|
+
try:
|
|
480
|
+
self.mw.plotter.remove_actor(self.actor)
|
|
481
|
+
self.mw.plotter.render()
|
|
482
|
+
except: pass
|
|
483
|
+
if self.dock:
|
|
484
|
+
self.dock.close()
|
|
485
|
+
try:
|
|
486
|
+
self.mw.clear_all()
|
|
487
|
+
except: pass
|
|
488
|
+
self.close()
|
|
489
|
+
|
|
490
|
+
# --- Entry Point ---
|
|
491
|
+
def run(mw):
|
|
492
|
+
dlg = MappedCubeSetupDialog(mw)
|
|
493
|
+
if dlg.exec() != QDialog.DialogCode.Accepted:
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
s_file = dlg.surface_file
|
|
497
|
+
p_file = dlg.property_file
|
|
498
|
+
|
|
499
|
+
if not s_file or not p_file: return
|
|
500
|
+
|
|
501
|
+
try:
|
|
502
|
+
from PyQt6.QtWidgets import QApplication, QProgressDialog
|
|
503
|
+
|
|
504
|
+
progress = QProgressDialog("Reading Cube Files...", "Cancel", 0, 2, mw)
|
|
505
|
+
progress.setWindowModality(Qt.WindowModality.WindowModal)
|
|
506
|
+
progress.show()
|
|
507
|
+
|
|
508
|
+
# Using the robust read_cube logic
|
|
509
|
+
meta1, grid_surf = read_cube(s_file)
|
|
510
|
+
progress.setValue(1)
|
|
511
|
+
QApplication.processEvents()
|
|
512
|
+
|
|
513
|
+
meta2, grid_prop = read_cube(p_file)
|
|
514
|
+
progress.setValue(2)
|
|
515
|
+
QApplication.processEvents()
|
|
516
|
+
progress.close()
|
|
517
|
+
|
|
518
|
+
# Visualize Atoms
|
|
519
|
+
atoms = meta1['atoms']
|
|
520
|
+
if Chem:
|
|
521
|
+
s = f"{len(atoms)}\n\n"
|
|
522
|
+
for n, p in atoms:
|
|
523
|
+
sym = pt.GetElementSymbol(n) if pt else "C"
|
|
524
|
+
s += f"{sym} {p[0]} {p[1]} {p[2]}\n"
|
|
525
|
+
mol = Chem.MolFromXYZBlock(s)
|
|
526
|
+
try:
|
|
527
|
+
from rdkit.Chem import rdDetermineBonds
|
|
528
|
+
rdDetermineBonds.DetermineConnectivity(mol)
|
|
529
|
+
except: pass
|
|
530
|
+
|
|
531
|
+
mw.current_mol = mol
|
|
532
|
+
if hasattr(mw, 'draw_molecule_3d'):
|
|
533
|
+
mw.draw_molecule_3d(mol)
|
|
534
|
+
|
|
535
|
+
if hasattr(mw, 'main_window_ui_manager'):
|
|
536
|
+
try: mw.main_window_ui_manager._enter_3d_viewer_ui_mode()
|
|
537
|
+
except: pass
|
|
538
|
+
|
|
539
|
+
for d in mw.findChildren(QDockWidget):
|
|
540
|
+
if d.windowTitle() == "Mapped Viewer":
|
|
541
|
+
d.close()
|
|
542
|
+
|
|
543
|
+
dock = QDockWidget("Mapped Viewer", mw)
|
|
544
|
+
dock.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea)
|
|
545
|
+
widget = MappedWidget(mw, dock, grid_surf, grid_prop)
|
|
546
|
+
dock.setWidget(widget)
|
|
547
|
+
mw.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock)
|
|
548
|
+
|
|
549
|
+
except Exception as e:
|
|
550
|
+
import traceback
|
|
551
|
+
traceback.print_exc()
|
|
552
|
+
QMessageBox.critical(mw, "Error", f"Failed:\n{e}")
|