Nanny7 commited on
Commit
860c7cd
·
1 Parent(s): ec4b0c3

Use matplotlib with 4 viewing angles for reliable orbital visualization

Browse files
Files changed (2) hide show
  1. app.py +87 -20
  2. force_rebuild.txt +1 -1
app.py CHANGED
@@ -253,35 +253,102 @@ def smiles_to_molecular_orbitals(smiles_input: str, name_input: str) -> str:
253
  if not Path(cube_file).exists():
254
  continue
255
 
256
- # Read cube file content
257
- cube_data = Path(cube_file).read_text()
258
 
259
- # Get molecular structure
260
- mol_block = Chem.MolToMolBlock(mol_3d)
261
 
262
- # Create py3Dmol viewer using the Python library
263
- import py3Dmol
 
 
 
 
 
 
264
 
265
- view = py3Dmol.view(width=800, height=600)
 
266
 
267
- # Add molecular structure
268
- view.addModel(mol_block, "sdf")
269
- view.setStyle({'stick': {'radius': 0.15}, 'sphere': {'scale': 0.3}})
 
270
 
271
- # Add orbital isosurfaces from cube data
272
- view.addVolumetricData(cube_data, "cube", {'isoval': 0.02, 'color': "blue", 'alpha': 0.75})
273
- view.addVolumetricData(cube_data, "cube", {'isoval': -0.02, 'color': "red", 'alpha': 0.75})
 
 
 
 
 
274
 
275
- view.zoomTo()
 
 
 
 
276
 
277
- # Generate standalone HTML
278
- viewer_html = view._make_html()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
  html_sections.append(
281
- f"<div style='margin: 20px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px;'>"
282
- f"<h3>{label} Orbital (Interactive 3D)</h3>"
283
- f"<p><small>Blue = positive orbital lobe, Red = negative orbital lobe. Use mouse to rotate, scroll to zoom.</small></p>"
284
- f"<div style='width: 100%; height: 600px;'>{viewer_html}</div>"
285
  f"</div>"
286
  )
287
 
 
253
  if not Path(cube_file).exists():
254
  continue
255
 
256
+ # Read cube file and parse
257
+ x, y, z, values = parse_cube_file(cube_file)
258
 
259
+ # Create matplotlib figure with 4 viewing angles
260
+ fig = plt.figure(figsize=(16, 12))
261
 
262
+ # Get atom positions
263
+ atom_coords = []
264
+ atom_symbols = []
265
+ for atom in mol_3d.GetAtoms():
266
+ pos = mol_3d.GetConformer().GetAtomPosition(atom.GetIdx())
267
+ atom_coords.append([pos.x, pos.y, pos.z])
268
+ atom_symbols.append(atom.GetSymbol())
269
+ atom_coords = np.array(atom_coords)
270
 
271
+ # Create 3D mesh grid
272
+ X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
273
 
274
+ # Sample points for orbital lobes
275
+ iso_val = 0.02
276
+ pos_mask = values > iso_val
277
+ neg_mask = values < -iso_val
278
 
279
+ # Get positive and negative points
280
+ pos_points = None
281
+ neg_points = None
282
+ if np.any(pos_mask):
283
+ pos_points = np.column_stack((X[pos_mask], Y[pos_mask], Z[pos_mask]))
284
+ if len(pos_points) > 2000:
285
+ sample_idx = np.random.choice(len(pos_points), 2000, replace=False)
286
+ pos_points = pos_points[sample_idx]
287
 
288
+ if np.any(neg_mask):
289
+ neg_points = np.column_stack((X[neg_mask], Y[neg_mask], Z[neg_mask]))
290
+ if len(neg_points) > 2000:
291
+ sample_idx = np.random.choice(len(neg_points), 2000, replace=False)
292
+ neg_points = neg_points[sample_idx]
293
 
294
+ # Create 4 subplots with different angles
295
+ viewing_angles = [
296
+ (30, 45, 'Front-Right'),
297
+ (30, 135, 'Front-Left'),
298
+ (30, 225, 'Back-Left'),
299
+ (30, 315, 'Back-Right')
300
+ ]
301
+
302
+ for idx, (elev, azim, view_name) in enumerate(viewing_angles):
303
+ ax = fig.add_subplot(2, 2, idx + 1, projection='3d')
304
+
305
+ # Plot atoms
306
+ for coord, symbol in zip(atom_coords, atom_symbols):
307
+ if symbol == 'C':
308
+ color, size = 'black', 200
309
+ elif symbol == 'O':
310
+ color, size = 'red', 250
311
+ elif symbol == 'N':
312
+ color, size = 'blue', 250
313
+ elif symbol == 'H':
314
+ color, size = 'lightgray', 100
315
+ else:
316
+ color, size = 'purple', 150
317
+ ax.scatter(*coord, c=color, s=size, alpha=0.9, edgecolors='white', linewidth=1.5)
318
+ ax.text(*coord, f' {symbol}', fontsize=10, fontweight='bold')
319
+
320
+ # Plot orbital lobes
321
+ if pos_points is not None:
322
+ ax.scatter(pos_points[:, 0], pos_points[:, 1], pos_points[:, 2],
323
+ c='blue', alpha=0.4, s=3, label='Positive (+)')
324
+
325
+ if neg_points is not None:
326
+ ax.scatter(neg_points[:, 0], neg_points[:, 1], neg_points[:, 2],
327
+ c='red', alpha=0.4, s=3, label='Negative (-)')
328
+
329
+ ax.set_xlabel('X (Å)', fontsize=10, fontweight='bold')
330
+ ax.set_ylabel('Y (Å)', fontsize=10, fontweight='bold')
331
+ ax.set_zlabel('Z (Å)', fontsize=10, fontweight='bold')
332
+ ax.set_title(f'{view_name} View', fontsize=12, fontweight='bold')
333
+ ax.view_init(elev=elev, azim=azim)
334
+ ax.legend(loc='upper right', fontsize=9)
335
+ ax.grid(True, alpha=0.3)
336
+
337
+ plt.suptitle(f'{label} Orbital - Multiple Views', fontsize=16, fontweight='bold', y=0.98)
338
+ plt.tight_layout()
339
+
340
+ # Convert to base64 image
341
+ buf = io.BytesIO()
342
+ plt.savefig(buf, format='png', dpi=150, bbox_inches='tight', facecolor='white')
343
+ buf.seek(0)
344
+ img_base64 = base64.b64encode(buf.read()).decode('utf-8')
345
+ plt.close(fig)
346
 
347
  html_sections.append(
348
+ f"<div style='margin: 20px 0; padding: 15px; background: white; border: 2px solid #ddd; border-radius: 8px;'>"
349
+ f"<h3 style='color: #333; margin-top: 0;'>{label} Orbital Visualization</h3>"
350
+ f"<img src='data:image/png;base64,{img_base64}' style='max-width: 100%; height: auto; border-radius: 4px;'/>"
351
+ f"<p style='margin-bottom: 0; color: #666;'><small><strong>Blue</strong> = positive orbital lobe | <strong>Red</strong> = negative orbital lobe | Four different viewing angles shown</small></p>"
352
  f"</div>"
353
  )
354
 
force_rebuild.txt CHANGED
@@ -1,2 +1,2 @@
1
  # Force rebuild
2
- 2025-11-08 v12
 
1
  # Force rebuild
2
+ 2025-11-08 v13