Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 24 kB View raw
1use bone_document::{Sketch, SketchEntity, SketchEntityKind, SketchRelation}; 2use bone_types::{IconId, SketchEntityId}; 3use bone_ui::strings::StringKey; 4 5use crate::strings; 6 7#[derive(Copy, Clone, Debug, PartialEq, Eq)] 8pub enum RelationKind { 9 Coincident, 10 Horizontal, 11 Vertical, 12 Parallel, 13 Perpendicular, 14 Tangent, 15 Equal, 16 Concentric, 17 Midpoint, 18 Symmetric, 19 Fix, 20} 21 22#[derive(Clone, Debug, PartialEq)] 23pub enum Eligibility { 24 Eligible(SketchRelation), 25 Disabled(StringKey), 26} 27 28#[derive(Copy, Clone)] 29struct RelationDescriptor { 30 key: &'static str, 31 label: StringKey, 32 icon: IconId, 33 check: fn(&Sketch, &[SketchEntityId]) -> Eligibility, 34} 35 36impl RelationKind { 37 pub const ALL: &'static [Self] = &[ 38 Self::Coincident, 39 Self::Horizontal, 40 Self::Vertical, 41 Self::Parallel, 42 Self::Perpendicular, 43 Self::Tangent, 44 Self::Equal, 45 Self::Concentric, 46 Self::Midpoint, 47 Self::Symmetric, 48 Self::Fix, 49 ]; 50 51 const fn descriptor(self) -> RelationDescriptor { 52 match self { 53 Self::Coincident => RelationDescriptor { 54 key: "rel.coincident", 55 label: strings::TOOL_COINCIDENT, 56 icon: IconId::Coincident, 57 check: coincident, 58 }, 59 Self::Horizontal => RelationDescriptor { 60 key: "rel.horizontal", 61 label: strings::TOOL_HORIZONTAL, 62 icon: IconId::Horizontal, 63 check: horizontal, 64 }, 65 Self::Vertical => RelationDescriptor { 66 key: "rel.vertical", 67 label: strings::TOOL_VERTICAL, 68 icon: IconId::Vertical, 69 check: vertical, 70 }, 71 Self::Parallel => RelationDescriptor { 72 key: "rel.parallel", 73 label: strings::TOOL_PARALLEL, 74 icon: IconId::Parallel, 75 check: parallel, 76 }, 77 Self::Perpendicular => RelationDescriptor { 78 key: "rel.perpendicular", 79 label: strings::TOOL_PERPENDICULAR, 80 icon: IconId::Perpendicular, 81 check: perpendicular, 82 }, 83 Self::Tangent => RelationDescriptor { 84 key: "rel.tangent", 85 label: strings::TOOL_TANGENT, 86 icon: IconId::Tangent, 87 check: tangent, 88 }, 89 Self::Equal => RelationDescriptor { 90 key: "rel.equal", 91 label: strings::TOOL_EQUAL, 92 icon: IconId::Equal, 93 check: equal, 94 }, 95 Self::Concentric => RelationDescriptor { 96 key: "rel.concentric", 97 label: strings::TOOL_CONCENTRIC, 98 icon: IconId::Concentric, 99 check: concentric, 100 }, 101 Self::Midpoint => RelationDescriptor { 102 key: "rel.midpoint", 103 label: strings::TOOL_MIDPOINT, 104 icon: IconId::Midpoint, 105 check: midpoint, 106 }, 107 Self::Symmetric => RelationDescriptor { 108 key: "rel.symmetric", 109 label: strings::TOOL_SYMMETRIC, 110 icon: IconId::Symmetric, 111 check: symmetric, 112 }, 113 Self::Fix => RelationDescriptor { 114 key: "rel.fix", 115 label: strings::TOOL_FIX, 116 icon: IconId::Fix, 117 check: fix, 118 }, 119 } 120 } 121 122 #[must_use] 123 pub const fn key(self) -> &'static str { 124 self.descriptor().key 125 } 126 127 #[must_use] 128 pub const fn label(self) -> StringKey { 129 self.descriptor().label 130 } 131 132 #[must_use] 133 pub const fn icon(self) -> IconId { 134 self.descriptor().icon 135 } 136} 137 138#[must_use] 139pub fn eligibility( 140 kind: RelationKind, 141 sketch: &Sketch, 142 selection: &[SketchEntityId], 143) -> Eligibility { 144 (kind.descriptor().check)(sketch, selection) 145} 146 147fn coincident(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 148 pair_eligibility( 149 sketch, 150 selection, 151 strings::REL_HINT_COINCIDENT, 152 |a, b| a == SketchEntityKind::Point || b == SketchEntityKind::Point, 153 SketchRelation::Coincident, 154 ) 155} 156 157fn horizontal(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 158 single_eligibility( 159 sketch, 160 selection, 161 strings::REL_HINT_ONE_LINE, 162 |k| k == SketchEntityKind::Line, 163 SketchRelation::Horizontal, 164 ) 165} 166 167fn vertical(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 168 single_eligibility( 169 sketch, 170 selection, 171 strings::REL_HINT_ONE_LINE, 172 |k| k == SketchEntityKind::Line, 173 SketchRelation::Vertical, 174 ) 175} 176 177fn parallel(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 178 pair_eligibility( 179 sketch, 180 selection, 181 strings::REL_HINT_TWO_LINES, 182 both_lines, 183 SketchRelation::Parallel, 184 ) 185} 186 187fn perpendicular(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 188 pair_eligibility( 189 sketch, 190 selection, 191 strings::REL_HINT_TWO_LINES, 192 both_lines, 193 SketchRelation::Perpendicular, 194 ) 195} 196 197fn tangent(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 198 pair_eligibility( 199 sketch, 200 selection, 201 strings::REL_HINT_TANGENT, 202 |a, b| !is_point(a) && !is_point(b) && (is_curved(a) || is_curved(b)), 203 SketchRelation::Tangent, 204 ) 205} 206 207fn equal(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 208 pair_eligibility( 209 sketch, 210 selection, 211 strings::REL_HINT_EQUAL, 212 |a, b| both_lines(a, b) || (is_curved(a) && is_curved(b)), 213 SketchRelation::Equal, 214 ) 215} 216 217fn concentric(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 218 pair_eligibility( 219 sketch, 220 selection, 221 strings::REL_HINT_TWO_CIRCULAR, 222 |a, b| is_curved(a) && is_curved(b), 223 SketchRelation::Concentric, 224 ) 225} 226 227fn midpoint(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 228 let disabled = || Eligibility::Disabled(strings::REL_HINT_MIDPOINT); 229 let Some((a, ka, b, kb)) = pair_kinds(sketch, selection) else { 230 return disabled(); 231 }; 232 let (point, line) = match (ka, kb) { 233 (SketchEntityKind::Point, SketchEntityKind::Line) => (a, b), 234 (SketchEntityKind::Line, SketchEntityKind::Point) => (b, a), 235 _ => return disabled(), 236 }; 237 let Some(SketchEntity::Line(host)) = sketch.entities().get(line) else { 238 return disabled(); 239 }; 240 if host.a() == point || host.b() == point { 241 return disabled(); 242 } 243 Eligibility::Eligible(SketchRelation::Midpoint { point, line }) 244} 245 246fn fix(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 247 single_eligibility( 248 sketch, 249 selection, 250 strings::REL_HINT_ENTITY, 251 |_| true, 252 SketchRelation::Fix, 253 ) 254} 255 256fn symmetric(sketch: &Sketch, selection: &[SketchEntityId]) -> Eligibility { 257 let disabled = || Eligibility::Disabled(strings::REL_HINT_SYMMETRIC); 258 if selection.len() != 3 { 259 return disabled(); 260 } 261 let kinds: Vec<(SketchEntityId, SketchEntityKind)> = selection 262 .iter() 263 .filter_map(|id| sketch.entities().get(*id).map(|e| (*id, e.kind()))) 264 .collect(); 265 if kinds.len() != 3 { 266 return disabled(); 267 } 268 let points: Vec<SketchEntityId> = kinds 269 .iter() 270 .filter(|(_, k)| *k == SketchEntityKind::Point) 271 .map(|(id, _)| *id) 272 .collect(); 273 let lines: Vec<SketchEntityId> = kinds 274 .iter() 275 .filter(|(_, k)| *k == SketchEntityKind::Line) 276 .map(|(id, _)| *id) 277 .collect(); 278 let [a, b] = points.as_slice() else { 279 return disabled(); 280 }; 281 let [axis] = lines.as_slice() else { 282 return disabled(); 283 }; 284 Eligibility::Eligible(SketchRelation::Symmetric { 285 a: *a, 286 b: *b, 287 axis: *axis, 288 }) 289} 290 291fn pair_eligibility( 292 sketch: &Sketch, 293 selection: &[SketchEntityId], 294 hint: StringKey, 295 predicate: impl Fn(SketchEntityKind, SketchEntityKind) -> bool, 296 ctor: impl Fn(SketchEntityId, SketchEntityId) -> SketchRelation, 297) -> Eligibility { 298 match pair_kinds(sketch, selection) { 299 Some((a, ka, b, kb)) if predicate(ka, kb) => Eligibility::Eligible(ctor(a, b)), 300 _ => Eligibility::Disabled(hint), 301 } 302} 303 304fn single_eligibility( 305 sketch: &Sketch, 306 selection: &[SketchEntityId], 307 hint: StringKey, 308 predicate: impl Fn(SketchEntityKind) -> bool, 309 ctor: impl Fn(SketchEntityId) -> SketchRelation, 310) -> Eligibility { 311 match selection { 312 [id] => entity_kind(sketch, *id) 313 .filter(|k| predicate(*k)) 314 .map_or(Eligibility::Disabled(hint), |_| { 315 Eligibility::Eligible(ctor(*id)) 316 }), 317 _ => Eligibility::Disabled(hint), 318 } 319} 320 321fn pair_kinds( 322 sketch: &Sketch, 323 selection: &[SketchEntityId], 324) -> Option<( 325 SketchEntityId, 326 SketchEntityKind, 327 SketchEntityId, 328 SketchEntityKind, 329)> { 330 let [a, b] = selection else { return None }; 331 if a == b { 332 return None; 333 } 334 let ka = entity_kind(sketch, *a)?; 335 let kb = entity_kind(sketch, *b)?; 336 Some((*a, ka, *b, kb)) 337} 338 339fn entity_kind(sketch: &Sketch, id: SketchEntityId) -> Option<SketchEntityKind> { 340 sketch.entities().get(id).map(SketchEntity::kind) 341} 342 343const fn is_curved(kind: SketchEntityKind) -> bool { 344 matches!(kind, SketchEntityKind::Arc | SketchEntityKind::Circle) 345} 346 347const fn is_point(kind: SketchEntityKind) -> bool { 348 matches!(kind, SketchEntityKind::Point) 349} 350 351const fn both_lines(a: SketchEntityKind, b: SketchEntityKind) -> bool { 352 matches!((a, b), (SketchEntityKind::Line, SketchEntityKind::Line)) 353} 354 355#[cfg(test)] 356mod tests { 357 use super::*; 358 use crate::sketch_mode::Plane; 359 use crate::tools::{add_arc, add_circle, add_line, add_point}; 360 use bone_types::{Length, Point2}; 361 use uom::si::length::millimeter; 362 363 fn fresh() -> Sketch { 364 Sketch::new(Plane::Xy.basis()) 365 } 366 367 #[test] 368 fn relation_kind_all_matches_descriptor_arms() { 369 assert_eq!(RelationKind::ALL.len(), 11); 370 let keys: std::collections::BTreeSet<_> = 371 RelationKind::ALL.iter().map(|k| k.key()).collect(); 372 assert_eq!(keys.len(), 11, "every kind has a unique widget key"); 373 } 374 375 #[test] 376 fn horizontal_eligible_on_single_line() { 377 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 378 let (s, p1) = add_point(s, Point2::from_mm(5.0, 0.0)); 379 let (s, line) = add_line(s, p0, p1, false); 380 assert_eq!( 381 eligibility(RelationKind::Horizontal, &s, &[line]), 382 Eligibility::Eligible(SketchRelation::Horizontal(line)), 383 ); 384 } 385 386 #[test] 387 fn horizontal_disabled_on_point_selection() { 388 let (s, p) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 389 assert_eq!( 390 eligibility(RelationKind::Horizontal, &s, &[p]), 391 Eligibility::Disabled(strings::REL_HINT_ONE_LINE), 392 ); 393 } 394 395 #[test] 396 fn horizontal_disabled_on_empty_selection() { 397 assert_eq!( 398 eligibility(RelationKind::Horizontal, &fresh(), &[]), 399 Eligibility::Disabled(strings::REL_HINT_ONE_LINE), 400 ); 401 } 402 403 #[test] 404 fn vertical_eligible_on_single_line() { 405 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 406 let (s, p1) = add_point(s, Point2::from_mm(0.0, 5.0)); 407 let (s, line) = add_line(s, p0, p1, false); 408 assert_eq!( 409 eligibility(RelationKind::Vertical, &s, &[line]), 410 Eligibility::Eligible(SketchRelation::Vertical(line)), 411 ); 412 } 413 414 #[test] 415 fn fix_eligible_on_any_single_entity() { 416 let (s, p) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 417 assert_eq!( 418 eligibility(RelationKind::Fix, &s, &[p]), 419 Eligibility::Eligible(SketchRelation::Fix(p)), 420 ); 421 } 422 423 #[test] 424 fn fix_eligible_on_line_arc_circle() { 425 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 426 let (s, p1) = add_point(s, Point2::from_mm(5.0, 0.0)); 427 let (s, p2) = add_point(s, Point2::from_mm(0.0, 5.0)); 428 let (s, line) = add_line(s, p0, p1, false); 429 let (s, circle) = add_circle(s, p0, Length::new::<millimeter>(2.0), false); 430 let (s, arc) = add_arc(s, p0, p1, p2, false); 431 [line, circle, arc].into_iter().for_each(|id| { 432 assert_eq!( 433 eligibility(RelationKind::Fix, &s, &[id]), 434 Eligibility::Eligible(SketchRelation::Fix(id)), 435 ); 436 }); 437 } 438 439 #[test] 440 fn fix_disabled_on_empty_or_pair() { 441 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 442 let (s, p1) = add_point(s, Point2::from_mm(1.0, 0.0)); 443 assert_eq!( 444 eligibility(RelationKind::Fix, &s, &[]), 445 Eligibility::Disabled(strings::REL_HINT_ENTITY), 446 ); 447 assert_eq!( 448 eligibility(RelationKind::Fix, &s, &[p0, p1]), 449 Eligibility::Disabled(strings::REL_HINT_ENTITY), 450 ); 451 } 452 453 #[test] 454 fn coincident_eligible_on_two_points() { 455 let (s, a) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 456 let (s, b) = add_point(s, Point2::from_mm(1.0, 0.0)); 457 assert_eq!( 458 eligibility(RelationKind::Coincident, &s, &[a, b]), 459 Eligibility::Eligible(SketchRelation::Coincident(a, b)), 460 ); 461 } 462 463 #[test] 464 fn coincident_eligible_on_point_and_line() { 465 let (s, a) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 466 let (s, b) = add_point(s, Point2::from_mm(5.0, 0.0)); 467 let (s, line) = add_line(s, a, b, false); 468 let (s, q) = add_point(s, Point2::from_mm(2.5, 0.0)); 469 assert_eq!( 470 eligibility(RelationKind::Coincident, &s, &[q, line]), 471 Eligibility::Eligible(SketchRelation::Coincident(q, line)), 472 ); 473 } 474 475 #[test] 476 fn coincident_eligible_on_point_and_circle() { 477 let (s, c) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 478 let (s, circle) = add_circle(s, c, Length::new::<millimeter>(2.0), false); 479 let (s, q) = add_point(s, Point2::from_mm(1.5, 1.5)); 480 assert_eq!( 481 eligibility(RelationKind::Coincident, &s, &[q, circle]), 482 Eligibility::Eligible(SketchRelation::Coincident(q, circle)), 483 ); 484 } 485 486 #[test] 487 fn coincident_disabled_on_two_lines() { 488 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 489 let (s, p1) = add_point(s, Point2::from_mm(1.0, 0.0)); 490 let (s, p2) = add_point(s, Point2::from_mm(0.0, 1.0)); 491 let (s, p3) = add_point(s, Point2::from_mm(1.0, 1.0)); 492 let (s, l1) = add_line(s, p0, p1, false); 493 let (s, l2) = add_line(s, p2, p3, false); 494 assert_eq!( 495 eligibility(RelationKind::Coincident, &s, &[l1, l2]), 496 Eligibility::Disabled(strings::REL_HINT_COINCIDENT), 497 ); 498 } 499 500 #[test] 501 fn parallel_eligible_on_two_lines() { 502 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 503 let (s, p1) = add_point(s, Point2::from_mm(1.0, 0.0)); 504 let (s, p2) = add_point(s, Point2::from_mm(0.0, 1.0)); 505 let (s, p3) = add_point(s, Point2::from_mm(1.0, 1.0)); 506 let (s, l1) = add_line(s, p0, p1, false); 507 let (s, l2) = add_line(s, p2, p3, false); 508 assert_eq!( 509 eligibility(RelationKind::Parallel, &s, &[l1, l2]), 510 Eligibility::Eligible(SketchRelation::Parallel(l1, l2)), 511 ); 512 } 513 514 #[test] 515 fn perpendicular_disabled_on_line_and_circle() { 516 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 517 let (s, p1) = add_point(s, Point2::from_mm(1.0, 0.0)); 518 let (s, line) = add_line(s, p0, p1, false); 519 let (s, circle) = add_circle(s, p0, Length::new::<millimeter>(2.0), false); 520 assert_eq!( 521 eligibility(RelationKind::Perpendicular, &s, &[line, circle]), 522 Eligibility::Disabled(strings::REL_HINT_TWO_LINES), 523 ); 524 } 525 526 #[test] 527 fn tangent_eligible_on_line_and_circle() { 528 let (s, c) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 529 let (s, p1) = add_point(s, Point2::from_mm(5.0, 0.0)); 530 let (s, p2) = add_point(s, Point2::from_mm(5.0, 3.0)); 531 let (s, line) = add_line(s, p1, p2, false); 532 let (s, circle) = add_circle(s, c, Length::new::<millimeter>(2.0), false); 533 assert_eq!( 534 eligibility(RelationKind::Tangent, &s, &[line, circle]), 535 Eligibility::Eligible(SketchRelation::Tangent(line, circle)), 536 ); 537 assert_eq!( 538 eligibility(RelationKind::Tangent, &s, &[circle, line]), 539 Eligibility::Eligible(SketchRelation::Tangent(circle, line)), 540 ); 541 } 542 543 #[test] 544 fn tangent_eligible_on_two_circles() { 545 let (s, c1) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 546 let (s, c2) = add_point(s, Point2::from_mm(10.0, 0.0)); 547 let (s, k1) = add_circle(s, c1, Length::new::<millimeter>(3.0), false); 548 let (s, k2) = add_circle(s, c2, Length::new::<millimeter>(3.0), false); 549 assert_eq!( 550 eligibility(RelationKind::Tangent, &s, &[k1, k2]), 551 Eligibility::Eligible(SketchRelation::Tangent(k1, k2)), 552 ); 553 } 554 555 #[test] 556 fn tangent_disabled_on_two_points() { 557 let (s, a) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 558 let (s, b) = add_point(s, Point2::from_mm(1.0, 0.0)); 559 assert_eq!( 560 eligibility(RelationKind::Tangent, &s, &[a, b]), 561 Eligibility::Disabled(strings::REL_HINT_TANGENT), 562 ); 563 } 564 565 #[test] 566 fn equal_eligible_on_two_lines() { 567 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 568 let (s, p1) = add_point(s, Point2::from_mm(1.0, 0.0)); 569 let (s, p2) = add_point(s, Point2::from_mm(0.0, 1.0)); 570 let (s, p3) = add_point(s, Point2::from_mm(1.0, 1.0)); 571 let (s, l1) = add_line(s, p0, p1, false); 572 let (s, l2) = add_line(s, p2, p3, false); 573 assert_eq!( 574 eligibility(RelationKind::Equal, &s, &[l1, l2]), 575 Eligibility::Eligible(SketchRelation::Equal(l1, l2)), 576 ); 577 } 578 579 #[test] 580 fn equal_eligible_on_two_circles() { 581 let (s, c1) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 582 let (s, c2) = add_point(s, Point2::from_mm(10.0, 0.0)); 583 let (s, k1) = add_circle(s, c1, Length::new::<millimeter>(3.0), false); 584 let (s, k2) = add_circle(s, c2, Length::new::<millimeter>(2.0), false); 585 assert_eq!( 586 eligibility(RelationKind::Equal, &s, &[k1, k2]), 587 Eligibility::Eligible(SketchRelation::Equal(k1, k2)), 588 ); 589 } 590 591 #[test] 592 fn equal_disabled_on_line_and_circle() { 593 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 594 let (s, p1) = add_point(s, Point2::from_mm(1.0, 0.0)); 595 let (s, line) = add_line(s, p0, p1, false); 596 let (s, circle) = add_circle(s, p0, Length::new::<millimeter>(2.0), false); 597 assert_eq!( 598 eligibility(RelationKind::Equal, &s, &[line, circle]), 599 Eligibility::Disabled(strings::REL_HINT_EQUAL), 600 ); 601 } 602 603 #[test] 604 fn concentric_eligible_on_circle_and_arc() { 605 let (s, c1) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 606 let (s, ks) = add_point(s, Point2::from_mm(3.0, 0.0)); 607 let (s, ke) = add_point(s, Point2::from_mm(0.0, 3.0)); 608 let (s, arc) = add_arc(s, c1, ks, ke, false); 609 let (s, circle) = add_circle(s, c1, Length::new::<millimeter>(2.0), false); 610 assert_eq!( 611 eligibility(RelationKind::Concentric, &s, &[arc, circle]), 612 Eligibility::Eligible(SketchRelation::Concentric(arc, circle)), 613 ); 614 } 615 616 #[test] 617 fn concentric_eligible_on_two_circles() { 618 let (s, c1) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 619 let (s, c2) = add_point(s, Point2::from_mm(10.0, 0.0)); 620 let (s, k1) = add_circle(s, c1, Length::new::<millimeter>(3.0), false); 621 let (s, k2) = add_circle(s, c2, Length::new::<millimeter>(2.0), false); 622 assert_eq!( 623 eligibility(RelationKind::Concentric, &s, &[k1, k2]), 624 Eligibility::Eligible(SketchRelation::Concentric(k1, k2)), 625 ); 626 } 627 628 #[test] 629 fn midpoint_eligible_in_either_order() { 630 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 631 let (s, p1) = add_point(s, Point2::from_mm(10.0, 0.0)); 632 let (s, line) = add_line(s, p0, p1, false); 633 let (s, q) = add_point(s, Point2::from_mm(5.0, 0.0)); 634 assert_eq!( 635 eligibility(RelationKind::Midpoint, &s, &[q, line]), 636 Eligibility::Eligible(SketchRelation::Midpoint { point: q, line }), 637 ); 638 assert_eq!( 639 eligibility(RelationKind::Midpoint, &s, &[line, q]), 640 Eligibility::Eligible(SketchRelation::Midpoint { point: q, line }), 641 ); 642 } 643 644 #[test] 645 fn midpoint_disabled_on_two_points() { 646 let (s, a) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 647 let (s, b) = add_point(s, Point2::from_mm(1.0, 0.0)); 648 assert_eq!( 649 eligibility(RelationKind::Midpoint, &s, &[a, b]), 650 Eligibility::Disabled(strings::REL_HINT_MIDPOINT), 651 ); 652 } 653 654 #[test] 655 fn midpoint_disabled_when_point_is_line_endpoint() { 656 let (s, a) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 657 let (s, b) = add_point(s, Point2::from_mm(10.0, 0.0)); 658 let (s, line) = add_line(s, a, b, false); 659 assert_eq!( 660 eligibility(RelationKind::Midpoint, &s, &[a, line]), 661 Eligibility::Disabled(strings::REL_HINT_MIDPOINT), 662 ); 663 assert_eq!( 664 eligibility(RelationKind::Midpoint, &s, &[line, b]), 665 Eligibility::Disabled(strings::REL_HINT_MIDPOINT), 666 ); 667 } 668 669 #[test] 670 fn tangent_disabled_on_two_lines() { 671 let (s, p0) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 672 let (s, p1) = add_point(s, Point2::from_mm(1.0, 0.0)); 673 let (s, p2) = add_point(s, Point2::from_mm(0.0, 1.0)); 674 let (s, p3) = add_point(s, Point2::from_mm(1.0, 1.0)); 675 let (s, l1) = add_line(s, p0, p1, false); 676 let (s, l2) = add_line(s, p2, p3, false); 677 assert_eq!( 678 eligibility(RelationKind::Tangent, &s, &[l1, l2]), 679 Eligibility::Disabled(strings::REL_HINT_TANGENT), 680 ); 681 } 682 683 #[test] 684 fn pair_relations_reject_same_id_twice() { 685 let (s, p) = add_point(fresh(), Point2::from_mm(0.0, 0.0)); 686 let cases = [ 687 (RelationKind::Coincident, strings::REL_HINT_COINCIDENT), 688 (RelationKind::Parallel, strings::REL_HINT_TWO_LINES), 689 (RelationKind::Perpendicular, strings::REL_HINT_TWO_LINES), 690 (RelationKind::Tangent, strings::REL_HINT_TANGENT), 691 (RelationKind::Equal, strings::REL_HINT_EQUAL), 692 (RelationKind::Concentric, strings::REL_HINT_TWO_CIRCULAR), 693 (RelationKind::Midpoint, strings::REL_HINT_MIDPOINT), 694 ]; 695 cases.into_iter().for_each(|(kind, hint)| { 696 assert_eq!( 697 eligibility(kind, &s, &[p, p]), 698 Eligibility::Disabled(hint), 699 "{kind:?} must reject [a, a]", 700 ); 701 }); 702 } 703}