use super::nurbs::{
admit_finite_weight, admit_weight, KnotVector, NurbsCurve, NurbsError, PoleValue,
};
use super::{FitTolerance, MAX_GEOMETRY_NESTING};
use crate::ids::PcurveId;
use crate::math::{Point2, Point3};
use crate::scalar::{FiniteReal, NonZeroReal, PositiveReal};
use crate::topology::ParameterInterval;
use crate::transform::Transform2;
use crate::units::{FinitePoint2, FiniteVector, NonzeroPoint2};
use cadmpeg_core::decode::{DecodeContext, ResourceLimit};
use cadmpeg_core::CodecError;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
mod construction;
pub mod evaluator;
mod scaling_admitted;
fn finite_axis_is_nonzero(axis: FinitePoint2) -> bool {
let axis = axis.get();
axis.u.hypot(axis.v) > 0.0
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct WeightedPole2<P = Point2> {
pub point: P,
pub weight: NonZeroReal,
}
#[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 PcurveNurbsPoles<P = Point2> {
Polynomial {
points: Vec<P>,
},
Rational {
points: Vec<WeightedPole2<P>>,
},
}
impl PcurveNurbsPoles {
pub fn edit_points(
&mut self,
edit: impl FnMut(&mut Point2) -> Result<(), NurbsError>,
) -> Result<(), NurbsError> {
let mut candidate = self.clone();
candidate.apply_points(edit)?;
*self = candidate;
Ok(())
}
fn apply_points(
&mut self,
mut edit: impl FnMut(&mut Point2) -> Result<(), NurbsError>,
) -> Result<(), NurbsError> {
match self {
Self::Polynomial { points } => {
for point in points.iter_mut() {
edit(point)?;
}
}
Self::Rational { points } => {
for pole in points.iter_mut() {
edit(&mut pole.point)?;
}
}
}
Ok(())
}
}
impl PcurveNurbsPoles<FinitePoint2> {
#[must_use]
pub fn to_raw(&self) -> PcurveNurbsPoles {
let Ok(raw) = self
.clone()
.try_map_points(|point| Ok::<_, std::convert::Infallible>(point.get()));
raw
}
#[must_use]
pub fn raw_points(&self) -> Vec<Point2> {
self.points().into_iter().map(FinitePoint2::get).collect()
}
}
impl<P> PcurveNurbsPoles<P> {
pub fn from_lanes(points: Vec<P>, weights: Option<Vec<f64>>) -> Result<Self, NurbsError> {
construction::pair_pcurve_lanes(points, weights, |index, weight| {
admit_weight("pcurve poles", index, weight)
})
}
pub fn from_finite_lanes(
points: Vec<P>,
weights: Option<Vec<FiniteReal>>,
) -> Result<Self, NurbsError> {
construction::pair_pcurve_lanes(points, weights, |index, weight| {
admit_finite_weight("pcurve poles", index, weight)
})
}
pub fn from_checked_lanes(
points: Vec<P>,
weights: Option<Vec<NonZeroReal>>,
) -> Result<Self, NurbsError> {
construction::pair_pcurve_lanes(points, weights, |_, weight| Ok(weight))
}
#[must_use]
pub fn count(&self) -> usize {
match self {
Self::Polynomial { points } => points.len(),
Self::Rational { points } => points.len(),
}
}
#[must_use]
pub fn weights(&self) -> Option<Vec<f64>> {
match self {
Self::Polynomial { .. } => None,
Self::Rational { points } => {
Some(points.iter().map(|pole| pole.weight.get()).collect())
}
}
}
pub fn reverse(&mut self) {
match self {
Self::Polynomial { points } => points.reverse(),
Self::Rational { points } => points.reverse(),
}
}
fn try_map_points<Q, E>(
self,
mut point: impl FnMut(P) -> Result<Q, E>,
) -> Result<PcurveNurbsPoles<Q>, E> {
Ok(match self {
Self::Polynomial { points } => PcurveNurbsPoles::Polynomial {
points: points.into_iter().map(point).collect::<Result<_, E>>()?,
},
Self::Rational { points } => PcurveNurbsPoles::Rational {
points: points
.into_iter()
.map(|pole| {
Ok(WeightedPole2 {
point: point(pole.point)?,
weight: pole.weight,
})
})
.collect::<Result<_, E>>()?,
},
})
}
}
impl<P: Copy> PcurveNurbsPoles<P> {
#[must_use]
pub fn point_at(&self, index: usize) -> Option<P> {
match self {
Self::Polynomial { points } => points.get(index).copied(),
Self::Rational { points } => points.get(index).map(|pole| pole.point),
}
}
#[must_use]
pub fn weight_at(&self, index: usize) -> Option<f64> {
match self {
Self::Polynomial { .. } => None,
Self::Rational { points } => points.get(index).map(|pole| pole.weight.get()),
}
}
#[must_use]
pub fn points(&self) -> Vec<P> {
match self {
Self::Polynomial { points } => points.clone(),
Self::Rational { points } => points.iter().map(|pole| pole.point).collect(),
}
}
}
impl PoleValue<FinitePoint2> for Point2 {
fn admit(self) -> Option<FinitePoint2> {
FinitePoint2::new(self)
}
}
impl PoleValue<FinitePoint2> for FinitePoint2 {
fn admit(self) -> Option<FinitePoint2> {
Some(self)
}
fn admit_pcurve_poles<E>(
poles: PcurveNurbsPoles<Self>,
_convert: impl FnOnce(PcurveNurbsPoles<Self>) -> Result<PcurveNurbsPoles<FinitePoint2>, E>,
) -> Result<PcurveNurbsPoles<FinitePoint2>, E> {
Ok(poles)
}
}
impl PoleValue<FiniteReal> for f64 {
fn admit(self) -> Option<FiniteReal> {
FiniteReal::new(self)
}
}
impl PoleValue<FiniteReal> for FiniteReal {
fn admit(self) -> Option<FiniteReal> {
Some(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "LinePcurveWire")]
pub struct LinePcurve {
origin: FinitePoint2,
direction: NonzeroPoint2,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct LinePcurveWire {
origin: Point2,
direction: Point2,
}
impl LinePcurve {
pub const U_AXIS: Self = Self {
origin: FinitePoint2::ZERO,
direction: NonzeroPoint2::U_AXIS,
};
#[must_use]
pub const fn new(origin: FinitePoint2, direction: NonzeroPoint2) -> Self {
Self { origin, direction }
}
pub fn try_new(origin: Point2, direction: Point2) -> Result<Self, &'static str> {
let origin = FinitePoint2::new(origin).ok_or("LinePcurve.origin must be finite")?;
let direction = NonzeroPoint2::new(direction)
.ok_or("LinePcurve.direction must be finite with squared norm greater than epsilon")?;
Ok(Self { origin, direction })
}
#[must_use]
pub const fn origin(&self) -> &FinitePoint2 {
&self.origin
}
#[must_use]
pub const fn direction(&self) -> &NonzeroPoint2 {
&self.direction
}
}
impl TryFrom<LinePcurveWire> for LinePcurve {
type Error = &'static str;
fn try_from(wire: LinePcurveWire) -> Result<Self, Self::Error> {
Self::try_new(wire.origin, wire.direction)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "PolarHarmonicPcurveWire")]
pub struct PolarHarmonicPcurve {
radial_center: FinitePoint2,
radial_cos: FinitePoint2,
radial_sin: FinitePoint2,
axial_origin: FiniteReal,
axial_cos: FiniteReal,
axial_sin: FiniteReal,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct PolarHarmonicPcurveWire {
radial_center: Point2,
radial_cos: Point2,
radial_sin: Point2,
axial_origin: f64,
axial_cos: f64,
axial_sin: f64,
}
impl PolarHarmonicPcurve {
pub fn try_new(
radial_center: Point2,
radial_cos: Point2,
radial_sin: Point2,
axial_origin: f64,
axial_cos: f64,
axial_sin: f64,
) -> Result<Self, &'static str> {
if !(radial_cos.u.hypot(radial_cos.v) > 0.0 || radial_sin.u.hypot(radial_sin.v) > 0.0) {
return Err("PolarHarmonicPcurve.radial_cos/radial_sin must not both be zero");
}
let radial_center = FinitePoint2::new(radial_center)
.ok_or("PolarHarmonicPcurve.radial_center must be finite")?;
let radial_cos =
FinitePoint2::new(radial_cos).ok_or("PolarHarmonicPcurve.radial_cos must be finite")?;
let radial_sin =
FinitePoint2::new(radial_sin).ok_or("PolarHarmonicPcurve.radial_sin must be finite")?;
let axial_origin = FiniteReal::new(axial_origin)
.ok_or("PolarHarmonicPcurve.axial_origin must be finite")?;
let axial_cos =
FiniteReal::new(axial_cos).ok_or("PolarHarmonicPcurve.axial_cos must be finite")?;
let axial_sin =
FiniteReal::new(axial_sin).ok_or("PolarHarmonicPcurve.axial_sin must be finite")?;
Ok(Self {
radial_center,
radial_cos,
radial_sin,
axial_origin,
axial_cos,
axial_sin,
})
}
#[must_use]
pub const fn radial_center(&self) -> &FinitePoint2 {
&self.radial_center
}
#[must_use]
pub const fn radial_cos(&self) -> &FinitePoint2 {
&self.radial_cos
}
#[must_use]
pub const fn radial_sin(&self) -> &FinitePoint2 {
&self.radial_sin
}
#[must_use]
pub const fn axial_origin(&self) -> FiniteReal {
self.axial_origin
}
#[must_use]
pub const fn axial_cos(&self) -> FiniteReal {
self.axial_cos
}
#[must_use]
pub const fn axial_sin(&self) -> FiniteReal {
self.axial_sin
}
}
impl TryFrom<PolarHarmonicPcurveWire> for PolarHarmonicPcurve {
type Error = &'static str;
fn try_from(wire: PolarHarmonicPcurveWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.radial_center,
wire.radial_cos,
wire.radial_sin,
wire.axial_origin,
wire.axial_cos,
wire.axial_sin,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "SphericalGreatCirclePcurveWire")]
pub struct SphericalGreatCirclePcurve {
azimuth_origin: FiniteReal,
azimuth_rate: FiniteReal,
plane_phase: FiniteReal,
plane_slope: FiniteReal,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct SphericalGreatCirclePcurveWire {
azimuth_origin: f64,
azimuth_rate: f64,
plane_phase: f64,
plane_slope: f64,
}
impl SphericalGreatCirclePcurve {
pub fn try_new(
azimuth_origin: f64,
azimuth_rate: f64,
plane_phase: f64,
plane_slope: f64,
) -> Result<Self, &'static str> {
if azimuth_rate == 0.0 {
return Err("SphericalGreatCirclePcurve.azimuth_rate must be nonzero");
}
let azimuth_origin = FiniteReal::new(azimuth_origin)
.ok_or("SphericalGreatCirclePcurve.azimuth_origin must be finite")?;
let azimuth_rate = FiniteReal::new(azimuth_rate)
.ok_or("SphericalGreatCirclePcurve.azimuth_rate must be finite")?;
let plane_phase = FiniteReal::new(plane_phase)
.ok_or("SphericalGreatCirclePcurve.plane_phase must be finite")?;
let plane_slope = FiniteReal::new(plane_slope)
.ok_or("SphericalGreatCirclePcurve.plane_slope must be finite")?;
Ok(Self {
azimuth_origin,
azimuth_rate,
plane_phase,
plane_slope,
})
}
#[must_use]
pub const fn azimuth_origin(&self) -> FiniteReal {
self.azimuth_origin
}
#[must_use]
pub const fn azimuth_rate(&self) -> FiniteReal {
self.azimuth_rate
}
#[must_use]
pub const fn plane_phase(&self) -> FiniteReal {
self.plane_phase
}
#[must_use]
pub const fn plane_slope(&self) -> FiniteReal {
self.plane_slope
}
#[must_use]
pub fn reflected(&self, reflection: f64) -> Option<Self> {
let azimuth_origin =
FiniteReal::new(self.azimuth_origin.get() + self.azimuth_rate.get() * reflection)?;
Some(Self {
azimuth_origin,
azimuth_rate: self.azimuth_rate.negated(),
plane_phase: self.plane_phase,
plane_slope: self.plane_slope,
})
}
}
impl TryFrom<SphericalGreatCirclePcurveWire> for SphericalGreatCirclePcurve {
type Error = &'static str;
fn try_from(wire: SphericalGreatCirclePcurveWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.azimuth_origin,
wire.azimuth_rate,
wire.plane_phase,
wire.plane_slope,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "CirclePcurveWire")]
pub struct CirclePcurve {
center: FinitePoint2,
x_axis: FinitePoint2,
y_axis: FinitePoint2,
radius: PositiveReal,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct CirclePcurveWire {
center: Point2,
x_axis: Point2,
y_axis: Point2,
radius: f64,
}
impl CirclePcurve {
pub fn from_parts(
center: FinitePoint2,
x_axis: FinitePoint2,
y_axis: FinitePoint2,
radius: PositiveReal,
) -> Option<Self> {
(finite_axis_is_nonzero(x_axis) && finite_axis_is_nonzero(y_axis)).then_some(Self {
center,
x_axis,
y_axis,
radius,
})
}
pub fn try_new(
center: Point2,
x_axis: Point2,
y_axis: Point2,
radius: f64,
) -> Result<Self, &'static str> {
if x_axis.u.hypot(x_axis.v) <= 0.0 {
return Err("CirclePcurve.x_axis must be nonzero");
}
if y_axis.u.hypot(y_axis.v) <= 0.0 {
return Err("CirclePcurve.y_axis must be nonzero");
}
let center = FinitePoint2::new(center).ok_or("CirclePcurve.center must be finite")?;
let x_axis = FinitePoint2::new(x_axis).ok_or("CirclePcurve.x_axis must be finite")?;
let y_axis = FinitePoint2::new(y_axis).ok_or("CirclePcurve.y_axis must be finite")?;
let radius =
PositiveReal::new(radius).ok_or("CirclePcurve.radius must be positive and finite")?;
Ok(Self {
center,
x_axis,
y_axis,
radius,
})
}
fn scaled_isotropic(self, scale: f64) -> Result<Self, &'static str> {
let center = self.center.get();
let center = FinitePoint2::new(Point2::new(center.u * scale, center.v * scale))
.ok_or("CirclePcurve.center must be finite")?;
let radius = PositiveReal::new(self.radius.get() * scale)
.ok_or("CirclePcurve.radius must be positive and finite")?;
Ok(Self {
center,
radius,
..self
})
}
#[must_use]
pub const fn center(&self) -> &FinitePoint2 {
&self.center
}
#[must_use]
pub const fn x_axis(&self) -> &FinitePoint2 {
&self.x_axis
}
#[must_use]
pub const fn y_axis(&self) -> &FinitePoint2 {
&self.y_axis
}
#[must_use]
pub const fn radius(&self) -> PositiveReal {
self.radius
}
}
impl TryFrom<CirclePcurveWire> for CirclePcurve {
type Error = &'static str;
fn try_from(wire: CirclePcurveWire) -> Result<Self, Self::Error> {
Self::try_new(wire.center, wire.x_axis, wire.y_axis, wire.radius)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "EllipsePcurveWire")]
pub struct EllipsePcurve {
center: FinitePoint2,
x_axis: FinitePoint2,
y_axis: FinitePoint2,
major_radius: PositiveReal,
minor_radius: PositiveReal,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct EllipsePcurveWire {
center: Point2,
x_axis: Point2,
y_axis: Point2,
major_radius: f64,
minor_radius: f64,
}
impl EllipsePcurve {
pub fn from_parts(
center: FinitePoint2,
x_axis: FinitePoint2,
y_axis: FinitePoint2,
major_radius: PositiveReal,
minor_radius: PositiveReal,
) -> Option<Self> {
(finite_axis_is_nonzero(x_axis) && finite_axis_is_nonzero(y_axis)).then_some(Self {
center,
x_axis,
y_axis,
major_radius,
minor_radius,
})
}
pub fn try_new(
center: Point2,
x_axis: Point2,
y_axis: Point2,
major_radius: f64,
minor_radius: f64,
) -> Result<Self, &'static str> {
if x_axis.u.hypot(x_axis.v) <= 0.0 {
return Err("EllipsePcurve.x_axis must be nonzero");
}
if y_axis.u.hypot(y_axis.v) <= 0.0 {
return Err("EllipsePcurve.y_axis must be nonzero");
}
let center = FinitePoint2::new(center).ok_or("EllipsePcurve.center must be finite")?;
let x_axis = FinitePoint2::new(x_axis).ok_or("EllipsePcurve.x_axis must be finite")?;
let y_axis = FinitePoint2::new(y_axis).ok_or("EllipsePcurve.y_axis must be finite")?;
let major_radius = PositiveReal::new(major_radius)
.ok_or("EllipsePcurve.major_radius must be positive and finite")?;
let minor_radius = PositiveReal::new(minor_radius)
.ok_or("EllipsePcurve.minor_radius must be positive and finite")?;
Ok(Self {
center,
x_axis,
y_axis,
major_radius,
minor_radius,
})
}
fn scaled_isotropic(self, scale: f64) -> Result<Self, &'static str> {
let center = self.center.get();
let center = FinitePoint2::new(Point2::new(center.u * scale, center.v * scale))
.ok_or("EllipsePcurve.center must be finite")?;
let major_radius = PositiveReal::new(self.major_radius.get() * scale)
.ok_or("EllipsePcurve.major_radius must be positive and finite")?;
let minor_radius = PositiveReal::new(self.minor_radius.get() * scale)
.ok_or("EllipsePcurve.minor_radius must be positive and finite")?;
Ok(Self {
center,
major_radius,
minor_radius,
..self
})
}
#[must_use]
pub const fn center(&self) -> &FinitePoint2 {
&self.center
}
#[must_use]
pub const fn x_axis(&self) -> &FinitePoint2 {
&self.x_axis
}
#[must_use]
pub const fn y_axis(&self) -> &FinitePoint2 {
&self.y_axis
}
#[must_use]
pub const fn major_radius(&self) -> PositiveReal {
self.major_radius
}
#[must_use]
pub const fn minor_radius(&self) -> PositiveReal {
self.minor_radius
}
#[must_use]
pub fn reversed_about_zero(&self) -> Self {
Self {
y_axis: self.y_axis.negated(),
..*self
}
}
}
impl TryFrom<EllipsePcurveWire> for EllipsePcurve {
type Error = &'static str;
fn try_from(wire: EllipsePcurveWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.center,
wire.x_axis,
wire.y_axis,
wire.major_radius,
wire.minor_radius,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "HarmonicPcurveWire")]
pub struct HarmonicPcurve {
center: FinitePoint2,
cosine: FinitePoint2,
sine: FinitePoint2,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct HarmonicPcurveWire {
center: Point2,
cosine: Point2,
sine: Point2,
}
impl HarmonicPcurve {
pub fn try_new(center: Point2, cosine: Point2, sine: Point2) -> Result<Self, &'static str> {
if !(cosine.u.hypot(cosine.v) > 0.0 || sine.u.hypot(sine.v) > 0.0) {
return Err("HarmonicPcurve.cosine/sine must not both be zero");
}
let center = FinitePoint2::new(center).ok_or("HarmonicPcurve.center must be finite")?;
let cosine = FinitePoint2::new(cosine).ok_or("HarmonicPcurve.cosine must be finite")?;
let sine = FinitePoint2::new(sine).ok_or("HarmonicPcurve.sine must be finite")?;
Ok(Self {
center,
cosine,
sine,
})
}
#[must_use]
pub const fn center(&self) -> &FinitePoint2 {
&self.center
}
#[must_use]
pub const fn cosine(&self) -> &FinitePoint2 {
&self.cosine
}
#[must_use]
pub const fn sine(&self) -> &FinitePoint2 {
&self.sine
}
}
impl TryFrom<HarmonicPcurveWire> for HarmonicPcurve {
type Error = &'static str;
fn try_from(wire: HarmonicPcurveWire) -> Result<Self, Self::Error> {
Self::try_new(wire.center, wire.cosine, wire.sine)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "ParabolaPcurveWire")]
pub struct ParabolaPcurve {
vertex: FinitePoint2,
x_axis: FinitePoint2,
y_axis: FinitePoint2,
focal_distance: PositiveReal,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct ParabolaPcurveWire {
vertex: Point2,
x_axis: Point2,
y_axis: Point2,
focal_distance: f64,
}
impl ParabolaPcurve {
pub fn from_parts(
vertex: FinitePoint2,
x_axis: FinitePoint2,
y_axis: FinitePoint2,
focal_distance: PositiveReal,
) -> Option<Self> {
(finite_axis_is_nonzero(x_axis) && finite_axis_is_nonzero(y_axis)).then_some(Self {
vertex,
x_axis,
y_axis,
focal_distance,
})
}
pub fn try_new(
vertex: Point2,
x_axis: Point2,
y_axis: Point2,
focal_distance: f64,
) -> Result<Self, &'static str> {
if x_axis.u.hypot(x_axis.v) <= 0.0 {
return Err("ParabolaPcurve.x_axis must be nonzero");
}
if y_axis.u.hypot(y_axis.v) <= 0.0 {
return Err("ParabolaPcurve.y_axis must be nonzero");
}
let vertex = FinitePoint2::new(vertex).ok_or("ParabolaPcurve.vertex must be finite")?;
let x_axis = FinitePoint2::new(x_axis).ok_or("ParabolaPcurve.x_axis must be finite")?;
let y_axis = FinitePoint2::new(y_axis).ok_or("ParabolaPcurve.y_axis must be finite")?;
let focal_distance = PositiveReal::new(focal_distance)
.ok_or("ParabolaPcurve.focal_distance must be positive and finite")?;
Ok(Self {
vertex,
x_axis,
y_axis,
focal_distance,
})
}
#[must_use]
pub const fn vertex(&self) -> &FinitePoint2 {
&self.vertex
}
#[must_use]
pub const fn x_axis(&self) -> &FinitePoint2 {
&self.x_axis
}
#[must_use]
pub const fn y_axis(&self) -> &FinitePoint2 {
&self.y_axis
}
#[must_use]
pub const fn focal_distance(&self) -> PositiveReal {
self.focal_distance
}
#[must_use]
pub fn reversed_about_zero(&self) -> Self {
Self {
y_axis: self.y_axis.negated(),
..*self
}
}
}
impl TryFrom<ParabolaPcurveWire> for ParabolaPcurve {
type Error = &'static str;
fn try_from(wire: ParabolaPcurveWire) -> Result<Self, Self::Error> {
Self::try_new(wire.vertex, wire.x_axis, wire.y_axis, wire.focal_distance)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "HyperbolaPcurveWire")]
pub struct HyperbolaPcurve {
center: FinitePoint2,
x_axis: FinitePoint2,
y_axis: FinitePoint2,
major_radius: PositiveReal,
minor_radius: PositiveReal,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct HyperbolaPcurveWire {
center: Point2,
x_axis: Point2,
y_axis: Point2,
major_radius: f64,
minor_radius: f64,
}
impl HyperbolaPcurve {
pub fn from_parts(
center: FinitePoint2,
x_axis: FinitePoint2,
y_axis: FinitePoint2,
major_radius: PositiveReal,
minor_radius: PositiveReal,
) -> Option<Self> {
(finite_axis_is_nonzero(x_axis) && finite_axis_is_nonzero(y_axis)).then_some(Self {
center,
x_axis,
y_axis,
major_radius,
minor_radius,
})
}
pub fn try_new(
center: Point2,
x_axis: Point2,
y_axis: Point2,
major_radius: f64,
minor_radius: f64,
) -> Result<Self, &'static str> {
if x_axis.u.hypot(x_axis.v) <= 0.0 {
return Err("HyperbolaPcurve.x_axis must be nonzero");
}
if y_axis.u.hypot(y_axis.v) <= 0.0 {
return Err("HyperbolaPcurve.y_axis must be nonzero");
}
let center = FinitePoint2::new(center).ok_or("HyperbolaPcurve.center must be finite")?;
let x_axis = FinitePoint2::new(x_axis).ok_or("HyperbolaPcurve.x_axis must be finite")?;
let y_axis = FinitePoint2::new(y_axis).ok_or("HyperbolaPcurve.y_axis must be finite")?;
let major_radius = PositiveReal::new(major_radius)
.ok_or("HyperbolaPcurve.major_radius must be positive and finite")?;
let minor_radius = PositiveReal::new(minor_radius)
.ok_or("HyperbolaPcurve.minor_radius must be positive and finite")?;
Ok(Self {
center,
x_axis,
y_axis,
major_radius,
minor_radius,
})
}
fn scaled_isotropic(self, scale: f64) -> Result<Self, &'static str> {
let center = self.center.get();
let center = FinitePoint2::new(Point2::new(center.u * scale, center.v * scale))
.ok_or("HyperbolaPcurve.center must be finite")?;
let major_radius = PositiveReal::new(self.major_radius.get() * scale)
.ok_or("HyperbolaPcurve.major_radius must be positive and finite")?;
let minor_radius = PositiveReal::new(self.minor_radius.get() * scale)
.ok_or("HyperbolaPcurve.minor_radius must be positive and finite")?;
Ok(Self {
center,
major_radius,
minor_radius,
..self
})
}
#[must_use]
pub const fn center(&self) -> &FinitePoint2 {
&self.center
}
#[must_use]
pub const fn x_axis(&self) -> &FinitePoint2 {
&self.x_axis
}
#[must_use]
pub const fn y_axis(&self) -> &FinitePoint2 {
&self.y_axis
}
#[must_use]
pub const fn major_radius(&self) -> PositiveReal {
self.major_radius
}
#[must_use]
pub const fn minor_radius(&self) -> PositiveReal {
self.minor_radius
}
#[must_use]
pub fn reversed_about_zero(&self) -> Self {
Self {
y_axis: self.y_axis.negated(),
..*self
}
}
}
impl TryFrom<HyperbolaPcurveWire> for HyperbolaPcurve {
type Error = &'static str;
fn try_from(wire: HyperbolaPcurveWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.center,
wire.x_axis,
wire.y_axis,
wire.major_radius,
wire.minor_radius,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "HyperbolicPcurveWire")]
pub struct HyperbolicPcurve {
center: FinitePoint2,
cosine: FinitePoint2,
sine: FinitePoint2,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct HyperbolicPcurveWire {
center: Point2,
cosine: Point2,
sine: Point2,
}
impl HyperbolicPcurve {
pub fn try_new(center: Point2, cosine: Point2, sine: Point2) -> Result<Self, &'static str> {
if !(cosine.u.hypot(cosine.v) > 0.0 || sine.u.hypot(sine.v) > 0.0) {
return Err("HyperbolicPcurve.cosine/sine must not both be zero");
}
let center = FinitePoint2::new(center).ok_or("HyperbolicPcurve.center must be finite")?;
let cosine = FinitePoint2::new(cosine).ok_or("HyperbolicPcurve.cosine must be finite")?;
let sine = FinitePoint2::new(sine).ok_or("HyperbolicPcurve.sine must be finite")?;
Ok(Self {
center,
cosine,
sine,
})
}
#[must_use]
pub const fn center(&self) -> &FinitePoint2 {
&self.center
}
#[must_use]
pub const fn cosine(&self) -> &FinitePoint2 {
&self.cosine
}
#[must_use]
pub const fn sine(&self) -> &FinitePoint2 {
&self.sine
}
}
impl TryFrom<HyperbolicPcurveWire> for HyperbolicPcurve {
type Error = &'static str;
fn try_from(wire: HyperbolicPcurveWire) -> Result<Self, Self::Error> {
Self::try_new(wire.center, wire.cosine, wire.sine)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "TrimmedPcurveWire")]
pub struct TrimmedPcurve {
#[cfg_attr(feature = "schema", schemars(with = "[f64; 2]"))]
parameter_range: ParameterInterval,
#[serde(default = "crate::default_true")]
same_sense: bool,
basis: Box<PcurveGeometry>,
#[serde(skip)]
depth: usize,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct TrimmedPcurveWire {
parameter_range: [f64; 2],
#[serde(default = "crate::default_true")]
same_sense: bool,
basis: Box<PcurveGeometry>,
}
impl TrimmedPcurve {
pub fn from_finite_parts(
parameter_range: [FiniteReal; 2],
same_sense: bool,
basis: Box<PcurveGeometry>,
) -> Result<Self, &'static str> {
let parameter_range = ParameterInterval::from_finite_endpoints(parameter_range.into())
.map_err(|_| "TrimmedPcurve.parameter_range must be finite and ordered")?;
let depth = nesting_depth_over(&basis)
.ok_or("TrimmedPcurve.basis nests past the admitted inline basis depth")?;
Ok(Self {
parameter_range,
same_sense,
basis,
depth,
})
}
pub fn try_new(
parameter_range: [f64; 2],
same_sense: bool,
basis: Box<PcurveGeometry>,
) -> Result<Self, &'static str> {
let first = FiniteReal::new(parameter_range[0])
.ok_or("TrimmedPcurve.parameter_range must be finite and ordered")?;
let last = FiniteReal::new(parameter_range[1])
.ok_or("TrimmedPcurve.parameter_range must be finite and ordered")?;
Self::from_finite_parts([first, last], same_sense, basis)
}
#[must_use]
pub const fn parameter_range(&self) -> ParameterInterval {
self.parameter_range
}
#[must_use]
pub const fn same_sense(&self) -> bool {
self.same_sense
}
#[must_use]
pub const fn basis(&self) -> &PcurveGeometry {
&self.basis
}
}
impl TryFrom<TrimmedPcurveWire> for TrimmedPcurve {
type Error = &'static str;
fn try_from(wire: TrimmedPcurveWire) -> Result<Self, Self::Error> {
Self::try_new(wire.parameter_range, wire.same_sense, wire.basis)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "OffsetPcurveWire")]
pub struct OffsetPcurve {
distance: FiniteReal,
basis: Box<PcurveGeometry>,
#[serde(skip)]
depth: usize,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct OffsetPcurveWire {
distance: f64,
basis: Box<PcurveGeometry>,
}
impl OffsetPcurve {
pub fn try_new(distance: f64, basis: Box<PcurveGeometry>) -> Result<Self, &'static str> {
let distance = FiniteReal::new(distance).ok_or("OffsetPcurve.distance must be finite")?;
Self::from_finite_parts(distance, basis)
}
pub fn from_finite_parts(
distance: FiniteReal,
basis: Box<PcurveGeometry>,
) -> Result<Self, &'static str> {
let depth = nesting_depth_over(&basis)
.ok_or("OffsetPcurve.basis nests past the admitted inline basis depth")?;
Ok(Self {
distance,
basis,
depth,
})
}
#[must_use]
pub const fn distance(&self) -> FiniteReal {
self.distance
}
#[must_use]
pub const fn basis(&self) -> &PcurveGeometry {
&self.basis
}
}
impl TryFrom<OffsetPcurveWire> for OffsetPcurve {
type Error = &'static str;
fn try_from(wire: OffsetPcurveWire) -> Result<Self, Self::Error> {
Self::try_new(wire.distance, wire.basis)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum PcurveGeometry {
Line(LinePcurve),
PolarHarmonic(PolarHarmonicPcurve),
PolarNurbs {
#[serde(flatten)]
#[cfg_attr(feature = "schema", schemars(flatten, with = "PolarPcurveNurbsWire"))]
nurbs: PolarPcurveNurbs,
},
SphericalGreatCircle(SphericalGreatCirclePcurve),
Circle(CirclePcurve),
Ellipse(EllipsePcurve),
Harmonic(HarmonicPcurve),
Parabola(ParabolaPcurve),
Hyperbola(HyperbolaPcurve),
Hyperbolic(HyperbolicPcurve),
Nurbs {
#[serde(flatten)]
#[cfg_attr(feature = "schema", schemars(flatten))]
nurbs: PcurveNurbs,
},
Transformed(PlacedPcurve),
Trimmed(TrimmedPcurve),
Offset(OffsetPcurve),
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct PolarNurbsPole<P = Point2, S = f64> {
pub radial: P,
pub axial: S,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PolarPcurveNurbs {
degree: u32,
knots: KnotVector,
poles: PolarNurbsPoles<FinitePoint2, FiniteReal>,
periodic: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct WeightedPolarNurbsPole<P = Point2, S = f64> {
pub radial: P,
pub axial: S,
pub weight: NonZeroReal,
}
#[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 PolarNurbsPoles<P = Point2, S = f64> {
Polynomial {
poles: Vec<PolarNurbsPole<P, S>>,
},
Rational {
poles: Vec<WeightedPolarNurbsPole<P, S>>,
},
}
impl PolarNurbsPoles<FinitePoint2, FiniteReal> {
#[must_use]
pub fn to_raw(&self) -> PolarNurbsPoles {
let Ok(raw) = self.clone().try_map_poles(|radial, axial| {
Ok::<_, std::convert::Infallible>((radial.get(), axial.get()))
});
raw
}
}
impl<P, S> PolarNurbsPoles<P, S> {
pub fn from_lanes(
poles: Vec<PolarNurbsPole<P, S>>,
weights: Option<Vec<f64>>,
) -> Result<Self, NurbsError> {
construction::pair_polar_lanes(poles, weights, |index, weight| {
admit_weight("polar poles", index, weight)
})
}
pub fn from_checked_lanes(
poles: Vec<PolarNurbsPole<P, S>>,
weights: Option<Vec<NonZeroReal>>,
) -> Result<Self, NurbsError> {
construction::pair_polar_lanes(poles, weights, |_, weight| Ok(weight))
}
#[must_use]
pub fn count(&self) -> usize {
match self {
Self::Polynomial { poles } => poles.len(),
Self::Rational { poles } => poles.len(),
}
}
#[must_use]
pub fn weights(&self) -> Option<Vec<f64>> {
match self {
Self::Polynomial { .. } => None,
Self::Rational { poles } => Some(poles.iter().map(|pole| pole.weight.get()).collect()),
}
}
pub fn reverse(&mut self) {
match self {
Self::Polynomial { poles } => poles.reverse(),
Self::Rational { poles } => poles.reverse(),
}
}
fn try_map_poles<Q, T, E>(
self,
mut pole: impl FnMut(P, S) -> Result<(Q, T), E>,
) -> Result<PolarNurbsPoles<Q, T>, E> {
Ok(match self {
Self::Polynomial { poles } => PolarNurbsPoles::Polynomial {
poles: poles
.into_iter()
.map(|row| {
let (radial, axial) = pole(row.radial, row.axial)?;
Ok(PolarNurbsPole { radial, axial })
})
.collect::<Result<_, E>>()?,
},
Self::Rational { poles } => PolarNurbsPoles::Rational {
poles: poles
.into_iter()
.map(|row| {
let (radial, axial) = pole(row.radial, row.axial)?;
Ok(WeightedPolarNurbsPole {
radial,
axial,
weight: row.weight,
})
})
.collect::<Result<_, E>>()?,
},
})
}
}
impl<P: Copy, S: Copy> PolarNurbsPoles<P, S> {
#[must_use]
pub fn poles(&self) -> Vec<PolarNurbsPole<P, S>> {
match self {
Self::Polynomial { poles } => poles.clone(),
Self::Rational { poles } => poles
.iter()
.map(|pole| PolarNurbsPole {
radial: pole.radial,
axial: pole.axial,
})
.collect(),
}
}
}
impl PolarPcurveNurbs {
pub fn new<P: PoleValue<FinitePoint2>, S: PoleValue<FiniteReal>, K: super::nurbs::KnotValue>(
degree: u32,
knots: K,
poles: PolarNurbsPoles<P, S>,
periodic: bool,
) -> Result<Self, NurbsError> {
construction::build_polar(degree, knots, poles, periodic)
}
pub const fn degree(&self) -> u32 {
self.degree
}
pub fn knots(&self) -> &KnotVector {
&self.knots
}
pub fn from_lanes<
P: PoleValue<FinitePoint2>,
S: PoleValue<FiniteReal>,
K: super::nurbs::KnotValue,
>(
degree: u32,
knots: K,
poles: Vec<PolarNurbsPole<P, S>>,
weights: Option<Vec<f64>>,
periodic: bool,
) -> Result<Self, NurbsError> {
let poles = construction::pair_polar_lanes(poles, weights, |index, weight| {
admit_weight("polar poles", index, weight)
})?;
construction::build_polar(degree, knots, poles, periodic)
}
pub fn from_checked_lanes<
P: PoleValue<FinitePoint2>,
S: PoleValue<FiniteReal>,
K: super::nurbs::KnotValue,
>(
degree: u32,
knots: K,
poles: Vec<PolarNurbsPole<P, S>>,
weights: Option<Vec<NonZeroReal>>,
periodic: bool,
) -> Result<Self, NurbsError> {
let poles = construction::pair_polar_lanes(poles, weights, |_, weight| Ok(weight))?;
construction::build_polar(degree, knots, poles, periodic)
}
pub fn poles(&self) -> Vec<PolarNurbsPole<FinitePoint2, FiniteReal>> {
self.poles.poles()
}
#[must_use]
pub fn radial_control_points(&self) -> Vec<FinitePoint2> {
self.poles().into_iter().map(|pole| pole.radial).collect()
}
#[must_use]
pub fn axial_control_values(&self) -> Vec<FiniteReal> {
self.poles().into_iter().map(|pole| pole.axial).collect()
}
pub const fn pole_rows(&self) -> &PolarNurbsPoles<FinitePoint2, FiniteReal> {
&self.poles
}
pub fn weights(&self) -> Option<Vec<NonZeroReal>> {
match &self.poles {
PolarNurbsPoles::Polynomial { .. } => None,
PolarNurbsPoles::Rational { poles } => {
Some(poles.iter().map(|pole| pole.weight).collect())
}
}
}
pub const fn periodic(&self) -> bool {
self.periodic
}
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct PolarPcurveNurbsWire {
degree: u32,
knots: Vec<f64>,
poles: PolarNurbsPoles,
#[serde(default)]
periodic: bool,
}
impl Serialize for PolarPcurveNurbs {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
PolarPcurveNurbsWire {
degree: self.degree,
knots: self.knots.to_vec(),
poles: self.poles.to_raw(),
periodic: self.periodic,
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for PolarPcurveNurbs {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let wire = PolarPcurveNurbsWire::deserialize(deserializer)?;
construction::build_polar(wire.degree, wire.knots, wire.poles, wire.periodic)
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct PcurveNurbs {
degree: u32,
#[cfg_attr(feature = "schema", schemars(with = "Vec<f64>"))]
knots: KnotVector,
#[cfg_attr(feature = "schema", schemars(with = "PcurveNurbsPoles"))]
poles: PcurveNurbsPoles<FinitePoint2>,
#[serde(default)]
periodic: bool,
}
impl PcurveNurbs {
pub fn try_map_control_points_in_place<E>(
&mut self,
mut map: impl FnMut(FinitePoint2) -> Result<FinitePoint2, E>,
) -> Result<(), E> {
match &mut self.poles {
PcurveNurbsPoles::Polynomial { points } => {
for point in points {
*point = map(*point)?;
}
}
PcurveNurbsPoles::Rational { points } => {
for pole in points {
pole.point = map(pole.point)?;
}
}
}
Ok(())
}
pub(crate) fn scale_points(&mut self, scale: PositiveReal) -> Result<(), NurbsError> {
fn scale_point(point: &mut FinitePoint2, scale: PositiveReal) -> Result<(), NurbsError> {
let raw = point.get();
*point = FinitePoint2::new(Point2::new(raw.u * scale.get(), raw.v * scale.get()))
.ok_or_else(|| {
NurbsError::Structure("control_points contains a non-finite point".to_owned())
})?;
Ok(())
}
match &mut self.poles {
PcurveNurbsPoles::Polynomial { points } => {
for point in points {
scale_point(point, scale)?;
}
}
PcurveNurbsPoles::Rational { points } => {
for pole in points {
scale_point(&mut pole.point, scale)?;
}
}
}
Ok(())
}
pub fn new<P: PoleValue<FinitePoint2>, K: super::nurbs::KnotValue>(
degree: u32,
knots: K,
poles: PcurveNurbsPoles<P>,
periodic: bool,
) -> Result<Self, NurbsError> {
construction::build_pcurve(degree, knots, poles, periodic)
}
pub fn lift(&self, mut lift: impl FnMut(Point2) -> Point3) -> Result<NurbsCurve, NurbsError> {
let points: Vec<Point3> = self
.poles
.points()
.into_iter()
.map(|point| lift(point.get()))
.collect();
let poles = super::nurbs::pair_curve_lanes(points, self.weights(), |_, weight| Ok(weight))?;
NurbsCurve::new(self.degree, self.knots.clone(), poles, self.periodic)
}
pub const fn degree(&self) -> u32 {
self.degree
}
pub fn knots(&self) -> &KnotVector {
&self.knots
}
pub fn from_lanes<P: PoleValue<FinitePoint2>, K: super::nurbs::KnotValue>(
degree: u32,
knots: K,
control_points: Vec<P>,
weights: Option<Vec<f64>>,
periodic: bool,
) -> Result<Self, NurbsError> {
let poles = construction::pair_pcurve_lanes(control_points, weights, |index, weight| {
admit_weight("pcurve poles", index, weight)
})?;
construction::build_pcurve(degree, knots, poles, periodic)
}
pub fn from_finite_lanes(
degree: u32,
knots: Vec<FiniteReal>,
control_points: Vec<FinitePoint2>,
weights: Option<Vec<FiniteReal>>,
periodic: bool,
) -> Result<Self, NurbsError> {
let poles = construction::pair_pcurve_lanes(control_points, weights, |index, weight| {
admit_finite_weight("pcurve poles", index, weight)
})?;
construction::build_pcurve(degree, knots, poles, periodic)
}
pub fn from_checked_lanes<P: PoleValue<FinitePoint2>, K: super::nurbs::KnotValue>(
degree: u32,
knots: K,
control_points: Vec<P>,
weights: Option<Vec<NonZeroReal>>,
periodic: bool,
) -> Result<Self, NurbsError> {
let poles =
construction::pair_pcurve_lanes(control_points, weights, |_, weight| Ok(weight))?;
construction::build_pcurve(degree, knots, poles, periodic)
}
pub const fn pole_rows(&self) -> &PcurveNurbsPoles<FinitePoint2> {
&self.poles
}
#[must_use]
pub fn control_points(&self) -> Vec<FinitePoint2> {
self.poles.points()
}
pub fn replace_admitted_control_points(&mut self, positions: &[FinitePoint2]) -> bool {
let count = match &self.poles {
PcurveNurbsPoles::Polynomial { points } => points.len(),
PcurveNurbsPoles::Rational { points } => points.len(),
};
if positions.len() != count {
return false;
}
match &mut self.poles {
PcurveNurbsPoles::Polynomial { points } => {
for (point, position) in points.iter_mut().zip(positions) {
*point = *position;
}
}
PcurveNurbsPoles::Rational { points } => {
for (pole, position) in points.iter_mut().zip(positions) {
pole.point = *position;
}
}
}
true
}
pub fn try_map_control_points<E>(
&mut self,
map: impl Fn(usize, FinitePoint2) -> Result<FinitePoint2, E>,
) -> Result<(), E> {
match &self.poles {
PcurveNurbsPoles::Polynomial { points } => {
for (index, point) in points.iter().copied().enumerate() {
map(index, point)?;
}
}
PcurveNurbsPoles::Rational { points } => {
for (index, pole) in points.iter().enumerate() {
map(index, pole.point)?;
}
}
}
match &mut self.poles {
PcurveNurbsPoles::Polynomial { points } => {
for (index, point) in points.iter_mut().enumerate() {
*point = map(index, *point)?;
}
}
PcurveNurbsPoles::Rational { points } => {
for (index, pole) in points.iter_mut().enumerate() {
pole.point = map(index, pole.point)?;
}
}
}
Ok(())
}
pub fn weights(&self) -> Option<Vec<NonZeroReal>> {
match &self.poles {
PcurveNurbsPoles::Polynomial { .. } => None,
PcurveNurbsPoles::Rational { points } => {
Some(points.iter().map(|pole| pole.weight).collect())
}
}
}
pub const fn periodic(&self) -> bool {
self.periodic
}
pub fn reverse_parameterization(&mut self) {
self.knots.reverse_negated();
self.poles.reverse();
}
}
impl<'de> Deserialize<'de> for PcurveNurbs {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Wire {
degree: u32,
knots: Vec<f64>,
poles: PcurveNurbsPoles,
#[serde(default)]
periodic: bool,
}
let wire = Wire::deserialize(deserializer)?;
construction::build_pcurve(wire.degree, wire.knots, wire.poles, wire.periodic)
.map_err(serde::de::Error::custom)
}
}
fn nesting_depth_over(basis: &PcurveGeometry) -> Option<usize> {
basis
.nesting_depth()
.checked_add(1)
.filter(|depth| *depth <= MAX_GEOMETRY_NESTING)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "PlacedPcurveWire")]
pub struct PlacedPcurve {
basis: Box<PcurveGeometry>,
transform: Transform2,
#[serde(skip)]
depth: usize,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct PlacedPcurveWire {
basis: Box<PcurveGeometry>,
transform: Transform2,
}
impl PlacedPcurve {
pub fn try_new(
basis: Box<PcurveGeometry>,
transform: Transform2,
) -> Result<Self, &'static str> {
let depth = nesting_depth_over(&basis)
.ok_or("PlacedPcurve.basis nests past the admitted inline basis depth")?;
Ok(Self {
basis,
transform,
depth,
})
}
#[must_use]
pub const fn basis(&self) -> &PcurveGeometry {
&self.basis
}
#[must_use]
pub const fn transform(&self) -> &Transform2 {
&self.transform
}
}
impl TryFrom<PlacedPcurveWire> for PlacedPcurve {
type Error = &'static str;
fn try_from(wire: PlacedPcurveWire) -> Result<Self, Self::Error> {
Self::try_new(wire.basis, wire.transform)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum PcurveCoordinateScaleError {
#[error("{0}")]
Invalid(String),
#[error("resource refusal: {0:?}")]
Resource(cadmpeg_core::decode::ResourceLimit),
}
impl From<CodecError> for PcurveCoordinateScaleError {
fn from(error: CodecError) -> Self {
match error {
CodecError::ResourceLimit(limit) => Self::Resource(limit),
CodecError::Malformed(message) => Self::Invalid(message),
error => Self::Invalid(error.to_string()),
}
}
}
impl PcurveGeometry {
#[must_use]
pub(crate) const fn nesting_depth(&self) -> usize {
match self {
Self::Transformed(placed) => placed.depth,
Self::Trimmed(trimmed) => trimmed.depth,
Self::Offset(offset) => offset.depth,
Self::Line(_)
| Self::PolarHarmonic(_)
| Self::PolarNurbs { .. }
| Self::SphericalGreatCircle(_)
| Self::Circle(_)
| Self::Ellipse(_)
| Self::Harmonic(_)
| Self::Parabola(_)
| Self::Hyperbola(_)
| Self::Hyperbolic(_)
| Self::Nurbs { .. } => 0,
}
}
pub fn try_scale_coordinates(
&mut self,
scales: [f64; 2],
) -> Result<(), PcurveCoordinateScaleError> {
let arena = cadmpeg_core::decode::DecodeArena::new();
let policy = cadmpeg_core::decode::DecodePolicy::default();
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy)
.map_err(PcurveCoordinateScaleError::from)?;
let candidate = match self
.clone()
.scaled_coordinates_owned(&ctx, scales)
.map_err(PcurveCoordinateScaleError::from)?
{
Ok(candidate) => candidate,
Err(message) => return Err(PcurveCoordinateScaleError::Invalid(message.to_owned())),
};
*self = candidate;
Ok(())
}
pub fn line_parameters(
&self,
ctx: &DecodeContext<'_>,
) -> Result<Option<(Point2, Point2)>, ResourceLimit> {
let _depth = ctx.enter_nested_limit("pcurve line parameter nesting")?;
Ok(match self {
Self::Line(line_pcurve) => {
let origin = line_pcurve.origin().as_raw();
let direction = line_pcurve.direction().as_raw();
Some((*origin, *direction))
}
Self::Transformed(placed) => {
let Some((origin, direction)) = placed.basis().line_parameters(ctx)? else {
return Ok(None);
};
let transform = placed.transform();
Some((
transform.apply_point(origin),
transform.apply_vector(direction),
))
}
Self::Trimmed(trimmed_pcurve) => {
let basis = trimmed_pcurve.basis();
basis.line_parameters(ctx)?
}
Self::PolarHarmonic(_) => None,
Self::PolarNurbs { .. } => None,
Self::SphericalGreatCircle(_) => None,
Self::Circle(_) => None,
Self::Ellipse(_) => None,
Self::Harmonic(_) => None,
Self::Parabola(_) => None,
Self::Hyperbola(_) => None,
Self::Hyperbolic(_) => None,
Self::Nurbs { .. } => None,
Self::Offset(_) => None,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Pcurve {
pub id: PcurveId,
pub geometry: PcurveGeometry,
#[serde(default)]
pub metadata: PcurveMetadata,
}
impl Pcurve {
pub fn wrapper_reversed(&self) -> Option<bool> {
self.metadata.wrapper_reversed()
}
pub fn native_tail_flags(&self) -> Option<[bool; 4]> {
self.metadata.native_tail_flags()
}
pub fn parameter_range(&self) -> Option<FiniteVector<2>> {
self.metadata.parameter_range()
}
pub fn fit_tolerance(&self) -> Option<FitTolerance> {
self.metadata.fit_tolerance()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "source", rename_all = "snake_case", deny_unknown_fields)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum PcurveMetadata {
AsmInline {
form: PcurveInlineForm,
},
General {
#[serde(default)]
form: PcurveGeneralForm,
},
}
impl Default for PcurveMetadata {
fn default() -> Self {
Self::General {
form: PcurveGeneralForm::default(),
}
}
}
impl PcurveMetadata {
pub const NON_FINITE_PARAMETER_RANGE: &'static str =
"pcurve parameter_range endpoints must be finite";
pub const INVALID_FIT_TOLERANCE: &'static str =
"pcurve fit_tolerance must be finite and non-negative";
#[must_use]
pub const fn general(
wrapper_reversed: Option<bool>,
parameter_range: Option<FiniteVector<2>>,
fit_tolerance: Option<FitTolerance>,
) -> Self {
Self::General {
form: PcurveGeneralForm::new(wrapper_reversed, parameter_range, fit_tolerance),
}
}
pub fn wrapper_reversed(&self) -> Option<bool> {
match self {
Self::AsmInline { form: inline } => Some(inline.wrapper_reversed),
Self::General { form: general } => general.wrapper_reversed,
}
}
pub fn native_tail_flags(&self) -> Option<[bool; 4]> {
match self {
Self::AsmInline { form: inline } => Some(inline.native_tail_flags),
Self::General { .. } => None,
}
}
pub fn parameter_range(&self) -> Option<FiniteVector<2>> {
match self {
Self::AsmInline { form: inline } => Some(inline.parameter_range),
Self::General { form: general } => general.parameter_range,
}
}
pub fn fit_tolerance(&self) -> Option<FitTolerance> {
match self {
Self::AsmInline { form: inline } => Some(inline.fit_tolerance()),
Self::General { form: general } => general.fit_tolerance(),
}
}
}
fn admit_pcurve_parameter_range(range: [f64; 2]) -> Result<FiniteVector<2>, &'static str> {
FiniteVector::new(range).ok_or(PcurveMetadata::NON_FINITE_PARAMETER_RANGE)
}
fn admit_pcurve_fit_tolerance(value: f64) -> Result<FitTolerance, &'static str> {
FitTolerance::try_new(value).map_err(|_| PcurveMetadata::INVALID_FIT_TOLERANCE)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "PcurveInlineFormWire")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct PcurveInlineForm {
pub wrapper_reversed: bool,
pub native_tail_flags: [bool; 4],
#[cfg_attr(feature = "schema", schemars(with = "[f64; 2]"))]
parameter_range: FiniteVector<2>,
fit_tolerance: FitTolerance,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
struct PcurveInlineFormWire {
wrapper_reversed: bool,
native_tail_flags: [bool; 4],
parameter_range: [f64; 2],
fit_tolerance: f64,
}
impl TryFrom<PcurveInlineFormWire> for PcurveInlineForm {
type Error = &'static str;
fn try_from(wire: PcurveInlineFormWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.wrapper_reversed,
wire.native_tail_flags,
wire.parameter_range,
wire.fit_tolerance,
)
}
}
impl PcurveInlineForm {
#[must_use]
pub const fn new(
wrapper_reversed: bool,
native_tail_flags: [bool; 4],
parameter_range: FiniteVector<2>,
fit_tolerance: FitTolerance,
) -> Self {
Self {
wrapper_reversed,
native_tail_flags,
parameter_range,
fit_tolerance,
}
}
pub(crate) fn try_new(
wrapper_reversed: bool,
native_tail_flags: [bool; 4],
parameter_range: [f64; 2],
fit_tolerance: f64,
) -> Result<Self, &'static str> {
Ok(Self::new(
wrapper_reversed,
native_tail_flags,
admit_pcurve_parameter_range(parameter_range)?,
admit_pcurve_fit_tolerance(fit_tolerance)?,
))
}
#[must_use]
pub const fn fit_tolerance(&self) -> FitTolerance {
self.fit_tolerance
}
pub fn set_fit_tolerance(&mut self, value: FitTolerance) {
self.fit_tolerance = value;
}
#[must_use]
pub const fn parameter_range(&self) -> FiniteVector<2> {
self.parameter_range
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "PcurveGeneralFormWire")]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct PcurveGeneralForm {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_wrapper_reversed"
)]
pub wrapper_reversed: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_parameter_range"
)]
#[cfg_attr(feature = "schema", schemars(with = "Option<[f64; 2]>"))]
parameter_range: Option<FiniteVector<2>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
fit_tolerance: Option<FitTolerance>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
struct PcurveGeneralFormWire {
#[serde(default, deserialize_with = "deserialize_wrapper_reversed")]
wrapper_reversed: Option<bool>,
#[serde(default, deserialize_with = "deserialize_parameter_range")]
parameter_range: Option<[f64; 2]>,
#[serde(
default,
deserialize_with = "deserialize_pcurve_general_form_wire_fit_tolerance"
)]
fit_tolerance: Option<f64>,
}
impl TryFrom<PcurveGeneralFormWire> for PcurveGeneralForm {
type Error = &'static str;
fn try_from(wire: PcurveGeneralFormWire) -> Result<Self, Self::Error> {
Self::try_new(
wire.wrapper_reversed,
wire.parameter_range,
wire.fit_tolerance,
)
}
}
impl PcurveGeneralForm {
#[must_use]
pub const fn new(
wrapper_reversed: Option<bool>,
parameter_range: Option<FiniteVector<2>>,
fit_tolerance: Option<FitTolerance>,
) -> Self {
Self {
wrapper_reversed,
parameter_range,
fit_tolerance,
}
}
pub(crate) fn try_new(
wrapper_reversed: Option<bool>,
parameter_range: Option<[f64; 2]>,
fit_tolerance: Option<f64>,
) -> Result<Self, &'static str> {
Ok(Self::new(
wrapper_reversed,
parameter_range
.map(admit_pcurve_parameter_range)
.transpose()?,
fit_tolerance.map(admit_pcurve_fit_tolerance).transpose()?,
))
}
#[must_use]
pub fn fit_tolerance(&self) -> Option<FitTolerance> {
self.fit_tolerance
}
pub fn set_fit_tolerance(&mut self, value: Option<FitTolerance>) {
self.fit_tolerance = value;
}
#[must_use]
pub const fn parameter_range(&self) -> Option<FiniteVector<2>> {
self.parameter_range
}
}
cadmpeg_core::named_optional_field!(deserialize_wrapper_reversed, bool, "wrapper_reversed");
cadmpeg_core::named_optional_field!(deserialize_parameter_range, [f64; 2], "parameter_range");
cadmpeg_core::named_optional_field!(
deserialize_pcurve_general_form_wire_fit_tolerance,
f64,
"fit_tolerance"
);
#[cfg(test)]
mod tests;
mod identity_rewrite;