Skip to content

Commit 29dfa24

Browse files
committed
support sdf and mole files for upload
1 parent b702cbe commit 29dfa24

2 files changed

Lines changed: 47 additions & 10 deletions

File tree

streamlit_app/app.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,50 @@
7272
# --- New job form ---
7373
inputs = render_molecule_input()
7474
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.
7677
xyz_path = None
7778
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).")
84119

85120
state = js.create_job(
86121
identifier=inputs["identifier"],

streamlit_app/components/molecule_input.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ def render_molecule_input() -> Optional[dict]:
4949
)
5050

5151
initial_xyz = st.file_uploader(
52-
"Initial XYZ (optional)",
53-
type=["xyz"],
54-
help="Upload an XYZ file to skip conformer generation",
52+
"Initial geometry (optional)",
53+
type=["xyz", "sdf", "mol"],
54+
help="Upload a 3D geometry to skip conformer generation. "
55+
"Accepts XYZ, SDF, or MOL. A computed 3D SDF is ideal for "
56+
"zwitterions like betaine.",
5557
)
5658

5759
st.divider()

0 commit comments

Comments
 (0)