pytrace 0.3.3

A Python library for ray tracing and image generation
Documentation
use glob::glob;
use pyo3::prelude::*;
use pyo3::PyObjectProtocol;
use std::process::Command;

use crate::external::*;
use pytrace_core::internal;
use pytrace_core::render::*;

#[pyclass]
#[text_signature = "(wth, hgt, iter, /)"]
pub struct Cfg {
    pub silent: bool,
    #[pyo3(get, set)]
    pub hgt: usize,
    #[pyo3(get, set)]
    pub wth: usize,
    #[pyo3(get, set)]
    pub iter: usize,
    cam: Option<Camera>,
    world: internal::World,
    sky: Option<Sky>,
    mov: Option<MovieCfg>,
    #[pyo3(get, set)]
    nbsync: usize,
}

struct MovieCfg {
    name: String,
    cnt: usize,
    modif: bool,
}

#[pymethods]
impl Cfg {
    #[new]
    pub fn new(wth: usize, hgt: usize, iter: usize) -> Self {
        ctrlc::set_handler(|| std::process::exit(2)).unwrap_or_else(|_| eprintln!("\x1b[33;1mWarning: you have multiple configurations at the same time.\x1b[0m
That's ok, but don't mix them up."));
        Self {
            silent: false,
            hgt,
            wth,
            iter,
            cam: None,
            world: internal::World::new(),
            sky: None,
            mov: None,
            nbsync: 5,
        }
    }

    #[text_signature = "($self, /)"]
    pub fn silence(&mut self) {
        self.silent = true;
    }

    #[text_signature = "($self, name, /)"]
    pub fn render(&self, name: String) {
        if let Some(mut cam) = self.cam {
            if cam.aspect < 0. {
                cam.aspect = self.wth as f64 / self.hgt as f64;
            }
            if let Some(sky) = &self.sky {
                render(Builder {
                    name,
                    silent: self.silent,
                    hgt: self.hgt,
                    wth: self.wth,
                    iter: self.iter,
                    cam: cam.to_internal(),
                    world: self.world.clone(),
                    sky: sky.to_internal(),
                    nbsync: self.nbsync,
                })
            } else {
                panic!("No sky provided")
            }
        } else {
            panic!("No camera provided")
        }
    }

    #[text_signature = "($self, r, g, b, /)"]
    pub fn set_background(&mut self, r: f64, g: f64, b: f64) {
        self.world.background = Some(internal::RGB(r, g, b));
        self.refresh();
    }

    #[text_signature = "($self, /)"]
    pub fn true_background(&mut self) {
        self.world.background = None;
        self.refresh();
    }

    #[text_signature = "($self, camera, /)"]
    pub fn set_cam(&mut self, cam: Camera) {
        self.cam = Some(cam);
        self.refresh();
    }

    #[text_signature = "($self, sky, /)"]
    pub fn set_sky(&mut self, sky: Sky) {
        self.sky = Some(sky);
        self.refresh();
    }

    #[text_signature = "($self, object, /)"]
    pub fn populate(&mut self, object: Prebuilt) {
        self.world.push_vec(object.extract());
        self.refresh();
    }

    #[text_signature = "($self, /)"]
    pub fn clear(&mut self) {
        self.world.clear();
        self.refresh();
    }

    #[text_signature = "($self, object, /)"]
    pub fn add_obj(&mut self, object: Construct) {
        self.world.push_vec(object.contents.canonical());
        self.refresh();
    }

    #[text_signature = "($self, name, /)"]
    pub fn start_movie(&mut self, name: String) {
        self.mov = Some(MovieCfg {
            name,
            cnt: 0,
            modif: true,
        });
    }

    #[text_signature = "($self, name, /)"]
    pub fn frame(&mut self) {
        if let Some(m) = &self.mov {
            println!("Creating frame {}", m.cnt);
            if m.modif {
                self.render(format!("{}-{}", &m.name, m.cnt));
            } else {
                Command::new("cp")
                    .arg(&format!("img-{}-{}.ppm", &m.name, m.cnt - 1))
                    .arg(&format!("img-{}-{}.ppm", &m.name, m.cnt))
                    .status()
                    .expect("Could not copy previous image");
            }
        } else {
            panic!("No movie configured");
        }
        if let Some(m) = &mut self.mov {
            m.cnt += 1;
            m.modif = false;
        }
    }

    #[text_signature = "($self, /)"]
    pub fn end_movie(&mut self) {
        if let Some(m) = &self.mov {
            Command::new("rm")
                .arg(&format!("{}.avi", &m.name))
                .status()
                .expect("No file to remove");
            let e = Command::new("ffmpeg")
                .arg("-pattern_type")
                .arg("sequence")
                .arg("-framerate")
                .arg("25")
                .arg("-i")
                .arg(&format!("img-{}-%d.ppm", &m.name))
                .arg("-vcodec")
                .arg("libx264")
                .arg(&format!("{}.avi", &m.name))
                .status()
                .expect("Failed to create movie");
            if e.success() {
                println!("Done creating movie, cleanup files");
                for f in
                    glob(&format!("img-{}-*.ppm", &m.name)).expect("Could not read glob pattern")
                {
                    Command::new("rm")
                        .arg(&format!("{}", f.unwrap().display()))
                        .status()
                        .expect("Failed to remove");
                }
                self.mov = None;
            }
        }
    }
}

impl Cfg {
    fn refresh(&mut self) {
        if let Some(m) = &mut self.mov {
            m.modif = true;
        }
    }
}

#[pyproto]
impl PyObjectProtocol for Cfg {
    fn __str__(self) -> PyResult<String> {
        Ok(format!(
            "Cfg {{
    silent: {},
    hgt:    {},
    wth:    {},
    iter:   {},
    nbsync: {},
}}",
            self.silent, self.hgt, self.wth, self.iter, self.nbsync,
        ))
    }
}