rs_opw_kinematics/
parameters_from_file.rs1use std::path::Path;
4
5use garde::Validate;
6use serde::Deserialize;
7use serde_saphyr::Options;
8
9use crate::parameter_error::ParameterError;
10use crate::parameters::opw_kinematics::Parameters;
11
12fn default_offsets() -> Vec<f64> { vec![0.0; 6] }
13fn default_sign_corrections() -> Vec<i8> { vec![1; 6] }
14
15fn opw_geometry_parameter(v: &f64, _ctx: &()) -> garde::Result {
16 if !v.is_finite() {
17 return Err(garde::Error::new("must be finite"));
18 }
19 Ok(())
20}
21
22fn joint_offset(v: &f64, _ctx: &()) -> garde::Result {
23 if !v.is_finite() {
24 return Err(garde::Error::new("must be finite"));
25 }
26 let limit = 2.0 * std::f64::consts::PI;
27 if *v < -limit || *v > limit {
28 return Err(garde::Error::new("must be within [-2*PI, 2*PI]"));
29 }
30 Ok(())
31}
32
33fn sign_correction(v: &i8, _ctx: &()) -> garde::Result {
34 if *v != -1 && *v != 1 {
35 return Err(garde::Error::new("must be -1 or 1"));
36 }
37 Ok(())
38}
39
40#[derive(Deserialize, Validate)]
41struct GeometricParameters {
42 #[garde(custom(opw_geometry_parameter))]
43 pub a1: f64,
44 #[garde(custom(opw_geometry_parameter))]
45 pub a2: f64,
46 #[garde(custom(opw_geometry_parameter))]
47 pub b: f64,
48 #[garde(custom(opw_geometry_parameter))]
49 pub c1: f64,
50 #[garde(custom(opw_geometry_parameter))]
51 pub c2: f64,
52 #[garde(custom(opw_geometry_parameter))]
53 pub c3: f64,
54 #[garde(custom(opw_geometry_parameter))]
55 pub c4: f64,
56 #[serde(default)]
58 #[garde(range(min = 5, max = 6))]
59 pub dof: Option<i8>,
60}
61
62#[derive(Deserialize, Validate)]
63struct Root {
64 #[garde(dive)]
65 pub opw_kinematics_geometric_parameters: GeometricParameters,
66 #[serde(default = "default_offsets")]
67 #[garde(length(min = 5, max = 6), inner(custom(joint_offset)))]
68 pub opw_kinematics_joint_offsets: Vec<f64>,
69 #[serde(default = "default_sign_corrections")]
70 #[garde(length(min = 5, max = 6), inner(custom(sign_correction)))]
71 pub opw_kinematics_joint_sign_corrections: Vec<i8>,
72 #[serde(default)]
74 #[garde(range(min = 5, max = 6))]
75 pub dof: Option<i8>,
76}
77
78impl Parameters {
79 pub fn from_yaml_file<P: AsRef<Path>>(path: P) -> Result<Self, ParameterError> {
100 let contents = std::fs::read_to_string(path)?;
101 let root: Root = serde_saphyr::from_str_with_options_valid(
102 &contents,
103 Options { angle_conversions: true, ..Default::default() }
104 ).map_err(|e| ParameterError::ParseError(format!("{}", e)))?;
105
106 let dof = match (root.dof, root.opw_kinematics_geometric_parameters.dof) {
110 (Some(top), Some(inner)) if top != inner => {
111 return Err(ParameterError::ParseError(format!(
112 "dof appears at top-level ({}) and under geometric parameters ({}) with conflicting values",
113 top, inner
114 )));
115 }
116 (Some(top), _) => top,
117 (None, Some(inner)) => inner,
118 (None, None) => 6,
119 };
120
121 let mut sign_corrections = vec_to_six(root.opw_kinematics_joint_sign_corrections, 1i8)?;
124
125 let mut offsets = vec_to_six(root.opw_kinematics_joint_offsets, 0.0f64)?;
128
129 if dof == 5 {
131 if sign_corrections[5] != 0 {
132 sign_corrections[5] = 0;
134 }
135 if offsets[5] != 0.0 {
136 offsets[5] = 0.0;
138 }
139 }
140
141 let gp = &root.opw_kinematics_geometric_parameters;
142
143 Ok(Parameters {
144 a1: gp.a1,
145 a2: gp.a2,
146 b: gp.b,
147 c1: gp.c1,
148 c2: gp.c2,
149 c3: gp.c3,
150 c4: gp.c4,
151 dof,
152 offsets,
153 sign_corrections,
154 })
155 }
156}
157
158fn vec_to_six<T: Copy>(
163 mut v: Vec<T>,
164 pad: T,
165) -> Result<[T; 6], ParameterError> {
166 if v.len() == 5 {
167 v.push(pad);
168 }
169 v.try_into()
170 .map_err(|v: Vec<T>| ParameterError::InvalidLength { expected: 6, found: v.len() })
171}