slvs/constraint/
pt_line_distance.rs

1use serde::{Deserialize, Serialize};
2
3use super::AsConstraintData;
4use crate::{
5    bindings::{Slvs_hEntity, Slvs_hGroup, SLVS_C_PT_LINE_DISTANCE},
6    define_element,
7    element::{AsGroup, AsHandle, AsSlvsType, FromSystem},
8    entity::{EntityHandle, LineSegment, Point, Workplane},
9    group::Group,
10    System,
11};
12
13define_element!(
14    SLVS_C_PT_LINE_DISTANCE,
15    /// The distance between `point` and `line` is equal to `distance`.
16    ///
17    /// If the constraint is projected onto a workplane, `distance` is a signed distance.
18    /// Positive vs. negative `distance` correspond to a point that is above vs. below
19    /// the line.
20    ///
21    /// If no workplane is provided, then `distance` must always be positive.
22    struct PtLineDistance {
23        point: EntityHandle<Point>,
24        line: EntityHandle<LineSegment>,
25        distance: f64,
26        /// If provided, constraint applies when projected onto this workplane.
27        workplane: Option<EntityHandle<Workplane>>,
28    }
29);
30
31impl AsConstraintData for PtLineDistance {
32    fn workplane(&self) -> Option<Slvs_hEntity> {
33        self.workplane.map(|workplane| workplane.handle())
34    }
35
36    fn val(&self) -> Option<f64> {
37        Some(self.distance)
38    }
39
40    fn points(&self) -> Option<[Slvs_hEntity; 2]> {
41        Some([self.point.handle(), 0])
42    }
43
44    fn entities(&self) -> Option<[Slvs_hEntity; 4]> {
45        Some([self.line.handle(), 0, 0, 0])
46    }
47}
48
49impl FromSystem for PtLineDistance {
50    fn from_system(sys: &System, element: &impl AsHandle) -> Result<Self, &'static str>
51    where
52        Self: Sized,
53    {
54        let slvs_constraint = sys.slvs_constraint(element.handle())?;
55
56        if SLVS_C_PT_LINE_DISTANCE == slvs_constraint.type_ as _ {
57            Ok(Self {
58                group: Group(slvs_constraint.group),
59                point: EntityHandle::new(slvs_constraint.ptA),
60                line: EntityHandle::new(slvs_constraint.entityA),
61                distance: slvs_constraint.valA,
62                workplane: match slvs_constraint.wrkpl {
63                    0 => None,
64                    h => Some(EntityHandle::new(h)),
65                },
66            })
67        } else {
68            Err("Expected constraint to have type SLVS_C_PT_LINE_DISTANCE.")
69        }
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use crate::{
76        constraint::PtLineDistance,
77        entity::{LineSegment, Normal, Point, Workplane},
78        len_within_tolerance,
79        utils::{distance, make_quaternion, project_on_line, project_on_plane},
80        System,
81    };
82
83    #[test]
84    fn on_workplane() {
85        let mut sys = System::new();
86
87        let workplane_g = sys.add_group();
88        let origin = sys
89            .sketch(Point::new_in_3d(workplane_g, [4.0, 89.0, 83.0]))
90            .expect("origin created");
91        let normal = sys
92            .sketch(Normal::new_in_3d(
93                workplane_g,
94                make_quaternion([69.0, -98.0, 48.0], [-25.0, -50.0, 57.0]),
95            ))
96            .expect("normal created");
97        let workplane = sys
98            .sketch(Workplane::new(workplane_g, origin, normal))
99            .expect("workplane created");
100
101        let g = sys.add_group();
102        let point = sys
103            .sketch(Point::new_in_3d(g, [-14.0, 67.0, -68.0]))
104            .expect("point created");
105
106        let line_start = sys
107            .sketch(Point::new_in_3d(g, [86.0, -54.0, -38.0]))
108            .expect("point created");
109        let line_end = sys
110            .sketch(Point::new_in_3d(g, [32.0, -92.0, -64.0]))
111            .expect("point created");
112        let line = sys
113            .sketch(LineSegment::new(g, line_start, line_end))
114            .expect("line created");
115
116        let dist = 14.0;
117        sys.constrain(PtLineDistance::new(g, point, line, dist, Some(workplane)))
118            .expect("constraint added");
119
120        dbg!(sys.solve(&g));
121
122        if let (
123            Point::In3d { coords: origin, .. },
124            Normal::In3d { quaternion, .. },
125            Point::In3d {
126                coords: point_coords,
127                ..
128            },
129            Point::In3d {
130                coords: line_start_coords,
131                ..
132            },
133            Point::In3d {
134                coords: line_end_coords,
135                ..
136            },
137        ) = (
138            sys.entity_data(&origin).expect("data found"),
139            sys.entity_data(&normal).expect("data found"),
140            sys.entity_data(&point).expect("data found"),
141            sys.entity_data(&line_start).expect("data found"),
142            sys.entity_data(&line_end).expect("data found"),
143        ) {
144            let proj_pt_coords = project_on_plane(point_coords, origin, quaternion);
145            let proj_line_start = project_on_plane(line_start_coords, origin, quaternion);
146            let proj_line_end = project_on_plane(line_end_coords, origin, quaternion);
147
148            let point_on_line = project_on_line(proj_pt_coords, proj_line_start, proj_line_end);
149
150            len_within_tolerance!(distance(proj_pt_coords, point_on_line), dist);
151        } else {
152            unreachable!()
153        }
154    }
155
156    #[test]
157    fn in_3d() {
158        let mut sys = System::new();
159
160        let g = sys.add_group();
161        let point = sys
162            .sketch(Point::new_in_3d(g, [-70.0, 67.0, -28.0]))
163            .expect("point created");
164
165        let line_start = sys
166            .sketch(Point::new_in_3d(g, [-45.0, 82.0, -29.0]))
167            .expect("point created");
168        let line_end = sys
169            .sketch(Point::new_in_3d(g, [67.0, 43.0, -31.0]))
170            .expect("point created");
171        let line = sys
172            .sketch(LineSegment::new(g, line_start, line_end))
173            .expect("line created");
174
175        let dist = 29.0;
176        sys.constrain(PtLineDistance::new(g, point, line, dist, None))
177            .expect("constraint added");
178
179        dbg!(sys.solve(&g));
180
181        if let (
182            Point::In3d {
183                coords: point_coords,
184                ..
185            },
186            Point::In3d {
187                coords: line_start_coords,
188                ..
189            },
190            Point::In3d {
191                coords: line_end_coords,
192                ..
193            },
194        ) = (
195            sys.entity_data(&point).expect("data found"),
196            sys.entity_data(&line_start).expect("data found"),
197            sys.entity_data(&line_end).expect("data found"),
198        ) {
199            let point_on_line = project_on_line(point_coords, line_start_coords, line_end_coords);
200
201            len_within_tolerance!(distance(point_coords, point_on_line), dist);
202        } else {
203            unreachable!()
204        }
205    }
206}