phytrace_sdk/models/domains/
manipulation.rs1use serde::{Deserialize, Serialize};
4use validator::Validate;
5
6use super::common::{ObjectRef, Pose, Position3D};
7use crate::models::enums::{ArmState, GraspPhase, ManipulationControlMode};
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct ManipulationDomain {
12 #[serde(skip_serializing_if = "Option::is_none")]
14 pub arm_state: Option<ArmState>,
15
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub end_effector: Option<EndEffector>,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub workspace: Option<Workspace>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub grasp: Option<GraspOperation>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub hand_guiding: Option<HandGuiding>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub control_mode: Option<ManipulationControlMode>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub collision_avoidance_active: Option<bool>,
39}
40
41#[derive(Debug, Clone, Default, Serialize, Deserialize)]
43pub struct EndEffector {
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub effector_type: Option<String>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub pose: Option<Pose>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub target_pose: Option<Pose>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub velocity_mps: Option<f64>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub force_n: Option<f64>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub torque_nm: Option<f64>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub tcp_offset: Option<Position3D>,
71}
72
73#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
75pub struct Workspace {
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub workspace_id: Option<String>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub workspace_type: Option<String>,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub within_bounds: Option<bool>,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
90 #[validate(range(min = 0.0))]
91 pub distance_to_boundary_m: Option<f64>,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
95 #[validate(range(min = 0.0, max = 1.0))]
96 pub reachability: Option<f64>,
97}
98
99#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
101pub struct GraspOperation {
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub phase: Option<GraspPhase>,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub target_object: Option<ObjectRef>,
109
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub grasp_type: Option<String>,
113
114 #[serde(skip_serializing_if = "Option::is_none")]
116 #[validate(range(min = 0.0, max = 1.0))]
117 pub confidence: Option<f64>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub grasp_pose: Option<Pose>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub pre_grasp_pose: Option<Pose>,
126
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub is_stable: Option<bool>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub slip_detected: Option<bool>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub force_n: Option<f64>,
138}
139
140#[derive(Debug, Clone, Default, Serialize, Deserialize)]
142pub struct HandGuiding {
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub enabled: Option<bool>,
146
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub is_active: Option<bool>,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub detected_force_n: Option<f64>,
154
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub compliance_level: Option<String>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub recording_path: Option<bool>,
162}
163
164impl ManipulationDomain {
165 pub fn new(state: ArmState) -> Self {
167 Self {
168 arm_state: Some(state),
169 ..Default::default()
170 }
171 }
172
173 pub fn with_end_effector(mut self, end_effector: EndEffector) -> Self {
175 self.end_effector = Some(end_effector);
176 self
177 }
178
179 pub fn with_grasp(mut self, grasp: GraspOperation) -> Self {
181 self.grasp = Some(grasp);
182 self
183 }
184
185 pub fn with_hand_guiding(mut self, hand_guiding: HandGuiding) -> Self {
187 self.hand_guiding = Some(hand_guiding);
188 self
189 }
190}
191
192impl GraspOperation {
193 pub fn new(phase: GraspPhase) -> Self {
195 Self {
196 phase: Some(phase),
197 ..Default::default()
198 }
199 }
200
201 pub fn with_target(mut self, object: ObjectRef) -> Self {
203 self.target_object = Some(object);
204 self
205 }
206
207 pub fn with_confidence(mut self, confidence: f64) -> Self {
209 self.confidence = Some(confidence);
210 self
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_manipulation_domain() {
220 let manip = ManipulationDomain::new(ArmState::Moving)
221 .with_grasp(GraspOperation::new(GraspPhase::Approaching));
222
223 assert_eq!(manip.arm_state, Some(ArmState::Moving));
224 assert!(manip.grasp.is_some());
225 }
226
227 #[test]
232 fn test_manipulation_domain_default() {
233 let manip = ManipulationDomain::default();
234 assert!(manip.arm_state.is_none());
235 assert!(manip.end_effector.is_none());
236 assert!(manip.workspace.is_none());
237 assert!(manip.grasp.is_none());
238 assert!(manip.hand_guiding.is_none());
239 assert!(manip.control_mode.is_none());
240 assert!(manip.collision_avoidance_active.is_none());
241 }
242
243 #[test]
244 fn test_manipulation_domain_new() {
245 let manip = ManipulationDomain::new(ArmState::Home);
246 assert_eq!(manip.arm_state, Some(ArmState::Home));
247 }
248
249 #[test]
250 fn test_manipulation_domain_with_end_effector() {
251 let end_effector = EndEffector {
252 effector_type: Some("parallel_gripper".to_string()),
253 velocity_mps: Some(0.5),
254 force_n: Some(10.0),
255 ..Default::default()
256 };
257
258 let manip = ManipulationDomain::new(ArmState::Moving).with_end_effector(end_effector);
259
260 assert!(manip.end_effector.is_some());
261 assert_eq!(manip.end_effector.as_ref().unwrap().force_n, Some(10.0));
262 }
263
264 #[test]
265 fn test_manipulation_domain_with_grasp() {
266 let grasp = GraspOperation::new(GraspPhase::Closing).with_confidence(0.95);
267
268 let manip = ManipulationDomain::new(ArmState::Grasping).with_grasp(grasp);
269
270 assert!(manip.grasp.is_some());
271 assert_eq!(manip.grasp.as_ref().unwrap().confidence, Some(0.95));
272 }
273
274 #[test]
275 fn test_manipulation_domain_with_hand_guiding() {
276 let hand_guiding = HandGuiding {
277 enabled: Some(true),
278 is_active: Some(true),
279 detected_force_n: Some(5.0),
280 ..Default::default()
281 };
282
283 let manip = ManipulationDomain::default().with_hand_guiding(hand_guiding);
284
285 assert!(manip.hand_guiding.is_some());
286 assert_eq!(manip.hand_guiding.as_ref().unwrap().is_active, Some(true));
287 }
288
289 #[test]
290 fn test_manipulation_domain_chained_builders() {
291 let manip = ManipulationDomain::new(ArmState::Moving)
292 .with_end_effector(EndEffector::default())
293 .with_grasp(GraspOperation::new(GraspPhase::PreGrasp))
294 .with_hand_guiding(HandGuiding::default());
295
296 assert!(manip.end_effector.is_some());
297 assert!(manip.grasp.is_some());
298 assert!(manip.hand_guiding.is_some());
299 }
300
301 #[test]
306 fn test_end_effector_default() {
307 let ee = EndEffector::default();
308 assert!(ee.effector_type.is_none());
309 assert!(ee.pose.is_none());
310 assert!(ee.target_pose.is_none());
311 assert!(ee.velocity_mps.is_none());
312 assert!(ee.force_n.is_none());
313 assert!(ee.torque_nm.is_none());
314 assert!(ee.tcp_offset.is_none());
315 }
316
317 #[test]
318 fn test_end_effector_full() {
319 let ee = EndEffector {
320 effector_type: Some("vacuum_gripper".to_string()),
321 pose: Some(Pose::from_position(0.5, 0.2, 0.8)),
322 target_pose: Some(Pose::from_position(0.6, 0.2, 0.5)),
323 velocity_mps: Some(0.3),
324 force_n: Some(15.0),
325 torque_nm: Some(2.5),
326 tcp_offset: Some(Position3D::new(0.0, 0.0, 0.1)),
327 };
328
329 assert_eq!(ee.effector_type, Some("vacuum_gripper".to_string()));
330 assert_eq!(ee.velocity_mps, Some(0.3));
331 assert_eq!(ee.torque_nm, Some(2.5));
332 }
333
334 #[test]
335 fn test_end_effector_serialization() {
336 let ee = EndEffector {
337 effector_type: Some("magnetic".to_string()),
338 force_n: Some(50.0),
339 ..Default::default()
340 };
341
342 let json = serde_json::to_string(&ee).unwrap();
343 let deserialized: EndEffector = serde_json::from_str(&json).unwrap();
344 assert_eq!(deserialized.force_n, Some(50.0));
345 }
346
347 #[test]
352 fn test_workspace_default() {
353 let ws = Workspace::default();
354 assert!(ws.workspace_id.is_none());
355 assert!(ws.workspace_type.is_none());
356 assert!(ws.within_bounds.is_none());
357 assert!(ws.distance_to_boundary_m.is_none());
358 assert!(ws.reachability.is_none());
359 }
360
361 #[test]
362 fn test_workspace_full() {
363 let ws = Workspace {
364 workspace_id: Some("ws-001".to_string()),
365 workspace_type: Some("picking_station".to_string()),
366 within_bounds: Some(true),
367 distance_to_boundary_m: Some(0.15),
368 reachability: Some(0.95),
369 };
370
371 assert_eq!(ws.within_bounds, Some(true));
372 assert_eq!(ws.reachability, Some(0.95));
373 }
374
375 #[test]
376 fn test_workspace_serialization() {
377 let ws = Workspace {
378 workspace_type: Some("assembly".to_string()),
379 ..Default::default()
380 };
381
382 let json = serde_json::to_string(&ws).unwrap();
383 let deserialized: Workspace = serde_json::from_str(&json).unwrap();
384 assert_eq!(deserialized.workspace_type, Some("assembly".to_string()));
385 }
386
387 #[test]
392 fn test_grasp_operation_new() {
393 let grasp = GraspOperation::new(GraspPhase::Failed);
394 assert_eq!(grasp.phase, Some(GraspPhase::Failed));
395 }
396
397 #[test]
398 fn test_grasp_operation_with_target() {
399 let obj = ObjectRef {
400 object_id: Some("obj-001".to_string()),
401 object_type: Some("box".to_string()),
402 ..Default::default()
403 };
404
405 let grasp = GraspOperation::new(GraspPhase::Approaching).with_target(obj);
406
407 assert!(grasp.target_object.is_some());
408 assert_eq!(
409 grasp.target_object.as_ref().unwrap().object_id,
410 Some("obj-001".to_string())
411 );
412 }
413
414 #[test]
415 fn test_grasp_operation_with_confidence() {
416 let grasp = GraspOperation::new(GraspPhase::Closing).with_confidence(0.88);
417
418 assert_eq!(grasp.confidence, Some(0.88));
419 }
420
421 #[test]
422 fn test_grasp_operation_default() {
423 let grasp = GraspOperation::default();
424 assert!(grasp.phase.is_none());
425 assert!(grasp.target_object.is_none());
426 assert!(grasp.grasp_type.is_none());
427 assert!(grasp.confidence.is_none());
428 assert!(grasp.grasp_pose.is_none());
429 assert!(grasp.pre_grasp_pose.is_none());
430 assert!(grasp.is_stable.is_none());
431 assert!(grasp.slip_detected.is_none());
432 assert!(grasp.force_n.is_none());
433 }
434
435 #[test]
436 fn test_grasp_operation_full() {
437 let grasp = GraspOperation {
438 phase: Some(GraspPhase::Secured),
439 target_object: Some(ObjectRef::default()),
440 grasp_type: Some("pinch".to_string()),
441 confidence: Some(0.92),
442 grasp_pose: Some(Pose::from_position(0.1, 0.2, 0.3)),
443 pre_grasp_pose: Some(Pose::from_position(0.1, 0.2, 0.5)),
444 is_stable: Some(true),
445 slip_detected: Some(false),
446 force_n: Some(8.5),
447 };
448
449 assert_eq!(grasp.grasp_type, Some("pinch".to_string()));
450 assert_eq!(grasp.is_stable, Some(true));
451 assert_eq!(grasp.force_n, Some(8.5));
452 }
453
454 #[test]
455 fn test_grasp_operation_chained_builders() {
456 let grasp = GraspOperation::new(GraspPhase::Closing)
457 .with_target(ObjectRef::default())
458 .with_confidence(0.9);
459
460 assert!(grasp.target_object.is_some());
461 assert_eq!(grasp.confidence, Some(0.9));
462 }
463
464 #[test]
469 fn test_hand_guiding_default() {
470 let hg = HandGuiding::default();
471 assert!(hg.enabled.is_none());
472 assert!(hg.is_active.is_none());
473 assert!(hg.detected_force_n.is_none());
474 assert!(hg.compliance_level.is_none());
475 assert!(hg.recording_path.is_none());
476 }
477
478 #[test]
479 fn test_hand_guiding_full() {
480 let hg = HandGuiding {
481 enabled: Some(true),
482 is_active: Some(true),
483 detected_force_n: Some(3.5),
484 compliance_level: Some("medium".to_string()),
485 recording_path: Some(true),
486 };
487
488 assert_eq!(hg.enabled, Some(true));
489 assert_eq!(hg.detected_force_n, Some(3.5));
490 assert_eq!(hg.recording_path, Some(true));
491 }
492
493 #[test]
494 fn test_hand_guiding_serialization() {
495 let hg = HandGuiding {
496 enabled: Some(true),
497 compliance_level: Some("high".to_string()),
498 ..Default::default()
499 };
500
501 let json = serde_json::to_string(&hg).unwrap();
502 let deserialized: HandGuiding = serde_json::from_str(&json).unwrap();
503 assert_eq!(deserialized.compliance_level, Some("high".to_string()));
504 }
505
506 #[test]
511 fn test_manipulation_domain_serialization_roundtrip() {
512 let manip = ManipulationDomain::new(ArmState::Moving)
513 .with_end_effector(EndEffector {
514 force_n: Some(10.0),
515 ..Default::default()
516 })
517 .with_grasp(GraspOperation::new(GraspPhase::Approaching));
518
519 let json = serde_json::to_string(&manip).unwrap();
520 let deserialized: ManipulationDomain = serde_json::from_str(&json).unwrap();
521
522 assert_eq!(deserialized.arm_state, Some(ArmState::Moving));
523 assert!(deserialized.end_effector.is_some());
524 assert!(deserialized.grasp.is_some());
525 }
526}