use crate::{
MolHandle, WASM_MAX_ATOMS, WASM_MAX_BATCH_ITEMS, WASM_MAX_INPUT_BYTES,
WASM_MAX_JSON_STRING_BYTES, enforce_wasm_molecule_size, escape_json_string,
parse_smiles_json_array, parse_wasm_string_json_array,
};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn generate_3d_pdb(mol: &MolHandle) -> String {
let coords = chematic_3d::generate_coords(&mol.inner);
chematic_3d::write_pdb(&mol.inner, &coords)
}
#[wasm_bindgen]
pub fn generate_3d_minimized_pdb(mol: &MolHandle) -> String {
let coords = chematic_3d::generate_coords(&mol.inner);
let minimized = chematic_3d::minimize(&mol.inner, coords);
chematic_3d::write_pdb(&mol.inner, &minimized)
}
#[wasm_bindgen]
pub fn generate_3d_etkdg_pdb(mol: &MolHandle) -> String {
let coords = chematic_3d::generate_coords_etkdg(&mol.inner);
chematic_3d::write_pdb(&mol.inner, &coords)
}
#[wasm_bindgen]
pub fn generate_3d_etkdg_minimized_pdb(mol: &MolHandle) -> String {
let coords = chematic_3d::generate_coords_etkdg(&mol.inner);
let minimized = chematic_3d::minimize(&mol.inner, coords);
chematic_3d::write_pdb(&mol.inner, &minimized)
}
#[wasm_bindgen]
pub fn mol_from_v3000_block(block: &str) -> Result<MolHandle, JsValue> {
if block.len() > WASM_MAX_INPUT_BYTES {
return Err(JsValue::from_str("V3000 block too large"));
}
let (mol, _meta) =
chematic_mol::parse_mol_v3000(block).map_err(|e| JsValue::from_str(&e.to_string()))?;
if mol.atom_count() > WASM_MAX_ATOMS {
return Err(JsValue::from_str(&format!(
"molecule too large (max {} atoms)",
WASM_MAX_ATOMS
)));
}
Ok(MolHandle {
inner: std::rc::Rc::new(mol),
})
}
#[wasm_bindgen]
pub fn mol_from_sdf_block(block: &str) -> Result<MolHandle, JsValue> {
if block.len() > WASM_MAX_INPUT_BYTES {
return Err(JsValue::from_str("MOL block too large"));
}
let (mol, _meta) =
chematic_mol::parse_mol(block).map_err(|e| JsValue::from_str(&e.to_string()))?;
if mol.atom_count() > WASM_MAX_ATOMS {
return Err(JsValue::from_str(&format!(
"molecule too large (max {} atoms)",
WASM_MAX_ATOMS
)));
}
Ok(MolHandle {
inner: std::rc::Rc::new(mol),
})
}
#[wasm_bindgen]
pub fn to_mol_block(mol: &MolHandle) -> String {
let meta = chematic_mol::MolMetadata::default();
let layout = chematic_depict::compute_layout(&mol.inner);
const SCALE: f64 = 1.5 / 40.0;
let coords: Vec<(f64, f64)> = (0..mol.inner.atom_count())
.map(|i| {
let p = layout.get(chematic_core::AtomIdx(i as u32));
(p.x * SCALE, -p.y * SCALE)
})
.collect();
chematic_mol::write_mol_with_coords(&mol.inner, &meta, &coords)
}
#[wasm_bindgen]
pub fn sdf_to_smiles_json(sdf: &str) -> String {
if sdf.len() > WASM_MAX_INPUT_BYTES {
return format!(
r#"[{{"error":"SDF input too large ({} bytes)"}}]"#,
sdf.len()
);
}
let entries: Vec<String> = chematic_mol::SdfReader::new(sdf)
.take(WASM_MAX_BATCH_ITEMS)
.map(|r| match r {
Ok((mol, _)) => {
let smi = chematic_smiles::canonical_smiles(&mol);
format!("\"{}\"", smi.replace('"', "\\\""))
}
Err(_) => "null".to_string(),
})
.collect();
format!("[{}]", entries.join(","))
}
#[wasm_bindgen]
pub fn mol_block_from_smiles(smiles: &str) -> Result<String, JsValue> {
let mol = chematic_smiles::parse(smiles)
.map(|m| MolHandle {
inner: std::rc::Rc::new(m),
})
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(to_mol_block(&mol))
}
#[wasm_bindgen]
pub fn sdf_to_records_json(sdf: &str) -> String {
if sdf.len() > WASM_MAX_INPUT_BYTES {
return format!(
r#"[{{"error":"SDF input too large ({} bytes)"}}]"#,
sdf.len()
);
}
let entries: Vec<String> = chematic_mol::SdfRecordReader::new(sdf)
.take(WASM_MAX_BATCH_ITEMS)
.map(|r| match r {
Ok(rec) => {
let smi = chematic_smiles::canonical_smiles(&rec.mol);
let name = escape_json_string(&rec.meta.name);
let props: Vec<String> = rec
.properties
.iter()
.map(|(k, v)| {
format!(
"\"{}\":\"{}\"",
escape_json_string(k),
escape_json_string(v)
)
})
.collect();
format!(
"{{\"smiles\":\"{}\",\"name\":\"{}\",\"properties\":{{{}}}}}",
escape_json_string(&smi),
name,
props.join(",")
)
}
Err(_) => "null".to_string(),
})
.collect();
format!("[{}]", entries.join(","))
}
#[wasm_bindgen]
#[allow(clippy::type_complexity)]
pub fn smiles_array_to_sdf(smiles_json: &str) -> Result<String, JsValue> {
let smiles_list = parse_smiles_json_array(smiles_json)?;
let mut records: Vec<(
chematic_core::Molecule,
chematic_mol::MolMetadata,
Vec<(f64, f64)>,
)> = Vec::new();
for smi in &smiles_list {
let mol = chematic_smiles::parse(smi).map_err(|e| JsValue::from_str(&e.to_string()))?;
enforce_wasm_molecule_size(&mol)?;
let layout = chematic_depict::compute_layout(&mol);
const SCALE: f64 = 1.5 / 40.0;
let coords: Vec<(f64, f64)> = (0..mol.atom_count())
.map(|i| {
let p = layout.get(chematic_core::AtomIdx(i as u32));
(p.x * SCALE, -p.y * SCALE)
})
.collect();
let meta = chematic_mol::MolMetadata::default();
records.push((mol, meta, coords));
}
let refs: Vec<(
&chematic_core::Molecule,
&chematic_mol::MolMetadata,
&[(f64, f64)],
)> = records
.iter()
.map(|(m, meta, c)| (m, meta, c.as_slice()))
.collect();
Ok(chematic_mol::write_sdf(&refs))
}
#[wasm_bindgen]
pub fn to_mol_v3000_block(mol: &MolHandle) -> String {
let layout = chematic_depict::compute_layout(&mol.inner);
const SCALE: f64 = 1.5 / 40.0;
let coords: Vec<(f64, f64)> = (0..mol.inner.atom_count())
.map(|i| {
let p = layout.get(chematic_core::AtomIdx(i as u32));
(p.x * SCALE, -p.y * SCALE)
})
.collect();
let meta = chematic_mol::MolMetadata::default();
chematic_mol::write_mol_v3000(&mol.inner, &meta, &coords)
}
#[wasm_bindgen]
pub fn mol_from_cml(cml: &str) -> Result<MolHandle, JsValue> {
if cml.len() > WASM_MAX_INPUT_BYTES {
return Err(JsValue::from_str("CML input too large"));
}
let (mol, _coords) =
chematic_mol::parse_cml(cml).map_err(|e| JsValue::from_str(&e.to_string()))?;
if mol.atom_count() > WASM_MAX_ATOMS {
return Err(JsValue::from_str(&format!(
"molecule too large (max {} atoms)",
WASM_MAX_ATOMS
)));
}
Ok(MolHandle {
inner: std::rc::Rc::new(mol),
})
}
#[wasm_bindgen]
pub fn to_cml(mol: &MolHandle) -> String {
let layout = chematic_depict::compute_layout(&mol.inner);
const SCALE: f64 = 1.5 / 40.0;
let coords: Vec<(f64, f64)> = (0..mol.inner.atom_count())
.map(|i| {
let p = layout.get(chematic_core::AtomIdx(i as u32));
(p.x * SCALE, -p.y * SCALE)
})
.collect();
chematic_mol::write_cml(&mol.inner, Some(&coords))
}
#[wasm_bindgen]
pub fn mol_from_moljson(json: &str) -> Result<MolHandle, JsValue> {
if json.len() > WASM_MAX_INPUT_BYTES {
return Err(JsValue::from_str("MolJSON input too large"));
}
let mol = chematic_mol::parse_moljson(json).map_err(|e| JsValue::from_str(&e.to_string()))?;
if mol.atom_count() > WASM_MAX_ATOMS {
return Err(JsValue::from_str(&format!(
"molecule too large (max {} atoms)",
WASM_MAX_ATOMS
)));
}
Ok(MolHandle {
inner: std::rc::Rc::new(mol),
})
}
#[wasm_bindgen]
pub fn to_moljson(mol: &MolHandle) -> String {
chematic_mol::write_moljson(&mol.inner)
}
#[wasm_bindgen]
pub fn mol_from_cdxml(cdxml: &str) -> Result<MolHandle, JsValue> {
if cdxml.len() > WASM_MAX_INPUT_BYTES {
return Err(JsValue::from_str("CDXML input too large"));
}
let (mol, _coords) =
chematic_mol::parse_cdxml(cdxml).map_err(|e| JsValue::from_str(&e.to_string()))?;
if mol.atom_count() > WASM_MAX_ATOMS {
return Err(JsValue::from_str(&format!(
"molecule too large (max {} atoms)",
WASM_MAX_ATOMS
)));
}
Ok(MolHandle {
inner: std::rc::Rc::new(mol),
})
}
#[wasm_bindgen]
pub fn cdxml_to_smiles_json(cdxml: &str) -> Result<String, JsValue> {
if cdxml.len() > WASM_MAX_INPUT_BYTES {
return Err(JsValue::from_str("CDXML input too large"));
}
let fragments =
chematic_mol::parse_cdxml_all(cdxml).map_err(|e| JsValue::from_str(&e.to_string()))?;
let parts: Vec<String> = fragments
.iter()
.take(WASM_MAX_BATCH_ITEMS)
.map(|(mol, _)| {
format!(
"\"{}\"",
escape_json_string(&chematic_smiles::canonical_smiles(mol))
)
})
.collect();
Ok(format!("[{}]", parts.join(",")))
}
#[wasm_bindgen]
pub fn mol_block_coords_json(mol_block: &str) -> Result<String, JsValue> {
let (_mol, _meta, coords) = chematic_mol::parse_mol_with_coords(mol_block)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let parts: Vec<String> = coords
.iter()
.map(|(x, y)| {
let xf = if x.is_finite() {
format!("{x:.4}")
} else {
"0".to_string()
};
let yf = if y.is_finite() {
format!("{y:.4}")
} else {
"0".to_string()
};
format!("[{xf},{yf}]")
})
.collect();
Ok(format!("[{}]", parts.join(",")))
}
#[wasm_bindgen]
pub fn sdf_from_records_json(
smiles_json: &str,
names_json: &str,
props_json: &str,
) -> Result<String, JsValue> {
let smiles_list = parse_smiles_json_array(smiles_json)?;
let names_list = parse_wasm_string_json_array(names_json, "names_json")?;
let props_list = parse_wasm_string_json_array(props_json, "props_json")?;
let n = smiles_list.len();
if names_list.len() != n || props_list.len() != n {
return Err(JsValue::from_str(
"sdf_from_records_json: smiles_json, names_json, and props_json must have the same length",
));
}
let mut sdf = String::new();
for i in 0..n {
let mol = chematic_smiles::parse(&smiles_list[i])
.map_err(|e| JsValue::from_str(&e.to_string()))?;
enforce_wasm_molecule_size(&mol)?;
let layout = chematic_depict::compute_layout(&mol);
const SCALE: f64 = 1.5 / 40.0;
let coords: Vec<(f64, f64)> = (0..mol.atom_count())
.map(|j| {
let p = layout.get(chematic_core::AtomIdx(j as u32));
(p.x * SCALE, -p.y * SCALE)
})
.collect();
let meta = chematic_mol::MolMetadata {
name: names_list[i].clone(),
comment: String::new(),
};
sdf.push_str(&chematic_mol::write_mol_with_coords(&mol, &meta, &coords));
for pair in props_list[i].lines() {
if let Some((key, value)) = pair.split_once('\t') {
sdf.push_str(&format!("> <{key}>\n{value}\n\n"));
}
}
sdf.push_str("$$$$\n");
}
Ok(sdf)
}
#[wasm_bindgen]
pub fn mol_from_xyz(xyz: &str) -> Result<MolHandle, JsValue> {
if xyz.len() > WASM_MAX_INPUT_BYTES {
return Err(JsValue::from_str("XYZ input too large"));
}
let (mol, _coords) =
chematic_3d::parse_xyz(xyz).map_err(|e| JsValue::from_str(&format!("{e:?}")))?;
if mol.atom_count() > WASM_MAX_ATOMS {
return Err(JsValue::from_str(&format!(
"molecule too large (max {} atoms)",
WASM_MAX_ATOMS
)));
}
Ok(MolHandle {
inner: std::rc::Rc::new(mol),
})
}
#[wasm_bindgen]
pub fn to_xyz(mol: &MolHandle) -> String {
let coords = chematic_3d::generate_coords(&mol.inner);
chematic_3d::write_xyz(&mol.inner, &coords, "")
}
#[wasm_bindgen]
pub fn mol_from_pdb(pdb: &str) -> MolHandle {
if pdb.len() > WASM_MAX_JSON_STRING_BYTES {
return MolHandle {
inner: std::rc::Rc::new(chematic_core::MoleculeBuilder::new().build()),
};
}
let atoms = chematic_3d::parse_pdb_atoms(pdb);
if atoms.len() > WASM_MAX_ATOMS {
return MolHandle {
inner: std::rc::Rc::new(chematic_core::MoleculeBuilder::new().build()),
};
}
let (mol, _coords) = chematic_3d::pdb_to_molecule(&atoms);
MolHandle {
inner: std::rc::Rc::new(mol),
}
}
#[wasm_bindgen]
pub fn mol2_to_smiles(mol2_str: &str) -> String {
match chematic_mol::parse_mol2(mol2_str) {
Ok((mol, _)) => chematic_smiles::write(&mol),
Err(e) => format!("error:{e}"),
}
}
#[wasm_bindgen]
pub fn smiles_to_mol2(smiles: &str) -> String {
match chematic_smiles::parse(smiles) {
Ok(mol) => chematic_mol::write_mol2(&mol, &[]),
Err(e) => format!("error:{e}"),
}
}
#[wasm_bindgen]
pub fn smiles_to_pdbqt(smiles: &str, coords_json: &str, charges_json: &str, name: &str) -> String {
let mol = match chematic_smiles::parse(smiles) {
Ok(m) => m,
Err(e) => return format!("error:{e}"),
};
let coords: Vec<(f64, f64, f64)> = serde_json::from_str::<Vec<[f64; 3]>>(coords_json)
.unwrap_or_default()
.into_iter()
.map(|c| (c[0], c[1], c[2]))
.collect();
let charges: Vec<f64> = serde_json::from_str(charges_json).unwrap_or_default();
chematic_mol::write_pdbqt(&mol, &coords, &charges, name)
}
#[wasm_bindgen]
pub fn inchi_from_smiles(smiles: &str) -> String {
match chematic_smiles::parse(smiles) {
Ok(mol) => chematic_inchi::inchi(&mol),
Err(e) => format!("error:{e}"),
}
}
#[wasm_bindgen]
pub fn inchikey_from_smiles(smiles: &str) -> String {
match chematic_smiles::parse(smiles) {
Ok(mol) => {
let inchi_str = chematic_inchi::inchi(&mol);
chematic_inchi::inchi_key(&inchi_str)
}
Err(e) => format!("error:{e}"),
}
}