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 struct LengthRatio {
17 line_a: EntityHandle<LineSegment>,
18 line_b: EntityHandle<LineSegment>,
19 ratio: f64,
20 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}