slvs/constraint/
length_ratio.rs

1use serde::{Deserialize, Serialize};
2
3use super::AsConstraintData;
4use crate::{
5    bindings::{Slvs_hEntity, Slvs_hGroup, SLVS_C_LENGTH_RATIO},
6    define_element,
7    element::{AsGroup, AsHandle, AsSlvsType, FromSystem},
8    entity::{EntityHandle, LineSegment, Workplane},
9    group::Group,
10    System,
11};
12
13define_element!(
14    SLVS_C_LENGTH_RATIO,
15    /// The length of `line_a` is `ratio` times longer than `line_b`.
16    struct LengthRatio {
17        line_a: EntityHandle<LineSegment>,
18        line_b: EntityHandle<LineSegment>,
19        ratio: f64,
20        /// If provided, constraint applies when projected onto this workplane.
21        workplane: Option<EntityHandle<Workplane>>,
22    }
23);
24
25impl AsConstraintData for LengthRatio {
26    fn workplane(&self) -> Option<Slvs_hEntity> {
27        self.workplane.map(|workplane| workplane.handle())
28    }
29
30    fn entities(&self) -> Option<[Slvs_hEntity; 4]> {
31        Some([self.line_a.handle(), self.line_b.handle(), 0, 0])
32    }
33
34    fn val(&self) -> Option<f64> {
35        Some(self.ratio)
36    }
37}
38
39impl FromSystem for LengthRatio {
40    fn from_system(sys: &System, element: &impl AsHandle) -> Result<Self, &'static str>
41    where
42        Self: Sized,
43    {
44        let slvs_constraint = sys.slvs_constraint(element.handle())?;
45
46        if SLVS_C_LENGTH_RATIO == slvs_constraint.type_ as _ {
47            Ok(Self {
48                group: Group(slvs_constraint.group),
49                line_a: EntityHandle::new(slvs_constraint.entityA),
50                line_b: EntityHandle::new(slvs_constraint.entityB),
51                ratio: slvs_constraint.valA,
52                workplane: match slvs_constraint.wrkpl {
53                    0 => None,
54                    h => Some(EntityHandle::new(h)),
55                },
56            })
57        } else {
58            Err("Expected constraint to have type SLVS_C_LENGTH_RATIO.")
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use crate::{
66        constraint::LengthRatio,
67        entity::{LineSegment, Normal, Point, Workplane},
68        len_within_tolerance,
69        utils::{distance, make_quaternion, project_on_plane},
70        System,
71    };
72
73    #[test]
74    fn on_workplane() {
75        let mut sys = System::new();
76
77        let workplane_g = sys.add_group();
78        let origin = sys
79            .sketch(Point::new_in_3d(workplane_g, [-20.0, 17.0, -59.0]))
80            .expect("origin created");
81        let normal = sys
82            .sketch(Normal::new_in_3d(
83                workplane_g,
84                make_quaternion([89.0, -52.0, 94.0], [-4.0, -44.0, -15.0]),
85            ))
86            .expect("normal created");
87        let workplane = sys
88            .sketch(Workplane::new(workplane_g, origin, normal))
89            .expect("workplane created");
90
91        let g = sys.add_group();
92
93        let a_start = sys
94            .sketch(Point::new_on_workplane(g, workplane, [99.0, -6.0]))
95            .expect("point created");
96        let a_end = sys
97            .sketch(Point::new_on_workplane(g, workplane, [70.0, 33.0]))
98            .expect("point created");
99        let line_a = sys
100            .sketch(LineSegment::new(g, a_start, a_end))
101            .expect("line created");
102
103        let b_start = sys
104            .sketch(Point::new_in_3d(g, [-58.0, 7.0, 56.0]))
105            .expect("point created");
106        let b_end = sys
107            .sketch(Point::new_in_3d(g, [68.0, 63.0, -77.0]))
108            .expect("point created");
109        let line_b = sys
110            .sketch(LineSegment::new(g, b_start, b_end))
111            .expect("line created");
112
113        let ratio = 2.5;
114        sys.constrain(LengthRatio::new(g, line_a, line_b, ratio, Some(workplane)))
115            .expect("constraint added");
116
117        dbg!(sys.solve(&g));
118
119        if let (
120            Point::In3d { coords: origin, .. },
121            Normal::In3d { quaternion, .. },
122            Point::OnWorkplane {
123                coords: a_start, ..
124            },
125            Point::OnWorkplane { coords: a_end, .. },
126            Point::In3d {
127                coords: b_start, ..
128            },
129            Point::In3d { coords: b_end, .. },
130        ) = (
131            sys.entity_data(&origin).expect("data found"),
132            sys.entity_data(&normal).expect("data found"),
133            sys.entity_data(&a_start).expect("data found"),
134            sys.entity_data(&a_end).expect("data found"),
135            sys.entity_data(&b_start).expect("data found"),
136            sys.entity_data(&b_end).expect("data found"),
137        ) {
138            let b_start = project_on_plane(b_start, origin, quaternion);
139            let b_end = project_on_plane(b_end, origin, quaternion);
140
141            len_within_tolerance!(distance(a_start, a_end) / distance(b_start, b_end), ratio);
142        } else {
143            unreachable!()
144        };
145    }
146
147    #[test]
148    fn in_3d() {
149        let mut sys = System::new();
150
151        let workplane_g = sys.add_group();
152        let origin = sys
153            .sketch(Point::new_in_3d(workplane_g, [-20.0, 17.0, -59.0]))
154            .expect("origin created");
155        let normal = sys
156            .sketch(Normal::new_in_3d(
157                workplane_g,
158                make_quaternion([89.0, -52.0, 94.0], [-4.0, -44.0, -15.0]),
159            ))
160            .expect("normal created");
161        let workplane = sys
162            .sketch(Workplane::new(workplane_g, origin, normal))
163            .expect("workplane created");
164
165        let g = sys.add_group();
166
167        let a_start = sys
168            .sketch(Point::new_on_workplane(g, workplane, [99.0, -6.0]))
169            .expect("point created");
170        let a_end = sys
171            .sketch(Point::new_on_workplane(g, workplane, [70.0, 33.0]))
172            .expect("point created");
173        let line_a = sys
174            .sketch(LineSegment::new(g, a_start, a_end))
175            .expect("line created");
176
177        let b_start = sys
178            .sketch(Point::new_in_3d(g, [-58.0, 7.0, 56.0]))
179            .expect("point created");
180        let b_end = sys
181            .sketch(Point::new_in_3d(g, [68.0, 63.0, -77.0]))
182            .expect("point created");
183        let line_b = sys
184            .sketch(LineSegment::new(g, b_start, b_end))
185            .expect("line created");
186
187        let ratio = 5.3;
188        sys.constrain(LengthRatio::new(g, line_a, line_b, ratio, None))
189            .expect("constraint added");
190
191        dbg!(sys.solve(&g));
192
193        if let (
194            Point::OnWorkplane {
195                coords: a_start, ..
196            },
197            Point::OnWorkplane { coords: a_end, .. },
198            Point::In3d {
199                coords: b_start, ..
200            },
201            Point::In3d { coords: b_end, .. },
202        ) = (
203            sys.entity_data(&a_start).expect("data found"),
204            sys.entity_data(&a_end).expect("data found"),
205            sys.entity_data(&b_start).expect("data found"),
206            sys.entity_data(&b_end).expect("data found"),
207        ) {
208            len_within_tolerance!(distance(a_start, a_end) / distance(b_start, b_end), ratio);
209        } else {
210            unreachable!()
211        };
212    }
213}