use std::io::{BufRead, BufReader, Read, Write};
use crate::error::{ImodError, ImodResult};
use crate::model::*;
pub fn read_ascii<R: Read>(reader: &mut R) -> ImodResult<Imod> {
let mut lines = Vec::new();
for line in BufReader::new(reader).lines() {
let line = line?;
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
lines.push(trimmed.to_string());
}
let mut pos = 0;
let mut model = Imod::default();
if pos >= lines.len() {
return Err(ImodError::CorruptData("empty file".into()));
}
let first = &lines[pos];
if !first.starts_with("imod ") {
return Err(ImodError::CorruptData(format!(
"expected 'imod N', got: {}",
first
)));
}
let parts: Vec<&str> = first.split_whitespace().collect();
if parts.len() >= 2 {
model.objsize = parts[1].parse().unwrap_or(0);
}
pos += 1;
if model.objsize > 0 {
model.obj = Vec::with_capacity(model.objsize as usize);
}
let mut current_obj_idx: i32 = -1;
let mut current_contour_idx: i32 = -1;
let mut current_mesh_idx: i32 = -1;
while pos < lines.len() {
let line = &lines[pos];
if line.starts_with("object ") {
let parts: Vec<&str> = line.split_whitespace().collect();
let ob: i32 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
let conts: i32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
let meshes: i32 = parts.get(3).and_then(|s| s.parse().ok()).unwrap_or(0);
current_obj_idx = ob;
current_contour_idx = -1;
current_mesh_idx = -1;
let obj = Iobj {
contsize: conts,
meshsize: meshes,
cont: if conts > 0 {
Vec::with_capacity(conts as usize)
} else {
Vec::new()
},
mesh: if meshes > 0 {
Vec::with_capacity(meshes as usize)
} else {
Vec::new()
},
..Iobj::default()
};
while (model.obj.len() as i32) <= ob {
model.obj.push(Iobj::default());
}
model.obj[ob as usize] = obj;
if ob >= model.objsize {
model.objsize = ob + 1;
}
pos += 1;
break;
}
parse_model_property(&mut model, line);
pos += 1;
}
while pos < lines.len() {
let line = &lines[pos].trim().to_string();
if line.starts_with("object ") {
let parts: Vec<&str> = line.split_whitespace().collect();
let ob: i32 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
let conts: i32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
let meshes: i32 = parts.get(3).and_then(|s| s.parse().ok()).unwrap_or(0);
current_obj_idx = ob;
current_contour_idx = -1;
current_mesh_idx = -1;
let obj = Iobj {
contsize: conts,
meshsize: meshes,
cont: if conts > 0 {
Vec::with_capacity(conts as usize)
} else {
Vec::new()
},
mesh: if meshes > 0 {
Vec::with_capacity(meshes as usize)
} else {
Vec::new()
},
..Iobj::default()
};
while (model.obj.len() as i32) <= ob {
model.obj.push(Iobj::default());
}
model.obj[ob as usize] = obj;
if ob >= model.objsize {
model.objsize = ob + 1;
}
pos += 1;
continue;
}
if line.starts_with("contour ") {
let parts: Vec<&str> = line.split_whitespace().collect();
let co: i32 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
let surf: i32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
let pts: i32 = parts.get(3).and_then(|s| s.parse().ok()).unwrap_or(0);
current_contour_idx = co;
let mut cont = Icont {
surf,
psize: pts,
pts: Vec::with_capacity(pts as usize),
..Icont::default()
};
pos += 1;
for _ in 0..pts {
if pos >= lines.len() {
return Err(ImodError::CorruptData(
"unexpected EOF in contour points".into(),
));
}
let pt_line = &lines[pos];
let pt_parts: Vec<&str> = pt_line.split_whitespace().collect();
let x: f32 = pt_parts.first().and_then(|s| s.parse().ok()).unwrap_or(0.0);
let y: f32 = pt_parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0.0);
let z: f32 = pt_parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0.0);
cont.pts.push(Ipoint { x, y, z });
if pt_parts.len() >= 4 {
let size: f32 = pt_parts[3].parse().unwrap_or(-1.0);
if size >= 0.0 {
if cont.sizes.is_none() {
cont.sizes = Some(vec![-1.0; pts as usize]);
}
if let Some(ref mut sizes) = cont.sizes {
sizes[cont.pts.len() - 1] = size;
}
}
}
pos += 1;
}
if current_obj_idx >= 0 && (current_obj_idx as usize) < model.obj.len() {
let obj = &mut model.obj[current_obj_idx as usize];
while (obj.cont.len() as i32) <= co {
obj.cont.push(Icont::default());
}
obj.cont[co as usize] = cont;
obj.contsize = obj.cont.len() as i32;
}
continue;
}
if line.starts_with("mesh ") {
let parts: Vec<&str> = line.split_whitespace().collect();
let mh: i32 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
let vsize: i32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
let lsize: i32 = parts.get(3).and_then(|s| s.parse().ok()).unwrap_or(0);
current_mesh_idx = mh;
let mut mesh = Imesh {
vsize,
lsize,
vert: Vec::with_capacity(vsize as usize),
list: Vec::with_capacity(lsize as usize),
..Imesh::default()
};
pos += 1;
for _ in 0..vsize {
if pos >= lines.len() {
return Err(ImodError::CorruptData(
"unexpected EOF in mesh vertices".into(),
));
}
let v_line = &lines[pos];
let v_parts: Vec<&str> = v_line.split_whitespace().collect();
let x: f32 = v_parts.first().and_then(|s| s.parse().ok()).unwrap_or(0.0);
let y: f32 = v_parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0.0);
let z: f32 = v_parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0.0);
mesh.vert.push(Ipoint { x, y, z });
pos += 1;
}
for _ in 0..lsize {
if pos >= lines.len() {
return Err(ImodError::CorruptData(
"unexpected EOF in mesh indices".into(),
));
}
let idx: i32 = lines[pos].trim().parse().unwrap_or(0);
mesh.list.push(idx);
pos += 1;
}
if current_obj_idx >= 0 && (current_obj_idx as usize) < model.obj.len() {
let obj = &mut model.obj[current_obj_idx as usize];
while (obj.mesh.len() as i32) <= mh {
obj.mesh.push(Imesh::default());
}
obj.mesh[mh as usize] = mesh;
obj.meshsize = obj.mesh.len() as i32;
}
continue;
}
if current_obj_idx >= 0 && (current_obj_idx as usize) < model.obj.len() {
parse_object_property(
&mut model.obj[current_obj_idx as usize],
line,
current_contour_idx,
current_mesh_idx,
);
} else {
parse_model_property(&mut model, line);
}
pos += 1;
}
model.objsize = model.obj.len() as i32;
Ok(model)
}
fn parse_model_property(model: &mut Imod, line: &str) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
return;
}
match parts[0] {
"max" if parts.len() >= 4 => {
model.xmax = parts[1].parse().unwrap_or(0);
model.ymax = parts[2].parse().unwrap_or(0);
model.zmax = parts[3].parse().unwrap_or(0);
}
"offsets" if parts.len() >= 4 => {
model.xoffset = parts[1].parse().unwrap_or(0.0);
model.yoffset = parts[2].parse().unwrap_or(0.0);
model.zoffset = parts[3].parse().unwrap_or(0.0);
}
"angles" if parts.len() >= 4 => {
model.alpha = parts[1].parse().unwrap_or(0.0);
model.beta = parts[2].parse().unwrap_or(0.0);
model.gamma = parts[3].parse().unwrap_or(0.0);
}
"scale" if parts.len() >= 4 => {
model.xscale = parts[1].parse().unwrap_or(1.0);
model.yscale = parts[2].parse().unwrap_or(1.0);
model.zscale = parts[3].parse().unwrap_or(1.0);
}
"mousemode" if parts.len() >= 2 => {
model.mousemode = parts[1].parse().unwrap_or(0);
}
"drawmode" if parts.len() >= 2 => {
model.drawmode = parts[1].parse().unwrap_or(0);
}
"b&w_level" if parts.len() >= 2 => {
let bw: String = parts[1]
.chars()
.filter(|&c| c.is_ascii_digit() || c == ',')
.collect();
if let Some(comma) = bw.find(',') {
model.blacklevel = bw[..comma].parse().unwrap_or(0);
model.whitelevel = bw[comma + 1..].parse().unwrap_or(255);
}
}
"resolution" if parts.len() >= 2 => {
let v: i32 = parts[1].parse().unwrap_or(3);
model.blacklevel = v; }
"threshold" if parts.len() >= 2 => {
model.blacklevel = parts[1].parse().unwrap_or(128);
}
"pixsize" if parts.len() >= 2 => {
model.pixsize = parts[1].parse().unwrap_or(1.0);
}
"units" if parts.len() >= 2 => {
model.units = match parts[1] {
"mm" => 1,
"um" => 2,
"nm" => 3,
_ => 0,
};
}
"flipped" if parts.len() >= 2 => {
let v: i32 = parts[1].parse().unwrap_or(0);
if v != 0 {
model.flags |= IMODF_FLIPYZ;
} else {
model.flags &= !IMODF_FLIPYZ;
}
}
"currentview" if parts.len() >= 2 => {
model.cview = parts[1].parse().unwrap_or(0);
}
_ => {}
}
}
fn parse_object_property(obj: &mut Iobj, line: &str, cont_idx: i32, _mesh_idx: i32) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
return;
}
match parts[0] {
"name" if line.len() > 5 => {
obj.name = line[5..].trim().to_string();
}
"color" if parts.len() >= 5 => {
obj.red = parts[1].parse().unwrap_or(0.5);
obj.green = parts[2].parse().unwrap_or(0.5);
obj.blue = parts[3].parse().unwrap_or(0.5);
obj.trans = parts[4].parse().unwrap_or(0);
}
"Fillcolor" if parts.len() >= 4 => {
obj.fillred = parts[1].parse().unwrap_or(0);
obj.fillgreen = parts[2].parse().unwrap_or(0);
obj.fillblue = parts[3].parse().unwrap_or(0);
}
"open" => {
obj.flags |= IMOD_OBJFLAG_OPEN;
}
"closed" => {
obj.flags &= !IMOD_OBJFLAG_OPEN;
}
"fill" => obj.flags |= IMOD_OBJFLAG_FILL,
"scattered" => obj.flags |= IMOD_OBJFLAG_SCAT,
"insideout" => obj.flags |= IMOD_OBJFLAG_OUT,
"drawmesh" => obj.flags |= IMOD_OBJFLAG_MESH,
"nolines" => obj.flags |= IMOD_OBJFLAG_NOLINE,
"bothsides" => obj.flags |= IMOD_OBJFLAG_TWO_SIDE,
"usefill" => obj.flags |= IMOD_OBJFLAG_FCOLOR,
"pntusefill" => obj.flags |= IMOD_OBJFLAG_FCOLOR_PNT,
"pntonsec" => obj.flags |= IMOD_OBJFLAG_PNT_ON_SEC,
"antialias" => obj.flags |= IMOD_OBJFLAG_ANTI_ALIAS,
"hastimes" => obj.flags |= IMOD_OBJFLAG_TIME,
"usevalue" => obj.flags |= IMOD_OBJFLAG_USE_VALUE,
"valcolor" => obj.flags |= IMOD_OBJFLAG_MCOLOR,
"nodraw" => obj.flags |= IMOD_OBJFLAG_OFF,
"linewidth" if parts.len() >= 2 => {
obj.linewidth = parts[1].parse().unwrap_or(1);
}
"surfsize" if parts.len() >= 2 => {
obj.surfsize = parts[1].parse().unwrap_or(0);
}
"pointsize" if parts.len() >= 2 => {
obj.pdrawsize = parts[1].parse().unwrap_or(0);
}
"axis" if parts.len() >= 2 => {
obj.axis = parts[1].parse().unwrap_or(0);
}
"width2D" if parts.len() >= 2 => {
obj.linewidth2 = parts[1].parse().unwrap_or(1);
}
"symbol" if parts.len() >= 2 => {
obj.symbol = parts[1].parse().unwrap_or(0);
}
"symsize" if parts.len() >= 2 => {
obj.symsize = parts[1].parse().unwrap_or(3);
}
"symflags" if parts.len() >= 2 => {
obj.symflags = parts[1].parse().unwrap_or(0);
}
"ambient" if parts.len() >= 2 => {
obj.ambient = parts[1].parse().unwrap_or(102);
}
"diffuse" if parts.len() >= 2 => {
obj.diffuse = parts[1].parse().unwrap_or(255);
}
"specular" if parts.len() >= 2 => {
obj.specular = parts[1].parse().unwrap_or(127);
}
"shininess" if parts.len() >= 2 => {
obj.shininess = parts[1].parse().unwrap_or(4);
}
"obquality" if parts.len() >= 2 => {
obj.quality = parts[1].parse().unwrap_or(0);
}
"valblack" if parts.len() >= 2 => {
obj.valblack = parts[1].parse().unwrap_or(0);
}
"valwhite" if parts.len() >= 2 => {
obj.valwhite = parts[1].parse().unwrap_or(255);
}
"meshthick" if parts.len() >= 2 => {
obj.mesh_thickness = parts[1].parse().unwrap_or(0);
}
"matflags2" if parts.len() >= 2 => {
obj.matflags2 = parts[1].parse().unwrap_or(0);
}
"contflags" if parts.len() >= 2 && cont_idx >= 0 => {
let idx = cont_idx as usize;
if idx < obj.cont.len() {
let val: u32 = parts[1].parse().unwrap_or(0);
obj.cont[idx].flags = val;
}
}
"conttime" if parts.len() >= 2 && cont_idx >= 0 => {
let idx = cont_idx as usize;
if idx < obj.cont.len() {
obj.cont[idx].time = parts[1].parse().unwrap_or(0);
}
}
"Meshflags" if parts.len() >= 2 && _mesh_idx >= 0 => {
let idx = _mesh_idx as usize;
if idx < obj.mesh.len() {
let val: u32 = parts[1].parse().unwrap_or(0);
obj.mesh[idx].flag = val;
}
}
"Meshtime" if parts.len() >= 2 && _mesh_idx >= 0 => {
let idx = _mesh_idx as usize;
if idx < obj.mesh.len() {
obj.mesh[idx].time = parts[1].parse().unwrap_or(0);
}
}
"Meshsurf" if parts.len() >= 2 && _mesh_idx >= 0 => {
let idx = _mesh_idx as usize;
if idx < obj.mesh.len() {
obj.mesh[idx].surf = parts[1].parse().unwrap_or(0);
}
}
_ => {}
}
}
pub fn write_ascii<W: Write>(writer: &mut W, model: &Imod) -> ImodResult<()> {
writeln!(writer, "# imod ascii file version 2.0")?;
writeln!(writer)?;
writeln!(writer, "imod {}", model.objsize)?;
writeln!(writer, "max {} {} {}", model.xmax, model.ymax, model.zmax)?;
writeln!(
writer,
"offsets {} {} {}",
model.xoffset, model.yoffset, model.zoffset
)?;
writeln!(
writer,
"angles {} {} {}",
model.alpha, model.beta, model.gamma
)?;
writeln!(
writer,
"scale {} {} {}",
model.xscale, model.yscale, model.zscale
)?;
writeln!(writer, "mousemode {}", model.mousemode)?;
writeln!(writer, "drawmode {}", model.drawmode)?;
writeln!(
writer,
"b&w_level {},{}",
model.blacklevel, model.whitelevel
)?;
writeln!(writer, "pixsize {}", model.pixsize)?;
let units_str = match model.units {
1 => "mm",
2 => "um",
3 => "nm",
_ => "",
};
writeln!(writer, "units {}", units_str)?;
writeln!(
writer,
"flipped {}",
if model.flags & IMODF_FLIPYZ != 0 {
1
} else {
0
}
)?;
writeln!(writer, "currentview {}", model.cview)?;
if let Some(ref ref_img) = model.ref_image {
writeln!(
writer,
"refcurscale {} {} {}",
ref_img.cscale.x, ref_img.cscale.y, ref_img.cscale.z
)?;
writeln!(
writer,
"refcurtrans {} {} {}",
ref_img.ctrans.x, ref_img.ctrans.y, ref_img.ctrans.z
)?;
if model.flags & IMODF_TILTOK != 0 {
writeln!(
writer,
"refcurrot {} {} {}",
ref_img.crot.x, ref_img.crot.y, ref_img.crot.z
)?;
}
if model.flags & IMODF_OTRANS_ORIGIN != 0 {
writeln!(
writer,
"refoldtrans {} {} {}",
ref_img.otrans.x, ref_img.otrans.y, ref_img.otrans.z
)?;
}
}
for (ob_idx, obj) in model.obj.iter().enumerate() {
let num_real = obj.mesh.iter().filter(|m| m.flag & 0x80000000 == 0).count() as i32;
writeln!(writer)?;
writeln!(writer, "object {} {} {}", ob_idx, obj.contsize, num_real)?;
writeln!(writer, "name {}", obj.name)?;
writeln!(
writer,
"color {} {} {} {}",
obj.red, obj.green, obj.blue, obj.trans
)?;
if obj.fillred != 0 || obj.fillgreen != 0 || obj.fillblue != 0 {
writeln!(
writer,
"Fillcolor {} {} {}",
obj.fillred, obj.fillgreen, obj.fillblue
)?;
}
if obj.flags & IMOD_OBJFLAG_OPEN != 0 {
writeln!(writer, "open")?;
}
if obj.flags & IMOD_OBJFLAG_SCAT != 0 {
writeln!(writer, "scattered")?;
}
if obj.flags & IMOD_OBJFLAG_OFF != 0 {
writeln!(writer, "nodraw")?;
}
if obj.flags & IMOD_OBJFLAG_OUT != 0 {
writeln!(writer, "insideout")?;
}
if obj.flags & IMOD_OBJFLAG_FILL != 0 {
writeln!(writer, "fill")?;
}
if obj.flags & IMOD_OBJFLAG_MESH != 0 {
writeln!(writer, "drawmesh")?;
}
if obj.flags & IMOD_OBJFLAG_NOLINE != 0 {
writeln!(writer, "nolines")?;
}
if obj.flags & IMOD_OBJFLAG_TWO_SIDE != 0 {
writeln!(writer, "bothsides")?;
}
if obj.flags & IMOD_OBJFLAG_FCOLOR != 0 {
writeln!(writer, "usefill")?;
}
if obj.flags & IMOD_OBJFLAG_FCOLOR_PNT != 0 {
writeln!(writer, "pntusefill")?;
}
if obj.flags & IMOD_OBJFLAG_PNT_ON_SEC != 0 {
writeln!(writer, "pntonsec")?;
}
if obj.flags & IMOD_OBJFLAG_ANTI_ALIAS != 0 {
writeln!(writer, "antialias")?;
}
if obj.flags & IMOD_OBJFLAG_TIME != 0 {
writeln!(writer, "hastimes")?;
}
if obj.flags & IMOD_OBJFLAG_USE_VALUE != 0 {
writeln!(writer, "usevalue")?;
}
if obj.flags & IMOD_OBJFLAG_MCOLOR != 0 {
writeln!(writer, "valcolor")?;
}
writeln!(writer, "linewidth {}", obj.linewidth)?;
writeln!(writer, "surfsize {}", obj.surfsize)?;
writeln!(writer, "pointsize {}", obj.pdrawsize)?;
writeln!(writer, "axis {}", obj.axis)?;
writeln!(writer, "drawmode {}", obj.drawmode)?;
writeln!(writer, "width2D {}", obj.linewidth2)?;
writeln!(writer, "symbol {}", obj.symbol)?;
writeln!(writer, "symsize {}", obj.symsize)?;
writeln!(writer, "symflags {}", obj.symflags)?;
writeln!(writer, "ambient {}", obj.ambient)?;
writeln!(writer, "diffuse {}", obj.diffuse)?;
writeln!(writer, "specular {}", obj.specular)?;
writeln!(writer, "shininess {}", obj.shininess)?;
writeln!(writer, "obquality {}", obj.quality)?;
writeln!(writer, "valblack {}", obj.valblack)?;
writeln!(writer, "valwhite {}", obj.valwhite)?;
writeln!(writer, "meshthick {}", obj.mesh_thickness)?;
writeln!(writer, "matflags2 {}", obj.matflags2)?;
for (co_idx, cont) in obj.cont.iter().enumerate() {
writeln!(writer, "contour {} {} {}", co_idx, cont.surf, cont.psize)?;
for pt_idx in 0..cont.psize as usize {
let pt = &cont.pts[pt_idx];
write!(writer, "{} {} {}", pt.x, pt.y, pt.z)?;
if let Some(ref sizes) = cont.sizes {
if pt_idx < sizes.len() && sizes[pt_idx] >= 0.0 {
write!(writer, " {}", sizes[pt_idx])?;
}
}
writeln!(writer)?;
}
if cont.flags != 0 {
writeln!(writer, "contflags {}", cont.flags)?;
}
if cont.time != 0 {
writeln!(writer, "conttime {}", cont.time)?;
}
}
let mut real_idx = 0;
for mesh in &obj.mesh {
if mesh.flag & 0x80000000 != 0 {
continue;
}
writeln!(writer, "mesh {} {} {}", real_idx, mesh.vsize, mesh.lsize)?;
real_idx += 1;
for pt in &mesh.vert {
writeln!(writer, "{} {} {}", pt.x, pt.y, pt.z)?;
}
for &idx in &mesh.list {
writeln!(writer, "{}", idx)?;
}
if mesh.flag != 0 {
writeln!(writer, "Meshflags {}", mesh.flag)?;
}
if mesh.time != 0 {
writeln!(writer, "Meshtime {}", mesh.time)?;
}
if mesh.surf != 0 {
writeln!(writer, "Meshsurf {}", mesh.surf)?;
}
}
}
writeln!(writer, "# end of IMOD model")?;
writer.flush()?;
Ok(())
}