Nanny7 commited on
Commit
784d74f
·
1 Parent(s): 2780791

Switch to 3Dmol.js for interactive 3D orbital visualization

Browse files
Files changed (2) hide show
  1. app.py +33 -89
  2. force_rebuild.txt +1 -1
app.py CHANGED
@@ -253,100 +253,44 @@ def smiles_to_molecular_orbitals(smiles_input: str, name_input: str) -> str:
253
  if not Path(cube_file).exists():
254
  continue
255
 
256
- # Parse cube file
257
- x, y, z, values = parse_cube_file(cube_file)
258
 
259
- # Create matplotlib 3D visualization
260
- fig = plt.figure(figsize=(12, 5))
261
 
262
- # Create two subplots side by side
263
- ax1 = fig.add_subplot(121, projection='3d')
264
- ax2 = fig.add_subplot(122, projection='3d')
265
 
266
- # Get atom positions
267
- atom_coords = []
268
- atom_symbols = []
269
- for atom in mol_3d.GetAtoms():
270
- pos = mol_3d.GetConformer().GetAtomPosition(atom.GetIdx())
271
- atom_coords.append([pos.x, pos.y, pos.z])
272
- atom_symbols.append(atom.GetSymbol())
273
- atom_coords = np.array(atom_coords)
274
-
275
- # Plot atoms on both subplots
276
- for ax, title in [(ax1, f'{label} - View 1'), (ax2, f'{label} - View 2')]:
277
- # Color atoms by element
278
- for i, (coord, symbol) in enumerate(zip(atom_coords, atom_symbols)):
279
- if symbol == 'C':
280
- color = 'gray'
281
- size = 100
282
- elif symbol == 'O':
283
- color = 'red'
284
- size = 120
285
- elif symbol == 'N':
286
- color = 'blue'
287
- size = 120
288
- elif symbol == 'H':
289
- color = 'lightgray'
290
- size = 60
291
- else:
292
- color = 'purple'
293
- size = 100
294
- ax.scatter(*coord, c=color, s=size, alpha=0.9, edgecolors='black', linewidth=0.5)
295
- ax.text(*coord, symbol, fontsize=8)
296
-
297
- # Sample the cube data for contour visualization
298
- iso_val = 0.02
299
- # Find where values exceed threshold
300
- pos_mask = values > iso_val
301
- neg_mask = values < -iso_val
302
-
303
- # Create 3D mesh grid
304
- X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
305
-
306
- # Plot positive regions (blue)
307
- if np.any(pos_mask):
308
- pos_points = np.column_stack((X[pos_mask], Y[pos_mask], Z[pos_mask]))
309
- if len(pos_points) > 0:
310
- sample_idx = np.random.choice(len(pos_points), min(1000, len(pos_points)), replace=False)
311
- ax.scatter(pos_points[sample_idx, 0],
312
- pos_points[sample_idx, 1],
313
- pos_points[sample_idx, 2],
314
- c='blue', alpha=0.3, s=1, label='Positive lobe')
315
-
316
- # Plot negative regions (red)
317
- if np.any(neg_mask):
318
- neg_points = np.column_stack((X[neg_mask], Y[neg_mask], Z[neg_mask]))
319
- if len(neg_points) > 0:
320
- sample_idx = np.random.choice(len(neg_points), min(1000, len(neg_points)), replace=False)
321
- ax.scatter(neg_points[sample_idx, 0],
322
- neg_points[sample_idx, 1],
323
- neg_points[sample_idx, 2],
324
- c='red', alpha=0.3, s=1, label='Negative lobe')
325
-
326
- ax.set_xlabel('X (Å)')
327
- ax.set_ylabel('Y (Å)')
328
- ax.set_zlabel('Z (Å)')
329
- ax.set_title(title)
330
- ax.legend(fontsize=8)
331
-
332
- # Set different viewing angles
333
- ax1.view_init(elev=20, azim=45)
334
- ax2.view_init(elev=20, azim=135)
335
-
336
- plt.tight_layout()
337
-
338
- # Convert to base64 image
339
- buf = io.BytesIO()
340
- plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
341
- buf.seek(0)
342
- img_base64 = base64.b64encode(buf.read()).decode('utf-8')
343
- plt.close(fig)
344
 
345
  html_sections.append(
346
- f"<div style='margin: 20px 0;'>"
347
- f"<h3>{label} Orbital</h3>"
348
- f"<img src='data:image/png;base64,{img_base64}' style='max-width: 100%; height: auto;'/>"
349
- f"<p><small>Blue = positive orbital lobe, Red = negative orbital lobe</small></p>"
350
  f"</div>"
351
  )
352
 
 
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
263
+ import py3Dmol
 
264
 
265
+ # Create the viewer with explicit size
266
+ viewer_html = f"""
267
+ <div style="height: 600px; width: 100%; position: relative; margin: 20px 0;">
268
+ <div id="viewer_{label}" style="height: 100%; width: 100%;"></div>
269
+ <script src="https://3dmol.csb.pitt.edu/build/3Dmol-min.js"></script>
270
+ <script>
271
+ var viewer_{label} = $3Dmol.createViewer("viewer_{label}", {{backgroundColor: 'white'}});
272
+
273
+ // Add molecular structure
274
+ viewer_{label}.addModel(`{mol_block}`, "mol");
275
+ viewer_{label}.setStyle({{}}, {{stick: {{radius: 0.15}}, sphere: {{scale: 0.3}}}});
276
+
277
+ // Add cube data for orbital
278
+ var cubeData = `{cube_data}`;
279
+ viewer_{label}.addVolumetricData(cubeData, "cube", {{isoval: 0.02, color: "blue", alpha: 0.7}});
280
+ viewer_{label}.addVolumetricData(cubeData, "cube", {{isoval: -0.02, color: "red", alpha: 0.7}});
281
+
282
+ viewer_{label}.zoomTo();
283
+ viewer_{label}.render();
284
+ viewer_{label}.zoom(1.2);
285
+ </script>
286
+ </div>
287
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
  html_sections.append(
290
+ f"<div style='margin: 20px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px;'>"
291
+ f"<h3>{label} Orbital (Interactive 3D)</h3>"
292
+ f"<p><small>Blue = positive orbital lobe, Red = negative orbital lobe. Use mouse to rotate, scroll to zoom.</small></p>"
293
+ f"{viewer_html}"
294
  f"</div>"
295
  )
296
 
force_rebuild.txt CHANGED
@@ -1,2 +1,2 @@
1
  # Force rebuild
2
- 2025-11-08 v10
 
1
  # Force rebuild
2
+ 2025-11-08 v11