Skip to main content

geop_ops_assembly/
mates.rs

1//! Mates: what holds the parts placed in a part together — two entities,
2//! each of a placed part or of the part itself, and what they must be to
3//! each other (see [`MateKind`]) — and solving them, by moving the placed
4//! parts (see [`crate::mechanism`]).
5//!
6//! What a mate asks is said by [`crate::residuals`] and [`crate::joints`].
7//! What is added here is how an element ([`MateEntity`]) becomes a point, a
8//! line, a plane or a joint's connector of a placed part: an element of a
9//! placed part moves with that part; an element of the part itself never
10//! moves.
11//!
12//! A joint is made between two frames, one on each part, which a click puts
13//! on whatever is picked — a face, an edge, a point or a datum (see
14//! [`MateEntity::frame`]): it turns about, or slides along, their common `z`
15//! axis, the second part's frame moving on the first's.
16//!
17//! A joint's coordinates — how far it is turned and slid — are parameters of
18//! the part's state, like the poses of its placed parts: named after the
19//! mate, `add_part(arm2,m1).angle` (see [`joint_parameter`]). Solving sets
20//! them, and a program sets them to place a mechanism at a pose. A joint
21//! whose coordinate the state does not give starts where its parts are.
22
23pub use crate::{
24    joints::{Connector, CouplingKind, Joint, JointKind, Limits, MOTIONS, Motion, Transmission},
25    residuals::{Geometry, Kind},
26};
27pub use geop_ops_model::mate_entity::MateEntity;
28
29use geop_core_math::geop_error::GeopResult;
30use geop_core_program::{
31    element::{HasElements, Kind as ElementKind, whole},
32    target::{Target, TargetReference},
33};
34use geop_ops_model::selection::Role;
35use serde::{Deserialize, Serialize};
36
37/// `roles` in words: `a point, a line or a plane`.
38fn describe_roles(roles: &[Role]) -> String {
39    match roles {
40        [] => String::new(),
41        [only] => only.describe().to_string(),
42        [rest @ .., last] => {
43            let rest: Vec<&str> = rest.iter().map(|r| r.describe()).collect();
44            format!("{} or {}", rest.join(", "), last.describe())
45        }
46    }
47}
48
49/// What a mate asks: that two entities touch, line up or keep a distance
50/// (a constraint); that one part may only turn or slide against another
51/// about an axis (a joint); or that two joints move together (a coupling).
52/// Serializes flat, by the kind's own `type`: `{"type": "revolute", "max":
53/// 90.0}`.
54#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
55#[serde(untagged)]
56pub enum MateKind {
57    Constraint(Kind),
58    Joint(JointKind),
59    Coupling(CouplingKind),
60    Anchor(Anchor),
61}
62
63/// What holds a part where it is, whatever the other mates ask: the part
64/// one entity of the mate is of never moves in a solve.
65#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
66#[serde(tag = "type", rename_all = "snake_case")]
67pub enum Anchor {
68    Fixed,
69}
70
71impl MateKind {
72    pub fn label(&self) -> &'static str {
73        match self {
74            MateKind::Constraint(kind) => kind.label(),
75            MateKind::Joint(kind) => kind.label(),
76            MateKind::Coupling(kind) => kind.transmission.label(),
77            MateKind::Anchor(Anchor::Fixed) => "Fixed",
78        }
79    }
80
81    /// What an element of a mate of this kind can be, for a constraint:
82    /// what its geometry is taken from (see [`Kind::roles`]). A joint puts
83    /// a frame on any element; a coupling ties joints, and a fixed mate is
84    /// given its element by the step: neither is picked by role.
85    pub fn roles(&self) -> &'static [Role] {
86        match self {
87            MateKind::Constraint(kind) => kind.roles(),
88            MateKind::Joint(_) | MateKind::Coupling(_) | MateKind::Anchor(_) => &[],
89        }
90    }
91}
92
93/// The name of the parameter that is the coordinate of `motion` of the
94/// joint named `mate`: `add_part(arm2,m1).angle` — in degrees — or
95/// `.distance`.
96pub fn joint_parameter(mate: &str, motion: Motion) -> String {
97    format!("{mate}.{}", motion.name())
98}
99
100/// The name of the parameter `foot` of the foot point a constraint named
101/// `mate` keeps its follower at (see [`Kind::feet`]): `add_part(cam,m1).t`.
102pub fn foot_parameter(mate: &str, foot: &str) -> String {
103    format!("{mate}.{foot}")
104}
105
106/// A mate: what two entities must be to each other — or, for a coupling,
107/// two joints. Serializes flat: `{"type": "distance", "value": 1.0,
108/// "entities": [...]}`.
109///
110/// One being edited may hold fewer than two entities or joints yet; only a
111/// complete one ([`Mate::is_complete`]) holds anything.
112#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
113pub struct Mate {
114    #[serde(flatten)]
115    pub kind: MateKind,
116    #[serde(default)]
117    pub entities: Vec<MateEntity>,
118    /// The joints a coupling ties, by their names in the part:
119    /// `add_part(gear2,m1)`.
120    #[serde(default, skip_serializing_if = "Vec::is_empty")]
121    pub joints: Vec<String>,
122}
123
124impl Mate {
125    /// A constraint of `kind` between `entities`.
126    pub fn constraint(kind: Kind, entities: Vec<MateEntity>) -> Self {
127        Mate {
128            kind: MateKind::Constraint(kind),
129            entities,
130            joints: Vec::new(),
131        }
132    }
133
134    /// A joint of `kind` between `entities`: the first part's connector,
135    /// then the second's.
136    pub fn joint(kind: JointKind, entities: Vec<MateEntity>) -> Self {
137        Mate {
138            kind: MateKind::Joint(kind),
139            entities,
140            joints: Vec::new(),
141        }
142    }
143
144    /// A fixed mate of no entity yet: in a step that places a part, it
145    /// holds that part (see `AddPart`).
146    pub fn fixed_here() -> Self {
147        Mate {
148            kind: MateKind::Anchor(Anchor::Fixed),
149            entities: Vec::new(),
150            joints: Vec::new(),
151        }
152    }
153
154    /// Whether it holds a part fixed.
155    pub fn is_fixed(&self) -> bool {
156        matches!(self.kind, MateKind::Anchor(Anchor::Fixed))
157    }
158
159    /// A coupling of `kind` between the joints named `joints`.
160    pub fn coupling(kind: CouplingKind, joints: Vec<String>) -> Self {
161        Mate {
162            kind: MateKind::Coupling(kind),
163            entities: Vec::new(),
164            joints,
165        }
166    }
167
168    /// Its two entities, if it has exactly two.
169    pub fn pair(&self) -> Option<[&MateEntity; 2]> {
170        match self.entities.as_slice() {
171            [a, b] => Some([a, b]),
172            _ => None,
173        }
174    }
175
176    /// Whether it has what it holds together: two entities, or for a
177    /// coupling two joints.
178    pub fn is_complete(&self) -> bool {
179        match self.kind {
180            MateKind::Coupling(_) => self.joints.len() == 2,
181            MateKind::Anchor(_) => self.entities.len() == 1,
182            _ => self.pair().is_some(),
183        }
184    }
185
186    /// What an entity has to be for this kind of mate, in words — for a
187    /// coupling, what its joints have to be.
188    pub fn needs(&self) -> String {
189        match self.kind {
190            MateKind::Constraint(kind) => match (kind.needs(0), kind.needs(1)) {
191                (Some(first), Some(second)) => format!("first {first}, then {second}"),
192                _ => describe_roles(self.kind.roles()),
193            },
194            MateKind::Joint(_) => {
195                "a face, an edge, a point or a datum: where it is picked, a frame is put"
196                    .to_string()
197            }
198            MateKind::Anchor(_) => "an entity of the part to hold".to_string(),
199            MateKind::Coupling(kind) => match kind.transmission.motions() {
200                [Motion::Turn, Motion::Turn] => "two joints that turn".to_string(),
201                [a, b] => format!("a joint that {}s, then one that {}s", a.verb(), b.verb()),
202            },
203        }
204    }
205}
206
207/// What an entity of a mate is to a solve: `what` it is, in the frame of
208/// the body it moves with — the placed part it is of, however deep, by
209/// name; none for the part itself, which never moves — and the entity, as
210/// the part names it, to say what the mate joins.
211#[derive(Clone, Debug, PartialEq)]
212pub struct On<T> {
213    pub body: Option<String>,
214    pub what: T,
215    pub entity: MateEntity,
216}
217
218/// A mate as a solve takes it, its entities resolved where their parts
219/// are: the value of its target ([`mate`]). It reads no state, so a part
220/// moved does not resolve it again.
221#[derive(Clone, Debug, PartialEq)]
222pub enum Resolved {
223    Constraint {
224        kind: Kind,
225        a: On<Geometry>,
226        b: On<Geometry>,
227    },
228    Joint {
229        kind: JointKind,
230        a: On<Connector>,
231        b: On<Connector>,
232    },
233    /// The joints it ties, by their names.
234    Coupling {
235        kind: CouplingKind,
236        joints: [String; 2],
237    },
238    /// The placed part `body` held where it is.
239    Fixed { body: String },
240}
241
242impl Resolved {
243    /// The entities it is between: none for a coupling, which is between
244    /// joints, or for a part held where it is.
245    pub fn entities(&self) -> Vec<MateEntity> {
246        match self {
247            Resolved::Constraint { a, b, .. } => vec![a.entity.clone(), b.entity.clone()],
248            Resolved::Joint { a, b, .. } => vec![a.entity.clone(), b.entity.clone()],
249            Resolved::Coupling { .. } | Resolved::Fixed { .. } => Vec::new(),
250        }
251    }
252}
253
254/// What the target of a mate is named, before the mate's name.
255const PREFIX: &str = "mate:";
256
257/// The mate `name`, resolved.
258pub fn mate(name: &str) -> TargetReference<Resolved> {
259    TargetReference::new(format!("{PREFIX}{name}"))
260}
261
262/// The name of the mate whose target is named `target`, if it is one.
263pub fn mate_name(target: &str) -> Option<&str> {
264    target.strip_prefix(PREFIX)
265}
266
267/// Each mate's own.
268impl Target for Resolved {}
269
270/// Read whole.
271impl ElementKind for Resolved {
272    const NAME: &'static str = "mate";
273}
274
275impl HasElements<Resolved> for Resolved {
276    type Element<'a> = &'a Resolved;
277
278    fn element(&self, name: &str) -> GeopResult<&Resolved> {
279        whole(self, name)
280    }
281
282    fn names_target(name: &str) -> bool {
283        name.starts_with(PREFIX)
284    }
285}