Skip to main content

arael_sketch_solver/
constraints.rs

1// ---------------------------------------------------------------------------
2// Cross-constraints (stored in root collections)
3// ---------------------------------------------------------------------------
4
5/// Which endpoints are shared between two arcs for tangent constraints.
6#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
7#[arael::model]
8pub enum SharedEndpoint {
9    #[default]
10    None,
11    StartStart,
12    StartEnd,
13    EndStart,
14    EndEnd,
15}
16
17// -- Point-Point --
18
19#[derive(serde::Serialize, serde::Deserialize)]
20#[arael::model]
21#[arael(constraint(hb, {
22    [a.pos.x - b.pos.x, a.pos.y - b.pos.y]
23}))]
24pub struct CoincidentPP {
25    #[arael(ref = root.points)]
26    pub a: Ref<Point>,
27    #[arael(ref = root.points)]
28    pub b: Ref<Point>,
29    #[serde(default)]
30    pub nid: u32,
31    #[arael(constraint_index)]
32    #[serde(skip)]
33    pub cid: u32,
34    #[serde(skip)]
35    pub hb: CrossBlock<Point, Point>,
36}
37
38#[derive(serde::Serialize, serde::Deserialize)]
39#[arael::model]
40#[arael(constraint(hb, {
41    let dx = a.pos.x - b.pos.x;
42    let dy = a.pos.y - b.pos.y;
43    [(sqrt(dx * dx + dy * dy) - distancepp.distance) * sketch.constraint_isigma]
44}))]
45pub struct DistancePP {
46    #[arael(ref = root.points)]
47    pub a: Ref<Point>,
48    #[arael(ref = root.points)]
49    pub b: Ref<Point>,
50    pub distance: f64,
51    #[serde(default)]
52    pub nid: u32,
53    #[arael(constraint_index)]
54    #[serde(skip)]
55    pub cid: u32,
56    #[serde(skip)]
57    pub hb: CrossBlock<Point, Point>,
58}
59
60// -- Line-Line endpoint distance --
61
62#[derive(serde::Serialize, serde::Deserialize)]
63#[arael::model]
64#[arael(constraint(hb, {
65    let dx = a.p1.x - b.p1.x; let dy = a.p1.y - b.p1.y;
66    [(sqrt(dx * dx + dy * dy) - distancell11.distance) * sketch.constraint_isigma]
67}))]
68pub struct DistanceLL11 {
69    #[arael(ref = root.lines)] pub a: Ref<Line>,
70    #[arael(ref = root.lines)] pub b: Ref<Line>,
71    pub distance: f64,
72    #[serde(default)]
73    pub nid: u32,
74    #[arael(constraint_index)]
75    #[serde(skip)]
76    pub cid: u32,
77    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
78}
79
80#[derive(serde::Serialize, serde::Deserialize)]
81#[arael::model]
82#[arael(constraint(hb, {
83    let dx = a.p1.x - b.p2.x; let dy = a.p1.y - b.p2.y;
84    [(sqrt(dx * dx + dy * dy) - distancell12.distance) * sketch.constraint_isigma]
85}))]
86pub struct DistanceLL12 {
87    #[arael(ref = root.lines)] pub a: Ref<Line>,
88    #[arael(ref = root.lines)] pub b: Ref<Line>,
89    pub distance: f64,
90    #[serde(default)]
91    pub nid: u32,
92    #[arael(constraint_index)]
93    #[serde(skip)]
94    pub cid: u32,
95    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
96}
97
98#[derive(serde::Serialize, serde::Deserialize)]
99#[arael::model]
100#[arael(constraint(hb, {
101    let dx = a.p2.x - b.p1.x; let dy = a.p2.y - b.p1.y;
102    [(sqrt(dx * dx + dy * dy) - distancell21.distance) * sketch.constraint_isigma]
103}))]
104pub struct DistanceLL21 {
105    #[arael(ref = root.lines)] pub a: Ref<Line>,
106    #[arael(ref = root.lines)] pub b: Ref<Line>,
107    pub distance: f64,
108    #[serde(default)]
109    pub nid: u32,
110    #[arael(constraint_index)]
111    #[serde(skip)]
112    pub cid: u32,
113    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
114}
115
116#[derive(serde::Serialize, serde::Deserialize)]
117#[arael::model]
118#[arael(constraint(hb, {
119    let dx = a.p2.x - b.p2.x; let dy = a.p2.y - b.p2.y;
120    [(sqrt(dx * dx + dy * dy) - distancell22.distance) * sketch.constraint_isigma]
121}))]
122pub struct DistanceLL22 {
123    #[arael(ref = root.lines)] pub a: Ref<Line>,
124    #[arael(ref = root.lines)] pub b: Ref<Line>,
125    pub distance: f64,
126    #[serde(default)]
127    pub nid: u32,
128    #[arael(constraint_index)]
129    #[serde(skip)]
130    pub cid: u32,
131    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
132}
133
134// -- Line endpoint to Point distance --
135
136#[derive(serde::Serialize, serde::Deserialize)]
137#[arael::model]
138#[arael(constraint(hb, {
139    let dx = line.p1.x - point.pos.x; let dy = line.p1.y - point.pos.y;
140    [(sqrt(dx * dx + dy * dy) - distancelp1.distance) * sketch.constraint_isigma]
141}))]
142pub struct DistanceLP1 {
143    #[arael(ref = root.lines)] pub line: Ref<Line>,
144    #[arael(ref = root.points)] pub point: Ref<Point>,
145    pub distance: f64,
146    #[serde(default)]
147    pub nid: u32,
148    #[arael(constraint_index)]
149    #[serde(skip)]
150    pub cid: u32,
151    #[serde(skip)] pub hb: CrossBlock<Line, Point>,
152}
153
154#[derive(serde::Serialize, serde::Deserialize)]
155#[arael::model]
156#[arael(constraint(hb, {
157    let dx = line.p2.x - point.pos.x; let dy = line.p2.y - point.pos.y;
158    [(sqrt(dx * dx + dy * dy) - distancelp2.distance) * sketch.constraint_isigma]
159}))]
160pub struct DistanceLP2 {
161    #[arael(ref = root.lines)] pub line: Ref<Line>,
162    #[arael(ref = root.points)] pub point: Ref<Point>,
163    pub distance: f64,
164    #[serde(default)]
165    pub nid: u32,
166    #[arael(constraint_index)]
167    #[serde(skip)]
168    pub cid: u32,
169    #[serde(skip)] pub hb: CrossBlock<Line, Point>,
170}
171
172// -- Arc-Point distance --
173
174#[derive(serde::Serialize, serde::Deserialize)]
175#[arael::model]
176#[arael(constraint(hb, {
177    let dx = arc.center.x - point.pos.x; let dy = arc.center.y - point.pos.y;
178    [(sqrt(dx * dx + dy * dy) - distancearccenterp.distance) * sketch.constraint_isigma]
179}))]
180pub struct DistanceArcCenterP {
181    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
182    #[arael(ref = root.points)] pub point: Ref<Point>,
183    pub distance: f64,
184    #[serde(default)]
185    pub nid: u32,
186    #[arael(constraint_index)]
187    #[serde(skip)]
188    pub cid: u32,
189    #[serde(skip)] pub hb: CrossBlock<Arc, Point>,
190}
191
192#[derive(serde::Serialize, serde::Deserialize)]
193#[arael::model]
194#[arael(constraint(hb, {
195    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
196    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
197    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
198    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
199    let dx = sx - point.pos.x; let dy = sy - point.pos.y;
200    [(sqrt(dx * dx + dy * dy) - distancearcstartp.distance) * sketch.constraint_isigma]
201}))]
202pub struct DistanceArcStartP {
203    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
204    #[arael(ref = root.points)] pub point: Ref<Point>,
205    pub distance: f64,
206    #[serde(default)]
207    pub nid: u32,
208    #[arael(constraint_index)]
209    #[serde(skip)]
210    pub cid: u32,
211    #[serde(skip)] pub hb: CrossBlock<Arc, Point>,
212}
213
214#[derive(serde::Serialize, serde::Deserialize)]
215#[arael::model]
216#[arael(constraint(hb, {
217    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
218    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
219    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
220    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
221    let dx = ex - point.pos.x; let dy = ey - point.pos.y;
222    [(sqrt(dx * dx + dy * dy) - distancearcendp.distance) * sketch.constraint_isigma]
223}))]
224pub struct DistanceArcEndP {
225    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
226    #[arael(ref = root.points)] pub point: Ref<Point>,
227    pub distance: f64,
228    #[serde(default)]
229    pub nid: u32,
230    #[arael(constraint_index)]
231    #[serde(skip)]
232    pub cid: u32,
233    #[serde(skip)] pub hb: CrossBlock<Arc, Point>,
234}
235
236// -- Arc-Line distance --
237
238#[derive(serde::Serialize, serde::Deserialize)]
239#[arael::model]
240#[arael(constraint(hb, {
241    let dx = arc.center.x - line.p1.x; let dy = arc.center.y - line.p1.y;
242    [(sqrt(dx * dx + dy * dy) - distancearccenterl1.distance) * sketch.constraint_isigma]
243}))]
244pub struct DistanceArcCenterL1 {
245    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
246    #[arael(ref = root.lines)] pub line: Ref<Line>,
247    pub distance: f64,
248    #[serde(default)]
249    pub nid: u32,
250    #[arael(constraint_index)]
251    #[serde(skip)]
252    pub cid: u32,
253    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
254}
255
256#[derive(serde::Serialize, serde::Deserialize)]
257#[arael::model]
258#[arael(constraint(hb, {
259    let dx = arc.center.x - line.p2.x; let dy = arc.center.y - line.p2.y;
260    [(sqrt(dx * dx + dy * dy) - distancearccenterl2.distance) * sketch.constraint_isigma]
261}))]
262pub struct DistanceArcCenterL2 {
263    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
264    #[arael(ref = root.lines)] pub line: Ref<Line>,
265    pub distance: f64,
266    #[serde(default)]
267    pub nid: u32,
268    #[arael(constraint_index)]
269    #[serde(skip)]
270    pub cid: u32,
271    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
272}
273
274#[derive(serde::Serialize, serde::Deserialize)]
275#[arael::model]
276#[arael(constraint(hb, {
277    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
278    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
279    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
280    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
281    let dx = sx - line.p1.x; let dy = sy - line.p1.y;
282    [(sqrt(dx * dx + dy * dy) - distancearcstartl1.distance) * sketch.constraint_isigma]
283}))]
284pub struct DistanceArcStartL1 {
285    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
286    #[arael(ref = root.lines)] pub line: Ref<Line>,
287    pub distance: f64,
288    #[serde(default)]
289    pub nid: u32,
290    #[arael(constraint_index)]
291    #[serde(skip)]
292    pub cid: u32,
293    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
294}
295
296#[derive(serde::Serialize, serde::Deserialize)]
297#[arael::model]
298#[arael(constraint(hb, {
299    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
300    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
301    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
302    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
303    let dx = sx - line.p2.x; let dy = sy - line.p2.y;
304    [(sqrt(dx * dx + dy * dy) - distancearcstartl2.distance) * sketch.constraint_isigma]
305}))]
306pub struct DistanceArcStartL2 {
307    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
308    #[arael(ref = root.lines)] pub line: Ref<Line>,
309    pub distance: f64,
310    #[serde(default)]
311    pub nid: u32,
312    #[arael(constraint_index)]
313    #[serde(skip)]
314    pub cid: u32,
315    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
316}
317
318#[derive(serde::Serialize, serde::Deserialize)]
319#[arael::model]
320#[arael(constraint(hb, {
321    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
322    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
323    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
324    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
325    let dx = ex - line.p1.x; let dy = ey - line.p1.y;
326    [(sqrt(dx * dx + dy * dy) - distancearcendl1.distance) * sketch.constraint_isigma]
327}))]
328pub struct DistanceArcEndL1 {
329    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
330    #[arael(ref = root.lines)] pub line: Ref<Line>,
331    pub distance: f64,
332    #[serde(default)]
333    pub nid: u32,
334    #[arael(constraint_index)]
335    #[serde(skip)]
336    pub cid: u32,
337    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
338}
339
340#[derive(serde::Serialize, serde::Deserialize)]
341#[arael::model]
342#[arael(constraint(hb, {
343    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
344    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
345    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
346    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
347    let dx = ex - line.p2.x; let dy = ey - line.p2.y;
348    [(sqrt(dx * dx + dy * dy) - distancearcendl2.distance) * sketch.constraint_isigma]
349}))]
350pub struct DistanceArcEndL2 {
351    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
352    #[arael(ref = root.lines)] pub line: Ref<Line>,
353    pub distance: f64,
354    #[serde(default)]
355    pub nid: u32,
356    #[arael(constraint_index)]
357    #[serde(skip)]
358    pub cid: u32,
359    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
360}
361
362// -- Arc-Arc distance --
363
364#[derive(serde::Serialize, serde::Deserialize)]
365#[arael::model]
366#[arael(constraint(hb, {
367    let dx = a.center.x - b.center.x; let dy = a.center.y - b.center.y;
368    [(sqrt(dx * dx + dy * dy) - distanceaacece.distance) * sketch.constraint_isigma]
369}))]
370pub struct DistanceAACeCe {
371    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
372    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
373    pub distance: f64,
374    #[serde(default)]
375    pub nid: u32,
376    #[arael(constraint_index)]
377    #[serde(skip)]
378    pub cid: u32,
379    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
380}
381
382#[derive(serde::Serialize, serde::Deserialize)]
383#[arael::model]
384#[arael(constraint(hb, {
385    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
386    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
387    let bsx = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
388    let bsy = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
389    let dx = a.center.x - bsx; let dy = a.center.y - bsy;
390    [(sqrt(dx * dx + dy * dy) - distanceaaces.distance) * sketch.constraint_isigma]
391}))]
392pub struct DistanceAACeS {
393    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
394    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
395    pub distance: f64,
396    #[serde(default)]
397    pub nid: u32,
398    #[arael(constraint_index)]
399    #[serde(skip)]
400    pub cid: u32,
401    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
402}
403
404#[derive(serde::Serialize, serde::Deserialize)]
405#[arael::model]
406#[arael(constraint(hb, {
407    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
408    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
409    let bex = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
410    let bey = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
411    let dx = a.center.x - bex; let dy = a.center.y - bey;
412    [(sqrt(dx * dx + dy * dy) - distanceaacee.distance) * sketch.constraint_isigma]
413}))]
414pub struct DistanceAACeE {
415    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
416    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
417    pub distance: f64,
418    #[serde(default)]
419    pub nid: u32,
420    #[arael(constraint_index)]
421    #[serde(skip)]
422    pub cid: u32,
423    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
424}
425
426#[derive(serde::Serialize, serde::Deserialize)]
427#[arael::model]
428#[arael(constraint(hb, {
429    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
430    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
431    let asx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
432    let asy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
433    let dx = asx - b.center.x; let dy = asy - b.center.y;
434    [(sqrt(dx * dx + dy * dy) - distanceaasce.distance) * sketch.constraint_isigma]
435}))]
436pub struct DistanceAASCe {
437    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
438    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
439    pub distance: f64,
440    #[serde(default)]
441    pub nid: u32,
442    #[arael(constraint_index)]
443    #[serde(skip)]
444    pub cid: u32,
445    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
446}
447
448#[derive(serde::Serialize, serde::Deserialize)]
449#[arael::model]
450#[arael(constraint(hb, {
451    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
452    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
453    let asx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
454    let asy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
455    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
456    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
457    let bsx = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
458    let bsy = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
459    let dx = asx - bsx; let dy = asy - bsy;
460    [(sqrt(dx * dx + dy * dy) - distanceaass.distance) * sketch.constraint_isigma]
461}))]
462pub struct DistanceAASS {
463    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
464    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
465    pub distance: f64,
466    #[serde(default)]
467    pub nid: u32,
468    #[arael(constraint_index)]
469    #[serde(skip)]
470    pub cid: u32,
471    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
472}
473
474#[derive(serde::Serialize, serde::Deserialize)]
475#[arael::model]
476#[arael(constraint(hb, {
477    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
478    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
479    let asx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
480    let asy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
481    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
482    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
483    let bex = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
484    let bey = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
485    let dx = asx - bex; let dy = asy - bey;
486    [(sqrt(dx * dx + dy * dy) - distanceaase.distance) * sketch.constraint_isigma]
487}))]
488pub struct DistanceAASE {
489    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
490    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
491    pub distance: f64,
492    #[serde(default)]
493    pub nid: u32,
494    #[arael(constraint_index)]
495    #[serde(skip)]
496    pub cid: u32,
497    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
498}
499
500#[derive(serde::Serialize, serde::Deserialize)]
501#[arael::model]
502#[arael(constraint(hb, {
503    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
504    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
505    let aex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
506    let aey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
507    let dx = aex - b.center.x; let dy = aey - b.center.y;
508    [(sqrt(dx * dx + dy * dy) - distanceaaece.distance) * sketch.constraint_isigma]
509}))]
510pub struct DistanceAAECe {
511    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
512    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
513    pub distance: f64,
514    #[serde(default)]
515    pub nid: u32,
516    #[arael(constraint_index)]
517    #[serde(skip)]
518    pub cid: u32,
519    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
520}
521
522#[derive(serde::Serialize, serde::Deserialize)]
523#[arael::model]
524#[arael(constraint(hb, {
525    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
526    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
527    let aex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
528    let aey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
529    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
530    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
531    let bsx = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
532    let bsy = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
533    let dx = aex - bsx; let dy = aey - bsy;
534    [(sqrt(dx * dx + dy * dy) - distanceaaes.distance) * sketch.constraint_isigma]
535}))]
536pub struct DistanceAAES {
537    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
538    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
539    pub distance: f64,
540    #[serde(default)]
541    pub nid: u32,
542    #[arael(constraint_index)]
543    #[serde(skip)]
544    pub cid: u32,
545    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
546}
547
548#[derive(serde::Serialize, serde::Deserialize)]
549#[arael::model]
550#[arael(constraint(hb, {
551    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
552    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
553    let aex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
554    let aey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
555    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
556    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
557    let bex = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
558    let bey = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
559    let dx = aex - bex; let dy = aey - bey;
560    [(sqrt(dx * dx + dy * dy) - distanceaaee.distance) * sketch.constraint_isigma]
561}))]
562pub struct DistanceAAEE {
563    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
564    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
565    pub distance: f64,
566    #[serde(default)]
567    pub nid: u32,
568    #[arael(constraint_index)]
569    #[serde(skip)]
570    pub cid: u32,
571    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
572}
573
574#[derive(serde::Serialize, serde::Deserialize)]
575#[arael::model]
576#[arael(constraint(hb, {
577    [(a.pos.x - b.pos.x - horizontaldistancepp.distance) * sketch.constraint_isigma]
578}))]
579pub struct HorizontalDistancePP {
580    #[arael(ref = root.points)]
581    pub a: Ref<Point>,
582    #[arael(ref = root.points)]
583    pub b: Ref<Point>,
584    pub distance: f64,
585    #[serde(default)]
586    pub nid: u32,
587    #[arael(constraint_index)]
588    #[serde(skip)]
589    pub cid: u32,
590    #[serde(skip)]
591    pub hb: CrossBlock<Point, Point>,
592}
593
594#[derive(serde::Serialize, serde::Deserialize)]
595#[arael::model]
596#[arael(constraint(hb, {
597    [(a.pos.y - b.pos.y - verticaldistancepp.distance) * sketch.constraint_isigma]
598}))]
599pub struct VerticalDistancePP {
600    #[arael(ref = root.points)]
601    pub a: Ref<Point>,
602    #[arael(ref = root.points)]
603    pub b: Ref<Point>,
604    pub distance: f64,
605    #[serde(default)]
606    pub nid: u32,
607    #[arael(constraint_index)]
608    #[serde(skip)]
609    pub cid: u32,
610    #[serde(skip)]
611    pub hb: CrossBlock<Point, Point>,
612}
613
614// -- Point-Line --
615
616// Point lies on infinite line through p1-p2 (cross product = 0)
617#[derive(serde::Serialize, serde::Deserialize)]
618#[arael::model]
619#[arael(constraint(hb, {
620    let dx = line.p2.x - line.p1.x;
621    let dy = line.p2.y - line.p1.y;
622    let len = sqrt(dx * dx + dy * dy);
623    [((point.pos.x - line.p1.x) * dy - (point.pos.y - line.p1.y) * dx) / len
624     * sketch.constraint_isigma]
625}))]
626pub struct PointOnLine {
627    #[arael(ref = root.points)]
628    pub point: Ref<Point>,
629    #[arael(ref = root.lines)]
630    pub line: Ref<Line>,
631    #[serde(default)]
632    pub nid: u32,
633    #[arael(constraint_index)]
634    #[serde(skip)]
635    pub cid: u32,
636    #[serde(skip)]
637    pub hb: CrossBlock<Point, Line>,
638}
639
640// Point at midpoint of line segment
641#[derive(serde::Serialize, serde::Deserialize)]
642#[arael::model]
643#[arael(constraint(hb, {
644    let mx = (line.p1.x + line.p2.x) * 0.5;
645    let my = (line.p1.y + line.p2.y) * 0.5;
646    [(point.pos.x - mx) * sketch.constraint_isigma,
647     (point.pos.y - my) * sketch.constraint_isigma]
648}))]
649pub struct MidpointConstraint {
650    #[arael(ref = root.points)]
651    pub point: Ref<Point>,
652    #[arael(ref = root.lines)]
653    pub line: Ref<Line>,
654    #[serde(default)]
655    pub nid: u32,
656    #[arael(constraint_index)]
657    #[serde(skip)]
658    pub cid: u32,
659    #[serde(skip)]
660    pub hb: CrossBlock<Point, Line>,
661}
662
663// Line P1 at midpoint of another line
664#[derive(serde::Serialize, serde::Deserialize)]
665#[arael::model]
666#[arael(constraint(hb, {
667    let mx = (target.p1.x + target.p2.x) * 0.5;
668    let my = (target.p1.y + target.p2.y) * 0.5;
669    [(line.p1.x - mx) * sketch.constraint_isigma,
670     (line.p1.y - my) * sketch.constraint_isigma]
671}))]
672pub struct MidpointLP1 {
673    #[arael(ref = root.lines)]
674    pub line: Ref<Line>,
675    #[arael(ref = root.lines)]
676    pub target: Ref<Line>,
677    #[serde(default)]
678    pub nid: u32,
679    #[arael(constraint_index)]
680    #[serde(skip)]
681    pub cid: u32,
682    #[serde(skip)]
683    pub hb: CrossBlock<Line, Line>,
684}
685
686// Line P2 at midpoint of another line
687#[derive(serde::Serialize, serde::Deserialize)]
688#[arael::model]
689#[arael(constraint(hb, {
690    let mx = (target.p1.x + target.p2.x) * 0.5;
691    let my = (target.p1.y + target.p2.y) * 0.5;
692    [(line.p2.x - mx) * sketch.constraint_isigma,
693     (line.p2.y - my) * sketch.constraint_isigma]
694}))]
695pub struct MidpointLP2 {
696    #[arael(ref = root.lines)]
697    pub line: Ref<Line>,
698    #[arael(ref = root.lines)]
699    pub target: Ref<Line>,
700    #[serde(default)]
701    pub nid: u32,
702    #[arael(constraint_index)]
703    #[serde(skip)]
704    pub cid: u32,
705    #[serde(skip)]
706    pub hb: CrossBlock<Line, Line>,
707}
708
709// Arc start point at midpoint of line
710#[derive(serde::Serialize, serde::Deserialize)]
711#[arael::model]
712#[arael(constraint(hb, {
713    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
714    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
715    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
716    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
717    let mx = (line.p1.x + line.p2.x) * 0.5;
718    let my = (line.p1.y + line.p2.y) * 0.5;
719    [(sx - mx) * sketch.constraint_isigma,
720     (sy - my) * sketch.constraint_isigma]
721}))]
722pub struct MidpointArcStart {
723    #[arael(ref = root.arcs)]
724    pub arc: Ref<Arc>,
725    #[arael(ref = root.lines)]
726    pub line: Ref<Line>,
727    #[serde(default)]
728    pub nid: u32,
729    #[arael(constraint_index)]
730    #[serde(skip)]
731    pub cid: u32,
732    #[serde(skip)]
733    pub hb: CrossBlock<Arc, Line>,
734}
735
736// Arc end point at midpoint of line
737#[derive(serde::Serialize, serde::Deserialize)]
738#[arael::model]
739#[arael(constraint(hb, {
740    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
741    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
742    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
743    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
744    let mx = (line.p1.x + line.p2.x) * 0.5;
745    let my = (line.p1.y + line.p2.y) * 0.5;
746    [(ex - mx) * sketch.constraint_isigma,
747     (ey - my) * sketch.constraint_isigma]
748}))]
749pub struct MidpointArcEnd {
750    #[arael(ref = root.arcs)]
751    pub arc: Ref<Arc>,
752    #[arael(ref = root.lines)]
753    pub line: Ref<Line>,
754    #[serde(default)]
755    pub nid: u32,
756    #[arael(constraint_index)]
757    #[serde(skip)]
758    pub cid: u32,
759    #[serde(skip)]
760    pub hb: CrossBlock<Arc, Line>,
761}
762
763// -- Midpoint on Arc (angular midpoint) --
764
765// Point at angular midpoint of arc
766#[derive(serde::Serialize, serde::Deserialize)]
767#[arael::model]
768#[arael(constraint(hb, {
769    let mid_angle = (arc.start_angle + arc.end_angle) * 0.5;
770    let ctm = cos(mid_angle); let stm = sin(mid_angle);
771    let crm = cos(arc.rotation); let srm = sin(arc.rotation);
772    let mx = arc.center.x + arc.radius * ctm * crm - arc.radius_b * stm * srm;
773    let my = arc.center.y + arc.radius * ctm * srm + arc.radius_b * stm * crm;
774    [(point.pos.x - mx) * sketch.constraint_isigma,
775     (point.pos.y - my) * sketch.constraint_isigma]
776}))]
777pub struct MidpointArcPoint {
778    #[arael(ref = root.points)]
779    pub point: Ref<Point>,
780    #[arael(ref = root.arcs)]
781    pub arc: Ref<Arc>,
782    #[serde(default)]
783    pub nid: u32,
784    #[arael(constraint_index)]
785    #[serde(skip)]
786    pub cid: u32,
787    #[serde(skip)]
788    pub hb: CrossBlock<Point, Arc>,
789}
790
791// Line P1 at angular midpoint of arc
792#[derive(serde::Serialize, serde::Deserialize)]
793#[arael::model]
794#[arael(constraint(hb, {
795    let mid_angle = (arc.start_angle + arc.end_angle) * 0.5;
796    let ctm = cos(mid_angle); let stm = sin(mid_angle);
797    let crm = cos(arc.rotation); let srm = sin(arc.rotation);
798    let mx = arc.center.x + arc.radius * ctm * crm - arc.radius_b * stm * srm;
799    let my = arc.center.y + arc.radius * ctm * srm + arc.radius_b * stm * crm;
800    [(line.p1.x - mx) * sketch.constraint_isigma,
801     (line.p1.y - my) * sketch.constraint_isigma]
802}))]
803pub struct MidpointLP1Arc {
804    #[arael(ref = root.lines)]
805    pub line: Ref<Line>,
806    #[arael(ref = root.arcs)]
807    pub arc: Ref<Arc>,
808    #[serde(default)]
809    pub nid: u32,
810    #[arael(constraint_index)]
811    #[serde(skip)]
812    pub cid: u32,
813    #[serde(skip)]
814    pub hb: CrossBlock<Line, Arc>,
815}
816
817// Line P2 at angular midpoint of arc
818#[derive(serde::Serialize, serde::Deserialize)]
819#[arael::model]
820#[arael(constraint(hb, {
821    let mid_angle = (arc.start_angle + arc.end_angle) * 0.5;
822    let ctm = cos(mid_angle); let stm = sin(mid_angle);
823    let crm = cos(arc.rotation); let srm = sin(arc.rotation);
824    let mx = arc.center.x + arc.radius * ctm * crm - arc.radius_b * stm * srm;
825    let my = arc.center.y + arc.radius * ctm * srm + arc.radius_b * stm * crm;
826    [(line.p2.x - mx) * sketch.constraint_isigma,
827     (line.p2.y - my) * sketch.constraint_isigma]
828}))]
829pub struct MidpointLP2Arc {
830    #[arael(ref = root.lines)]
831    pub line: Ref<Line>,
832    #[arael(ref = root.arcs)]
833    pub arc: Ref<Arc>,
834    #[serde(default)]
835    pub nid: u32,
836    #[arael(constraint_index)]
837    #[serde(skip)]
838    pub cid: u32,
839    #[serde(skip)]
840    pub hb: CrossBlock<Line, Arc>,
841}
842
843// Arc start at angular midpoint of another arc
844#[derive(serde::Serialize, serde::Deserialize)]
845#[arael::model]
846#[arael(constraint(hb, {
847    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
848    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
849    let sx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
850    let sy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
851    let mid_angle = (b.start_angle + b.end_angle) * 0.5;
852    let ctm_b = cos(mid_angle); let stm_b = sin(mid_angle);
853    let crm_b = cos(b.rotation); let srm_b = sin(b.rotation);
854    let mx = b.center.x + b.radius * ctm_b * crm_b - b.radius_b * stm_b * srm_b;
855    let my = b.center.y + b.radius * ctm_b * srm_b + b.radius_b * stm_b * crm_b;
856    [(sx - mx) * sketch.constraint_isigma,
857     (sy - my) * sketch.constraint_isigma]
858}))]
859pub struct MidpointArcStartArc {
860    #[arael(ref = root.arcs)]
861    pub a: Ref<Arc>,
862    #[arael(ref = root.arcs)]
863    pub b: Ref<Arc>,
864    #[serde(default)]
865    pub nid: u32,
866    #[arael(constraint_index)]
867    #[serde(skip)]
868    pub cid: u32,
869    #[serde(skip)]
870    pub hb: CrossBlock<Arc, Arc>,
871}
872
873// Arc end at angular midpoint of another arc
874#[derive(serde::Serialize, serde::Deserialize)]
875#[arael::model]
876#[arael(constraint(hb, {
877    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
878    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
879    let ex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
880    let ey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
881    let mid_angle = (b.start_angle + b.end_angle) * 0.5;
882    let ctm_b = cos(mid_angle); let stm_b = sin(mid_angle);
883    let crm_b = cos(b.rotation); let srm_b = sin(b.rotation);
884    let mx = b.center.x + b.radius * ctm_b * crm_b - b.radius_b * stm_b * srm_b;
885    let my = b.center.y + b.radius * ctm_b * srm_b + b.radius_b * stm_b * crm_b;
886    [(ex - mx) * sketch.constraint_isigma,
887     (ey - my) * sketch.constraint_isigma]
888}))]
889pub struct MidpointArcEndArc {
890    #[arael(ref = root.arcs)]
891    pub a: Ref<Arc>,
892    #[arael(ref = root.arcs)]
893    pub b: Ref<Arc>,
894    #[serde(default)]
895    pub nid: u32,
896    #[arael(constraint_index)]
897    #[serde(skip)]
898    pub cid: u32,
899    #[serde(skip)]
900    pub hb: CrossBlock<Arc, Arc>,
901}
902
903// -- Point-Arc --
904
905// Point lies on ellipse/circle defined by arc
906#[derive(serde::Serialize, serde::Deserialize)]
907#[arael::model]
908#[arael(constraint(hb, {
909    let dx = point.pos.x - arc.center.x;
910    let dy = point.pos.y - arc.center.y;
911    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
912    let u = dx * cr + dy * sr;
913    let v = 0.0 - dx * sr + dy * cr;
914    [(u * u / (arc.radius * arc.radius) + v * v / (arc.radius_b * arc.radius_b) - 1.0) * sketch.constraint_isigma]
915}))]
916pub struct PointOnArc {
917    #[arael(ref = root.points)]
918    pub point: Ref<Point>,
919    #[arael(ref = root.arcs)]
920    pub arc: Ref<Arc>,
921    #[serde(default)]
922    pub nid: u32,
923    #[arael(constraint_index)]
924    #[serde(skip)]
925    pub cid: u32,
926    #[serde(skip)]
927    pub hb: CrossBlock<Point, Arc>,
928}
929
930// Point coincides with arc center
931#[derive(serde::Serialize, serde::Deserialize)]
932#[arael::model]
933#[arael(constraint(hb, {
934    [(point.pos.x - arc.center.x) * sketch.constraint_isigma,
935     (point.pos.y - arc.center.y) * sketch.constraint_isigma]
936}))]
937pub struct CoincidentArcCenter {
938    #[arael(ref = root.points)]
939    pub point: Ref<Point>,
940    #[arael(ref = root.arcs)]
941    pub arc: Ref<Arc>,
942    #[serde(default)]
943    pub nid: u32,
944    #[arael(constraint_index)]
945    #[serde(skip)]
946    pub cid: u32,
947    #[serde(skip)]
948    pub hb: CrossBlock<Point, Arc>,
949}
950
951// Point coincides with arc start endpoint (center + radius * [cos(sa), sin(sa)])
952#[derive(serde::Serialize, serde::Deserialize)]
953#[arael::model]
954#[arael(constraint(hb, {
955    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
956    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
957    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
958    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
959    [(point.pos.x - sx) * sketch.constraint_isigma,
960     (point.pos.y - sy) * sketch.constraint_isigma]
961}))]
962pub struct CoincidentArcStart {
963    #[arael(ref = root.points)]
964    pub point: Ref<Point>,
965    #[arael(ref = root.arcs)]
966    pub arc: Ref<Arc>,
967    #[serde(default)]
968    pub nid: u32,
969    #[arael(constraint_index)]
970    #[serde(skip)]
971    pub cid: u32,
972    #[serde(skip)]
973    pub hb: CrossBlock<Point, Arc>,
974}
975
976// Point coincides with arc end endpoint (center + radius * [cos(ea), sin(ea)])
977#[derive(serde::Serialize, serde::Deserialize)]
978#[arael::model]
979#[arael(constraint(hb, {
980    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
981    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
982    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
983    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
984    [(point.pos.x - ex) * sketch.constraint_isigma,
985     (point.pos.y - ey) * sketch.constraint_isigma]
986}))]
987pub struct CoincidentArcEnd {
988    #[arael(ref = root.points)]
989    pub point: Ref<Point>,
990    #[arael(ref = root.arcs)]
991    pub arc: Ref<Arc>,
992    #[serde(default)]
993    pub nid: u32,
994    #[arael(constraint_index)]
995    #[serde(skip)]
996    pub cid: u32,
997    #[serde(skip)]
998    pub hb: CrossBlock<Point, Arc>,
999}
1000
1001// -- Line-Line --
1002
1003// Parallel: cross product of direction vectors = 0
1004#[derive(serde::Serialize, serde::Deserialize)]
1005#[arael::model]
1006#[arael(constraint(hb, {
1007    let dx1 = a.p2.x - a.p1.x;
1008    let dy1 = a.p2.y - a.p1.y;
1009    let dx2 = b.p2.x - b.p1.x;
1010    let dy2 = b.p2.y - b.p1.y;
1011    let len1 = sqrt(dx1 * dx1 + dy1 * dy1);
1012    let len2 = sqrt(dx2 * dx2 + dy2 * dy2);
1013    let mlen = (len1 + len2) / 2.0;
1014    [(dx1 * dy2 - dy1 * dx2) / mlen * sketch.constraint_isigma]
1015}))]
1016pub struct Parallel {
1017    #[arael(ref = root.lines)]
1018    pub a: Ref<Line>,
1019    #[arael(ref = root.lines)]
1020    pub b: Ref<Line>,
1021    #[serde(default)]
1022    pub nid: u32,
1023    #[arael(constraint_index)]
1024    #[serde(skip)]
1025    pub cid: u32,
1026    #[serde(skip)]
1027    pub hb: CrossBlock<Line, Line>,
1028}
1029
1030// Perpendicular: dot product of direction vectors = 0, with direction enforcement.
1031// The Heaviside on the unnormalized cross product prevents direction reversal.
1032// Its gradient at zero line length is the other line's direction -- always well-defined.
1033#[derive(serde::Serialize, serde::Deserialize)]
1034#[arael::model]
1035#[arael(constraint(hb, {
1036    let dx1 = a.p2.x - a.p1.x;
1037    let dy1 = a.p2.y - a.p1.y;
1038    let dx2 = b.p2.x - b.p1.x;
1039    let dy2 = b.p2.y - b.p1.y;
1040    let len1 = sqrt(dx1 * dx1 + dy1 * dy1);
1041    let len2 = sqrt(dx2 * dx2 + dy2 * dy2);
1042    let mlen = (len1 + len2) / 2.0;
1043    let cross = dx1 * dy2 - dy1 * dx2;
1044    let d = sketch.min_length - perpendicular.dir_sign * cross;
1045    [
1046        (dx1 * dx2 + dy1 * dy2) / mlen * sketch.constraint_isigma,
1047        heaviside(d) * d * sketch.constraint_isigma
1048    ]
1049}))]
1050pub struct Perpendicular {
1051    #[arael(ref = root.lines)]
1052    pub a: Ref<Line>,
1053    #[arael(ref = root.lines)]
1054    pub b: Ref<Line>,
1055    #[serde(default = "default_dir_sign")]
1056    pub dir_sign: f64,
1057    #[serde(default)]
1058    pub nid: u32,
1059    #[arael(constraint_index)]
1060    #[serde(skip)]
1061    pub cid: u32,
1062    #[serde(skip)]
1063    pub hb: CrossBlock<Line, Line>,
1064}
1065
1066// Arc-Line parallel: ellipse's major-axis direction parallel to a
1067// line's direction. Residual is the unnormalised 2D cross product of
1068// the line direction `(dx, dy)` with the ellipse's major-axis unit
1069// vector `(cos(rotation), sin(rotation))`, which equals
1070// `|line| * sin(angle_between)`. Zero iff axes are parallel or
1071// antiparallel -- natural pi-periodicity means the solver does not
1072// fight the ellipse's inherent two-fold rotational symmetry.
1073//
1074// We deliberately do NOT divide by the line length. The old form
1075// `... / len * isigma` normalised the residual to `sin(angle)`, but
1076// that made the Jacobian wrt positions scale as `isigma / len`, so
1077// on a long line the SVD's singular value from this row shrank like
1078// `1 / len` -- producing tiny sigmas at large sketch scales and
1079// breaking the rank algorithm's gap detection. The unnormalised
1080// form gives Jacobian wrt positions ~= `isigma` (scale-invariant)
1081// and Jacobian wrt rotation ~= `len * isigma` (scale-linear, same
1082// family as coincidence-to-arc-endpoint derivatives wrt angles).
1083// The SV from this row is now in the same order as the other
1084// angle-mode sigmas, not collapsing to zero at large scales.
1085//
1086// Guarded on arc.is_ellipse; circular arcs have rotation fixed at 0
1087// and the constraint would be meaningless.
1088#[derive(serde::Serialize, serde::Deserialize)]
1089#[arael::model]
1090#[arael(constraint(hb, guard = arc.is_ellipse, {
1091    let dx = line.p2.x - line.p1.x;
1092    let dy = line.p2.y - line.p1.y;
1093    [(cos(arc.rotation) * dy - sin(arc.rotation) * dx) * sketch.constraint_isigma]
1094}))]
1095pub struct ArcLineParallel {
1096    #[arael(ref = root.arcs)]
1097    pub arc: Ref<Arc>,
1098    #[arael(ref = root.lines)]
1099    pub line: Ref<Line>,
1100    #[serde(default)]
1101    pub nid: u32,
1102    #[arael(constraint_index)]
1103    #[serde(skip)]
1104    pub cid: u32,
1105    #[serde(skip)]
1106    pub hb: CrossBlock<Arc, Line>,
1107}
1108
1109// Arc-Arc parallel: two ellipses share the same major-axis direction.
1110// Residual is sin(a.rotation - b.rotation) -- zero when rotations
1111// match modulo pi, which matches the ellipse's inherent two-fold
1112// rotational symmetry. Guarded on both is_ellipse; inert for
1113// circular-arc operands (whose rotations are fixed at 0 anyway).
1114#[derive(serde::Serialize, serde::Deserialize)]
1115#[arael::model]
1116#[arael(constraint(hb, guard = a.is_ellipse && b.is_ellipse, {
1117    [sin(a.rotation - b.rotation) * sketch.constraint_isigma]
1118}))]
1119pub struct ArcArcParallel {
1120    #[arael(ref = root.arcs)]
1121    pub a: Ref<Arc>,
1122    #[arael(ref = root.arcs)]
1123    pub b: Ref<Arc>,
1124    #[serde(default)]
1125    pub nid: u32,
1126    #[arael(constraint_index)]
1127    #[serde(skip)]
1128    pub cid: u32,
1129    #[serde(skip)]
1130    pub hb: CrossBlock<Arc, Arc>,
1131}
1132
1133// Collinear: line2 endpoints both lie on infinite line of line1
1134#[derive(serde::Serialize, serde::Deserialize)]
1135#[arael::model]
1136#[arael(constraint(hb, {
1137    let dx = a.p2.x - a.p1.x;
1138    let dy = a.p2.y - a.p1.y;
1139    let len = sqrt(dx * dx + dy * dy);
1140    let cross1 = ((b.p1.x - a.p1.x) * dy - (b.p1.y - a.p1.y) * dx) / len;
1141    let cross2 = ((b.p2.x - a.p1.x) * dy - (b.p2.y - a.p1.y) * dx) / len;
1142    [cross1 * sketch.constraint_isigma, cross2 * sketch.constraint_isigma]
1143}))]
1144pub struct Collinear {
1145    #[arael(ref = root.lines)]
1146    pub a: Ref<Line>,
1147    #[arael(ref = root.lines)]
1148    pub b: Ref<Line>,
1149    #[serde(default)]
1150    pub nid: u32,
1151    #[arael(constraint_index)]
1152    #[serde(skip)]
1153    pub cid: u32,
1154    #[serde(skip)]
1155    pub hb: CrossBlock<Line, Line>,
1156}
1157
1158// Equal length
1159#[derive(serde::Serialize, serde::Deserialize)]
1160#[arael::model]
1161#[arael(constraint(hb, {
1162    let dx1 = a.p2.x - a.p1.x;
1163    let dy1 = a.p2.y - a.p1.y;
1164    let dx2 = b.p2.x - b.p1.x;
1165    let dy2 = b.p2.y - b.p1.y;
1166    [(sqrt(dx1*dx1 + dy1*dy1) - sqrt(dx2*dx2 + dy2*dy2)) * sketch.constraint_isigma]
1167}))]
1168pub struct EqualLength {
1169    #[arael(ref = root.lines)]
1170    pub a: Ref<Line>,
1171    #[arael(ref = root.lines)]
1172    pub b: Ref<Line>,
1173    #[serde(default)]
1174    pub nid: u32,
1175    #[arael(constraint_index)]
1176    #[serde(skip)]
1177    pub cid: u32,
1178    #[serde(skip)]
1179    pub hb: CrossBlock<Line, Line>,
1180}
1181
1182// Angle between lines
1183#[derive(serde::Serialize, serde::Deserialize)]
1184#[arael::model]
1185#[arael(constraint(hb, {
1186    let dx1 = a.p2.x - a.p1.x;
1187    let dy1 = a.p2.y - a.p1.y;
1188    let dx2 = b.p2.x - b.p1.x;
1189    let dy2 = b.p2.y - b.p1.y;
1190    [(atan2(dx1 * dy2 - dy1 * dx2, dx1 * dx2 + dy1 * dy2) - angleconstraint.angle)
1191     * sketch.constraint_isigma]
1192}))]
1193pub struct AngleConstraint {
1194    #[arael(ref = root.lines)]
1195    pub a: Ref<Line>,
1196    #[arael(ref = root.lines)]
1197    pub b: Ref<Line>,
1198    pub angle: f64,  // target angle in radians
1199    #[serde(default)]
1200    pub nid: u32,
1201    #[arael(constraint_index)]
1202    #[serde(skip)]
1203    pub cid: u32,
1204    #[serde(skip)]
1205    pub hb: CrossBlock<Line, Line>,
1206}
1207
1208// -- Line-Point (endpoint coincidence) --
1209
1210// Line p1 coincides with standalone point
1211#[derive(serde::Serialize, serde::Deserialize)]
1212#[arael::model]
1213#[arael(constraint(hb, {
1214    [(line.p1.x - point.pos.x) * sketch.constraint_isigma,
1215     (line.p1.y - point.pos.y) * sketch.constraint_isigma]
1216}))]
1217pub struct CoincidentLP1 {
1218    #[arael(ref = root.lines)]
1219    pub line: Ref<Line>,
1220    #[arael(ref = root.points)]
1221    pub point: Ref<Point>,
1222    #[serde(default)]
1223    pub nid: u32,
1224    #[arael(constraint_index)]
1225    #[serde(skip)]
1226    pub cid: u32,
1227    #[serde(skip)]
1228    pub hb: CrossBlock<Line, Point>,
1229}
1230
1231// Line p2 coincides with standalone point
1232#[derive(serde::Serialize, serde::Deserialize)]
1233#[arael::model]
1234#[arael(constraint(hb, {
1235    [(line.p2.x - point.pos.x) * sketch.constraint_isigma,
1236     (line.p2.y - point.pos.y) * sketch.constraint_isigma]
1237}))]
1238pub struct CoincidentLP2 {
1239    #[arael(ref = root.lines)]
1240    pub line: Ref<Line>,
1241    #[arael(ref = root.points)]
1242    pub point: Ref<Point>,
1243    #[serde(default)]
1244    pub nid: u32,
1245    #[arael(constraint_index)]
1246    #[serde(skip)]
1247    pub cid: u32,
1248    #[serde(skip)]
1249    pub hb: CrossBlock<Line, Point>,
1250}
1251
1252// -- Line-Line endpoint coincidence (4 variants for endpoint combos) --
1253
1254// a.p1 == b.p1
1255#[derive(serde::Serialize, serde::Deserialize)]
1256#[arael::model]
1257#[arael(constraint(hb, {
1258    [(a.p1.x - b.p1.x) * sketch.constraint_isigma,
1259     (a.p1.y - b.p1.y) * sketch.constraint_isigma]
1260}))]
1261pub struct CoincidentLL11 {
1262    #[arael(ref = root.lines)]
1263    pub a: Ref<Line>,
1264    #[arael(ref = root.lines)]
1265    pub b: Ref<Line>,
1266    #[serde(default)]
1267    pub nid: u32,
1268    #[arael(constraint_index)]
1269    #[serde(skip)]
1270    pub cid: u32,
1271    #[serde(skip)]
1272    pub hb: CrossBlock<Line, Line>,
1273}
1274
1275// a.p1 == b.p2
1276#[derive(serde::Serialize, serde::Deserialize)]
1277#[arael::model]
1278#[arael(constraint(hb, {
1279    [(a.p1.x - b.p2.x) * sketch.constraint_isigma,
1280     (a.p1.y - b.p2.y) * sketch.constraint_isigma]
1281}))]
1282pub struct CoincidentLL12 {
1283    #[arael(ref = root.lines)]
1284    pub a: Ref<Line>,
1285    #[arael(ref = root.lines)]
1286    pub b: Ref<Line>,
1287    #[serde(default)]
1288    pub nid: u32,
1289    #[arael(constraint_index)]
1290    #[serde(skip)]
1291    pub cid: u32,
1292    #[serde(skip)]
1293    pub hb: CrossBlock<Line, Line>,
1294}
1295
1296// a.p2 == b.p1  (most common: end of line a -> start of line b)
1297#[derive(serde::Serialize, serde::Deserialize)]
1298#[arael::model]
1299#[arael(constraint(hb, {
1300    [(a.p2.x - b.p1.x) * sketch.constraint_isigma,
1301     (a.p2.y - b.p1.y) * sketch.constraint_isigma]
1302}))]
1303pub struct CoincidentLL21 {
1304    #[arael(ref = root.lines)]
1305    pub a: Ref<Line>,
1306    #[arael(ref = root.lines)]
1307    pub b: Ref<Line>,
1308    #[serde(default)]
1309    pub nid: u32,
1310    #[arael(constraint_index)]
1311    #[serde(skip)]
1312    pub cid: u32,
1313    #[serde(skip)]
1314    pub hb: CrossBlock<Line, Line>,
1315}
1316
1317// a.p2 == b.p2
1318#[derive(serde::Serialize, serde::Deserialize)]
1319#[arael::model]
1320#[arael(constraint(hb, {
1321    [(a.p2.x - b.p2.x) * sketch.constraint_isigma,
1322     (a.p2.y - b.p2.y) * sketch.constraint_isigma]
1323}))]
1324pub struct CoincidentLL22 {
1325    #[arael(ref = root.lines)]
1326    pub a: Ref<Line>,
1327    #[arael(ref = root.lines)]
1328    pub b: Ref<Line>,
1329    #[serde(default)]
1330    pub nid: u32,
1331    #[arael(constraint_index)]
1332    #[serde(skip)]
1333    pub cid: u32,
1334    #[serde(skip)]
1335    pub hb: CrossBlock<Line, Line>,
1336}
1337
1338// -- Line-Arc --
1339
1340// Line tangent to arc/ellipse. Uses perpendicular-distance residual (always active)
1341// plus a gradient-based residual when the tangent point is a shared endpoint.
1342// For ellipses, the effective radius along the line normal direction is used.
1343#[derive(serde::Serialize, serde::Deserialize)]
1344#[arael::model]
1345// Perpendicular distance from center to line = sign * effective_radius (no shared endpoint)
1346#[arael(constraint(hb, guard = !self.p1_arc_start && !self.p1_arc_end && !self.p2_arc_start && !self.p2_arc_end, {
1347    let dx = line.p2.x - line.p1.x;
1348    let dy = line.p2.y - line.p1.y;
1349    let len = sqrt(dx * dx + dy * dy);
1350    let dist = ((arc.center.x - line.p1.x) * dy - (arc.center.y - line.p1.y) * dx) / len;
1351    let nx = 0.0 - dy / len;
1352    let ny = dx / len;
1353    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
1354    let nlx = nx * cr + ny * sr;
1355    let nly = 0.0 - nx * sr + ny * cr;
1356    let r_eff = sqrt(nlx * nlx * arc.radius * arc.radius + nly * nly * arc.radius_b * arc.radius_b);
1357    [(dist - tangentla.sign * r_eff) * sketch.constraint_isigma]
1358}))]
1359// Directed tangent at shared endpoint: uses arc parametric endpoint position
1360// instead of the line endpoint for the direction vector, so the constraint
1361// stays well-defined even for zero-length lines.  Four variants for which
1362// line endpoint (p1/p2) meets which arc endpoint (start/end).
1363#[arael(constraint(hb, guard = self.p1_arc_start, {
1364    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
1365    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
1366    let ax = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
1367    let ay = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
1368    let tx = 0.0 - arc.radius * st * cr - arc.radius_b * ct * sr;
1369    let ty = 0.0 - arc.radius * st * sr + arc.radius_b * ct * cr;
1370    let tlen = sqrt(tx * tx + ty * ty);
1371    let dx = line.p2.x - ax;
1372    let dy = line.p2.y - ay;
1373    let projx = tangentla.dir_sign * (dx * tx + dy * ty) / tlen;
1374    let projy = (0.0 - dx * ty + dy * tx) / tlen;
1375    let d = sketch.min_length - projx;
1376    [
1377        heaviside(d) * d * sketch.constraint_isigma,
1378        projy * sketch.constraint_isigma
1379    ]
1380}))]
1381#[arael(constraint(hb, guard = self.p1_arc_end, {
1382    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
1383    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
1384    let ax = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
1385    let ay = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
1386    let tx = 0.0 - arc.radius * st * cr - arc.radius_b * ct * sr;
1387    let ty = 0.0 - arc.radius * st * sr + arc.radius_b * ct * cr;
1388    let tlen = sqrt(tx * tx + ty * ty);
1389    let dx = line.p2.x - ax;
1390    let dy = line.p2.y - ay;
1391    let projx = tangentla.dir_sign * (dx * tx + dy * ty) / tlen;
1392    let projy = (0.0 - dx * ty + dy * tx) / tlen;
1393    let d = sketch.min_length - projx;
1394    [
1395        heaviside(d) * d * sketch.constraint_isigma,
1396        projy * sketch.constraint_isigma
1397    ]
1398}))]
1399#[arael(constraint(hb, guard = self.p2_arc_start, {
1400    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
1401    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
1402    let ax = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
1403    let ay = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
1404    let tx = 0.0 - arc.radius * st * cr - arc.radius_b * ct * sr;
1405    let ty = 0.0 - arc.radius * st * sr + arc.radius_b * ct * cr;
1406    let tlen = sqrt(tx * tx + ty * ty);
1407    let dx = line.p1.x - ax;
1408    let dy = line.p1.y - ay;
1409    let projx = tangentla.dir_sign * (dx * tx + dy * ty) / tlen;
1410    let projy = (0.0 - dx * ty + dy * tx) / tlen;
1411    let d = sketch.min_length - projx;
1412    [
1413        heaviside(d) * d * sketch.constraint_isigma,
1414        projy * sketch.constraint_isigma
1415    ]
1416}))]
1417#[arael(constraint(hb, guard = self.p2_arc_end, {
1418    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
1419    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
1420    let ax = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
1421    let ay = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
1422    let tx = 0.0 - arc.radius * st * cr - arc.radius_b * ct * sr;
1423    let ty = 0.0 - arc.radius * st * sr + arc.radius_b * ct * cr;
1424    let tlen = sqrt(tx * tx + ty * ty);
1425    let dx = line.p1.x - ax;
1426    let dy = line.p1.y - ay;
1427    let projx = tangentla.dir_sign * (dx * tx + dy * ty) / tlen;
1428    let projy = (0.0 - dx * ty + dy * tx) / tlen;
1429    let d = sketch.min_length - projx;
1430    [
1431        heaviside(d) * d * sketch.constraint_isigma,
1432        projy * sketch.constraint_isigma
1433    ]
1434}))]
1435pub struct TangentLA {
1436    #[arael(ref = root.lines)]
1437    pub line: Ref<Line>,
1438    #[arael(ref = root.arcs)]
1439    pub arc: Ref<Arc>,
1440    #[serde(default = "default_tangent_sign")]
1441    pub sign: f64,
1442    #[serde(skip)]
1443    pub p1_arc_start: bool,
1444    #[serde(skip)]
1445    pub p1_arc_end: bool,
1446    #[serde(skip)]
1447    pub p2_arc_start: bool,
1448    #[serde(skip)]
1449    pub p2_arc_end: bool,
1450    #[serde(skip, default = "default_dir_sign")]
1451    pub dir_sign: f64,
1452    #[serde(default)]
1453    pub nid: u32,
1454    #[arael(constraint_index)]
1455    #[serde(skip)]
1456    pub cid: u32,
1457    #[serde(skip)]
1458    pub hb: CrossBlock<Line, Arc>,
1459}
1460
1461fn default_dir_sign() -> f64 { f64::NAN }
1462
1463fn default_tangent_sign() -> f64 { 1.0 }
1464
1465// -- Arc-Arc --
1466
1467// Concentric: centers coincide
1468#[derive(serde::Serialize, serde::Deserialize)]
1469#[arael::model]
1470#[arael(constraint(hb, {
1471    [(a.center.x - b.center.x) * sketch.constraint_isigma,
1472     (a.center.y - b.center.y) * sketch.constraint_isigma]
1473}))]
1474pub struct Concentric {
1475    #[arael(ref = root.arcs)]
1476    pub a: Ref<Arc>,
1477    #[arael(ref = root.arcs)]
1478    pub b: Ref<Arc>,
1479    #[serde(default)]
1480    pub nid: u32,
1481    #[arael(constraint_index)]
1482    #[serde(skip)]
1483    pub cid: u32,
1484    #[serde(skip)]
1485    pub hb: CrossBlock<Arc, Arc>,
1486}
1487
1488// Radial distance between two concentric arcs/circles. Self-contained:
1489// the residual enforces both center-coincidence (`a.center == b.center`)
1490// and the signed radial gap (`b.radius - a.radius = sign * distance`).
1491// `sign` is captured at dimension creation time so the gap stays
1492// sign-stable under big value updates (no mirror flip on which arc is
1493// outer). Self-containment means the dim survives manual deletion of
1494// the paired `Concentric` constraint -- the circles stay concentric
1495// because the dim is enforcing it directly.
1496#[derive(serde::Serialize, serde::Deserialize)]
1497#[arael::model]
1498#[arael(constraint(hb, name = "concentric_distance", {
1499    [(a.center.x - b.center.x) * sketch.constraint_isigma,
1500     (a.center.y - b.center.y) * sketch.constraint_isigma,
1501     (b.radius - a.radius - distanceconcentric.distance * distanceconcentric.sign)
1502     * sketch.constraint_isigma]
1503}))]
1504pub struct DistanceConcentric {
1505    #[arael(ref = root.arcs)]
1506    pub a: Ref<Arc>,
1507    #[arael(ref = root.arcs)]
1508    pub b: Ref<Arc>,
1509    #[serde(default = "default_tangent_sign")]
1510    pub sign: f64,
1511    pub distance: f64,
1512    #[serde(default)]
1513    pub nid: u32,
1514    #[arael(constraint_index)]
1515    #[serde(skip)]
1516    pub cid: u32,
1517    #[serde(skip)]
1518    pub hb: CrossBlock<Arc, Arc>,
1519}
1520
1521// Equal radius
1522#[derive(serde::Serialize, serde::Deserialize)]
1523#[arael::model]
1524#[arael(constraint(hb, {
1525    [(a.radius - b.radius) * sketch.constraint_isigma]
1526}))]
1527pub struct EqualRadius {
1528    #[arael(ref = root.arcs)]
1529    pub a: Ref<Arc>,
1530    #[arael(ref = root.arcs)]
1531    pub b: Ref<Arc>,
1532    #[serde(default)]
1533    pub nid: u32,
1534    #[arael(constraint_index)]
1535    #[serde(skip)]
1536    pub cid: u32,
1537    #[serde(skip)]
1538    pub hb: CrossBlock<Arc, Arc>,
1539}
1540
1541// Tangent arc-arc (external tangency).
1542// Uses effective radii along center-to-center direction.
1543// Generalizes circles: when rx=ry=r, r_eff = r.
1544#[derive(serde::Serialize, serde::Deserialize)]
1545#[arael::model]
1546// No shared endpoint: center-distance = sum of effective radii
1547#[arael(constraint(hb, guard = self.shared == SharedEndpoint::None, {
1548    let dx = a.center.x - b.center.x;
1549    let dy = a.center.y - b.center.y;
1550    let dist = sqrt(dx * dx + dy * dy);
1551    let nx = dx / dist;
1552    let ny = dy / dist;
1553    let cra = cos(a.rotation);
1554    let sra = sin(a.rotation);
1555    let nxa = nx * cra + ny * sra;
1556    let nya = 0.0 - nx * sra + ny * cra;
1557    let r_eff_a = sqrt(nxa * nxa * a.radius * a.radius + nya * nya * a.radius_b * a.radius_b);
1558    let crb = cos(b.rotation);
1559    let srb = sin(b.rotation);
1560    let nxb = 0.0 - nx * crb - ny * srb;
1561    let nyb = nx * srb - ny * crb;
1562    let r_eff_b = sqrt(nxb * nxb * b.radius * b.radius + nyb * nyb * b.radius_b * b.radius_b);
1563    [(dist - r_eff_a - r_eff_b) * sketch.constraint_isigma]
1564}))]
1565// Shared endpoint a.start = b.start: cross(tangent_a, tangent_b) = 0
1566#[arael(constraint(hb, guard = self.shared == SharedEndpoint::StartStart, {
1567    let tax = 0.0 - a.radius * sin(a.start_angle) * cos(a.rotation) - a.radius_b * cos(a.start_angle) * sin(a.rotation);
1568    let tay = 0.0 - a.radius * sin(a.start_angle) * sin(a.rotation) + a.radius_b * cos(a.start_angle) * cos(a.rotation);
1569    let tbx = 0.0 - b.radius * sin(b.start_angle) * cos(b.rotation) - b.radius_b * cos(b.start_angle) * sin(b.rotation);
1570    let tby = 0.0 - b.radius * sin(b.start_angle) * sin(b.rotation) + b.radius_b * cos(b.start_angle) * cos(b.rotation);
1571    [(tax * tby - tay * tbx) * sketch.constraint_isigma]
1572}))]
1573// Shared endpoint a.start = b.end
1574#[arael(constraint(hb, guard = self.shared == SharedEndpoint::StartEnd, {
1575    let tax = 0.0 - a.radius * sin(a.start_angle) * cos(a.rotation) - a.radius_b * cos(a.start_angle) * sin(a.rotation);
1576    let tay = 0.0 - a.radius * sin(a.start_angle) * sin(a.rotation) + a.radius_b * cos(a.start_angle) * cos(a.rotation);
1577    let tbx = 0.0 - b.radius * sin(b.end_angle) * cos(b.rotation) - b.radius_b * cos(b.end_angle) * sin(b.rotation);
1578    let tby = 0.0 - b.radius * sin(b.end_angle) * sin(b.rotation) + b.radius_b * cos(b.end_angle) * cos(b.rotation);
1579    [(tax * tby - tay * tbx) * sketch.constraint_isigma]
1580}))]
1581// Shared endpoint a.end = b.start
1582#[arael(constraint(hb, guard = self.shared == SharedEndpoint::EndStart, {
1583    let tax = 0.0 - a.radius * sin(a.end_angle) * cos(a.rotation) - a.radius_b * cos(a.end_angle) * sin(a.rotation);
1584    let tay = 0.0 - a.radius * sin(a.end_angle) * sin(a.rotation) + a.radius_b * cos(a.end_angle) * cos(a.rotation);
1585    let tbx = 0.0 - b.radius * sin(b.start_angle) * cos(b.rotation) - b.radius_b * cos(b.start_angle) * sin(b.rotation);
1586    let tby = 0.0 - b.radius * sin(b.start_angle) * sin(b.rotation) + b.radius_b * cos(b.start_angle) * cos(b.rotation);
1587    [(tax * tby - tay * tbx) * sketch.constraint_isigma]
1588}))]
1589// Shared endpoint a.end = b.end
1590#[arael(constraint(hb, guard = self.shared == SharedEndpoint::EndEnd, {
1591    let tax = 0.0 - a.radius * sin(a.end_angle) * cos(a.rotation) - a.radius_b * cos(a.end_angle) * sin(a.rotation);
1592    let tay = 0.0 - a.radius * sin(a.end_angle) * sin(a.rotation) + a.radius_b * cos(a.end_angle) * cos(a.rotation);
1593    let tbx = 0.0 - b.radius * sin(b.end_angle) * cos(b.rotation) - b.radius_b * cos(b.end_angle) * sin(b.rotation);
1594    let tby = 0.0 - b.radius * sin(b.end_angle) * sin(b.rotation) + b.radius_b * cos(b.end_angle) * cos(b.rotation);
1595    [(tax * tby - tay * tbx) * sketch.constraint_isigma]
1596}))]
1597pub struct TangentAA {
1598    #[arael(ref = root.arcs)]
1599    pub a: Ref<Arc>,
1600    #[arael(ref = root.arcs)]
1601    pub b: Ref<Arc>,
1602    #[serde(default)]
1603    pub shared: SharedEndpoint,
1604    #[serde(default)]
1605    pub nid: u32,
1606    #[arael(constraint_index)]
1607    #[serde(skip)]
1608    pub cid: u32,
1609    #[serde(skip)]
1610    pub hb: CrossBlock<Arc, Arc>,
1611}
1612
1613// -- Line endpoint <-> Arc point coincidence --
1614
1615#[derive(serde::Serialize, serde::Deserialize)]
1616#[arael::model]
1617#[arael(constraint(hb, {
1618    [(line.p1.x - arc.center.x) * sketch.constraint_isigma,
1619     (line.p1.y - arc.center.y) * sketch.constraint_isigma]
1620}))]
1621pub struct CoincidentLP1ArcCenter {
1622    #[arael(ref = root.lines)]
1623    pub line: Ref<Line>,
1624    #[arael(ref = root.arcs)]
1625    pub arc: Ref<Arc>,
1626    #[serde(default)]
1627    pub nid: u32,
1628    #[arael(constraint_index)]
1629    #[serde(skip)]
1630    pub cid: u32,
1631    #[serde(skip)]
1632    pub hb: CrossBlock<Line, Arc>,
1633}
1634
1635#[derive(serde::Serialize, serde::Deserialize)]
1636#[arael::model]
1637#[arael(constraint(hb, {
1638    [(line.p2.x - arc.center.x) * sketch.constraint_isigma,
1639     (line.p2.y - arc.center.y) * sketch.constraint_isigma]
1640}))]
1641pub struct CoincidentLP2ArcCenter {
1642    #[arael(ref = root.lines)]
1643    pub line: Ref<Line>,
1644    #[arael(ref = root.arcs)]
1645    pub arc: Ref<Arc>,
1646    #[serde(default)]
1647    pub nid: u32,
1648    #[arael(constraint_index)]
1649    #[serde(skip)]
1650    pub cid: u32,
1651    #[serde(skip)]
1652    pub hb: CrossBlock<Line, Arc>,
1653}
1654
1655#[derive(serde::Serialize, serde::Deserialize)]
1656#[arael::model]
1657#[arael(constraint(hb, {
1658    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
1659    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
1660    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
1661    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
1662    [(line.p1.x - sx) * sketch.constraint_isigma,
1663     (line.p1.y - sy) * sketch.constraint_isigma]
1664}))]
1665pub struct CoincidentLP1ArcStart {
1666    #[arael(ref = root.lines)]
1667    pub line: Ref<Line>,
1668    #[arael(ref = root.arcs)]
1669    pub arc: Ref<Arc>,
1670    #[serde(default)]
1671    pub nid: u32,
1672    #[arael(constraint_index)]
1673    #[serde(skip)]
1674    pub cid: u32,
1675    #[serde(skip)]
1676    pub hb: CrossBlock<Line, Arc>,
1677}
1678
1679#[derive(serde::Serialize, serde::Deserialize)]
1680#[arael::model]
1681#[arael(constraint(hb, {
1682    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
1683    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
1684    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
1685    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
1686    [(line.p2.x - sx) * sketch.constraint_isigma,
1687     (line.p2.y - sy) * sketch.constraint_isigma]
1688}))]
1689pub struct CoincidentLP2ArcStart {
1690    #[arael(ref = root.lines)]
1691    pub line: Ref<Line>,
1692    #[arael(ref = root.arcs)]
1693    pub arc: Ref<Arc>,
1694    #[serde(default)]
1695    pub nid: u32,
1696    #[arael(constraint_index)]
1697    #[serde(skip)]
1698    pub cid: u32,
1699    #[serde(skip)]
1700    pub hb: CrossBlock<Line, Arc>,
1701}
1702
1703#[derive(serde::Serialize, serde::Deserialize)]
1704#[arael::model]
1705#[arael(constraint(hb, {
1706    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
1707    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
1708    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
1709    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
1710    [(line.p1.x - ex) * sketch.constraint_isigma,
1711     (line.p1.y - ey) * sketch.constraint_isigma]
1712}))]
1713pub struct CoincidentLP1ArcEnd {
1714    #[arael(ref = root.lines)]
1715    pub line: Ref<Line>,
1716    #[arael(ref = root.arcs)]
1717    pub arc: Ref<Arc>,
1718    #[serde(default)]
1719    pub nid: u32,
1720    #[arael(constraint_index)]
1721    #[serde(skip)]
1722    pub cid: u32,
1723    #[serde(skip)]
1724    pub hb: CrossBlock<Line, Arc>,
1725}
1726
1727#[derive(serde::Serialize, serde::Deserialize)]
1728#[arael::model]
1729#[arael(constraint(hb, {
1730    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
1731    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
1732    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
1733    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
1734    [(line.p2.x - ex) * sketch.constraint_isigma,
1735     (line.p2.y - ey) * sketch.constraint_isigma]
1736}))]
1737pub struct CoincidentLP2ArcEnd {
1738    #[arael(ref = root.lines)]
1739    pub line: Ref<Line>,
1740    #[arael(ref = root.arcs)]
1741    pub arc: Ref<Arc>,
1742    #[serde(default)]
1743    pub nid: u32,
1744    #[arael(constraint_index)]
1745    #[serde(skip)]
1746    pub cid: u32,
1747    #[serde(skip)]
1748    pub hb: CrossBlock<Line, Arc>,
1749}
1750
1751// -- Arc-Arc endpoint coincidence --
1752
1753#[derive(serde::Serialize, serde::Deserialize)]
1754#[arael::model]
1755#[arael(constraint(hb, {
1756    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
1757    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
1758    let bsx = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
1759    let bsy = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
1760    [(a.center.x - bsx) * sketch.constraint_isigma,
1761     (a.center.y - bsy) * sketch.constraint_isigma]
1762}))]
1763pub struct CoincidentArcCenterStart {
1764    #[arael(ref = root.arcs)]
1765    pub a: Ref<Arc>,
1766    #[arael(ref = root.arcs)]
1767    pub b: Ref<Arc>,
1768    #[serde(default)]
1769    pub nid: u32,
1770    #[arael(constraint_index)]
1771    #[serde(skip)]
1772    pub cid: u32,
1773    #[serde(skip)]
1774    pub hb: CrossBlock<Arc, Arc>,
1775}
1776
1777#[derive(serde::Serialize, serde::Deserialize)]
1778#[arael::model]
1779#[arael(constraint(hb, {
1780    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
1781    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
1782    let bex = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
1783    let bey = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
1784    [(a.center.x - bex) * sketch.constraint_isigma,
1785     (a.center.y - bey) * sketch.constraint_isigma]
1786}))]
1787pub struct CoincidentArcCenterEnd {
1788    #[arael(ref = root.arcs)]
1789    pub a: Ref<Arc>,
1790    #[arael(ref = root.arcs)]
1791    pub b: Ref<Arc>,
1792    #[serde(default)]
1793    pub nid: u32,
1794    #[arael(constraint_index)]
1795    #[serde(skip)]
1796    pub cid: u32,
1797    #[serde(skip)]
1798    pub hb: CrossBlock<Arc, Arc>,
1799}
1800
1801#[derive(serde::Serialize, serde::Deserialize)]
1802#[arael::model]
1803#[arael(constraint(hb, {
1804    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
1805    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
1806    let asx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
1807    let asy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
1808    [(asx - b.center.x) * sketch.constraint_isigma,
1809     (asy - b.center.y) * sketch.constraint_isigma]
1810}))]
1811pub struct CoincidentArcStartCenter {
1812    #[arael(ref = root.arcs)]
1813    pub a: Ref<Arc>,
1814    #[arael(ref = root.arcs)]
1815    pub b: Ref<Arc>,
1816    #[serde(default)]
1817    pub nid: u32,
1818    #[arael(constraint_index)]
1819    #[serde(skip)]
1820    pub cid: u32,
1821    #[serde(skip)]
1822    pub hb: CrossBlock<Arc, Arc>,
1823}
1824
1825#[derive(serde::Serialize, serde::Deserialize)]
1826#[arael::model]
1827#[arael(constraint(hb, {
1828    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
1829    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
1830    let aex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
1831    let aey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
1832    [(aex - b.center.x) * sketch.constraint_isigma,
1833     (aey - b.center.y) * sketch.constraint_isigma]
1834}))]
1835pub struct CoincidentArcEndCenter {
1836    #[arael(ref = root.arcs)]
1837    pub a: Ref<Arc>,
1838    #[arael(ref = root.arcs)]
1839    pub b: Ref<Arc>,
1840    #[serde(default)]
1841    pub nid: u32,
1842    #[arael(constraint_index)]
1843    #[serde(skip)]
1844    pub cid: u32,
1845    #[serde(skip)]
1846    pub hb: CrossBlock<Arc, Arc>,
1847}
1848
1849#[derive(serde::Serialize, serde::Deserialize)]
1850#[arael::model]
1851#[arael(constraint(hb, {
1852    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
1853    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
1854    let asx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
1855    let asy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
1856    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
1857    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
1858    let bsx = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
1859    let bsy = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
1860    [(asx - bsx) * sketch.constraint_isigma,
1861     (asy - bsy) * sketch.constraint_isigma]
1862}))]
1863pub struct CoincidentArcStartStart {
1864    #[arael(ref = root.arcs)]
1865    pub a: Ref<Arc>,
1866    #[arael(ref = root.arcs)]
1867    pub b: Ref<Arc>,
1868    #[serde(default)]
1869    pub nid: u32,
1870    #[arael(constraint_index)]
1871    #[serde(skip)]
1872    pub cid: u32,
1873    #[serde(skip)]
1874    pub hb: CrossBlock<Arc, Arc>,
1875}
1876
1877#[derive(serde::Serialize, serde::Deserialize)]
1878#[arael::model]
1879#[arael(constraint(hb, {
1880    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
1881    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
1882    let asx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
1883    let asy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
1884    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
1885    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
1886    let bex = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
1887    let bey = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
1888    [(asx - bex) * sketch.constraint_isigma,
1889     (asy - bey) * sketch.constraint_isigma]
1890}))]
1891pub struct CoincidentArcStartEnd {
1892    #[arael(ref = root.arcs)]
1893    pub a: Ref<Arc>,
1894    #[arael(ref = root.arcs)]
1895    pub b: Ref<Arc>,
1896    #[serde(default)]
1897    pub nid: u32,
1898    #[arael(constraint_index)]
1899    #[serde(skip)]
1900    pub cid: u32,
1901    #[serde(skip)]
1902    pub hb: CrossBlock<Arc, Arc>,
1903}
1904
1905#[derive(serde::Serialize, serde::Deserialize)]
1906#[arael::model]
1907#[arael(constraint(hb, {
1908    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
1909    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
1910    let aex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
1911    let aey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
1912    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
1913    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
1914    let bsx = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
1915    let bsy = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
1916    [(aex - bsx) * sketch.constraint_isigma,
1917     (aey - bsy) * sketch.constraint_isigma]
1918}))]
1919pub struct CoincidentArcEndStart {
1920    #[arael(ref = root.arcs)]
1921    pub a: Ref<Arc>,
1922    #[arael(ref = root.arcs)]
1923    pub b: Ref<Arc>,
1924    #[serde(default)]
1925    pub nid: u32,
1926    #[arael(constraint_index)]
1927    #[serde(skip)]
1928    pub cid: u32,
1929    #[serde(skip)]
1930    pub hb: CrossBlock<Arc, Arc>,
1931}
1932
1933#[derive(serde::Serialize, serde::Deserialize)]
1934#[arael::model]
1935#[arael(constraint(hb, {
1936    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
1937    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
1938    let aex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
1939    let aey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
1940    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
1941    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
1942    let bex = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
1943    let bey = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
1944    [(aex - bex) * sketch.constraint_isigma,
1945     (aey - bey) * sketch.constraint_isigma]
1946}))]
1947pub struct CoincidentArcEndEnd {
1948    #[arael(ref = root.arcs)]
1949    pub a: Ref<Arc>,
1950    #[arael(ref = root.arcs)]
1951    pub b: Ref<Arc>,
1952    #[serde(default)]
1953    pub nid: u32,
1954    #[arael(constraint_index)]
1955    #[serde(skip)]
1956    pub cid: u32,
1957    #[serde(skip)]
1958    pub hb: CrossBlock<Arc, Arc>,
1959}
1960
1961// -- Line endpoint on line --
1962
1963// Line a's p1 lies on infinite line through b's p1-p2
1964#[derive(serde::Serialize, serde::Deserialize)]
1965#[arael::model]
1966#[arael(constraint(hb, {
1967    let dx = b.p2.x - b.p1.x;
1968    let dy = b.p2.y - b.p1.y;
1969    let len = sqrt(dx * dx + dy * dy);
1970    [((a.p1.x - b.p1.x) * dy - (a.p1.y - b.p1.y) * dx) / len
1971     * sketch.constraint_isigma]
1972}))]
1973pub struct LineP1OnLine {
1974    #[arael(ref = root.lines)]
1975    pub a: Ref<Line>,
1976    #[arael(ref = root.lines)]
1977    pub b: Ref<Line>,
1978    #[serde(default)]
1979    pub nid: u32,
1980    #[arael(constraint_index)]
1981    #[serde(skip)]
1982    pub cid: u32,
1983    #[serde(skip)]
1984    pub hb: CrossBlock<Line, Line>,
1985}
1986
1987// Line a's p2 lies on infinite line through b's p1-p2
1988#[derive(serde::Serialize, serde::Deserialize)]
1989#[arael::model]
1990#[arael(constraint(hb, {
1991    let dx = b.p2.x - b.p1.x;
1992    let dy = b.p2.y - b.p1.y;
1993    let len = sqrt(dx * dx + dy * dy);
1994    [((a.p2.x - b.p1.x) * dy - (a.p2.y - b.p1.y) * dx) / len
1995     * sketch.constraint_isigma]
1996}))]
1997pub struct LineP2OnLine {
1998    #[arael(ref = root.lines)]
1999    pub a: Ref<Line>,
2000    #[arael(ref = root.lines)]
2001    pub b: Ref<Line>,
2002    #[serde(default)]
2003    pub nid: u32,
2004    #[arael(constraint_index)]
2005    #[serde(skip)]
2006    pub cid: u32,
2007    #[serde(skip)]
2008    pub hb: CrossBlock<Line, Line>,
2009}
2010
2011// -- Line endpoint on arc --
2012
2013// Line p1 lies on ellipse/circle defined by arc
2014#[derive(serde::Serialize, serde::Deserialize)]
2015#[arael::model]
2016#[arael(constraint(hb, {
2017    let dx = line.p1.x - arc.center.x;
2018    let dy = line.p1.y - arc.center.y;
2019    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2020    let u = dx * cr + dy * sr;
2021    let v = 0.0 - dx * sr + dy * cr;
2022    [(u * u / (arc.radius * arc.radius) + v * v / (arc.radius_b * arc.radius_b) - 1.0) * sketch.constraint_isigma]
2023}))]
2024pub struct LineP1OnArc {
2025    #[arael(ref = root.lines)]
2026    pub line: Ref<Line>,
2027    #[arael(ref = root.arcs)]
2028    pub arc: Ref<Arc>,
2029    #[serde(default)]
2030    pub nid: u32,
2031    #[arael(constraint_index)]
2032    #[serde(skip)]
2033    pub cid: u32,
2034    #[serde(skip)]
2035    pub hb: CrossBlock<Line, Arc>,
2036}
2037
2038// Line p2 lies on ellipse/circle defined by arc
2039#[derive(serde::Serialize, serde::Deserialize)]
2040#[arael::model]
2041#[arael(constraint(hb, {
2042    let dx = line.p2.x - arc.center.x;
2043    let dy = line.p2.y - arc.center.y;
2044    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2045    let u = dx * cr + dy * sr;
2046    let v = 0.0 - dx * sr + dy * cr;
2047    [(u * u / (arc.radius * arc.radius) + v * v / (arc.radius_b * arc.radius_b) - 1.0) * sketch.constraint_isigma]
2048}))]
2049pub struct LineP2OnArc {
2050    #[arael(ref = root.lines)]
2051    pub line: Ref<Line>,
2052    #[arael(ref = root.arcs)]
2053    pub arc: Ref<Arc>,
2054    #[serde(default)]
2055    pub nid: u32,
2056    #[arael(constraint_index)]
2057    #[serde(skip)]
2058    pub cid: u32,
2059    #[serde(skip)]
2060    pub hb: CrossBlock<Line, Arc>,
2061}
2062
2063// -- Symmetry (3-entity) --
2064
2065// Symmetry of 3 lines: from each B endpoint, cast a ray and intersect
2066// with A and C. Signed ray parameters must sum to zero.
2067// `use_normal_ray` selects ray direction: true = B's normal (default),
2068// false = B's direction (for when A/C are nearly perpendicular to B).
2069// Set at constraint creation based on initial geometry.
2070// Dense 3-entity coupling: one named CrossBlock<Line, Line> per unordered
2071// ref pair. Packed NA*NB storage is faster than TripletBlock's COO push.
2072// `cross = (refA, refB)` is mandatory here since all three CrossBlocks
2073// share the same type signature.
2074#[derive(serde::Serialize, serde::Deserialize)]
2075#[arael::model]
2076#[arael(constraint([hb_ab, hb_ac, hb_bc], {
2077    let bn = (b.p2 - b.p1).across();
2078    let bd = b.p2 - b.p1;
2079    let ad = a.p2 - a.p1;
2080    let cd = c.p2 - c.p1;
2081    // Normalize by direction vector lengths for well-conditioned residuals
2082    let alen = ad.norm();
2083    let clen = cd.norm();
2084    let blen = bd.norm();
2085    // Ray along B's normal: equidistant perpendicular to B
2086    let bna = bn.cross(ad) / (blen * alen);
2087    let bnc = bn.cross(cd) / (blen * clen);
2088    let rn1 = (a.p1 - b.p1).cross(ad) / alen * bnc + (c.p1 - b.p1).cross(cd) / clen * bna;
2089    let rn2 = (a.p1 - b.p2).cross(ad) / alen * bnc + (c.p1 - b.p2).cross(cd) / clen * bna;
2090    // Intersection of A and C lies on line B (normalized)
2091    let adc = ad.cross(cd) / (alen * clen);
2092    let r3 = (a.p1 - b.p1).cross(bd) / blen * adc + ad.cross(bd) / (alen * blen) * (c.p1 - a.p1).cross(cd) / clen;
2093    [rn1 * sketch.constraint_isigma,
2094     rn2 * sketch.constraint_isigma,
2095     r3 * sketch.constraint_isigma]
2096}))]
2097pub struct SymmetryLL {
2098    #[arael(ref = root.lines)]
2099    pub a: Ref<Line>,
2100    #[arael(ref = root.lines)]
2101    pub b: Ref<Line>,
2102    #[arael(ref = root.lines)]
2103    pub c: Ref<Line>,
2104    #[serde(default)]
2105    pub nid: u32,
2106    #[arael(constraint_index)]
2107    #[serde(skip)]
2108    pub cid: u32,
2109    #[arael(cross = (a, b))]
2110    #[serde(skip)]
2111    pub hb_ab: CrossBlock<Line, Line>,
2112    #[arael(cross = (a, c))]
2113    #[serde(skip)]
2114    pub hb_ac: CrossBlock<Line, Line>,
2115    #[arael(cross = (b, c))]
2116    #[serde(skip)]
2117    pub hb_bc: CrossBlock<Line, Line>,
2118}
2119
2120// -- Distance Point-Line --
2121
2122#[derive(serde::Serialize, serde::Deserialize)]
2123#[arael::model]
2124#[arael(constraint(hb, {
2125    let dx = line.p2.x - line.p1.x;
2126    let dy = line.p2.y - line.p1.y;
2127    let len = sqrt(dx * dx + dy * dy);
2128    let dist = ((point.pos.x - line.p1.x) * dy - (point.pos.y - line.p1.y) * dx) / len;
2129    [(dist - distancepl.distance) * sketch.constraint_isigma]
2130}))]
2131pub struct DistancePL {
2132    #[arael(ref = root.points)]
2133    pub point: Ref<Point>,
2134    #[arael(ref = root.lines)]
2135    pub line: Ref<Line>,
2136    pub distance: f64,  // signed distance (positive = left of line direction)
2137    #[serde(default)]
2138    pub nid: u32,
2139    #[arael(constraint_index)]
2140    #[serde(skip)]
2141    pub cid: u32,
2142    #[serde(skip)]
2143    pub hb: CrossBlock<Point, Line>,
2144}
2145
2146// Line endpoint p1 to line (signed perpendicular distance)
2147#[derive(serde::Serialize, serde::Deserialize)]
2148#[arael::model]
2149#[arael(constraint(hb, {
2150    let dx = b.p2.x - b.p1.x;
2151    let dy = b.p2.y - b.p1.y;
2152    let len = sqrt(dx * dx + dy * dy);
2153    let dist = ((a.p1.x - b.p1.x) * dy - (a.p1.y - b.p1.y) * dx) / len;
2154    [(dist - distancelp1l.distance) * sketch.constraint_isigma]
2155}))]
2156pub struct DistanceLP1L {
2157    #[arael(ref = root.lines)] pub a: Ref<Line>,
2158    #[arael(ref = root.lines)] pub b: Ref<Line>,
2159    pub distance: f64,
2160    #[serde(default)]
2161    pub nid: u32,
2162    #[arael(constraint_index)]
2163    #[serde(skip)]
2164    pub cid: u32,
2165    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
2166}
2167
2168// Line endpoint p2 to line (signed perpendicular distance)
2169#[derive(serde::Serialize, serde::Deserialize)]
2170#[arael::model]
2171#[arael(constraint(hb, {
2172    let dx = b.p2.x - b.p1.x;
2173    let dy = b.p2.y - b.p1.y;
2174    let len = sqrt(dx * dx + dy * dy);
2175    let dist = ((a.p2.x - b.p1.x) * dy - (a.p2.y - b.p1.y) * dx) / len;
2176    [(dist - distancelp2l.distance) * sketch.constraint_isigma]
2177}))]
2178pub struct DistanceLP2L {
2179    #[arael(ref = root.lines)] pub a: Ref<Line>,
2180    #[arael(ref = root.lines)] pub b: Ref<Line>,
2181    pub distance: f64,
2182    #[serde(default)]
2183    pub nid: u32,
2184    #[arael(constraint_index)]
2185    #[serde(skip)]
2186    pub cid: u32,
2187    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
2188}
2189
2190// Arc center to line (signed perpendicular distance)
2191#[derive(serde::Serialize, serde::Deserialize)]
2192#[arael::model]
2193#[arael(constraint(hb, {
2194    let dx = line.p2.x - line.p1.x;
2195    let dy = line.p2.y - line.p1.y;
2196    let len = sqrt(dx * dx + dy * dy);
2197    let dist = ((arc.center.x - line.p1.x) * dy - (arc.center.y - line.p1.y) * dx) / len;
2198    [(dist - distancearccenterl.distance) * sketch.constraint_isigma]
2199}))]
2200pub struct DistanceArcCenterL {
2201    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2202    #[arael(ref = root.lines)] pub line: Ref<Line>,
2203    pub distance: f64,
2204    #[serde(default)]
2205    pub nid: u32,
2206    #[arael(constraint_index)]
2207    #[serde(skip)]
2208    pub cid: u32,
2209    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
2210}
2211
2212// Arc start to line (signed perpendicular distance)
2213#[derive(serde::Serialize, serde::Deserialize)]
2214#[arael::model]
2215#[arael(constraint(hb, {
2216    let dx = line.p2.x - line.p1.x;
2217    let dy = line.p2.y - line.p1.y;
2218    let len = sqrt(dx * dx + dy * dy);
2219    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
2220    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2221    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
2222    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
2223    let dist = ((sx - line.p1.x) * dy - (sy - line.p1.y) * dx) / len;
2224    [(dist - distancearcstartl.distance) * sketch.constraint_isigma]
2225}))]
2226pub struct DistanceArcStartL {
2227    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2228    #[arael(ref = root.lines)] pub line: Ref<Line>,
2229    pub distance: f64,
2230    #[serde(default)]
2231    pub nid: u32,
2232    #[arael(constraint_index)]
2233    #[serde(skip)]
2234    pub cid: u32,
2235    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
2236}
2237
2238// Arc end to line (signed perpendicular distance)
2239#[derive(serde::Serialize, serde::Deserialize)]
2240#[arael::model]
2241#[arael(constraint(hb, {
2242    let dx = line.p2.x - line.p1.x;
2243    let dy = line.p2.y - line.p1.y;
2244    let len = sqrt(dx * dx + dy * dy);
2245    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
2246    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2247    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
2248    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
2249    let dist = ((ex - line.p1.x) * dy - (ey - line.p1.y) * dx) / len;
2250    [(dist - distancearcendl.distance) * sketch.constraint_isigma]
2251}))]
2252pub struct DistanceArcEndL {
2253    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2254    #[arael(ref = root.lines)] pub line: Ref<Line>,
2255    pub distance: f64,
2256    #[serde(default)]
2257    pub nid: u32,
2258    #[arael(constraint_index)]
2259    #[serde(skip)]
2260    pub cid: u32,
2261    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
2262}
2263
2264// ---------------------------------------------------------------------------
2265// Symmetry: two points about a mirror line
2266// ---------------------------------------------------------------------------
2267
2268/// Two points forced symmetric about a mirror line.
2269/// Residual: reflect a across line, compare to c.
2270/// Dense 3-entity coupling: packed CrossBlocks for each pair beat
2271/// TripletBlock's COO push. hb_ac is unambiguous by type (only
2272/// Point-Point pair); the two Point-Line blocks need explicit
2273/// `cross = (..)` to pick their ref.
2274#[derive(serde::Serialize, serde::Deserialize)]
2275#[arael::model]
2276#[arael(constraint([hb_ac, hb_al, hb_cl], {
2277    let dx = line.p2.x - line.p1.x;
2278    let dy = line.p2.y - line.p1.y;
2279    let len2 = dx * dx + dy * dy;
2280    // Signed perpendicular distance of a from line (unnormalized)
2281    let da = (a.pos.x - line.p1.x) * dy - (a.pos.y - line.p1.y) * dx;
2282    // Reflect a across line
2283    let rx = a.pos.x - 2.0 * da * dy / len2;
2284    let ry = a.pos.y + 2.0 * da * dx / len2;
2285    [(rx - c.pos.x) * sketch.constraint_isigma,
2286     (ry - c.pos.y) * sketch.constraint_isigma]
2287}))]
2288pub struct SymmetryPP {
2289    #[arael(ref = root.points)]
2290    pub a: Ref<Point>,
2291    #[arael(ref = root.points)]
2292    pub c: Ref<Point>,
2293    #[arael(ref = root.lines)]
2294    pub line: Ref<Line>,
2295    #[serde(default)]
2296    pub nid: u32,
2297    #[arael(constraint_index)]
2298    #[serde(skip)]
2299    pub cid: u32,
2300    #[serde(skip)]
2301    pub hb_ac: CrossBlock<Point, Point>,
2302    #[arael(cross = (a, line))]
2303    #[serde(skip)]
2304    pub hb_al: CrossBlock<Point, Line>,
2305    #[arael(cross = (c, line))]
2306    #[serde(skip)]
2307    pub hb_cl: CrossBlock<Point, Line>,
2308}
2309
2310// Symmetry of two arcs/ellipses about a mirror line.
2311// Guarded: circle path uses 3 residuals (center + radius), ellipse path
2312// adds radius_b equality + rotation reflection (5 residuals).
2313// Cannot unify because radius_b is a real parameter for circles (equality
2314// constraint), so the extra residuals affect DOF counting.
2315// Dense 3-entity coupling; decomposed into packed CrossBlocks per pair.
2316// Both guarded constraint bodies share the same three CrossBlock fields.
2317#[derive(serde::Serialize, serde::Deserialize)]
2318#[arael::model]
2319#[arael(constraint([hb_ac, hb_al, hb_cl], guard = !a.is_ellipse && !c.is_ellipse, {
2320    let dx = line.p2.x - line.p1.x;
2321    let dy = line.p2.y - line.p1.y;
2322    let len2 = dx * dx + dy * dy;
2323    let da = (a.center.x - line.p1.x) * dy - (a.center.y - line.p1.y) * dx;
2324    let rx = a.center.x - 2.0 * da * dy / len2;
2325    let ry = a.center.y + 2.0 * da * dx / len2;
2326    [(rx - c.center.x) * sketch.constraint_isigma,
2327     (ry - c.center.y) * sketch.constraint_isigma,
2328     (a.radius - c.radius) * sketch.constraint_isigma]
2329}))]
2330#[arael(constraint([hb_ac, hb_al, hb_cl], guard = a.is_ellipse || c.is_ellipse, {
2331    let dx = line.p2.x - line.p1.x;
2332    let dy = line.p2.y - line.p1.y;
2333    let len2 = dx * dx + dy * dy;
2334    let da = (a.center.x - line.p1.x) * dy - (a.center.y - line.p1.y) * dx;
2335    let rx = a.center.x - 2.0 * da * dy / len2;
2336    let ry = a.center.y + 2.0 * da * dx / len2;
2337    let alpha = atan2(dy, dx);
2338    let reflected_rot = 2.0 * alpha - a.rotation;
2339    [(rx - c.center.x) * sketch.constraint_isigma,
2340     (ry - c.center.y) * sketch.constraint_isigma,
2341     (a.radius - c.radius) * sketch.constraint_isigma,
2342     (a.radius_b - c.radius_b) * sketch.constraint_isigma,
2343     sin(reflected_rot - c.rotation) * sketch.constraint_isigma]
2344}))]
2345pub struct SymmetryAA {
2346    #[arael(ref = root.arcs)]
2347    pub a: Ref<Arc>,
2348    #[arael(ref = root.arcs)]
2349    pub c: Ref<Arc>,
2350    #[arael(ref = root.lines)]
2351    pub line: Ref<Line>,
2352    #[serde(default)]
2353    pub nid: u32,
2354    #[arael(constraint_index)]
2355    #[serde(skip)]
2356    pub cid: u32,
2357    #[serde(skip)]
2358    pub hb_ac: CrossBlock<Arc, Arc>,
2359    #[arael(cross = (a, line))]
2360    #[serde(skip)]
2361    pub hb_al: CrossBlock<Arc, Line>,
2362    #[arael(cross = (c, line))]
2363    #[serde(skip)]
2364    pub hb_cl: CrossBlock<Arc, Line>,
2365}
2366
2367// ---------------------------------------------------------------------------
2368// Axis distance (horizontal/vertical unified via guard flag)
2369// ---------------------------------------------------------------------------
2370
2371// -- Line-Line --
2372
2373#[derive(serde::Serialize, serde::Deserialize)]
2374#[arael::model]
2375#[arael(constraint(hb, guard = self.horizontal, {
2376    [(a.p1.x - b.p1.x - axisdistancell11.distance) * sketch.constraint_isigma]
2377}))]
2378#[arael(constraint(hb, guard = !self.horizontal, {
2379    [(a.p1.y - b.p1.y - axisdistancell11.distance) * sketch.constraint_isigma]
2380}))]
2381pub struct AxisDistanceLL11 {
2382    #[arael(ref = root.lines)] pub a: Ref<Line>,
2383    #[arael(ref = root.lines)] pub b: Ref<Line>,
2384    pub distance: f64,
2385    pub horizontal: bool,
2386    #[serde(default)]
2387    pub nid: u32,
2388    #[arael(constraint_index)]
2389    #[serde(skip)]
2390    pub cid: u32,
2391    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
2392}
2393
2394#[derive(serde::Serialize, serde::Deserialize)]
2395#[arael::model]
2396#[arael(constraint(hb, guard = self.horizontal, {
2397    [(a.p1.x - b.p2.x - axisdistancell12.distance) * sketch.constraint_isigma]
2398}))]
2399#[arael(constraint(hb, guard = !self.horizontal, {
2400    [(a.p1.y - b.p2.y - axisdistancell12.distance) * sketch.constraint_isigma]
2401}))]
2402pub struct AxisDistanceLL12 {
2403    #[arael(ref = root.lines)] pub a: Ref<Line>,
2404    #[arael(ref = root.lines)] pub b: Ref<Line>,
2405    pub distance: f64,
2406    pub horizontal: bool,
2407    #[serde(default)]
2408    pub nid: u32,
2409    #[arael(constraint_index)]
2410    #[serde(skip)]
2411    pub cid: u32,
2412    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
2413}
2414
2415#[derive(serde::Serialize, serde::Deserialize)]
2416#[arael::model]
2417#[arael(constraint(hb, guard = self.horizontal, {
2418    [(a.p2.x - b.p1.x - axisdistancell21.distance) * sketch.constraint_isigma]
2419}))]
2420#[arael(constraint(hb, guard = !self.horizontal, {
2421    [(a.p2.y - b.p1.y - axisdistancell21.distance) * sketch.constraint_isigma]
2422}))]
2423pub struct AxisDistanceLL21 {
2424    #[arael(ref = root.lines)] pub a: Ref<Line>,
2425    #[arael(ref = root.lines)] pub b: Ref<Line>,
2426    pub distance: f64,
2427    pub horizontal: bool,
2428    #[serde(default)]
2429    pub nid: u32,
2430    #[arael(constraint_index)]
2431    #[serde(skip)]
2432    pub cid: u32,
2433    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
2434}
2435
2436#[derive(serde::Serialize, serde::Deserialize)]
2437#[arael::model]
2438#[arael(constraint(hb, guard = self.horizontal, {
2439    [(a.p2.x - b.p2.x - axisdistancell22.distance) * sketch.constraint_isigma]
2440}))]
2441#[arael(constraint(hb, guard = !self.horizontal, {
2442    [(a.p2.y - b.p2.y - axisdistancell22.distance) * sketch.constraint_isigma]
2443}))]
2444pub struct AxisDistanceLL22 {
2445    #[arael(ref = root.lines)] pub a: Ref<Line>,
2446    #[arael(ref = root.lines)] pub b: Ref<Line>,
2447    pub distance: f64,
2448    pub horizontal: bool,
2449    #[serde(default)]
2450    pub nid: u32,
2451    #[arael(constraint_index)]
2452    #[serde(skip)]
2453    pub cid: u32,
2454    #[serde(skip)] pub hb: CrossBlock<Line, Line>,
2455}
2456
2457// -- Line-Point --
2458
2459#[derive(serde::Serialize, serde::Deserialize)]
2460#[arael::model]
2461#[arael(constraint(hb, guard = self.horizontal, {
2462    [(line.p1.x - point.pos.x - axisdistancelp1.distance) * sketch.constraint_isigma]
2463}))]
2464#[arael(constraint(hb, guard = !self.horizontal, {
2465    [(line.p1.y - point.pos.y - axisdistancelp1.distance) * sketch.constraint_isigma]
2466}))]
2467pub struct AxisDistanceLP1 {
2468    #[arael(ref = root.lines)] pub line: Ref<Line>,
2469    #[arael(ref = root.points)] pub point: Ref<Point>,
2470    pub distance: f64,
2471    pub horizontal: bool,
2472    #[serde(default)]
2473    pub nid: u32,
2474    #[arael(constraint_index)]
2475    #[serde(skip)]
2476    pub cid: u32,
2477    #[serde(skip)] pub hb: CrossBlock<Line, Point>,
2478}
2479
2480#[derive(serde::Serialize, serde::Deserialize)]
2481#[arael::model]
2482#[arael(constraint(hb, guard = self.horizontal, {
2483    [(line.p2.x - point.pos.x - axisdistancelp2.distance) * sketch.constraint_isigma]
2484}))]
2485#[arael(constraint(hb, guard = !self.horizontal, {
2486    [(line.p2.y - point.pos.y - axisdistancelp2.distance) * sketch.constraint_isigma]
2487}))]
2488pub struct AxisDistanceLP2 {
2489    #[arael(ref = root.lines)] pub line: Ref<Line>,
2490    #[arael(ref = root.points)] pub point: Ref<Point>,
2491    pub distance: f64,
2492    pub horizontal: bool,
2493    #[serde(default)]
2494    pub nid: u32,
2495    #[arael(constraint_index)]
2496    #[serde(skip)]
2497    pub cid: u32,
2498    #[serde(skip)] pub hb: CrossBlock<Line, Point>,
2499}
2500
2501// -- Arc-Point --
2502
2503#[derive(serde::Serialize, serde::Deserialize)]
2504#[arael::model]
2505#[arael(constraint(hb, guard = self.horizontal, {
2506    [(arc.center.x - point.pos.x - axisdistancearccenterp.distance) * sketch.constraint_isigma]
2507}))]
2508#[arael(constraint(hb, guard = !self.horizontal, {
2509    [(arc.center.y - point.pos.y - axisdistancearccenterp.distance) * sketch.constraint_isigma]
2510}))]
2511pub struct AxisDistanceArcCenterP {
2512    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2513    #[arael(ref = root.points)] pub point: Ref<Point>,
2514    pub distance: f64,
2515    pub horizontal: bool,
2516    #[serde(default)]
2517    pub nid: u32,
2518    #[arael(constraint_index)]
2519    #[serde(skip)]
2520    pub cid: u32,
2521    #[serde(skip)] pub hb: CrossBlock<Arc, Point>,
2522}
2523
2524#[derive(serde::Serialize, serde::Deserialize)]
2525#[arael::model]
2526#[arael(constraint(hb, guard = self.horizontal, {
2527    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
2528    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2529    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
2530    [(sx - point.pos.x - axisdistancearcstartp.distance) * sketch.constraint_isigma]
2531}))]
2532#[arael(constraint(hb, guard = !self.horizontal, {
2533    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
2534    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2535    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
2536    [(sy - point.pos.y - axisdistancearcstartp.distance) * sketch.constraint_isigma]
2537}))]
2538pub struct AxisDistanceArcStartP {
2539    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2540    #[arael(ref = root.points)] pub point: Ref<Point>,
2541    pub distance: f64,
2542    pub horizontal: bool,
2543    #[serde(default)]
2544    pub nid: u32,
2545    #[arael(constraint_index)]
2546    #[serde(skip)]
2547    pub cid: u32,
2548    #[serde(skip)] pub hb: CrossBlock<Arc, Point>,
2549}
2550
2551#[derive(serde::Serialize, serde::Deserialize)]
2552#[arael::model]
2553#[arael(constraint(hb, guard = self.horizontal, {
2554    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
2555    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2556    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
2557    [(ex - point.pos.x - axisdistancearcendp.distance) * sketch.constraint_isigma]
2558}))]
2559#[arael(constraint(hb, guard = !self.horizontal, {
2560    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
2561    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2562    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
2563    [(ey - point.pos.y - axisdistancearcendp.distance) * sketch.constraint_isigma]
2564}))]
2565pub struct AxisDistanceArcEndP {
2566    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2567    #[arael(ref = root.points)] pub point: Ref<Point>,
2568    pub distance: f64,
2569    pub horizontal: bool,
2570    #[serde(default)]
2571    pub nid: u32,
2572    #[arael(constraint_index)]
2573    #[serde(skip)]
2574    pub cid: u32,
2575    #[serde(skip)] pub hb: CrossBlock<Arc, Point>,
2576}
2577
2578// -- Arc-Line --
2579
2580#[derive(serde::Serialize, serde::Deserialize)]
2581#[arael::model]
2582#[arael(constraint(hb, guard = self.horizontal, {
2583    [(arc.center.x - line.p1.x - axisdistancearccenterl1.distance) * sketch.constraint_isigma]
2584}))]
2585#[arael(constraint(hb, guard = !self.horizontal, {
2586    [(arc.center.y - line.p1.y - axisdistancearccenterl1.distance) * sketch.constraint_isigma]
2587}))]
2588pub struct AxisDistanceArcCenterL1 {
2589    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2590    #[arael(ref = root.lines)] pub line: Ref<Line>,
2591    pub distance: f64,
2592    pub horizontal: bool,
2593    #[serde(default)]
2594    pub nid: u32,
2595    #[arael(constraint_index)]
2596    #[serde(skip)]
2597    pub cid: u32,
2598    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
2599}
2600
2601#[derive(serde::Serialize, serde::Deserialize)]
2602#[arael::model]
2603#[arael(constraint(hb, guard = self.horizontal, {
2604    [(arc.center.x - line.p2.x - axisdistancearccenterl2.distance) * sketch.constraint_isigma]
2605}))]
2606#[arael(constraint(hb, guard = !self.horizontal, {
2607    [(arc.center.y - line.p2.y - axisdistancearccenterl2.distance) * sketch.constraint_isigma]
2608}))]
2609pub struct AxisDistanceArcCenterL2 {
2610    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2611    #[arael(ref = root.lines)] pub line: Ref<Line>,
2612    pub distance: f64,
2613    pub horizontal: bool,
2614    #[serde(default)]
2615    pub nid: u32,
2616    #[arael(constraint_index)]
2617    #[serde(skip)]
2618    pub cid: u32,
2619    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
2620}
2621
2622#[derive(serde::Serialize, serde::Deserialize)]
2623#[arael::model]
2624#[arael(constraint(hb, guard = self.horizontal, {
2625    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
2626    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2627    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
2628    [(sx - line.p1.x - axisdistancearcstartl1.distance) * sketch.constraint_isigma]
2629}))]
2630#[arael(constraint(hb, guard = !self.horizontal, {
2631    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
2632    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2633    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
2634    [(sy - line.p1.y - axisdistancearcstartl1.distance) * sketch.constraint_isigma]
2635}))]
2636pub struct AxisDistanceArcStartL1 {
2637    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2638    #[arael(ref = root.lines)] pub line: Ref<Line>,
2639    pub distance: f64,
2640    pub horizontal: bool,
2641    #[serde(default)]
2642    pub nid: u32,
2643    #[arael(constraint_index)]
2644    #[serde(skip)]
2645    pub cid: u32,
2646    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
2647}
2648
2649#[derive(serde::Serialize, serde::Deserialize)]
2650#[arael::model]
2651#[arael(constraint(hb, guard = self.horizontal, {
2652    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
2653    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2654    let sx = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
2655    [(sx - line.p2.x - axisdistancearcstartl2.distance) * sketch.constraint_isigma]
2656}))]
2657#[arael(constraint(hb, guard = !self.horizontal, {
2658    let ct = cos(arc.start_angle); let st = sin(arc.start_angle);
2659    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2660    let sy = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
2661    [(sy - line.p2.y - axisdistancearcstartl2.distance) * sketch.constraint_isigma]
2662}))]
2663pub struct AxisDistanceArcStartL2 {
2664    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2665    #[arael(ref = root.lines)] pub line: Ref<Line>,
2666    pub distance: f64,
2667    pub horizontal: bool,
2668    #[serde(default)]
2669    pub nid: u32,
2670    #[arael(constraint_index)]
2671    #[serde(skip)]
2672    pub cid: u32,
2673    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
2674}
2675
2676#[derive(serde::Serialize, serde::Deserialize)]
2677#[arael::model]
2678#[arael(constraint(hb, guard = self.horizontal, {
2679    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
2680    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2681    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
2682    [(ex - line.p1.x - axisdistancearcendl1.distance) * sketch.constraint_isigma]
2683}))]
2684#[arael(constraint(hb, guard = !self.horizontal, {
2685    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
2686    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2687    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
2688    [(ey - line.p1.y - axisdistancearcendl1.distance) * sketch.constraint_isigma]
2689}))]
2690pub struct AxisDistanceArcEndL1 {
2691    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2692    #[arael(ref = root.lines)] pub line: Ref<Line>,
2693    pub distance: f64,
2694    pub horizontal: bool,
2695    #[serde(default)]
2696    pub nid: u32,
2697    #[arael(constraint_index)]
2698    #[serde(skip)]
2699    pub cid: u32,
2700    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
2701}
2702
2703#[derive(serde::Serialize, serde::Deserialize)]
2704#[arael::model]
2705#[arael(constraint(hb, guard = self.horizontal, {
2706    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
2707    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2708    let ex = arc.center.x + arc.radius * ct * cr - arc.radius_b * st * sr;
2709    [(ex - line.p2.x - axisdistancearcendl2.distance) * sketch.constraint_isigma]
2710}))]
2711#[arael(constraint(hb, guard = !self.horizontal, {
2712    let ct = cos(arc.end_angle); let st = sin(arc.end_angle);
2713    let cr = cos(arc.rotation); let sr = sin(arc.rotation);
2714    let ey = arc.center.y + arc.radius * ct * sr + arc.radius_b * st * cr;
2715    [(ey - line.p2.y - axisdistancearcendl2.distance) * sketch.constraint_isigma]
2716}))]
2717pub struct AxisDistanceArcEndL2 {
2718    #[arael(ref = root.arcs)] pub arc: Ref<Arc>,
2719    #[arael(ref = root.lines)] pub line: Ref<Line>,
2720    pub distance: f64,
2721    pub horizontal: bool,
2722    #[serde(default)]
2723    pub nid: u32,
2724    #[arael(constraint_index)]
2725    #[serde(skip)]
2726    pub cid: u32,
2727    #[serde(skip)] pub hb: CrossBlock<Arc, Line>,
2728}
2729
2730// -- Arc-Arc --
2731
2732#[derive(serde::Serialize, serde::Deserialize)]
2733#[arael::model]
2734#[arael(constraint(hb, guard = self.horizontal, {
2735    [(a.center.x - b.center.x - axisdistanceaacece.distance) * sketch.constraint_isigma]
2736}))]
2737#[arael(constraint(hb, guard = !self.horizontal, {
2738    [(a.center.y - b.center.y - axisdistanceaacece.distance) * sketch.constraint_isigma]
2739}))]
2740pub struct AxisDistanceAACeCe {
2741    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
2742    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
2743    pub distance: f64,
2744    pub horizontal: bool,
2745    #[serde(default)]
2746    pub nid: u32,
2747    #[arael(constraint_index)]
2748    #[serde(skip)]
2749    pub cid: u32,
2750    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
2751}
2752
2753#[derive(serde::Serialize, serde::Deserialize)]
2754#[arael::model]
2755#[arael(constraint(hb, guard = self.horizontal, {
2756    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
2757    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2758    let bsx = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
2759    [(a.center.x - bsx - axisdistanceaaces.distance) * sketch.constraint_isigma]
2760}))]
2761#[arael(constraint(hb, guard = !self.horizontal, {
2762    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
2763    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2764    let bsy = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
2765    [(a.center.y - bsy - axisdistanceaaces.distance) * sketch.constraint_isigma]
2766}))]
2767pub struct AxisDistanceAACeS {
2768    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
2769    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
2770    pub distance: f64,
2771    pub horizontal: bool,
2772    #[serde(default)]
2773    pub nid: u32,
2774    #[arael(constraint_index)]
2775    #[serde(skip)]
2776    pub cid: u32,
2777    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
2778}
2779
2780#[derive(serde::Serialize, serde::Deserialize)]
2781#[arael::model]
2782#[arael(constraint(hb, guard = self.horizontal, {
2783    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
2784    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2785    let bex = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
2786    [(a.center.x - bex - axisdistanceaacee.distance) * sketch.constraint_isigma]
2787}))]
2788#[arael(constraint(hb, guard = !self.horizontal, {
2789    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
2790    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2791    let bey = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
2792    [(a.center.y - bey - axisdistanceaacee.distance) * sketch.constraint_isigma]
2793}))]
2794pub struct AxisDistanceAACeE {
2795    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
2796    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
2797    pub distance: f64,
2798    pub horizontal: bool,
2799    #[serde(default)]
2800    pub nid: u32,
2801    #[arael(constraint_index)]
2802    #[serde(skip)]
2803    pub cid: u32,
2804    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
2805}
2806
2807#[derive(serde::Serialize, serde::Deserialize)]
2808#[arael::model]
2809#[arael(constraint(hb, guard = self.horizontal, {
2810    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
2811    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2812    let asx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
2813    [(asx - b.center.x - axisdistanceaasce.distance) * sketch.constraint_isigma]
2814}))]
2815#[arael(constraint(hb, guard = !self.horizontal, {
2816    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
2817    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2818    let asy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
2819    [(asy - b.center.y - axisdistanceaasce.distance) * sketch.constraint_isigma]
2820}))]
2821pub struct AxisDistanceAASCe {
2822    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
2823    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
2824    pub distance: f64,
2825    pub horizontal: bool,
2826    #[serde(default)]
2827    pub nid: u32,
2828    #[arael(constraint_index)]
2829    #[serde(skip)]
2830    pub cid: u32,
2831    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
2832}
2833
2834#[derive(serde::Serialize, serde::Deserialize)]
2835#[arael::model]
2836#[arael(constraint(hb, guard = self.horizontal, {
2837    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
2838    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2839    let asx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
2840    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
2841    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2842    let bsx = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
2843    [(asx - bsx - axisdistanceaass.distance) * sketch.constraint_isigma]
2844}))]
2845#[arael(constraint(hb, guard = !self.horizontal, {
2846    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
2847    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2848    let asy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
2849    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
2850    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2851    let bsy = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
2852    [(asy - bsy - axisdistanceaass.distance) * sketch.constraint_isigma]
2853}))]
2854pub struct AxisDistanceAASS {
2855    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
2856    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
2857    pub distance: f64,
2858    pub horizontal: bool,
2859    #[serde(default)]
2860    pub nid: u32,
2861    #[arael(constraint_index)]
2862    #[serde(skip)]
2863    pub cid: u32,
2864    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
2865}
2866
2867#[derive(serde::Serialize, serde::Deserialize)]
2868#[arael::model]
2869#[arael(constraint(hb, guard = self.horizontal, {
2870    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
2871    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2872    let asx = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
2873    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
2874    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2875    let bex = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
2876    [(asx - bex - axisdistanceaase.distance) * sketch.constraint_isigma]
2877}))]
2878#[arael(constraint(hb, guard = !self.horizontal, {
2879    let ct_a = cos(a.start_angle); let st_a = sin(a.start_angle);
2880    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2881    let asy = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
2882    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
2883    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2884    let bey = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
2885    [(asy - bey - axisdistanceaase.distance) * sketch.constraint_isigma]
2886}))]
2887pub struct AxisDistanceAASE {
2888    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
2889    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
2890    pub distance: f64,
2891    pub horizontal: bool,
2892    #[serde(default)]
2893    pub nid: u32,
2894    #[arael(constraint_index)]
2895    #[serde(skip)]
2896    pub cid: u32,
2897    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
2898}
2899
2900#[derive(serde::Serialize, serde::Deserialize)]
2901#[arael::model]
2902#[arael(constraint(hb, guard = self.horizontal, {
2903    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
2904    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2905    let aex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
2906    [(aex - b.center.x - axisdistanceaaece.distance) * sketch.constraint_isigma]
2907}))]
2908#[arael(constraint(hb, guard = !self.horizontal, {
2909    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
2910    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2911    let aey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
2912    [(aey - b.center.y - axisdistanceaaece.distance) * sketch.constraint_isigma]
2913}))]
2914pub struct AxisDistanceAAECe {
2915    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
2916    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
2917    pub distance: f64,
2918    pub horizontal: bool,
2919    #[serde(default)]
2920    pub nid: u32,
2921    #[arael(constraint_index)]
2922    #[serde(skip)]
2923    pub cid: u32,
2924    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
2925}
2926
2927#[derive(serde::Serialize, serde::Deserialize)]
2928#[arael::model]
2929#[arael(constraint(hb, guard = self.horizontal, {
2930    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
2931    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2932    let aex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
2933    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
2934    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2935    let bsx = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
2936    [(aex - bsx - axisdistanceaaes.distance) * sketch.constraint_isigma]
2937}))]
2938#[arael(constraint(hb, guard = !self.horizontal, {
2939    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
2940    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2941    let aey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
2942    let ct_b = cos(b.start_angle); let st_b = sin(b.start_angle);
2943    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2944    let bsy = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
2945    [(aey - bsy - axisdistanceaaes.distance) * sketch.constraint_isigma]
2946}))]
2947pub struct AxisDistanceAAES {
2948    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
2949    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
2950    pub distance: f64,
2951    pub horizontal: bool,
2952    #[serde(default)]
2953    pub nid: u32,
2954    #[arael(constraint_index)]
2955    #[serde(skip)]
2956    pub cid: u32,
2957    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
2958}
2959
2960#[derive(serde::Serialize, serde::Deserialize)]
2961#[arael::model]
2962#[arael(constraint(hb, guard = self.horizontal, {
2963    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
2964    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2965    let aex = a.center.x + a.radius * ct_a * cr_a - a.radius_b * st_a * sr_a;
2966    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
2967    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2968    let bex = b.center.x + b.radius * ct_b * cr_b - b.radius_b * st_b * sr_b;
2969    [(aex - bex - axisdistanceaaee.distance) * sketch.constraint_isigma]
2970}))]
2971#[arael(constraint(hb, guard = !self.horizontal, {
2972    let ct_a = cos(a.end_angle); let st_a = sin(a.end_angle);
2973    let cr_a = cos(a.rotation); let sr_a = sin(a.rotation);
2974    let aey = a.center.y + a.radius * ct_a * sr_a + a.radius_b * st_a * cr_a;
2975    let ct_b = cos(b.end_angle); let st_b = sin(b.end_angle);
2976    let cr_b = cos(b.rotation); let sr_b = sin(b.rotation);
2977    let bey = b.center.y + b.radius * ct_b * sr_b + b.radius_b * st_b * cr_b;
2978    [(aey - bey - axisdistanceaaee.distance) * sketch.constraint_isigma]
2979}))]
2980pub struct AxisDistanceAAEE {
2981    #[arael(ref = root.arcs)] pub a: Ref<Arc>,
2982    #[arael(ref = root.arcs)] pub b: Ref<Arc>,
2983    pub distance: f64,
2984    pub horizontal: bool,
2985    #[serde(default)]
2986    pub nid: u32,
2987    #[arael(constraint_index)]
2988    #[serde(skip)]
2989    pub cid: u32,
2990    #[serde(skip)] pub hb: CrossBlock<Arc, Arc>,
2991}
2992