use crate::SdfNode;
use glam::Vec3;
use std::path::Path;
pub use alice_sdf::io::{export_3mf, export_fbx, export_stl, export_stl_ascii, FbxConfig};
pub use alice_sdf::mesh::{
dual_contouring, sdf_to_mesh, DualContouringConfig, MarchingCubesConfig, Mesh, MeshRepair,
Vertex,
};
#[derive(Debug, Clone)]
pub struct PrintConfig {
pub resolution: usize,
pub bounds_min: Vec3,
pub bounds_max: Vec3,
pub scale_mm: f32,
}
impl Default for PrintConfig {
fn default() -> Self {
Self {
resolution: 128,
bounds_min: Vec3::splat(-2.0),
bounds_max: Vec3::splat(2.0),
scale_mm: 10.0,
}
}
}
impl PrintConfig {
#[must_use]
pub const fn preview() -> Self {
Self {
resolution: 64,
bounds_min: Vec3::splat(-2.0),
bounds_max: Vec3::splat(2.0),
scale_mm: 10.0,
}
}
#[must_use]
pub const fn high_quality() -> Self {
Self {
resolution: 256,
bounds_min: Vec3::splat(-2.0),
bounds_max: Vec3::splat(2.0),
scale_mm: 10.0,
}
}
#[must_use]
pub const fn ultra() -> Self {
Self {
resolution: 512,
bounds_min: Vec3::splat(-2.0),
bounds_max: Vec3::splat(2.0),
scale_mm: 10.0,
}
}
#[must_use]
pub const fn with_bounds(mut self, min: Vec3, max: Vec3) -> Self {
self.bounds_min = min;
self.bounds_max = max;
self
}
#[must_use]
pub const fn with_scale_mm(mut self, scale_mm: f32) -> Self {
self.scale_mm = scale_mm;
self
}
}
#[derive(Debug)]
pub enum ExportError {
Parse(crate::runtime_parser::ParseError),
Io(std::io::Error),
EmptyMesh,
}
impl std::fmt::Display for ExportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Parse(e) => write!(f, "LOL parse error: {e}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::EmptyMesh => write!(f, "generated mesh has no triangles"),
}
}
}
impl std::error::Error for ExportError {}
impl From<crate::runtime_parser::ParseError> for ExportError {
fn from(e: crate::runtime_parser::ParseError) -> Self {
Self::Parse(e)
}
}
impl From<std::io::Error> for ExportError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
impl From<alice_sdf::io::IoError> for ExportError {
fn from(e: alice_sdf::io::IoError) -> Self {
Self::Io(std::io::Error::other(e.to_string()))
}
}
#[must_use]
pub fn node_to_mesh(node: &SdfNode, config: &PrintConfig) -> Mesh {
let mc_config = MarchingCubesConfig {
resolution: config.resolution,
compute_normals: true,
..MarchingCubesConfig::default()
};
let mesh = sdf_to_mesh(node, config.bounds_min, config.bounds_max, &mc_config);
let mut mesh = MeshRepair::repair_all(&mesh, 5e-3);
if (config.scale_mm - 1.0).abs() > f32::EPSILON {
for v in &mut mesh.vertices {
v.position *= config.scale_mm;
}
}
mesh
}
pub fn node_to_stl(
node: &SdfNode,
path: impl AsRef<Path>,
config: &PrintConfig,
) -> Result<ExportStats, ExportError> {
let mesh = node_to_mesh(node, config);
if mesh.indices.is_empty() {
return Err(ExportError::EmptyMesh);
}
let stats = ExportStats::from_mesh(&mesh, &path);
export_stl(&mesh, path)?;
Ok(stats)
}
pub fn node_to_3mf(
node: &SdfNode,
path: impl AsRef<Path>,
config: &PrintConfig,
) -> Result<ExportStats, ExportError> {
let mesh = node_to_mesh(node, config);
if mesh.indices.is_empty() {
return Err(ExportError::EmptyMesh);
}
let stats = ExportStats::from_mesh(&mesh, &path);
export_3mf(&mesh, path)?;
Ok(stats)
}
pub fn lol_to_stl(
lol_text: &str,
path: impl AsRef<Path>,
config: &PrintConfig,
) -> Result<ExportStats, ExportError> {
let node = crate::runtime_parser::parse_lol(lol_text)?;
node_to_stl(&node, path, config)
}
pub fn lol_to_3mf(
lol_text: &str,
path: impl AsRef<Path>,
config: &PrintConfig,
) -> Result<ExportStats, ExportError> {
let node = crate::runtime_parser::parse_lol(lol_text)?;
node_to_3mf(&node, path, config)
}
pub fn node_to_fbx(
node: &SdfNode,
path: impl AsRef<Path>,
config: &PrintConfig,
) -> Result<ExportStats, ExportError> {
let mesh = node_to_mesh(node, config);
if mesh.indices.is_empty() {
return Err(ExportError::EmptyMesh);
}
let stats = ExportStats::from_mesh(&mesh, &path);
export_fbx(&mesh, path, &FbxConfig::binary(), None)?;
Ok(stats)
}
pub fn lol_to_fbx(
lol_text: &str,
path: impl AsRef<Path>,
config: &PrintConfig,
) -> Result<ExportStats, ExportError> {
let node = crate::runtime_parser::parse_lol(lol_text)?;
node_to_fbx(&node, path, config)
}
#[must_use]
pub fn node_to_mesh_dual_contouring(node: &SdfNode, config: &PrintConfig) -> Mesh {
let dc_config = DualContouringConfig {
resolution: config.resolution,
compute_normals: true,
..DualContouringConfig::default()
};
let mut mesh = dual_contouring(node, config.bounds_min, config.bounds_max, &dc_config);
if (config.scale_mm - 1.0).abs() > f32::EPSILON {
for v in &mut mesh.vertices {
v.position *= config.scale_mm;
}
}
mesh
}
pub fn node_to_stl_dual_contouring(
node: &SdfNode,
path: impl AsRef<Path>,
config: &PrintConfig,
) -> Result<ExportStats, ExportError> {
let mesh = node_to_mesh_dual_contouring(node, config);
if mesh.indices.is_empty() {
return Err(ExportError::EmptyMesh);
}
let stats = ExportStats::from_mesh(&mesh, &path);
export_stl(&mesh, path)?;
Ok(stats)
}
pub fn node_to_3mf_dual_contouring(
node: &SdfNode,
path: impl AsRef<Path>,
config: &PrintConfig,
) -> Result<ExportStats, ExportError> {
let mesh = node_to_mesh_dual_contouring(node, config);
if mesh.indices.is_empty() {
return Err(ExportError::EmptyMesh);
}
let stats = ExportStats::from_mesh(&mesh, &path);
export_3mf(&mesh, path)?;
Ok(stats)
}
#[derive(Debug, Clone)]
pub struct ExportStats {
pub vertex_count: usize,
pub triangle_count: usize,
pub path: String,
}
impl ExportStats {
fn from_mesh(mesh: &Mesh, path: &impl AsRef<Path>) -> Self {
Self {
vertex_count: mesh.vertices.len(),
triangle_count: mesh.indices.len() / 3,
path: path.as_ref().display().to_string(),
}
}
}
impl std::fmt::Display for ExportStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}: {} vertices, {} triangles",
self.path, self.vertex_count, self.triangle_count
)
}
}