use crate::model::ResourceId;
use glam::Vec3;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ObjectType {
#[default]
Model,
Support,
#[serde(rename = "solidsupport")]
SolidSupport,
Surface,
Other,
}
impl std::fmt::Display for ObjectType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ObjectType::Model => write!(f, "model"),
ObjectType::Support => write!(f, "support"),
ObjectType::SolidSupport => write!(f, "solidsupport"),
ObjectType::Surface => write!(f, "surface"),
ObjectType::Other => write!(f, "other"),
}
}
}
impl ObjectType {
pub fn requires_manifold(&self) -> bool {
matches!(self, ObjectType::Model | ObjectType::SolidSupport)
}
pub fn can_be_in_build(&self) -> bool {
!matches!(self, ObjectType::Other)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Object {
pub id: ResourceId,
#[serde(default)]
pub object_type: ObjectType,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub part_number: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<ResourceId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pindex: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumbnail: Option<String>,
pub geometry: Geometry,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Geometry {
Mesh(Mesh),
Components(Components),
SliceStack(ResourceId),
VolumetricStack(ResourceId),
BooleanShape(BooleanShape),
DisplacementMesh(DisplacementMesh),
}
impl Geometry {
pub fn has_content(&self) -> bool {
match self {
Geometry::Mesh(mesh) => !mesh.vertices.is_empty() || !mesh.triangles.is_empty(),
Geometry::Components(c) => !c.components.is_empty(),
Geometry::BooleanShape(_) => true,
Geometry::DisplacementMesh(_) => true,
Geometry::SliceStack(_) | Geometry::VolumetricStack(_) => false,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Components {
pub components: Vec<Component>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Component {
pub object_id: ResourceId,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<Uuid>,
#[serde(default = "default_transform", skip_serializing_if = "is_identity")]
pub transform: glam::Mat4,
}
fn default_transform() -> glam::Mat4 {
glam::Mat4::IDENTITY
}
fn is_identity(transform: &glam::Mat4) -> bool {
*transform == glam::Mat4::IDENTITY
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum BooleanOperationType {
#[default]
Union,
Difference,
Intersection,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BooleanOperation {
#[serde(default)]
pub operation_type: BooleanOperationType,
pub object_id: ResourceId,
#[serde(default = "default_transform", skip_serializing_if = "is_identity")]
pub transform: glam::Mat4,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BooleanShape {
pub base_object_id: ResourceId,
#[serde(default = "default_transform", skip_serializing_if = "is_identity")]
pub base_transform: glam::Mat4,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_path: Option<String>,
pub operations: Vec<BooleanOperation>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Mesh {
pub vertices: Vec<Vertex>,
pub triangles: Vec<Triangle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub beam_lattice: Option<BeamLattice>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct BeamLattice {
#[serde(skip_serializing_if = "Option::is_none")]
pub radius: Option<f32>,
#[serde(default)]
pub min_length: f32,
#[serde(default)]
pub precision: f32,
#[serde(default)]
pub clipping_mode: ClippingMode,
pub beams: Vec<Beam>,
pub beam_sets: Vec<BeamSet>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ClippingMode {
#[default]
None,
Inside,
Outside,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Beam {
pub v1: u32,
pub v2: u32,
pub r1: f32,
pub r2: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub p1: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p2: Option<u32>,
#[serde(default)]
pub cap_mode: CapMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CapMode {
#[default]
Sphere,
Hemisphere,
Butt,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct BeamSet {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub identifier: Option<String>,
pub refs: Vec<u32>,
}
impl Mesh {
pub fn new() -> Self {
Self::default()
}
pub fn add_vertex(&mut self, x: f32, y: f32, z: f32) -> u32 {
self.vertices.push(Vertex { x, y, z });
(self.vertices.len() - 1) as u32
}
pub fn add_triangle(&mut self, v1: u32, v2: u32, v3: u32) {
self.triangles.push(Triangle {
v1,
v2,
v3,
..Default::default()
});
}
pub fn compute_aabb(&self) -> Option<crate::model::stats::BoundingBox> {
if self.vertices.is_empty() {
return None;
}
let initial = (
f32::INFINITY,
f32::INFINITY,
f32::INFINITY,
f32::NEG_INFINITY,
f32::NEG_INFINITY,
f32::NEG_INFINITY,
);
#[cfg(feature = "parallel")]
let (min_x, min_y, min_z, max_x, max_y, max_z) = {
use rayon::prelude::*;
self.vertices
.par_iter()
.fold(
|| initial,
|acc, v| {
(
acc.0.min(v.x),
acc.1.min(v.y),
acc.2.min(v.z),
acc.3.max(v.x),
acc.4.max(v.y),
acc.5.max(v.z),
)
},
)
.reduce(
|| initial,
|a, b| {
(
a.0.min(b.0),
a.1.min(b.1),
a.2.min(b.2),
a.3.max(b.3),
a.4.max(b.4),
a.5.max(b.5),
)
},
)
};
#[cfg(not(feature = "parallel"))]
let (min_x, min_y, min_z, max_x, max_y, max_z) =
self.vertices.iter().fold(initial, |acc, v| {
(
acc.0.min(v.x),
acc.1.min(v.y),
acc.2.min(v.z),
acc.3.max(v.x),
acc.4.max(v.y),
acc.5.max(v.z),
)
});
Some(crate::model::stats::BoundingBox {
min: [min_x, min_y, min_z],
max: [max_x, max_y, max_z],
})
}
pub fn compute_area_and_volume(&self) -> (f64, f64) {
if self.triangles.is_empty() {
return (0.0, 0.0);
}
#[cfg(feature = "parallel")]
let (area, volume) = {
use rayon::prelude::*;
self.triangles
.par_iter()
.fold(
|| (0.0f64, 0.0f64),
|acc, t| {
let (area, volume) = self.compute_triangle_stats(t);
(acc.0 + area, acc.1 + volume)
},
)
.reduce(|| (0.0, 0.0), |a, b| (a.0 + b.0, a.1 + b.1))
};
#[cfg(not(feature = "parallel"))]
let (area, volume) = self.triangles.iter().fold((0.0f64, 0.0f64), |acc, t| {
let (area, volume) = self.compute_triangle_stats(t);
(acc.0 + area, acc.1 + volume)
});
(area, volume)
}
fn compute_triangle_stats(&self, t: &Triangle) -> (f64, f64) {
let v1 = glam::Vec3::new(
self.vertices[t.v1 as usize].x,
self.vertices[t.v1 as usize].y,
self.vertices[t.v1 as usize].z,
);
let v2 = glam::Vec3::new(
self.vertices[t.v2 as usize].x,
self.vertices[t.v2 as usize].y,
self.vertices[t.v2 as usize].z,
);
let v3 = glam::Vec3::new(
self.vertices[t.v3 as usize].x,
self.vertices[t.v3 as usize].y,
self.vertices[t.v3 as usize].z,
);
let edge1 = v2 - v1;
let edge2 = v3 - v1;
let cross = edge1.cross(edge2);
let triangle_area = 0.5 * cross.length() as f64;
let triangle_volume = (v1.dot(v2.cross(v3)) / 6.0) as f64;
(triangle_area, triangle_volume)
}
pub fn compute_triangle_area(&self, triangle: &Triangle) -> f64 {
let v1 = glam::Vec3::new(
self.vertices[triangle.v1 as usize].x,
self.vertices[triangle.v1 as usize].y,
self.vertices[triangle.v1 as usize].z,
);
let v2 = glam::Vec3::new(
self.vertices[triangle.v2 as usize].x,
self.vertices[triangle.v2 as usize].y,
self.vertices[triangle.v2 as usize].z,
);
let v3 = glam::Vec3::new(
self.vertices[triangle.v3 as usize].x,
self.vertices[triangle.v3 as usize].y,
self.vertices[triangle.v3 as usize].z,
);
let edge1 = v2 - v1;
let edge2 = v3 - v1;
let cross = edge1.cross(edge2);
0.5 * cross.length() as f64
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct Vertex {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl From<Vec3> for Vertex {
fn from(v: Vec3) -> Self {
Self {
x: v.x,
y: v.y,
z: v.z,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct Triangle {
pub v1: u32,
pub v2: u32,
pub v3: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub p1: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p2: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p3: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
pub struct NormalVector {
pub nx: f32,
pub ny: f32,
pub nz: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
pub struct GradientVector {
pub gu: f32,
pub gv: f32,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct DisplacementTriangle {
pub v1: u32,
pub v2: u32,
pub v3: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub d1: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub d2: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub d3: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p1: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p2: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub p3: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<u32>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct DisplacementMesh {
pub vertices: Vec<Vertex>,
pub triangles: Vec<DisplacementTriangle>,
pub normals: Vec<NormalVector>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gradients: Option<Vec<GradientVector>>,
}