use serde::{Deserialize, Serialize};
use crate::{render, Error, Mol, MolRenderOptions, Rendered};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShadeSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub atoms: Option<Vec<f64>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bonds: Option<Vec<f64>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub colormap: Option<String>,
#[serde(default = "default_shade_vmin")]
pub vmin: f64,
#[serde(default = "default_shade_vmax")]
pub vmax: f64,
}
fn default_shade_vmin() -> f64 {
0.0
}
fn default_shade_vmax() -> f64 {
1.0
}
impl Default for ShadeSpec {
fn default() -> Self {
Self {
atoms: None,
bonds: None,
colormap: None,
vmin: 0.0,
vmax: 1.0,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MolNode {
#[serde(rename = "type", default = "mol_type")]
pub type_: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub smiles: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cxsmiles: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub molfile: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub shade: Option<ShadeSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub star_labels: Option<Vec<Option<String>>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scale: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub weight: Option<f64>,
}
fn mol_type() -> String {
"mol".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum DepictSpec {
Mol {
#[serde(default, skip_serializing_if = "Option::is_none")]
smiles: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
cxsmiles: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
molfile: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
color: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
shade: Option<ShadeSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
star_labels: Option<Vec<Option<String>>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
scale: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
weight: Option<f64>,
},
Group {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default)]
children: Vec<MolNode>,
},
}
impl DepictSpec {
fn owned_mols(&self) -> Vec<MolNode> {
match self {
DepictSpec::Mol {
smiles,
cxsmiles,
molfile,
id,
color,
shade,
star_labels,
scale,
weight,
} => vec![MolNode {
type_: "mol".into(),
smiles: smiles.clone(),
cxsmiles: cxsmiles.clone(),
molfile: molfile.clone(),
id: id.clone(),
color: color.clone(),
shade: shade.clone(),
star_labels: star_labels.clone(),
scale: *scale,
weight: *weight,
}],
DepictSpec::Group { children, .. } => children.clone(),
}
}
}
impl MolNode {
fn structure(&self) -> Result<&str, Error> {
if let Some(ref s) = self.molfile {
if !s.trim().is_empty() {
return Ok(s.as_str());
}
}
if let Some(ref s) = self.cxsmiles {
if !s.trim().is_empty() {
return Ok(s.as_str());
}
}
if let Some(ref s) = self.smiles {
if !s.trim().is_empty() {
return Ok(s.as_str());
}
}
Err(Error::Message(
"mol node needs smiles, cxsmiles, or molfile".into(),
))
}
}
pub type MolSpec = MolNode;
pub fn depict(spec: &DepictSpec) -> Result<Vec<Rendered>, Error> {
let mut out: Vec<Rendered> = Vec::new();
for entry in spec.owned_mols() {
let structure = entry.structure()?;
let mut mol = Mol::from_source(structure)?;
let opts = MolRenderOptions {
id: entry.id.clone(),
color: entry.color.clone(),
atom_shade: entry.shade.as_ref().and_then(|s| s.atoms.clone()),
bond_shade: entry.shade.as_ref().and_then(|s| s.bonds.clone()),
shade_vmin: entry.shade.as_ref().map(|s| s.vmin),
shade_vmax: entry.shade.as_ref().map(|s| s.vmax),
mark_atoms: None,
mark_bonds: None,
star_labels: entry.star_labels.clone(),
scale: entry.scale,
weight: entry.weight,
align_to: None,
};
out.push(render(&mut mol, opts)?);
}
Ok(out)
}