use crate::features::{FinitePoint3, FiniteVector3};
use crate::ids::{CurveId, PcurveId, ProceduralCurveId, ProceduralSurfaceId, SurfaceId, UnknownId};
use crate::math::{
sum::{fast_dot, ExactSignedSum},
Point3, Vector3,
};
use crate::provenance::SourceObjectAssociation;
use crate::scalar::{FiniteReal, NonNegativeReal, PositiveI64};
use crate::transform::Transform;
use crate::units::FiniteVector;
use cadmpeg_core::decode::{u64_from_index, DecodeContext};
use cadmpeg_core::CodecError;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::num::NonZeroI64;
pub(super) fn charge_decode_copy<T>(
count: usize,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<(), CodecError> {
let count = u64_from_index(count);
let bytes = count
.checked_mul(u64_from_index(std::mem::size_of::<T>()))
.ok_or_else(|| ctx.refuse_codec_limit(operation, u64::MAX, count))?;
ctx.charge_collection_items(count, operation)?;
ctx.charge_retained(bytes, operation)
}
pub(super) fn copy_decode_slice<T: Copy>(
values: &[T],
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Vec<T>, CodecError> {
ctx.copy_slice(values, operation)
}
pub mod analytic;
pub mod nurbs;
pub mod pcurve;
pub mod sampled;
pub mod scaling;
use analytic::{
CircleCurve, ConeSurface, CylinderSurface, DegenerateCurve, EllipseCurve, HyperbolaCurve,
LineCurve, ParabolaCurve, PlaneSurface, SphereSurface, TorusSurface,
};
use nurbs::{NurbsCurve, NurbsSurface};
use pcurve::PcurveGeometry;
use sampled::{PolygonalSurface, PolylineCurve};
pub mod curve_payloads;
mod lanes;
pub mod surface_payloads;
pub const MAX_GEOMETRY_NESTING: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "state", rename_all = "snake_case", deny_unknown_fields)]
pub enum LegacyExtensionFlags {
Absent {},
Disabled {},
Enabled {
secondary: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_tertiary"
)]
tertiary: Option<bool>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "layout", rename_all = "snake_case", deny_unknown_fields)]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub enum OffsetExtension<R = f64> {
Legacy {
flags: LegacyExtensionFlags,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
Revision {
form: Box<RevisionSurfaceForm<[bool; 4], R>>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum SolvedSurfaceGeometry {
Plane(PlaneSurface),
Cylinder(CylinderSurface),
Cone(ConeSurface),
Sphere(SphereSurface),
Torus(TorusSurface),
Nurbs(NurbsSurface),
Polygonal(PolygonalSurface),
Transformed(PlacedSurface),
Unknown {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_record"
)]
record: Option<UnknownId>,
},
}
impl SolvedSurfaceGeometry {
pub fn try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
ctx.charge_work(1, operation)?;
Ok(match self {
Self::Plane(value) => Self::Plane(*value),
Self::Cylinder(value) => Self::Cylinder(*value),
Self::Cone(value) => Self::Cone(*value),
Self::Sphere(value) => Self::Sphere(*value),
Self::Torus(value) => Self::Torus(*value),
Self::Nurbs(value) => Self::Nurbs(value.try_clone_for_decode(ctx, operation)?),
Self::Polygonal(value) => Self::Polygonal(value.try_clone_for_decode(ctx, operation)?),
Self::Transformed(value) => {
let _depth = ctx.enter_nested(operation)?;
charge_decode_copy::<Self>(1, ctx, operation)?;
Self::Transformed(PlacedSurface {
basis: Box::new(value.basis.try_clone_for_decode(ctx, operation)?),
transform: value.transform,
depth: value.depth,
})
}
Self::Unknown { record } => Self::Unknown {
record: record
.as_ref()
.map(|id| id.try_clone_for_decode(ctx, operation))
.transpose()?,
},
})
}
#[must_use]
pub(crate) const fn nesting_depth(&self) -> usize {
match self {
Self::Transformed(placed) => placed.depth,
Self::Plane(_)
| Self::Cylinder(_)
| Self::Cone(_)
| Self::Sphere(_)
| Self::Torus(_)
| Self::Nurbs(_)
| Self::Polygonal(_)
| Self::Unknown { .. } => 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "PlacedSurfaceWire")]
pub struct PlacedSurface {
basis: Box<SolvedSurfaceGeometry>,
transform: Transform,
#[serde(skip)]
depth: usize,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct PlacedSurfaceWire {
basis: Box<SolvedSurfaceGeometry>,
transform: Transform,
}
impl PlacedSurface {
pub fn try_new(
basis: Box<SolvedSurfaceGeometry>,
transform: Transform,
) -> Result<Self, &'static str> {
let Some(depth) = basis
.nesting_depth()
.checked_add(1)
.filter(|depth| *depth <= MAX_GEOMETRY_NESTING)
else {
return Err("PlacedSurface.basis nests past the admitted inline basis depth");
};
Ok(Self {
basis,
transform,
depth,
})
}
#[must_use]
pub const fn basis(&self) -> &SolvedSurfaceGeometry {
&self.basis
}
#[must_use]
pub const fn transform(&self) -> &Transform {
&self.transform
}
pub const fn set_transform(&mut self, transform: Transform) {
self.transform = transform;
}
}
impl TryFrom<PlacedSurfaceWire> for PlacedSurface {
type Error = &'static str;
fn try_from(wire: PlacedSurfaceWire) -> Result<Self, Self::Error> {
Self::try_new(wire.basis, wire.transform)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum SurfaceGeometry {
Procedural {
construction: ProceduralSurfaceId,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_surface_geometry_cache"
)]
#[cfg_attr(feature = "schema", schemars(skip))]
cache: Option<SolvedSurfaceGeometry>,
},
#[serde(untagged)]
Solved(SolvedSurfaceGeometry),
}
impl SurfaceGeometry {
pub fn try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
Ok(match self {
Self::Procedural {
construction,
cache,
} => Self::Procedural {
construction: construction.try_clone_for_decode(ctx, operation)?,
cache: cache
.as_ref()
.map(|geometry| geometry.try_clone_for_decode(ctx, operation))
.transpose()?,
},
Self::Solved(geometry) => Self::Solved(geometry.try_clone_for_decode(ctx, operation)?),
})
}
#[must_use]
pub const fn procedural_construction(&self) -> Option<&ProceduralSurfaceId> {
match self {
Self::Procedural { construction, .. } => Some(construction),
Self::Solved(_) => None,
}
}
#[must_use]
pub const fn solved_cache(&self) -> Option<&SolvedSurfaceGeometry> {
match self {
Self::Procedural { cache, .. } => cache.as_ref(),
Self::Solved(_) => None,
}
}
#[must_use]
pub const fn solved(&self) -> Option<&SolvedSurfaceGeometry> {
match self {
Self::Procedural { cache, .. } => cache.as_ref(),
Self::Solved(geometry) => Some(geometry),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Surface {
pub id: SurfaceId,
pub geometry: SurfaceGeometry,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_source_object"
)]
pub source_object: Option<SourceObjectAssociation>,
}
impl Surface {
pub fn try_clone_for_decode(
&self,
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, cadmpeg_core::CodecError> {
let id = self.id.try_clone_for_decode(ctx, operation)?;
let geometry = self.geometry.try_clone_for_decode(ctx, operation)?;
let source_object = self
.source_object
.as_ref()
.map(|source| source.try_clone_for_decode(ctx, operation))
.transpose()?;
Ok(Self {
id,
geometry,
source_object,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum SolvedCurveGeometry {
Line(LineCurve),
Circle(CircleCurve),
Ellipse(EllipseCurve),
Parabola(ParabolaCurve),
Hyperbola(HyperbolaCurve),
Degenerate(DegenerateCurve),
Composite {
segments: CompositeCurveSegments,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
self_intersect: Option<bool>,
},
Nurbs(NurbsCurve),
Polyline(PolylineCurve),
Transformed(PlacedCurve),
Unknown {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_record"
)]
record: Option<UnknownId>,
},
}
impl SolvedCurveGeometry {
pub fn try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
ctx.charge_work(1, operation)?;
Ok(match self {
Self::Line(value) => Self::Line(*value),
Self::Circle(value) => Self::Circle(*value),
Self::Ellipse(value) => Self::Ellipse(*value),
Self::Parabola(value) => Self::Parabola(*value),
Self::Hyperbola(value) => Self::Hyperbola(*value),
Self::Degenerate(value) => Self::Degenerate(*value),
Self::Composite {
segments,
self_intersect,
} => {
let mut copy = Vec::new();
ctx.reserve_vec(&mut copy, segments.len(), operation)?;
ctx.charge_work(u64_from_index(segments.len()), operation)?;
for segment in segments {
copy.push(CompositeCurveSegment {
curve: segment.curve.try_clone_for_decode(ctx, operation)?,
same_sense: segment.same_sense,
transition: segment.transition,
});
}
Self::Composite {
segments: CompositeCurveSegments::try_from(copy)
.map_err(CodecError::malformed)?,
self_intersect: *self_intersect,
}
}
Self::Nurbs(value) => Self::Nurbs(value.try_clone_for_decode(ctx, operation)?),
Self::Polyline(value) => Self::Polyline(value.try_clone_for_decode(ctx, operation)?),
Self::Transformed(value) => {
Self::Transformed(value.try_clone_for_decode(ctx, operation)?)
}
Self::Unknown { record } => Self::Unknown {
record: record
.as_ref()
.map(|id| id.try_clone_for_decode(ctx, operation))
.transpose()?,
},
})
}
#[must_use]
pub(crate) const fn nesting_depth(&self) -> usize {
match self {
Self::Transformed(placed) => placed.depth,
Self::Line(_)
| Self::Circle(_)
| Self::Ellipse(_)
| Self::Parabola(_)
| Self::Hyperbola(_)
| Self::Degenerate(_)
| Self::Composite { .. }
| Self::Nurbs(_)
| Self::Polyline(_)
| Self::Unknown { .. } => 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "PlacedCurveWire")]
pub struct PlacedCurve {
basis: Box<SolvedCurveGeometry>,
transform: Transform,
#[serde(skip)]
depth: usize,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct PlacedCurveWire {
basis: Box<SolvedCurveGeometry>,
transform: Transform,
}
impl PlacedCurve {
fn try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
let _depth = ctx.enter_nested(operation)?;
charge_decode_copy::<SolvedCurveGeometry>(1, ctx, operation)?;
let basis = if let SolvedCurveGeometry::Transformed(placed) = self.basis.as_ref() {
ctx.charge_work(1, operation)?;
SolvedCurveGeometry::Transformed(placed.try_clone_for_decode(ctx, operation)?)
} else {
self.basis.try_clone_for_decode(ctx, operation)?
};
Ok(Self {
basis: Box::new(basis),
transform: self.transform,
depth: self.depth,
})
}
pub fn try_new(
basis: Box<SolvedCurveGeometry>,
transform: Transform,
) -> Result<Self, &'static str> {
let Some(depth) = basis
.nesting_depth()
.checked_add(1)
.filter(|depth| *depth <= MAX_GEOMETRY_NESTING)
else {
return Err("PlacedCurve.basis nests past the admitted inline basis depth");
};
Ok(Self {
basis,
transform,
depth,
})
}
#[must_use]
pub const fn basis(&self) -> &SolvedCurveGeometry {
&self.basis
}
#[must_use]
pub const fn transform(&self) -> &Transform {
&self.transform
}
pub const fn set_transform(&mut self, transform: Transform) {
self.transform = transform;
}
}
impl TryFrom<PlacedCurveWire> for PlacedCurve {
type Error = &'static str;
fn try_from(wire: PlacedCurveWire) -> Result<Self, Self::Error> {
Self::try_new(wire.basis, wire.transform)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum CurveGeometry {
Procedural {
construction: ProceduralCurveId,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_curve_geometry_cache"
)]
#[cfg_attr(feature = "schema", schemars(skip))]
cache: Option<SolvedCurveGeometry>,
},
#[serde(untagged)]
Solved(SolvedCurveGeometry),
}
impl CurveGeometry {
pub fn try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
Ok(match self {
Self::Procedural {
construction,
cache,
} => Self::Procedural {
construction: construction.try_clone_for_decode(ctx, operation)?,
cache: cache
.as_ref()
.map(|geometry| geometry.try_clone_for_decode(ctx, operation))
.transpose()?,
},
Self::Solved(geometry) => Self::Solved(geometry.try_clone_for_decode(ctx, operation)?),
})
}
#[must_use]
pub const fn procedural_construction(&self) -> Option<&ProceduralCurveId> {
match self {
Self::Procedural { construction, .. } => Some(construction),
Self::Solved(_) => None,
}
}
#[must_use]
pub const fn solved_cache(&self) -> Option<&SolvedCurveGeometry> {
match self {
Self::Procedural { cache, .. } => cache.as_ref(),
Self::Solved(_) => None,
}
}
#[cfg(test)]
pub(crate) const fn solved_cache_mut(&mut self) -> Option<&mut SolvedCurveGeometry> {
match self {
Self::Procedural { cache, .. } => cache.as_mut(),
Self::Solved(_) => None,
}
}
#[must_use]
pub const fn solved(&self) -> Option<&SolvedCurveGeometry> {
match self {
Self::Procedural { cache, .. } => cache.as_ref(),
Self::Solved(geometry) => Some(geometry),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "Vec<CompositeCurveSegment>")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct CompositeCurveSegments(Vec<CompositeCurveSegment>);
impl TryFrom<Vec<CompositeCurveSegment>> for CompositeCurveSegments {
type Error = &'static str;
fn try_from(segments: Vec<CompositeCurveSegment>) -> Result<Self, Self::Error> {
if segments.is_empty() {
return Err("composite curve segments must not be empty");
}
Ok(Self(segments))
}
}
impl std::ops::Deref for CompositeCurveSegments {
type Target = [CompositeCurveSegment];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for CompositeCurveSegments {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'a> IntoIterator for &'a CompositeCurveSegments {
type Item = &'a CompositeCurveSegment;
type IntoIter = std::slice::Iter<'a, CompositeCurveSegment>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct CompositeCurveSegment {
pub curve: CurveId,
pub same_sense: bool,
pub transition: CompositeCurveTransition,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum CompositeCurveTransition {
Discontinuous,
Continuous,
ContSameGradient,
ContSameGradientSameCurvature,
}
pub fn derive_reference_direction(axis: Vector3) -> Vector3 {
let norm = axis.norm();
let axis = if norm.is_finite() && norm != 0.0 {
Vector3::new(axis.x / norm, axis.y / norm, axis.z / norm)
} else if let Some(unit) = crate::features::FiniteVector3::new(axis)
.and_then(crate::features::FiniteVector3::unit_nonzero)
{
unit
} else {
return Vector3::new(1.0, 0.0, 0.0);
};
let basis = if axis.x.abs() <= axis.y.abs() && axis.x.abs() <= axis.z.abs() {
Vector3::new(1.0, 0.0, 0.0)
} else if axis.y.abs() <= axis.z.abs() {
Vector3::new(0.0, 1.0, 0.0)
} else {
Vector3::new(0.0, 0.0, 1.0)
};
let dot = axis.x * basis.x + axis.y * basis.y + axis.z * basis.z;
let projected = Vector3::new(
basis.x - dot * axis.x,
basis.y - dot * axis.y,
basis.z - dot * axis.z,
);
let projected_norm = projected.norm();
Vector3::new(
projected.x / projected_norm,
projected.y / projected_norm,
projected.z / projected_norm,
)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Curve {
pub id: CurveId,
pub geometry: CurveGeometry,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_carrier_parameter_range"
)]
pub parameter_range: Option<crate::topology::IncreasingParameterInterval>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_source_object"
)]
pub source_object: Option<SourceObjectAssociation>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "[Option<f64>; 4]", into = "[Option<f64>; 4]")]
pub struct RecordBounds([Option<f64>; 4]);
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("record bounds must contain only finite values")]
pub struct RecordBoundsError;
impl RecordBounds {
pub fn try_new(raw: [Option<f64>; 4]) -> Result<Self, RecordBoundsError> {
if raw.iter().flatten().all(|value| value.is_finite()) {
Ok(Self(raw))
} else {
Err(RecordBoundsError)
}
}
#[must_use]
pub const fn from_corners(
u: crate::topology::IncreasingParameterInterval,
v: crate::topology::IncreasingParameterInterval,
) -> Self {
Self([
Some(u.lower()),
Some(v.lower()),
Some(u.upper()),
Some(v.upper()),
])
}
#[must_use]
pub const fn from_finite([first, second, third, fourth]: [FiniteReal; 4]) -> Self {
Self([
Some(first.get()),
Some(second.get()),
Some(third.get()),
Some(fourth.get()),
])
}
#[must_use]
pub const fn get(self) -> [Option<f64>; 4] {
self.0
}
}
impl TryFrom<[Option<f64>; 4]> for RecordBounds {
type Error = RecordBoundsError;
fn try_from(raw: [Option<f64>; 4]) -> Result<Self, Self::Error> {
Self::try_new(raw)
}
}
impl From<RecordBounds> for [Option<f64>; 4] {
fn from(bounds: RecordBounds) -> Self {
bounds.get()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ProceduralSurface {
pub id: ProceduralSurfaceId,
definition: ProceduralSurfaceDefinition,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
record_bounds: Option<RecordBounds>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum SplineSurfaceParameters<R = f64> {
OrderedRanges {
ranges: [[R; 2]; 2],
},
RevisionRanges {
intervals: [[Option<R>; 2]; 2],
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "layout", rename_all = "snake_case", deny_unknown_fields)]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub enum ExactSpline<R = f64> {
Legacy {
ranges: [[R; 2]; 2],
extension: i64,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
Revision {
intervals: [[Option<R>; 2]; 2],
extension: i64,
form: Box<RevisionSurfaceForm<Vec<bool>, R>>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct CompoundComponent<T, R = f64> {
pub parameter: R,
pub component: T,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "CompoundCurveConstructionWire")]
pub struct CompoundCurveConstruction {
parameters: Vec<FiniteReal>,
#[cfg_attr(feature = "schema", schemars(with = "Vec<CompoundComponent<CurveId>>"))]
components: Vec<CompoundComponent<CurveId, FiniteReal>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct CompoundCurveConstructionWire {
parameters: Vec<f64>,
components: Vec<CompoundComponent<CurveId>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
}
impl TryFrom<CompoundCurveConstructionWire> for CompoundCurveConstruction {
type Error = &'static str;
fn try_from(wire: CompoundCurveConstructionWire) -> Result<Self, Self::Error> {
Self::try_new(wire.parameters, wire.components, wire.cache)
}
}
impl CompoundCurveConstruction {
pub fn try_new(
parameters: Vec<f64>,
components: Vec<CompoundComponent<CurveId>>,
cache: Option<LegacyCache>,
) -> Result<Self, &'static str> {
const INVALID: &str = "compound curve parameters must be finite";
if components.is_empty() {
return Err("compound curve components must not be empty");
}
let [parameters] = FiniteReal::lanes([parameters]).ok_or(INVALID)?;
let components = components
.into_iter()
.map(CompoundComponent::admit)
.collect::<Option<Vec<_>>>()
.ok_or(INVALID)?;
Ok(Self {
parameters,
components,
cache,
})
}
#[must_use]
pub fn parameters(&self) -> &[FiniteReal] {
&self.parameters
}
#[must_use]
pub fn components(&self) -> &[CompoundComponent<CurveId, FiniteReal>] {
&self.components
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ProceduralSurfaceDefinition {
Exact(surface_payloads::ExactSurfacePayload),
Compound(surface_payloads::CompoundSurfacePayload),
SubSurface(surface_payloads::SubSurfaceConstruction),
Taper(surface_payloads::TaperSurfaceConstruction),
Loft(surface_payloads::LoftSurfacePayload),
CompoundLoft(surface_payloads::CompoundLoftSurfacePayload),
RevisionCompoundLoft {
construction: Box<RevisionCompoundLoftConstruction>,
},
ScaledCompoundLoft(surface_payloads::ScaledCompoundLoftSurfacePayload),
Skin(surface_payloads::SkinSurfacePayload),
Law(surface_payloads::LawSurfacePayload),
Net(surface_payloads::NetSurfacePayload),
G2Blend(surface_payloads::G2BlendSurfacePayload),
RevisionG2Blend {
construction: Box<RevisionG2BlendConstruction>,
},
VariableBlend(surface_payloads::VariableBlendSurfacePayload),
VertexBlend(surface_payloads::VertexBlendSurfacePayload),
Extrusion(surface_payloads::ExtrusionSurfaceConstruction),
LinearSweep(surface_payloads::LinearSweepSurfaceConstruction),
Revolution(surface_payloads::RevolutionSurfaceConstruction),
AxisRevolution(surface_payloads::AxisRevolutionSurfaceConstruction),
Sum(surface_payloads::SumSurfaceConstruction),
Sweep(surface_payloads::SweepSurfacePayload),
TSpline {
construction: Box<TSplineSurfaceConstruction>,
},
Helix {
construction: Box<HelixSurfaceConstruction>,
},
Deformable(surface_payloads::DeformableSurfacePayload),
Offset(surface_payloads::OffsetSurfaceConstruction),
Subset(surface_payloads::SubsetSurfaceConstruction),
Replica {
source: SurfaceId,
transform: Transform,
},
ParallelOffset(surface_payloads::ParallelOffsetSurfaceConstruction),
DegenerateTorus {
select_outer: bool,
},
CurveBounded {
support: SurfaceId,
boundaries: Vec<CurveId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
boundary_pcurves: Vec<PcurveId>,
implicit_outer: bool,
},
Ruled {
first: CurveId,
second: CurveId,
#[serde(default, skip_serializing_if = "Option::is_none")]
cache: Option<LegacyCache>,
},
Blend(surface_payloads::BlendSurfacePayload),
RollingBallJet(RollingBallJetStations),
Unknown {
#[serde(default, skip_serializing_if = "Option::is_none")]
record: Option<UnknownId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
cache: Option<LegacyCache>,
},
}
#[derive(Deserialize)]
#[serde(
remote = "ProceduralSurfaceDefinition",
tag = "kind",
rename_all = "snake_case",
deny_unknown_fields
)]
enum ProceduralSurfaceDefinitionWire {
Exact(surface_payloads::ExactSurfacePayload),
Compound(surface_payloads::CompoundSurfacePayload),
SubSurface(surface_payloads::SubSurfaceConstruction),
Taper(surface_payloads::TaperSurfaceConstruction),
Loft(surface_payloads::LoftSurfacePayload),
CompoundLoft(surface_payloads::CompoundLoftSurfacePayload),
RevisionCompoundLoft {
construction: Box<RevisionCompoundLoftConstruction>,
},
ScaledCompoundLoft(surface_payloads::ScaledCompoundLoftSurfacePayload),
Skin(surface_payloads::SkinSurfacePayload),
Law(surface_payloads::LawSurfacePayload),
Net(surface_payloads::NetSurfacePayload),
G2Blend(surface_payloads::G2BlendSurfacePayload),
RevisionG2Blend {
construction: Box<RevisionG2BlendConstruction>,
},
VariableBlend(surface_payloads::VariableBlendSurfacePayload),
VertexBlend(surface_payloads::VertexBlendSurfacePayload),
Extrusion(surface_payloads::ExtrusionSurfaceConstruction),
LinearSweep(surface_payloads::LinearSweepSurfaceConstruction),
Revolution(surface_payloads::RevolutionSurfaceConstruction),
AxisRevolution(surface_payloads::AxisRevolutionSurfaceConstruction),
Sum(surface_payloads::SumSurfaceConstruction),
Sweep(surface_payloads::SweepSurfacePayload),
TSpline {
construction: Box<TSplineSurfaceConstruction>,
},
Helix {
construction: Box<HelixSurfaceConstruction>,
},
Deformable(surface_payloads::DeformableSurfacePayload),
Offset(surface_payloads::OffsetSurfaceConstruction),
Subset(surface_payloads::SubsetSurfaceConstruction),
Replica {
source: SurfaceId,
transform: Transform,
},
ParallelOffset(surface_payloads::ParallelOffsetSurfaceConstruction),
DegenerateTorus {
select_outer: bool,
},
CurveBounded {
support: SurfaceId,
boundaries: Vec<CurveId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
boundary_pcurves: Vec<PcurveId>,
implicit_outer: bool,
},
Ruled {
first: CurveId,
second: CurveId,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
Blend(surface_payloads::BlendSurfacePayload),
RollingBallJet(RollingBallJetStations),
Unknown {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_record"
)]
record: Option<UnknownId>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
}
impl<'de> Deserialize<'de> for ProceduralSurfaceDefinition {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
ProceduralSurfaceDefinitionWire::deserialize(deserializer)
}
}
impl ProceduralSurfaceDefinition {
fn revision_cache(
&self,
) -> Option<&RevisionCacheForm<RevisionSurfaceParameterization<FiniteReal>>> {
match self {
Self::Exact(payload) => payload.revision_cache(),
Self::Taper(definition_payload) => {
let revision_form = definition_payload.revision_form();
revision_form.as_ref().map(|form| &form.cache)
}
Self::Extrusion(definition_payload) => {
let revision_form = definition_payload.revision_form();
revision_form.as_ref().map(|form| &form.cache)
}
Self::Revolution(definition_payload) => {
let revision_form = definition_payload.revision_form();
revision_form.as_ref().map(|form| &form.cache)
}
Self::Sum(payload) => payload.revision_form().map(|form| &form.cache),
Self::Offset(payload) => match payload.extension() {
OffsetExtension::Revision { form } => Some(&form.cache),
OffsetExtension::Legacy { .. } => None,
},
Self::Loft(payload) => payload.revision_cache(),
Self::RevisionCompoundLoft { construction } => Some(&construction.cache),
Self::RevisionG2Blend { construction } => Some(&construction.cache),
Self::Sweep(payload) => payload.revision_cache(),
Self::TSpline { construction } => construction.cache.form().map(|form| &form.cache),
Self::Deformable(payload) => payload.revision_cache(),
Self::Blend(payload) => payload.revision_cache(),
Self::Compound(_)
| Self::SubSurface(_)
| Self::CompoundLoft(_)
| Self::ScaledCompoundLoft(_)
| Self::Skin(_)
| Self::Law(_)
| Self::Net(_)
| Self::G2Blend(_)
| Self::VariableBlend(_)
| Self::VertexBlend(_)
| Self::LinearSweep(_)
| Self::AxisRevolution(_)
| Self::Helix { .. }
| Self::Subset(_)
| Self::Replica { .. }
| Self::ParallelOffset(_)
| Self::DegenerateTorus { .. }
| Self::CurveBounded { .. }
| Self::Ruled { .. }
| Self::RollingBallJet(_)
| Self::Unknown { .. } => None,
}
}
fn write_revision_fit_tolerance(&mut self, write: ToleranceWrite) -> RevisionCacheWrite {
match self {
Self::Exact(payload) => payload.write_revision_fit_tolerance(write),
Self::Taper(payload) => payload.write_revision_fit_tolerance(write),
Self::Extrusion(payload) => payload.write_revision_fit_tolerance(write),
Self::Revolution(payload) => payload.write_revision_fit_tolerance(write),
Self::Sum(payload) => payload.write_revision_fit_tolerance(write),
Self::Offset(payload) => payload.write_revision_fit_tolerance(write),
Self::Loft(payload) => payload.write_revision_fit_tolerance(write),
Self::RevisionCompoundLoft { construction } => {
construction.cache.write_fit_tolerance(write)
}
Self::RevisionG2Blend { construction } => construction.cache.write_fit_tolerance(write),
Self::Sweep(payload) => payload.write_revision_fit_tolerance(write),
Self::TSpline { construction } => write_revision_form_tolerance(
construction.cache.form_mut().map(|form| &mut form.cache),
write,
),
Self::Deformable(payload) => payload.write_revision_fit_tolerance(write),
Self::Blend(payload) => payload.write_revision_fit_tolerance(write),
Self::Compound(_)
| Self::SubSurface(_)
| Self::CompoundLoft(_)
| Self::ScaledCompoundLoft(_)
| Self::Skin(_)
| Self::Law(_)
| Self::Net(_)
| Self::G2Blend(_)
| Self::VariableBlend(_)
| Self::VertexBlend(_)
| Self::LinearSweep(_)
| Self::AxisRevolution(_)
| Self::Helix { .. }
| Self::Subset(_)
| Self::Replica { .. }
| Self::ParallelOffset(_)
| Self::DegenerateTorus { .. }
| Self::CurveBounded { .. }
| Self::Ruled { .. }
| Self::RollingBallJet(_)
| Self::Unknown { .. } => RevisionCacheWrite::NoForm,
}
}
#[must_use]
pub fn owns_revision_cache(&self) -> bool {
self.revision_cache().is_some() || matches!(self, Self::VariableBlend(..))
}
}
enum RevisionCacheWrite {
Written,
Parameterized,
NoForm,
InvalidValue(f64),
}
impl RevisionCacheWrite {
const fn into_set_result(self) -> Result<(), CacheContractError> {
match self {
Self::Written => Ok(()),
Self::Parameterized => Err(CacheContractError::Parameterized),
Self::NoForm => Err(CacheContractError::Layout(NO_LEGACY_SLOT)),
Self::InvalidValue(value) => Err(CacheContractError::InvalidValue { value }),
}
}
}
#[derive(Clone, Copy)]
enum ToleranceWrite {
State(FitTolerance),
Raise(FitTolerance),
Scale(crate::scalar::PositiveReal),
}
fn write_revision_form_tolerance<P>(
form: Option<&mut RevisionCacheForm<P>>,
write: ToleranceWrite,
) -> RevisionCacheWrite {
form.map_or(RevisionCacheWrite::NoForm, |form| {
form.write_fit_tolerance(write)
})
}
const fn clear_revision_form_tolerance<P>(
form: Option<&RevisionCacheForm<P>>,
) -> Result<(), CacheContractError> {
match form {
Some(RevisionCacheForm::SolvedCache { .. }) => Err(CacheContractError::MissingSolved),
Some(RevisionCacheForm::Parameterization(_)) | None => Ok(()),
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "f64", into = "f64")]
pub struct FitTolerance(f64);
impl FitTolerance {
pub const ZERO: Self = Self(0.0);
pub fn try_new(value: f64) -> Result<Self, CacheContractError> {
if value.is_finite() && value >= 0.0 {
Ok(Self(value))
} else {
Err(CacheContractError::InvalidValue { value })
}
}
#[must_use]
pub const fn get(self) -> f64 {
self.0
}
#[must_use]
pub fn scaled(self, scale: crate::scalar::PositiveReal) -> Option<Self> {
let value = self.0 * scale.get();
value.is_finite().then_some(Self(value))
}
}
impl TryFrom<f64> for FitTolerance {
type Error = CacheContractError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl From<FitTolerance> for f64 {
fn from(value: FitTolerance) -> Self {
value.get()
}
}
impl From<NonNegativeReal> for FitTolerance {
fn from(value: NonNegativeReal) -> Self {
Self(value.get())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct LegacyCache {
pub fit_tolerance: FitTolerance,
}
impl LegacyCache {
#[must_use]
pub const fn new(fit_tolerance: FitTolerance) -> Self {
Self { fit_tolerance }
}
pub fn try_new(fit_tolerance: f64) -> Result<Self, CacheContractError> {
FitTolerance::try_new(fit_tolerance).map(Self::new)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "layout", rename_all = "snake_case", deny_unknown_fields)]
pub enum CacheContract<F> {
Legacy {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
Revision {
form: F,
},
}
impl<F> CacheContract<F> {
#[must_use]
pub const fn legacy() -> Self {
Self::Legacy { cache: None }
}
#[must_use]
pub fn from_form(form: Option<F>) -> Self {
match form {
Some(form) => Self::Revision { form },
None => Self::legacy(),
}
}
#[must_use]
pub const fn form(&self) -> Option<&F> {
match self {
Self::Revision { form } => Some(form),
Self::Legacy { .. } => None,
}
}
pub const fn form_mut(&mut self) -> Option<&mut F> {
match self {
Self::Revision { form } => Some(form),
Self::Legacy { .. } => None,
}
}
#[must_use]
pub const fn legacy_fit_tolerance(&self) -> Option<FitTolerance> {
match self {
Self::Legacy { cache: Some(cache) } => Some(cache.fit_tolerance),
Self::Legacy { cache: None } | Self::Revision { .. } => None,
}
}
const fn legacy_cache_mut(&mut self) -> Option<&mut Option<LegacyCache>> {
match self {
Self::Legacy { cache } => Some(cache),
Self::Revision { .. } => None,
}
}
}
impl<F> CacheContract<F> {
#[must_use]
pub const fn is_bare_legacy(&self) -> bool {
matches!(self, Self::Legacy { cache: None })
}
}
impl<F> Default for CacheContract<F> {
fn default() -> Self {
Self::legacy()
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum CacheContractError {
#[error("fit_tolerance must be finite and non-negative, got {value}")]
InvalidValue {
value: f64,
},
#[error("a parameterized cache form states no fit tolerance")]
Parameterized,
#[error("a solved cache form states a fit tolerance")]
MissingSolved,
#[error("{0}")]
Layout(&'static str),
}
impl From<CacheContractError> for cadmpeg_core::CodecError {
fn from(error: CacheContractError) -> Self {
Self::Malformed(error.to_string())
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ProceduralGeometryError {
#[error("{0}")]
Payload(&'static str),
#[error(transparent)]
Cache(#[from] CacheContractError),
#[error(transparent)]
Members(#[from] crate::features::BodySelectionError),
}
const NO_LEGACY_SLOT: &str = "this construction states no solved-cache fit tolerance";
const PARAMETERIZED_NO_SOLVED_CACHE: &str =
"a parameterized cache form takes no solved-cache fit tolerance";
const REQUIRED_LEGACY_SLOT: &str = "this construction states a solved-cache fit tolerance";
enum LegacyCacheSlot<'a> {
Optional(&'a mut Option<LegacyCache>),
Required(&'a mut LegacyCache),
}
impl LegacyCacheSlot<'_> {
const fn set(&mut self, cache: Option<LegacyCache>) -> Result<(), CacheContractError> {
match (self, cache) {
(Self::Optional(slot), cache) => {
**slot = cache;
Ok(())
}
(Self::Required(slot), Some(cache)) => {
**slot = cache;
Ok(())
}
(Self::Required(_), None) => Err(CacheContractError::Layout(REQUIRED_LEGACY_SLOT)),
}
}
fn scale_fit_tolerance(
&mut self,
scale: crate::scalar::PositiveReal,
) -> Result<(), CacheContractError> {
let cache = match self {
Self::Optional(slot) => slot.as_mut(),
Self::Required(cache) => Some(&mut **cache),
};
if let Some(cache) = cache {
let value = cache.fit_tolerance;
cache.fit_tolerance =
value
.scaled(scale)
.ok_or_else(|| CacheContractError::InvalidValue {
value: value.get() * scale.get(),
})?;
}
Ok(())
}
}
impl ProceduralSurfaceDefinition {
#[must_use]
pub fn legacy_cache(&self) -> Option<LegacyCache> {
match self {
Self::Exact(payload) => payload.legacy_cache(),
Self::Compound(payload) => payload.legacy_cache(),
Self::Taper(payload) => payload.legacy_cache(),
Self::Loft(payload) => payload.legacy_cache(),
Self::CompoundLoft(payload) => payload.legacy_cache(),
Self::ScaledCompoundLoft(payload) => payload.legacy_cache(),
Self::Skin(payload) => payload.legacy_cache(),
Self::Law(payload) => payload.legacy_cache(),
Self::Net(payload) => payload.legacy_cache(),
Self::G2Blend(payload) => payload.legacy_cache(),
Self::Extrusion(payload) => payload.legacy_cache(),
Self::Revolution(payload) => payload.legacy_cache(),
Self::Sum(payload) => payload.legacy_cache(),
Self::Sweep(payload) => payload.legacy_cache(),
Self::TSpline { construction } => construction.legacy_cache(),
Self::Deformable(payload) => payload.legacy_cache(),
Self::Offset(payload) => payload.legacy_cache(),
Self::Subset(payload) => payload.legacy_cache(),
Self::Blend(payload) => payload.legacy_cache(),
Self::Ruled { cache, .. } | Self::Unknown { cache, .. } => *cache,
Self::SubSurface(_)
| Self::RevisionCompoundLoft { .. }
| Self::RevisionG2Blend { .. }
| Self::VariableBlend(_)
| Self::VertexBlend(_)
| Self::LinearSweep(_)
| Self::AxisRevolution(_)
| Self::Helix { .. }
| Self::Replica { .. }
| Self::ParallelOffset(_)
| Self::DegenerateTorus { .. }
| Self::CurveBounded { .. }
| Self::RollingBallJet(_) => None,
}
}
fn legacy_cache_slot_mut(&mut self) -> Option<LegacyCacheSlot<'_>> {
match self {
Self::Exact(payload) => payload.legacy_cache_slot_mut(),
Self::Compound(payload) => Some(payload.legacy_cache_slot_mut()),
Self::Taper(payload) => payload.legacy_cache_slot_mut(),
Self::Loft(payload) => payload.legacy_cache_slot_mut(),
Self::CompoundLoft(payload) => Some(payload.legacy_cache_slot_mut()),
Self::ScaledCompoundLoft(payload) => Some(payload.legacy_cache_slot_mut()),
Self::Skin(payload) => Some(payload.legacy_cache_slot_mut()),
Self::Law(payload) => payload.legacy_cache_slot_mut(),
Self::Net(payload) => Some(payload.legacy_cache_slot_mut()),
Self::G2Blend(payload) => Some(payload.legacy_cache_slot_mut()),
Self::Extrusion(payload) => payload.legacy_cache_slot_mut(),
Self::Revolution(payload) => payload.legacy_cache_slot_mut(),
Self::Sum(payload) => payload.legacy_cache_slot_mut(),
Self::Sweep(payload) => payload.legacy_cache_slot_mut(),
Self::TSpline { construction } => construction.legacy_cache_slot_mut(),
Self::Deformable(payload) => payload.legacy_cache_slot_mut(),
Self::Offset(payload) => payload.legacy_cache_slot_mut(),
Self::Subset(payload) => Some(payload.legacy_cache_slot_mut()),
Self::Blend(payload) => payload.legacy_cache_slot_mut(),
Self::Ruled { cache, .. } | Self::Unknown { cache, .. } => {
Some(LegacyCacheSlot::Optional(cache))
}
Self::SubSurface(_)
| Self::RevisionCompoundLoft { .. }
| Self::RevisionG2Blend { .. }
| Self::VariableBlend(_)
| Self::VertexBlend(_)
| Self::LinearSweep(_)
| Self::AxisRevolution(_)
| Self::Helix { .. }
| Self::Replica { .. }
| Self::ParallelOffset(_)
| Self::DegenerateTorus { .. }
| Self::CurveBounded { .. }
| Self::RollingBallJet(_) => None,
}
}
pub fn set_legacy_cache(
&mut self,
cache: Option<LegacyCache>,
) -> Result<(), CacheContractError> {
match self.legacy_cache_slot_mut() {
Some(mut slot) => slot.set(cache),
None => cache.map_or(Ok(()), |_| Err(CacheContractError::Layout(NO_LEGACY_SLOT))),
}
}
#[must_use]
pub fn cache_fit_tolerance(&self) -> Option<FitTolerance> {
if let Self::VariableBlend(payload) = self {
return payload.construction().cache.fit_tolerance();
}
match self.revision_cache() {
Some(RevisionCacheForm::SolvedCache { fit_tolerance }) => Some(*fit_tolerance),
Some(RevisionCacheForm::Parameterization(_)) => None,
None => self.legacy_cache().map(|cache| cache.fit_tolerance),
}
}
pub fn set_cache_fit_tolerance(
&mut self,
value: Option<FitTolerance>,
) -> Result<(), CacheContractError> {
if let Self::VariableBlend(payload) = self {
return payload.write_cache_fit_tolerance(value);
}
if self.owns_revision_cache() {
return match value {
Some(value) => self
.write_revision_fit_tolerance(ToleranceWrite::State(value))
.into_set_result(),
None => clear_revision_form_tolerance(self.revision_cache()),
};
}
self.set_legacy_cache(value.map(LegacyCache::new))
}
fn scale_cache_fit_tolerance(
&mut self,
scale: crate::scalar::PositiveReal,
) -> Result<(), CacheContractError> {
if let Self::VariableBlend(payload) = self {
return payload.scale_cache_fit_tolerance(scale);
}
match self.write_revision_fit_tolerance(ToleranceWrite::Scale(scale)) {
RevisionCacheWrite::Written | RevisionCacheWrite::Parameterized => Ok(()),
RevisionCacheWrite::NoForm => self
.legacy_cache_slot_mut()
.map_or(Ok(()), |mut slot| slot.scale_fit_tolerance(scale)),
RevisionCacheWrite::InvalidValue(value) => {
Err(CacheContractError::InvalidValue { value })
}
}
}
}
impl ProceduralCurveDefinition {
#[must_use]
pub fn legacy_cache(&self) -> Option<LegacyCache> {
match self {
Self::Compound(construction) => construction.legacy_cache(),
Self::Helix(construction) => construction.legacy_cache(),
Self::Subset(payload) => payload.legacy_cache(),
Self::VectorOffset(payload) => payload.legacy_cache(),
Self::TwoSidedOffset(payload) => payload.legacy_cache(),
Self::Spring(payload) => payload.legacy_cache(),
Self::SurfaceOffset(payload) => payload.legacy_cache(),
Self::Exact { cache }
| Self::Law { cache, .. }
| Self::Intersection { cache, .. }
| Self::TolerantIntersection { cache, .. }
| Self::Unknown { cache, .. } => *cache,
Self::ThreeSurfaceIntersection(_)
| Self::SurfaceCurve { .. }
| Self::Silhouette(_)
| Self::Deformable(_)
| Self::Projection(_)
| Self::Offset(_)
| Self::SpatialOffset(_)
| Self::Replica { .. }
| Self::BlendSpine { .. } => None,
}
}
fn legacy_cache_slot_mut(&mut self) -> Option<&mut Option<LegacyCache>> {
match self {
Self::Compound(construction) => Some(construction.legacy_cache_slot_mut()),
Self::Helix(construction) => Some(construction.legacy_cache_slot_mut()),
Self::Subset(payload) => Some(payload.legacy_cache_slot_mut()),
Self::VectorOffset(payload) => Some(payload.legacy_cache_slot_mut()),
Self::TwoSidedOffset(payload) => Some(payload.legacy_cache_slot_mut()),
Self::Spring(payload) => payload.legacy_cache_slot_mut(),
Self::SurfaceOffset(payload) => payload.legacy_cache_slot_mut(),
Self::Exact { cache }
| Self::Law { cache, .. }
| Self::Intersection { cache, .. }
| Self::TolerantIntersection { cache, .. }
| Self::Unknown { cache, .. } => Some(cache),
Self::ThreeSurfaceIntersection(_)
| Self::SurfaceCurve { .. }
| Self::Silhouette(_)
| Self::Deformable(_)
| Self::Projection(_)
| Self::Offset(_)
| Self::SpatialOffset(_)
| Self::Replica { .. }
| Self::BlendSpine { .. } => None,
}
}
pub fn set_legacy_cache(&mut self, cache: LegacyCache) -> Result<(), CacheContractError> {
match self.legacy_cache_slot_mut() {
Some(slot) => {
*slot = Some(cache);
Ok(())
}
None => Err(CacheContractError::Layout(NO_LEGACY_SLOT)),
}
}
pub fn clear_legacy_cache(&mut self) {
if let Some(slot) = self.legacy_cache_slot_mut() {
*slot = None;
}
}
#[must_use]
pub fn cache_fit_tolerance(&self) -> Option<FitTolerance> {
match self.revision_cache() {
Some(RevisionCacheForm::SolvedCache { fit_tolerance }) => Some(*fit_tolerance),
Some(RevisionCacheForm::Parameterization(_)) => None,
None => self.legacy_cache().map(|cache| cache.fit_tolerance),
}
}
pub fn set_cache_fit_tolerance(
&mut self,
value: Option<FitTolerance>,
) -> Result<(), CacheContractError> {
if self.owns_revision_cache() {
return match value {
Some(value) => self
.write_revision_fit_tolerance(ToleranceWrite::State(value))
.into_set_result(),
None => clear_revision_form_tolerance(self.revision_cache()),
};
}
match value {
Some(value) => self.set_legacy_cache(LegacyCache::new(value)),
None => {
self.clear_legacy_cache();
Ok(())
}
}
}
fn scale_cache_fit_tolerance(
&mut self,
scale: crate::scalar::PositiveReal,
) -> Result<(), CacheContractError> {
match self.write_revision_fit_tolerance(ToleranceWrite::Scale(scale)) {
RevisionCacheWrite::Written | RevisionCacheWrite::Parameterized => Ok(()),
RevisionCacheWrite::NoForm => {
if let Some(Some(cache)) = self.legacy_cache_slot_mut() {
let value = cache.fit_tolerance;
cache.fit_tolerance =
value
.scaled(scale)
.ok_or_else(|| CacheContractError::InvalidValue {
value: value.get() * scale.get(),
})?;
}
Ok(())
}
RevisionCacheWrite::InvalidValue(value) => {
Err(CacheContractError::InvalidValue { value })
}
}
}
pub fn require_cache_fit_tolerance(
&mut self,
value: FitTolerance,
) -> Result<(), CacheContractError> {
match self.write_revision_fit_tolerance(ToleranceWrite::Raise(value)) {
RevisionCacheWrite::Written => Ok(()),
RevisionCacheWrite::Parameterized => {
Err(CacheContractError::Layout(PARAMETERIZED_NO_SOLVED_CACHE))
}
RevisionCacheWrite::NoForm => match self.legacy_cache_slot_mut() {
Some(slot @ None) => {
*slot = Some(LegacyCache::new(value));
Ok(())
}
Some(Some(cache)) => {
if value.get() > cache.fit_tolerance.get() {
cache.fit_tolerance = value;
}
Ok(())
}
None => Err(CacheContractError::Layout(NO_LEGACY_SLOT)),
},
RevisionCacheWrite::InvalidValue(value) => {
Err(CacheContractError::InvalidValue { value })
}
}
}
}
fn set_variable_blend_cache(
cache: &mut VariableBlendCache<FiniteReal>,
value: Option<FitTolerance>,
) -> Result<(), CacheContractError> {
match (cache, value) {
(VariableBlendCache::Parameterization { .. }, Some(_)) => {
Err(CacheContractError::Parameterized)
}
(VariableBlendCache::Parameterization { .. } | VariableBlendCache::Stale {}, None) => {
Ok(())
}
(VariableBlendCache::Current { fit_tolerance, .. }, Some(value)) => {
*fit_tolerance = value;
Ok(())
}
(VariableBlendCache::Current { .. }, None) => Err(CacheContractError::MissingSolved),
(VariableBlendCache::Stale {}, Some(_)) => Err(CacheContractError::Layout(
"a stale variable-blend cache states no fit tolerance",
)),
}
}
impl ProceduralSurface {
pub fn new(
id: ProceduralSurfaceId,
definition: ProceduralSurfaceDefinition,
record_bounds: Option<RecordBounds>,
) -> Self {
Self {
id,
definition,
record_bounds,
}
}
#[must_use]
pub const fn record_bounds(&self) -> Option<RecordBounds> {
self.record_bounds
}
#[must_use]
pub fn definition(&self) -> &ProceduralSurfaceDefinition {
&self.definition
}
pub fn edit_definition<R>(
&mut self,
edit: impl FnOnce(&mut ProceduralSurfaceDefinition) -> R,
) -> R {
edit(&mut self.definition)
}
#[must_use]
pub fn cache_fit_tolerance(&self) -> Option<FitTolerance> {
self.definition.cache_fit_tolerance()
}
pub fn set_cache_fit_tolerance(
&mut self,
value: Option<f64>,
) -> Result<(), CacheContractError> {
let value = value.map(FitTolerance::try_new).transpose()?;
self.definition.set_cache_fit_tolerance(value)
}
pub fn scale_cache_fit_tolerance(
&mut self,
scale: crate::scalar::PositiveReal,
) -> Result<(), CacheContractError> {
self.definition.scale_cache_fit_tolerance(scale)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum DeformableSurfaceData<R = f64, V = Vector3, P = Point3> {
Full {
leading_vectors: [V; 4],
leading_parameter: R,
leading_flags: [bool; 3],
selector: i64,
surface: SurfaceId,
native_id: i64,
flag: bool,
first_parameter: R,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
version_value: Option<i64>,
second_parameter: R,
curve: CurveId,
frames: Box<[DeformableVectorFrame<R, V>; 2]>,
trailing_value: i64,
},
SurfaceCurve {
surface: SurfaceId,
native_id: i64,
flag: bool,
first_parameter: R,
selector: i64,
second_parameter: R,
curve: CurveId,
vectors: [V; 4],
frame_parameter: R,
flags: [bool; 3],
parameter_triples: Vec<[R; 3]>,
},
Plain {
frame: Box<DeformableSurfaceFrame<R, V, P>>,
parameter_triples: Vec<[R; 3]>,
},
Guided {
frame: Box<DeformableSurfaceFrame<R, V, P>>,
selector: i64,
guide_parameter: R,
},
Minimal {
vectors: [V; 4],
selector: i64,
},
RevisionMode3 {
leading_vectors: [V; 4],
leading_parameter: R,
leading_flags: [bool; 3],
trailing_point: P,
trailing_vectors: [V; 2],
frame_parameter: R,
frame_flags: [bool; 2],
parameters: [R; 3],
trailing_flags: [bool; 5],
trailing_parameter: R,
trailing_value: i64,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct DeformableVectorFrame<R = f64, V = Vector3> {
pub vectors: [V; 4],
pub parameter: R,
pub flags: [bool; 3],
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct DeformableSurfaceFrame<R = f64, V = Vector3, P = Point3> {
pub leading_vectors: [V; 4],
pub leading_parameter: R,
pub leading_flags: [bool; 3],
pub secondary_vectors: [V; 3],
pub secondary_parameter: R,
pub secondary_flags: [bool; 2],
pub point: P,
pub trailing_flags: [bool; 5],
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>, P: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub struct DeformableSurfaceConstruction<R = f64, V = Vector3, P = Point3> {
pub support: SurfaceId,
pub data: DeformableSurfaceData<R, V, P>,
#[serde(default, skip_serializing_if = "CacheContract::is_bare_legacy")]
pub cache: CacheContract<RevisionSurfaceForm<Vec<bool>, R>>,
pub discontinuities: [Vec<R>; 6],
pub discontinuity_flag: bool,
}
const EPS_HELIX_SURFACE_RADIUS_RELATIVE: f64 = 1.0e-9;
const EPS_HELIX_CURVE_RADIUS: f64 = 1.0e-9;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "HelixPathConstructionWire")]
pub struct HelixPathConstruction {
angle_range: FiniteVector<2>,
center: FinitePoint3,
major: FiniteVector3,
minor: FiniteVector3,
pitch: FiniteVector3,
apex_factor: FiniteReal,
axis: FiniteVector3,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct HelixPathConstructionWire {
angle_range: [f64; 2],
center: Point3,
major: Vector3,
minor: Vector3,
pitch: Vector3,
apex_factor: f64,
axis: Vector3,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HelixFrame<Axis = Vector3> {
pub center: Point3,
pub major: Vector3,
pub minor: Vector3,
pub pitch: Vector3,
pub axis: Axis,
}
impl HelixPathConstruction {
pub fn try_new(
angle_range: [f64; 2],
frame: HelixFrame,
apex_factor: f64,
) -> Result<Self, &'static str> {
Self::try_from_axis(angle_range, frame, apex_factor, FiniteVector3::new)
}
pub fn try_with_unit_axis(
angle_range: [f64; 2],
frame: HelixFrame<crate::units::UnitVector3>,
apex_factor: f64,
) -> Result<Self, &'static str> {
Self::try_from_axis(angle_range, frame, apex_factor, |axis| Some(axis.into()))
}
fn try_from_axis<Axis>(
angle_range: [f64; 2],
frame: HelixFrame<Axis>,
apex_factor: f64,
admit_axis: impl FnOnce(Axis) -> Option<FiniteVector3>,
) -> Result<Self, &'static str> {
let HelixFrame {
center,
major,
minor,
pitch,
axis,
} = frame;
let major_length = major.norm();
let minor_length = minor.norm();
if !(major_length.is_finite()
&& minor_length.is_finite()
&& major_length > 0.0
&& minor_length > 0.0
&& (major_length - minor_length).abs()
<= EPS_HELIX_SURFACE_RADIUS_RELATIVE * major_length.max(minor_length))
{
return Err("helix surface path major and minor must define a circular path");
}
let angle_range = FiniteVector::new(angle_range)
.ok_or("HelixPathConstruction.angle_range must be finite")?;
let center =
FinitePoint3::new(center).ok_or("HelixPathConstruction.center must be finite")?;
let major =
FiniteVector3::new(major).ok_or("HelixPathConstruction.major must be finite")?;
let minor =
FiniteVector3::new(minor).ok_or("HelixPathConstruction.minor must be finite")?;
let pitch =
FiniteVector3::new(pitch).ok_or("HelixPathConstruction.pitch must be finite")?;
let apex_factor = FiniteReal::new(apex_factor)
.ok_or("HelixPathConstruction.apex_factor must be finite")?;
let axis = admit_axis(axis).ok_or("HelixPathConstruction.axis must be finite")?;
Ok(Self {
angle_range,
center,
major,
minor,
pitch,
apex_factor,
axis,
})
}
#[must_use]
pub const fn angle_range(&self) -> &FiniteVector<2> {
&self.angle_range
}
#[must_use]
pub const fn center(&self) -> &FinitePoint3 {
&self.center
}
#[must_use]
pub const fn major(&self) -> &FiniteVector3 {
&self.major
}
#[must_use]
pub const fn minor(&self) -> &FiniteVector3 {
&self.minor
}
#[must_use]
pub const fn pitch(&self) -> &FiniteVector3 {
&self.pitch
}
#[must_use]
pub const fn apex_factor(&self) -> FiniteReal {
self.apex_factor
}
#[must_use]
pub const fn axis(&self) -> &FiniteVector3 {
&self.axis
}
}
impl TryFrom<HelixPathConstructionWire> for HelixPathConstruction {
type Error = &'static str;
fn try_from(wire: HelixPathConstructionWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.angle_range,
HelixFrame {
center: wire.center,
major: wire.major,
minor: wire.minor,
pitch: wire.pitch,
axis: wire.axis,
},
wire.apex_factor,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "HelixCurveConstructionWire")]
pub struct HelixCurveConstruction {
angle_range: FiniteVector<2>,
center: FinitePoint3,
major: FiniteVector3,
minor: FiniteVector3,
pitch: FiniteVector3,
apex_factor: FiniteReal,
axis: FiniteVector3,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct HelixCurveConstructionWire {
angle_range: [f64; 2],
center: Point3,
major: Vector3,
minor: Vector3,
pitch: Vector3,
apex_factor: f64,
axis: Vector3,
#[serde(default, deserialize_with = "deserialize_cache")]
cache: Option<LegacyCache>,
}
impl HelixCurveConstruction {
pub fn try_new(
angle_range: [f64; 2],
frame: HelixFrame,
apex_factor: f64,
cache: Option<LegacyCache>,
) -> Result<Self, &'static str> {
let HelixFrame {
center,
major,
minor,
pitch,
axis,
} = frame;
if angle_range[0] > angle_range[1] {
return Err("helix curve angle_range must be ordered");
}
let major_radius = major.norm();
let minor_radius = minor.norm();
if major_radius <= f64::EPSILON
|| minor_radius <= f64::EPSILON
|| axis.norm() <= f64::EPSILON
{
return Err("helix curve major, minor, and axis must be non-degenerate");
}
if major.is_finite()
&& minor.is_finite()
&& (!major_radius.is_finite()
|| !minor_radius.is_finite()
|| (major_radius - minor_radius).abs() > EPS_HELIX_CURVE_RADIUS)
{
return Err("helix curve major and minor radii must agree");
}
let angle_range = FiniteVector::new(angle_range)
.ok_or("HelixCurveConstruction.angle_range must be finite")?;
let center =
FinitePoint3::new(center).ok_or("HelixCurveConstruction.center must be finite")?;
let major =
FiniteVector3::new(major).ok_or("HelixCurveConstruction.major must be finite")?;
let minor =
FiniteVector3::new(minor).ok_or("HelixCurveConstruction.minor must be finite")?;
let pitch =
FiniteVector3::new(pitch).ok_or("HelixCurveConstruction.pitch must be finite")?;
let apex_factor = FiniteReal::new(apex_factor)
.ok_or("HelixCurveConstruction.apex_factor must be finite")?;
let axis = FiniteVector3::new(axis).ok_or("HelixCurveConstruction.axis must be finite")?;
Ok(Self {
angle_range,
center,
major,
minor,
pitch,
apex_factor,
axis,
cache,
})
}
#[must_use]
pub const fn angle_range(&self) -> &FiniteVector<2> {
&self.angle_range
}
#[must_use]
pub const fn center(&self) -> &FinitePoint3 {
&self.center
}
#[must_use]
pub const fn major(&self) -> &FiniteVector3 {
&self.major
}
#[must_use]
pub const fn minor(&self) -> &FiniteVector3 {
&self.minor
}
#[must_use]
pub const fn pitch(&self) -> &FiniteVector3 {
&self.pitch
}
#[must_use]
pub const fn apex_factor(&self) -> FiniteReal {
self.apex_factor
}
#[must_use]
pub const fn axis(&self) -> &FiniteVector3 {
&self.axis
}
}
impl TryFrom<HelixCurveConstructionWire> for HelixCurveConstruction {
type Error = &'static str;
fn try_from(wire: HelixCurveConstructionWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.angle_range,
HelixFrame {
center: wire.center,
major: wire.major,
minor: wire.minor,
pitch: wire.pitch,
axis: wire.axis,
},
wire.apex_factor,
wire.cache,
)
}
}
impl HelixCurveConstruction {
pub fn try_reverse_parameterization(&mut self) -> Result<(), &'static str> {
let [start, end] = self.angle_range.finite_components();
let turns = end.turns_from(start).get();
let radial_end = self.apex_factor.get().mul_add(turns, 1.0);
if !turns.is_finite() || !radial_end.is_finite() || radial_end == 0.0 {
return Err("helix curve reversal has no finite nonzero starting radius");
}
let scale = |vector: Vector3| {
Vector3::new(
vector.x * radial_end,
vector.y * radial_end,
vector.z * radial_end,
)
};
let candidate = Self::try_new(
self.angle_range.reversed_negated().get(),
HelixFrame {
center: self.center.get().translated(self.pitch.get(), turns),
major: scale(self.major.get()),
minor: scale(self.minor.negated().get()),
pitch: self.pitch.negated().get(),
axis: self.axis.get(),
},
-self.apex_factor.get() / radial_end,
self.cache,
)?;
*self = candidate;
Ok(())
}
pub fn try_scale_lengths(&mut self, scale: f64) -> Result<(), &'static str> {
let vector =
|value: Vector3| Vector3::new(value.x * scale, value.y * scale, value.z * scale);
let center = Point3::new(
self.center.x * scale,
self.center.y * scale,
self.center.z * scale,
);
let major = vector(self.major.get());
let minor = vector(self.minor.get());
let pitch = vector(self.pitch.get());
let major_radius = major.norm();
let minor_radius = minor.norm();
if major_radius <= f64::EPSILON || minor_radius <= f64::EPSILON {
return Err("helix curve major, minor, and axis must be non-degenerate");
}
if major.is_finite()
&& minor.is_finite()
&& (!major_radius.is_finite()
|| !minor_radius.is_finite()
|| (major_radius - minor_radius).abs() > EPS_HELIX_CURVE_RADIUS)
{
return Err("helix curve major and minor radii must agree");
}
let center =
FinitePoint3::new(center).ok_or("HelixCurveConstruction.center must be finite")?;
let major =
FiniteVector3::new(major).ok_or("HelixCurveConstruction.major must be finite")?;
let minor =
FiniteVector3::new(minor).ok_or("HelixCurveConstruction.minor must be finite")?;
let pitch =
FiniteVector3::new(pitch).ok_or("HelixCurveConstruction.pitch must be finite")?;
self.center = center;
self.major = major;
self.minor = minor;
self.pitch = pitch;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "HelixCircleProfileWire")]
pub struct HelixCircleProfile {
length: FiniteReal,
radius: FiniteReal,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct HelixCircleProfileWire {
length: f64,
radius: f64,
}
impl HelixCircleProfile {
pub fn try_new(length: f64, radius: f64) -> Result<Self, &'static str> {
if radius == 0.0 {
return Err("helix circle profile radius must be finite and nonzero");
}
let length = FiniteReal::new(length).ok_or("helix circle profile length must be finite")?;
let radius = FiniteReal::new(radius).ok_or("helix circle profile radius must be finite")?;
Ok(Self { length, radius })
}
#[must_use]
pub const fn length(&self) -> FiniteReal {
self.length
}
#[must_use]
pub const fn radius(&self) -> FiniteReal {
self.radius
}
}
impl TryFrom<HelixCircleProfileWire> for HelixCircleProfile {
type Error = &'static str;
fn try_from(wire: HelixCircleProfileWire) -> Result<Self, Self::Error> {
Self::try_new(wire.length, wire.radius)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "HelixLineProfileWire")]
pub struct HelixLineProfile {
direction: FiniteVector3,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct HelixLineProfileWire {
direction: Vector3,
}
impl HelixLineProfile {
pub fn try_new(direction: Vector3) -> Result<Self, &'static str> {
const INVALID: &str = "helix line profile direction must be finite and non-degenerate";
let direction = FiniteVector3::new(direction).ok_or(INVALID)?;
if direction.x == 0.0 && direction.y == 0.0 && direction.z == 0.0 {
return Err(INVALID);
}
Ok(Self { direction })
}
#[must_use]
pub const fn direction(&self) -> FiniteVector3 {
self.direction
}
}
impl TryFrom<HelixLineProfileWire> for HelixLineProfile {
type Error = &'static str;
fn try_from(wire: HelixLineProfileWire) -> Result<Self, Self::Error> {
Self::try_new(wire.direction)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum HelixSurfaceProfile {
Circle(HelixCircleProfile),
Line(HelixLineProfile),
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "HelixSurfaceConstructionWire")]
pub struct HelixSurfaceConstruction {
angle_range: FiniteVector<2>,
dimension_range: FiniteVector<2>,
path: HelixPathConstruction,
profile: HelixSurfaceProfile,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct HelixSurfaceConstructionWire {
angle_range: [f64; 2],
dimension_range: [f64; 2],
path: HelixPathConstruction,
profile: HelixSurfaceProfile,
}
impl HelixSurfaceConstruction {
pub fn try_new(
angle_range: [f64; 2],
dimension_range: [f64; 2],
path: HelixPathConstruction,
profile: HelixSurfaceProfile,
) -> Result<Self, &'static str> {
let angle_range =
FiniteVector::new(angle_range).ok_or("helix surface angle_range must be finite")?;
let dimension_range = FiniteVector::new(dimension_range)
.ok_or("helix surface dimension_range must be finite")?;
Ok(Self {
angle_range,
dimension_range,
path,
profile,
})
}
#[must_use]
pub const fn angle_range(&self) -> &FiniteVector<2> {
&self.angle_range
}
#[must_use]
pub const fn dimension_range(&self) -> &FiniteVector<2> {
&self.dimension_range
}
#[must_use]
pub const fn path(&self) -> &HelixPathConstruction {
&self.path
}
#[must_use]
pub const fn profile(&self) -> &HelixSurfaceProfile {
&self.profile
}
}
impl TryFrom<HelixSurfaceConstructionWire> for HelixSurfaceConstruction {
type Error = &'static str;
fn try_from(wire: HelixSurfaceConstructionWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.angle_range,
wire.dimension_range,
wire.path,
wire.profile,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "i64", into = "i64")]
pub struct SubtypeTableIndex(i64);
impl SubtypeTableIndex {
pub fn try_new(index: i64) -> Result<Self, &'static str> {
if index >= 0 {
Ok(Self(index))
} else {
Err("subtype table index must be non-negative")
}
}
#[must_use]
pub const fn get(self) -> i64 {
self.0
}
}
impl TryFrom<i64> for SubtypeTableIndex {
type Error = &'static str;
fn try_from(index: i64) -> Result<Self, Self::Error> {
Self::try_new(index)
}
}
impl From<SubtypeTableIndex> for i64 {
fn from(index: SubtypeTableIndex) -> Self {
index.get()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
tag = "kind",
rename = "inline",
try_from = "InlineTSplineSubtransformWire"
)]
pub struct InlineTSplineSubtransform {
pub program: cadmpeg_core::text::NonBlankString,
pub separator: Option<bool>,
pub values: cadmpeg_core::text::NonBlankString,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
enum InlineTSplineSubtransformWire {
Inline {
program: String,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
separator: Option<bool>,
values: String,
},
}
impl TryFrom<InlineTSplineSubtransformWire> for InlineTSplineSubtransform {
type Error = &'static str;
fn try_from(wire: InlineTSplineSubtransformWire) -> Result<Self, Self::Error> {
let InlineTSplineSubtransformWire::Inline {
program,
separator,
values,
} = wire;
Self::try_new(program, separator, values)
}
}
impl InlineTSplineSubtransform {
pub fn try_new(
program: impl Into<String>,
separator: Option<bool>,
values: impl Into<String>,
) -> Result<Self, &'static str> {
Ok(Self {
program: cadmpeg_core::text::NonBlankString::new(program)
.ok_or("T-spline program must not be empty")?,
separator,
values: cadmpeg_core::text::NonBlankString::new(values)
.ok_or("T-spline values must not be empty")?,
})
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "TSplineSubtransformWire")]
pub enum TSplineSubtransform {
Inline(InlineTSplineSubtransform),
Resolved {
index: SubtypeTableIndex,
transform: Box<InlineTSplineSubtransform>,
},
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
enum TSplineSubtransformWire {
Inline {
program: String,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
separator: Option<bool>,
values: String,
},
Reference {
index: SubtypeTableIndex,
resolved: Box<InlineTSplineSubtransform>,
},
}
impl TryFrom<TSplineSubtransformWire> for TSplineSubtransform {
type Error = &'static str;
fn try_from(wire: TSplineSubtransformWire) -> Result<Self, Self::Error> {
match wire {
TSplineSubtransformWire::Inline {
program,
separator,
values,
} => InlineTSplineSubtransform::try_new(program, separator, values).map(Self::Inline),
TSplineSubtransformWire::Reference { index, resolved } => Ok(Self::Resolved {
index,
transform: resolved,
}),
}
}
}
impl Serialize for TSplineSubtransform {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
#[derive(Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum Wire<'a> {
Reference {
index: SubtypeTableIndex,
resolved: &'a InlineTSplineSubtransform,
},
}
match self {
Self::Inline(inline) => inline.serialize(serializer),
Self::Resolved { index, transform } => Wire::Reference {
index: *index,
resolved: transform,
}
.serialize(serializer),
}
}
}
impl TSplineSubtransform {
#[must_use]
pub fn inline(&self) -> &InlineTSplineSubtransform {
match self {
Self::Inline(inline) => inline,
Self::Resolved { transform, .. } => transform,
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
try_from = "TSplineSurfaceConstructionWire",
into = "TSplineSurfaceConstructionWire"
)]
pub struct TSplineSurfaceConstruction {
parameter_ranges: [crate::topology::ParameterInterval; 2],
type_code: i64,
subtransform: TSplineSubtransform,
trailing_value: i64,
discontinuities: [Vec<FiniteReal>; 6],
discontinuity_flag: bool,
#[serde(default, skip_serializing_if = "CacheContract::is_bare_legacy")]
cache: CacheContract<RevisionSurfaceForm<Vec<bool>, FiniteReal>>,
}
impl TSplineSurfaceConstruction {
pub fn try_new(
parameter_ranges: [[f64; 2]; 2],
type_code: i64,
subtransform: TSplineSubtransform,
trailing_value: i64,
discontinuities: [Vec<f64>; 6],
discontinuity_flag: bool,
cache: CacheContract<RevisionSurfaceForm>,
) -> Result<Self, ProceduralGeometryError> {
let parameter_ranges = [
crate::topology::ParameterInterval::new(parameter_ranges[0])
.map_err(ProceduralGeometryError::Payload)?,
crate::topology::ParameterInterval::new(parameter_ranges[1])
.map_err(ProceduralGeometryError::Payload)?,
];
let discontinuities = FiniteReal::lanes(discontinuities).ok_or(
ProceduralGeometryError::Payload("T-spline discontinuities must be finite"),
)?;
let cache = cache.admit_form(RevisionSurfaceForm::admit).ok_or(
ProceduralGeometryError::Payload("T-spline revision cache form is invalid"),
)?;
Ok(Self {
parameter_ranges,
type_code,
subtransform,
trailing_value,
discontinuities,
discontinuity_flag,
cache,
})
}
pub fn parameter_ranges(&self) -> [crate::topology::ParameterInterval; 2] {
self.parameter_ranges
}
pub const fn type_code(&self) -> i64 {
self.type_code
}
pub const fn subtransform(&self) -> &TSplineSubtransform {
&self.subtransform
}
pub const fn trailing_value(&self) -> i64 {
self.trailing_value
}
pub const fn discontinuities(&self) -> &[Vec<FiniteReal>; 6] {
&self.discontinuities
}
pub const fn discontinuity_flag(&self) -> bool {
self.discontinuity_flag
}
pub const fn revision_form(&self) -> Option<&RevisionSurfaceForm<Vec<bool>, FiniteReal>> {
self.cache.form()
}
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename = "TSplineSurfaceConstruction"))]
#[serde(deny_unknown_fields)]
struct TSplineSurfaceConstructionWire {
parameter_ranges: [[f64; 2]; 2],
type_code: i64,
subtransform: TSplineSubtransform,
trailing_value: i64,
discontinuities: [Vec<f64>; 6],
discontinuity_flag: bool,
#[serde(default, skip_serializing_if = "CacheContract::is_bare_legacy")]
cache: CacheContract<RevisionSurfaceForm>,
}
impl TryFrom<TSplineSurfaceConstructionWire> for TSplineSurfaceConstruction {
type Error = ProceduralGeometryError;
fn try_from(wire: TSplineSurfaceConstructionWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.parameter_ranges,
wire.type_code,
wire.subtransform,
wire.trailing_value,
wire.discontinuities,
wire.discontinuity_flag,
wire.cache,
)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct BlendSupport {
pub surface: SurfaceId,
#[serde(default)]
pub reversed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RollingBallJetStation<R = f64, V = Vector3, P = Point3> {
pub knot: R,
pub multiplicity: u32,
pub site: RollingBallJetSite<R, V, P>,
}
const EPS_ROLLING_BALL_RADIUS: f64 = 1.0e-9;
const ROLLING_BALL_KNOTS: &str = "rolling-ball jet knots must be finite and strictly increasing";
fn admit_rolling_ball_radii(
site: &RollingBallJetSite<FiniteReal, FiniteVector3, FinitePoint3>,
) -> Result<(), &'static str> {
let first_radius = site.first_limit.distance(site.center.get());
let second_radius = site.second_limit.distance(site.center.get());
if !first_radius.is_finite()
|| first_radius <= 0.0
|| !second_radius.is_finite()
|| (first_radius - second_radius).abs()
> EPS_ROLLING_BALL_RADIUS * first_radius.max(second_radius).max(1.0)
{
return Err("rolling-ball jet site radii must be finite and agree within tolerance, with a positive first radius");
}
Ok(())
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(try_from = "RollingBallJetReadWire")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct RollingBallJetStations {
degree: u32,
#[cfg_attr(feature = "schema", schemars(with = "Vec<RollingBallJetStation>"))]
stations: Vec<RollingBallJetStation<FiniteReal, FiniteVector3, FinitePoint3>>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RollingBallJetError {
#[error("{0}")]
Invalid(&'static str),
#[error("{0}")]
Admission(String),
}
impl RollingBallJetStations {
pub fn try_new(
degree: u32,
stations: Vec<RollingBallJetStation>,
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
) -> Result<Result<Self, &'static str>, cadmpeg_core::CodecError> {
Self::from_raw(
degree,
stations,
|count, operation| {
ctx.charge_work(cadmpeg_core::decode::u64_from_index(count), operation)
},
|count| ctx.collection_vec(count, "rolling-ball jet stations"),
)
}
fn from_raw<E>(
degree: u32,
stations: Vec<RollingBallJetStation>,
mut work: impl FnMut(usize, &'static str) -> Result<(), E>,
allocate: impl FnOnce(
usize,
) -> Result<
Vec<RollingBallJetStation<FiniteReal, FiniteVector3, FinitePoint3>>,
E,
>,
) -> Result<Result<Self, &'static str>, E> {
if let Err(error) = Self::admit_controls(&mut work, degree, &stations, |row| row.knot)? {
return Ok(Err(error));
}
let mut admitted = allocate(stations.len())?;
for row in stations {
work(1, "rolling-ball jet station controls")?;
let Some(knot) = FiniteReal::new(row.knot) else {
return Ok(Err(ROLLING_BALL_KNOTS));
};
let site = match row.site.admit() {
Ok(site) => site,
Err(error) => return Ok(Err(error)),
};
if let Err(error) = admit_rolling_ball_radii(&site) {
return Ok(Err(error));
}
admitted.push(RollingBallJetStation {
knot,
multiplicity: row.multiplicity,
site,
});
}
Ok(Ok(Self {
degree,
stations: admitted,
}))
}
pub fn from_parts(
degree: u32,
stations: Vec<RollingBallJetStation<FiniteReal, FiniteVector3, FinitePoint3>>,
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
) -> Result<Result<Self, &'static str>, cadmpeg_core::CodecError> {
if let Err(error) = Self::admit_controls(
&mut |count, operation| {
ctx.charge_work(cadmpeg_core::decode::u64_from_index(count), operation)
},
degree,
&stations,
|row| row.knot.get(),
)? {
return Ok(Err(error));
}
for station in &stations {
ctx.charge_work(1, "rolling-ball jet station controls")?;
if let Err(error) = admit_rolling_ball_radii(&station.site) {
return Ok(Err(error));
}
}
Ok(Ok(Self { degree, stations }))
}
fn admit_controls<R, V, P, E>(
work: &mut impl FnMut(usize, &'static str) -> Result<(), E>,
degree: u32,
stations: &[RollingBallJetStation<R, V, P>],
knot: impl Fn(&RollingBallJetStation<R, V, P>) -> f64,
) -> Result<Result<(), &'static str>, E> {
let Some(maximum) = degree.checked_add(1).filter(|_| degree != 0) else {
return Ok(Err(
"rolling-ball jet degree must be positive with a representable end multiplicity",
));
};
if stations.len() < 2 {
return Ok(Err(
"rolling-ball jet stations must contain at least two rows",
));
}
if stations[0].multiplicity != maximum
|| stations[stations.len() - 1].multiplicity != maximum
{
return Ok(Err(
"rolling-ball jet multiplicities must be in 1..=degree+1 with clamped ends",
));
}
for station in stations {
work(1, "rolling-ball jet multiplicities")?;
if station.multiplicity == 0 || station.multiplicity > maximum {
return Ok(Err(
"rolling-ball jet multiplicities must be in 1..=degree+1 with clamped ends",
));
}
}
let mut previous = None;
for station in stations {
work(1, "rolling-ball jet knots")?;
let value = knot(station);
if !value.is_finite() || previous.is_some_and(|last| value <= last) {
return Ok(Err(ROLLING_BALL_KNOTS));
}
previous = Some(value);
}
Ok(Ok(()))
}
#[must_use]
pub const fn degree(&self) -> u32 {
self.degree
}
#[must_use]
pub fn stations(&self) -> &[RollingBallJetStation<FiniteReal, FiniteVector3, FinitePoint3>] {
&self.stations
}
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct RollingBallJetReadWire {
degree: u32,
stations: Vec<RollingBallJetStation>,
}
impl TryFrom<RollingBallJetReadWire> for RollingBallJetStations {
type Error = RollingBallJetError;
fn try_from(wire: RollingBallJetReadWire) -> Result<Self, Self::Error> {
Self::from_raw(
wire.degree,
wire.stations,
|_, _| Ok::<(), RollingBallJetError>(()),
|count| {
let mut values = Vec::new();
values
.try_reserve_exact(count)
.map_err(|error| RollingBallJetError::Admission(error.to_string()))?;
Ok(values)
},
)?
.map_err(RollingBallJetError::Invalid)
}
}
#[derive(Serialize)]
struct RollingBallJetWriteWire<'a> {
degree: u32,
stations: &'a [RollingBallJetStation<FiniteReal, FiniteVector3, FinitePoint3>],
}
impl Serialize for RollingBallJetStations {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
RollingBallJetWriteWire {
degree: self.degree,
stations: &self.stations,
}
.serialize(serializer)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RollingBallJetSite<R = f64, V = Vector3, P = Point3> {
pub first_limit: P,
pub second_limit: P,
pub center: P,
pub angle: R,
pub first_derivative: RollingBallJetDerivative<R, V>,
pub second_derivative: RollingBallJetDerivative<R, V>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RollingBallJetDerivative<R = f64, V = Vector3> {
pub first_limit: V,
pub second_limit: V,
pub center: V,
pub angle: R,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum BlendCrossSection {
Circular,
Conic,
Polynomial,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "F: Deserialize<'de> + Default, R: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(bound = "F: JsonSchema + Default, R: JsonSchema + Serialize")
)]
pub struct RevisionSurfaceForm<F: Default = Vec<bool>, R = f64> {
pub revision: PositiveI64,
#[serde(default)]
pub support_bounds: [Option<R>; 4],
#[serde(default)]
pub reference_endpoints: [Option<R>; 2],
#[serde(default)]
pub second_endpoints: [Option<R>; 2],
#[serde(default)]
pub flags: F,
pub cache: RevisionCacheForm<RevisionSurfaceParameterization<R>>,
#[serde(default)]
pub discontinuities: [Vec<R>; 6],
pub tail_flag: bool,
#[serde(default)]
pub trailing_flags: Vec<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum RevisionCacheForm<P = RevisionSurfaceParameterization> {
SolvedCache {
fit_tolerance: FitTolerance,
},
Parameterization(P),
}
impl<P> RevisionCacheForm<P> {
fn write_fit_tolerance(&mut self, write: ToleranceWrite) -> RevisionCacheWrite {
match self {
Self::SolvedCache { fit_tolerance } => {
match write {
ToleranceWrite::State(value) => *fit_tolerance = value,
ToleranceWrite::Raise(value) => {
if value.get() > fit_tolerance.get() {
*fit_tolerance = value;
}
}
ToleranceWrite::Scale(scale) => {
let Some(scaled) = fit_tolerance.scaled(scale) else {
return RevisionCacheWrite::InvalidValue(
fit_tolerance.get() * scale.get(),
);
};
*fit_tolerance = scaled;
}
}
RevisionCacheWrite::Written
}
Self::Parameterization(_) => RevisionCacheWrite::Parameterized,
}
}
#[must_use]
pub const fn parameterization(&self) -> Option<&P> {
match self {
Self::SolvedCache { .. } => None,
Self::Parameterization(parameterization) => Some(parameterization),
}
}
#[must_use]
pub const fn fit_tolerance(&self) -> Option<FitTolerance> {
match self {
Self::SolvedCache { fit_tolerance } => Some(*fit_tolerance),
Self::Parameterization(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub enum VariableBlendCache<R = f64> {
Current {
shape_prefix: NonZeroI64,
fit_tolerance: FitTolerance,
},
Stale {},
Parameterization {
shape_prefix: i64,
parameterization: RevisionSurfaceParameterization<R>,
},
}
impl<R> VariableBlendCache<R> {
fn scale_fit_tolerance(
&mut self,
scale: crate::scalar::PositiveReal,
) -> Result<(), CacheContractError> {
if let Self::Current { fit_tolerance, .. } = self {
let value = *fit_tolerance;
*fit_tolerance =
value
.scaled(scale)
.ok_or_else(|| CacheContractError::InvalidValue {
value: value.get() * scale.get(),
})?;
}
Ok(())
}
#[must_use]
pub const fn shape_prefix(&self) -> i64 {
match self {
Self::Current { shape_prefix, .. } => shape_prefix.get(),
Self::Stale {} => 0,
Self::Parameterization { shape_prefix, .. } => *shape_prefix,
}
}
#[must_use]
pub const fn parameterization(&self) -> Option<&RevisionSurfaceParameterization<R>> {
match self {
Self::Parameterization {
parameterization, ..
} => Some(parameterization),
_ => None,
}
}
#[must_use]
pub const fn fit_tolerance(&self) -> Option<FitTolerance> {
match self {
Self::Current { fit_tolerance, .. } => Some(*fit_tolerance),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>"))]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub struct RevisionSurfaceParameterization<R = f64> {
#[serde(default)]
pub u_interval: [Option<R>; 2],
#[serde(default)]
pub v_interval: [Option<R>; 2],
pub u_closure: i64,
pub v_closure: i64,
pub u_singularity: i64,
pub v_singularity: i64,
}
impl<R> Default for RevisionSurfaceParameterization<R> {
fn default() -> Self {
Self {
u_interval: [None, None],
v_interval: [None, None],
u_closure: 0,
v_closure: 0,
u_singularity: 0,
v_singularity: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum TaperSurfaceKind<R = f64, V = Vector3> {
Standard {},
Orthogonal {
sense: bool,
},
Edge {
draft: V,
},
Shadow {
draft: V,
sine: R,
cosine: R,
},
Ruled {
draft: V,
sine: R,
cosine: R,
factor: R,
},
Swept {
draft: V,
sine: R,
cosine: R,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>"))]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub struct LoftSubdataRow<R = f64> {
pub parameters: [R; 2],
pub columns: Vec<[R; 2]>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_extra"
)]
pub extra: Option<[R; 2]>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "form", rename_all = "snake_case", deny_unknown_fields)]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub enum LoftSubdata<R = f64> {
Type211 {
dimensions: [i64; 2],
row: [R; 2],
},
Table(LoftSubdataTable<R>),
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "schema", schemars(into = "LoftSubdataTableWire<R>"))]
#[serde(
try_from = "LoftSubdataTableWire<R>",
bound(deserialize = "R: Deserialize<'de>")
)]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub struct LoftSubdataTable<R = f64> {
type_code: i64,
rows: Vec<LoftSubdataRow<R>>,
row_count: i64,
column_count: i64,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
struct LoftSubdataTableWire<R = f64> {
type_code: i64,
rows: Vec<LoftSubdataRow<R>>,
}
#[derive(Serialize)]
struct LoftSubdataTableWriteWire<'a, R> {
type_code: i64,
rows: &'a [LoftSubdataRow<R>],
}
impl<R: Serialize> Serialize for LoftSubdataTable<R> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
LoftSubdataTableWriteWire {
type_code: self.type_code,
rows: &self.rows,
}
.serialize(serializer)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("loft subdata rows do not share one column width")]
pub struct RaggedLoftTable;
impl<R> LoftSubdataTable<R> {
pub fn new(type_code: i64, rows: Vec<LoftSubdataRow<R>>) -> Result<Self, RaggedLoftTable> {
let row_count = i64::try_from(rows.len()).map_err(|_| RaggedLoftTable)?;
let column_count = rows.first().map_or(0, |row| row.columns.len());
let column_count_i64 = i64::try_from(column_count).map_err(|_| RaggedLoftTable)?;
if rows.iter().any(|row| row.columns.len() != column_count) {
return Err(RaggedLoftTable);
}
Ok(Self {
type_code,
rows,
row_count,
column_count: column_count_i64,
})
}
}
impl<R> TryFrom<LoftSubdataTableWire<R>> for LoftSubdataTable<R> {
type Error = RaggedLoftTable;
fn try_from(wire: LoftSubdataTableWire<R>) -> Result<Self, Self::Error> {
Self::new(wire.type_code, wire.rows)
}
}
impl LoftSubdata {
#[must_use]
pub fn type_211(dimensions: [i64; 2], row: [f64; 2]) -> Self {
Self::Type211 { dimensions, row }
}
#[must_use]
pub fn table(type_code: i64, rows: Vec<LoftSubdataRow>) -> Option<Self> {
LoftSubdataTable::new(type_code, rows).ok().map(Self::Table)
}
}
impl<R> LoftSubdata<R> {
#[must_use]
pub fn type_code(&self) -> i64 {
match self {
Self::Type211 { .. } => 211,
Self::Table(table) => table.type_code,
}
}
#[must_use]
pub fn row_count(&self) -> i64 {
match self {
Self::Type211 { dimensions, .. } => dimensions[0],
Self::Table(table) => table.row_count,
}
}
#[must_use]
pub fn column_count(&self) -> i64 {
match self {
Self::Type211 { dimensions, .. } => dimensions[1],
Self::Table(table) => table.column_count,
}
}
pub fn visit_rows(&self, mut visit: impl FnMut(&[R; 2], &[[R; 2]], Option<&[R; 2]>)) {
match self {
Self::Type211 { row, .. } => visit(row, &[], None),
Self::Table(table) => {
for row in &table.rows {
visit(&row.parameters, &row.columns, row.extra.as_ref());
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename = "LoftProfileData"))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct ClassicLoftProfileData<R = f64, V = Vector3> {
pub surface: SurfaceId,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_pcurve"
)]
pub pcurve: Option<PcurveGeometry>,
pub first_flag: bool,
pub asm_extension: i64,
pub subdata: LoftSubdata<R>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_direction"
)]
pub direction: Option<V>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub enum LoftMemberForm<R = f64, V = Vector3> {
Support {
type_code: i64,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_surface"
)]
surface: Option<SurfaceId>,
#[serde(default)]
support_bounds: [Option<R>; 4],
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_pcurve"
)]
pcurve: Option<PcurveGeometry>,
first_flag: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_asm_extension"
)]
asm_extension: Option<i64>,
subdata: LoftSubdata<R>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_direction"
)]
direction: Option<V>,
},
PcurvePair {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_pcurve"
)]
pcurve: Option<PcurveGeometry>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_secondary_pcurve"
)]
secondary_pcurve: Option<PcurveGeometry>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_asm_extension"
)]
asm_extension: Option<i64>,
subdata: LoftSubdata<R>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_direction"
)]
direction: Option<V>,
},
}
impl<R, V> LoftMemberForm<R, V> {
#[must_use]
pub fn type_code(&self) -> i64 {
match self {
Self::Support { type_code, .. } => *type_code,
Self::PcurvePair { .. } => 0,
}
}
#[must_use]
pub fn surface(&self) -> Option<&SurfaceId> {
match self {
Self::Support { surface, .. } => surface.as_ref(),
Self::PcurvePair { .. } => None,
}
}
#[must_use]
pub fn subdata(&self) -> &LoftSubdata<R> {
match self {
Self::Support { subdata, .. } | Self::PcurvePair { subdata, .. } => subdata,
}
}
#[must_use]
pub fn direction(&self) -> Option<&V> {
match self {
Self::Support { direction, .. } | Self::PcurvePair { direction, .. } => {
direction.as_ref()
}
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>"))]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub struct LoftPathCurve<R = f64> {
#[serde(rename = "curve")]
pub id: CurveId,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_endpoints"
)]
pub endpoints: Option<[Option<R>; 2]>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct LoftProfileMember<R = f64, V = Vector3> {
pub profile: LoftPathCurve<R>,
pub form: LoftMemberForm<R, V>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>"))]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub struct LoftPath<R = f64> {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_path"
)]
pub path: Option<LoftPathCurve<R>>,
pub auxiliaries: Vec<CurveId>,
pub flag: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct LoftSectionEntry<R = f64, V = Vector3> {
pub parameter: R,
pub profile: Vec<LoftProfileMember<R, V>>,
pub path: LoftPath<R>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>"))]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub struct LoftRevisionForm<R = f64> {
pub revision: PositiveI64,
#[serde(default)]
pub flags: [bool; 4],
#[serde(default)]
pub ints: [i64; 2],
pub cache: RevisionCacheForm<RevisionSurfaceParameterization<R>>,
#[serde(default)]
pub discontinuities: [Vec<R>; 6],
pub tail_flag: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct LoftSection<R = f64, V = Vector3> {
pub entries: Vec<LoftSectionEntry<R, V>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum LoftBridgeToken<R = f64> {
Boolean(bool),
Integer(i64),
Double(R),
Text(String),
Enum(i64),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct G2BlendSide<V = Vector3> {
pub label: String,
pub surface: SurfaceId,
pub curve: CurveId,
pub pcurves: [Option<PcurveGeometry>; 2],
pub direction: V,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>"))]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub enum G2BlendFirstShape<R = f64> {
Full {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_support"
)]
support: Option<G2BlendFullSupport>,
},
None {
coefficients: [R; 9],
tolerance: FitTolerance,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_extension"
)]
extension: Option<LoftBridgeToken<R>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_pcurve"
)]
pcurve: Option<PcurveGeometry>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct G2BlendFullSupport {
pub surface: SurfaceId,
pub tolerance: FitTolerance,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct G2BlendConstruction<R = f64, V = Vector3> {
pub first: G2BlendSide<V>,
pub singularity: i64,
pub first_shape: G2BlendFirstShape<R>,
pub second: G2BlendSide<V>,
pub second_exact_surface: SurfaceId,
pub center_curve: CurveId,
pub center_parameters: [R; 2],
pub center_flag: i64,
pub parameter_ranges: [[R; 2]; 2],
pub trailing_parameters: [R; 4],
pub discontinuities: [Vec<R>; 3],
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RollingBallSupportSurface<S = SurfaceId, R = f64> {
pub surface: S,
pub parameter_ranges: [[Option<R>; 2]; 2],
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RollingBallSupportCurve<C = CurveId, R = f64> {
pub curve: C,
pub parameter_range: [Option<R>; 2],
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(bound(deserialize = "P: Deserialize<'de>"))]
#[serde(deny_unknown_fields)]
pub struct RollingBallSideExtension<P = PcurveGeometry> {
pub value: i64,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
pub pcurve: Option<P>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(bound(
serialize = "S: Serialize, C: Serialize, P: Serialize, R: Serialize, L: Serialize",
deserialize = "S: Deserialize<'de>, C: Deserialize<'de>, P: Deserialize<'de>, R: Deserialize<'de>, L: Deserialize<'de>"
))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(
bound = "S: JsonSchema + Serialize, C: JsonSchema + Serialize, P: JsonSchema + Serialize, R: JsonSchema + Serialize, L: JsonSchema + Serialize"
)
)]
pub struct RollingBallSide<S = SurfaceId, C = CurveId, P = PcurveGeometry, R = f64, L = Point3> {
pub support_kind: VariableBlendSupportKind,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_rolling_ball_side_surface"
)]
pub surface: Option<RollingBallSupportSurface<S, R>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_curve"
)]
pub curve: Option<RollingBallSupportCurve<C, R>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_rolling_ball_side_pcurve"
)]
pub pcurve: Option<P>,
pub location: L,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_rolling_ball_side_secondary_pcurve"
)]
pub secondary_pcurve: Option<P>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_rolling_ball_side_extension"
)]
pub extension: Option<RollingBallSideExtension<P>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RollingBallThirdSide<V = Vector3> {
pub label: String,
pub surface: SurfaceId,
pub curve: CurveId,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_pcurve"
)]
pub pcurve: Option<PcurveGeometry>,
pub direction: V,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_secondary_pcurve"
)]
pub secondary_pcurve: Option<PcurveGeometry>,
pub extension: i64,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_tertiary_pcurve"
)]
pub tertiary_pcurve: Option<PcurveGeometry>,
pub flag: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum RollingBallRadiusSelector<T = f64> {
None {},
Value {
value: T,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>, P: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub struct RollingBallConstruction<R = f64, V = Vector3, P = Point3> {
pub revision: PositiveI64,
pub sides: [RollingBallSide<SurfaceId, CurveId, PcurveGeometry, R, P>; 2],
pub slice: CurveId,
#[serde(default)]
pub slice_range: [Option<R>; 2],
pub offsets: [R; 2],
pub radius_selector: RollingBallRadiusSelector<R>,
pub u_range: [Option<R>; 2],
pub v_range: [Option<R>; 2],
pub shape_prefix: i64,
pub parameters: [R; 2],
pub tail: i64,
pub cache: RevisionCacheForm<RevisionSurfaceParameterization<R>>,
pub discontinuities: [Vec<R>; 6],
#[serde(default)]
pub tail_flag: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_third"
)]
pub third: Option<Box<RollingBallThirdSide<V>>>,
#[serde(default)]
pub tail_extensions: [i64; 3],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum VariableBlendSupportKind {
CosineCurve,
Curve,
PointCurve,
Surface,
ZeroCurve,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum VariableBlendConvexity {
Convex,
Concave,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum VariableBlendRenderMode {
RollingBallEnvelope,
RollingBallSnapshot,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct VariableBlendInterpolationPoint<R = f64, V = Vector3, P = Point3> {
pub parameter: R,
pub radius: R,
pub tangents: [Option<R>; 2],
pub location: P,
pub normal: V,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "i64", into = "i64")]
pub enum EdgeOffsetDiscriminator {
Zero,
One,
}
impl EdgeOffsetDiscriminator {
pub const fn from_code(code: i64) -> Option<Self> {
match code {
0 => Some(Self::Zero),
1 => Some(Self::One),
_ => None,
}
}
pub const fn code(self) -> i64 {
match self {
Self::Zero => 0,
Self::One => 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("edge-offset discriminator must be 0 or 1")]
pub struct EdgeOffsetDiscriminatorCode;
impl TryFrom<i64> for EdgeOffsetDiscriminator {
type Error = EdgeOffsetDiscriminatorCode;
fn try_from(code: i64) -> Result<Self, Self::Error> {
Self::from_code(code).ok_or(EdgeOffsetDiscriminatorCode)
}
}
impl From<EdgeOffsetDiscriminator> for i64 {
fn from(discriminator: EdgeOffsetDiscriminator) -> Self {
discriminator.code()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum VariableBlendTerminal<R = f64> {
Double(R),
Text(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct VariableBlendValue<R = f64, V = Vector3, P = Point3> {
pub modern_flag: bool,
pub calibrated: i64,
pub payload: VariableBlendValuePayload<R, V, P>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum VariableBlendValuePayload<R = f64, V = Vector3, P = Point3> {
TwoEnds {
discriminator: i64,
parameters: [R; 2],
radii: [R; 2],
},
FixedWidth {
discriminator: i64,
parameters: [R; 2],
width: R,
},
EdgeOffset {
discriminator: EdgeOffsetDiscriminator,
scalars: [R; 2],
lengths: [R; 1],
},
Functional {
discriminator: i64,
parameter: R,
radius: R,
function: PcurveGeometry,
terminal: VariableBlendTerminal<R>,
},
Constant {
discriminator: i64,
parameters: [R; 2],
radius: R,
variable_chamfer: i64,
chamfer_type: i64,
nested: Box<VariableBlendValue<R, V, P>>,
},
Interpolated {
discriminator: i64,
parameter: R,
radius: R,
function: PcurveGeometry,
enum_count: i64,
#[serde(default)]
enum_tagged: bool,
points: Vec<VariableBlendInterpolationPoint<R, V, P>>,
},
}
impl<R, V, P> VariableBlendValuePayload<R, V, P> {
pub const fn discriminator(&self) -> i64 {
match self {
Self::EdgeOffset { discriminator, .. } => discriminator.code(),
Self::TwoEnds { discriminator, .. }
| Self::FixedWidth { discriminator, .. }
| Self::Functional { discriminator, .. }
| Self::Constant { discriminator, .. }
| Self::Interpolated { discriminator, .. } => *discriminator,
}
}
pub const fn native_name(&self) -> &'static str {
match self {
Self::TwoEnds { .. } => "two_ends",
Self::FixedWidth { .. } => "fixed_width",
Self::EdgeOffset { .. } => "edge_offset",
Self::Functional { .. } => "functional",
Self::Constant { .. } => "const",
Self::Interpolated { .. } => "interp",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum VariableBlendRadii<R = f64, V = Vector3, P = Point3> {
Single {
value: VariableBlendValue<R, V, P>,
},
Two {
first: VariableBlendValue<R, V, P>,
second: VariableBlendValue<R, V, P>,
},
}
impl<R, V, P> VariableBlendRadii<R, V, P> {
#[must_use]
pub const fn first(&self) -> &VariableBlendValue<R, V, P> {
match self {
Self::Single { value } | Self::Two { first: value, .. } => value,
}
}
#[must_use]
pub const fn second(&self) -> Option<&VariableBlendValue<R, V, P>> {
match self {
Self::Single { .. } => None,
Self::Two { second, .. } => Some(second),
}
}
#[must_use]
pub const fn is_single(&self) -> bool {
matches!(self, Self::Single { .. })
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>, P: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub enum VariableBlendCrossSection<R = f64, V = Vector3, P = Point3> {
Circular {},
Thumbweights {
parameters: [R; 2],
},
RoundedChamfer {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_radius"
)]
radius: Option<Box<VariableBlendValue<R, V, P>>>,
},
G2Round {
parameters: [R; 2],
},
UnclassifiedBare {
selector: VariableBlendBareCrossSection,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[repr(i64)]
#[serde(deny_unknown_fields)]
pub enum VariableBlendBareCrossSection {
Selector2 = 2,
Selector4 = 4,
Selector5 = 5,
Selector6 = 6,
}
impl VariableBlendBareCrossSection {
pub const fn native_selector(self) -> i64 {
match self {
Self::Selector2 => 2,
Self::Selector4 => 4,
Self::Selector5 => 5,
Self::Selector6 => 6,
}
}
}
impl TryFrom<i64> for VariableBlendBareCrossSection {
type Error = ();
fn try_from(value: i64) -> Result<Self, Self::Error> {
match value {
2 => Ok(Self::Selector2),
4 => Ok(Self::Selector4),
5 => Ok(Self::Selector5),
6 => Ok(Self::Selector6),
_ => Err(()),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum VariableBlendSurfaceSubtype {
#[default]
VariableBlend,
SurfaceSurface,
CurveCurve,
CurveSurface,
SurfaceCurveFree,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>, P: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub struct VariableBlendConstruction<R = f64, V = Vector3, P = Point3> {
#[serde(default)]
pub subtype: VariableBlendSurfaceSubtype,
pub revision: PositiveI64,
pub sides: [RollingBallSide<SurfaceId, CurveId, PcurveGeometry, R, P>; 2],
pub slice: CurveId,
#[serde(default)]
pub slice_range: [Option<R>; 2],
pub offsets: [R; 2],
pub radii: VariableBlendRadii<R, V, P>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cross_section"
)]
pub cross_section: Option<VariableBlendCrossSection<R, V, P>>,
pub u_range: [R; 2],
#[serde(
rename = "v_lower",
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_v_lower"
)]
pub v_lower: Option<R>,
pub shape_parameter: R,
pub shape_length: R,
pub shape_tail: i64,
pub cache: VariableBlendCache<R>,
pub discontinuities: [Vec<R>; 6],
pub tail_flag: bool,
pub tail_extensions: [i64; 3],
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_secondary_curve"
)]
pub secondary_curve: Option<RollingBallSupportCurve<CurveId, R>>,
pub convexity: VariableBlendConvexity,
pub render_mode: VariableBlendRenderMode,
pub post_range: [Option<R>; 2],
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_post_curve"
)]
pub post_curve: Option<CurveId>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_post_pcurve"
)]
pub post_pcurve: Option<PcurveGeometry>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "RevisionG2BlendConstructionWire")]
pub struct RevisionG2BlendConstruction {
revision: PositiveI64,
leading_parameters: [FiniteReal; 2],
#[cfg_attr(feature = "schema", schemars(with = "Box<[RollingBallSide; 2]>"))]
sides: [RollingBallSide<SurfaceId, CurveId, PcurveGeometry, FiniteReal, FinitePoint3>; 2],
center: CurveId,
#[serde(default)]
center_range: [Option<FiniteReal>; 2],
radii: [FiniteReal; 2],
radius_selector: RollingBallRadiusSelector<PositiveI64>,
u_range: [Option<FiniteReal>; 2],
v_range: [Option<FiniteReal>; 2],
shape_prefix: i64,
shape_parameter: FiniteReal,
shape_length: FiniteReal,
shape_tail: i64,
#[cfg_attr(feature = "schema", schemars(with = "RevisionCacheForm"))]
cache: RevisionCacheForm<RevisionSurfaceParameterization<FiniteReal>>,
#[serde(default)]
discontinuities: [Vec<FiniteReal>; 6],
tail_flag: bool,
tail_extensions: [i64; 3],
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename = "RevisionG2BlendConstruction"))]
#[serde(deny_unknown_fields)]
pub struct RevisionG2BlendConstructionWire {
pub revision: PositiveI64,
pub leading_parameters: [f64; 2],
pub sides: Box<[RollingBallSide; 2]>,
pub center: CurveId,
#[serde(default)]
pub center_range: [Option<f64>; 2],
pub radii: [f64; 2],
pub radius_selector: RollingBallRadiusSelector<PositiveI64>,
pub u_range: [Option<f64>; 2],
pub v_range: [Option<f64>; 2],
pub shape_prefix: i64,
pub shape_parameter: f64,
pub shape_length: f64,
pub shape_tail: i64,
pub cache: RevisionCacheForm,
#[serde(default)]
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub tail_extensions: [i64; 3],
}
impl RevisionG2BlendConstruction {
pub fn admit(wire: RevisionG2BlendConstructionWire) -> Result<Self, ProceduralGeometryError> {
let invalid = || {
ProceduralGeometryError::Payload("revision g2 blend construction payload is invalid")
};
let [first, second] = *wire.sides;
let (
Some(leading_parameters),
Some(center_range),
Some(radii),
Some(u_range),
Some(v_range),
Some([shape_parameter, shape_length]),
Some(discontinuities),
Some(first),
Some(second),
Some(cache),
) = (
FiniteReal::array(wire.leading_parameters),
FiniteReal::optional(wire.center_range),
FiniteReal::array(wire.radii),
FiniteReal::optional(wire.u_range),
FiniteReal::optional(wire.v_range),
FiniteReal::array([wire.shape_parameter, wire.shape_length]),
FiniteReal::lanes(wire.discontinuities),
first.admit(),
second.admit(),
wire.cache.admit(),
)
else {
return Err(invalid());
};
Ok(Self {
revision: wire.revision,
leading_parameters,
sides: [first, second],
center: wire.center,
center_range,
radii,
radius_selector: wire.radius_selector,
u_range,
v_range,
shape_prefix: wire.shape_prefix,
shape_parameter,
shape_length,
shape_tail: wire.shape_tail,
cache,
discontinuities,
tail_flag: wire.tail_flag,
tail_extensions: wire.tail_extensions,
})
}
#[must_use]
pub const fn revision(&self) -> PositiveI64 {
self.revision
}
#[must_use]
pub const fn leading_parameters(&self) -> [FiniteReal; 2] {
self.leading_parameters
}
#[must_use]
pub const fn sides(
&self,
) -> &[RollingBallSide<SurfaceId, CurveId, PcurveGeometry, FiniteReal, FinitePoint3>; 2] {
&self.sides
}
#[must_use]
pub const fn center(&self) -> &CurveId {
&self.center
}
#[must_use]
pub const fn center_range(&self) -> [Option<FiniteReal>; 2] {
self.center_range
}
#[must_use]
pub const fn radii(&self) -> [FiniteReal; 2] {
self.radii
}
#[must_use]
pub const fn radius_selector(&self) -> &RollingBallRadiusSelector<PositiveI64> {
&self.radius_selector
}
#[must_use]
pub const fn u_range(&self) -> [Option<FiniteReal>; 2] {
self.u_range
}
#[must_use]
pub const fn v_range(&self) -> [Option<FiniteReal>; 2] {
self.v_range
}
#[must_use]
pub const fn shape_prefix(&self) -> i64 {
self.shape_prefix
}
#[must_use]
pub const fn shape_parameter(&self) -> FiniteReal {
self.shape_parameter
}
#[must_use]
pub const fn shape_length(&self) -> FiniteReal {
self.shape_length
}
#[must_use]
pub const fn shape_tail(&self) -> i64 {
self.shape_tail
}
#[must_use]
pub const fn cache(&self) -> &RevisionCacheForm<RevisionSurfaceParameterization<FiniteReal>> {
&self.cache
}
#[must_use]
pub const fn discontinuities(&self) -> &[Vec<FiniteReal>; 6] {
&self.discontinuities
}
#[must_use]
pub const fn tail_flag(&self) -> bool {
self.tail_flag
}
#[must_use]
pub const fn tail_extensions(&self) -> [i64; 3] {
self.tail_extensions
}
}
impl TryFrom<RevisionG2BlendConstructionWire> for RevisionG2BlendConstruction {
type Error = ProceduralGeometryError;
fn try_from(wire: RevisionG2BlendConstructionWire) -> Result<Self, Self::Error> {
Self::admit(wire)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "RevisionCompoundLoftConstructionWire")]
pub struct RevisionCompoundLoftConstruction {
revision: PositiveI64,
#[cfg_attr(feature = "schema", schemars(with = "RevisionCacheForm"))]
cache: RevisionCacheForm<RevisionSurfaceParameterization<FiniteReal>>,
#[serde(default)]
discontinuities: [Vec<FiniteReal>; 6],
tail_flag: bool,
#[cfg_attr(feature = "schema", schemars(with = "Vec<LoftProfileMember>"))]
base_profile: Vec<LoftProfileMember<FiniteReal, FiniteVector3>>,
#[cfg_attr(feature = "schema", schemars(with = "LoftPath"))]
base_path: LoftPath<FiniteReal>,
#[cfg_attr(feature = "schema", schemars(with = "Vec<LoftSectionEntry>"))]
entries: Vec<LoftSectionEntry<FiniteReal, FiniteVector3>>,
flags: [bool; 2],
kind_flags: [bool; 2],
#[cfg_attr(feature = "schema", schemars(with = "CompoundLoftDirection"))]
direction: CompoundLoftDirection<FiniteVector3>,
tail: RevisionCompoundLoftTail<CurveId, FiniteReal>,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(
feature = "schema",
schemars(rename = "RevisionCompoundLoftConstruction")
)]
#[serde(deny_unknown_fields)]
pub struct RevisionCompoundLoftConstructionWire {
pub revision: PositiveI64,
pub cache: RevisionCacheForm,
#[serde(default)]
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub base_profile: Vec<LoftProfileMember>,
pub base_path: LoftPath,
pub entries: Vec<LoftSectionEntry>,
pub flags: [bool; 2],
pub kind_flags: [bool; 2],
pub direction: CompoundLoftDirection,
pub tail: RevisionCompoundLoftTail<CurveId>,
}
impl RevisionCompoundLoftConstruction {
pub fn admit(
wire: RevisionCompoundLoftConstructionWire,
) -> Result<Self, ProceduralGeometryError> {
let invalid = || {
ProceduralGeometryError::Payload(
"revision compound loft construction payload is invalid",
)
};
let (
Some(discontinuities),
Some(tail),
Some(cache),
Some(base_profile),
Some(base_path),
Some(entries),
Some(direction),
) = (
FiniteReal::lanes(wire.discontinuities),
wire.tail.admit(),
wire.cache.admit(),
wire.base_profile
.into_iter()
.map(LoftProfileMember::admit)
.collect::<Option<Vec<_>>>(),
wire.base_path.admit(),
wire.entries
.into_iter()
.map(LoftSectionEntry::admit)
.collect::<Option<Vec<_>>>(),
wire.direction.admit(),
)
else {
return Err(invalid());
};
Ok(Self {
revision: wire.revision,
cache,
discontinuities,
tail_flag: wire.tail_flag,
base_profile,
base_path,
entries,
flags: wire.flags,
kind_flags: wire.kind_flags,
direction,
tail,
})
}
#[must_use]
pub const fn revision(&self) -> PositiveI64 {
self.revision
}
#[must_use]
pub const fn cache(&self) -> &RevisionCacheForm<RevisionSurfaceParameterization<FiniteReal>> {
&self.cache
}
#[must_use]
pub const fn discontinuities(&self) -> &[Vec<FiniteReal>; 6] {
&self.discontinuities
}
#[must_use]
pub const fn tail_flag(&self) -> bool {
self.tail_flag
}
#[must_use]
pub fn base_profile(&self) -> &[LoftProfileMember<FiniteReal, FiniteVector3>] {
&self.base_profile
}
#[must_use]
pub const fn base_path(&self) -> &LoftPath<FiniteReal> {
&self.base_path
}
#[must_use]
pub fn entries(&self) -> &[LoftSectionEntry<FiniteReal, FiniteVector3>] {
&self.entries
}
#[must_use]
pub const fn flags(&self) -> [bool; 2] {
self.flags
}
#[must_use]
pub const fn kind_flags(&self) -> [bool; 2] {
self.kind_flags
}
#[must_use]
pub const fn direction(&self) -> &CompoundLoftDirection<FiniteVector3> {
&self.direction
}
#[must_use]
pub const fn tail(&self) -> &RevisionCompoundLoftTail<CurveId, FiniteReal> {
&self.tail
}
}
impl TryFrom<RevisionCompoundLoftConstructionWire> for RevisionCompoundLoftConstruction {
type Error = ProceduralGeometryError;
fn try_from(wire: RevisionCompoundLoftConstructionWire) -> Result<Self, Self::Error> {
Self::admit(wire)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
tag = "kind",
rename_all = "snake_case",
deny_unknown_fields,
bound(
serialize = "T: Serialize, S: Serialize",
deserialize = "T: Deserialize<'de>, S: Deserialize<'de>"
)
)]
pub enum RevisionCompoundLoftTail<T, S = f64> {
Unbounded {},
LowerBound {
lower: S,
},
UpperBound {
upper: S,
},
Curve {
interval: [S; 2],
curve: T,
},
}
impl<T> RevisionCompoundLoftTail<T> {
fn admit(self) -> Option<RevisionCompoundLoftTail<T, FiniteReal>> {
Some(match self {
Self::Unbounded {} => RevisionCompoundLoftTail::Unbounded {},
Self::LowerBound { lower } => RevisionCompoundLoftTail::LowerBound {
lower: FiniteReal::new(lower)?,
},
Self::UpperBound { upper } => RevisionCompoundLoftTail::UpperBound {
upper: FiniteReal::new(upper)?,
},
Self::Curve { interval, curve } => RevisionCompoundLoftTail::Curve {
interval: FiniteReal::array(interval)?,
curve,
},
})
}
}
impl<T, S: Copy> RevisionCompoundLoftTail<T, S> {
#[must_use]
pub const fn interval(&self) -> [Option<S>; 2] {
match self {
Self::Unbounded {} => [None, None],
Self::LowerBound { lower } => [Some(*lower), None],
Self::UpperBound { upper } => [None, Some(*upper)],
Self::Curve { interval, .. } => [Some(interval[0]), Some(interval[1])],
}
}
#[must_use]
pub const fn curve(&self) -> Option<&T> {
match self {
Self::Curve { curve, .. } => Some(curve),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub struct VertexBlendBoundary<R = f64, V = Vector3, P = Point3> {
pub boundary_type: bool,
pub magic: V,
pub u_smoothing: bool,
pub v_smoothing: bool,
pub fullness: R,
pub geometry: VertexBlendBoundaryGeometry<R, V, P>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "form", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum VertexBlendTwists<P = Point3> {
None {},
One {
twist: P,
},
Two {
twists: [P; 2],
},
}
impl<P> VertexBlendTwists<P> {
#[must_use]
pub const fn form(&self) -> i64 {
match self {
Self::None {} => 0,
Self::One { .. } => 1,
Self::Two { .. } => 3,
}
}
#[must_use]
pub fn entries(&self) -> &[P] {
match self {
Self::None {} => &[],
Self::One { twist } => std::slice::from_ref(twist),
Self::Two { twists } => twists,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>, P: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub enum VertexBlendBoundaryGeometry<R = f64, V = Vector3, P = Point3> {
Circle {
curve: CurveId,
#[serde(default)]
curve_endpoints: [Option<R>; 2],
twists: VertexBlendTwists<P>,
parameters: [R; 2],
sense: bool,
},
Degenerate {
location: P,
normals: [V; 2],
},
Pcurve {
surface: SurfaceId,
#[serde(default)]
support_bounds: [Option<R>; 4],
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_pcurve"
)]
pcurve: Option<PcurveGeometry>,
sense: bool,
fit_tolerance: FitTolerance,
},
Plane {
normal: V,
parameters: [R; 2],
curve: CurveId,
#[serde(default)]
curve_endpoints: [Option<R>; 2],
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub struct VertexBlendConstruction<R = f64, V = Vector3, P = Point3> {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_revision"
)]
pub revision: Option<PositiveI64>,
pub boundaries: Vec<VertexBlendBoundary<R, V, P>>,
pub grid_size: i64,
pub fit_tolerance: FitTolerance,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct CompoundLoftScaleMember<R = f64, V = Vector3> {
pub type_code: i64,
pub curve: CurveId,
pub data: ClassicLoftProfileData<R, V>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct CompoundLoftScale<R = f64, V = Vector3> {
pub members: Vec<CompoundLoftScaleMember<R, V>>,
pub path: CurveId,
pub auxiliaries: Vec<CurveId>,
pub tail: [i64; 2],
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum CompoundLoftDirection<V = Vector3> {
Vector {
value: V,
},
Curve {
curve: CurveId,
selector: NonZeroI64,
},
}
impl<V> CompoundLoftDirection<V> {
#[must_use]
pub const fn selector(&self) -> i64 {
match self {
Self::Vector { .. } => 0,
Self::Curve { selector, .. } => selector.get(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub enum CompoundLoftTail<R = f64, V = Vector3> {
Six {
flags: [bool; 2],
scale: Box<CompoundLoftScale<R, V>>,
selector: i64,
direction: V,
parameter_range: [R; 2],
curve: CurveId,
},
Seven {
first_flag: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_first_scale"
)]
first_scale: Option<Box<CompoundLoftScale<R, V>>>,
second_flag: bool,
second_scale: Box<CompoundLoftScale<R, V>>,
selector: i64,
direction: V,
trailing_flags: [bool; 2],
},
Zero {
flags: [bool; 2],
direction: CompoundLoftDirection<V>,
trailing_flags: [bool; 2],
},
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(
try_from = "Vec<CompoundLoftScale<R, V>>",
bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>")
)]
pub struct CompoundLoftScales<const CAPACITY: usize, R = f64, V = Vector3>(
Vec<CompoundLoftScale<R, V>>,
);
impl<const CAPACITY: usize, R: Serialize, V: Serialize> Serialize
for CompoundLoftScales<CAPACITY, R, V>
{
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
#[cfg(feature = "schema")]
impl<const CAPACITY: usize, R, V> JsonSchema for CompoundLoftScales<CAPACITY, R, V>
where
CompoundLoftScale<R, V>: JsonSchema,
{
fn schema_name() -> std::borrow::Cow<'static, str> {
format!("CompoundLoftScales_{CAPACITY}").into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let mut schema = Vec::<CompoundLoftScale<R, V>>::json_schema(generator);
schema.insert("maxItems".into(), CAPACITY.into());
schema
}
}
impl<const CAPACITY: usize, R, V> CompoundLoftScales<CAPACITY, R, V> {
pub fn try_new(scales: Vec<CompoundLoftScale<R, V>>) -> Result<Self, &'static str> {
if scales.len() > CAPACITY {
return Err("compound loft scales exceed slot capacity");
}
Ok(Self(scales))
}
pub fn try_from_slots(
slots: impl IntoIterator<Item = Option<CompoundLoftScale<R, V>>>,
) -> Result<Self, &'static str> {
let mut scales = Vec::new();
let mut absent = false;
for (index, slot) in slots.into_iter().enumerate() {
if index >= CAPACITY {
return Err("compound loft scales exceed slot capacity");
}
match slot {
Some(scale) if !absent => scales.push(scale),
Some(_) => return Err("compound loft scales must form a leading prefix"),
None => absent = true,
}
}
Ok(Self(scales))
}
#[must_use]
pub fn as_slice(&self) -> &[CompoundLoftScale<R, V>] {
&self.0
}
}
impl<const CAPACITY: usize, R, V> TryFrom<Vec<CompoundLoftScale<R, V>>>
for CompoundLoftScales<CAPACITY, R, V>
{
type Error = &'static str;
fn try_from(scales: Vec<CompoundLoftScale<R, V>>) -> Result<Self, Self::Error> {
Self::try_new(scales)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct CompoundLoftConstruction<R = f64, V = Vector3> {
pub scales: CompoundLoftScales<5, R, V>,
pub flags: [bool; 2],
pub tail: CompoundLoftTail<R, V>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum ScaledCompoundLoftShape<R = f64> {
Full {},
None {
parameter_ranges: [[R; 2]; 2],
parameters: [Vec<R>; 2],
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub enum ScaledCompoundLoftBranch<R = f64, V = Vector3> {
ExtendedVector {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_first_scale"
)]
first_scale: Option<Box<CompoundLoftScale<R, V>>>,
second_scale: Box<CompoundLoftScale<R, V>>,
selector: i64,
direction: V,
},
ExtendedCurve {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_scale"
)]
scale: Option<Box<CompoundLoftScale<R, V>>>,
flag: bool,
singularity: i64,
curve: CurveId,
},
Direct {
flag: bool,
direction: CompoundLoftDirection<V>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct ScaledCompoundLoftConstruction<R = f64, V = Vector3> {
pub singularity: i64,
pub shape: ScaledCompoundLoftShape<R>,
pub discontinuities: [Vec<R>; 6],
pub discontinuity_flag: bool,
pub scales: CompoundLoftScales<3, R, V>,
pub flags: [bool; 2],
pub selector: i64,
pub branch: ScaledCompoundLoftBranch<R, V>,
pub trailing_flags: [bool; 2],
pub tail_kind: i64,
pub tail_directions: [V; 2],
pub tail_singularity: i64,
pub tail_curve: CurveId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub enum LawFormula<R = f64, V = Vector3, P = Point3> {
Null {},
Named {
name: cadmpeg_core::text::NonBlankString,
variables: Vec<LawExpression<R, V, P>>,
},
}
impl<R, V, P> LawFormula<R, V, P> {
#[must_use]
pub fn variables(&self) -> &[LawExpression<R, V, P>] {
match self {
Self::Null {} => &[],
Self::Named { variables, .. } => variables,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "LawFormula")]
pub struct FiniteLawFormula(
#[cfg_attr(feature = "schema", schemars(with = "LawFormula"))]
LawFormula<FiniteReal, FiniteVector3, FinitePoint3>,
);
impl TryFrom<LawFormula> for FiniteLawFormula {
type Error = &'static str;
fn try_from(formula: LawFormula) -> Result<Self, Self::Error> {
Self::try_new(formula)
}
}
impl FiniteLawFormula {
pub fn try_new(formula: LawFormula) -> Result<Self, &'static str> {
formula.admit().map(Self).ok_or(LAW_FORMULA_NOT_FINITE)
}
#[must_use]
pub const fn formula(&self) -> &LawFormula<FiniteReal, FiniteVector3, FinitePoint3> {
&self.0
}
}
const LAW_FORMULA_NOT_FINITE: &str = "law formula constants must be finite";
const LAW_EXPRESSION_DEPTH_LIMIT: usize = 64;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>, P: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub struct LawSurfaceConstruction<R = f64, V = Vector3, P = Point3> {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_parameter_ranges"
)]
pub parameter_ranges: Option<[[R; 2]; 2]>,
pub primary: LawFormula<R, V, P>,
pub additional: Vec<LawFormula<R, V, P>>,
pub tail: LawSurfaceTail<R>,
pub discontinuities: [Vec<R>; 6],
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum LawSurfaceTail<R = f64> {
Full {
cache: LegacyCache,
},
Summary {
parameters: [Vec<R>; 2],
fit_tolerance: FitTolerance,
closures: [i64; 2],
singularities: [i64; 2],
},
None {
parameter_ranges: [[R; 2]; 2],
closures: [i64; 2],
singularities: [i64; 2],
},
Historical {},
Optimal {},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub enum LawExpression<R = f64, V = Vector3, P = Point3> {
Null {},
Text {
value: cadmpeg_core::text::NonBlankString,
},
Integer {
value: i64,
},
Double {
value: R,
},
Point {
value: P,
},
Vector {
value: V,
},
Transform {
scalars: [R; 13],
enums: [i64; 3],
},
TransformVec {
vectors: [V; 4],
scale: R,
flags: [bool; 3],
},
Edge {
#[serde(flatten)]
curve: LoftPathCurve<R>,
parameters: [R; 2],
},
Spline {
native_id: i64,
knots: Vec<R>,
controls: Vec<R>,
point: P,
},
Algebraic {
operator: String,
operands: Vec<LawExpression<R, V, P>>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub struct SkinSurfaceProfile<R = f64, V = Vector3> {
pub type_code: i64,
pub curve: CurveId,
pub data: ClassicLoftProfileData<R, V>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize")
)]
pub enum SkinSurfaceLayout<R = f64, V = Vector3> {
Profiles {
profiles: Vec<SkinSurfaceProfile<R, V>>,
path: CurveId,
tail: [i64; 2],
},
Compact {
inner_count: i64,
curve: CurveId,
subdata: LoftSubdata<R>,
first_tail: i64,
secondary_curve: CurveId,
second_tail: i64,
},
}
impl<R, V> SkinSurfaceLayout<R, V> {
pub fn inner_count(&self) -> Result<i64, cadmpeg_core::CodecError> {
match self {
Self::Profiles { profiles, .. } => i64::try_from(profiles.len()).map_err(|_| {
cadmpeg_core::decode::refuse_local_limit(
"skin surface profile count",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(profiles.len()),
)
}),
Self::Compact { inner_count, .. } => Ok(*inner_count),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub struct SkinSurfaceConstruction<R = f64, V = Vector3, P = Point3> {
pub surface_boolean: i64,
pub surface_normal: i64,
pub surface_direction: i64,
pub count: i64,
pub parameter: R,
pub layout: SkinSurfaceLayout<R, V>,
pub direction: V,
pub trailing_parameter: R,
pub formula: LawFormula<R, V, P>,
pub parameter_curve: CurveId,
pub discontinuities: [Vec<R>; 6],
pub discontinuity_flag: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub struct NetSurfaceConstruction<R = f64, V = Vector3, P = Point3> {
pub sections: Box<[LoftSection<R, V>; 2]>,
pub frame_parameters: [R; 12],
pub flag: i64,
pub directions: [V; 4],
pub formulas: Box<[LawFormula<R, V, P>; 4]>,
pub discontinuities: [Vec<R>; 6],
pub discontinuity_flag: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub enum SweepSurfaceLayout<R = f64, V = Vector3, P = Point3> {
ProfileFirst {
secondary_kind: i64,
directions: [V; 5],
origin: P,
parameters: [R; 4],
formulas: Box<[LawFormula<R, V, P>; 3]>,
},
ExplicitFormula {
mode: i64,
profile_range: [R; 2],
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
profile_frame: Option<(P, V)>,
origin: P,
directions: [V; 3],
trajectory_flag: bool,
path_range: [R; 2],
path_parameter: R,
formula_flag: bool,
formula: LawFormula<R, V, P>,
trailing_flag: bool,
},
ExplicitGuide {
mode: i64,
profile_range: [R; 2],
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
profile_frame: Option<(P, V)>,
origin: P,
directions: [V; 3],
trajectory_flag: bool,
path_range: [R; 2],
path_parameter: R,
guide_flags: [bool; 2],
guide_curve: CurveId,
guide_range: [R; 2],
guide_modes: [i64; 2],
guide_parameters: [R; 6],
trailing_flags: [bool; 3],
},
ExplicitSurface {
mode: i64,
profile_range: [R; 2],
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
profile_frame: Option<(P, V)>,
origin: P,
directions: [V; 3],
trajectory_flag: bool,
path_range: [R; 2],
path_parameter: R,
singularity: i64,
support_surface: SurfaceId,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
auxiliary_curve: Option<CurveId>,
support_flag: bool,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
legacy_flag: Option<bool>,
},
LawDriven {
mode: i64,
profile_range: [R; 2],
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
profile_frame: Option<(P, V)>,
origin: P,
directions: [V; 3],
first_law: Box<LawExpression<R, V, P>>,
first_mode: i64,
first_range: [R; 2],
law_direction: V,
path_mode: i64,
path_flag: bool,
path_range: [R; 2],
path_parameter: R,
second_law_flag: bool,
second_law: Box<LawExpression<R, V, P>>,
formula_mode: i64,
formula: LawFormula<R, V, P>,
trailing_flag: bool,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>"))]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub struct SweepRevisionForm<R = f64> {
pub revision: PositiveI64,
pub primary_flag: bool,
#[serde(default)]
pub profile_endpoints: [Option<R>; 2],
#[serde(default)]
pub path_endpoints: [Option<R>; 2],
pub cache: RevisionCacheForm<RevisionSurfaceParameterization<R>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>, V: Deserialize<'de>, P: Deserialize<'de>"))]
#[cfg_attr(
feature = "schema",
schemars(
bound = "R: JsonSchema + Serialize, V: JsonSchema + Serialize, P: JsonSchema + Serialize"
)
)]
pub struct SweepSurfaceConstruction<R = f64, V = Vector3, P = Point3> {
pub primary_kind: i64,
#[serde(default, skip_serializing_if = "CacheContract::is_bare_legacy")]
pub cache: CacheContract<SweepRevisionForm<R>>,
pub layout: SweepSurfaceLayout<R, V, P>,
pub discontinuities: [Vec<R>; 6],
pub discontinuity_flag: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum BlendRadiusLaw {
Constant {
signed_radius: crate::scalar::Length,
},
Linear {
start: crate::scalar::Length,
end: crate::scalar::Length,
},
Law {
curve: NurbsCurve,
},
}
const NON_FINITE_BLEND_RADIUS: ProceduralGeometryError =
ProceduralGeometryError::Payload("blend radius law is not finite");
impl BlendRadiusLaw {
pub fn constant(signed_radius: f64) -> Result<Self, ProceduralGeometryError> {
Ok(Self::Constant {
signed_radius: crate::scalar::Length::new(signed_radius)
.ok_or(NON_FINITE_BLEND_RADIUS)?,
})
}
pub fn linear(start: f64, end: f64) -> Result<Self, ProceduralGeometryError> {
Ok(Self::Linear {
start: crate::scalar::Length::new(start).ok_or(NON_FINITE_BLEND_RADIUS)?,
end: crate::scalar::Length::new(end).ok_or(NON_FINITE_BLEND_RADIUS)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ProceduralCurve {
pub id: ProceduralCurveId,
definition: ProceduralCurveDefinition,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct SupportPcurve {
pub geometry: PcurveGeometry,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_parameter_range"
)]
pub parameter_range: Option<DirectedParameterRange>,
}
impl SupportPcurve {
#[must_use]
pub const fn new(
geometry: PcurveGeometry,
parameter_range: Option<DirectedParameterRange>,
) -> Self {
Self {
geometry,
parameter_range,
}
}
}
impl From<PcurveGeometry> for SupportPcurve {
fn from(geometry: PcurveGeometry) -> Self {
Self::new(geometry, None)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct DirectedParameterRange([f64; 2]);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParameterRangeError;
impl std::fmt::Display for ParameterRangeError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("parameter range endpoints must be finite and distinct")
}
}
impl std::error::Error for ParameterRangeError {}
impl DirectedParameterRange {
pub fn from_angle_endpoints(
values: [crate::scalar::Angle; 2],
) -> Result<Self, ParameterRangeError> {
(values[0] != values[1])
.then_some(Self(values.map(crate::scalar::Angle::get)))
.ok_or(ParameterRangeError)
}
pub fn from_finite_endpoints(
values: [crate::scalar::FiniteReal; 2],
) -> Result<Self, ParameterRangeError> {
(values[0] != values[1])
.then_some(Self(values.map(crate::scalar::FiniteReal::get)))
.ok_or(ParameterRangeError)
}
pub fn new(values: [f64; 2]) -> Result<Self, ParameterRangeError> {
let [first, second] = values.map(FiniteReal::new);
let [Some(first), Some(second)] = [first, second] else {
return Err(ParameterRangeError);
};
Self::from_finite_endpoints([first, second])
}
#[must_use]
pub const fn endpoints(self) -> [f64; 2] {
self.0
}
}
impl<'de> Deserialize<'de> for DirectedParameterRange {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Self::new(<[f64; 2]>::deserialize(deserializer)?).map_err(serde::de::Error::custom)
}
}
fn require_mapped_interval(
sides: &[IntcurveSupportSide; 2],
parameter_range: crate::topology::ParameterInterval,
) -> Result<(), &'static str> {
let [start, end] = parameter_range.endpoints();
if start == end
&& sides.iter().any(|side| {
side.pcurve
.as_ref()
.is_some_and(|pcurve| pcurve.parameter_range.is_some())
})
{
return Err(
"support context parameter_range must be nonzero for an explicit pcurve mapping",
);
}
Ok(())
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct IntcurveSupportSide {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_surface"
)]
pub surface: Option<SurfaceId>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_intcurve_support_side_pcurve"
)]
pub pcurve: Option<SupportPcurve>,
}
impl IntcurveSupportSide {
#[must_use]
pub fn pcurve_parameter_range(&self) -> Option<DirectedParameterRange> {
self.pcurve
.as_ref()
.and_then(|pcurve| pcurve.parameter_range)
}
#[must_use]
pub fn pcurve_parameter(
&self,
solved_parameter_range: crate::topology::ParameterInterval,
parameter: f64,
) -> Option<FiniteReal> {
let admitted_parameter = FiniteReal::new(parameter)?;
let Some(pcurve_interval) = self.pcurve.as_ref()?.parameter_range else {
return Some(admitted_parameter);
};
let [pcurve_start, pcurve_end] = pcurve_interval.finite_endpoints();
let pcurve_range = pcurve_interval.endpoints();
let solved_parameter_range = solved_parameter_range.endpoints();
let solved_span = solved_parameter_range[1] - solved_parameter_range[0];
if solved_span == 0.0 {
return None;
}
if parameter == solved_parameter_range[0] {
return Some(pcurve_start);
}
if parameter == solved_parameter_range[1] {
return Some(pcurve_end);
}
let offset = parameter - solved_parameter_range[0];
let mapped_span = pcurve_range[1] - pcurve_range[0];
if solved_span.is_finite() && offset.is_finite() && mapped_span.is_finite() {
let fraction = offset / solved_span;
let advance = fraction * mapped_span;
if fraction.is_normal() && advance.is_normal() {
if let Some(mapped) = fast_dot(
[pcurve_range[0], advance],
[1.0, 1.0],
[pcurve_range[0], advance],
) {
return Some(mapped);
}
}
}
let mut numerator = ExactSignedSum::default();
numerator.add_product(pcurve_range[0], solved_parameter_range[1]);
numerator.add_product(-pcurve_range[0], parameter);
numerator.add_product(pcurve_range[1], parameter);
numerator.add_product(-pcurve_range[1], solved_parameter_range[0]);
let mut denominator = ExactSignedSum::default();
denominator.add_product(solved_parameter_range[1], 1.0);
denominator.add_product(-solved_parameter_range[0], 1.0);
let denominator = denominator.finish()?;
numerator.finish().map_or(Some(FiniteReal::ZERO), |value| {
value.quotient(denominator).ok()
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "LawCurveVersionFormWire")]
pub struct LawCurveVersionForm {
stamp: i64,
post_enum: i64,
parameter_range: [Option<FiniteReal>; 2],
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct LawCurveVersionFormWire {
stamp: i64,
post_enum: i64,
parameter_range: [Option<f64>; 2],
}
impl TryFrom<LawCurveVersionFormWire> for LawCurveVersionForm {
type Error = &'static str;
fn try_from(wire: LawCurveVersionFormWire) -> Result<Self, Self::Error> {
Self::try_new(wire.stamp, wire.post_enum, wire.parameter_range)
}
}
impl LawCurveVersionForm {
pub fn try_new(
stamp: i64,
post_enum: i64,
parameter_range: [Option<f64>; 2],
) -> Result<Self, &'static str> {
let parameter_range = FiniteReal::optional(parameter_range)
.ok_or("law curve parameter_range bounds must be finite")?;
Ok(Self {
stamp,
post_enum,
parameter_range,
})
}
#[must_use]
pub const fn stamp(&self) -> i64 {
self.stamp
}
#[must_use]
pub const fn post_enum(&self) -> i64 {
self.post_enum
}
#[must_use]
pub const fn parameter_range(&self) -> [Option<FiniteReal>; 2] {
self.parameter_range
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "IntcurveSupportContextWire")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct IntcurveSupportContext {
sides: [IntcurveSupportSide; 2],
parameter_range: crate::topology::ParameterInterval,
discontinuities: [Vec<FiniteReal>; 3],
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct IntcurveSupportContextWire {
sides: [IntcurveSupportSide; 2],
parameter_range: [f64; 2],
discontinuities: [Vec<f64>; 3],
}
impl TryFrom<IntcurveSupportContextWire> for IntcurveSupportContext {
type Error = &'static str;
fn try_from(wire: IntcurveSupportContextWire) -> Result<Self, Self::Error> {
Self::try_new(wire.sides, wire.parameter_range, wire.discontinuities)
}
}
impl IntcurveSupportContext {
pub fn try_new(
sides: [IntcurveSupportSide; 2],
parameter_range: [f64; 2],
discontinuities: [Vec<f64>; 3],
) -> Result<Self, &'static str> {
let parameter_range = crate::topology::ParameterInterval::new(parameter_range)
.map_err(|_| "support context parameter_range must be finite and ordered")?;
require_mapped_interval(&sides, parameter_range)?;
let discontinuities = FiniteReal::lanes(discontinuities)
.ok_or("support context discontinuities must be finite")?;
Ok(Self {
sides,
parameter_range,
discontinuities,
})
}
pub fn from_parts(
sides: [IntcurveSupportSide; 2],
parameter_range: crate::topology::ParameterInterval,
discontinuities: [Vec<FiniteReal>; 3],
) -> Result<Self, &'static str> {
require_mapped_interval(&sides, parameter_range)?;
Ok(Self {
sides,
parameter_range,
discontinuities,
})
}
#[must_use]
pub fn over_interval(
sides: [IntcurveSupportSide; 2],
parameter_range: crate::topology::IncreasingParameterInterval,
) -> Self {
Self {
sides,
parameter_range: parameter_range.into(),
discontinuities: std::array::from_fn(|_| Vec::new()),
}
}
pub fn set_surface(&mut self, side: usize, surface: Option<SurfaceId>) {
self.sides[side].surface = surface;
}
pub fn set_unmapped_pcurve(&mut self, side: usize, geometry: Option<PcurveGeometry>) {
self.sides[side].pcurve = geometry.map(SupportPcurve::from);
}
pub fn try_copy_pcurve_for_decode(
&mut self,
ctx: &DecodeContext<'_>,
source: usize,
target: usize,
) -> Result<(), CodecError> {
if source == target {
return Ok(());
}
let copied = self.sides[source]
.pcurve
.as_ref()
.map(|pcurve| {
Ok::<_, CodecError>(SupportPcurve::new(
pcurve
.geometry
.try_clone_for_decode(ctx, "intersection support pcurve copy")?,
pcurve.parameter_range,
))
})
.transpose()?;
self.sides[target].pcurve = copied;
Ok(())
}
#[must_use]
pub const fn sides(&self) -> &[IntcurveSupportSide; 2] {
&self.sides
}
#[must_use]
pub const fn parameter_range(&self) -> crate::topology::ParameterInterval {
self.parameter_range
}
#[must_use]
pub const fn discontinuities(&self) -> &[Vec<FiniteReal>; 3] {
&self.discontinuities
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "TolerantIntersectionConstructionWire")]
pub struct TolerantIntersectionConstruction {
supports: [SurfaceId; 2],
endpoints: [FinitePoint3; 2],
tolerance: NonNegativeReal,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct TolerantIntersectionConstructionWire {
supports: [SurfaceId; 2],
endpoints: [Point3; 2],
tolerance: f64,
}
impl TryFrom<TolerantIntersectionConstructionWire> for TolerantIntersectionConstruction {
type Error = &'static str;
fn try_from(wire: TolerantIntersectionConstructionWire) -> Result<Self, Self::Error> {
Self::try_new(wire.supports, wire.endpoints, wire.tolerance)
}
}
impl TolerantIntersectionConstruction {
pub fn try_new(
supports: [SurfaceId; 2],
endpoints: [Point3; 2],
tolerance: f64,
) -> Result<Self, &'static str> {
if supports[0] == supports[1] {
return Err("tolerant intersection supports must be distinct");
}
let [start, end] = endpoints.map(FinitePoint3::new);
let (Some(start), Some(end)) = (start, end) else {
return Err("tolerant intersection endpoints must be finite");
};
let endpoints = [start, end];
let tolerance = NonNegativeReal::new(tolerance)
.ok_or("tolerant intersection tolerance must be finite and non-negative")?;
Ok(Self {
supports,
endpoints,
tolerance,
})
}
pub fn from_parts(
supports: [SurfaceId; 2],
endpoints: [FinitePoint3; 2],
tolerance: NonNegativeReal,
) -> Result<Self, &'static str> {
if supports[0] == supports[1] {
return Err("tolerant intersection supports must be distinct");
}
Ok(Self {
supports,
endpoints,
tolerance,
})
}
#[must_use]
pub const fn supports(&self) -> &[SurfaceId; 2] {
&self.supports
}
#[must_use]
pub const fn endpoints(&self) -> &[FinitePoint3; 2] {
&self.endpoints
}
#[must_use]
pub const fn tolerance(&self) -> NonNegativeReal {
self.tolerance
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "TolerantIntersectionParameterizationWire")]
pub struct TolerantIntersectionParameterization {
pub pcurves: [PcurveGeometry; 2],
parameter_range: crate::topology::IncreasingParameterInterval,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct TolerantIntersectionParameterizationWire {
pcurves: [PcurveGeometry; 2],
parameter_range: [f64; 2],
}
impl TryFrom<TolerantIntersectionParameterizationWire> for TolerantIntersectionParameterization {
type Error = &'static str;
fn try_from(wire: TolerantIntersectionParameterizationWire) -> Result<Self, Self::Error> {
Self::try_new(wire.pcurves, wire.parameter_range)
}
}
impl TolerantIntersectionParameterization {
pub fn try_new(
pcurves: [PcurveGeometry; 2],
parameter_range: [f64; 2],
) -> Result<Self, &'static str> {
let parameter_range = crate::topology::IncreasingParameterInterval::new(parameter_range)
.ok_or(
"tolerant intersection parameter_range must be finite and strictly increasing",
)?;
Ok(Self {
pcurves,
parameter_range,
})
}
#[must_use]
pub const fn parameter_range(&self) -> crate::topology::IncreasingParameterInterval {
self.parameter_range
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>"))]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub struct CacheFirstCurveForm<R = f64> {
pub revision: PositiveI64,
pub cache: RevisionCacheForm<CacheFirstCurveParameterization<R>>,
#[serde(default)]
pub support_bounds: [[Option<R>; 4]; 2],
#[serde(default)]
pub solved_range: [Option<R>; 2],
pub extension: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
tag = "kind",
content = "value",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum SpringSupport<R = f64> {
Surface(SurfaceId),
Ranges([[R; 2]; 2]),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
tag = "kind",
content = "value",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum SpringPcurve<R = f64> {
Pcurve(PcurveGeometry),
Range([R; 2]),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
#[cfg_attr(
feature = "schema",
schemars(bound = "R: JsonSchema + Serialize, I: JsonSchema + Serialize")
)]
pub enum SpringLayout<R = f64, I = [f64; 2]> {
ContextFirst {
supports: [SpringSupport<R>; 2],
first_pcurve: Box<SpringPcurve<R>>,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
second_pcurve: Option<PcurveGeometry>,
parameter_range: I,
discontinuities: [Vec<R>; 3],
discontinuity_flag: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
CacheFirst {
context: IntcurveSupportContext,
form: CacheFirstCurveForm<R>,
},
}
impl<R, I> SpringLayout<R, I> {
fn cache_first(&self) -> Option<&CacheFirstCurveForm<R>> {
match self {
Self::CacheFirst { form, .. } => Some(form),
Self::ContextFirst { .. } => None,
}
}
fn write_revision_fit_tolerance(&mut self, write: ToleranceWrite) -> RevisionCacheWrite {
match self {
Self::CacheFirst { form, .. } => form.cache.write_fit_tolerance(write),
Self::ContextFirst { .. } => RevisionCacheWrite::NoForm,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "R: Deserialize<'de>"))]
#[cfg_attr(feature = "schema", schemars(bound = "R: JsonSchema + Serialize"))]
pub struct CacheFirstCurveParameterization<R = f64> {
#[serde(default)]
pub interval: [Option<R>; 2],
pub closed_form: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "SurfaceCurveTailWire")]
pub struct SurfaceCurveTail {
extension: i64,
revision: PositiveI64,
#[cfg_attr(
feature = "schema",
schemars(with = "RevisionCacheForm<CacheFirstCurveParameterization>")
)]
cache: RevisionCacheForm<CacheFirstCurveParameterization<FiniteReal>>,
support_bounds: [RecordBounds; 2],
#[serde(default)]
solved_range: [Option<FiniteReal>; 2],
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct SurfaceCurveTailWire {
extension: i64,
revision: PositiveI64,
cache: RevisionCacheForm<CacheFirstCurveParameterization>,
#[serde(default)]
support_bounds: [[Option<f64>; 4]; 2],
#[serde(default)]
solved_range: [Option<f64>; 2],
}
impl TryFrom<SurfaceCurveTailWire> for SurfaceCurveTail {
type Error = &'static str;
fn try_from(wire: SurfaceCurveTailWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.extension,
wire.revision,
wire.cache,
wire.support_bounds,
wire.solved_range,
)
}
}
impl SurfaceCurveTail {
pub fn try_new(
extension: i64,
revision: PositiveI64,
cache: RevisionCacheForm<CacheFirstCurveParameterization>,
support_bounds: [[Option<f64>; 4]; 2],
solved_range: [Option<f64>; 2],
) -> Result<Self, &'static str> {
const INVALID: &str = "surface curve tail bounds must be finite";
let [first, second] = support_bounds.map(|bounds| RecordBounds::try_new(bounds).ok());
let (Some(first), Some(second), Some(solved_range), Some(cache)) = (
first,
second,
FiniteReal::optional(solved_range),
cache.admit(),
) else {
return Err(INVALID);
};
Ok(Self {
extension,
revision,
cache,
support_bounds: [first, second],
solved_range,
})
}
#[must_use]
pub const fn extension(&self) -> i64 {
self.extension
}
#[must_use]
pub const fn revision(&self) -> PositiveI64 {
self.revision
}
#[must_use]
pub const fn cache(&self) -> &RevisionCacheForm<CacheFirstCurveParameterization<FiniteReal>> {
&self.cache
}
#[must_use]
pub const fn support_bounds(&self) -> &[RecordBounds; 2] {
&self.support_bounds
}
#[must_use]
pub const fn solved_range(&self) -> &[Option<FiniteReal>; 2] {
&self.solved_range
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct SurfaceCurveCacheFirst<F> {
pub form: SurfaceCurveTail,
pub flags: F,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ParametricSurfaceCurveFlags {
pub flag: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_second_flag"
)]
pub second_flag: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum ProjectionTail<R = f64> {
EarlyClose {
flag: bool,
},
Ranged {
flag: bool,
parameter_range: [R; 2],
role: ProjectionRole,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum ProjectionRole {
#[serde(rename = "surf1")]
Surf1,
#[serde(rename = "surf2")]
Surf2,
}
impl ProjectionRole {
pub fn parse(value: &str) -> Option<Self> {
match value {
"surf1" => Some(Self::Surf1),
"surf2" => Some(Self::Surf2),
_ => None,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Surf1 => "surf1",
Self::Surf2 => "surf2",
}
}
}
impl<'de> Deserialize<'de> for ProjectionRole {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::parse(&value).ok_or_else(|| {
serde::de::Error::custom(format!(
"invalid projection role field value `{value}`; expected `surf1` or `surf2`"
))
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "family", rename_all = "snake_case", deny_unknown_fields)]
pub enum SurfaceCurveFamily {
Blend {
context: IntcurveSupportContext,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_tail"
)]
tail: Option<SurfaceCurveCacheFirst<bool>>,
},
SurfaceConstrained {
context: IntcurveSupportContext,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_tail"
)]
tail: Option<SurfaceCurveCacheFirst<bool>>,
},
Parametric {
context: IntcurveSupportContext,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_surface_curve_family_tail"
)]
tail: Option<SurfaceCurveCacheFirst<ParametricSurfaceCurveFlags>>,
},
Skin {
context: IntcurveSupportContext,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_tail"
)]
tail: Option<SurfaceCurveCacheFirst<bool>>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum SurfaceCurveFamilyKind {
Blend,
SurfaceConstrained,
Parametric,
Skin,
}
impl SurfaceCurveFamily {
#[must_use]
pub const fn kind(&self) -> SurfaceCurveFamilyKind {
match self {
Self::Blend { .. } => SurfaceCurveFamilyKind::Blend,
Self::SurfaceConstrained { .. } => SurfaceCurveFamilyKind::SurfaceConstrained,
Self::Parametric { .. } => SurfaceCurveFamilyKind::Parametric,
Self::Skin { .. } => SurfaceCurveFamilyKind::Skin,
}
}
#[must_use]
pub const fn context(&self) -> &IntcurveSupportContext {
match self {
Self::Blend { context, .. }
| Self::SurfaceConstrained { context, .. }
| Self::Parametric { context, .. }
| Self::Skin { context, .. } => context,
}
}
fn revision_cache(
&self,
) -> Option<&RevisionCacheForm<CacheFirstCurveParameterization<FiniteReal>>> {
match self {
Self::Blend { tail, .. }
| Self::SurfaceConstrained { tail, .. }
| Self::Skin { tail, .. } => tail.as_ref().map(|first| &first.form.cache),
Self::Parametric { tail, .. } => tail.as_ref().map(|first| &first.form.cache),
}
}
fn write_revision_fit_tolerance(&mut self, write: ToleranceWrite) -> RevisionCacheWrite {
let form = match self {
Self::Blend { tail, .. }
| Self::SurfaceConstrained { tail, .. }
| Self::Skin { tail, .. } => tail.as_mut().map(|first| &mut first.form.cache),
Self::Parametric { tail, .. } => tail.as_mut().map(|first| &mut first.form.cache),
};
write_revision_form_tolerance(form, write)
}
#[must_use]
pub fn has_same_form(&self, other: &Self) -> bool {
match (self, other) {
(Self::Blend { tail: first, .. }, Self::Blend { tail: second, .. })
| (
Self::SurfaceConstrained { tail: first, .. },
Self::SurfaceConstrained { tail: second, .. },
)
| (Self::Skin { tail: first, .. }, Self::Skin { tail: second, .. }) => first == second,
(Self::Parametric { tail: first, .. }, Self::Parametric { tail: second, .. }) => {
first == second
}
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum SilhouetteKind {
Standard {},
Parametric {},
Taper {
draft_factor: crate::scalar::FiniteReal,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum DeformableCurveData<R = f64, V = Vector3, P = Point3> {
VectorField {
vectors: [V; 4],
parameter_pairs: Vec<[R; 2]>,
},
Mode3 {
leading_vectors: [V; 4],
leading_parameter: R,
leading_flags: [bool; 3],
trailing_point: P,
trailing_vectors: [V; 2],
frame_parameter: R,
frame_flags: [bool; 2],
parameters: [R; 3],
trailing_flags: [bool; 5],
trailing_parameter: R,
trailing_value: i64,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum DeformableCurveSource {
Curve {
curve: CurveId,
},
NativeReference {
flag: bool,
index: i64,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "side", rename_all = "snake_case", deny_unknown_fields)]
pub enum OffsetSide<V = Vector3> {
PlaneNormal {
normal: V,
},
Direction {
direction: V,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_offset_side_support"
)]
support: Option<SurfaceId>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum CurveOffsetRange<R = f64> {
Uniform {
#[serde(deserialize_with = "deserialize_curve_offset_interval")]
parameter_range: crate::topology::IncreasingParameterInterval,
},
Variable {
#[serde(deserialize_with = "deserialize_curve_offset_interval")]
parameter_range: crate::topology::IncreasingParameterInterval,
distance_law: CurveOffsetDistanceLaw<R>,
},
}
pub(crate) const INVALID_CURVE_OFFSET: &str =
"curve offset distance, side, range, or law is invalid";
fn admit_curve_offset_interval(
range: [f64; 2],
) -> Result<crate::topology::IncreasingParameterInterval, ProceduralGeometryError> {
crate::topology::IncreasingParameterInterval::new(range)
.ok_or(ProceduralGeometryError::Payload(INVALID_CURVE_OFFSET))
}
fn deserialize_curve_offset_interval<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<crate::topology::IncreasingParameterInterval, D::Error> {
crate::topology::IncreasingParameterInterval::new(<[f64; 2]>::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom(INVALID_CURVE_OFFSET))
}
impl CurveOffsetRange {
pub fn uniform(parameter_range: [f64; 2]) -> Result<Self, ProceduralGeometryError> {
Ok(Self::Uniform {
parameter_range: admit_curve_offset_interval(parameter_range)?,
})
}
pub fn variable(
parameter_range: [f64; 2],
distance_law: CurveOffsetDistanceLaw,
) -> Result<Self, ProceduralGeometryError> {
Ok(Self::Variable {
parameter_range: admit_curve_offset_interval(parameter_range)?,
distance_law,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ProceduralCurveDefinition {
Exact {
#[serde(default, skip_serializing_if = "Option::is_none")]
cache: Option<LegacyCache>,
},
Law {
context: IntcurveSupportContext,
#[serde(default, skip_serializing_if = "Option::is_none")]
version: Option<LawCurveVersionForm>,
extension: i64,
#[cfg_attr(feature = "schema", schemars(with = "LawFormula"))]
primary: FiniteLawFormula,
#[cfg_attr(feature = "schema", schemars(with = "Vec<LawFormula>"))]
additional: Vec<FiniteLawFormula>,
#[serde(default, skip_serializing_if = "Option::is_none")]
cache: Option<LegacyCache>,
},
Compound(CompoundCurveConstruction),
Helix(HelixCurveConstruction),
Intersection {
context: IntcurveSupportContext,
discontinuity_flag: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
cache: Option<LegacyCache>,
},
TolerantIntersection {
construction: TolerantIntersectionConstruction,
#[serde(default, skip_serializing_if = "Option::is_none")]
parameterization: Option<TolerantIntersectionParameterization>,
#[serde(default, skip_serializing_if = "Option::is_none")]
cache: Option<LegacyCache>,
},
ThreeSurfaceIntersection(curve_payloads::ThreeSurfaceIntersectionCurvePayload),
SurfaceCurve {
family: SurfaceCurveFamily,
},
Silhouette(curve_payloads::SilhouetteCurveConstruction),
SurfaceOffset(curve_payloads::SurfaceOffsetCurveConstruction),
Spring(curve_payloads::SpringCurvePayload),
Deformable(curve_payloads::DeformableCurveConstruction),
Projection(curve_payloads::ProjectionCurvePayload),
Offset(curve_payloads::OffsetCurveConstruction),
SpatialOffset(curve_payloads::SpatialOffsetCurveConstruction),
TwoSidedOffset(curve_payloads::TwoSidedOffsetCurveConstruction),
VectorOffset(curve_payloads::VectorOffsetCurveConstruction),
Subset(curve_payloads::SubsetCurveConstruction),
Replica {
source: CurveId,
transform: Transform,
},
BlendSpine {
#[serde(default, skip_serializing_if = "Option::is_none")]
blend_surface: Option<SurfaceId>,
},
Unknown {
#[serde(default, skip_serializing_if = "Option::is_none")]
native_kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
record: Option<UnknownId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
cache: Option<LegacyCache>,
},
}
#[derive(Deserialize)]
#[serde(
remote = "ProceduralCurveDefinition",
tag = "kind",
rename_all = "snake_case",
deny_unknown_fields
)]
enum ProceduralCurveDefinitionWire {
Exact {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
Law {
context: IntcurveSupportContext,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_version"
)]
version: Option<LawCurveVersionForm>,
extension: i64,
primary: FiniteLawFormula,
additional: Vec<FiniteLawFormula>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
Compound(CompoundCurveConstruction),
Helix(HelixCurveConstruction),
Intersection {
context: IntcurveSupportContext,
discontinuity_flag: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
TolerantIntersection {
construction: TolerantIntersectionConstruction,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_parameterization"
)]
parameterization: Option<TolerantIntersectionParameterization>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
ThreeSurfaceIntersection(curve_payloads::ThreeSurfaceIntersectionCurvePayload),
SurfaceCurve {
family: SurfaceCurveFamily,
},
Silhouette(curve_payloads::SilhouetteCurveConstruction),
SurfaceOffset(curve_payloads::SurfaceOffsetCurveConstruction),
Spring(curve_payloads::SpringCurvePayload),
Deformable(curve_payloads::DeformableCurveConstruction),
Projection(curve_payloads::ProjectionCurvePayload),
Offset(curve_payloads::OffsetCurveConstruction),
SpatialOffset(curve_payloads::SpatialOffsetCurveConstruction),
TwoSidedOffset(curve_payloads::TwoSidedOffsetCurveConstruction),
VectorOffset(curve_payloads::VectorOffsetCurveConstruction),
Subset(curve_payloads::SubsetCurveConstruction),
Replica {
source: CurveId,
transform: Transform,
},
BlendSpine {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_blend_surface"
)]
blend_surface: Option<SurfaceId>,
},
Unknown {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_native_kind"
)]
native_kind: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_record"
)]
record: Option<UnknownId>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_cache"
)]
cache: Option<LegacyCache>,
},
}
impl<'de> Deserialize<'de> for ProceduralCurveDefinition {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
ProceduralCurveDefinitionWire::deserialize(deserializer)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct VectorOffsetRoles {
pub source: i64,
pub offset: i64,
}
impl ProceduralCurveDefinition {
fn revision_cache(
&self,
) -> Option<&RevisionCacheForm<CacheFirstCurveParameterization<FiniteReal>>> {
match self {
Self::SurfaceCurve { family } => family.revision_cache(),
Self::SurfaceOffset(payload) => payload.cache_first().as_ref().map(|form| &form.cache),
Self::Spring(definition_payload) => {
let layout = definition_payload.layout();
layout.cache_first().map(|form| &form.cache)
}
Self::Deformable(definition_payload) => {
let cache_first = definition_payload.cache_first();
Some(&cache_first.cache)
}
_ => None,
}
}
fn write_revision_fit_tolerance(&mut self, write: ToleranceWrite) -> RevisionCacheWrite {
match self {
Self::SurfaceCurve { family } => family.write_revision_fit_tolerance(write),
Self::SurfaceOffset(payload) => payload.write_revision_fit_tolerance(write),
Self::Spring(payload) => payload.write_revision_fit_tolerance(write),
Self::Deformable(payload) => payload.write_revision_fit_tolerance(write),
_ => RevisionCacheWrite::NoForm,
}
}
}
impl ProceduralCurveDefinition {
#[must_use]
pub fn owns_revision_cache(&self) -> bool {
self.revision_cache().is_some()
}
}
impl ProceduralCurve {
pub fn new(id: ProceduralCurveId, definition: ProceduralCurveDefinition) -> Self {
Self { id, definition }
}
#[must_use]
pub fn definition(&self) -> &ProceduralCurveDefinition {
&self.definition
}
pub fn replace_definition(&mut self, definition: ProceduralCurveDefinition) {
self.definition = definition;
}
pub fn edit_definition<R>(
&mut self,
edit: impl FnOnce(&mut ProceduralCurveDefinition) -> R,
) -> R {
edit(&mut self.definition)
}
pub fn intersection_context_mut(&mut self) -> Option<&mut IntcurveSupportContext> {
match &mut self.definition {
ProceduralCurveDefinition::Intersection { context, .. } => Some(context),
_ => None,
}
}
#[must_use]
pub fn cache_fit_tolerance(&self) -> Option<FitTolerance> {
self.definition.cache_fit_tolerance()
}
pub fn set_cache_fit_tolerance(
&mut self,
value: Option<f64>,
) -> Result<(), CacheContractError> {
let value = value.map(FitTolerance::try_new).transpose()?;
self.definition.set_cache_fit_tolerance(value)
}
pub fn require_cache_fit_tolerance(
&mut self,
value: FitTolerance,
) -> Result<(), CacheContractError> {
self.definition.require_cache_fit_tolerance(value)
}
pub fn scale_cache_fit_tolerance(
&mut self,
scale: crate::scalar::PositiveReal,
) -> Result<(), CacheContractError> {
self.definition.scale_cache_fit_tolerance(scale)
}
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub(crate) struct ProceduralSurfaceRow {
id: ProceduralSurfaceId,
surface: SurfaceId,
definition: ProceduralSurfaceDefinition,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_record_bounds"
)]
record_bounds: Option<RecordBounds>,
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub(crate) struct ProceduralCurveRow {
id: ProceduralCurveId,
curve: CurveId,
definition: ProceduralCurveDefinition,
}
impl ProceduralSurfaceRow {
pub(crate) fn new(surface: SurfaceId, procedural: &ProceduralSurface) -> Self {
Self {
id: procedural.id.clone(),
surface,
definition: procedural.definition.clone(),
record_bounds: procedural.record_bounds,
}
}
pub(crate) fn into_parts(self) -> (SurfaceId, ProceduralSurface) {
(
self.surface,
ProceduralSurface::new(self.id, self.definition, self.record_bounds),
)
}
}
impl ProceduralCurveRow {
pub(crate) fn new(curve: CurveId, procedural: &ProceduralCurve) -> Self {
Self {
id: procedural.id.clone(),
curve,
definition: procedural.definition.clone(),
}
}
pub(crate) fn into_parts(self) -> (CurveId, ProceduralCurve) {
(
self.curve,
ProceduralCurve {
id: self.id,
definition: self.definition,
},
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum CurveOffsetLawBasis {
ArcLength,
Parameter,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "u8", into = "u8")]
pub struct CurveOffsetCoordinate(u8);
impl CurveOffsetCoordinate {
pub fn try_new(coordinate: u8) -> Result<Self, &'static str> {
match coordinate {
1..=3 => Ok(Self(coordinate)),
_ => Err("curve offset coordinate must be 1, 2, or 3"),
}
}
#[must_use]
pub const fn get(self) -> u8 {
self.0
}
}
impl TryFrom<u8> for CurveOffsetCoordinate {
type Error = &'static str;
fn try_from(coordinate: u8) -> Result<Self, Self::Error> {
Self::try_new(coordinate)
}
}
impl From<CurveOffsetCoordinate> for u8 {
fn from(coordinate: CurveOffsetCoordinate) -> Self {
coordinate.get()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum CurveOffsetDistanceLaw<R = f64> {
Linear {
basis: CurveOffsetLawBasis,
distances: [R; 2],
#[serde(deserialize_with = "deserialize_curve_offset_interval")]
control_range: crate::topology::IncreasingParameterInterval,
},
Coordinate {
function: CurveId,
coordinate: CurveOffsetCoordinate,
basis: CurveOffsetLawBasis,
function_parameter_offset: R,
function_parameter_scale: R,
},
}
impl CurveOffsetDistanceLaw {
pub fn linear(
basis: CurveOffsetLawBasis,
distances: [f64; 2],
control_range: [f64; 2],
) -> Result<Self, ProceduralGeometryError> {
Ok(Self::Linear {
basis,
distances,
control_range: admit_curve_offset_interval(control_range)?,
})
}
}
#[cfg(test)]
mod tests;
impl CompoundCurveConstruction {
#[must_use]
pub const fn legacy_cache(&self) -> Option<LegacyCache> {
self.cache
}
const fn legacy_cache_slot_mut(&mut self) -> &mut Option<LegacyCache> {
&mut self.cache
}
}
impl HelixCurveConstruction {
#[must_use]
pub const fn legacy_cache(&self) -> Option<LegacyCache> {
self.cache
}
const fn legacy_cache_slot_mut(&mut self) -> &mut Option<LegacyCache> {
&mut self.cache
}
}
impl TSplineSurfaceConstruction {
#[must_use]
pub const fn legacy_cache(&self) -> Option<LegacyCache> {
match self.cache.legacy_fit_tolerance() {
Some(fit_tolerance) => Some(LegacyCache { fit_tolerance }),
None => None,
}
}
fn legacy_cache_slot_mut(&mut self) -> Option<LegacyCacheSlot<'_>> {
self.cache.legacy_cache_mut().map(LegacyCacheSlot::Optional)
}
}
cadmpeg_core::named_optional_field!(
deserialize_carrier_parameter_range,
crate::topology::IncreasingParameterInterval,
"parameter_range"
);
cadmpeg_core::named_optional_field!(deserialize_tertiary, bool, "tertiary");
cadmpeg_core::named_optional_field!(deserialize_cache, LegacyCache, "cache");
cadmpeg_core::named_optional_field!(deserialize_record, UnknownId, "record");
cadmpeg_core::named_optional_field!(
deserialize_surface_geometry_cache,
SolvedSurfaceGeometry,
"cache"
);
cadmpeg_core::named_optional_field!(
deserialize_source_object,
SourceObjectAssociation,
"source_object"
);
cadmpeg_core::named_optional_field!(
deserialize_curve_geometry_cache,
SolvedCurveGeometry,
"cache"
);
cadmpeg_core::named_optional_field!(deserialize_extra<R>, [R; 2], "extra");
cadmpeg_core::named_optional_field!(deserialize_pcurve, PcurveGeometry, "pcurve");
cadmpeg_core::named_optional_field!(deserialize_direction<V>, V, "direction");
cadmpeg_core::named_optional_field!(deserialize_surface, SurfaceId, "surface");
cadmpeg_core::named_optional_field!(deserialize_asm_extension, i64, "asm_extension");
cadmpeg_core::named_optional_field!(
deserialize_secondary_pcurve,
PcurveGeometry,
"secondary_pcurve"
);
cadmpeg_core::named_optional_field!(deserialize_endpoints<R>, [Option<R>; 2], "endpoints");
cadmpeg_core::named_optional_field!(deserialize_path<R>, LoftPathCurve<R>, "path");
cadmpeg_core::named_optional_field!(deserialize_support, G2BlendFullSupport, "support");
cadmpeg_core::named_optional_field!(deserialize_extension<R>, LoftBridgeToken<R>, "extension");
cadmpeg_core::named_optional_field!(
deserialize_rolling_ball_side_surface<S, R>,
RollingBallSupportSurface<S, R>,
"surface"
);
cadmpeg_core::named_optional_field!(
deserialize_curve<C, R>,
RollingBallSupportCurve<C, R>,
"curve"
);
cadmpeg_core::named_optional_field!(deserialize_rolling_ball_side_pcurve<P>, P, "pcurve");
cadmpeg_core::named_optional_field!(
deserialize_rolling_ball_side_secondary_pcurve<P>,
P,
"secondary_pcurve"
);
cadmpeg_core::named_optional_field!(
deserialize_rolling_ball_side_extension<P>,
RollingBallSideExtension<P>,
"extension"
);
cadmpeg_core::named_optional_field!(
deserialize_tertiary_pcurve,
PcurveGeometry,
"tertiary_pcurve"
);
cadmpeg_core::named_optional_field!(deserialize_third<V>, Box<RollingBallThirdSide<V>>, "third");
cadmpeg_core::named_optional_field!(
deserialize_radius<R, V, P>,
Box<VariableBlendValue<R, V, P>>,
"radius"
);
cadmpeg_core::named_optional_field!(
deserialize_cross_section<R, V, P>,
VariableBlendCrossSection<R, V, P>,
"cross_section"
);
cadmpeg_core::named_optional_field!(deserialize_v_lower<R>, R, "v_lower");
cadmpeg_core::named_optional_field!(
deserialize_secondary_curve<R>,
RollingBallSupportCurve<CurveId, R>,
"secondary_curve"
);
cadmpeg_core::named_optional_field!(deserialize_post_curve, CurveId, "post_curve");
cadmpeg_core::named_optional_field!(deserialize_post_pcurve, PcurveGeometry, "post_pcurve");
cadmpeg_core::named_optional_field!(deserialize_revision, PositiveI64, "revision");
cadmpeg_core::named_optional_field!(
deserialize_first_scale<R, V>,
Box<CompoundLoftScale<R, V>>,
"first_scale"
);
cadmpeg_core::named_optional_field!(
deserialize_scale<R, V>,
Box<CompoundLoftScale<R, V>>,
"scale"
);
cadmpeg_core::named_optional_field!(
deserialize_parameter_ranges<R>,
[[R; 2]; 2],
"parameter_ranges"
);
cadmpeg_core::named_optional_field!(
deserialize_parameter_range,
DirectedParameterRange,
"parameter_range"
);
cadmpeg_core::named_optional_field!(
deserialize_intcurve_support_side_pcurve,
SupportPcurve,
"pcurve"
);
cadmpeg_core::named_optional_field!(deserialize_second_flag, bool, "second_flag");
cadmpeg_core::named_optional_field!(deserialize_tail, SurfaceCurveCacheFirst<bool>, "tail");
cadmpeg_core::named_optional_field!(
deserialize_surface_curve_family_tail,
SurfaceCurveCacheFirst<ParametricSurfaceCurveFlags>,
"tail"
);
cadmpeg_core::named_optional_field!(deserialize_offset_side_support, SurfaceId, "support");
cadmpeg_core::named_optional_field!(deserialize_version, LawCurveVersionForm, "version");
cadmpeg_core::named_optional_field!(
deserialize_parameterization,
TolerantIntersectionParameterization,
"parameterization"
);
cadmpeg_core::named_optional_field!(deserialize_blend_surface, SurfaceId, "blend_surface");
cadmpeg_core::named_optional_field!(deserialize_native_kind, String, "native_kind");
cadmpeg_core::named_optional_field!(deserialize_record_bounds, RecordBounds, "record_bounds");
mod identity_rewrite;
mod serialization;