use std::collections::{BTreeMap, HashSet};
use crate::assets::AssetId;
use crate::ids::{
BodyId, CurveId, EdgeId, FaceId, FeatureInputTopologyId, FeatureResultTopologyId,
HistoricalBodyId, HistoricalEdgeId, HistoricalFaceId, HistoricalVertexId, OccurrenceId, SubdId,
VertexId,
};
use crate::math::{Point2, Point3, Vector3};
use crate::products::JointId;
use crate::scalar::{
Angle, FiniteReal, Fraction, InteriorAngle, Length, NonNegativeLength, NonZeroLength,
NonZeroReal, PositiveAngle, PositiveLength, PositiveReal, SlopeAngle,
};
use crate::transform::Transform;
use crate::units::{FinitePoint2, FiniteVector, UnitVector3};
use cadmpeg_core::text::NonBlankString;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
macro_rules! selection_field_deserializer {
($name:ident, $field:literal) => {
fn $name<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: serde::Deserializer<'de>,
T: Deserialize<'de>,
{
T::deserialize(deserializer)
.map_err(|error| serde::de::Error::custom(format!("{}: {error}", $field)))
}
};
}
mod body_selection;
mod decode_clone;
mod membership;
mod selection_overlap;
pub mod edge_treatments;
use edge_treatments::{ChamferGroup, FilletGroup, FullRoundFilletGroup, RadiusSpec};
pub mod holes;
use holes::{HoleBottom, HolePlacement, HoleProfileFilter, HoleShape};
pub mod patterns;
use patterns::{PatternKind, PatternSeed};
macro_rules! checked_feature_geometry {
($(#[$meta:meta])* $name:ident, $raw:ident, $value:ident, $valid:expr, $error:literal $(, $getter:ident)*) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct $name($raw);
rewrite_scalar!($name);
impl $name {
pub fn new($value: $raw) -> Option<Self> {
($valid).then_some(Self($value))
}
$(checked_feature_geometry!(@getter $getter, $raw);)*
}
impl PartialEq<$raw> for $name {
fn eq(&self, other: &$raw) -> bool { self.0 == *other }
}
impl std::ops::Deref for $name {
type Target = $raw;
fn deref(&self) -> &$raw { &self.0 }
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = $raw::deserialize(deserializer)?;
Self::try_from(value).map_err(serde::de::Error::custom)
}
}
impl TryFrom<$raw> for $name {
type Error = &'static str;
fn try_from(value: $raw) -> Result<Self, Self::Error> {
Self::new(value).ok_or($error)
}
}
impl From<$name> for $raw {
fn from(value: $name) -> Self { value.0 }
}
};
(@getter get, $raw:ident) => {
pub const fn get(self) -> $raw { self.0 }
};
(@getter as_raw, $raw:ident) => {
pub const fn as_raw(&self) -> &$raw { &self.0 }
};
}
checked_feature_geometry!(
FinitePoint3, Point3, value,
value.is_finite(),
"FinitePoint3 coordinates must be finite", get, as_raw
);
impl FinitePoint3 {
pub const ZERO: Self = Self(Point3 {
x: 0.0,
y: 0.0,
z: 0.0,
});
#[must_use]
pub const fn from_coordinates(x: FiniteReal, y: FiniteReal, z: FiniteReal) -> Self {
Self(Point3 {
x: x.get(),
y: y.get(),
z: z.get(),
})
}
#[must_use]
pub fn negated(self) -> Self {
Self(Point3::new(-self.0.x, -self.0.y, -self.0.z))
}
#[must_use]
pub fn scaled(self, scale: PositiveReal) -> Option<Self> {
let scale = scale.get();
Self::new(Point3::new(
self.0.x * scale,
self.0.y * scale,
self.0.z * scale,
))
}
pub(crate) fn array<const N: usize>(values: [Point3; N]) -> Option<[Self; N]> {
values
.iter()
.all(Point3::is_finite)
.then(|| values.map(Self))
}
#[must_use]
pub fn raw_array<const N: usize>(values: [Self; N]) -> [Point3; N] {
values.map(Self::get)
}
}
impl From<FiniteVector<3>> for FinitePoint3 {
fn from(value: FiniteVector<3>) -> Self {
let [x, y, z] = value.get();
Self(Point3::new(x, y, z))
}
}
checked_feature_geometry!(
FiniteVector3, Vector3, value,
value.is_finite(),
"FiniteVector3 components must be finite", get, as_raw
);
impl FiniteVector3 {
pub const ZERO: Self = Self(Vector3 {
x: 0.0,
y: 0.0,
z: 0.0,
});
#[must_use]
pub const fn from_components(x: FiniteReal, y: FiniteReal, z: FiniteReal) -> Self {
Self(Vector3 {
x: x.get(),
y: y.get(),
z: z.get(),
})
}
#[must_use]
pub const fn as_point(self) -> FinitePoint3 {
FinitePoint3(Point3 {
x: self.0.x,
y: self.0.y,
z: self.0.z,
})
}
#[must_use]
pub fn negated(self) -> Self {
Self(Vector3::new(-self.0.x, -self.0.y, -self.0.z))
}
pub(crate) fn array<const N: usize>(values: [Vector3; N]) -> Option<[Self; N]> {
values
.iter()
.all(Vector3::is_finite)
.then(|| values.map(Self))
}
#[must_use]
pub fn raw_array<const N: usize>(values: [Self; N]) -> [Vector3; N] {
values.map(Self::get)
}
}
checked_feature_geometry!(
FeatureDirection3, Vector3, value,
value.dot(value).is_finite() && value.dot(value) > 0.0,
"FeatureDirection3 norm must be finite and nonzero", get
);
impl From<UnitVector3> for FiniteVector3 {
fn from(value: UnitVector3) -> Self {
Self(*value.as_raw())
}
}
impl UnitVector3 {
#[must_use]
pub fn finite_cross(self, other: UnitVector3) -> FiniteVector3 {
FiniteVector3(self.as_raw().cross(*other.as_raw()))
}
}
impl Transform {
#[must_use]
pub fn translation(self) -> FiniteVector3 {
let rows = self.affine_rows();
FiniteVector3(Vector3::new(rows[0][3], rows[1][3], rows[2][3]))
}
#[must_use]
pub fn linear_columns(self) -> [FiniteVector3; 3] {
let rows = self.affine_rows();
std::array::from_fn(|column| {
FiniteVector3(Vector3::new(
rows[0][column],
rows[1][column],
rows[2][column],
))
})
}
}
impl From<FeatureDirection3> for FiniteVector3 {
fn from(value: FeatureDirection3) -> Self {
Self(value.0)
}
}
impl From<UnitVector3> for FeatureDirection3 {
fn from(value: UnitVector3) -> Self {
Self(*value.as_raw())
}
}
impl FeatureDirection3 {
pub const Z_AXIS: Self = Self(Vector3 {
x: 0.0,
y: 0.0,
z: 1.0,
});
#[must_use]
pub fn reversed(self) -> Self {
Self(Vector3::new(-self.0.x, -self.0.y, -self.0.z))
}
#[must_use]
pub fn from_unit_without_small_components(direction: UnitVector3) -> Self {
const EPS_CANONICAL_COMPONENT: f64 = 1.0e-12;
let component = |value: f64| {
if value.abs() <= EPS_CANONICAL_COMPONENT {
0.0
} else {
value
}
};
let value = direction.as_raw();
Self(Vector3::new(
component(value.x),
component(value.y),
component(value.z),
))
}
}
checked_feature_geometry!(
FeatureRigidPlacement, Transform, value, value.is_proper_rigid(),
"FeatureRigidPlacement must be a finite right-handed rigid transform"
);
impl FeatureRigidPlacement {
pub fn identity() -> Self {
Self(Transform::identity())
}
#[must_use]
pub fn with_translation(self, translation: FiniteVector3) -> Self {
Self(self.0.with_translation(translation))
}
#[must_use]
pub fn scaled_translation(self, scale: PositiveReal) -> Option<Self> {
Some(Self(self.0.scaled_translation(scale)?))
}
}
const EPS_FEATURE_UNIT_FRAME: f64 = 1.0e-9;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "FeatureUnitPlaneFrameWire")]
pub struct FeatureUnitPlaneFrame {
origin: FinitePoint3,
u_axis: UnitVector3,
v_axis: UnitVector3,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct FeatureUnitPlaneFrameWire {
origin: Point3,
u_axis: Vector3,
v_axis: Vector3,
}
impl FeatureUnitPlaneFrame {
pub fn new(origin: Point3, u_axis: Vector3, v_axis: Vector3) -> Option<Self> {
let origin = FinitePoint3::new(origin)?;
let u_axis = UnitVector3::new(u_axis)?;
let v_axis = UnitVector3::new(v_axis)?;
Self::from_parts(origin, u_axis, v_axis)
}
pub fn from_parts(
origin: FinitePoint3,
u_axis: UnitVector3,
v_axis: UnitVector3,
) -> Option<Self> {
(u_axis.as_raw().dot(*v_axis.as_raw()).abs() <= EPS_FEATURE_UNIT_FRAME).then_some(Self {
origin,
u_axis,
v_axis,
})
}
#[must_use]
pub fn with_origin(self, origin: FinitePoint3) -> Self {
Self { origin, ..self }
}
pub fn origin(self) -> FinitePoint3 {
self.origin
}
pub fn u_axis(self) -> UnitVector3 {
self.u_axis
}
pub fn v_axis(self) -> UnitVector3 {
self.v_axis
}
}
impl TryFrom<FeatureUnitPlaneFrameWire> for FeatureUnitPlaneFrame {
type Error = &'static str;
fn try_from(wire: FeatureUnitPlaneFrameWire) -> Result<Self, Self::Error> {
Self::new(wire.origin, wire.u_axis, wire.v_axis)
.ok_or("feature plane requires a finite origin and perpendicular unit axes")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
try_from = "FeatureCoordinateFrameWire",
into = "FeatureCoordinateFrameWire"
)]
pub struct FeatureCoordinateFrame {
plane: FeatureUnitPlaneFrame,
z_axis: UnitVector3,
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct FeatureCoordinateFrameWire {
origin: Point3,
x_axis: Vector3,
y_axis: Vector3,
z_axis: Vector3,
}
impl FeatureCoordinateFrame {
pub fn new(origin: Point3, x_axis: Vector3, y_axis: Vector3, z_axis: Vector3) -> Option<Self> {
let plane = FeatureUnitPlaneFrame::new(origin, x_axis, y_axis)?;
let z_axis = UnitVector3::new(z_axis)?;
Self::from_parts(plane, z_axis)
}
pub fn from_parts(plane: FeatureUnitPlaneFrame, z_axis: UnitVector3) -> Option<Self> {
let x_axis = *plane.u_axis().as_raw();
let y_axis = *plane.v_axis().as_raw();
let z = *z_axis.as_raw();
(x_axis.dot(z).abs() <= EPS_FEATURE_UNIT_FRAME
&& y_axis.dot(z).abs() <= EPS_FEATURE_UNIT_FRAME
&& x_axis.cross(y_axis).dot(z) >= 1.0 - EPS_FEATURE_UNIT_FRAME)
.then_some(Self { plane, z_axis })
}
#[must_use]
pub fn with_origin(self, origin: FinitePoint3) -> Self {
Self {
plane: self.plane.with_origin(origin),
..self
}
}
pub fn origin(self) -> FinitePoint3 {
self.plane.origin()
}
pub fn x_axis(self) -> UnitVector3 {
self.plane.u_axis()
}
pub fn y_axis(self) -> UnitVector3 {
self.plane.v_axis()
}
pub fn z_axis(self) -> UnitVector3 {
self.z_axis
}
}
impl TryFrom<FeatureCoordinateFrameWire> for FeatureCoordinateFrame {
type Error = &'static str;
fn try_from(wire: FeatureCoordinateFrameWire) -> Result<Self, Self::Error> {
Self::new(wire.origin, wire.x_axis, wire.y_axis, wire.z_axis)
.ok_or("feature coordinate frame requires finite origin and right-handed perpendicular unit axes")
}
}
impl From<FeatureCoordinateFrame> for FeatureCoordinateFrameWire {
fn from(frame: FeatureCoordinateFrame) -> Self {
Self {
origin: frame.origin().get(),
x_axis: frame.x_axis().into(),
y_axis: frame.y_axis().into(),
z_axis: frame.z_axis().into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "[Point2; 2]", into = "[Point2; 2]")]
pub struct FeatureImageBounds([FinitePoint2; 2]);
impl FeatureImageBounds {
pub fn new(corners: [Point2; 2]) -> Option<Self> {
let [first, second] = corners;
let (Some(first), Some(second)) = (FinitePoint2::new(first), FinitePoint2::new(second))
else {
return None;
};
(first.u != second.u && first.v != second.v).then_some(Self([first, second]))
}
pub fn corners(self) -> [FinitePoint2; 2] {
self.0
}
}
impl TryFrom<[Point2; 2]> for FeatureImageBounds {
type Error = &'static str;
fn try_from(corners: [Point2; 2]) -> Result<Self, Self::Error> {
Self::new(corners).ok_or("image bounds require finite corners and nonzero width and height")
}
}
impl From<FeatureImageBounds> for [Point2; 2] {
fn from(bounds: FeatureImageBounds) -> Self {
bounds.0.map(FinitePoint2::get)
}
}
const EPS_FEATURE_PLANE_ORTHOGONAL: f64 = 1.0e-9;
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct FeaturePlaneFrameWire {
origin: Point3,
normal: Vector3,
u_axis: Vector3,
}
macro_rules! checked_feature_plane_frame {
($(#[$meta:meta])* $name:ident, $normal_length:ident, $u_length:ident, $bound:expr) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "FeaturePlaneFrameWire", into = "FeaturePlaneFrameWire")]
pub struct $name {
origin: FinitePoint3,
normal: FeatureDirection3,
u_axis: FeatureDirection3,
}
rewrite_scalar!($name);
impl $name {
pub fn new(origin: Point3, normal: Vector3, u_axis: Vector3) -> Option<Self> {
let origin = FinitePoint3::new(origin)?;
let normal = FeatureDirection3::new(normal)?;
let u_axis = FeatureDirection3::new(u_axis)?;
Self::from_parts(origin, normal, u_axis)
}
pub fn from_parts(origin: FinitePoint3, normal: FeatureDirection3, u_axis: FeatureDirection3) -> Option<Self> {
let $normal_length = normal.norm();
let $u_length = u_axis.norm();
if normal.dot(u_axis.get()).abs() > $bound { return None; }
Some(Self { origin, normal, u_axis })
}
#[must_use]
pub fn with_origin(self, origin: FinitePoint3) -> Self { Self { origin, ..self } }
pub fn origin(self) -> FinitePoint3 { self.origin }
pub fn normal(self) -> FeatureDirection3 { self.normal }
pub fn u_axis(self) -> FeatureDirection3 { self.u_axis }
}
impl TryFrom<FeaturePlaneFrameWire> for $name {
type Error = &'static str;
fn try_from(wire: FeaturePlaneFrameWire) -> Result<Self, Self::Error> {
Self::new(wire.origin, wire.normal, wire.u_axis)
.ok_or("plane frame requires finite origin and nonzero perpendicular directions")
}
}
impl From<$name> for FeaturePlaneFrameWire {
fn from(frame: $name) -> Self {
Self {
origin: frame.origin().get(),
normal: frame.normal().get(),
u_axis: frame.u_axis().get(),
}
}
}
};
}
checked_feature_plane_frame!(
FeatureDatumPlaneFrame, normal_length, u_length,
{ let scale = normal_length * u_length; if !scale.is_finite() { return None; } EPS_FEATURE_PLANE_ORTHOGONAL * scale }
);
checked_feature_plane_frame!(
FeatureSupportPlaneFrame, normal_length, u_length,
EPS_FEATURE_PLANE_ORTHOGONAL * normal_length * u_length
);
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "FeatureLineSegmentWire")]
pub struct FeatureLineSegment {
start: FinitePoint3,
end: FinitePoint3,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct FeatureLineSegmentWire {
start: Point3,
end: Point3,
}
impl FeatureLineSegment {
pub fn new(start: Point3, end: Point3) -> Option<Self> {
let start = FinitePoint3::new(start)?;
let end = FinitePoint3::new(end)?;
Self::from_parts(start, end)
}
pub fn from_parts(start: FinitePoint3, end: FinitePoint3) -> Option<Self> {
(start != end).then_some(Self { start, end })
}
pub fn start(self) -> FinitePoint3 {
self.start
}
pub fn end(self) -> FinitePoint3 {
self.end
}
}
impl TryFrom<FeatureLineSegmentWire> for FeatureLineSegment {
type Error = &'static str;
fn try_from(wire: FeatureLineSegmentWire) -> Result<Self, Self::Error> {
Self::new(wire.start, wire.end).ok_or("line start and end must be finite and distinct")
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "FeaturePolylineWire")]
pub struct FeaturePolyline {
points: Vec<FinitePoint3>,
closed: bool,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct FeaturePolylineWire {
points: Vec<Point3>,
closed: bool,
}
impl FeaturePolyline {
pub fn new(points: Vec<Point3>, closed: bool) -> Option<Self> {
if points.len() < 2 || (closed && points.len() < 3) {
return None;
}
let points = points
.into_iter()
.map(FinitePoint3::new)
.collect::<Option<Vec<_>>>()?;
Self::from_parts(points, closed)
}
pub fn from_parts(points: Vec<FinitePoint3>, closed: bool) -> Option<Self> {
if points.len() < 2
|| (closed && points.len() < 3)
|| points.windows(2).any(|pair| pair[0] == pair[1])
{
return None;
}
Some(Self { points, closed })
}
pub fn points(&self) -> &[FinitePoint3] {
&self.points
}
pub fn closed(&self) -> bool {
self.closed
}
}
impl TryFrom<FeaturePolylineWire> for FeaturePolyline {
type Error = &'static str;
fn try_from(wire: FeaturePolylineWire) -> Result<Self, Self::Error> {
Self::new(wire.points, wire.closed).ok_or("polyline points must be finite, adjacent-distinct, and sufficient for its closed state")
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
try_from = "FeatureEquationCurveWire",
into = "FeatureEquationCurveWire"
)]
pub struct FeatureEquationCurve {
parameter: String,
x_expression: String,
y_expression: String,
z_expression: String,
domain: crate::topology::IncreasingParameterInterval,
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct FeatureEquationCurveWire {
parameter: String,
x_expression: String,
y_expression: String,
z_expression: String,
start: f64,
end: f64,
}
impl FeatureEquationCurve {
pub fn new(
parameter: String,
x_expression: String,
y_expression: String,
z_expression: String,
start: f64,
end: f64,
) -> Option<Self> {
if [¶meter, &x_expression, &y_expression, &z_expression]
.into_iter()
.any(|value| value.trim().is_empty())
{
return None;
}
let domain = crate::topology::IncreasingParameterInterval::new([start, end])?;
Some(Self {
parameter,
x_expression,
y_expression,
z_expression,
domain,
})
}
pub fn parameter(&self) -> &str {
&self.parameter
}
pub fn x_expression(&self) -> &str {
&self.x_expression
}
pub fn y_expression(&self) -> &str {
&self.y_expression
}
pub fn z_expression(&self) -> &str {
&self.z_expression
}
pub fn domain(&self) -> crate::topology::IncreasingParameterInterval {
self.domain
}
}
impl TryFrom<FeatureEquationCurveWire> for FeatureEquationCurve {
type Error = &'static str;
fn try_from(wire: FeatureEquationCurveWire) -> Result<Self, Self::Error> {
Self::new(wire.parameter, wire.x_expression, wire.y_expression, wire.z_expression, wire.start, wire.end)
.ok_or("equation-curve expressions must be nonblank and its start and end finite and increasing")
}
}
const EPS_FEATURE_ELLIPSE_AXES_ORTHO: f64 = 1.0e-9;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "FeatureCircularArcWire", into = "FeatureCircularArcWire")]
pub struct FeatureCircularArc {
center: FinitePoint3,
normal: FeatureDirection3,
radius: PositiveLength,
angles: crate::geometry::DirectedParameterRange,
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct FeatureCircularArcWire {
center: Point3,
normal: Vector3,
radius: PositiveLength,
start_angle: f64,
end_angle: f64,
}
impl FeatureCircularArc {
pub fn new(
center: Point3,
normal: Vector3,
radius: PositiveLength,
angles: crate::geometry::DirectedParameterRange,
) -> Option<Self> {
Some(Self::from_parts(
FinitePoint3::new(center)?,
FeatureDirection3::new(normal)?,
radius,
angles,
))
}
#[must_use]
pub const fn from_parts(
center: FinitePoint3,
normal: FeatureDirection3,
radius: PositiveLength,
angles: crate::geometry::DirectedParameterRange,
) -> Self {
Self {
center,
normal,
radius,
angles,
}
}
pub fn center(self) -> FinitePoint3 {
self.center
}
pub fn normal(self) -> FeatureDirection3 {
self.normal
}
pub fn radius(self) -> PositiveLength {
self.radius
}
pub fn angles(self) -> crate::geometry::DirectedParameterRange {
self.angles
}
}
impl TryFrom<FeatureCircularArcWire> for FeatureCircularArc {
type Error = &'static str;
fn try_from(wire: FeatureCircularArcWire) -> Result<Self, Self::Error> {
let angles =
crate::geometry::DirectedParameterRange::new([wire.start_angle, wire.end_angle])
.map_err(|_| "arc start_angle and end_angle must be finite and distinct")?;
Self::new(wire.center, wire.normal, wire.radius, angles)
.ok_or("circular-arc center must be finite and normal must have finite nonzero norm")
}
}
impl From<FeatureCircularArc> for FeatureCircularArcWire {
fn from(value: FeatureCircularArc) -> Self {
let [start_angle, end_angle] = value.angles.endpoints();
Self {
center: value.center.get(),
normal: value.normal.get(),
radius: value.radius,
start_angle,
end_angle,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "FeatureEllipticArcWire", into = "FeatureEllipticArcWire")]
pub struct FeatureEllipticArc {
center: FinitePoint3,
normal: FeatureDirection3,
major_axis: FeatureDirection3,
radii: [PositiveLength; 2],
angles: crate::geometry::DirectedParameterRange,
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct FeatureEllipticArcWire {
center: Point3,
normal: Vector3,
major_axis: Vector3,
major_radius: PositiveLength,
minor_radius: PositiveLength,
start_angle: f64,
end_angle: f64,
}
impl FeatureEllipticArc {
pub fn new(
center: Point3,
normal: Vector3,
major_axis: Vector3,
radii: [PositiveLength; 2],
angles: crate::geometry::DirectedParameterRange,
) -> Option<Self> {
let center = FinitePoint3::new(center)?;
let normal = FeatureDirection3::new(normal)?;
let major_axis = FeatureDirection3::new(major_axis)?;
if normal.dot(major_axis.get()).abs() > EPS_FEATURE_ELLIPSE_AXES_ORTHO
|| radii[1].get() > radii[0].get()
{
return None;
}
Some(Self {
center,
normal,
major_axis,
radii,
angles,
})
}
#[must_use]
pub const fn with_center(self, center: FinitePoint3) -> Self {
Self { center, ..self }
}
#[must_use]
pub fn with_scaled_radii(self, scale: PositiveReal) -> Option<Self> {
let radii =
PositiveLength::scale_ordered_pair(self.radii.map(PositiveLength::get), scale).ok()?;
Some(Self { radii, ..self })
}
pub fn center(self) -> FinitePoint3 {
self.center
}
pub fn normal(self) -> FeatureDirection3 {
self.normal
}
pub fn major_axis(self) -> FeatureDirection3 {
self.major_axis
}
pub fn radii(self) -> [PositiveLength; 2] {
self.radii
}
pub fn angles(self) -> crate::geometry::DirectedParameterRange {
self.angles
}
}
impl TryFrom<FeatureEllipticArcWire> for FeatureEllipticArc {
type Error = &'static str;
fn try_from(wire: FeatureEllipticArcWire) -> Result<Self, Self::Error> {
let angles =
crate::geometry::DirectedParameterRange::new([wire.start_angle, wire.end_angle])
.map_err(|_| "arc start_angle and end_angle must be finite and distinct")?;
Self::new(
wire.center,
wire.normal,
wire.major_axis,
[wire.major_radius, wire.minor_radius],
angles,
)
.ok_or("elliptic-arc center, axes, or major/minor radius order is invalid")
}
}
impl From<FeatureEllipticArc> for FeatureEllipticArcWire {
fn from(value: FeatureEllipticArc) -> Self {
let [start_angle, end_angle] = value.angles.endpoints();
let [major_radius, minor_radius] = value.radii;
Self {
center: value.center.get(),
normal: value.normal.get(),
major_axis: value.major_axis.get(),
major_radius,
minor_radius,
start_angle,
end_angle,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct DraftPull {
pub direction: FeatureDirection3,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_plane"
)]
pub plane: Option<FeatureId>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum DraftAnchor {
NeutralPlane {
plane: FaceSelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_pull"
)]
pull: Option<DraftPull>,
},
PartingLine {
tool: FaceSelection,
pull: DraftPull,
},
}
impl DraftAnchor {
#[must_use]
pub const fn pull(&self) -> Option<&DraftPull> {
match self {
Self::NeutralPlane { pull, .. } => pull.as_ref(),
Self::PartingLine { pull, .. } => Some(pull),
}
}
}
crate::ids::id_type!(
FeatureId, compose, into_string, key
);
crate::ids::id_type!(
ConfigurationId, compose
);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct DesignConfiguration {
pub id: ConfigurationId,
#[serde(default)]
pub ordinal: u32,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub active: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_source_index"
)]
pub source_index: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_name"
)]
pub name: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_material"
)]
pub material: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
pub properties: BTreeMap<NonBlankString, String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
pub parameter_overrides: BTreeMap<ParameterId, String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_bodies"
)]
pub bodies: Option<DistinctMembers<BodyId>>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
pub parameter_values: BTreeMap<ParameterId, ParameterValue>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
pub feature_states: BTreeMap<FeatureId, ConfigurationFeatureState>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_native_ref"
)]
pub native_ref: Option<String>,
}
impl DesignConfiguration {
pub fn suppressed_features(&self) -> impl Iterator<Item = &FeatureId> {
self.feature_states
.iter()
.filter_map(|(feature, state)| state.evaluation.is_suppressed().then_some(feature))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ConfigurationFeatureState {
pub evaluation: ConfigurationEvaluation,
#[serde(
default,
skip_serializing_if = "DistinctMembers::is_empty",
deserialize_with = "deserialize_dependencies"
)]
pub dependencies: DistinctMembers<FeatureId>,
pub definition: FeatureDefinition,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum ConfigurationEvaluation {
Suppressed {},
Active {
#[serde(default, skip_serializing_if = "DistinctMembers::is_empty")]
outputs: DistinctMembers<BodyId>,
},
}
impl<'de> Deserialize<'de> for ConfigurationEvaluation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
enum Wire {
Suppressed {},
Active {
#[serde(default)]
outputs: Vec<BodyId>,
},
}
Ok(match Wire::deserialize(deserializer)? {
Wire::Suppressed {} => Self::Suppressed {},
Wire::Active { outputs } => {
let mut seen = std::collections::HashSet::new();
seen.try_reserve(outputs.len())
.map_err(serde::de::Error::custom)?;
for output in &outputs {
if !seen.insert(output) {
return Err(serde::de::Error::custom(
"outputs: members must be distinct",
));
}
}
Self::Active {
outputs: DistinctMembers(outputs),
}
}
})
}
}
impl ConfigurationEvaluation {
#[must_use]
pub const fn is_suppressed(&self) -> bool {
matches!(self, Self::Suppressed {})
}
#[must_use]
pub fn outputs(&self) -> &[BodyId] {
match self {
Self::Suppressed {} => &[],
Self::Active { outputs } => outputs,
}
}
}
crate::ids::id_type!(
ParameterId, compose, key
);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct DesignParameter {
pub id: ParameterId,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_owner"
)]
pub owner: Option<FeatureId>,
#[serde(default)]
pub ordinal: u32,
pub name: String,
pub expression: String,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_display"
)]
pub display: Option<DimensionDisplay>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_value"
)]
pub value: Option<ParameterValue>,
#[serde(
default,
skip_serializing_if = "DistinctMembers::is_empty",
deserialize_with = "deserialize_dependencies"
)]
pub dependencies: DistinctMembers<ParameterId>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
pub properties: BTreeMap<NonBlankString, String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_pmi"
)]
pub pmi: Option<ParameterPmi>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_native_ref"
)]
pub native_ref: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ParameterPmi {
pub subtype: PmiDimensionSubtype,
pub precision: i64,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_display_text"
)]
pub display_text: Option<String>,
pub basic: bool,
pub inspection: bool,
pub reference_only: bool,
pub native_ref: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", content = "native_kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum PmiDimensionSubtype {
Linear,
Angle,
Diameter,
Radial,
Ordinate,
Count,
Native(String),
}
#[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 DimensionDisplay {
Diameter,
Radius,
}
#[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 ParameterValue {
Length(Length),
Angle(Angle),
Real(#[serde(deserialize_with = "deserialize_parameter_real")] FiniteReal),
Integer(i64),
Boolean(bool),
String(String),
}
crate::units::named_field!(deserialize_parameter_real, FiniteReal, "value");
fn deserialize_dependencies<'de, D, T>(deserializer: D) -> Result<DistinctMembers<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: Deserialize<'de> + Eq + std::hash::Hash,
{
crate::units::deserialize_named(deserializer, "dependencies")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct PolygonSideCount(u32);
impl PolygonSideCount {
pub fn new(value: u32) -> Option<Self> {
(value >= 3).then_some(Self(value))
}
}
impl<'de> Deserialize<'de> for PolygonSideCount {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::new(u32::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("polygon sides must be at least three"))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FeatureEvaluation {
definition: FeatureDefinition,
outputs: DistinctMembers<BodyId>,
}
impl FeatureEvaluation {
#[must_use]
pub const fn new(definition: FeatureDefinition, outputs: DistinctMembers<BodyId>) -> Self {
Self {
definition,
outputs,
}
}
#[must_use]
pub const fn from_definition(definition: FeatureDefinition) -> Self {
Self {
definition,
outputs: DistinctMembers(Vec::new()),
}
}
pub fn into_parts(self) -> (FeatureDefinition, DistinctMembers<BodyId>) {
(self.definition, self.outputs)
}
pub const fn definition(&self) -> &FeatureDefinition {
&self.definition
}
pub const fn outputs(&self) -> &Vec<BodyId> {
&self.outputs.0
}
pub fn edit(
&mut self,
edit: impl FnOnce(&mut FeatureDefinition, &mut DistinctMembers<BodyId>),
) {
edit(&mut self.definition, &mut self.outputs);
}
pub fn set_definition(&mut self, definition: FeatureDefinition) {
self.definition = definition;
}
pub fn set_outputs(&mut self, outputs: DistinctMembers<BodyId>) {
self.outputs = outputs;
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Feature {
pub id: FeatureId,
pub ordinal: u64,
pub name: Option<String>,
pub suppressed: Option<bool>,
pub dependencies: DistinctMembers<FeatureId>,
pub source_properties: BTreeMap<NonBlankString, String>,
pub source_tag: Option<String>,
pub source_text: Option<String>,
pub source_content: FeatureContent,
pub evaluation: FeatureEvaluation,
pub native_ref: Option<String>,
}
#[derive(Serialize)]
pub(crate) struct FeatureWriteWire<'a> {
id: &'a FeatureId,
ordinal: u64,
#[serde(skip_serializing_if = "Option::is_none")]
name: &'a Option<String>,
suppressed: &'a Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
regeneration_parent: Option<&'a FeatureId>,
#[serde(skip_serializing_if = "DistinctMembers::is_empty")]
dependencies: &'a DistinctMembers<FeatureId>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
source_properties: &'a BTreeMap<NonBlankString, String>,
#[serde(skip_serializing_if = "Option::is_none")]
source_tag: &'a Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
source_text: &'a Option<String>,
#[serde(skip_serializing_if = "FeatureContent::is_empty")]
source_content: &'a FeatureContent,
#[serde(skip_serializing_if = "Vec::is_empty")]
outputs: &'a Vec<BodyId>,
definition: &'a FeatureDefinition,
#[serde(skip_serializing_if = "Option::is_none")]
native_ref: &'a Option<String>,
}
impl<'a> FeatureWriteWire<'a> {
pub(crate) fn new(feature: &'a Feature, regeneration_parent: Option<&'a FeatureId>) -> Self {
Self {
id: &feature.id,
ordinal: feature.ordinal,
name: &feature.name,
suppressed: &feature.suppressed,
regeneration_parent,
dependencies: &feature.dependencies,
source_properties: &feature.source_properties,
source_tag: &feature.source_tag,
source_text: &feature.source_text,
source_content: &feature.source_content,
outputs: feature.evaluation.outputs(),
definition: feature.evaluation.definition(),
native_ref: &feature.native_ref,
}
}
fn standalone(feature: &'a Feature) -> Self {
Self::new(feature, None)
}
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub(crate) struct FeatureRowWire {
id: FeatureId,
ordinal: u64,
#[serde(default, deserialize_with = "deserialize_name")]
name: Option<String>,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
suppressed: Option<bool>,
#[serde(default, deserialize_with = "deserialize_regeneration_parent")]
regeneration_parent: Option<FeatureId>,
#[serde(default, deserialize_with = "deserialize_dependencies")]
dependencies: DistinctMembers<FeatureId>,
#[serde(default)]
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
source_properties: BTreeMap<NonBlankString, String>,
#[serde(default, deserialize_with = "deserialize_source_tag")]
source_tag: Option<String>,
#[serde(default, deserialize_with = "deserialize_source_text")]
source_text: Option<String>,
#[serde(default)]
source_content: FeatureContent,
#[serde(default)]
#[cfg_attr(feature = "schema", schemars(with = "Vec<BodyId>"))]
outputs: DistinctMembers<BodyId>,
definition: FeatureDefinition,
#[serde(default, deserialize_with = "deserialize_native_ref")]
native_ref: Option<String>,
}
impl FeatureRowWire {
pub(crate) fn into_parts(self) -> (Feature, Option<FeatureId>) {
let regeneration_parent = self.regeneration_parent;
let feature = FeatureReadWire {
id: self.id,
ordinal: self.ordinal,
name: self.name,
suppressed: self.suppressed,
dependencies: self.dependencies,
source_properties: self.source_properties,
source_tag: self.source_tag,
source_text: self.source_text,
source_content: self.source_content,
outputs: self.outputs,
definition: self.definition,
native_ref: self.native_ref,
}
.into_feature();
(feature, regeneration_parent)
}
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct FeatureReadWire {
id: FeatureId,
ordinal: u64,
#[serde(default, deserialize_with = "deserialize_name")]
name: Option<String>,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
suppressed: Option<bool>,
#[serde(default, deserialize_with = "deserialize_dependencies")]
dependencies: DistinctMembers<FeatureId>,
#[serde(default)]
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
source_properties: BTreeMap<NonBlankString, String>,
#[serde(default, deserialize_with = "deserialize_source_tag")]
source_tag: Option<String>,
#[serde(default, deserialize_with = "deserialize_source_text")]
source_text: Option<String>,
#[serde(default)]
source_content: FeatureContent,
#[serde(default)]
#[cfg_attr(feature = "schema", schemars(with = "Vec<BodyId>"))]
outputs: DistinctMembers<BodyId>,
definition: FeatureDefinition,
#[serde(default, deserialize_with = "deserialize_native_ref")]
native_ref: Option<String>,
}
impl FeatureReadWire {
fn into_feature(self) -> Feature {
Feature {
id: self.id,
ordinal: self.ordinal,
name: self.name,
suppressed: self.suppressed,
dependencies: self.dependencies,
source_properties: self.source_properties,
source_tag: self.source_tag,
source_text: self.source_text,
source_content: self.source_content,
evaluation: FeatureEvaluation::new(self.definition, self.outputs),
native_ref: self.native_ref,
}
}
}
impl<'de> Deserialize<'de> for Feature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(FeatureReadWire::deserialize(deserializer)?.into_feature())
}
}
impl Serialize for Feature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
FeatureWriteWire::standalone(self).serialize(serializer)
}
}
#[cfg(feature = "schema")]
impl JsonSchema for Feature {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Feature".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
FeatureReadWire::json_schema(generator)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct FeatureInputTopology {
pub id: FeatureInputTopologyId,
pub input_of: FeatureId,
#[serde(default, skip_serializing_if = "DistinctMembers::is_empty")]
pub bodies: DistinctMembers<HistoricalBodyId>,
#[serde(default, skip_serializing_if = "DistinctMembers::is_empty")]
pub faces: DistinctMembers<HistoricalFaceId>,
#[serde(default, skip_serializing_if = "DistinctMembers::is_empty")]
pub edges: DistinctMembers<HistoricalEdgeId>,
#[serde(default, skip_serializing_if = "DistinctMembers::is_empty")]
pub vertices: DistinctMembers<HistoricalVertexId>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_native_ref"
)]
pub native_ref: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum SelectionMember {
Body {
id: NonBlankString,
},
Face {
id: NonBlankString,
},
Edge {
id: NonBlankString,
},
Vertex {
id: NonBlankString,
},
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(
try_from = "NonEmptyMembers<SelectionMember>",
into = "Vec<SelectionMember>"
)]
pub struct FeatureResultMembers {
bodies: Vec<NonBlankString>,
faces: Vec<NonBlankString>,
edges: Vec<NonBlankString>,
vertices: Vec<NonBlankString>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum FeatureResultMemberError {
#[error(transparent)]
Members(#[from] BodySelectionError),
#[error("feature result bodies must be distinct")]
RepeatedBody,
#[error("feature result faces must be distinct")]
RepeatedFace,
#[error("feature result edges must be distinct")]
RepeatedEdge,
#[error("feature result vertices must be distinct")]
RepeatedVertex,
}
impl TryFrom<NonEmptyMembers<SelectionMember>> for FeatureResultMembers {
type Error = FeatureResultMemberError;
fn try_from(members: NonEmptyMembers<SelectionMember>) -> Result<Self, Self::Error> {
let mut sorted = Self {
bodies: Vec::new(),
faces: Vec::new(),
edges: Vec::new(),
vertices: Vec::new(),
};
for member in members {
let (target, id) = match member {
SelectionMember::Body { id } => (&mut sorted.bodies, id),
SelectionMember::Face { id } => (&mut sorted.faces, id),
SelectionMember::Edge { id } => (&mut sorted.edges, id),
SelectionMember::Vertex { id } => (&mut sorted.vertices, id),
};
target
.try_reserve(1)
.map_err(|_| BodySelectionError::Allocation)?;
target.push(id);
}
sorted.validate()
}
}
impl FeatureResultMembers {
pub fn new(
bodies: Vec<NonBlankString>,
faces: Vec<NonBlankString>,
edges: Vec<NonBlankString>,
vertices: Vec<NonBlankString>,
) -> Result<Self, FeatureResultMemberError> {
Self {
bodies,
faces,
edges,
vertices,
}
.validate()
}
fn validate(self) -> Result<Self, FeatureResultMemberError> {
if self.bodies.is_empty()
&& self.faces.is_empty()
&& self.edges.is_empty()
&& self.vertices.is_empty()
{
return Err(BodySelectionError::Empty.into());
}
for (error, values) in [
(FeatureResultMemberError::RepeatedBody, &self.bodies),
(FeatureResultMemberError::RepeatedFace, &self.faces),
(FeatureResultMemberError::RepeatedEdge, &self.edges),
(FeatureResultMemberError::RepeatedVertex, &self.vertices),
] {
if !membership::distinct(values, |_| true) {
return Err(error);
}
}
Ok(self)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct FeatureResultTopology {
pub id: FeatureResultTopologyId,
pub output_of: FeatureId,
#[cfg_attr(feature = "schema", schemars(with = "Vec<SelectionMember>"))]
members: FeatureResultMembers,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_native_ref"
)]
pub native_ref: Option<String>,
}
impl FeatureResultTopology {
pub fn new(
id: FeatureResultTopologyId,
output_of: FeatureId,
members: FeatureResultMembers,
native_ref: Option<String>,
) -> Self {
Self {
id,
output_of,
members,
native_ref,
}
}
pub fn bodies(&self) -> &[NonBlankString] {
&self.members.bodies
}
pub fn faces(&self) -> &[NonBlankString] {
&self.members.faces
}
pub fn edges(&self) -> &[NonBlankString] {
&self.members.edges
}
pub fn vertices(&self) -> &[NonBlankString] {
&self.members.vertices
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct FeatureContent(Vec<FeatureSourceContent>);
impl TryFrom<Vec<FeatureSourceContent>> for FeatureContent {
type Error = FeatureCollectionError;
fn try_from(value: Vec<FeatureSourceContent>) -> Result<Self, Self::Error> {
Self::build(value).map_err(FeatureCollectionError::Invalid)
}
}
impl FeatureContent {
fn build(value: Vec<FeatureSourceContent>) -> Result<Self, &'static str> {
if !membership::distinct(&value, |item| {
!matches!(item, FeatureSourceContent::Text(_))
}) {
return Err("source_content repeats a parameter or child-feature reference");
}
Ok(Self(value))
}
pub fn text(values: impl IntoIterator<Item = String>) -> Self {
Self(values.into_iter().map(FeatureSourceContent::Text).collect())
}
pub fn push(&mut self, value: FeatureSourceContent) -> Result<(), FeatureCollectionError> {
if !matches!(value, FeatureSourceContent::Text(_)) {
for member in &self.0 {
if member == &value {
return Err(FeatureCollectionError::Invalid(
"source_content repeats a parameter or child-feature reference",
));
}
}
}
self.0.push(value);
Ok(())
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl std::ops::Deref for FeatureContent {
type Target = [FeatureSourceContent];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> IntoIterator for &'a FeatureContent {
type Item = &'a FeatureSourceContent;
type IntoIter = std::slice::Iter<'a, FeatureSourceContent>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'de> Deserialize<'de> for FeatureContent {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let values = Vec::<FeatureSourceContent>::deserialize(deserializer)?;
Self::build(values).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum FeatureSourceContent {
Text(String),
Parameter(ParameterId),
Feature(FeatureId),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "reference", rename_all = "snake_case", deny_unknown_fields)]
pub enum DatumPlaneReference {
Feature {
feature: FeatureId,
},
Face {
face: FaceSelection,
},
ResolvedPlane {
frame: FeatureSupportPlaneFrame,
},
}
#[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 SketchPointSelection {
Unresolved,
Planar {
sketch: crate::sketches::SketchId,
point: crate::sketches::SketchEntityId,
native: String,
},
Spatial {
sketch: crate::sketches::SpatialSketchId,
point: crate::sketches::SpatialSketchEntityId,
native: String,
},
Native(String),
}
#[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 DatumPointConstruction {
CircleCenter {
edge: EdgeSelection,
},
TwoEdgeIntersection {
edges: [EdgeSelection; 2],
},
ThreePlaneIntersection {
planes: Box<[DatumPlaneReference; 3]>,
},
Vertex {
vertex: VertexSelection,
},
SketchPoint {
point: SketchPointSelection,
},
EdgePlaneIntersection {
edge: EdgeSelection,
plane: DatumPlaneReference,
},
DistanceOnEdge {
edge: EdgeSelection,
fraction: Fraction,
},
}
#[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 DecalMapping {
FitToFaces,
}
impl DatumPointConstruction {
pub fn feature_references(&self) -> impl Iterator<Item = &FeatureId> {
let references = match self {
Self::ThreePlaneIntersection { planes } => planes.each_ref().map(|plane| match plane {
DatumPlaneReference::Feature { feature } => Some(feature),
DatumPlaneReference::Face { .. } | DatumPlaneReference::ResolvedPlane { .. } => {
None
}
}),
Self::EdgePlaneIntersection {
plane: DatumPlaneReference::Feature { feature },
..
} => [Some(feature), None, None],
Self::Vertex {
vertex: VertexSelection::Generated { vertex, .. },
} => [Some(&vertex.feature), None, None],
Self::CircleCenter { .. }
| Self::TwoEdgeIntersection { .. }
| Self::Vertex { .. }
| Self::SketchPoint { .. }
| Self::EdgePlaneIntersection { .. }
| Self::DistanceOnEdge { .. } => [None; 3],
};
references.into_iter().flatten()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NativeFeatureKind {
Canvas,
Decal,
Draft,
Fillet,
Chamfer,
Extrude,
DeleteFace,
SurfaceDeleteFace,
Other(String),
}
impl NativeFeatureKind {
pub fn as_str(&self) -> &str {
match self {
Self::Canvas => "Canvas",
Self::Decal => "Decal",
Self::Draft => "Draft",
Self::Fillet => "Fillet",
Self::Chamfer => "Chamfer",
Self::Extrude => "Extrude",
Self::DeleteFace => "DeleteFace",
Self::SurfaceDeleteFace => "SurfaceDeleteFace",
Self::Other(value) => value,
}
}
}
impl From<String> for NativeFeatureKind {
fn from(value: String) -> Self {
match value.as_str() {
"Canvas" => Self::Canvas,
"Decal" => Self::Decal,
"Draft" => Self::Draft,
"Fillet" => Self::Fillet,
"Chamfer" => Self::Chamfer,
"Extrude" => Self::Extrude,
"DeleteFace" => Self::DeleteFace,
"SurfaceDeleteFace" => Self::SurfaceDeleteFace,
_ => Self::Other(value),
}
}
}
impl From<&str> for NativeFeatureKind {
fn from(value: &str) -> Self {
value.to_owned().into()
}
}
impl std::fmt::Display for NativeFeatureKind {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
impl Serialize for NativeFeatureKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for NativeFeatureKind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(String::deserialize(deserializer)?.into())
}
}
#[cfg(feature = "schema")]
impl JsonSchema for NativeFeatureKind {
fn schema_name() -> std::borrow::Cow<'static, str> {
"NativeFeatureKind".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
String::json_schema(generator)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
tag = "kind",
content = "path",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum LoftGuidance {
Guides(Vec<PathRef>),
Centerline(PathRef),
}
impl Default for LoftGuidance {
fn default() -> Self {
Self::Guides(Vec::new())
}
}
fn known_body_count(selection: &BodySelection) -> Option<usize> {
match selection {
BodySelection::Bodies(bodies) | BodySelection::Resolved { bodies, .. } => {
Some(bodies.len())
}
BodySelection::Local { bodies, .. } => Some(bodies.len()),
BodySelection::ResolvedSet { members } => Some(members.count()),
BodySelection::Historical { bodies, .. } => Some(bodies.len()),
BodySelection::HistoricalSet { members, .. } => Some(members.count()),
BodySelection::Generated { bodies, .. } => Some(bodies.len()),
BodySelection::Unresolved | BodySelection::Native(_) | BodySelection::NativeSet(_) => None,
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct SewBodySelection(BodySelection);
impl TryFrom<BodySelection> for SewBodySelection {
type Error = &'static str;
fn try_from(bodies: BodySelection) -> Result<Self, Self::Error> {
if known_body_count(&bodies).is_some_and(|count| count < 2) {
return Err("bodies must select at least two bodies for sewing");
}
Ok(Self(bodies))
}
}
impl std::ops::Deref for SewBodySelection {
type Target = BodySelection;
fn deref(&self) -> &BodySelection {
&self.0
}
}
impl AsRef<BodySelection> for SewBodySelection {
fn as_ref(&self) -> &BodySelection {
&self.0
}
}
impl<'de> Deserialize<'de> for SewBodySelection {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::try_from(BodySelection::deserialize(deserializer)?).map_err(serde::de::Error::custom)
}
}
macro_rules! selection_operands {
($name:ident, $wire:ident, $selection:ty, $first:ident, $second:ident, $arity:expr, $overlap:ident $(, $edit:ident)?) => {
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct $name {
$first: $selection,
$second: $selection,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct $wire {
$first: $selection,
$second: $selection,
}
impl $name {
pub fn new(
$first: $selection,
$second: $selection,
) -> Result<Self, &'static str> {
if !($arity)(&$first) || selection_overlap::$overlap(&$first, &$second) {
return Err(concat!(
stringify!($first),
" and ",
stringify!($second),
" must have valid arity and disjoint membership"
));
}
Ok(Self { $first, $second })
}
pub fn $first(&self) -> &$selection {
&self.$first
}
pub fn $second(&self) -> &$selection {
&self.$second
}
$(
pub fn $edit(
&mut self,
edit: impl FnOnce(&mut $selection, &mut $selection),
) -> Result<(), &'static str> {
let mut first = self.$first.clone();
let mut second = self.$second.clone();
edit(&mut first, &mut second);
*self = Self::new(first, second)?;
Ok(())
}
)?
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let wire = $wire::deserialize(deserializer)?;
Self::new(wire.$first, wire.$second).map_err(serde::de::Error::custom)
}
}
};
}
selection_operands!(
FaceBlendOperands,
FaceBlendOperandsWire,
FaceSelection,
first_faces,
second_faces,
|_: &FaceSelection| true,
face_selections_overlap
);
selection_operands!(
ReplaceFaceOperands,
ReplaceFaceOperandsWire,
FaceSelection,
targets,
replacements,
|_: &FaceSelection| true,
face_selections_overlap,
try_edit
);
selection_operands!(
SectionOperands,
SectionOperandsWire,
BodySelection,
first,
second,
|_: &BodySelection| true,
body_selections_overlap
);
selection_operands!(
CombineOperands,
CombineOperandsWire,
BodySelection,
target,
tools,
|first| known_body_count(first).is_none_or(|count| count == 1),
body_selections_overlap,
try_edit
);
selection_operands!(
TrimBodyOperands,
TrimBodyOperandsWire,
BodySelection,
targets,
tools,
|_: &BodySelection| true,
body_selections_overlap
);
impl CombineOperands {
pub fn into_parts(self) -> (BodySelection, BodySelection) {
(self.target, self.tools)
}
}
impl ReplaceFaceOperands {
pub fn into_parts(self) -> (FaceSelection, FaceSelection) {
(self.targets, self.replacements)
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct TreeChildren {
#[serde(default, skip_serializing_if = "DistinctMembers::is_empty")]
children: DistinctMembers<FeatureId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
active_child: Option<FeatureId>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct TreeChildrenWire {
#[serde(default)]
children: Vec<FeatureId>,
#[serde(default, deserialize_with = "deserialize_active_child")]
active_child: Option<FeatureId>,
}
impl TreeChildren {
pub fn new(
children: Vec<FeatureId>,
active_child: Option<FeatureId>,
) -> Result<Self, FeatureCollectionError> {
Self::build(children, active_child).map_err(FeatureCollectionError::Invalid)
}
fn build(
children: Vec<FeatureId>,
active_child: Option<FeatureId>,
) -> Result<Self, &'static str> {
if active_child
.as_ref()
.is_some_and(|active| !children.contains(active))
{
return Err("active_child must belong to children");
}
if !membership::distinct(&children, |_| true) {
return Err("members must be distinct");
}
Ok(Self {
children: DistinctMembers(children),
active_child,
})
}
pub fn is_empty(&self) -> bool {
self.children.is_empty()
}
pub fn active_child(&self) -> &Option<FeatureId> {
&self.active_child
}
pub fn insert(&mut self, child: FeatureId) {
self.children.insert(child);
}
}
impl std::ops::Deref for TreeChildren {
type Target = [FeatureId];
fn deref(&self) -> &[FeatureId] {
&self.children
}
}
impl<'a> IntoIterator for &'a TreeChildren {
type Item = &'a FeatureId;
type IntoIter = std::slice::Iter<'a, FeatureId>;
fn into_iter(self) -> Self::IntoIter {
self.children.iter()
}
}
impl<'de> Deserialize<'de> for TreeChildren {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let wire = TreeChildrenWire::deserialize(deserializer)?;
Self::build(wire.children, wire.active_child).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "definition", rename_all = "snake_case", deny_unknown_fields)]
pub enum FeatureOperation {
TreeNode {
role: FeatureTreeNodeRole,
#[serde(default, skip_serializing_if = "TreeChildren::is_empty")]
children: TreeChildren,
},
BaseFeature {
bodies: BodySelection,
},
MeshImport {
#[serde(deserialize_with = "deserialize_local_tessellations")]
tessellations: SelectionMembers<String>,
},
InsertBodies {
bodies: InsertedBodies,
},
InsertComponent {
occurrence: OccurrenceId,
},
AssemblyJoint {
joint: JointId,
},
Form {
cages: Vec<SubdId>,
},
CosmeticThread {
face: FaceSelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_diameter"
)]
diameter: Option<PositiveLength>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_extent"
)]
extent: Option<CosmeticThreadExtent>,
},
ReferenceImage {
asset: AssetId,
#[serde(default = "crate::default_true")]
visible: bool,
#[serde(default)]
mirror_u: bool,
#[serde(default)]
mirror_v: bool,
frame: FeatureUnitPlaneFrame,
bounds: FeatureImageBounds,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_opacity"
)]
opacity: Option<Fraction>,
},
Decal {
asset: AssetId,
faces: FaceSelection,
mapping: DecalMapping,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_opacity"
)]
opacity: Option<Fraction>,
},
DatumPrincipalPlane {
plane: PrincipalPlane,
},
DatumPlane {
frame: FeatureDatumPlaneFrame,
},
DatumThreePointPlane {
frame: FeatureDatumPlaneFrame,
points: ThreePointSelection,
},
DatumOffsetPlane {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_reference"
)]
reference: Option<DatumPlaneReference>,
distance: Length,
},
DatumAxis {
origin: FinitePoint3,
direction: FeatureDirection3,
},
DatumPoint {
position: FinitePoint3,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_construction"
)]
construction: Option<Box<DatumPointConstruction>>,
},
PointGeometry {
position: FinitePoint3,
},
LineSegment {
segment: FeatureLineSegment,
},
CircularArc {
arc: FeatureCircularArc,
},
EllipticArc {
arc: FeatureEllipticArc,
},
Polyline {
chain: FeaturePolyline,
},
RegularPolygonCurve {
sides: PolygonSideCount,
circumradius: PositiveLength,
},
PlanarPatch {
length: PositiveLength,
width: PositiveLength,
},
FaceFromShapes {
sources: BodySelection,
#[serde(rename = "face_maker_class")]
#[cfg_attr(feature = "schema", schemars(with = "String"))]
face_maker: FaceMaker,
},
DatumCoordinateSystem {
frame: FeatureCoordinateFrame,
},
Block {
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
dimensions: Option<[PositiveLength; 3]>,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
placement: Option<FeatureRigidPlacement>,
op: BooleanOp,
},
EquationCurve {
curve: FeatureEquationCurve,
},
ProjectedCurve {
source: PathRef,
target_faces: FaceSelection,
#[serde(default)]
direction: CurveProjectionDirection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_bidirectional"
)]
bidirectional: Option<bool>,
},
ProjectOnSurface {
sources: PathRef,
support_face: FaceSelection,
direction: UnitVector3,
mode: SurfaceProjectionMode,
height: NonNegativeLength,
offset: Length,
},
CompositeCurve {
#[serde(deserialize_with = "deserialize_local_segments")]
segments: NonEmptyMembers<PathRef>,
#[serde(default)]
closed: bool,
},
Helix {
axis_origin: FinitePoint3,
axis_direction: FeatureDirection3,
radius: PositiveLength,
shape: HelixShape,
revolutions: PositiveReal,
#[serde(default)]
start_angle: Angle,
clockwise: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_segment_turns"
)]
segment_turns: Option<PositiveReal>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_construction_style"
)]
construction_style: Option<HelixConstructionStyle>,
},
HelixNativeAxis {
#[serde(deserialize_with = "deserialize_local_axis_native_ref")]
axis_native_ref: NonBlankString,
#[serde(alias = "radius")]
axial_rise: Length,
#[serde(alias = "height")]
pitch: Length,
revolutions: PositiveReal,
start_angle: Angle,
clockwise: bool,
},
Coil {
construction: CoilConstruction,
result: CoilResult,
},
Sphere {
center: FinitePoint3,
radius: PositiveLength,
op: BooleanOp,
},
Torus {
center: FinitePoint3,
axis: UnitVector3,
major_radius: PositiveLength,
minor_radius: PositiveLength,
op: BooleanOp,
},
Wrap {
profile: PlanarProfileRef,
face: FaceSelection,
mode: WrapMode,
},
Sketch {
sketch: SketchFeatureBinding,
},
SpatialSketch {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_sketch"
)]
sketch: Option<crate::sketches::SpatialSketchId>,
},
SketchBlockDefinition {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_feature_operation_sketch"
)]
sketch: Option<crate::sketches::SketchId>,
},
SketchBlockInstance {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_block"
)]
block: Option<FeatureId>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_placement"
)]
placement: Option<crate::transform::Transform>,
},
StoredGeometry {},
ExtractBody {
source: BodySelection,
},
DerivedGeometry {
source: FeatureId,
},
ImportedGeometry {
path: GeometryImportPath,
format: GeometryImportFormat,
},
Primitive {
solid: PrimitiveSolid,
op: BooleanOp,
},
Revolve {
construction: RevolveConstruction,
op: BooleanOp,
},
Sweep {
shape: SweepShape,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_path"
)]
path: Option<PathRef>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_orientation"
)]
orientation: Option<SweepOrientation>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_transition"
)]
transition: Option<SweepTransition>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_transformation"
)]
transformation: Option<SweepTransformation>,
#[serde(default)]
path_tangent: bool,
#[serde(default)]
linearize: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_twist"
)]
twist: Option<Angle>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_path_extent"
)]
path_extent: Option<SweepPathExtent>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_guide_rail"
)]
guide_rail: Option<SweepGuideRail>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_taper"
)]
taper: Option<Angle>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_scale"
)]
scale: Option<PositiveReal>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_multi_profile_faces"
)]
allow_multi_profile_faces: Option<bool>,
},
HelicalSweep {
construction: HelicalSweepConstruction,
op: BooleanOp,
},
Binder {
sources: Vec<BinderSource>,
construction: BinderConstruction,
},
Rib {
construction: RibConstruction,
op: BooleanOp,
},
SheetMetalBaseFlange {
profile: PlanarProfileRef,
thickness: PositiveLength,
side: SheetMetalThicknessSide,
},
SheetMetalEdgeFlange {
edges: EdgeSelection,
height: SheetMetalFlangeHeight,
angle: Angle,
height_datum: SheetMetalHeightDatum,
bend_position: SheetMetalBendPosition,
width: SheetMetalFlangeWidth,
bend_radius: PositiveLength,
},
SheetMetalHem {
edges: EdgeSelection,
form: SheetMetalHemForm,
direction: SheetMetalHemDirection,
bend_radius: PositiveLength,
},
Fillet {
#[serde(deserialize_with = "deserialize_local_groups")]
groups: NonEmptyMembers<FilletGroup>,
},
FullRoundFillet {
#[serde(deserialize_with = "deserialize_local_groups")]
groups: NonEmptyMembers<FullRoundFilletGroup>,
},
FaceBlend {
operands: FaceBlendOperands,
radius: RadiusSpec,
},
Chamfer {
#[serde(deserialize_with = "deserialize_local_groups")]
groups: NonEmptyMembers<ChamferGroup>,
#[serde(default)]
flip_direction: bool,
},
Shell {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_feature_operation_bodies"
)]
bodies: Option<BodySelection>,
removed_faces: FaceSelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_thickness"
)]
thickness: Option<PositiveLength>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_outward"
)]
outward: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_mode"
)]
mode: Option<ShellMode>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_join"
)]
join: Option<ShellJoin>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_resolve_intersections"
)]
resolve_intersections: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_self_intersections"
)]
allow_self_intersections: Option<bool>,
},
OffsetShape {
source: BodySelection,
distance: NonZeroLength,
mode: ShellMode,
join: ShellJoin,
resolve_intersections: bool,
allow_self_intersections: bool,
fill: bool,
planar: bool,
},
Compound {
members: BodySelection,
},
RefineShape {
source: BodySelection,
},
ReverseShape {
source: BodySelection,
},
RuledBetweenCurves {
first: PathRef,
second: PathRef,
orientation: RuledCurveOrientation,
},
SectionShape {
operands: SectionOperands,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_approximate"
)]
approximate: Option<bool>,
},
MirrorShape {
source: BodySelection,
plane_origin: FinitePoint3,
plane_normal: UnitVector3,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_plane_reference"
)]
plane_reference: Option<FaceSelection>,
},
Thicken {
faces: FaceSelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_thickness"
)]
thickness: Option<PositiveLength>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_side"
)]
side: Option<ThickenSide>,
},
OffsetSurface {
faces: FaceSelection,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
distance: Option<Length>,
},
KnitSurface {
faces: FaceSelection,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
merge_entities: Option<bool>,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
create_solid: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_gap_tolerance"
)]
gap_tolerance: Option<NonNegativeLength>,
},
SewBodies {
bodies: SewBodySelection,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
gap_tolerance: Option<PositiveLength>,
},
FilledSurface {
boundary: SurfaceBoundary,
support_faces: FaceSelection,
#[serde(
default = "FilledSurfaceContinuityState::unresolved",
skip_serializing_if = "FilledSurfaceContinuityState::is_unresolved"
)]
continuity: FilledSurfaceContinuityState,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_merge_result"
)]
merge_result: Option<bool>,
},
TrimSurface {
faces: FaceSelection,
tool: PathRef,
keep: TrimRegion,
},
ExtendSurface {
faces: FaceSelection,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
distance: Option<PositiveLength>,
method: SurfaceExtension,
},
RuledSurface {
edges: EdgeSelection,
support_faces: FaceSelection,
mode: RuledSurfaceMode,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_angle"
)]
angle: Option<Angle>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_alternate_face"
)]
alternate_face: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_corner"
)]
corner: Option<RuledSurfaceCorner>,
},
Draft {
faces: FaceSelection,
anchor: DraftAnchor,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
angle: Option<SlopeAngle>,
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
outward: Option<bool>,
},
Combine {
operands: CombineOperands,
op: BooleanKind,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
keep_tools: bool,
},
BoundaryFill {
tools: BodySelection,
#[serde(deserialize_with = "deserialize_local_cells")]
cells: NonEmptyMembers<BodySelection>,
},
CutWithSurface {
targets: BodySelection,
tools: FaceSelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_reverse"
)]
reverse: Option<bool>,
},
TrimBodies {
operands: TrimBodyOperands,
keep: BodyTrimSide,
},
SplitBody {
targets: BodySelection,
tools: FaceSelection,
},
SplitFace {
targets: FaceSelection,
tool: SplitFaceTool,
},
DeleteBody {
bodies: BodySelection,
mode: BodyRetentionMode,
},
DeleteFace {
faces: FaceSelection,
heal: bool,
},
ReplaceFace {
operands: ReplaceFaceOperands,
},
MoveFace {
faces: FaceSelection,
motion: FaceMotion,
},
MoveBody {
bodies: BodySelection,
translation: FiniteVector3,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_rotation"
)]
rotation: Option<AxisAngle>,
#[serde(default)]
copies: u32,
},
Dome {
faces: FaceSelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_height"
)]
height: Option<PositiveLength>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_elliptical"
)]
elliptical: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_reverse"
)]
reverse: Option<bool>,
},
Flex {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_axis"
)]
axis: Option<FeatureDirection3>,
mode: FlexMode,
},
Scale {
bodies: BodySelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_center"
)]
center: Option<ScaleCenter>,
factors: ScaleFactors,
},
Hole {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_profile"
)]
profile: Option<PlanarProfileRef>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_profile_filter"
)]
profile_filter: Option<HoleProfileFilter>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_face"
)]
face: Option<FaceSelection>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_direction"
)]
direction: Option<FeatureDirection3>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_placements"
)]
placements: Option<Vec<HolePlacement>>,
shape: HoleShape,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_feature_operation_extent"
)]
extent: Option<LinearTermination>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_bottom"
)]
bottom: Option<HoleBottom>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_taper_angle"
)]
taper_angle: Option<InteriorAngle>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_multi_profile_faces"
)]
allow_multi_profile_faces: Option<bool>,
},
Pattern {
seeds: Vec<PatternSeed>,
pattern: PatternKind,
},
Unresolved {
family: UnresolvedFamily,
},
Native {
kind: NativeFeatureKind,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[serde(deserialize_with = "cadmpeg_core::distinct_keys::btree_map")]
parameters: BTreeMap<NonBlankString, String>,
},
Extrude {
profile: ProfileRef,
#[serde(default)]
direction: ExtrudeDirection,
#[serde(default)]
start: ExtrudeStart,
extent: ExtrudeExtent,
op: BooleanOp,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_solid"
)]
solid: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_face_maker"
)]
face_maker: Option<FaceMaker>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_inner_wire_taper"
)]
inner_wire_taper: Option<InnerWireTaper>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_length_along_profile_normal"
)]
length_along_profile_normal: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_multi_profile_faces"
)]
allow_multi_profile_faces: Option<bool>,
},
Loft {
sections: Vec<LoftSection>,
guidance: LoftGuidance,
op: BooleanOp,
#[serde(default)]
closed: bool,
#[serde(default = "crate::default_true")]
solid: bool,
#[serde(default)]
ruled: bool,
#[serde(default)]
linearize: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_max_degree"
)]
max_degree: Option<std::num::NonZeroU32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_multi_profile_faces"
)]
allow_multi_profile_faces: Option<bool>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "definition", rename_all = "snake_case", deny_unknown_fields)]
pub enum FeatureDefinition {
PostProcess {
operation: FeatureOperation,
refine: bool,
fuzzy_tolerance: FuzzyTolerance,
},
#[serde(untagged)]
Operation(FeatureOperation),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum UnresolvedFamily {
DatumPlane,
DatumAxis,
DatumPoint,
DatumCoordinateSystem,
BridgeCurve,
Brep,
Cylinder,
Cone,
Sphere,
Thread,
DetailedThread,
Loft,
ThroughCurveMesh,
FreeformSurface,
Extrude,
Revolve,
Fillet,
ExtractFace,
CopyFace,
LinkedFace,
FillHole,
BoundarySurface,
Draft,
DeleteFace,
MoveFace,
MirrorFace,
SubdivisionBody,
TopologyOptimization,
MoveObject,
}
impl FeatureOperation {
pub fn body_output_family(&self) -> Option<&'static str> {
match self {
Self::BaseFeature { .. } => Some("base feature"),
Self::Unresolved { family } => match family {
UnresolvedFamily::Brep => Some("brep"),
UnresolvedFamily::ThroughCurveMesh => Some("through curve mesh"),
UnresolvedFamily::ExtractFace => Some("extract face"),
UnresolvedFamily::CopyFace => Some("copy face"),
UnresolvedFamily::LinkedFace => Some("linked face"),
UnresolvedFamily::FillHole => Some("fill hole"),
UnresolvedFamily::MoveFace => Some("move face"),
UnresolvedFamily::MirrorFace => Some("mirror face"),
UnresolvedFamily::SubdivisionBody => Some("subdivision body"),
UnresolvedFamily::TopologyOptimization => Some("topology optimization"),
UnresolvedFamily::MoveObject => Some("move object"),
UnresolvedFamily::Cylinder => Some("cylinder"),
UnresolvedFamily::Cone => Some("cone"),
UnresolvedFamily::Sphere => Some("sphere"),
UnresolvedFamily::Thread => Some("thread"),
UnresolvedFamily::DetailedThread => Some("detailed thread"),
UnresolvedFamily::Extrude => Some("extrude"),
UnresolvedFamily::Revolve => Some("revolve"),
UnresolvedFamily::Fillet => Some("fillet"),
UnresolvedFamily::DeleteFace => Some("delete face"),
UnresolvedFamily::DatumPlane
| UnresolvedFamily::DatumAxis
| UnresolvedFamily::DatumPoint
| UnresolvedFamily::DatumCoordinateSystem
| UnresolvedFamily::BridgeCurve
| UnresolvedFamily::Loft
| UnresolvedFamily::FreeformSurface
| UnresolvedFamily::BoundarySurface
| UnresolvedFamily::Draft => None,
},
Self::Block { .. } => Some("block"),
Self::Sphere { .. } => Some("sphere"),
Self::ExtractBody { .. } => Some("extract body"),
Self::Loft { .. } => Some("loft"),
Self::TrimSurface { .. } => Some("trim surface"),
Self::ExtendSurface { .. } => Some("extend surface"),
Self::RuledSurface { .. } => Some("ruled surface"),
Self::Hole { .. } => Some("hole"),
Self::Rib { .. } => Some("rib"),
Self::Chamfer { .. } => Some("chamfer"),
Self::Fillet { .. } => Some("fillet"),
Self::FullRoundFillet { .. } => Some("fillet"),
Self::FaceBlend { .. } => Some("face blend"),
Self::Shell { .. } => Some("shell"),
Self::SewBodies { .. } => Some("sew bodies"),
Self::TrimBodies { .. } => Some("trim bodies"),
Self::Extrude { .. } => Some("extrude"),
Self::Revolve { .. } => Some("revolve"),
Self::Sweep { .. } => Some("sweep"),
Self::OffsetSurface { .. } => Some("offset surface"),
Self::Thicken { .. } => Some("thicken"),
Self::Draft { .. } => Some("draft"),
Self::Pattern { .. } => Some("pattern"),
Self::Combine { .. } => Some("body combine"),
Self::ReplaceFace { .. } => Some("replace face"),
Self::DeleteFace { .. } => Some("delete face"),
_ => None,
}
}
}
impl FeatureDefinition {
#[must_use]
pub const fn operation(&self) -> &FeatureOperation {
match self {
Self::PostProcess { operation, .. } | Self::Operation(operation) => operation,
}
}
#[must_use]
pub fn body_output_family(&self) -> Option<&'static str> {
match self {
Self::PostProcess { operation, .. } => operation.body_output_family(),
Self::Operation(operation) => operation.body_output_family(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum ExtrudeDirection {
Unresolved {},
ProfileNormal {},
ReversedProfileNormal {},
Explicit {
vector: FeatureDirection3,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_source"
)]
source: Option<ExtrusionDirectionSource>,
},
}
impl Default for ExtrudeDirection {
fn default() -> Self {
Self::ProfileNormal {}
}
}
#[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 GeometryImportFormat {
Step,
Iges,
Brep,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
tag = "kind",
content = "value",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum FuzzyTolerance {
KernelDefault,
Automatic,
Explicit(PositiveLength),
}
#[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 ShellMode {
Skin,
Pipe,
BothSides,
}
#[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 ShellJoin {
Arc,
Tangent,
Intersection,
}
#[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 RuledCurveOrientation {
Automatic,
Forward,
Reversed,
}
const INVALID_PRIMITIVE_DIMENSIONS: &str = "primitive dimensions are invalid";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "PrimitiveSolidKind", into = "PrimitiveSolidKind")]
pub struct PrimitiveSolid(PrimitiveSolidKind);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrimitiveSolidScaleError {
NonFinite,
Admission(&'static str),
}
impl PrimitiveSolid {
pub fn new(kind: PrimitiveSolidKind) -> Result<Self, &'static str> {
let positive = |value: Length| value.get() > 0.0;
let valid = match &kind {
PrimitiveSolidKind::Box {
length,
width,
height,
} => positive(*length) && positive(*width) && positive(*height),
PrimitiveSolidKind::Cylinder { radius, height, .. } => {
positive(*radius) && positive(*height)
}
PrimitiveSolidKind::Cone {
radius1,
radius2,
height,
..
} => {
radius1.get() >= 0.0
&& radius2.get() >= 0.0
&& (positive(*radius1) || positive(*radius2))
&& positive(*height)
}
PrimitiveSolidKind::Sphere {
radius,
latitude1,
latitude2,
..
} => positive(*radius) && latitude1.get() < latitude2.get(),
PrimitiveSolidKind::Ellipsoid {
x_radius,
y_radius,
z_radius,
latitude1,
latitude2,
..
} => {
positive(*x_radius)
&& positive(*y_radius)
&& positive(*z_radius)
&& latitude1.get() < latitude2.get()
}
PrimitiveSolidKind::Torus {
major_radius,
minor_radius,
latitude1,
latitude2,
..
} => {
positive(*major_radius)
&& positive(*minor_radius)
&& latitude1.get() < latitude2.get()
}
PrimitiveSolidKind::Prism {
sides,
circumradius,
height,
} => *sides >= 3 && positive(*circumradius) && positive(*height),
PrimitiveSolidKind::Wedge {
xmin,
ymin,
zmin,
x2min,
z2min,
xmax,
ymax,
zmax,
x2max,
z2max,
} => {
xmax.get() > xmin.get()
&& ymax.get() > ymin.get()
&& zmax.get() > zmin.get()
&& x2max.get() >= x2min.get()
&& z2max.get() >= z2min.get()
}
};
valid
.then_some(Self(kind))
.ok_or(INVALID_PRIMITIVE_DIMENSIONS)
}
pub fn scaled(&self, scale: PositiveReal) -> Result<Self, PrimitiveSolidScaleError> {
let scaled = |value: Length| {
Length::new(value.get() * scale.get()).ok_or(PrimitiveSolidScaleError::NonFinite)
};
let positive = |value: Length| value.get() > 0.0;
let (kind, valid) = match &self.0 {
PrimitiveSolidKind::Box {
length,
width,
height,
} => {
let [length, width, height] = [scaled(*length)?, scaled(*width)?, scaled(*height)?];
(
PrimitiveSolidKind::Box {
length,
width,
height,
},
positive(length) && positive(width) && positive(height),
)
}
PrimitiveSolidKind::Cylinder {
radius,
height,
angle,
} => {
let [radius, height] = [scaled(*radius)?, scaled(*height)?];
(
PrimitiveSolidKind::Cylinder {
radius,
height,
angle: *angle,
},
positive(radius) && positive(height),
)
}
PrimitiveSolidKind::Cone {
radius1,
radius2,
height,
angle,
} => {
let [radius1, radius2, height] =
[scaled(*radius1)?, scaled(*radius2)?, scaled(*height)?];
(
PrimitiveSolidKind::Cone {
radius1,
radius2,
height,
angle: *angle,
},
(positive(radius1) || positive(radius2)) && positive(height),
)
}
PrimitiveSolidKind::Sphere {
radius,
latitude1,
latitude2,
longitude,
} => {
let radius = scaled(*radius)?;
(
PrimitiveSolidKind::Sphere {
radius,
latitude1: *latitude1,
latitude2: *latitude2,
longitude: *longitude,
},
positive(radius),
)
}
PrimitiveSolidKind::Ellipsoid {
x_radius,
y_radius,
z_radius,
latitude1,
latitude2,
longitude,
} => {
let [x_radius, y_radius, z_radius] =
[scaled(*x_radius)?, scaled(*y_radius)?, scaled(*z_radius)?];
(
PrimitiveSolidKind::Ellipsoid {
x_radius,
y_radius,
z_radius,
latitude1: *latitude1,
latitude2: *latitude2,
longitude: *longitude,
},
positive(x_radius) && positive(y_radius) && positive(z_radius),
)
}
PrimitiveSolidKind::Torus {
major_radius,
minor_radius,
latitude1,
latitude2,
longitude,
} => {
let [major_radius, minor_radius] = [scaled(*major_radius)?, scaled(*minor_radius)?];
(
PrimitiveSolidKind::Torus {
major_radius,
minor_radius,
latitude1: *latitude1,
latitude2: *latitude2,
longitude: *longitude,
},
positive(major_radius) && positive(minor_radius),
)
}
PrimitiveSolidKind::Prism {
sides,
circumradius,
height,
} => {
let [circumradius, height] = [scaled(*circumradius)?, scaled(*height)?];
(
PrimitiveSolidKind::Prism {
sides: *sides,
circumradius,
height,
},
positive(circumradius) && positive(height),
)
}
PrimitiveSolidKind::Wedge {
xmin,
ymin,
zmin,
x2min,
z2min,
xmax,
ymax,
zmax,
x2max,
z2max,
} => {
let [xmin, ymin, zmin, x2min, z2min, xmax, ymax, zmax, x2max, z2max] = [
scaled(*xmin)?,
scaled(*ymin)?,
scaled(*zmin)?,
scaled(*x2min)?,
scaled(*z2min)?,
scaled(*xmax)?,
scaled(*ymax)?,
scaled(*zmax)?,
scaled(*x2max)?,
scaled(*z2max)?,
];
(
PrimitiveSolidKind::Wedge {
xmin,
ymin,
zmin,
x2min,
z2min,
xmax,
ymax,
zmax,
x2max,
z2max,
},
xmax.get() > xmin.get() && ymax.get() > ymin.get() && zmax.get() > zmin.get(),
)
}
};
valid
.then_some(Self(kind))
.ok_or(PrimitiveSolidScaleError::Admission(
INVALID_PRIMITIVE_DIMENSIONS,
))
}
pub fn kind(&self) -> &PrimitiveSolidKind {
&self.0
}
}
impl TryFrom<PrimitiveSolidKind> for PrimitiveSolid {
type Error = &'static str;
fn try_from(kind: PrimitiveSolidKind) -> Result<Self, Self::Error> {
Self::new(kind)
}
}
impl From<PrimitiveSolid> for PrimitiveSolidKind {
fn from(solid: PrimitiveSolid) -> Self {
solid.0
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum PrimitiveSolidKind {
Box {
length: Length,
width: Length,
height: Length,
},
Cylinder {
radius: Length,
height: Length,
angle: Angle,
},
Cone {
radius1: Length,
radius2: Length,
height: Length,
angle: Angle,
},
Sphere {
radius: Length,
latitude1: Angle,
latitude2: Angle,
longitude: Angle,
},
Ellipsoid {
x_radius: Length,
y_radius: Length,
z_radius: Length,
latitude1: Angle,
latitude2: Angle,
longitude: Angle,
},
Torus {
major_radius: Length,
minor_radius: Length,
latitude1: Angle,
latitude2: Angle,
longitude: Angle,
},
Prism {
sides: u32,
circumradius: Length,
height: Length,
},
Wedge {
xmin: Length,
ymin: Length,
zmin: Length,
x2min: Length,
z2min: Length,
xmax: Length,
ymax: Length,
zmax: Length,
x2max: Length,
z2max: Length,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "state", rename_all = "snake_case", deny_unknown_fields)]
pub enum RevolveConstruction {
Unresolved(PartialRevolveConstruction),
Resolved {
profile: PlanarProfileRef,
axis: RevolutionAxis,
extent: RevolveExtent,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_solid"
)]
solid: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_face_maker_class"
)]
#[serde(rename = "face_maker_class")]
#[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
face_maker: Option<FaceMaker>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_fuse_order"
)]
fuse_order: Option<RevolutionFuseOrder>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_multi_profile_faces"
)]
allow_multi_profile_faces: Option<bool>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "missing", rename_all = "snake_case", deny_unknown_fields)]
pub enum PartialRevolveConstruction {
Profile {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_partial_revolve_construction_axis"
)]
axis: Option<RevolutionAxis>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_partial_revolve_construction_extent"
)]
extent: Option<RevolveExtent>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_solid"
)]
solid: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_face_maker_class"
)]
#[serde(rename = "face_maker_class")]
#[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
face_maker: Option<FaceMaker>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_fuse_order"
)]
fuse_order: Option<RevolutionFuseOrder>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_multi_profile_faces"
)]
allow_multi_profile_faces: Option<bool>,
},
Axis {
profile: PlanarProfileRef,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_partial_revolve_construction_extent"
)]
extent: Option<RevolveExtent>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_solid"
)]
solid: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_face_maker_class"
)]
#[serde(rename = "face_maker_class")]
#[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
face_maker: Option<FaceMaker>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_fuse_order"
)]
fuse_order: Option<RevolutionFuseOrder>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_multi_profile_faces"
)]
allow_multi_profile_faces: Option<bool>,
},
Extent {
profile: PlanarProfileRef,
axis: RevolutionAxis,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_solid"
)]
solid: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_face_maker_class"
)]
#[serde(rename = "face_maker_class")]
#[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
face_maker: Option<FaceMaker>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_fuse_order"
)]
fuse_order: Option<RevolutionFuseOrder>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_multi_profile_faces"
)]
allow_multi_profile_faces: Option<bool>,
},
}
struct RevolveSelections {
solid: Option<bool>,
face_maker: Option<FaceMaker>,
fuse_order: Option<RevolutionFuseOrder>,
allow_multi_profile_faces: Option<bool>,
}
impl RevolveConstruction {
fn selections(&self) -> RevolveSelections {
let (solid, face_maker, fuse_order, allow_multi_profile_faces) = match self {
Self::Resolved {
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
..
}
| Self::Unresolved(
PartialRevolveConstruction::Profile {
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
..
}
| PartialRevolveConstruction::Axis {
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
..
}
| PartialRevolveConstruction::Extent {
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
..
},
) => (solid, face_maker, fuse_order, allow_multi_profile_faces),
};
RevolveSelections {
solid: *solid,
face_maker: face_maker.clone(),
fuse_order: *fuse_order,
allow_multi_profile_faces: *allow_multi_profile_faces,
}
}
pub const fn is_resolved(&self) -> bool {
matches!(self, Self::Resolved { .. })
}
pub const fn profile(&self) -> Option<&PlanarProfileRef> {
match self {
Self::Unresolved(PartialRevolveConstruction::Profile { .. }) => None,
Self::Unresolved(
PartialRevolveConstruction::Axis { profile, .. }
| PartialRevolveConstruction::Extent { profile, .. },
)
| Self::Resolved { profile, .. } => Some(profile),
}
}
pub const fn profile_mut(&mut self) -> Option<&mut PlanarProfileRef> {
match self {
Self::Unresolved(PartialRevolveConstruction::Profile { .. }) => None,
Self::Unresolved(
PartialRevolveConstruction::Axis { profile, .. }
| PartialRevolveConstruction::Extent { profile, .. },
)
| Self::Resolved { profile, .. } => Some(profile),
}
}
pub fn set_profile(&mut self, profile: Option<PlanarProfileRef>) {
let RevolveSelections {
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
} = self.selections();
let axis = self.axis().cloned();
let extent = self.extent().cloned();
*self = match (profile, axis, extent) {
(None, axis, extent) => Self::Unresolved(PartialRevolveConstruction::Profile {
axis,
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
}),
(Some(profile), None, extent) => Self::Unresolved(PartialRevolveConstruction::Axis {
profile,
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
}),
(Some(profile), Some(axis), None) => {
Self::Unresolved(PartialRevolveConstruction::Extent {
profile,
axis,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
})
}
(Some(profile), Some(axis), Some(extent)) => Self::Resolved {
profile,
axis,
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
},
};
}
pub const fn axis(&self) -> Option<&RevolutionAxis> {
match self {
Self::Unresolved(
PartialRevolveConstruction::Profile { axis: None, .. }
| PartialRevolveConstruction::Axis { .. },
) => None,
Self::Unresolved(
PartialRevolveConstruction::Profile {
axis: Some(axis), ..
}
| PartialRevolveConstruction::Extent { axis, .. },
)
| Self::Resolved { axis, .. } => Some(axis),
}
}
pub const fn axis_mut(&mut self) -> Option<&mut RevolutionAxis> {
match self {
Self::Unresolved(
PartialRevolveConstruction::Profile { axis: None, .. }
| PartialRevolveConstruction::Axis { .. },
) => None,
Self::Unresolved(
PartialRevolveConstruction::Profile {
axis: Some(axis), ..
}
| PartialRevolveConstruction::Extent { axis, .. },
)
| Self::Resolved { axis, .. } => Some(axis),
}
}
pub fn set_axis(&mut self, axis: Option<RevolutionAxis>) {
let old = std::mem::replace(
self,
Self::Unresolved(PartialRevolveConstruction::Profile {
axis: None,
extent: None,
solid: None,
face_maker: None,
fuse_order: None,
allow_multi_profile_faces: None,
}),
);
let (profile, extent, solid, face_maker, fuse_order, allow_multi_profile_faces) = match old
{
Self::Unresolved(PartialRevolveConstruction::Profile {
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
..
}) => (
None,
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
),
Self::Unresolved(PartialRevolveConstruction::Axis {
profile,
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
}) => (
Some(profile),
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
),
Self::Unresolved(PartialRevolveConstruction::Extent {
profile,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
..
}) => (
Some(profile),
None,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
),
Self::Resolved {
profile,
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
..
} => (
Some(profile),
Some(extent),
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
),
};
*self = match (profile, axis, extent) {
(None, axis, extent) => Self::Unresolved(PartialRevolveConstruction::Profile {
axis,
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
}),
(Some(profile), None, extent) => Self::Unresolved(PartialRevolveConstruction::Axis {
profile,
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
}),
(Some(profile), Some(axis), None) => {
Self::Unresolved(PartialRevolveConstruction::Extent {
profile,
axis,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
})
}
(Some(profile), Some(axis), Some(extent)) => Self::Resolved {
profile,
axis,
extent,
solid,
face_maker,
fuse_order,
allow_multi_profile_faces,
},
};
}
pub const fn extent(&self) -> Option<&RevolveExtent> {
match self {
Self::Unresolved(
PartialRevolveConstruction::Profile { extent: None, .. }
| PartialRevolveConstruction::Axis { extent: None, .. }
| PartialRevolveConstruction::Extent { .. },
) => None,
Self::Unresolved(
PartialRevolveConstruction::Profile {
extent: Some(extent),
..
}
| PartialRevolveConstruction::Axis {
extent: Some(extent),
..
},
)
| Self::Resolved { extent, .. } => Some(extent),
}
}
pub const fn extent_mut(&mut self) -> Option<&mut RevolveExtent> {
match self {
Self::Unresolved(
PartialRevolveConstruction::Profile { extent: None, .. }
| PartialRevolveConstruction::Axis { extent: None, .. }
| PartialRevolveConstruction::Extent { .. },
) => None,
Self::Unresolved(
PartialRevolveConstruction::Profile {
extent: Some(extent),
..
}
| PartialRevolveConstruction::Axis {
extent: Some(extent),
..
},
)
| Self::Resolved { extent, .. } => Some(extent),
}
}
pub const fn solid(&self) -> Option<bool> {
match self {
Self::Resolved { solid, .. }
| Self::Unresolved(
PartialRevolveConstruction::Profile { solid, .. }
| PartialRevolveConstruction::Axis { solid, .. }
| PartialRevolveConstruction::Extent { solid, .. },
) => *solid,
}
}
pub const fn face_maker(&self) -> Option<&FaceMaker> {
match self {
Self::Resolved { face_maker, .. }
| Self::Unresolved(
PartialRevolveConstruction::Profile { face_maker, .. }
| PartialRevolveConstruction::Axis { face_maker, .. }
| PartialRevolveConstruction::Extent { face_maker, .. },
) => face_maker.as_ref(),
}
}
pub const fn fuse_order(&self) -> Option<RevolutionFuseOrder> {
match self {
Self::Resolved { fuse_order, .. }
| Self::Unresolved(
PartialRevolveConstruction::Profile { fuse_order, .. }
| PartialRevolveConstruction::Axis { fuse_order, .. }
| PartialRevolveConstruction::Extent { fuse_order, .. },
) => *fuse_order,
}
}
pub const fn allow_multi_profile_faces(&self) -> Option<bool> {
match self {
Self::Resolved {
allow_multi_profile_faces,
..
}
| Self::Unresolved(
PartialRevolveConstruction::Profile {
allow_multi_profile_faces,
..
}
| PartialRevolveConstruction::Axis {
allow_multi_profile_faces,
..
}
| PartialRevolveConstruction::Extent {
allow_multi_profile_faces,
..
},
) => *allow_multi_profile_faces,
}
}
}
#[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 RevolutionFuseOrder {
BaseFirst,
FeatureFirst,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RevolutionAxis {
pub origin: FinitePoint3,
pub direction: FeatureDirection3,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_revolution_axis_reference"
)]
pub reference: Option<PathRef>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RibConstruction {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_profile"
)]
pub profile: Option<PlanarProfileRef>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_rib_construction_direction"
)]
pub direction: Option<FeatureDirection3>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_thickness"
)]
pub thickness: Option<PositiveLength>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_rib_construction_side"
)]
pub side: Option<RibSide>,
#[serde(default)]
pub draft: RibDraft,
}
#[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 RibSide {
OneSided,
Centered,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", content = "angle", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum RibDraft {
#[default]
Unresolved,
None,
Angle(SlopeAngle),
}
#[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 FeatureTreeNodeRole {
Annotations,
AmbientLight,
Comments,
CrossSections,
DesignBinder,
Details,
DissectedProfile,
DirectionalLight,
Equations,
ExplodedViews,
Favorites,
FeatureFolder,
History,
LightsAndCameras,
Markups,
ModelOrigin,
PointLight,
Materials,
Notes,
SelectionSets,
Sensors,
SheetMetal,
SolidBodies,
SpotLight,
SurfaceBodies,
Tables,
}
#[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 CosmeticThreadExtent {
Blind {
length: PositiveLength,
},
Through {},
}
#[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 PrincipalPlane {
Front,
Top,
Right,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(from = "SketchFeatureBindingWire", into = "SketchFeatureBindingWire")]
#[serde(deny_unknown_fields)]
pub enum SketchFeatureBinding {
Unresolved,
Planar(Option<crate::sketches::SketchId>),
}
impl SketchFeatureBinding {
pub fn id(&self) -> Option<&crate::sketches::SketchId> {
match self {
Self::Unresolved => None,
Self::Planar(sketch) => sketch.as_ref(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "space", rename_all = "snake_case", deny_unknown_fields)]
enum SketchFeatureBindingWire {
Unresolved {},
Planar {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_feature_operation_sketch"
)]
sketch: Option<crate::sketches::SketchId>,
},
}
impl From<SketchFeatureBindingWire> for SketchFeatureBinding {
fn from(value: SketchFeatureBindingWire) -> Self {
match value {
SketchFeatureBindingWire::Unresolved {} => Self::Unresolved,
SketchFeatureBindingWire::Planar { sketch } => Self::Planar(sketch),
}
}
}
#[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 BodyTrimSide {
Unresolved,
Forward,
Reverse,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum CurveProjectionDirection {
Vector(FeatureDirection3),
State(CurveProjectionDirectionState),
}
impl Default for CurveProjectionDirection {
fn default() -> Self {
Self::State(CurveProjectionDirectionState::TargetNormal)
}
}
#[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 CurveProjectionDirectionState {
Unresolved,
TargetNormal,
}
#[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 BodyRetentionMode {
Unresolved,
DeleteSelected,
KeepSelected,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum WrapMode {
Emboss {
depth: PositiveLength,
},
Deboss {
depth: PositiveLength,
},
Scribe,
}
#[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 SurfaceContinuity {
Contact,
Tangent,
Curvature,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct FilledSurfaceContinuity {
pub conditions: NonEmptyMembers<SurfaceContinuity>,
}
impl FilledSurfaceContinuity {
#[must_use]
pub fn uniform(&self) -> Option<SurfaceContinuity> {
let mut components = self.conditions.iter();
let first = *components.next()?;
components
.all(|continuity| continuity == &first)
.then_some(first)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct FilledSurfaceContinuityState(
#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")]
Option<FilledSurfaceContinuity>,
);
impl FilledSurfaceContinuityState {
#[must_use]
pub const fn unresolved() -> Self {
Self(None)
}
#[must_use]
pub fn uniform(continuity: SurfaceContinuity) -> Self {
Self(Some(FilledSurfaceContinuity {
conditions: NonEmptyMembers::one(continuity),
}))
}
#[must_use]
pub const fn per_boundary(conditions: NonEmptyMembers<SurfaceContinuity>) -> Self {
Self(Some(FilledSurfaceContinuity { conditions }))
}
#[must_use]
pub const fn resolved(&self) -> Option<&FilledSurfaceContinuity> {
self.0.as_ref()
}
#[must_use]
pub fn uniform_value(&self) -> Option<SurfaceContinuity> {
self.0.as_ref().and_then(FilledSurfaceContinuity::uniform)
}
#[must_use]
pub const fn is_unresolved(&self) -> bool {
self.0.is_none()
}
}
#[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 SurfaceBoundary {
Edges(EdgeSelection),
Path(PathRef),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum TrimRegion {
Unresolved,
Inside,
Outside,
Cells(TrimCellSelection),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "TrimCellSelectionWire")]
pub struct TrimCellSelection {
removed: Vec<u64>,
total: u64,
}
impl TrimCellSelection {
#[must_use]
pub fn new(removed: Vec<u64>, total: u64) -> Option<Self> {
let mut seen = HashSet::with_capacity(removed.len());
(total > 0
&& !removed.is_empty()
&& removed
.iter()
.all(|ordinal| *ordinal > 0 && *ordinal <= total)
&& removed.iter().all(|ordinal| seen.insert(*ordinal)))
.then_some(Self { removed, total })
}
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct TrimCellSelectionWire {
removed: Vec<u64>,
total: u64,
}
impl TryFrom<TrimCellSelectionWire> for TrimCellSelection {
type Error = &'static str;
fn try_from(wire: TrimCellSelectionWire) -> Result<Self, Self::Error> {
Self::new(wire.removed, wire.total)
.ok_or("trim cell selection removed must be nonempty, unique, and within total")
}
}
#[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 SurfaceExtension {
Unresolved,
Natural,
Linear,
Perpendicular,
}
#[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 RuledSurfaceMode {
Normal {
distance: PositiveLength,
},
Tangent {
distance: PositiveLength,
},
Direction {
direction: FeatureDirection3,
distance: PositiveLength,
},
}
#[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 RuledSurfaceCorner {
Rounded,
Mitered,
}
#[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 ScaleCenter {
Centroid,
ModelOrigin,
Point(FinitePoint3),
Native(String),
}
#[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 ScaleFactors {
Unresolved {},
Uniform {
factor: NonZeroReal,
},
PerAxis {
factors: [NonZeroReal; 3],
},
}
impl ScaleFactors {
#[must_use]
pub fn resolved(self) -> Option<Vector3> {
match self {
Self::Unresolved {} => None,
Self::Uniform { factor } => {
Some(Vector3::new(factor.get(), factor.get(), factor.get()))
}
Self::PerAxis { factors } => Some(Vector3::new(
factors[0].get(),
factors[1].get(),
factors[2].get(),
)),
}
}
}
#[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 ThickenSide {
Forward,
Reverse,
Both,
}
#[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 SheetMetalHeightDatum {
InnerFaces,
OuterFaces,
}
#[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 SheetMetalBendPosition {
Outside,
Inside,
Adjacent,
TangentToSide,
}
#[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 SheetMetalHemForm {
Flat {
length: PositiveLength,
},
Open {
gap: NonNegativeLength,
length: PositiveLength,
},
GapLength {
gap: NonNegativeLength,
length: PositiveLength,
},
Rolled {
radius: PositiveLength,
angle: Angle,
},
Teardrop {
gap: NonNegativeLength,
length: PositiveLength,
radius: PositiveLength,
},
}
#[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 SheetMetalHemDirection {
Forward,
Reverse,
Unresolved,
}
#[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 SheetMetalFlangeHeightTarget {
Feature(FeatureId),
Native(String),
}
#[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 SheetMetalFlangeHeight {
Distance(PositiveLength),
ToObject {
target: SheetMetalFlangeHeightTarget,
offset: Length,
},
}
#[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 SheetMetalFlangeWidth {
FullEdge,
Symmetric {
width: PositiveLength,
},
TwoSides {
first: PositiveLength,
second: PositiveLength,
},
TwoSidesPerEdge {
widths: SheetMetalFlangeEdgeWidths,
},
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct SheetMetalFlangeEdgeWidths(Vec<SheetMetalFlangeTwoSidedWidth>);
impl SheetMetalFlangeEdgeWidths {
pub fn new(widths: Vec<SheetMetalFlangeTwoSidedWidth>) -> Result<Self, &'static str> {
if widths.is_empty() {
Err("sheet-metal flange widths must contain at least one pair")
} else {
Ok(Self(widths))
}
}
pub fn as_mut_slice(&mut self) -> &mut [SheetMetalFlangeTwoSidedWidth] {
&mut self.0
}
}
impl<'de> Deserialize<'de> for SheetMetalFlangeEdgeWidths {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Self::new(Vec::deserialize(deserializer)?).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct SheetMetalFlangeTwoSidedWidth {
#[serde(deserialize_with = "deserialize_flange_first")]
pub first: PositiveLength,
#[serde(deserialize_with = "deserialize_flange_second")]
pub second: PositiveLength,
}
#[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 SheetMetalThicknessSide {
Forward,
Symmetric,
}
selection_field_deserializer!(deserialize_selection_native, "native");
selection_field_deserializer!(deserialize_selection_local_id, "local_id");
selection_field_deserializer!(deserialize_selection_edges, "edges");
selection_field_deserializer!(deserialize_selection_faces, "faces");
selection_field_deserializer!(deserialize_selection_unresolved, "unresolved");
selection_field_deserializer!(deserialize_flange_first, "first");
selection_field_deserializer!(deserialize_flange_second, "second");
selection_field_deserializer!(deserialize_selection_profiles, "profiles");
selection_field_deserializer!(deserialize_selection_entities, "entities");
selection_field_deserializer!(deserialize_selection_selections, "selections");
selection_field_deserializer!(deserialize_selection_curves, "curves");
selection_field_deserializer!(deserialize_profile_parameter_range, "parameter_range");
selection_field_deserializer!(deserialize_local_tessellations, "tessellations");
selection_field_deserializer!(deserialize_local_segments, "segments");
selection_field_deserializer!(deserialize_local_axis_native_ref, "axis_native_ref");
selection_field_deserializer!(deserialize_local_groups, "groups");
selection_field_deserializer!(deserialize_local_cells, "cells");
selection_field_deserializer!(deserialize_local_document, "document");
selection_field_deserializer!(deserialize_local_object, "object");
selection_field_deserializer!(deserialize_local_reference, "reference");
selection_field_deserializer!(deserialize_local_subelements, "subelements");
selection_field_deserializer!(deserialize_local_value, "value");
#[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 EdgeSelection {
Unresolved,
All,
Edges(Vec<EdgeId>),
Resolved {
edges: Vec<EdgeId>,
native: String,
},
Historical {
state: FeatureInputTopologyId,
#[serde(deserialize_with = "deserialize_selection_edges")]
edges: SelectionMembers<HistoricalEdgeId>,
#[serde(deserialize_with = "deserialize_selection_native")]
native: NonBlankString,
},
HistoricalPartial {
state: FeatureInputTopologyId,
#[serde(deserialize_with = "deserialize_selection_edges")]
edges: DistinctMembers<HistoricalEdgeId>,
#[serde(deserialize_with = "deserialize_selection_unresolved")]
unresolved: NativeSelections,
#[serde(deserialize_with = "deserialize_selection_native")]
native: NonBlankString,
},
Generated {
#[serde(deserialize_with = "deserialize_selection_edges")]
edges: NonEmptyMembers<GeneratedEdgeRef>,
#[serde(deserialize_with = "deserialize_selection_native")]
native: SelectionReference,
},
Native(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct GeneratedEdgeRef {
pub feature: FeatureId,
#[serde(deserialize_with = "deserialize_selection_local_id")]
pub local_id: SelectionReference,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct GeneratedFaceRef {
pub feature: FeatureId,
#[serde(deserialize_with = "deserialize_selection_local_id")]
pub local_id: SelectionReference,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct GeneratedVertexRef {
pub feature: FeatureId,
#[serde(deserialize_with = "deserialize_selection_local_id")]
pub local_id: SelectionReference,
}
#[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 VertexSelection {
Unresolved,
Generated {
vertex: GeneratedVertexRef,
#[serde(deserialize_with = "deserialize_selection_native")]
native: SelectionReference,
},
Historical {
state: FeatureInputTopologyId,
vertex: HistoricalVertexId,
#[serde(deserialize_with = "deserialize_selection_native")]
native: NonBlankString,
},
Native(#[serde(deserialize_with = "deserialize_selection_native")] SelectionReference),
}
#[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 FaceSelection {
Unresolved,
Faces(Vec<FaceId>),
Resolved {
faces: Vec<FaceId>,
native: String,
},
Historical {
state: FeatureInputTopologyId,
#[serde(deserialize_with = "deserialize_selection_faces")]
faces: SelectionMembers<HistoricalFaceId>,
#[serde(deserialize_with = "deserialize_selection_native")]
native: NonBlankString,
},
HistoricalPartial {
state: FeatureInputTopologyId,
#[serde(deserialize_with = "deserialize_selection_faces")]
faces: DistinctMembers<HistoricalFaceId>,
#[serde(deserialize_with = "deserialize_selection_unresolved")]
unresolved: NativeSelections,
#[serde(deserialize_with = "deserialize_selection_native")]
native: NonBlankString,
},
Generated {
#[serde(deserialize_with = "deserialize_selection_faces")]
faces: NonEmptyMembers<GeneratedFaceRef>,
#[serde(deserialize_with = "deserialize_selection_native")]
native: SelectionReference,
},
Native(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct NonEmptyMembers<T>(Vec<T>);
impl<T> TryFrom<Vec<T>> for NonEmptyMembers<T> {
type Error = BodySelectionError;
fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
if value.is_empty() {
return Err(BodySelectionError::Empty);
}
Ok(Self(value))
}
}
impl<T> NonEmptyMembers<T> {
pub fn one(member: T) -> Self {
Self(vec![member])
}
pub fn push(&mut self, member: T) {
self.0.push(member);
}
pub fn map<U>(self, map: impl FnMut(T) -> U) -> NonEmptyMembers<U> {
NonEmptyMembers(self.0.into_iter().map(map).collect())
}
pub fn as_slice(&self) -> &[T] {
&self.0
}
}
impl<T> std::ops::Deref for NonEmptyMembers<T> {
type Target = [T];
fn deref(&self) -> &[T] {
&self.0
}
}
impl<T> IntoIterator for NonEmptyMembers<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a, T> IntoIterator for &'a NonEmptyMembers<T> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for NonEmptyMembers<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::try_from(Vec::<T>::deserialize(deserializer)?).map_err(serde::de::Error::custom)
}
}
impl<T> std::ops::DerefMut for NonEmptyMembers<T> {
fn deref_mut(&mut self) -> &mut [T] {
&mut self.0
}
}
impl<'a, T> IntoIterator for &'a mut NonEmptyMembers<T> {
type Item = &'a mut T;
type IntoIter = std::slice::IterMut<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}
impl VertexSelection {
pub fn generated(
vertex: GeneratedVertexRef,
native: String,
) -> Result<Self, BodySelectionError> {
SelectionReference::new(native).map(|native| Self::Generated { vertex, native })
}
pub fn historical(
state: FeatureInputTopologyId,
vertex: HistoricalVertexId,
native: String,
) -> Result<Self, BodySelectionError> {
NonBlankString::new(native)
.ok_or(BodySelectionError::BlankNativeMember)
.map(|native| Self::Historical {
state,
vertex,
native,
})
}
pub fn native(native: String) -> Result<Self, BodySelectionError> {
SelectionReference::new(native).map(Self::Native)
}
}
impl EdgeSelection {
pub fn historical(
state: FeatureInputTopologyId,
edges: Vec<HistoricalEdgeId>,
native: String,
) -> Result<Self, BodySelectionError> {
let edges = SelectionMembers::try_from(edges)?;
let native = NonBlankString::new(native).ok_or(BodySelectionError::BlankNativeMember)?;
Ok(Self::Historical {
state,
edges,
native,
})
}
pub fn historical_partial(
state: FeatureInputTopologyId,
edges: Vec<HistoricalEdgeId>,
unresolved: Vec<String>,
native: String,
) -> Result<Self, BodySelectionError> {
let edges =
DistinctMembers::try_from(edges).map_err(|_| BodySelectionError::RepeatedBody)?;
let unresolved = NativeSelections::try_from(unresolved)?;
let native = NonBlankString::new(native).ok_or(BodySelectionError::BlankNativeMember)?;
Ok(Self::HistoricalPartial {
state,
edges,
unresolved,
native,
})
}
pub fn generated(
edges: Vec<GeneratedEdgeRef>,
native: String,
) -> Result<Self, BodySelectionError> {
let edges = edges.try_into()?;
SelectionReference::new(native).map(|native| Self::Generated { edges, native })
}
}
impl FaceSelection {
pub fn historical(
state: FeatureInputTopologyId,
faces: Vec<HistoricalFaceId>,
native: String,
) -> Result<Self, BodySelectionError> {
let faces = SelectionMembers::try_from(faces)?;
let native = NonBlankString::new(native).ok_or(BodySelectionError::BlankNativeMember)?;
Ok(Self::Historical {
state,
faces,
native,
})
}
pub fn historical_partial(
state: FeatureInputTopologyId,
faces: Vec<HistoricalFaceId>,
unresolved: Vec<String>,
native: String,
) -> Result<Self, BodySelectionError> {
let faces =
DistinctMembers::try_from(faces).map_err(|_| BodySelectionError::RepeatedBody)?;
let unresolved = NativeSelections::try_from(unresolved)?;
let native = NonBlankString::new(native).ok_or(BodySelectionError::BlankNativeMember)?;
Ok(Self::HistoricalPartial {
state,
faces,
unresolved,
native,
})
}
pub fn generated(
faces: Vec<GeneratedFaceRef>,
native: String,
) -> Result<Self, BodySelectionError> {
let faces = faces.try_into()?;
SelectionReference::new(native).map(|native| Self::Generated { faces, native })
}
}
impl GeneratedEdgeRef {
pub fn new(feature: FeatureId, local_id: String) -> Result<Self, BodySelectionError> {
SelectionReference::new(local_id).map(|local_id| Self { feature, local_id })
}
}
impl GeneratedFaceRef {
pub fn new(feature: FeatureId, local_id: String) -> Result<Self, BodySelectionError> {
SelectionReference::new(local_id).map(|local_id| Self { feature, local_id })
}
}
impl GeneratedVertexRef {
pub fn new(feature: FeatureId, local_id: String) -> Result<Self, BodySelectionError> {
SelectionReference::new(local_id).map(|local_id| Self { feature, local_id })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct SelectionReference(String);
impl TryFrom<String> for SelectionReference {
type Error = BodySelectionError;
fn try_from(value: String) -> Result<Self, Self::Error> {
if value.trim().is_empty() {
return Err(BodySelectionError::BlankNativeMember);
}
Ok(Self(value))
}
}
impl SelectionReference {
pub fn new(value: String) -> Result<Self, BodySelectionError> {
Self::try_from(value)
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::ops::Deref for SelectionReference {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl PartialEq<str> for SelectionReference {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&str> for SelectionReference {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl<'de> Deserialize<'de> for SelectionReference {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::try_from(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum FeatureCollectionError {
#[error("{0}")]
Invalid(&'static str),
}
impl From<FeatureCollectionError> for cadmpeg_core::CodecError {
fn from(error: FeatureCollectionError) -> Self {
match error {
FeatureCollectionError::Invalid(message) => Self::malformed(message),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct DistinctMembers<T>(Vec<T>);
impl<T> Default for DistinctMembers<T> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<T: Eq + std::hash::Hash> TryFrom<Vec<T>> for DistinctMembers<T> {
type Error = FeatureCollectionError;
fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
if !membership::distinct(&value, |_| true) {
return Err(FeatureCollectionError::Invalid("members must be distinct"));
}
Ok(Self(value))
}
}
impl<T: PartialEq> DistinctMembers<T> {
pub fn insert(&mut self, value: T) -> bool {
membership::insert(&mut self.0, value)
}
}
impl<T> DistinctMembers<T> {
pub fn clear(&mut self) {
self.0.clear();
}
pub fn retain(&mut self, predicate: impl FnMut(&T) -> bool) {
self.0.retain(predicate);
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn as_slice(&self) -> &[T] {
&self.0
}
}
impl<T: PartialEq> Extend<T> for DistinctMembers<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for member in iter {
membership::insert(&mut self.0, member);
}
}
}
impl<T: PartialEq> FromIterator<T> for DistinctMembers<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut members = Self::default();
members.extend(iter);
members
}
}
impl<T> std::ops::Deref for DistinctMembers<T> {
type Target = [T];
fn deref(&self) -> &[T] {
&self.0
}
}
impl<'a, T> IntoIterator for &'a DistinctMembers<T> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'de, T: Deserialize<'de> + Eq + std::hash::Hash> Deserialize<'de> for DistinctMembers<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let values = Vec::<T>::deserialize(deserializer)?;
Self::try_from(values).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct SelectionMembers<T>(Vec<T>);
impl<T: Eq + std::hash::Hash> TryFrom<Vec<T>> for SelectionMembers<T> {
type Error = BodySelectionError;
fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
if value.is_empty() {
return Err(BodySelectionError::Empty);
}
if !membership::distinct(&value, |_| true) {
return Err(BodySelectionError::RepeatedBody);
}
Ok(Self(value))
}
}
impl<T> SelectionMembers<T> {
pub fn as_slice(&self) -> &[T] {
&self.0
}
}
impl<T> IntoIterator for SelectionMembers<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<T> std::ops::Deref for SelectionMembers<T> {
type Target = [T];
fn deref(&self) -> &[T] {
&self.0
}
}
impl<'a, T> IntoIterator for &'a SelectionMembers<T> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'de, T: Deserialize<'de> + Eq + std::hash::Hash> Deserialize<'de> for SelectionMembers<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let values = Vec::<T>::deserialize(deserializer)?;
Self::try_from(values).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct NativeSelections(Vec<String>);
impl TryFrom<Vec<String>> for NativeSelections {
type Error = BodySelectionError;
fn try_from(value: Vec<String>) -> Result<Self, Self::Error> {
if value.is_empty() {
return Err(BodySelectionError::Empty);
}
if value.iter().any(|name| name.trim().is_empty()) {
return Err(BodySelectionError::BlankNativeMember);
}
if !membership::distinct(&value, |_| true) {
return Err(BodySelectionError::RepeatedNativeMember);
}
Ok(Self(value))
}
}
impl NativeSelections {
pub fn as_slice(&self) -> &[String] {
&self.0
}
}
impl IntoIterator for NativeSelections {
type Item = String;
type IntoIter = std::vec::IntoIter<String>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl std::ops::Deref for NativeSelections {
type Target = [String];
fn deref(&self) -> &[String] {
&self.0
}
}
impl<'de> Deserialize<'de> for NativeSelections {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let values = Vec::<String>::deserialize(deserializer)?;
Self::try_from(values).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum BodySelectionError {
#[error("body selection set must not be empty")]
Empty,
#[error("body selection set repeats a body")]
RepeatedBody,
#[error("body selection member must not be blank")]
BlankNativeMember,
#[error("body selection set repeats a native member")]
RepeatedNativeMember,
#[error("selection member allocation failed")]
Allocation,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(
tag = "kind",
content = "value",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum InsertedBodies {
Native(String),
Resolved {
native: String,
},
}
impl InsertedBodies {
#[must_use]
pub const fn is_resolved(&self) -> bool {
matches!(self, Self::Resolved { .. })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct BodyMember<B> {
body: B,
native: cadmpeg_core::text::NonBlankString,
}
impl<B> BodyMember<B> {
pub const fn new(body: B, native: cadmpeg_core::text::NonBlankString) -> Self {
Self { body, native }
}
#[must_use]
pub const fn body(&self) -> &B {
&self.body
}
#[must_use]
pub fn into_parts(self) -> (B, String) {
(self.body, self.native.into_string())
}
}
impl<'de, B> Deserialize<'de> for BodyMember<B>
where
B: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Wire<B> {
body: B,
native: String,
}
let wire = Wire::deserialize(deserializer)?;
let native = cadmpeg_core::text::NonBlankString::new(wire.native).ok_or_else(|| {
serde::de::Error::custom(BodySelectionError::BlankNativeMember.to_string())
})?;
Ok(Self::new(wire.body, native))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct BodyMembers<B>(Vec<BodyMember<B>>);
impl<B> BodyMembers<B> {
pub fn try_from_rows(rows: Vec<BodyMember<B>>) -> Result<Self, BodySelectionError>
where
B: Eq + std::hash::Hash,
{
if rows.is_empty() {
return Err(BodySelectionError::Empty);
}
let mut bodies = std::collections::HashSet::new();
let mut native = std::collections::HashSet::new();
for row in &rows {
if !bodies.insert(&row.body) {
return Err(BodySelectionError::RepeatedBody);
}
if !native.insert(&row.native) {
return Err(BodySelectionError::RepeatedNativeMember);
}
}
Ok(Self(rows))
}
#[must_use]
pub const fn count(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> std::slice::Iter<'_, BodyMember<B>> {
self.0.iter()
}
pub fn bodies(&self) -> impl Iterator<Item = &B> {
self.0.iter().map(BodyMember::body)
}
}
impl<'a, B> IntoIterator for &'a BodyMembers<B> {
type Item = &'a BodyMember<B>;
type IntoIter = std::slice::Iter<'a, BodyMember<B>>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'de, B> Deserialize<'de> for BodyMembers<B>
where
B: Deserialize<'de> + Eq + std::hash::Hash,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let rows = Vec::<BodyMember<B>>::deserialize(deserializer)?;
Self::try_from_rows(rows).map_err(serde::de::Error::custom)
}
}
#[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 BodySelection {
Unresolved,
Bodies(#[cfg_attr(feature = "schema", schemars(with = "Vec<BodyId>"))] DistinctMembers<BodyId>),
Resolved {
#[cfg_attr(feature = "schema", schemars(with = "Vec<BodyId>"))]
bodies: DistinctMembers<BodyId>,
native: String,
},
ResolvedSet {
members: BodyMembers<BodyId>,
},
Historical {
state: FeatureInputTopologyId,
bodies: SelectionMembers<HistoricalBodyId>,
native: SelectionReference,
},
HistoricalSet {
state: FeatureInputTopologyId,
members: BodyMembers<HistoricalBodyId>,
},
Generated {
bodies: SelectionMembers<GeneratedBodyRef>,
native: SelectionReference,
},
Local {
bodies: NativeSelections,
native: SelectionReference,
},
Native(String),
NativeSet(NativeSelections),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct GeneratedBodyRef {
pub feature: FeatureId,
pub local_id: SelectionReference,
}
impl GeneratedBodyRef {
pub fn new(feature: FeatureId, local_id: String) -> Result<Self, BodySelectionError> {
SelectionReference::new(local_id).map(|local_id| Self { feature, local_id })
}
}
#[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 FaceMotion {
Offset {
distance: Length,
},
Translate {
direction: FeatureDirection3,
distance: Length,
},
Rotate {
axis_origin: FinitePoint3,
axis_dir: FeatureDirection3,
angle: Angle,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct AxisAngle {
pub origin: FinitePoint3,
pub direction: FeatureDirection3,
pub angle: Angle,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum ExtrudeStart {
Unresolved {},
ProfilePlane {},
OffsetProfilePlane {
offset: Length,
},
FromFace {
face: FaceSelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_offset"
)]
offset: Option<Length>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum LinearTermination {
Unresolved {},
Blind {
length: NonZeroLength,
},
ThroughAll {},
ThroughNext {},
ToFirst {},
ToLast {},
ToFace {
face: FaceSelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_offset"
)]
offset: Option<Length>,
},
ToVertex {
vertex: VertexSelection,
},
OffsetFromFace {
face: FaceSelection,
offset: PositiveLength,
},
ToShape {
target: FaceSelection,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum AngularTermination {
Unresolved {},
ThroughAll {},
ThroughNext {},
ToFirst {},
ToLast {},
ToFace {
face: FaceSelection,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_offset"
)]
offset: Option<Length>,
},
ToVertex {
vertex: VertexSelection,
},
OffsetFromFace {
face: FaceSelection,
offset: PositiveLength,
},
ToShape {
target: FaceSelection,
},
Angle {
angle: PositiveAngle,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ExtrudeSide {
pub termination: LinearTermination,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_draft"
)]
pub draft: Option<SlopeAngle>,
}
impl Default for ExtrudeStart {
fn default() -> Self {
Self::ProfilePlane {}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum ExtrudeExtent {
OneSided {
side: ExtrudeSide,
},
TwoSided {
first: ExtrudeSide,
second: ExtrudeSide,
},
Symmetric {
side: ExtrudeSide,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum RevolveExtent {
OneSided {
termination: AngularTermination,
},
TwoSided {
first: AngularTermination,
second: AngularTermination,
},
Symmetric {
termination: AngularTermination,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum ExtrusionDirectionSource {
Custom {},
Edge {
reference: PathRef,
},
ProfileNormal {},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FaceMaker {
Simple,
Cheese,
Extrusion,
Bullseye,
Unified,
Other(NonBlankString),
}
impl FaceMaker {
pub fn new(class: impl Into<String>) -> Option<Self> {
let class = class.into();
Some(match class.as_str() {
"Part::FaceMakerSimple" => Self::Simple,
"Part::FaceMakerCheese" => Self::Cheese,
"Part::FaceMakerExtrusion" => Self::Extrusion,
"Part::FaceMakerBullseye" => Self::Bullseye,
"Part::FaceMakerUnified" => Self::Unified,
_ => Self::Other(NonBlankString::new(class)?),
})
}
pub fn as_str(&self) -> &str {
match self {
Self::Simple => "Part::FaceMakerSimple",
Self::Cheese => "Part::FaceMakerCheese",
Self::Extrusion => "Part::FaceMakerExtrusion",
Self::Bullseye => "Part::FaceMakerBullseye",
Self::Unified => "Part::FaceMakerUnified",
Self::Other(class) => class.as_str(),
}
}
pub const fn mode(&self) -> u32 {
match self {
Self::Simple => 0,
Self::Cheese => 1,
Self::Extrusion => 2,
Self::Bullseye => 3,
Self::Unified | Self::Other(_) => 4,
}
}
}
impl Serialize for FaceMaker {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for FaceMaker {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Self::new(String::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("face maker class must not be empty"))
}
}
#[cfg(feature = "schema")]
impl JsonSchema for FaceMaker {
fn schema_name() -> std::borrow::Cow<'static, str> {
"FaceMaker".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
String::json_schema(generator)
}
}
#[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 InnerWireTaper {
Inverted,
SameAsOuter,
}
#[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 HelixConstructionStyle {
Legacy,
Corrected,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum HelixShape {
Cylindrical {
pitch: NonZeroLength,
},
Conical {
pitch: NonZeroLength,
cone_angle: SlopeAngle,
},
Spiral {
radial_growth: Length,
},
}
#[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 SurfaceProjectionMode {
All,
Faces,
Edges,
}
#[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 BooleanOp {
Unresolved,
Join,
Cut,
Intersect,
NewBody,
}
#[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 BooleanKind {
Join,
Cut,
Intersect,
}
impl From<BooleanKind> for BooleanOp {
fn from(value: BooleanKind) -> Self {
match value {
BooleanKind::Join => Self::Join,
BooleanKind::Cut => Self::Cut,
BooleanKind::Intersect => Self::Intersect,
}
}
}
impl TryFrom<BooleanOp> for BooleanKind {
type Error = BooleanOp;
fn try_from(value: BooleanOp) -> Result<Self, Self::Error> {
match value {
BooleanOp::Join => Ok(Self::Join),
BooleanOp::Cut => Ok(Self::Cut),
BooleanOp::Intersect => Ok(Self::Intersect),
BooleanOp::Unresolved | BooleanOp::NewBody => Err(value),
}
}
}
impl From<SolidSweepOperation> for BooleanOp {
fn from(value: SolidSweepOperation) -> Self {
match value {
SolidSweepOperation::NewBody => Self::NewBody,
SolidSweepOperation::Join => Self::Join,
SolidSweepOperation::Cut => Self::Cut,
SolidSweepOperation::Intersect => Self::Intersect,
}
}
}
impl TryFrom<BooleanOp> for SolidSweepOperation {
type Error = BooleanOp;
fn try_from(value: BooleanOp) -> Result<Self, Self::Error> {
match value {
BooleanOp::NewBody => Ok(Self::NewBody),
BooleanOp::Join => Ok(Self::Join),
BooleanOp::Cut => Ok(Self::Cut),
BooleanOp::Intersect => Ok(Self::Intersect),
BooleanOp::Unresolved => Err(value),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct CoilConstruction {
pub placement: CoilPlacement,
pub diameter: PositiveLength,
pub extent: CoilExtent,
pub section: CoilSection,
pub section_placement: CoilSectionPlacement,
pub clockwise: bool,
pub taper: Angle,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "CoilPlacementWire", into = "CoilPlacementWire")]
pub enum CoilPlacement {
Explicit {
frame: FeatureUnitPlaneFrame,
},
Native {
native_ref: SelectionReference,
},
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
enum CoilPlacementWire {
Explicit {
origin: Point3,
axis: Vector3,
radial: Vector3,
},
Native {
native_ref: SelectionReference,
},
}
impl TryFrom<CoilPlacementWire> for CoilPlacement {
type Error = &'static str;
fn try_from(wire: CoilPlacementWire) -> Result<Self, Self::Error> {
Ok(match wire {
CoilPlacementWire::Explicit {
origin,
axis,
radial,
} => Self::Explicit {
frame: FeatureUnitPlaneFrame::new(origin, axis, radial).ok_or(
"coil placement requires finite origin and perpendicular unit directions",
)?,
},
CoilPlacementWire::Native { native_ref } => Self::Native { native_ref },
})
}
}
#[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 CoilExtent {
RevolutionsHeight {
revolutions: PositiveReal,
height: Length,
},
RevolutionsPitch {
revolutions: PositiveReal,
pitch: NonZeroLength,
},
HeightPitch {
height: NonZeroLength,
pitch: NonZeroLength,
},
Spiral {
revolutions: PositiveReal,
radial_pitch: NonZeroLength,
},
}
#[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 CoilSection {
Circular {
diameter: PositiveLength,
},
Square {
size: PositiveLength,
},
ExternalTriangle {
size: PositiveLength,
},
InternalTriangle {
size: PositiveLength,
},
}
#[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 CoilSectionPlacement {
Inside,
Center,
Outside,
}
#[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 CoilResult {
NewBody {},
Boolean {
operation: BooleanKind,
targets: BodySelection,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
pub enum SweepMode {
Unresolved {},
Solid {
op: SolidSweepOperation,
},
Surface {},
}
#[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 SolidSweepOperation {
NewBody,
Join,
Cut,
Intersect,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct SweepPathExtent {
pub along_fraction: Fraction,
pub against_fraction: Fraction,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct SweepGuideRail {
pub path: PathRef,
pub extent: SweepPathExtent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum NoGeneratedSection {}
pub type SheetSweepSection = SweepSection<NoGeneratedSection>;
#[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 SweepSection<G = GeneratedSweepSection> {
Unresolved(#[serde(deserialize_with = "cadmpeg_core::absent_key::nullable")] Option<String>),
Profile(PlanarProfileRef),
Generated(G),
}
impl<G> SweepSection<G> {
pub const fn referenced_profile(&self) -> Option<&PlanarProfileRef> {
match self {
Self::Profile(profile) => Some(profile),
Self::Unresolved(_) | Self::Generated(_) => None,
}
}
pub fn referenced_profile_mut(&mut self) -> Option<&mut PlanarProfileRef> {
match self {
Self::Profile(profile) => Some(profile),
Self::Unresolved(_) | Self::Generated(_) => None,
}
}
pub const fn is_unresolved(&self) -> bool {
matches!(self, Self::Unresolved(_))
}
pub fn unresolved_native(&self) -> Option<&str> {
match self {
Self::Unresolved(native) => native.as_deref(),
Self::Profile(_) | Self::Generated(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SweepCircularRegion {
outer_radius: PositiveLength,
#[serde(default, skip_serializing_if = "Option::is_none")]
wall_thickness: Option<PositiveLength>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct SweepCircularRegionWire {
outer_radius: PositiveLength,
#[serde(default, deserialize_with = "deserialize_wall_thickness")]
wall_thickness: Option<PositiveLength>,
}
impl SweepCircularRegion {
pub fn new(
outer_radius: PositiveLength,
wall_thickness: Option<PositiveLength>,
) -> Result<Self, &'static str> {
if wall_thickness.is_some_and(|wall| wall.get() >= outer_radius.get()) {
return Err("wall_thickness must be less than outer_radius");
}
Ok(Self {
outer_radius,
wall_thickness,
})
}
pub const fn outer_radius(self) -> PositiveLength {
self.outer_radius
}
pub const fn wall_thickness(self) -> Option<PositiveLength> {
self.wall_thickness
}
}
impl<'de> Deserialize<'de> for SweepCircularRegion {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let wire = SweepCircularRegionWire::deserialize(deserializer)?;
Self::new(wire.outer_radius, wire.wall_thickness).map_err(serde::de::Error::custom)
}
}
impl From<SheetSweepSection> for SweepSection {
fn from(section: SheetSweepSection) -> Self {
match section {
SweepSection::Unresolved(native) => Self::Unresolved(native),
SweepSection::Profile(profile) => Self::Profile(profile),
SweepSection::Generated(generated) => match generated {},
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "mode", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum SweepShape {
Unresolved {
section: SheetSweepSection,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
sections: Vec<SheetSweepSection>,
},
Solid {
op: SolidSweepOperation,
section: SweepSection,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
sections: Vec<SweepSection>,
},
Surface {
section: SheetSweepSection,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
sections: Vec<SheetSweepSection>,
},
}
impl SweepShape {
pub fn unresolved(native: Option<String>) -> Self {
Self::Unresolved {
section: SweepSection::Unresolved(native),
sections: Vec::new(),
}
}
pub fn sheet_sections(
mode: SweepMode,
section: SheetSweepSection,
sections: Vec<SheetSweepSection>,
) -> Self {
match mode {
SweepMode::Unresolved {} => Self::Unresolved { section, sections },
SweepMode::Surface {} => Self::Surface { section, sections },
SweepMode::Solid { op } => Self::Solid {
op,
section: section.into(),
sections: sections.into_iter().map(Into::into).collect(),
},
}
}
pub const fn mode(&self) -> SweepMode {
match self {
Self::Unresolved { .. } => SweepMode::Unresolved {},
Self::Solid { op, .. } => SweepMode::Solid { op: *op },
Self::Surface { .. } => SweepMode::Surface {},
}
}
pub const fn section_is_unresolved(&self) -> bool {
match self {
Self::Unresolved { section, .. } | Self::Surface { section, .. } => {
section.is_unresolved()
}
Self::Solid { section, .. } => section.is_unresolved(),
}
}
pub fn any_section_is_unresolved(&self) -> bool {
match self {
Self::Unresolved { section, sections } | Self::Surface { section, sections } => {
section.is_unresolved() || sections.iter().any(SweepSection::is_unresolved)
}
Self::Solid {
section, sections, ..
} => section.is_unresolved() || sections.iter().any(SweepSection::is_unresolved),
}
}
pub fn any_section_names_an_unresolved_carrier(&self) -> bool {
match self {
Self::Unresolved { section, sections } | Self::Surface { section, sections } => {
std::iter::once(section)
.chain(sections)
.any(|section| section.unresolved_native().is_some())
}
Self::Solid {
section, sections, ..
} => std::iter::once(section)
.chain(sections)
.any(|section| section.unresolved_native().is_some()),
}
}
pub fn additional_section_count(&self) -> usize {
match self {
Self::Unresolved { sections, .. } | Self::Surface { sections, .. } => sections.len(),
Self::Solid { sections, .. } => sections.len(),
}
}
pub const fn referenced_profile(&self) -> Option<&PlanarProfileRef> {
match self {
Self::Unresolved { section, .. } | Self::Surface { section, .. } => {
section.referenced_profile()
}
Self::Solid { section, .. } => section.referenced_profile(),
}
}
pub fn referenced_profile_mut(&mut self) -> Option<&mut PlanarProfileRef> {
match self {
Self::Unresolved { section, .. } | Self::Surface { section, .. } => {
section.referenced_profile_mut()
}
Self::Solid { section, .. } => section.referenced_profile_mut(),
}
}
pub fn referenced_profiles(&self) -> impl Iterator<Item = &PlanarProfileRef> + Clone {
let (primary, sheet_sections, solid_sections): (
Option<&PlanarProfileRef>,
&[SheetSweepSection],
&[SweepSection],
) = match self {
Self::Unresolved { section, sections } | Self::Surface { section, sections } => {
(section.referenced_profile(), sections, &[])
}
Self::Solid {
section, sections, ..
} => (section.referenced_profile(), &[], sections),
};
primary
.into_iter()
.chain(
sheet_sections
.iter()
.filter_map(SweepSection::referenced_profile),
)
.chain(
solid_sections
.iter()
.filter_map(SweepSection::referenced_profile),
)
}
pub fn generated_sections_mut(&mut self) -> impl Iterator<Item = &mut GeneratedSweepSection> {
let (section, sections) = match self {
Self::Unresolved { .. } | Self::Surface { .. } => (None, &mut [][..]),
Self::Solid {
section, sections, ..
} => (Some(section), sections.as_mut_slice()),
};
section
.into_iter()
.chain(sections.iter_mut())
.filter_map(|section| match section {
SweepSection::Generated(generated) => Some(generated),
SweepSection::Unresolved(_) | SweepSection::Profile(_) => None,
})
}
pub const fn generated_section(&self) -> Option<&GeneratedSweepSection> {
match self {
Self::Unresolved { .. } | Self::Surface { .. } => None,
Self::Solid { section, .. } => match section {
SweepSection::Generated(generated) => Some(generated),
SweepSection::Unresolved(_) | SweepSection::Profile(_) => None,
},
}
}
#[must_use]
pub fn solid_sections(
op: SolidSweepOperation,
section: SweepSection,
sections: Vec<SweepSection>,
) -> Self {
Self::Solid {
op,
section,
sections,
}
}
#[must_use]
pub fn into_sections(self) -> (SweepSection, Vec<SweepSection>) {
match self {
Self::Unresolved { section, sections } | Self::Surface { section, sections } => (
section.into(),
sections.into_iter().map(Into::into).collect(),
),
Self::Solid {
section, sections, ..
} => (section, sections),
}
}
pub fn set_referenced_profile(&mut self, profile: PlanarProfileRef) {
match self {
Self::Unresolved { section, .. } | Self::Surface { section, .. } => {
*section = SweepSection::Profile(profile);
}
Self::Solid { section, .. } => *section = SweepSection::Profile(profile),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "shape", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum GeneratedSweepSection {
CircularRegion {
region: SweepCircularRegion,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct SketchProfileBoundaryUse {
pub entity: crate::sketches::SketchEntityId,
#[serde(deserialize_with = "deserialize_profile_parameter_range")]
pub parameter_range: crate::geometry::DirectedParameterRange,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub reversed: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "SketchProfileLoopsWire")]
pub struct SketchProfileLoops {
outer: u32,
#[serde(skip_serializing_if = "DistinctMembers::is_empty")]
holes: DistinctMembers<u32>,
}
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct SketchProfileLoopsWire {
outer: u32,
#[serde(default)]
holes: Vec<u32>,
}
impl SketchProfileLoops {
pub fn new(outer: u32, holes: Vec<u32>) -> Result<Self, &'static str> {
let holes = DistinctMembers::try_from(holes).map_err(|_| "holes must be distinct")?;
if holes.contains(&outer) {
return Err("holes must not contain outer");
}
Ok(Self { outer, holes })
}
pub fn outer(&self) -> u32 {
self.outer
}
pub fn holes(&self) -> &[u32] {
self.holes.as_slice()
}
}
impl TryFrom<SketchProfileLoopsWire> for SketchProfileLoops {
type Error = FeatureCollectionError;
fn try_from(wire: SketchProfileLoopsWire) -> Result<Self, Self::Error> {
let mut seen = std::collections::HashSet::new();
seen.try_reserve(wire.holes.len())
.map_err(|_| FeatureCollectionError::Invalid("hole member allocation failed"))?;
for hole in &wire.holes {
if !seen.insert(hole) {
return Err(FeatureCollectionError::Invalid("holes must be distinct"));
}
}
if seen.contains(&wire.outer) {
return Err(FeatureCollectionError::Invalid(
"holes must not contain outer",
));
}
Ok(Self {
outer: wire.outer,
holes: DistinctMembers(wire.holes),
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "region", rename_all = "snake_case", deny_unknown_fields)]
pub enum SketchProfileRegion {
Loops {
#[serde(flatten)]
loops: SketchProfileLoops,
},
Trimmed {
outer_boundary: NonEmptyMembers<SketchProfileBoundaryUse>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
hole_boundaries: Vec<NonEmptyMembers<SketchProfileBoundaryUse>>,
},
}
impl SketchProfileRegion {
pub fn loops(outer: u32, holes: Vec<u32>) -> Result<Self, &'static str> {
SketchProfileLoops::new(outer, holes).map(|loops| Self::Loops { loops })
}
pub fn trimmed(
outer_boundary: Vec<SketchProfileBoundaryUse>,
hole_boundaries: Vec<Vec<SketchProfileBoundaryUse>>,
) -> Result<Self, &'static str> {
Ok(Self::Trimmed {
outer_boundary: outer_boundary
.try_into()
.map_err(|_| "outer_boundary must not be empty")?,
hole_boundaries: hole_boundaries
.into_iter()
.map(|ring| {
ring.try_into()
.map_err(|_| "hole_boundaries must not contain an empty ring")
})
.collect::<Result<_, _>>()?,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct SketchProfileRegions(Vec<SketchProfileRegion>);
impl TryFrom<Vec<SketchProfileRegion>> for SketchProfileRegions {
type Error = &'static str;
fn try_from(regions: Vec<SketchProfileRegion>) -> Result<Self, Self::Error> {
if regions.is_empty() {
return Err("regions must not be empty");
}
if regions
.iter()
.enumerate()
.any(|(index, region)| regions[..index].contains(region))
{
return Err("regions must be distinct");
}
Ok(Self(regions))
}
}
impl SketchProfileRegions {
pub fn as_slice(&self) -> &[SketchProfileRegion] {
&self.0
}
}
impl std::ops::Deref for SketchProfileRegions {
type Target = [SketchProfileRegion];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'de> Deserialize<'de> for SketchProfileRegions {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::try_from(Vec::<SketchProfileRegion>::deserialize(deserializer)?)
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum SweepOrientation {
CorrectedFrenet {},
Fixed {},
Frenet {},
Auxiliary {
path: PathRef,
tangent: bool,
curvilinear: bool,
},
GuideSurface {
faces: FaceSelection,
},
Binormal {
direction: UnitVector3,
},
}
#[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 SweepTransition {
Transformed,
RightCorner,
RoundCorner,
}
#[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 SweepTransformation {
Constant,
MultiSection,
Linear,
SShape,
Interpolation,
}
#[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 HelicalSweepTravel {
Axial {
height: NonZeroLength,
},
Radial {
radial_growth: NonZeroLength,
},
Conical {
height: NonZeroLength,
radial_growth: NonZeroLength,
},
}
impl HelicalSweepTravel {
pub fn new(height: Length, radial_growth: Length) -> Option<Self> {
match (
NonZeroLength::try_from(height).ok(),
NonZeroLength::try_from(radial_growth).ok(),
) {
(None, None) => None,
(Some(height), None) => Some(Self::Axial { height }),
(None, Some(radial_growth)) => Some(Self::Radial { radial_growth }),
(Some(height), Some(radial_growth)) => Some(Self::Conical {
height,
radial_growth,
}),
}
}
pub fn height(self) -> Length {
match self {
Self::Radial { .. } => Length::ZERO,
Self::Axial { height } | Self::Conical { height, .. } => height.into(),
}
}
pub fn radial_growth(self) -> Length {
match self {
Self::Axial { .. } => Length::ZERO,
Self::Radial { radial_growth } | Self::Conical { radial_growth, .. } => {
radial_growth.into()
}
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct HelicalSweepConstruction {
pub profile: PlanarProfileRef,
pub axis_origin: FinitePoint3,
pub axis_direction: UnitVector3,
pub law: HelicalSweepLaw,
pub pitch: NonNegativeLength,
pub travel: HelicalSweepTravel,
pub turns: PositiveReal,
pub cone_angle: Angle,
pub left_handed: bool,
pub reversed: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_tolerance"
)]
pub tolerance: Option<PositiveReal>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_allow_multi_profile_faces"
)]
pub allow_multi_profile_faces: Option<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 HelicalSweepLaw {
PitchHeightAngle,
PitchTurnsAngle,
HeightTurnsAngle,
HeightTurnsGrowth,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct BinderSource {
pub target: BinderTarget,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[serde(deserialize_with = "deserialize_local_subelements")]
pub subelements: Vec<NonBlankString>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum BinderTarget {
Feature {
feature: FeatureId,
},
External {
#[serde(deserialize_with = "deserialize_local_document")]
document: NonBlankString,
#[serde(deserialize_with = "deserialize_local_object")]
object: NonBlankString,
},
Native {
#[serde(deserialize_with = "deserialize_local_reference")]
reference: NonBlankString,
},
}
#[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 BinderConstruction {
Shape {
trace_support: bool,
},
SubShape {
lifecycle: BinderLifecycle,
placement: BinderPlacement,
copy_on_change: BinderCopyOnChange,
claim_children: bool,
fuse: bool,
make_face: bool,
partial_load: bool,
refine: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_binder_construction_offset"
)]
offset: Option<BinderOffset>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_context"
)]
context: Option<BinderTarget>,
},
}
#[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 BinderLifecycle {
Synchronized,
Frozen,
Detached,
}
#[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 BinderPlacement {
Relative,
Global,
}
#[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 BinderCopyOnChange {
Disabled,
Enabled,
Mutated,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct BinderOffset {
pub distance: NonZeroLength,
pub join: BinderOffsetJoin,
pub fill: bool,
pub open_result: bool,
pub intersection: 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 BinderOffsetJoin {
Arcs,
Tangent,
Intersection,
}
#[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 PlanarProfileRef {
Unresolved(String),
Native(String),
Sketch(crate::sketches::SketchId),
SketchProfiles {
sketch: crate::sketches::SketchId,
#[serde(deserialize_with = "deserialize_selection_profiles")]
profiles: SelectionMembers<u32>,
},
SketchRegions {
sketch: crate::sketches::SketchId,
regions: SketchProfileRegions,
},
SketchEntities {
sketch: crate::sketches::SketchId,
#[serde(deserialize_with = "deserialize_selection_entities")]
entities: SelectionMembers<crate::sketches::SketchEntityId>,
},
SketchSelection {
sketch: crate::sketches::SketchId,
#[serde(deserialize_with = "deserialize_selection_selections")]
selections: NativeSelections,
},
HistoricalFaces {
state: FeatureInputTopologyId,
#[serde(deserialize_with = "deserialize_selection_faces")]
faces: SelectionMembers<HistoricalFaceId>,
#[serde(deserialize_with = "deserialize_selection_native")]
native: NativeSelections,
},
Feature(FeatureId),
Generated {
#[serde(deserialize_with = "deserialize_selection_curves")]
curves: NonEmptyMembers<GeneratedCurveRef>,
#[serde(deserialize_with = "deserialize_selection_native")]
native: SelectionReference,
},
Faces(Vec<FaceId>),
}
#[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 ProfileRef {
SpatialSketchProfiles {
sketch: crate::sketches::SpatialSketchId,
#[serde(deserialize_with = "deserialize_selection_profiles")]
profiles: SelectionMembers<u32>,
},
SpatialSketchSelection {
sketch: crate::sketches::SpatialSketchId,
#[serde(deserialize_with = "deserialize_selection_selections")]
selections: NativeSelections,
},
#[serde(untagged)]
Planar(PlanarProfileRef),
}
impl From<crate::sketches::SketchId> for PlanarProfileRef {
fn from(sketch: crate::sketches::SketchId) -> Self {
Self::Sketch(sketch)
}
}
impl PlanarProfileRef {
#[must_use]
pub const fn native(reference: String) -> Self {
Self::Native(reference)
}
}
impl ProfileRef {
#[must_use]
pub const fn planar(&self) -> Option<&PlanarProfileRef> {
match self {
Self::Planar(profile) => Some(profile),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum LoftSection {
Profile(ProfileRef),
Point(LoftPointSection),
}
#[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 LoftPointSection {
#[serde(rename = "native_point")]
Native(#[serde(deserialize_with = "deserialize_local_value")] NonBlankString),
Point(FinitePoint3),
Vertex(VertexId),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct GeneratedCurveRef {
pub feature: FeatureId,
#[serde(deserialize_with = "deserialize_selection_local_id")]
pub local_id: SelectionReference,
}
#[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 PathRef {
Unresolved(String),
Native(String),
Sketch(crate::sketches::SketchId),
SketchCurves {
sketch: crate::sketches::SketchId,
#[serde(deserialize_with = "deserialize_selection_curves")]
curves: SelectionMembers<crate::sketches::SketchEntityId>,
},
SpatialSketchSelection {
sketch: crate::sketches::SpatialSketchId,
#[serde(deserialize_with = "deserialize_selection_selections")]
selections: NativeSelections,
},
SpatialSketchCurves {
sketch: crate::sketches::SpatialSketchId,
#[serde(deserialize_with = "deserialize_selection_curves")]
curves: SelectionMembers<crate::sketches::SpatialSketchEntityId>,
},
Edges(Vec<EdgeId>),
Curves(Vec<CurveId>),
HistoricalEdges {
state: FeatureInputTopologyId,
#[serde(deserialize_with = "deserialize_selection_edges")]
edges: SelectionMembers<HistoricalEdgeId>,
#[serde(deserialize_with = "deserialize_selection_native")]
native: NonBlankString,
},
}
impl PlanarProfileRef {
pub fn sketch_regions(
sketch: crate::sketches::SketchId,
regions: Vec<SketchProfileRegion>,
) -> Result<Self, &'static str> {
Ok(Self::SketchRegions {
sketch,
regions: regions.try_into()?,
})
}
pub fn sketch_profiles(
sketch: crate::sketches::SketchId,
profiles: Vec<u32>,
) -> Result<Self, BodySelectionError> {
let profiles = SelectionMembers::try_from(profiles)?;
Ok(Self::SketchProfiles { sketch, profiles })
}
pub fn sketch_entities(
sketch: crate::sketches::SketchId,
entities: Vec<crate::sketches::SketchEntityId>,
) -> Result<Self, BodySelectionError> {
let entities = SelectionMembers::try_from(entities)?;
Ok(Self::SketchEntities { sketch, entities })
}
pub fn sketch_selection(
sketch: crate::sketches::SketchId,
selections: Vec<String>,
) -> Result<Self, BodySelectionError> {
let selections = NativeSelections::try_from(selections)?;
Ok(Self::SketchSelection { sketch, selections })
}
pub fn historical_faces(
state: FeatureInputTopologyId,
faces: Vec<HistoricalFaceId>,
native: Vec<String>,
) -> Result<Self, BodySelectionError> {
let faces = SelectionMembers::try_from(faces)?;
let native = NativeSelections::try_from(native)?;
Ok(Self::HistoricalFaces {
state,
faces,
native,
})
}
pub fn generated(
curves: Vec<GeneratedCurveRef>,
native: String,
) -> Result<Self, BodySelectionError> {
let curves = curves.try_into()?;
SelectionReference::new(native).map(|native| Self::Generated { curves, native })
}
}
impl ProfileRef {
pub fn spatial_sketch_profiles(
sketch: crate::sketches::SpatialSketchId,
profiles: Vec<u32>,
) -> Result<Self, BodySelectionError> {
let profiles = SelectionMembers::try_from(profiles)?;
Ok(Self::SpatialSketchProfiles { sketch, profiles })
}
pub fn spatial_sketch_selection(
sketch: crate::sketches::SpatialSketchId,
selections: Vec<String>,
) -> Result<Self, BodySelectionError> {
let selections = NativeSelections::try_from(selections)?;
Ok(Self::SpatialSketchSelection { sketch, selections })
}
}
impl PathRef {
pub fn sketch_curves(
sketch: crate::sketches::SketchId,
curves: Vec<crate::sketches::SketchEntityId>,
) -> Result<Self, BodySelectionError> {
let curves = SelectionMembers::try_from(curves)?;
Ok(Self::SketchCurves { sketch, curves })
}
pub fn spatial_sketch_curves(
sketch: crate::sketches::SpatialSketchId,
curves: Vec<crate::sketches::SpatialSketchEntityId>,
) -> Result<Self, BodySelectionError> {
let curves = SelectionMembers::try_from(curves)?;
Ok(Self::SpatialSketchCurves { sketch, curves })
}
pub fn historical_edges(
state: FeatureInputTopologyId,
edges: Vec<HistoricalEdgeId>,
native: String,
) -> Result<Self, BodySelectionError> {
let edges = SelectionMembers::try_from(edges)?;
let native = NonBlankString::new(native).ok_or(BodySelectionError::BlankNativeMember)?;
Ok(Self::HistoricalEdges {
state,
edges,
native,
})
}
}
impl GeneratedCurveRef {
pub fn new(feature: FeatureId, local_id: String) -> Result<Self, BodySelectionError> {
SelectionReference::new(local_id).map(|local_id| Self { feature, local_id })
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct ThreePointSelection(Box<[VertexSelection; 3]>);
impl TryFrom<Box<[VertexSelection; 3]>> for ThreePointSelection {
type Error = &'static str;
fn try_from(points: Box<[VertexSelection; 3]>) -> Result<Self, Self::Error> {
for (first, second) in [(0, 1), (0, 2), (1, 2)] {
if selection_overlap::vertex_targets_equal(&points[first], &points[second]) {
return Err("points must select three distinct vertex targets");
}
}
let mut state: Option<&FeatureInputTopologyId> = None;
for point in points.iter() {
if let VertexSelection::Historical {
state: candidate, ..
} = point
{
match state {
Some(state) if state.as_str() != candidate.as_str() => {
return Err("points must use the same input topology");
}
Some(_) => {}
None => state = Some(candidate),
}
}
}
Ok(Self(points))
}
}
impl std::ops::Deref for ThreePointSelection {
type Target = [VertexSelection; 3];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'de> Deserialize<'de> for ThreePointSelection {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::try_from(Box::<[VertexSelection; 3]>::deserialize(deserializer)?)
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct SplitFacePlanes(SelectionMembers<FeatureId>);
impl TryFrom<Vec<FeatureId>> for SplitFacePlanes {
type Error = &'static str;
fn try_from(planes: Vec<FeatureId>) -> Result<Self, Self::Error> {
if planes.len() < 2 {
return Err("planes must contain at least two planes");
}
Ok(Self(
planes.try_into().map_err(|_| "planes must be distinct")?,
))
}
}
impl std::ops::Deref for SplitFacePlanes {
type Target = [FeatureId];
fn deref(&self) -> &[FeatureId] {
&self.0
}
}
impl<'a> IntoIterator for &'a SplitFacePlanes {
type Item = &'a FeatureId;
type IntoIter = std::slice::Iter<'a, FeatureId>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'de> Deserialize<'de> for SplitFacePlanes {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::try_from(Vec::<FeatureId>::deserialize(deserializer)?)
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct GeometryImportPath(String);
impl TryFrom<String> for GeometryImportPath {
type Error = &'static str;
fn try_from(path: String) -> Result<Self, Self::Error> {
if path.is_empty() || path.contains('\0') {
return Err("path must be nonempty and contain no NUL");
}
Ok(Self(path))
}
}
impl std::ops::Deref for GeometryImportPath {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl<'de> Deserialize<'de> for GeometryImportPath {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::try_from(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
}
}
#[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 SplitFaceTool {
Path(PathRef),
Plane {
plane: FeatureId,
},
Planes {
planes: SplitFacePlanes,
},
}
#[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 FlexMode {
Unresolved {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_form"
)]
form: Option<FlexForm>,
},
Bending {
angle: Angle,
},
Twisting {
angle: Angle,
},
Tapering {
factor: PositiveReal,
},
Stretching {
distance: Length,
},
}
#[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 FlexForm {
Bending,
Twisting,
Tapering,
Stretching,
}
#[cfg(test)]
mod tests;
cadmpeg_core::named_optional_field!(deserialize_plane, FeatureId, "plane");
cadmpeg_core::named_optional_field!(deserialize_pull, DraftPull, "pull");
cadmpeg_core::named_optional_field!(deserialize_source_index, u32, "source_index");
cadmpeg_core::named_optional_field!(deserialize_name, String, "name");
cadmpeg_core::named_optional_field!(deserialize_material, String, "material");
cadmpeg_core::named_optional_field!(deserialize_bodies, DistinctMembers<BodyId>, "bodies");
cadmpeg_core::named_optional_field!(deserialize_native_ref, String, "native_ref");
cadmpeg_core::named_optional_field!(deserialize_owner, FeatureId, "owner");
cadmpeg_core::named_optional_field!(deserialize_display, DimensionDisplay, "display");
cadmpeg_core::named_optional_field!(deserialize_value, ParameterValue, "value");
cadmpeg_core::named_optional_field!(deserialize_pmi, ParameterPmi, "pmi");
cadmpeg_core::named_optional_field!(deserialize_display_text, String, "display_text");
cadmpeg_core::named_optional_field!(
deserialize_regeneration_parent,
FeatureId,
"regeneration_parent"
);
cadmpeg_core::named_optional_field!(deserialize_source_tag, String, "source_tag");
cadmpeg_core::named_optional_field!(deserialize_source_text, String, "source_text");
cadmpeg_core::named_optional_field!(deserialize_active_child, FeatureId, "active_child");
cadmpeg_core::named_optional_field!(deserialize_diameter, PositiveLength, "diameter");
cadmpeg_core::named_optional_field!(deserialize_extent, CosmeticThreadExtent, "extent");
cadmpeg_core::named_optional_field!(deserialize_opacity, Fraction, "opacity");
cadmpeg_core::named_optional_field!(deserialize_reference, DatumPlaneReference, "reference");
cadmpeg_core::named_optional_field!(
deserialize_construction,
Box<DatumPointConstruction>,
"construction"
);
cadmpeg_core::named_optional_field!(deserialize_bidirectional, bool, "bidirectional");
cadmpeg_core::named_optional_field!(deserialize_segment_turns, PositiveReal, "segment_turns");
cadmpeg_core::named_optional_field!(
deserialize_construction_style,
HelixConstructionStyle,
"construction_style"
);
cadmpeg_core::named_optional_field!(
deserialize_sketch,
crate::sketches::SpatialSketchId,
"sketch"
);
cadmpeg_core::named_optional_field!(
deserialize_feature_operation_sketch,
crate::sketches::SketchId,
"sketch"
);
cadmpeg_core::named_optional_field!(deserialize_block, FeatureId, "block");
cadmpeg_core::named_optional_field!(
deserialize_placement,
crate::transform::Transform,
"placement"
);
cadmpeg_core::named_optional_field!(deserialize_path, PathRef, "path");
cadmpeg_core::named_optional_field!(deserialize_orientation, SweepOrientation, "orientation");
cadmpeg_core::named_optional_field!(deserialize_transition, SweepTransition, "transition");
cadmpeg_core::named_optional_field!(
deserialize_transformation,
SweepTransformation,
"transformation"
);
cadmpeg_core::named_optional_field!(deserialize_twist, Angle, "twist");
cadmpeg_core::named_optional_field!(deserialize_path_extent, SweepPathExtent, "path_extent");
cadmpeg_core::named_optional_field!(deserialize_guide_rail, SweepGuideRail, "guide_rail");
cadmpeg_core::named_optional_field!(deserialize_taper, Angle, "taper");
cadmpeg_core::named_optional_field!(deserialize_scale, PositiveReal, "scale");
cadmpeg_core::named_optional_field!(
deserialize_allow_multi_profile_faces,
bool,
"allow_multi_profile_faces"
);
cadmpeg_core::named_optional_field!(
deserialize_feature_operation_bodies,
BodySelection,
"bodies"
);
cadmpeg_core::named_optional_field!(deserialize_thickness, PositiveLength, "thickness");
cadmpeg_core::named_optional_field!(deserialize_outward, bool, "outward");
cadmpeg_core::named_optional_field!(deserialize_mode, ShellMode, "mode");
cadmpeg_core::named_optional_field!(deserialize_join, ShellJoin, "join");
cadmpeg_core::named_optional_field!(
deserialize_resolve_intersections,
bool,
"resolve_intersections"
);
cadmpeg_core::named_optional_field!(
deserialize_allow_self_intersections,
bool,
"allow_self_intersections"
);
cadmpeg_core::named_optional_field!(deserialize_approximate, bool, "approximate");
cadmpeg_core::named_optional_field!(
deserialize_plane_reference,
FaceSelection,
"plane_reference"
);
cadmpeg_core::named_optional_field!(deserialize_side, ThickenSide, "side");
cadmpeg_core::named_optional_field!(
deserialize_gap_tolerance,
NonNegativeLength,
"gap_tolerance"
);
cadmpeg_core::named_optional_field!(deserialize_merge_result, bool, "merge_result");
cadmpeg_core::named_optional_field!(deserialize_angle, Angle, "angle");
cadmpeg_core::named_optional_field!(deserialize_alternate_face, bool, "alternate_face");
cadmpeg_core::named_optional_field!(deserialize_corner, RuledSurfaceCorner, "corner");
cadmpeg_core::named_optional_field!(deserialize_reverse, bool, "reverse");
cadmpeg_core::named_optional_field!(deserialize_rotation, AxisAngle, "rotation");
cadmpeg_core::named_optional_field!(deserialize_height, PositiveLength, "height");
cadmpeg_core::named_optional_field!(deserialize_elliptical, bool, "elliptical");
cadmpeg_core::named_optional_field!(deserialize_axis, FeatureDirection3, "axis");
cadmpeg_core::named_optional_field!(deserialize_center, ScaleCenter, "center");
cadmpeg_core::named_optional_field!(deserialize_profile, PlanarProfileRef, "profile");
cadmpeg_core::named_optional_field!(
deserialize_profile_filter,
HoleProfileFilter,
"profile_filter"
);
cadmpeg_core::named_optional_field!(deserialize_face, FaceSelection, "face");
cadmpeg_core::named_optional_field!(deserialize_direction, FeatureDirection3, "direction");
cadmpeg_core::named_optional_field!(deserialize_placements, Vec<HolePlacement>, "placements");
cadmpeg_core::named_optional_field!(
deserialize_feature_operation_extent,
LinearTermination,
"extent"
);
cadmpeg_core::named_optional_field!(deserialize_bottom, HoleBottom, "bottom");
cadmpeg_core::named_optional_field!(deserialize_taper_angle, InteriorAngle, "taper_angle");
cadmpeg_core::named_optional_field!(deserialize_solid, bool, "solid");
cadmpeg_core::named_optional_field!(deserialize_face_maker, FaceMaker, "face_maker");
cadmpeg_core::named_optional_field!(
deserialize_inner_wire_taper,
InnerWireTaper,
"inner_wire_taper"
);
cadmpeg_core::named_optional_field!(
deserialize_length_along_profile_normal,
bool,
"length_along_profile_normal"
);
cadmpeg_core::named_optional_field!(deserialize_max_degree, std::num::NonZeroU32, "max_degree");
cadmpeg_core::named_optional_field!(deserialize_source, ExtrusionDirectionSource, "source");
cadmpeg_core::named_optional_field!(deserialize_face_maker_class, FaceMaker, "face_maker_class");
cadmpeg_core::named_optional_field!(deserialize_fuse_order, RevolutionFuseOrder, "fuse_order");
cadmpeg_core::named_optional_field!(
deserialize_partial_revolve_construction_axis,
RevolutionAxis,
"axis"
);
cadmpeg_core::named_optional_field!(
deserialize_partial_revolve_construction_extent,
RevolveExtent,
"extent"
);
cadmpeg_core::named_optional_field!(deserialize_revolution_axis_reference, PathRef, "reference");
cadmpeg_core::named_optional_field!(
deserialize_rib_construction_direction,
FeatureDirection3,
"direction"
);
cadmpeg_core::named_optional_field!(deserialize_rib_construction_side, RibSide, "side");
cadmpeg_core::named_optional_field!(deserialize_offset, Length, "offset");
cadmpeg_core::named_optional_field!(deserialize_draft, SlopeAngle, "draft");
cadmpeg_core::named_optional_field!(deserialize_wall_thickness, PositiveLength, "wall_thickness");
cadmpeg_core::named_optional_field!(deserialize_tolerance, PositiveReal, "tolerance");
cadmpeg_core::named_optional_field!(
deserialize_binder_construction_offset,
BinderOffset,
"offset"
);
cadmpeg_core::named_optional_field!(deserialize_context, BinderTarget, "context");
cadmpeg_core::named_optional_field!(deserialize_form, FlexForm, "form");
impl Feature {
pub fn configuration_state(&self) -> ConfigurationFeatureState {
ConfigurationFeatureState {
evaluation: if self.suppressed.unwrap_or(false) {
ConfigurationEvaluation::Suppressed {}
} else {
ConfigurationEvaluation::Active {
outputs: self.evaluation.outputs.clone(),
}
},
dependencies: self.dependencies.clone(),
definition: self.evaluation.definition.clone(),
}
}
}
mod identity_rewrite;
mod serialization;