use std::process::Command;
use crate::{Error, Job, Result};
pub fn to_csg(job: &Job) -> Result<()> {
openscad(job, "csg")
}
pub fn to_off(job: &Job) -> Result<()> {
openscad(job, "off")
}
pub fn to_png(job: &Job) -> Result<()> {
openscad(job, "png")
}
pub fn to_stl(job: &Job) -> Result<()> {
openscad(job, "stl")
}
fn openscad(job: &Job, _out_ext: &str) -> Result<()> {
require_tool("openscad")?;
let status = Command::new("openscad")
.arg("-o")
.arg(job.output)
.arg(job.input)
.status()
.map_err(|e| Error::ToolFailed {
tool: "openscad",
message: e.to_string(),
})?;
if !status.success() {
return Err(Error::ToolFailed {
tool: "openscad",
message: format!("openscad exited with status {status}"),
});
}
Ok(())
}
pub fn stl_to_png(job: &Job) -> Result<()> {
require_tool("openscad")?;
let wrapper = tempfile::Builder::new()
.prefix("univert-stl-")
.suffix(".scad")
.tempfile()?;
std::fs::write(
wrapper.path(),
format!("import(\"{}\");\n", job.input.display()),
)?;
let status = Command::new("openscad")
.arg("-o")
.arg(job.output)
.arg(wrapper.path())
.status()
.map_err(|e| Error::ToolFailed {
tool: "openscad",
message: e.to_string(),
})?;
if !status.success() {
return Err(Error::ToolFailed {
tool: "openscad",
message: format!("openscad exited with status {status}"),
});
}
Ok(())
}
fn require_tool(name: &'static str) -> Result<()> {
if which_tool(name) {
Ok(())
} else {
Err(Error::MissingTool {
from: String::new(),
to: String::new(),
tool: name,
})
}
}
pub(crate) fn which_tool(name: &str) -> bool {
if let Ok(path) = std::env::var("PATH") {
for dir in std::env::split_paths(&path) {
let candidate = dir.join(name);
if candidate.is_file() {
return true;
}
let candidate = dir.join(format!("{name}.exe"));
if candidate.is_file() {
return true;
}
}
}
false
}