Skip to main content

brep_cam/multiaxis/
orientation.rs

1//! Tool-axis control for multi-axis finishing (lane C10b, row 1): the axis
2//! of a cutter-location point from the surface normal and travel direction
3//! (lead about the binormal, tilt about the tangent), toward or away from a
4//! point or a line, or at a fixed angle from vertical; smoothing that limits
5//! the change per sample; a limit on the angle from vertical. The
6//! [`AxisMode`] enum itself lives in the model (persisted operation data);
7//! this module adds its geometry.
8
9use super::slerp;
10use crate::kinematics::rotate;
11pub use crate::model::AxisMode;
12use crate::model::Point3;
13
14impl AxisMode {
15    /// The unit axis at a contact point `cc` with unit surface normal `n` and
16    /// unit travel tangent `t` (already perpendicular to `n`). `None` when the
17    /// mode is degenerate there (the contact lies on the point or the line).
18    pub fn axis_at(&self, cc: Point3, n: Point3, t: Point3) -> Option<Point3> {
19        match self {
20            AxisMode::Normal { lead_deg, tilt_deg } => {
21                let b = n.cross(t);
22                let a = rotate(b, lead_deg.to_radians(), n);
23                rotate(t, tilt_deg.to_radians(), a).normalized()
24            }
25            AxisMode::Point { point, away } => {
26                let d = if *away { cc.sub(*point) } else { point.sub(cc) };
27                d.normalized()
28            }
29            AxisMode::Line { origin, direction, away } => {
30                let d = direction.normalized()?;
31                let rel = cc.sub(*origin);
32                let perp = rel.sub(d.scale(rel.dot(d)));
33                let v = if *away { perp } else { perp.scale(-1.0) };
34                v.normalized()
35            }
36            AxisMode::FixedFromVertical { angle_deg, azimuth_deg } => {
37                let (s, c) = angle_deg.to_radians().sin_cos();
38                let (sa, ca) = azimuth_deg.to_radians().sin_cos();
39                Some(Point3::new(s * ca, s * sa, c))
40            }
41        }
42    }
43}
44
45/// Limit the angle between consecutive axes to `max_rad` (alternating
46/// forward and backward passes until stable); returns the number of changes.
47pub fn smooth_axes(axes: &mut [Point3], max_rad: f64) -> usize {
48    let mut changed = 0;
49    let n = axes.len();
50    for round in 0..64 {
51        let mut round_changes = 0;
52        for k in 1..n {
53            let (i, j) = if round % 2 == 0 { (k - 1, k) } else { (n - k, n - k - 1) };
54            let angle = axes[i].dot(axes[j]).clamp(-1.0, 1.0).acos();
55            if angle > max_rad {
56                axes[j] = slerp(axes[i], axes[j], (max_rad / angle) * (1.0 - 1e-9));
57                round_changes += 1;
58            }
59        }
60        changed += round_changes;
61        if round_changes == 0 {
62            break;
63        }
64    }
65    changed
66}
67
68/// Pull `axis` back to within `max_rad` of +Z when it leans further; returns
69/// the clamped axis and whether it changed.
70pub fn clamp_from_vertical(axis: Point3, max_rad: f64) -> (Point3, bool) {
71    let angle = axis.dot(Point3::Z).clamp(-1.0, 1.0).acos();
72    if angle <= max_rad {
73        return (axis, false);
74    }
75    (slerp(Point3::Z, axis, max_rad / angle), true)
76}