|
72 | 72 | # --- New job form --- |
73 | 73 | inputs = render_molecule_input() |
74 | 74 | if inputs is not None: |
75 | | - # Save uploaded XYZ to a temp file if provided |
| 75 | + # Save uploaded geometry to a temp XYZ file. |
| 76 | + # SDF/MOL files are converted to XYZ via RDKit. |
76 | 77 | xyz_path = None |
77 | 78 | if inputs["initial_xyz_bytes"] is not None: |
78 | | - tmp = tempfile.NamedTemporaryFile( |
79 | | - delete=False, suffix=".xyz", dir=tempfile.gettempdir() |
80 | | - ) |
81 | | - tmp.write(inputs["initial_xyz_bytes"].getvalue()) |
82 | | - tmp.close() |
83 | | - xyz_path = tmp.name |
| 79 | + uploaded = inputs["initial_xyz_bytes"] |
| 80 | + fname = uploaded.name.lower() |
| 81 | + |
| 82 | + if fname.endswith(".xyz"): |
| 83 | + tmp = tempfile.NamedTemporaryFile( |
| 84 | + delete=False, suffix=".xyz", dir=tempfile.gettempdir() |
| 85 | + ) |
| 86 | + tmp.write(uploaded.getvalue()) |
| 87 | + tmp.close() |
| 88 | + xyz_path = tmp.name |
| 89 | + elif fname.endswith((".sdf", ".mol")): |
| 90 | + from rdkit import Chem |
| 91 | + from rdkit.Chem import AllChem, rdmolfiles |
| 92 | + raw = uploaded.getvalue().decode("utf-8", errors="replace") |
| 93 | + mol = Chem.MolFromMolBlock(raw, removeHs=False) |
| 94 | + if mol is None: |
| 95 | + st.error("Could not parse the uploaded SDF/MOL file.") |
| 96 | + else: |
| 97 | + # Check if geometry is 3D (z-coords not all zero) |
| 98 | + conf = mol.GetConformer() |
| 99 | + zs = [conf.GetAtomPosition(i).z for i in range(mol.GetNumAtoms())] |
| 100 | + is_3d = any(abs(z) > 0.01 for z in zs) |
| 101 | + if not is_3d: |
| 102 | + st.warning( |
| 103 | + "The uploaded MOL/SDF appears to be **2D**. " |
| 104 | + "RDKit will embed it into 3D, but the resulting " |
| 105 | + "geometry may not be optimal for zwitterions. " |
| 106 | + "A computed 3D SDF is strongly recommended." |
| 107 | + ) |
| 108 | + mol = Chem.AddHs(mol) |
| 109 | + AllChem.EmbedMolecule(mol, randomSeed=42) |
| 110 | + AllChem.MMFFOptimizeMolecule(mol, mmffVariant="MMFF94s") |
| 111 | + # Write to XYZ |
| 112 | + tmp = tempfile.NamedTemporaryFile( |
| 113 | + delete=False, suffix=".xyz", dir=tempfile.gettempdir() |
| 114 | + ) |
| 115 | + tmp.write(Chem.MolToXYZBlock(mol).encode("utf-8")) |
| 116 | + tmp.close() |
| 117 | + xyz_path = tmp.name |
| 118 | + st.info(f"Converted {uploaded.name} → XYZ ({mol.GetNumAtoms()} atoms).") |
84 | 119 |
|
85 | 120 | state = js.create_job( |
86 | 121 | identifier=inputs["identifier"], |
|
0 commit comments