1#![allow(
12 clippy::expect_used,
13 reason = "an example that does not build is a bug in it"
14)]
15
16use std::collections::BTreeMap;
17
18use geop_core_editor::registry::{Example, ExampleFiles, ExampleSet};
19use geop_core_math::{
20 primitives::{FrameAxis, Pose},
21 vector::Vector3,
22};
23use geop_core_program::{
24 operation_registry::OperationRegistry,
25 part::Part,
26 program::Program,
27 runner::Runner,
28 values::{Setting, Settings, StateValue, StateValues, pose_state},
29};
30use geop_core_sketch::CurveId;
31use geop_lib_stdlib::{Drawing, outline_plane};
32use geop_ops_extrude_revolve::{
33 Extent, Extents, Extrude, ExtrudeArgs, Output,
34 examples::{box_with_drill_hole, parametric_plate},
35};
36use geop_ops_model::datums::{datum, origin_plane};
37use geop_ops_model::parameters::{Material, Parameters, set_parameters};
38use geop_ops_model::selection::Selected;
39use geop_ops_model::sketch::{
40 Sketch, SketchPlane,
41 figures::{circle, n, rectangle, solved},
42 whole_sketch,
43};
44use geop_ops_model::{Combine, PartDescription, element, read_model};
45use geop_ops_sketch::{AddSketch, AddSketchArgs};
46
47use crate::{
48 AddPartArgs, ExplodeArgs, Joint, JointKind, Kind, Limits, Mate, Motion, MotionArgs,
49 joint_parameter,
50};
51use geop_core_math::primitives::DatumComponent;
52use geop_core_program::element::ElementReference;
53use geop_ops_model::datums::{ORIGIN, component_name, datums};
54use geop_ops_model::mate_entity::MateEntity;
55use geop_ops_model::placed::motion::MotionStudy;
56
57fn on_origin(normal: FrameAxis) -> SketchPlane {
59 SketchPlane::Datum(origin_plane(normal))
60}
61
62pub fn pose(position: [f64; 3], degrees: [f64; 3]) -> Pose {
67 (Pose::from_euler(Vector3::from_array(position.map(n)), degrees.map(n)))
68 .and_then(|pose| pose.saved())
69 .expect("an example's rotation is one")
70}
71
72fn built(program: &Program) -> Part {
75 let registry = (OperationRegistry::default().with(AddSketch))
76 .and_then(|r| r.with(Extrude))
77 .expect("the operations each have a name of their own");
78 let mut part = Part::default();
79 (program.run(&Runner::new(registry), &mut part)).expect("the part builds");
80 part
81}
82
83pub fn pin() -> Program {
86 let mut program = Program::new();
87 let mut sketch = Sketch::new();
88 circle(&mut sketch, [0.0, 0.0], 0.4);
89 program.push(
90 "pin_sketch",
91 AddSketchArgs::on(on_origin(FrameAxis::Z), solved(sketch)),
92 );
93 program.push(
94 "pin",
95 ExtrudeArgs {
96 sketch: whole_sketch("pin_sketch"),
97 extent: Extents::blind(2.0),
98 output: Output::Solid(Combine::NewBody),
99 draft: 0.0,
100 },
101 );
102 program
103}
104
105pub fn pin_in_plate_assembly() -> Program {
110 let mut program = Program::new();
111 program.push(
112 "plate",
113 AddPartArgs {
114 file: "plate.geop".into(),
115 ..Default::default()
116 }
117 .fixed(),
118 );
119 let mate = |kind, [a, b]: [&str; 2]| {
120 Mate::constraint(kind, vec![placed_face("pin", a), placed_face("plate", b)])
121 };
122 program.push(
123 "pin",
124 AddPartArgs {
125 file: "pin.geop".into(),
126 mates: BTreeMap::from([
127 (
128 "m1".into(),
129 mate(
130 Kind::Concentric,
131 ["extrude(pin,pin_sketch,c1)", "extrude(hole,hole_sketch,c1)"],
132 ),
133 ),
134 (
135 "m2".into(),
136 mate(
137 Kind::Coincident,
138 ["extrude(pin,start)", "extrude(hole,end)"],
139 ),
140 ),
141 ]),
142 ..Default::default()
143 },
144 );
145 let pose = |position| StateValue::Pose(pose(position, [0.0; 3]));
146 program.state = StateValues::from([
147 (pose_state("plate"), pose([0.0; 3])),
148 (pose_state("pin"), pose([1.0, 1.0, 0.5])),
149 ]);
150 program
151}
152
153fn link_sketch(length: f64) -> (Sketch, [CurveId; 2]) {
160 let mut sketch = Sketch::new();
161 rectangle(&mut sketch, [-0.5, -0.5], length + 1.0, 1.0);
162 let near = circle(&mut sketch, [0.0, 0.0], 0.3);
163 let far = circle(&mut sketch, [length, 0.0], 0.3);
164 (solved(sketch), [near, far])
165}
166
167pub fn bar(length: f64) -> Program {
170 let mut program = Program::new();
171 set_parameters(
172 &mut program,
173 Parameters {
174 material: Material::named("Aluminium 6061"),
175 ..Parameters::default()
176 },
177 );
178 program.push(
179 "link_sketch",
180 AddSketchArgs::on(on_origin(FrameAxis::Z), link_sketch(length).0),
181 );
182 program.push(
183 "link",
184 ExtrudeArgs {
185 sketch: whole_sketch("link_sketch"),
186 extent: Extents::blind(0.2),
187 output: Output::Solid(Combine::NewBody),
188 draft: 0.0,
189 },
190 );
191 program
192}
193
194pub fn link() -> Program {
196 bar(3.0)
197}
198
199fn placed_face(instance: &str, name: impl Into<String>) -> MateEntity {
201 MateEntity::of(instance, Selected::Face(element(name)))
202}
203
204pub fn link_holes(instance: &str) -> [MateEntity; 2] {
206 (link_sketch(3.0).1)
207 .map(|hole| placed_face(instance, format!("extrude(link,link_sketch,{hole})")))
208}
209
210fn on_top(above: &str, below: &str) -> Mate {
214 Mate::constraint(
215 Kind::Coincident,
216 vec![
217 placed_face(above, "extrude(link,start)"),
218 placed_face(below, "extrude(link,end)"),
219 ],
220 )
221}
222
223fn pinned(link: &str, hole: usize, on: &str, pin: usize) -> Mate {
226 Mate::constraint(
227 Kind::Concentric,
228 vec![link_holes(on)[pin].clone(), link_holes(link)[hole].clone()],
229 )
230}
231
232fn placed(file: &str, fixed: bool, mates: Vec<Mate>) -> AddPartArgs {
234 let args = AddPartArgs {
235 file: file.into(),
236 mates: mates
237 .into_iter()
238 .enumerate()
239 .map(|(i, mate)| (format!("m{}", i + 1), mate))
240 .collect(),
241 ..Default::default()
242 };
243 if fixed { args.fixed() } else { args }
244}
245
246pub fn chain_assembly() -> Program {
250 let mut program = Program::new();
251 program.push("link1", placed("link.geop", true, Vec::new()));
252 for (before, link) in [("link1", "link2"), ("link2", "link3")] {
253 program.push(
254 link,
255 placed(
256 "link.geop",
257 false,
258 vec![pinned(link, 0, before, 1), on_top(link, before)],
259 ),
260 );
261 }
262 let pose = |x, z| StateValue::Pose(pose([x, 0.0, z], [0.0; 3]));
263 program.state = StateValues::from([
264 (pose_state("link1"), pose(0.0, 0.0)),
265 (pose_state("link2"), pose(3.0, 0.2)),
266 (pose_state("link3"), pose(6.0, 0.4)),
267 ]);
268 program
269}
270
271pub fn link_rim(instance: &str, hole: usize, cap: &str) -> MateEntity {
276 let name = format!(
277 "extrude(link,link_sketch,{},{cap})",
278 link_sketch(3.0).1[hole]
279 );
280 MateEntity::of(instance, Selected::Edge(element(name)))
281}
282
283fn hinged(link: &str, on: &str, min: f64, max: f64) -> Mate {
288 Mate::joint(
289 JointKind::new(Joint::Revolute).limited(
290 Motion::Turn,
291 Limits {
292 min: Some(n(min)),
293 max: Some(n(max)),
294 },
295 ),
296 vec![link_rim(on, 1, "end"), link_rim(link, 0, "start")],
297 )
298}
299
300pub fn arm_assembly() -> Program {
306 let mut program = Program::new();
307 program.push("upper", placed("link.geop", true, Vec::new()));
308 program.push(
309 "fore",
310 placed(
311 "link.geop",
312 false,
313 vec![hinged("fore", "upper", -150.0, 150.0)],
314 ),
315 );
316 program.push(
317 "hand",
318 placed(
319 "link.geop",
320 false,
321 vec![hinged("hand", "fore", -120.0, 120.0)],
322 ),
323 );
324 let (elbow, wrist) = (30.0_f64, -45.0_f64);
325 let turned = |angle: f64| {
326 [
327 3.0 * angle.to_radians().cos(),
328 3.0 * angle.to_radians().sin(),
329 ]
330 };
331 let fore = [3.0, 0.0];
332 let [dx, dy] = turned(elbow);
333 let hand = [fore[0] + dx, fore[1] + dy];
334 let at = |[x, y]: [f64; 2], z, turn| StateValue::Pose(pose([x, y, z], [0.0, 0.0, turn]));
335 program.state = StateValues::from([
336 (pose_state("upper"), at([0.0, 0.0], 0.0, 0.0)),
337 (pose_state("fore"), at(fore, 0.2, elbow)),
338 (pose_state("hand"), at(hand, 0.4, elbow + wrist)),
339 (
340 joint_parameter("add_part(fore,m1)", Motion::Turn),
341 StateValue::Number(n(elbow)),
342 ),
343 (
344 joint_parameter("add_part(hand,m1)", Motion::Turn),
345 StateValue::Number(n(wrist)),
346 ),
347 ]);
348 program
349}
350
351pub fn four_bar_assembly() -> Program {
357 let mut program = Program::new();
358 program.push("ground", placed("ground.geop", true, Vec::new()));
359 program.push(
360 "crank",
361 placed(
362 "crank.geop",
363 false,
364 vec![pinned("crank", 0, "ground", 0), on_top("crank", "ground")],
365 ),
366 );
367 program.push(
368 "rocker",
369 placed(
370 "rocker.geop",
371 false,
372 vec![pinned("rocker", 0, "ground", 1), on_top("rocker", "ground")],
373 ),
374 );
375 program.push(
376 "coupler",
377 placed(
378 "coupler.geop",
379 false,
380 vec![
381 pinned("coupler", 0, "crank", 1),
382 pinned("coupler", 1, "rocker", 1),
383 on_top("coupler", "crank"),
384 ],
385 ),
386 );
387 let pose = |position, turn| StateValue::Pose(pose(position, [0.0, 0.0, turn]));
390 program.state = StateValues::from([
391 (pose_state("ground"), pose([0.0, 0.0, 0.0], 0.0)),
392 (pose_state("crank"), pose([0.0, 0.0, 0.2], 60.0)),
393 (
394 pose_state("rocker"),
395 pose([4.0, 0.0, 0.2], 82.690_722_887_668_12),
396 ),
397 (
398 pose_state("coupler"),
399 pose([0.75, 1.299_038_105_676_658, 0.4], 24.780_674_143_948_414),
400 ),
401 ]);
402 program
403}
404
405pub fn plates_assembly() -> Program {
408 let mut program = Program::new();
409 program.push(
410 "small",
411 AddPartArgs {
412 file: "plate.geop".into(),
413 ..Default::default()
414 }
415 .fixed(),
416 );
417 program.push(
418 "large",
419 AddPartArgs {
420 file: "plate.geop".into(),
421 parameters: Settings::from([
422 ("width".to_string(), Setting::Number(5.0)),
423 ("screw".to_string(), Setting::Text("M6".into())),
424 ("color".to_string(), Setting::Text("#3e7bd0".into())),
425 ]),
426 ..Default::default()
427 }
428 .fixed(),
429 );
430 program.state = StateValues::from([
431 (
432 pose_state("small"),
433 StateValue::Pose(pose([0.0; 3], [0.0; 3])),
434 ),
435 (
436 pose_state("large"),
437 StateValue::Pose(pose([0.0, 3.0, 0.0], [0.0; 3])),
438 ),
439 ]);
440 program
441}
442
443pub fn metric_plate() -> Program {
446 let none = Program::new();
447 let corner = |x: &str, y: &str| [x.to_string(), y.to_string()];
448 let mut outline = Drawing::new(&none).expect("no parameters to resolve");
449 let mut hole = Drawing::new(&none).expect("no parameters to resolve");
450 outline
451 .polygon(&[
452 corner("0", "0"),
453 corner("40", "0"),
454 corner("40", "40"),
455 corner("0", "40"),
456 ])
457 .expect("the corners are numbers");
458 let centre = hole.point("20", "20").expect("the centre is numbers");
459 hole.circle(centre, "4.5")
460 .expect("the diameter is a number");
461 let mut program = Program::new();
462 program.push("outline", outline.on(outline_plane()));
463 program.push(
464 "plate",
465 ExtrudeArgs {
466 sketch: whole_sketch("outline"),
467 extent: Extents::blind(5.0),
468 output: Output::Solid(Combine::NewBody),
469 draft: 0.0,
470 },
471 );
472 program.push("hole_sketch", hole.on(outline_plane()));
473 program.push(
474 "hole",
475 ExtrudeArgs {
476 sketch: whole_sketch("hole_sketch"),
477 extent: Extents {
478 side1: Extent::ThroughAll,
479 ..Extents::blind(0.0)
480 },
481 output: Output::Solid(Combine::cut("extrude(plate)")),
482 draft: 0.0,
483 },
484 );
485 program
486}
487
488pub fn metric_plate_hole() -> String {
490 let plate = built(&metric_plate());
491 let part = read_model(&plate.current()).expect("the plate has a model");
492 PartDescription::of(&part)
493 .expect("the plate describes itself")
494 .faces
495 .into_keys()
496 .find(|name| name.starts_with("extrude(hole,hole_sketch,"))
497 .expect("the hole has a face")
498}
499
500pub fn bolted_plate() -> Program {
504 let mut program = Program::new();
505 program.push(
506 "plate",
507 AddPartArgs {
508 file: "plate.geop".into(),
509 ..Default::default()
510 }
511 .fixed(),
512 );
513 let mate = |kind, a: MateEntity, b: MateEntity| Mate::constraint(kind, vec![a, b]);
514 let datum_of = |part: &str, name: &str| MateEntity::of(part, Selected::Datum(datum(name)));
515 let size = |row: &str| Settings::from([("size".to_string(), Setting::Text(row.into()))]);
516 program.push(
517 "screw",
518 AddPartArgs {
519 file: "std:iso4762_socket_head_cap_screw.geop".into(),
520 parameters: size("M4x12"),
521 mates: BTreeMap::from([
522 (
523 "m1".into(),
524 mate(
525 Kind::Concentric,
526 datum_of("screw", "axis"),
527 placed_face("plate", metric_plate_hole()),
528 ),
529 ),
530 (
531 "m2".into(),
532 mate(
533 Kind::Coincident,
534 datum_of("screw", "seat"),
535 placed_face("plate", "extrude(plate,end)"),
536 ),
537 ),
538 ]),
539 mirror: None,
540 },
541 );
542 program.push(
543 "nut",
544 AddPartArgs {
545 file: "std:iso4032_hex_nut.geop".into(),
546 parameters: size("M4"),
547 mates: BTreeMap::from([
548 (
549 "m1".into(),
550 mate(
551 Kind::Concentric,
552 datum_of("nut", "axis"),
553 datum_of("screw", "axis"),
554 ),
555 ),
556 (
557 "m2".into(),
558 mate(
559 Kind::Coincident,
560 datum_of("nut", "base"),
561 placed_face("plate", "extrude(plate,start)"),
562 ),
563 ),
564 ]),
565 mirror: None,
566 },
567 );
568 program.state = StateValues::from([
569 (
570 pose_state("plate"),
571 StateValue::Pose(pose([0.0; 3], [0.0; 3])),
572 ),
573 (
574 pose_state("screw"),
575 StateValue::Pose(pose([20.0, 20.0, 5.0], [0.0; 3])),
576 ),
577 (
578 pose_state("nut"),
579 StateValue::Pose(pose([20.0, 20.0, 0.0], [180.0, 0.0, 0.0])),
580 ),
581 ]);
582 program
583}
584
585pub fn cam() -> Program {
589 let mut program = Program::new();
590 let mut sketch = Sketch::new();
591 circle(&mut sketch, [0.5, 0.0], 2.0);
592 program.push(
593 "cam_sketch",
594 AddSketchArgs::on(on_origin(FrameAxis::Z), solved(sketch)),
595 );
596 program.push(
597 "cam",
598 ExtrudeArgs {
599 sketch: whole_sketch("cam_sketch"),
600 extent: Extents {
601 symmetric: true,
602 ..Extents::blind(1.0)
603 },
604 output: Output::Solid(Combine::NewBody),
605 draft: 0.0,
606 },
607 );
608 program
609}
610
611fn origin_datum(instance: Option<&str>, name: String) -> MateEntity {
614 let datum = Selected::Datum(ElementReference::new(datums(), name));
615 match instance {
616 Some(instance) => MateEntity::of(instance, datum),
617 None => MateEntity::on(datum),
618 }
619}
620
621pub fn cam_follower_assembly() -> Program {
628 let mut program = Program::new();
629 let frame = |instance| origin_datum(instance, ORIGIN.to_string());
630 let x_plane = |instance| {
631 let name = component_name(ORIGIN, DatumComponent::Plane(FrameAxis::X));
632 origin_datum(instance, name)
633 };
634 let spin = Mate::joint(
635 JointKind::new(Joint::Revolute),
636 vec![frame(None), frame(Some("cam"))],
637 );
638 program.push("cam", placed("cam.geop", false, vec![spin]));
639 let slide = Mate::joint(
640 JointKind::new(Joint::Slider),
641 vec![x_plane(None), x_plane(Some("follower"))],
642 );
643 let touch = Mate::constraint(
644 Kind::Tangent,
645 vec![
646 placed_face("cam", "extrude(cam,cam_sketch,c1)"),
647 MateEntity::of(
648 "follower",
649 Selected::Edge(element("extrude(pin,pin_sketch,c1,start)")),
650 ),
651 ],
652 );
653 program.push("follower", placed("pin.geop", false, vec![slide, touch]));
654 program.push(
655 "spin",
656 MotionArgs {
657 study: MotionStudy {
658 drivers: vec![joint_parameter("add_part(cam,m1)", Motion::Turn)],
659 keys: vec![vec![0.0, 0.0], vec![4.0, 360.0]],
660 frames: 25,
661 },
662 },
663 );
664 program.push(
665 "apart",
666 ExplodeArgs {
667 parts: vec!["follower".into()],
668 distance: 3.0,
669 ..Default::default()
670 },
671 );
672 let at = |x| StateValue::Pose(pose([x, 0.0, 0.0], [0.0; 3]));
675 program.state = StateValues::from([
676 (pose_state("cam"), at(0.0)),
677 (pose_state("follower"), at(2.9)),
678 (
679 joint_parameter("add_part(cam,m1)", Motion::Turn),
680 StateValue::Number(n(0.0)),
681 ),
682 (
683 joint_parameter("add_part(follower,m1)", Motion::Slide),
684 StateValue::Number(n(2.9)),
685 ),
686 ]);
687 program
688}
689
690fn drawn(text: &str) -> Program {
695 Program::from_json(text).expect("a drawn example is a program")
696}
697
698pub fn set() -> ExampleSet {
702 ExampleSet {
703 programs: vec![("pin", pin), ("link", link)],
704 workspaces: workspaces(),
705 }
706}
707
708pub fn workspaces() -> Vec<Example<ExampleFiles>> {
711 vec![
712 ("press", || {
713 vec![
714 ("asm.geop", drawn(include_str!("drawn/press/asm.geop"))),
715 ("base.geop", drawn(include_str!("drawn/press/base.geop"))),
716 (
717 "handle.geop",
718 drawn(include_str!("drawn/press/handle.geop")),
719 ),
720 (
721 "slider.geop",
722 drawn(include_str!("drawn/press/slider.geop")),
723 ),
724 ("link.geop", drawn(include_str!("drawn/press/link.geop"))),
725 ]
726 }),
727 ("duck", || {
728 vec![
729 ("duck.geop", drawn(include_str!("drawn/duck/duck.geop"))),
730 (
731 "left_leg.geop",
732 drawn(include_str!("drawn/duck/left_leg.geop")),
733 ),
734 (
735 "right_leg.geop",
736 drawn(include_str!("drawn/duck/right_leg.geop")),
737 ),
738 (
739 "left_hip.geop",
740 drawn(include_str!("drawn/duck/left_hip.geop")),
741 ),
742 (
743 "left_knee.geop",
744 drawn(include_str!("drawn/duck/left_knee.geop")),
745 ),
746 (
747 "left_ankle.geop",
748 drawn(include_str!("drawn/duck/left_ankle.geop")),
749 ),
750 ("foot.geop", drawn(include_str!("drawn/duck/foot.geop"))),
751 ("neck.geop", drawn(include_str!("drawn/duck/neck.geop"))),
752 ("head.geop", drawn(include_str!("drawn/duck/head.geop"))),
753 ("mouth.geop", drawn(include_str!("drawn/duck/mouth.geop"))),
754 ]
755 }),
756 ("pin_in_plate", || {
757 vec![
758 ("assembly.geop", pin_in_plate_assembly()),
759 ("plate.geop", box_with_drill_hole()),
760 ("pin.geop", pin()),
761 ]
762 }),
763 ("chain", || {
764 vec![("chain.geop", chain_assembly()), ("link.geop", link())]
765 }),
766 ("parametric_plates", || {
767 vec![
768 ("plates.geop", plates_assembly()),
769 ("plate.geop", parametric_plate()),
770 ]
771 }),
772 ("four_bar", || {
773 vec![
774 ("four_bar.geop", four_bar_assembly()),
775 ("ground.geop", bar(4.0)),
776 ("crank.geop", bar(1.5)),
777 ("rocker.geop", bar(3.0)),
778 ("coupler.geop", bar(4.0)),
779 ]
780 }),
781 ("arm", || {
782 vec![("arm.geop", arm_assembly()), ("link.geop", link())]
783 }),
784 ("bolted_plate", || {
785 vec![
786 ("bolted_plate.geop", bolted_plate()),
787 ("plate.geop", metric_plate()),
788 ]
789 }),
790 ("cam_follower", || {
791 vec![
792 ("cam_follower.geop", cam_follower_assembly()),
793 ("cam.geop", cam()),
794 ("pin.geop", pin()),
795 ]
796 }),
797 ]
798}