use crate::{
bpy,
error::BlError,
export::{BlendExport, BlendExporter},
import::{BlendImport, BlendImporter},
result::Result,
};
use pyo3::Python;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug)]
pub struct BlendProject {
no_send_sync: std::marker::PhantomData<*const ()>,
}
impl BlendProject {
pub fn empty(py: Python) -> Result<Self> {
Self::ensure_thread_safety()?;
bpy::ops::wm::read_factory_settings(py, true)?;
Ok(Self {
no_send_sync: std::marker::PhantomData,
})
}
pub fn open(&self, py: Python, filepath: impl AsRef<Path>) -> Result<Self> {
Self::ensure_thread_safety()?;
let filepath = filepath.as_ref();
if !filepath.is_file() {
return Err(BlError::ValueError(format!(
"Filepath '{}' does not point to a valid file.",
filepath.display()
)));
}
bpy::ops::wm::open_mainfile(py, filepath)?;
Ok(Self {
no_send_sync: std::marker::PhantomData,
})
}
pub fn save(&self, py: Python, filepath: impl AsRef<Path>) -> Result<PathBuf> {
let filepath = Self::check_save_filepath(filepath)?;
bpy::ops::wm::save_mainfile(py, filepath.as_ref())?;
Ok(filepath)
}
pub fn import<E: BlendImport>(&self, importer: E, filepath: impl AsRef<Path>) -> Result<()> {
importer.import(filepath)
}
pub fn import_default(&self, filepath: impl AsRef<Path>) -> Result<()> {
BlendImporter::from_filepath_extension(&filepath)?.import(&filepath)
}
pub fn export<E: BlendExport>(
&self,
exporter: E,
filepath: impl AsRef<Path>,
) -> Result<PathBuf> {
exporter.export(filepath)
}
pub fn export_default(&self, filepath: impl AsRef<Path>) -> Result<PathBuf> {
BlendExporter::from_filepath_extension(&filepath)?.export(&filepath)
}
fn check_save_filepath(filepath: impl AsRef<Path>) -> Result<PathBuf> {
let filepath = filepath.as_ref();
if filepath.as_os_str().is_empty() {
return Err(BlError::ValueError("Filepath cannot be empty".to_string()));
}
if filepath.is_dir() {
return Err(BlError::ValueError(format!(
"Filepath cannot be a directory: '{}'",
filepath.display()
)));
}
match filepath.extension() {
Some(invalid_ext) if invalid_ext.to_ascii_lowercase() != "blend" => {
Err(BlError::ValueError(format!(
"Invalid file extension (expected: 'blend', actual: '{invalid_ext}')",
invalid_ext = invalid_ext.to_str().unwrap()
)))
}
_ => Ok(filepath.with_extension("blend")),
}
}
}
impl Default for BlendProject {
fn default() -> Self {
Self::ensure_thread_safety().unwrap();
Python::with_gil(|py| {
bpy::ops::wm::read_factory_settings(py, false).unwrap();
});
Self {
no_send_sync: std::marker::PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt::Debug;
#[test]
fn trait_impls() {
const fn trait_impls_noop<T: Sized + Unpin + Clone + Debug>() {}
trait_impls_noop::<BlendProject>();
}
}