#[expect(
clippy::disallowed_types,
reason = "SolverArc is the one place allowed to hold the solver's arc datum"
)]
use ezpz::datatypes::inputs::DatumCircularArc;
use ezpz::datatypes::inputs::DatumPoint;
use kcl_error::SourceRange;
use crate::KclError;
use crate::execution::SketchVarId;
use crate::front::ArcDirection;
#[derive(Debug, Clone, Copy)]
pub(crate) struct SolverArc {
center: DatumPoint,
sweep_start: DatumPoint,
sweep_end: DatumPoint,
}
impl SolverArc {
pub(crate) fn new(
center: [SketchVarId; 2],
declared_start: [SketchVarId; 2],
declared_end: [SketchVarId; 2],
direction: ArcDirection,
range: SourceRange,
) -> Result<Self, KclError> {
let datum_point = |point: [SketchVarId; 2]| -> Result<DatumPoint, KclError> {
Ok(DatumPoint::new_xy(
point[0].to_constraint_id(range)?,
point[1].to_constraint_id(range)?,
))
};
let (sweep_start, sweep_end) = direction.ccw_order(declared_start, declared_end);
Ok(Self {
center: datum_point(center)?,
sweep_start: datum_point(sweep_start)?,
sweep_end: datum_point(sweep_end)?,
})
}
#[expect(
clippy::disallowed_types,
reason = "SolverArc is the one place allowed to build the solver's arc datum"
)]
fn datum(self) -> DatumCircularArc {
DatumCircularArc {
center: self.center,
start: self.sweep_start,
end: self.sweep_end,
}
}
#[expect(
clippy::disallowed_methods,
reason = "SolverArc is the one place allowed to build solver arc constraints"
)]
pub(crate) fn arc_constraint(self) -> ezpz::Constraint {
ezpz::Constraint::Arc(self.datum())
}
#[expect(
clippy::disallowed_methods,
reason = "SolverArc is the one place allowed to build solver arc constraints"
)]
pub(crate) fn point_coincident_constraint(self, point: DatumPoint) -> ezpz::Constraint {
ezpz::Constraint::PointArcCoincident(self.datum(), point)
}
#[expect(
clippy::disallowed_methods,
reason = "SolverArc is the one place allowed to build solver arc constraints"
)]
pub(crate) fn point_bisects_constraints(self, point: DatumPoint) -> [ezpz::Constraint; 2] {
ezpz::Constraint::point_bisects_arc(self.datum(), point)
}
#[expect(
clippy::disallowed_methods,
reason = "SolverArc is the one place allowed to build solver arc constraints"
)]
pub(crate) fn radius_constraint(self, radius: f64) -> ezpz::Constraint {
ezpz::Constraint::ArcRadius(self.datum(), radius)
}
}
#[cfg(test)]
mod tests {
use super::*;
const CENTER: [SketchVarId; 2] = [SketchVarId(0), SketchVarId(1)];
const START: [SketchVarId; 2] = [SketchVarId(2), SketchVarId(3)];
const END: [SketchVarId; 2] = [SketchVarId(4), SketchVarId(5)];
fn point_ids(point: DatumPoint) -> [ezpz::Id; 2] {
[point.x_id, point.y_id]
}
#[test]
fn counterclockwise_arc_keeps_declared_order() {
let arc = SolverArc::new(CENTER, START, END, ArcDirection::Ccw, SourceRange::default()).unwrap();
let datum = arc.datum();
assert_eq!(point_ids(datum.center), [0, 1]);
assert_eq!(point_ids(datum.start), [2, 3]);
assert_eq!(point_ids(datum.end), [4, 5]);
}
#[test]
fn clockwise_arc_swaps_declared_start_and_end() {
let arc = SolverArc::new(CENTER, START, END, ArcDirection::Cw, SourceRange::default()).unwrap();
let datum = arc.datum();
assert_eq!(point_ids(datum.center), [0, 1]);
assert_eq!(point_ids(datum.start), [4, 5]);
assert_eq!(point_ids(datum.end), [2, 3]);
}
}