Define a kernel-neutral native B-rep format and guarded adapter (#461)
A SolidCraft-owned portable B-rep schema (geometry schema 2 with analytic curves and surfaces, explicit frames and ranges, fitted intersection curves) and a versioned body envelope (SCBREP03) with its own metadata records, a bounded binary codec, a validator with resource budgets, and an adapter that refuses what it can't convert exactly. Pinned by a box-and-fillet golden fixture. Not yet written by application saves. Refs #303.
This commit is contained in:
1 parent
3159fc0dd0
commit
9f88537927
14 files changed
+3925
-1
No files matched your search
@@ -0,0 +1,409 @@
|
||||
//! SolidCraft-owned portable B-rep data, independent of a modelling backend.
|
||||
//! Millimetres, radians, right-handed Z-up. See docs/native-brep.md for semantics.
|
||||
//! Validation checks structure and resource budgets, not geometric equivalence or trust.
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod envelope;
|
||||
|
||||
pub const VERSION: u32 = 2;
|
||||
pub const MAX_ITEMS: usize = 100_000;
|
||||
const MAX_DEPTH: usize = 16;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Brep {
|
||||
pub version: u32,
|
||||
pub shells: Vec<Shell>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Shell {
|
||||
pub vertices: Vec<[f64; 3]>,
|
||||
pub edges: Vec<Edge>,
|
||||
pub faces: Vec<Face>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Edge {
|
||||
pub vertices: [u32; 2],
|
||||
pub curve: Curve,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Face {
|
||||
pub loops: Vec<Vec<EdgeUse>>,
|
||||
pub surface: Surface,
|
||||
pub forward: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EdgeUse {
|
||||
pub edge: u32,
|
||||
pub forward: bool,
|
||||
}
|
||||
|
||||
/// Right-handed orthonormal frame; z = x_axis cross y_axis.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Frame {
|
||||
pub origin: [f64; 3],
|
||||
pub x_axis: [f64; 3],
|
||||
pub y_axis: [f64; 3],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum Curve {
|
||||
Line { start: [f64; 3], end: [f64; 3] },
|
||||
CircleArc { frame: Frame, radius: f64, angle: [f64; 2] },
|
||||
EllipseArc { frame: Frame, radii: [f64; 2], angle: [f64; 2] },
|
||||
Polyline { points: Vec<[f64; 3]> },
|
||||
Spline { knots: Vec<f64>, points: Vec<[f64; 3]> },
|
||||
RationalSpline { knots: Vec<f64>, homogeneous: Vec<[f64; 4]> },
|
||||
Intersection { knots: Vec<f64>, points: Vec<[f64; 3]>, fitting_tolerance: f64, faces: Option<[u32; 2]> },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum Surface {
|
||||
Plane { origin: [f64; 3], u_axis: [f64; 3], v_axis: [f64; 3] },
|
||||
Cylinder { frame: Frame, radius: f64, angle: [f64; 2], height: [f64; 2] },
|
||||
Cone { frame: Frame, radius_at_origin: f64, half_angle: f64, angle: [f64; 2], height: [f64; 2] },
|
||||
Sphere { frame: Frame, radius: f64, longitude: [f64; 2], latitude: [f64; 2] },
|
||||
Torus { frame: Frame, major_radius: f64, minor_radius: f64, longitude: [f64; 2], meridian: [f64; 2] },
|
||||
Spline { u_knots: Vec<f64>, v_knots: Vec<f64>, points: Vec<Vec<[f64; 3]>> },
|
||||
RationalSpline { u_knots: Vec<f64>, v_knots: Vec<f64>, homogeneous: Vec<Vec<[f64; 4]>> },
|
||||
Revolution { profile: Box<Curve>, origin: [f64; 3], axis: [f64; 3], angle: [f64; 2] },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
#[error("portable B-rep: {0}")]
|
||||
pub struct Invalid(pub &'static str);
|
||||
|
||||
type Result<T> = std::result::Result<T, Invalid>;
|
||||
|
||||
fn require(condition: bool, message: &'static str) -> Result<()> {
|
||||
if condition { Ok(()) } else { Err(Invalid(message)) }
|
||||
}
|
||||
|
||||
struct Budget(usize);
|
||||
impl Budget {
|
||||
fn add(&mut self, n: usize) -> Result<()> {
|
||||
self.0 = self.0.checked_add(n).ok_or(Invalid("item budget"))?;
|
||||
require(self.0 <= MAX_ITEMS, "item budget")
|
||||
}
|
||||
}
|
||||
fn numbers(values: impl IntoIterator<Item = f64>) -> Result<()> {
|
||||
require(values.into_iter().all(|v| v.is_finite() && v.abs() <= 1e12), "non-finite or excessive coordinate")
|
||||
}
|
||||
fn points<const N: usize>(values: &[[f64; N]], budget: &mut Budget) -> Result<()> {
|
||||
budget.add(values.len())?;
|
||||
numbers(values.iter().flatten().copied())
|
||||
}
|
||||
fn knots(values: &[f64], count: usize, budget: &mut Budget) -> Result<()> {
|
||||
budget.add(values.len())?;
|
||||
numbers(values.iter().copied())?;
|
||||
let degree = values.len().checked_sub(count.saturating_add(1)).ok_or(Invalid("spline degree"))?;
|
||||
require((2..=4096).contains(&count) && (1..=16).contains(°ree) && degree < count, "spline degree or size")?;
|
||||
require(values.windows(2).all(|w| w.first() <= w.get(1)), "unordered knots")?;
|
||||
require(values.get(degree) < values.get(count), "empty spline domain")?;
|
||||
// No knot may have multiplicity greater than degree + 1.
|
||||
let mut run = 1;
|
||||
for pair in values.windows(2) {
|
||||
run = if pair.first() == pair.get(1) { run + 1 } else { 1 };
|
||||
require(run <= degree + 1, "knot multiplicity")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn weights(values: &[[f64; 4]]) -> Result<()> {
|
||||
require(values.iter().all(|p| p.get(3).is_some_and(|w| *w >= 1e-12)), "non-positive rational weight")
|
||||
}
|
||||
fn grid<const N: usize>(u: &[f64], v: &[f64], rows: &[Vec<[f64; N]>], budget: &mut Budget) -> Result<()> {
|
||||
let columns = rows.first().map_or(0, Vec::len);
|
||||
knots(u, rows.len(), budget)?;
|
||||
knots(v, columns, budget)?;
|
||||
for row in rows {
|
||||
require(row.len() == columns, "ragged surface")?;
|
||||
points(row, budget)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn vector(p: [f64; 3]) -> crate::Vec3 {
|
||||
crate::Vec3::new(p[0], p[1], p[2])
|
||||
}
|
||||
fn frame(f: &Frame, budget: &mut Budget) -> Result<()> {
|
||||
points(&[f.origin, f.x_axis, f.y_axis], budget)?;
|
||||
let (x, y) = (vector(f.x_axis), vector(f.y_axis));
|
||||
require((x.len() - 1.).abs() <= 1e-8 && (y.len() - 1.).abs() <= 1e-8 && x.dot(y).abs() <= 1e-8, "nonorthonormal frame")
|
||||
}
|
||||
fn range(r: &[f64; 2]) -> Result<()> {
|
||||
numbers(*r)?;
|
||||
require(r[0] < r[1], "empty parameter range")
|
||||
}
|
||||
fn angle(r: &[f64; 2]) -> Result<()> {
|
||||
range(r)?;
|
||||
require(r[1] - r[0] <= std::f64::consts::TAU + 1e-12, "angular range exceeds one turn")
|
||||
}
|
||||
fn radius(r: f64) -> Result<()> {
|
||||
require(r.is_finite() && (1e-12..=1e12).contains(&r), "radius")
|
||||
}
|
||||
fn curve(c: &Curve, depth: usize, budget: &mut Budget) -> Result<()> {
|
||||
require(depth <= MAX_DEPTH, "geometry depth")?;
|
||||
budget.add(1)?;
|
||||
match c {
|
||||
Curve::Line { start, end } => {
|
||||
points(&[*start, *end], budget)?;
|
||||
require(start != end, "collapsed line")
|
||||
}
|
||||
Curve::CircleArc { frame: f, radius: r, angle: a } => {
|
||||
frame(f, budget)?;
|
||||
radius(*r)?;
|
||||
angle(a)
|
||||
}
|
||||
Curve::EllipseArc { frame: f, radii, angle: a } => {
|
||||
frame(f, budget)?;
|
||||
for r in radii {
|
||||
radius(*r)?;
|
||||
}
|
||||
angle(a)
|
||||
}
|
||||
Curve::Polyline { points: p } => {
|
||||
require(p.len() >= 2, "empty polyline")?;
|
||||
points(p, budget)?;
|
||||
require(p.windows(2).all(|p| p.first() != p.get(1)), "collapsed polyline segment")
|
||||
}
|
||||
Curve::Spline { knots: k, points: p } => {
|
||||
knots(k, p.len(), budget)?;
|
||||
points(p, budget)
|
||||
}
|
||||
Curve::RationalSpline { knots: k, homogeneous: p } => {
|
||||
knots(k, p.len(), budget)?;
|
||||
points(p, budget)?;
|
||||
weights(p)
|
||||
}
|
||||
Curve::Intersection { knots: k, points: p, fitting_tolerance, .. } => {
|
||||
require((1e-12..=1.0).contains(fitting_tolerance), "intersection fitting tolerance")?;
|
||||
knots(k, p.len(), budget)?;
|
||||
points(p, budget)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn surface(s: &Surface, depth: usize, budget: &mut Budget) -> Result<()> {
|
||||
require(depth <= MAX_DEPTH, "geometry depth")?;
|
||||
budget.add(1)?;
|
||||
match s {
|
||||
Surface::Plane { origin, u_axis, v_axis } => {
|
||||
points(&[*origin, *u_axis, *v_axis], budget)?;
|
||||
require(vector(*u_axis).cross(vector(*v_axis)).len() > 1e-12, "degenerate plane")
|
||||
}
|
||||
Surface::Cylinder { frame: f, radius: r, angle: a, height } => {
|
||||
frame(f, budget)?;
|
||||
radius(*r)?;
|
||||
angle(a)?;
|
||||
range(height)
|
||||
}
|
||||
Surface::Cone { frame: f, radius_at_origin, half_angle, angle: a, height } => {
|
||||
frame(f, budget)?;
|
||||
numbers([*radius_at_origin, *half_angle])?;
|
||||
require(*radius_at_origin >= 0. && *half_angle > 0. && *half_angle < std::f64::consts::FRAC_PI_2, "cone dimensions")?;
|
||||
angle(a)?;
|
||||
range(height)?;
|
||||
require(height.iter().all(|h| radius_at_origin + h * half_angle.tan() >= 0.), "negative cone radius")
|
||||
}
|
||||
Surface::Sphere { frame: f, radius: r, longitude, latitude } => {
|
||||
frame(f, budget)?;
|
||||
radius(*r)?;
|
||||
angle(longitude)?;
|
||||
range(latitude)?;
|
||||
require(latitude[0] >= -std::f64::consts::FRAC_PI_2 && latitude[1] <= std::f64::consts::FRAC_PI_2, "sphere latitude")
|
||||
}
|
||||
Surface::Torus { frame: f, major_radius, minor_radius, longitude, meridian } => {
|
||||
frame(f, budget)?;
|
||||
radius(*major_radius)?;
|
||||
radius(*minor_radius)?;
|
||||
require(major_radius > minor_radius, "ring torus radii")?;
|
||||
angle(longitude)?;
|
||||
angle(meridian)
|
||||
}
|
||||
Surface::Spline { u_knots, v_knots, points: p } => grid(u_knots, v_knots, p, budget),
|
||||
Surface::RationalSpline { u_knots, v_knots, homogeneous: p } => {
|
||||
grid(u_knots, v_knots, p, budget)?;
|
||||
for row in p {
|
||||
weights(row)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Surface::Revolution { profile, origin, axis, angle: a } => {
|
||||
points(&[*origin, *axis], budget)?;
|
||||
require((vector(*axis).len() - 1.0).abs() <= 1e-8, "revolution axis is not unit length")?;
|
||||
angle(a)?;
|
||||
curve(profile, depth + 1, budget)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Brep {
|
||||
/// Validate portable structure without calling a kernel or evaluating geometry.
|
||||
/// Does not certify watertightness, surface coincidence, or authoritative state.
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
require(self.version == VERSION, "unsupported version")?;
|
||||
require(!self.shells.is_empty() && self.shells.len() <= 1024, "shell count")?;
|
||||
let mut budget = Budget(0);
|
||||
for shell in &self.shells {
|
||||
points(&shell.vertices, &mut budget)?;
|
||||
budget.add(shell.edges.len())?;
|
||||
budget.add(shell.faces.len())?;
|
||||
require(!shell.faces.is_empty(), "empty shell")?;
|
||||
for edge in &shell.edges {
|
||||
require(edge.vertices.iter().all(|&v| (v as usize) < shell.vertices.len()), "edge vertices")?;
|
||||
curve(&edge.curve, 0, &mut budget)?;
|
||||
if let Curve::Intersection { faces: Some(hints), .. } = &edge.curve {
|
||||
require(hints.iter().all(|&i| (i as usize) < shell.faces.len()), "intersection face hint")?;
|
||||
}
|
||||
}
|
||||
for face in &shell.faces {
|
||||
require(!face.loops.is_empty(), "face loops")?;
|
||||
surface(&face.surface, 0, &mut budget)?;
|
||||
for wire in &face.loops {
|
||||
require(!wire.is_empty(), "empty wire")?;
|
||||
budget.add(wire.len())?;
|
||||
let mut first = None;
|
||||
let mut last = None;
|
||||
for use_ in wire {
|
||||
let edge = shell.edges.get(use_.edge as usize).ok_or(Invalid("edge reference"))?;
|
||||
let [a, b] = edge.vertices;
|
||||
let (start, end) = if use_.forward { (a, b) } else { (b, a) };
|
||||
require(last.is_none_or(|v| v == start), "disconnected wire")?;
|
||||
first.get_or_insert(start);
|
||||
last = Some(end);
|
||||
}
|
||||
require(first == last, "open wire")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn triangle() -> Brep {
|
||||
let vertices = vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.]];
|
||||
let edges = (0..3)
|
||||
.map(|i| Edge { vertices: [i as u32, ((i + 1) % 3) as u32], curve: Curve::Line { start: vertices[i], end: vertices[(i + 1) % 3] } })
|
||||
.collect();
|
||||
Brep {
|
||||
version: VERSION,
|
||||
shells: vec![Shell {
|
||||
vertices,
|
||||
edges,
|
||||
faces: vec![Face {
|
||||
loops: vec![(0..3).map(|edge| EdgeUse { edge, forward: true }).collect()],
|
||||
surface: Surface::Plane { origin: [0.; 3], u_axis: [1., 0., 0.], v_axis: [0., 1., 0.] },
|
||||
forward: true,
|
||||
}],
|
||||
}],
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn portable_open_face_round_trip_without_a_kernel() {
|
||||
let body = triangle();
|
||||
body.validate().unwrap();
|
||||
let bytes = serde_json::to_vec(&body).unwrap();
|
||||
let restored: Brep = serde_json::from_slice(&bytes).unwrap();
|
||||
restored.validate().unwrap();
|
||||
assert_eq!(body, restored);
|
||||
assert!(!String::from_utf8(bytes).unwrap().contains("truck"));
|
||||
}
|
||||
#[test]
|
||||
fn topology_references_and_wire_orientation_are_checked() {
|
||||
let mut b = triangle();
|
||||
b.shells[0].faces[0].loops[0][0].edge = u32::MAX;
|
||||
assert!(b.validate().is_err());
|
||||
let mut b = triangle();
|
||||
b.shells[0].faces[0].loops[0][1].forward = false;
|
||||
assert!(b.validate().is_err());
|
||||
let mut b = triangle();
|
||||
b.shells[0].edges[0].vertices[0] = u32::MAX;
|
||||
assert!(b.validate().is_err());
|
||||
let mut b = triangle();
|
||||
b.version += 1;
|
||||
assert!(b.validate().is_err());
|
||||
let mut b = triangle();
|
||||
b.shells[0].vertices[0][0] = f64::NAN;
|
||||
assert!(b.validate().is_err());
|
||||
}
|
||||
#[test]
|
||||
fn closed_edge_can_share_its_endpoint_vertex() {
|
||||
let mut body = triangle();
|
||||
body.shells[0].vertices.truncate(1);
|
||||
body.shells[0].edges =
|
||||
vec![Edge { vertices: [0, 0], curve: Curve::Polyline { points: vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 0.]] } }];
|
||||
body.shells[0].faces[0].loops = vec![vec![EdgeUse { edge: 0, forward: true }]];
|
||||
body.validate().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spline_domains_weights_and_nesting_are_bounded() {
|
||||
let mut budget = Budget(0);
|
||||
assert!(knots(&[0., 0., 1., 1.], 2, &mut budget).is_ok());
|
||||
for bad in [vec![0., 0., 0., 0.], vec![0., 1., 0., 1.], vec![0., 0., f64::INFINITY, 1.]] {
|
||||
assert!(knots(&bad, 2, &mut Budget(0)).is_err());
|
||||
}
|
||||
let c = Curve::RationalSpline { knots: vec![0., 0., 1., 1.], homogeneous: vec![[0., 0., 0., 0.], [1., 0., 0., 1.]] };
|
||||
assert!(curve(&c, 0, &mut Budget(0)).is_err());
|
||||
let p = Surface::Plane { origin: [0.; 3], u_axis: [1., 0., 0.], v_axis: [2., 0., 0.] };
|
||||
assert!(surface(&p, 0, &mut Budget(0)).is_err());
|
||||
assert!(curve(&Curve::Line { start: [0.; 3], end: [1.; 3] }, MAX_DEPTH + 1, &mut Budget(0)).is_err());
|
||||
assert!(Budget(MAX_ITEMS).add(1).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analytic_records_have_independent_frames_and_domains() {
|
||||
use std::f64::consts::{FRAC_PI_2, TAU};
|
||||
let f = Frame { origin: [1., 2., 3.], x_axis: [1., 0., 0.], y_axis: [0., 1., 0.] };
|
||||
let surfaces = [
|
||||
Surface::Cylinder { frame: f.clone(), radius: 2., angle: [0., TAU], height: [-1., 3.] },
|
||||
Surface::Cone { frame: f.clone(), radius_at_origin: 2., half_angle: 0.2, angle: [0., TAU], height: [0., 3.] },
|
||||
Surface::Sphere { frame: f.clone(), radius: 2., longitude: [0., TAU], latitude: [-FRAC_PI_2, FRAC_PI_2] },
|
||||
Surface::Torus { frame: f.clone(), major_radius: 4., minor_radius: 1., longitude: [0., TAU], meridian: [0., TAU] },
|
||||
Surface::Revolution {
|
||||
profile: Box::new(Curve::Line { start: [2., 0., 0.], end: [2., 0., 3.] }),
|
||||
origin: [0.; 3],
|
||||
axis: [0., 0., 1.],
|
||||
angle: [0., TAU],
|
||||
},
|
||||
];
|
||||
for s in surfaces {
|
||||
surface(&s, 0, &mut Budget(0)).unwrap();
|
||||
let encoded = serde_json::to_string(&s).unwrap();
|
||||
assert_eq!(serde_json::from_str::<Surface>(&encoded).unwrap(), s);
|
||||
assert!(!encoded.contains("columns") && !encoded.contains("forward"));
|
||||
}
|
||||
for c in [
|
||||
Curve::CircleArc { frame: f.clone(), radius: 2., angle: [0., FRAC_PI_2] },
|
||||
Curve::EllipseArc { frame: f.clone(), radii: [2., 3.], angle: [0., TAU] },
|
||||
Curve::Intersection { knots: vec![0., 0., 1., 1.], points: vec![[0.; 3], [1.; 3]], fitting_tolerance: 1e-6, faces: None },
|
||||
] {
|
||||
curve(&c, 0, &mut Budget(0)).unwrap();
|
||||
let encoded = serde_json::to_string(&c).unwrap();
|
||||
assert_eq!(serde_json::from_str::<Curve>(&encoded).unwrap(), c);
|
||||
assert!(!encoded.contains("leader") && !encoded.contains("surfaces"));
|
||||
}
|
||||
let mut bad = f;
|
||||
bad.y_axis = bad.x_axis;
|
||||
assert!(frame(&bad, &mut Budget(0)).is_err());
|
||||
assert!(angle(&[0., TAU + 0.1]).is_err());
|
||||
assert!(range(&[2., 1.]).is_err());
|
||||
assert!(radius(f64::NAN).is_err());
|
||||
let mut b = triangle();
|
||||
b.shells[0].edges[0].curve =
|
||||
Curve::Intersection { knots: vec![0., 0., 1., 1.], points: vec![[0.; 3], [1., 0., 0.]], fitting_tolerance: 1e-6, faces: Some([0, 1]) };
|
||||
assert!(b.validate().is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
//! Stable version-one body metadata records. Runtime model types never define this wire contract.
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const VERSION: u32 = 1;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Metadata {
|
||||
pub version: u32,
|
||||
pub color: Option<[f32; 3]>,
|
||||
pub paint: Option<Paint>,
|
||||
pub boundaries: Option<Vec<Vec<[f64; 3]>>>,
|
||||
pub face_sources: Option<Vec<Option<FaceSource>>>,
|
||||
pub history: Option<History>,
|
||||
pub preserve_boundaries: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Plane {
|
||||
pub origin: [f64; 3],
|
||||
pub x_axis: [f64; 3],
|
||||
pub y_axis: [f64; 3],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum Segment {
|
||||
Line { start: [f64; 2], end: [f64; 2] },
|
||||
Arc { center: [f64; 2], radius: f64, start: f64, sweep: f64 },
|
||||
Cubic { points: [[f64; 2]; 4] },
|
||||
Conic { points: [[f64; 2]; 3], middle_weight: f64 },
|
||||
Bezier { points: Vec<[f64; 2]> },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Region {
|
||||
pub outer: Vec<Segment>,
|
||||
pub holes: Vec<Vec<Segment>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum FaceSource {
|
||||
ExtrusionSide { segment: Segment, plane: Option<Plane>, coalesced: bool },
|
||||
CertifiedExtrusionSide { producer: u64, sketch: u64, curve: String, plane: Option<Plane> },
|
||||
ExtrusionCap { high: bool, region: Region, plane: Plane },
|
||||
CertifiedExtrusionCap { producer: u64, sketch: u64, high: bool, outer: Vec<String>, holes: Vec<Vec<String>>, plane: Plane },
|
||||
CylinderCap { producer: u64, high: bool, plane: Plane },
|
||||
BoxFace { producer: Option<u64>, axis: u8, high: bool, plane: Plane },
|
||||
PlanarShell { producer: Option<u64>, source: Box<FaceSource>, inner: bool, plane: Plane },
|
||||
Imported { item: u64, face: u64, payload_sha256: Option<String>, producer: Option<u64> },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Paint {
|
||||
pub opacity: f32,
|
||||
pub faces: Vec<FacePaint>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FacePaint {
|
||||
pub face: u32,
|
||||
pub color: [f32; 3],
|
||||
pub opacity: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct History {
|
||||
pub operands: u32,
|
||||
pub faces: Vec<FaceHistory>,
|
||||
pub edges: Vec<EdgeHistory>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FaceHistory {
|
||||
pub origins: Vec<FaceOrigin>,
|
||||
pub change: Change,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EdgeHistory {
|
||||
pub faces: Vec<u32>,
|
||||
pub origins: Vec<EdgeOrigin>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum FaceOrigin {
|
||||
Face { operand: u32, face: u32 },
|
||||
Profile { segment: u32 },
|
||||
Cap { end: bool },
|
||||
Edge { operand: u32, edge: u32 },
|
||||
Offset { operand: u32, face: u32 },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum EdgeOrigin {
|
||||
Edge { operand: u32, edge: u32 },
|
||||
Meeting { sides: [Vec<FaceOrigin>; 2] },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Change {
|
||||
Kept,
|
||||
Modified,
|
||||
Split,
|
||||
Merged,
|
||||
Generated,
|
||||
}
|
||||
@@ -3,11 +3,12 @@
|
||||
//! (B-spline basis and NURBS surfaces in [`bspline`], one-surface fills of closed loops in
|
||||
//! [`fill`], T-splines in [`tspline`]). Units are millimetres.
|
||||
//!
|
||||
//! This crate has no knowledge of sketches, documents or the B-rep kernel; every other crate
|
||||
//! This crate has no knowledge of sketches, documents or a modelling backend; every other crate
|
||||
//! builds on it.
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod brep;
|
||||
pub mod bspline;
|
||||
pub mod env;
|
||||
pub mod fill;
|
||||
|
||||
Reference in new issue
Block a user