use numpy::{PyArray1, PyArray2, PyArrayMethods};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use crate::io::{load, save};
use crate::types::SdfTree;
use super::helpers::{mesh_to_numpy, numpy_to_mesh};
use super::node::PySdfNode;
#[pyfunction]
pub fn save_sdf(py: Python<'_>, node: &PySdfNode, path: &str) -> PyResult<()> {
let tree = SdfTree::new(node.inner.clone());
let path = path.to_string();
py.detach(|| save(&tree, &path))
.map_err(|e| PyValueError::new_err(format!("Save error: {}", e)))
}
#[pyfunction]
pub fn load_sdf(py: Python<'_>, path: &str) -> PyResult<PySdfNode> {
let path = path.to_string();
let tree = py
.detach(|| load(&path))
.map_err(|e| PyValueError::new_err(format!("Load error: {}", e)))?;
Ok(PySdfNode { inner: tree.root })
}
#[pyfunction]
pub fn from_json(json_str: &str) -> PyResult<PySdfNode> {
use crate::io::from_json_string;
let tree = from_json_string(json_str)
.map_err(|e| PyValueError::new_err(format!("JSON parse error: {}", e)))?;
Ok(PySdfNode { inner: tree.root })
}
#[pyfunction]
pub fn to_json(node: &PySdfNode) -> PyResult<String> {
use crate::io::to_json_string;
let tree = SdfTree::new(node.inner.clone());
to_json_string(&tree).map_err(|e| PyValueError::new_err(format!("JSON serialize error: {}", e)))
}
#[pyfunction]
#[pyo3(signature = (vertices, indices, path))]
pub fn export_obj(
py: Python<'_>,
vertices: &Bound<'_, PyArray2<f32>>,
indices: &Bound<'_, PyArray1<u32>>,
path: &str,
) -> PyResult<()> {
use crate::io::{export_obj as io_export_obj, ObjConfig};
let mesh = numpy_to_mesh(vertices, indices)?;
py.detach(|| io_export_obj(&mesh, path, &ObjConfig::default(), None))
.map_err(|e| PyValueError::new_err(format!("Export error: {}", e)))
}
#[pyfunction]
#[pyo3(signature = (vertices, indices, path))]
pub fn export_glb(
py: Python<'_>,
vertices: &Bound<'_, PyArray2<f32>>,
indices: &Bound<'_, PyArray1<u32>>,
path: &str,
) -> PyResult<()> {
use crate::io::{export_glb as io_export_glb, GltfConfig};
let mesh = numpy_to_mesh(vertices, indices)?;
py.detach(|| io_export_glb(&mesh, path, &GltfConfig::default(), None))
.map_err(|e| PyValueError::new_err(format!("Export error: {}", e)))
}
#[pyfunction]
#[pyo3(signature = (vertices, indices))]
pub fn export_glb_bytes<'py>(
py: Python<'py>,
vertices: &Bound<'py, PyArray2<f32>>,
indices: &Bound<'py, PyArray1<u32>>,
) -> PyResult<Vec<u8>> {
use crate::io::{export_glb_bytes as io_export_glb_bytes, GltfConfig};
let mesh = numpy_to_mesh(vertices, indices)?;
py.detach(|| io_export_glb_bytes(&mesh, &GltfConfig::default(), None))
.map_err(|e| PyValueError::new_err(format!("Export error: {}", e)))
}
#[pyfunction]
#[pyo3(signature = (vertices, indices, path))]
pub fn export_fbx(
py: Python<'_>,
vertices: &Bound<'_, PyArray2<f32>>,
indices: &Bound<'_, PyArray1<u32>>,
path: &str,
) -> PyResult<()> {
use crate::io::{export_fbx as io_export_fbx, FbxConfig};
let mesh = numpy_to_mesh(vertices, indices)?;
py.detach(|| io_export_fbx(&mesh, path, &FbxConfig::default(), None))
.map_err(|e| PyValueError::new_err(format!("Export error: {}", e)))
}
#[pyfunction]
#[pyo3(signature = (vertices, indices, path))]
pub fn export_usda(
py: Python<'_>,
vertices: &Bound<'_, PyArray2<f32>>,
indices: &Bound<'_, PyArray1<u32>>,
path: &str,
) -> PyResult<()> {
use crate::io::{export_usda as io_export_usda, UsdConfig};
let mesh = numpy_to_mesh(vertices, indices)?;
py.detach(|| io_export_usda(&mesh, path, &UsdConfig::default(), None))
.map_err(|e| PyValueError::new_err(format!("Export error: {}", e)))
}
#[pyfunction]
#[pyo3(signature = (vertices, indices, path))]
pub fn export_alembic(
py: Python<'_>,
vertices: &Bound<'_, PyArray2<f32>>,
indices: &Bound<'_, PyArray1<u32>>,
path: &str,
) -> PyResult<()> {
use crate::io::{export_alembic as io_export_alembic, AlembicConfig};
let mesh = numpy_to_mesh(vertices, indices)?;
py.detach(|| io_export_alembic(&mesh, path, &AlembicConfig::default()))
.map_err(|e| PyValueError::new_err(format!("Export error: {}", e)))
}
#[pyfunction]
#[pyo3(signature = (vertices, indices))]
pub fn uv_unwrap<'py>(
py: Python<'py>,
vertices: &Bound<'py, PyArray2<f32>>,
indices: &Bound<'py, PyArray1<u32>>,
) -> PyResult<(
Bound<'py, PyArray2<f32>>,
Bound<'py, PyArray2<f32>>,
Bound<'py, PyArray1<u32>>,
)> {
use crate::mesh::{apply_uvs, uv_unwrap as mesh_uv_unwrap, UvUnwrapConfig};
let mut mesh = numpy_to_mesh(vertices, indices)?;
py.detach(|| {
let result = mesh_uv_unwrap(&mesh, &UvUnwrapConfig::default());
apply_uvs(&mut mesh, &result);
});
let n = mesh.vertices.len();
let positions: Vec<f32> = mesh
.vertices
.iter()
.flat_map(|v| [v.position.x, v.position.y, v.position.z])
.collect();
let pos_array = numpy::PyArray1::from_vec(py, positions)
.reshape([n, 3])
.map_err(|e| PyValueError::new_err(format!("Reshape error: {}", e)))?;
let uvs: Vec<f32> = mesh
.vertices
.iter()
.flat_map(|v| [v.uv.x, v.uv.y])
.collect();
let uv_array = numpy::PyArray1::from_vec(py, uvs)
.reshape([n, 2])
.map_err(|e| PyValueError::new_err(format!("Reshape error: {}", e)))?;
let indices_array = numpy::PyArray1::from_slice(py, &mesh.indices);
Ok((pos_array, uv_array, indices_array))
}
#[pyfunction]
#[pyo3(signature = (vertices, indices, path))]
pub fn save_abm(
py: Python<'_>,
vertices: &Bound<'_, PyArray2<f32>>,
indices: &Bound<'_, PyArray1<u32>>,
path: &str,
) -> PyResult<()> {
use crate::io::save_abm as io_save_abm;
let mesh = numpy_to_mesh(vertices, indices)?;
let path = path.to_string();
py.detach(move || io_save_abm(&mesh, &path))
.map_err(|e| PyValueError::new_err(format!("ABM save error: {}", e)))
}
#[pyfunction]
#[pyo3(signature = (path))]
pub fn load_abm<'py>(
py: Python<'py>,
path: &str,
) -> PyResult<(Bound<'py, PyArray2<f32>>, Bound<'py, PyArray1<u32>>)> {
use crate::io::load_abm as io_load_abm;
let path = path.to_string();
let mesh = py
.detach(move || io_load_abm(&path))
.map_err(|e| PyValueError::new_err(format!("ABM load error: {}", e)))?;
mesh_to_numpy(py, &mesh)
}
#[pyfunction]
#[pyo3(signature = (vertices, indices, path, flip_z=true, flip_winding=true, scale=1.0))]
pub fn export_unity(
py: Python<'_>,
vertices: &Bound<'_, PyArray2<f32>>,
indices: &Bound<'_, PyArray1<u32>>,
path: &str,
flip_z: bool,
flip_winding: bool,
scale: f32,
) -> PyResult<()> {
use crate::io::{export_unity_mesh as io_export_unity, UnityMeshConfig};
let mesh = numpy_to_mesh(vertices, indices)?;
let path = path.to_string();
let config = UnityMeshConfig {
scale,
flip_z,
flip_winding,
name: "AliceSdfMesh".to_string(),
};
py.detach(move || io_export_unity(&mesh, &path, &config))
.map_err(|e| PyValueError::new_err(format!("Unity export error: {}", e)))
}
#[pyfunction]
#[pyo3(signature = (vertices, indices, path, scale=100.0))]
pub fn export_ue5(
py: Python<'_>,
vertices: &Bound<'_, PyArray2<f32>>,
indices: &Bound<'_, PyArray1<u32>>,
path: &str,
scale: f32,
) -> PyResult<()> {
use crate::io::{export_ue5_mesh as io_export_ue5, Ue5MeshConfig};
let mesh = numpy_to_mesh(vertices, indices)?;
let path = path.to_string();
let config = Ue5MeshConfig {
scale,
name: "SM_AliceSdf".to_string(),
lod_index: 0,
};
py.detach(move || io_export_ue5(&mesh, &path, &config))
.map_err(|e| PyValueError::new_err(format!("UE5 export error: {}", e)))
}
#[pyfunction]
pub fn import_glb_bytes<'py>(
py: Python<'py>,
data: &[u8],
) -> PyResult<(Bound<'py, PyArray2<f32>>, Bound<'py, PyArray1<u32>>)> {
use crate::io::import_glb_bytes as io_import_glb_bytes;
let data = data.to_vec();
let mesh = py
.detach(move || io_import_glb_bytes(&data))
.map_err(|e| PyValueError::new_err(format!("GLB import error: {}", e)))?;
mesh_to_numpy(py, &mesh)
}
#[pyfunction]
pub fn import_glb<'py>(
py: Python<'py>,
path: &str,
) -> PyResult<(Bound<'py, PyArray2<f32>>, Bound<'py, PyArray1<u32>>)> {
use crate::io::import_glb as io_import_glb;
let path = path.to_string();
let mesh = py
.detach(move || io_import_glb(&path))
.map_err(|e| PyValueError::new_err(format!("GLB import error: {}", e)))?;
mesh_to_numpy(py, &mesh)
}
#[pyfunction]
#[pyo3(signature = (vertices, indices, capsule_radius_factor=0.05))]
pub fn mesh_to_sdf(
py: Python<'_>,
vertices: &Bound<'_, PyArray2<f32>>,
indices: &Bound<'_, PyArray1<u32>>,
capsule_radius_factor: f32,
) -> PyResult<PySdfNode> {
use crate::mesh::{mesh_to_sdf as rust_mesh_to_sdf, MeshToSdfConfig};
use glam::Vec3;
let verts_arr = unsafe { vertices.as_array() };
let idx_arr = unsafe { indices.as_array() };
if verts_arr.shape()[1] != 3 {
return Err(PyValueError::new_err("Vertices must have shape (N, 3)"));
}
let verts: Vec<Vec3> = verts_arr
.rows()
.into_iter()
.map(|row| Vec3::new(row[0], row[1], row[2]))
.collect();
let idx: Vec<u32> = idx_arr.to_vec();
let config = MeshToSdfConfig {
capsule_radius_factor,
..MeshToSdfConfig::fast()
};
let node = py.detach(|| rust_mesh_to_sdf(&verts, &idx, &config));
Ok(PySdfNode { inner: node })
}