use super::Point3;
use serde::{Deserialize, Serialize};
macro_rules! string_id {
($(#[$doc:meta])* $name:ident) => {
$(#[$doc])*
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(pub String);
impl $name {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for $name {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
impl From<String> for $name {
fn from(s: String) -> Self {
Self(s)
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
};
}
string_id!(
SetupId
);
string_id!(
ToolId
);
string_id!(
OperationId
);
string_id!(
MachineId
);
string_id!(
HolderId
);
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TargetRef {
pub solid: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub face: Option<String>,
}
impl TargetRef {
pub fn solid(solid: impl Into<String>) -> Self {
Self { solid: solid.into(), face: None }
}
pub fn face(solid: impl Into<String>, face: impl Into<String>) -> Self {
Self { solid: solid.into(), face: Some(face.into()) }
}
pub fn is_whole_body(&self) -> bool {
self.face.is_none()
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TargetSelection {
#[serde(default)]
pub target_solids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub drive_faces: Vec<TargetRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub avoid_faces: Vec<TargetRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub protected_solids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub chains: Vec<ChainRef>,
}
impl TargetSelection {
pub fn validate(&self) -> Result<(), String> {
for (i, chain) in self.chains.iter().enumerate() {
chain.validate().map_err(|m| format!("chain {i}: {m}"))?;
}
Ok(())
}
pub fn solids<I, S>(solids: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self { target_solids: solids.into_iter().map(Into::into).collect(), ..Default::default() }
}
pub fn protected_bodies(&self) -> Vec<String> {
let mut all: Vec<String> =
self.target_solids.iter().chain(self.protected_solids.iter()).cloned().collect();
all.sort();
all.dedup();
all
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EdgeRef {
pub solid: String,
pub edge: String,
}
impl EdgeRef {
pub fn new(solid: impl Into<String>, edge: impl Into<String>) -> Self {
Self { solid: solid.into(), edge: edge.into() }
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct ChainRef {
pub source: ChainSource,
pub closed: bool,
pub reversed: bool,
pub side: ChainSide,
pub offset: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<Point3>,
}
impl Default for ChainRef {
fn default() -> Self {
Self { source: ChainSource::default(), closed: false, reversed: false, side: ChainSide::Auto, offset: 0.0, start: None }
}
}
impl ChainRef {
pub fn validate(&self) -> Result<(), String> {
self.source.validate()?;
if !self.offset.is_finite() {
return Err("chain offset is not finite".into());
}
if self.start.map_or(false, |p| !p.is_finite()) {
return Err("chain start point is not finite".into());
}
if !self.closed && matches!(self.side, ChainSide::Inside | ChainSide::Outside) {
return Err("inside/outside needs a closed chain; use left or right on an open chain".into());
}
Ok(())
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase", rename_all_fields = "camelCase")]
pub enum ChainSource {
Edges { edges: Vec<EdgeRef> },
SketchCurves { sketch: String, curves: Vec<String> },
SketchProfile {
sketch: String,
#[serde(default)]
regions: Vec<u32>,
},
}
impl Default for ChainSource {
fn default() -> Self {
ChainSource::Edges { edges: Vec::new() }
}
}
impl ChainSource {
pub const VARIANTS: &'static [&'static str] = &["edges", "sketchCurves", "sketchProfile"];
pub fn validate(&self) -> Result<(), String> {
match self {
ChainSource::Edges { edges } => {
if edges.is_empty() {
return Err("an edge chain needs at least one edge".into());
}
let mut seen = std::collections::BTreeSet::new();
for e in edges {
if e.solid.is_empty() || e.edge.is_empty() {
return Err("an edge reference needs a body and an edge name".into());
}
if !seen.insert(e) {
return Err(format!("edge {:?} of {:?} appears twice in the chain", e.edge, e.solid));
}
}
Ok(())
}
ChainSource::SketchCurves { sketch, curves } => {
if sketch.is_empty() || curves.is_empty() || curves.iter().any(|c| c.is_empty()) {
return Err("a sketch chain needs a sketch id and at least one curve id".into());
}
let unique: std::collections::BTreeSet<&String> = curves.iter().collect();
if unique.len() != curves.len() {
return Err("a sketch chain lists a curve twice".into());
}
Ok(())
}
ChainSource::SketchProfile { sketch, regions } => {
if sketch.is_empty() {
return Err("a sketch profile needs a sketch id".into());
}
let unique: std::collections::BTreeSet<&u32> = regions.iter().collect();
if unique.len() != regions.len() {
return Err("a sketch profile lists a region twice".into());
}
Ok(())
}
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ChainSide {
#[default]
Auto,
Left,
Right,
Inside,
Outside,
On,
}
impl ChainSide {
pub const VARIANTS: &'static [&'static str] = &["auto", "left", "right", "inside", "outside", "on"];
}