Skip to main content

brep_cam/model/
probe.rs

1//! Typed probing intent. All positions/normals are in MACHINE coordinates.
2use super::{Point3,TargetRef,WorkOffset};
3use serde::{Deserialize,Serialize};
4#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
5#[derive(Clone,Copy,Debug,PartialEq,Eq,Serialize,Deserialize)]
6#[serde(rename_all="camelCase")]
7pub enum ProbeKind {SingleSurface,OutsideCorner,InsideCorner,Web,Pocket,Bore,Boss,Rectangle,Angle,Inspection}
8impl ProbeKind {
9    /// Serde names of every variant, in declaration order.
10    pub const VARIANTS: &'static [&'static str] = &["singleSurface","outsideCorner","insideCorner","web","pocket","bore","boss","rectangle","angle","inspection"];
11}
12#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
13#[derive(Clone,Debug,PartialEq,Serialize,Deserialize)]
14#[serde(rename_all="camelCase")]
15pub struct ProbePoint {
16    pub id:String,
17    pub face:TargetRef,
18    pub position:Point3,
19    /// Unit normal toward free space (and the approaching stylus).
20    pub normal:Point3,
21    pub lower_tolerance:f64,
22    pub upper_tolerance:f64,
23}
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25#[derive(Clone,Debug,PartialEq,Serialize,Deserialize)]
26#[serde(rename_all="camelCase",default)]
27pub struct ProbeSettings {
28    pub stylus_radius:f64,
29    pub shank_radius:f64,
30    /// Top of the vertical shank above the stylus centre.
31    pub shank_length:f64,
32    pub approach_distance:f64,
33    /// Maximum travel beyond nominal contact; only executed if no trigger occurs.
34    pub overtravel:f64,
35    pub feed:f64,
36    pub result_prefix:String,
37}
38impl Default for ProbeSettings {fn default()->Self {Self {stylus_radius:1.,shank_radius:0.25,shank_length:20.,approach_distance:2.,overtravel:0.5,feed:100.,result_prefix:"probe".into()}}}
39#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
40#[derive(Clone,Debug,PartialEq,Serialize,Deserialize)]
41#[serde(rename_all="camelCase",default)]
42pub struct ProbeWcsParams {
43    /// Serialized as `probeKind`: the strategy's own `kind` tag sits at the same level.
44    #[serde(rename="probeKind")]
45    pub kind:ProbeKind,
46    pub points:Vec<ProbePoint>,pub settings:ProbeSettings,pub wcs:WorkOffset}
47impl Default for ProbeWcsParams {fn default()->Self {Self {kind:ProbeKind::SingleSurface,points:Vec::new(),settings:ProbeSettings::default(),wcs:WorkOffset::default()}}}
48#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
49#[derive(Clone,Debug,PartialEq,Default,Serialize,Deserialize)]
50#[serde(rename_all="camelCase",default)]
51pub struct ProbeGeometryParams {pub points:Vec<ProbePoint>,pub settings:ProbeSettings}
52/// An expanded trigger move, never ordinary cutting. The reference is the
53/// bottom of the stylus sphere; expected target = nominal + radius*normal - Z*radius.
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
55#[derive(Clone,Debug,PartialEq,Serialize,Deserialize)]
56#[serde(rename_all="camelCase")]
57pub struct ProbeCycle {
58    pub target:Point3,
59    pub approach:Point3,
60    pub nominal:ProbePoint,
61    pub stylus_radius:f64,
62    pub overtravel:f64,
63    pub feed:f64,
64    pub kind:ProbeKind,
65    pub result_var:String,
66    pub point_index:u32,
67    pub point_count:u32,
68    /// Update intent for the completed cycle; never apply per point.
69    pub wcs:Option<WorkOffset>,
70}
71impl ProbeSettings {
72    pub fn validate(&self)->Result<(),String> {
73        for v in [self.stylus_radius,self.shank_radius,self.shank_length,self.approach_distance,self.overtravel,self.feed] {if !v.is_finite()||v<=0. {return Err("probe dimensions, travel and feed must be finite and positive".into());}}
74        if self.shank_radius>=self.stylus_radius || self.shank_length<=self.stylus_radius || self.result_prefix.is_empty() || !self.result_prefix.bytes().all(|c|c.is_ascii_alphanumeric()||c==b'_') {return Err("invalid probe shank or result prefix".into());}Ok(())
75    }
76}
77impl ProbePoint {
78    pub fn validate(&self)->Result<(),String> {
79        if self.id.is_empty() || !self.id.bytes().all(|c|c.is_ascii_alphanumeric()||c==b'_') || self.face.solid.is_empty() || !self.position.is_finite() || !self.normal.is_finite() || (self.normal.length()-1.).abs()>1e-6 || !self.lower_tolerance.is_finite() || !self.upper_tolerance.is_finite() || self.lower_tolerance>self.upper_tolerance {return Err("invalid probe nominal point, normal, identity or tolerance".into());}Ok(())
80    }
81}
82impl ProbeWcsParams {pub fn validate(&self)->Result<(),String> {self.settings.validate()?;if self.kind==ProbeKind::Inspection || self.wcs.number==0 || !self.wcs.origin.is_finite() {return Err("invalid probing WCS intent".into());}validate_points(&self.points)}}
83impl ProbeGeometryParams {pub fn validate(&self)->Result<(),String> {self.settings.validate()?;validate_points(&self.points)}}
84fn validate_points(points:&[ProbePoint])->Result<(),String> {if points.is_empty()||points.len()>10000 {return Err("probe needs 1..10000 nominal points".into());}let mut ids=std::collections::BTreeSet::new();for p in points {p.validate()?;if !ids.insert(&p.id) {return Err("duplicate probe point identity".into());}}Ok(())}
85impl ProbeCycle {pub fn is_finite(&self)->bool {self.target.is_finite()&&self.approach.is_finite()&&self.nominal.validate().is_ok()&&self.stylus_radius.is_finite()&&self.stylus_radius>0.&&self.overtravel.is_finite()&&self.overtravel>0.&&self.feed.is_finite()&&self.feed>0.}}