use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SheetTree {
pub thickness: f64,
pub root: Flat,
#[serde(default)]
pub default_inside_radius: f64,
#[serde(default = "half")]
pub default_k_factor: f64,
#[serde(default = "identity_placement")]
pub root_transform: [[f64; 3]; 4],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Flat {
pub id: String,
pub outline: Vec<[f64; 2]>,
#[serde(default)]
pub edges: Vec<Edge>,
#[serde(default)]
pub holes: Vec<Hole>,
#[serde(default)]
pub hole_bends: Vec<HoleBend>,
#[serde(default)]
pub outline_curves: std::collections::BTreeMap<String, crate::NurbsCurve>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HoleBend {
pub id: String,
pub hole: usize,
pub segment: usize,
#[serde(default)]
pub reversed: bool,
pub angle_deg: f64,
pub inside_radius: f64,
#[serde(default = "half")]
pub k_factor: f64,
pub kind: HoleBendKind,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum HoleBendKind {
Straight { child: Box<Flat> },
Collar { leg: f64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Hole {
pub outer: Vec<crate::NurbsCurve>,
#[serde(default)]
pub islands: Vec<Vec<crate::NurbsCurve>>,
#[serde(default)]
pub z_min: Option<f64>,
#[serde(default)]
pub z_max: Option<f64>,
}
impl Hole {
pub fn through(outer: Vec<crate::NurbsCurve>) -> Self {
Hole {
outer,
islands: Vec::new(),
z_min: None,
z_max: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Edge {
#[serde(default)]
pub id: String,
#[serde(default)]
pub bend: Option<Bend>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bend {
pub id: String,
pub angle_deg: f64,
pub inside_radius: f64,
#[serde(default = "half")]
pub k_factor: f64,
pub child: Box<Flat>,
}
fn half() -> f64 {
0.5
}
pub fn identity_placement() -> [[f64; 3]; 4] {
[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
]
}
impl SheetTree {
pub fn half_thickness(&self) -> f64 {
self.thickness * 0.5
}
}
impl Bend {
pub fn mid_radius(&self, thickness: f64) -> f64 {
(self.inside_radius + thickness * 0.5).max(1e-6)
}
pub fn neutral_radius(&self, thickness: f64) -> f64 {
self.mid_radius(thickness) + (self.k_factor - 0.5) * thickness
}
pub fn allowance(&self, thickness: f64) -> f64 {
self.angle_deg.to_radians().abs() * self.neutral_radius(thickness)
}
}
impl Flat {
pub(crate) fn find_by_id(&self, flat_id: &str) -> Option<&Self> {
if self.id == flat_id {
return Some(self);
}
for edge in &self.edges {
if let Some(bend) = &edge.bend {
if let Some(found) = bend.child.find_by_id(flat_id) {
return Some(found);
}
}
}
for hole_bend in &self.hole_bends {
if let HoleBendKind::Straight { child } = &hole_bend.kind {
if let Some(found) = child.find_by_id(flat_id) {
return Some(found);
}
}
}
None
}
pub(crate) fn find_by_id_mut(&mut self, flat_id: &str) -> Option<&mut Self> {
if self.id == flat_id {
return Some(self);
}
for edge in &mut self.edges {
if let Some(bend) = &mut edge.bend {
if let Some(found) = bend.child.find_by_id_mut(flat_id) {
return Some(found);
}
}
}
for hole_bend in &mut self.hole_bends {
if let HoleBendKind::Straight { child } = &mut hole_bend.kind {
if let Some(found) = child.find_by_id_mut(flat_id) {
return Some(found);
}
}
}
None
}
pub(super) fn densified_outline(&self) -> Result<Vec<[f64; 2]>, String> {
if self.outline_curves.is_empty() {
return Ok(self.outline.clone());
}
let mut poly = Vec::with_capacity(self.outline.len());
for (i, vertex) in self.outline.iter().enumerate() {
match self
.edges
.get(i)
.and_then(|edge| self.outline_curves.get(&edge.id))
{
Some(curve) => {
let [t0, t1] = curve.domain()?;
for step in 0..16 {
let t = t0 + (t1 - t0) * (step as f64) / 16.0;
let p = curve.evaluate(t)?;
poly.push([p.x, p.y]);
}
}
None => poly.push(*vertex),
}
}
Ok(poly)
}
}