Skip to main content

geop_ops_assembly/
mechanism.rs

1//! The mechanism of a part: its mates as constraints on the program's state
2//! — where its placed parts are, how its joints are turned — and what they
3//! declare for solving it: the parts a fixed mate holds, each joint's
4//! coordinates as values a solve moves and a user sets. Solving, checking
5//! and measuring is generic ([`crate::solving`]).
6//!
7//! A mate is worked out in two steps, each a target of the run. Its
8//! entities are resolved once, in the frames of the parts they are of
9//! ([`resolve`], the target [`crate::mate`]), which reads no state. Its
10//! constraint on the state ([`constrain`]) places those where the state
11//! has the parts, and is all that is made again when a part moves. A
12//! joint's limits are constraints of their own ([`limit`]), which hold
13//! anywhere short of them.
14//!
15//! A constraint's derivatives are by the state's own steps (see
16//! [`geop_core_program::values::StateValue`]): a pose's by a twist in its own frame, a
17//! coordinate's by what is added to it. They are exact, through every pose
18//! a placement reads — a part placed in a placed part moves with both —
19//! computed in dual numbers, one state at a time.
20//!
21//! The editor solves ([`crate::solving::solve`]): the solve moves the state
22//! until the constraints hold, making only the constraints it asks again
23//! at every state it tries, of the part as built (see
24//! [`geop_lib_constraints::constraint::Remade`]) — which is why a
25//! constraint reads only the state and what reads none — and the editor
26//! runs the program once more with the state found.
27//!
28//! The constraints of one step's mates are defined together, and so are
29//! their unknowns (see [`define`]).
30
31use geop_core_program::{
32    element::ElementReference, part::StepContext, placed::instance_path, reader::Reader,
33    values::StateValue,
34};
35use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
36
37use geop_core_math::{
38    geop_error::{GeopError, GeopResult, WithContext},
39    primitives::Pose,
40    scalars::{Number, Scalar},
41    solvers::system::{Mobility, tolerance},
42    vector::Vector3,
43    with_context,
44};
45use geop_lib_constraints::{
46    Bound, Constraint, Constraints,
47    constraint::define_constraints,
48    unknown::{Unknown, declare_unknowns},
49};
50use geop_ops_model::parameters::{Factor, compose, placement};
51use geop_ops_model::selection::Selected;
52
53use geop_ops_model::placed::{drawn, locate, moved_by};
54
55use crate::{
56    joints::{Connector, Coordinates, JointKind, MOTIONS, Motion, joint_residuals, measure},
57    mates::{Mate, MateKind, On, Resolved, foot_parameter, joint_parameter, mate, mate_name},
58    residuals::{D, Geometry, Kind, contact_residuals, residuals, seed_feet},
59    solving::{Setting, Settings, largest, scale_of, settings_of},
60};
61use geop_ops_model::mate_entity::MateEntity;
62
63thread_local! {
64    static MATES_RESOLVED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
65}
66
67/// How many mates this thread has resolved — their elements found and
68/// measured on the parts placed — ever. What any question about the mates
69/// costs grows with this, so a test can bound the work an edit does by the
70/// mates it touches, rather than time it.
71pub fn mates_resolved() -> usize {
72    MATES_RESOLVED.get()
73}
74
75/// The size of the placed parts `bodies` — at least one: what a mate
76/// between them turns a turn into a length by.
77///
78/// A part made of parts placed in it — a sub-assembly — is as large as
79/// the largest of them, however deep: drawn whole, it is drawn where its
80/// state puts them, and a constraint reads no target that reads the state.
81fn size(reader: &Reader, bodies: &[&Option<String>]) -> GeopResult<Scalar> {
82    let mut pieces: Vec<String> = (bodies.iter().copied().flatten()).cloned().collect();
83    let mut rigid = Vec::new();
84    while let Some(body) = pieces.pop() {
85        let inside = reader.placed_part(&body)?.instances();
86        match inside.is_empty() {
87            true => rigid.push(body),
88            false => pieces.extend(inside.into_iter().map(|name| instance_path(&body, name))),
89        }
90    }
91    largest(rigid.iter().map(|body| drawn(reader, body)))
92}
93
94/// The geometry `entity`, the `k`th of `mate`, a constraint of `kind`, is
95/// to it, in its own part — or, where what it is on is not what the kind
96/// takes, the element it was picked at: a click by a face's rim picks the
97/// face at the rim, and a tangent takes the rim.
98fn geometry_in(
99    kind: &Kind,
100    mate: &Mate,
101    k: usize,
102    entity: &MateEntity,
103    reader: &Reader,
104) -> GeopResult<Geometry> {
105    let shape = entity.shape(reader)?;
106    let (surface, curve) = (|| entity.surface(reader), || entity.curve(reader));
107    if let Some(geometry) = kind.geometry(k, &shape, surface, curve)? {
108        return Ok(geometry);
109    }
110    let at = (entity.at.as_ref()).map(|at| MateEntity::on(at.clone()));
111    let picked_at = match &at {
112        Some(at) => {
113            let (surface, curve) = (|| at.surface(reader), || at.curve(reader));
114            kind.geometry(k, &at.shape(reader)?, surface, curve)?
115        }
116        None => None,
117    };
118    picked_at.ok_or_else(|| {
119        let needs = kind.needs(k).map_or_else(|| mate.needs(), str::to_string);
120        GeopError::new(format!("{} is not {needs}", entity.label()))
121    })
122}
123
124/// The connector `entity` is to a joint, in its own part: what it makes one
125/// of itself, or the frame it gives.
126fn connector_in(entity: &MateEntity, reader: &Reader) -> GeopResult<Connector> {
127    let shape = entity.shape(reader)?;
128    let face = matches!(entity.on, Selected::Face(_)) && entity.at.is_none();
129    match Connector::of(&shape, face) {
130        Some(connector) => connector,
131        None => {
132            let frame = entity.frame(reader)?;
133            Connector::new(*frame.origin(), *frame.w(), Some(*frame.u()))
134        }
135    }
136}
137
138/// What `what` makes of `entity`, on the body it moves with: in the frame
139/// of the placed part it is of, or of the part itself.
140fn on<T>(
141    reader: &Reader,
142    entity: &MateEntity,
143    what: impl Fn(&MateEntity, &Reader) -> GeopResult<T>,
144) -> GeopResult<On<T>> {
145    Ok(On {
146        body: (!entity.part().is_empty()).then(|| entity.part().to_string()),
147        what: what(entity, reader)?,
148        entity: entity.clone(),
149    })
150}
151
152/// The mate `name`, `mate`, resolved in the part read: the generator of its
153/// target [`crate::mate`]. A fixed mate fixes the part its element is of.
154pub(crate) fn resolve(reader: &Reader, (name, mate): &(String, Mate)) -> GeopResult<Resolved> {
155    MATES_RESOLVED.set(MATES_RESOLVED.get() + 1);
156    let ctx = with_context!("mate {name:?}");
157    let resolved = match mate.kind {
158        MateKind::Constraint(kind) => {
159            let [a, b] = mate
160                .pair()
161                .ok_or_else(|| GeopError::new("it needs two elements"))
162                .with_context(ctx)?;
163            let geometry = |k| move |e: &MateEntity, r: &Reader| geometry_in(&kind, mate, k, e, r);
164            let (a, b) = (on(reader, a, geometry(0))?, on(reader, b, geometry(1))?);
165            // Whether the kind can hold between the two is a question of
166            // their geometry alone, whatever bodies they are of.
167            kind.fits(&a.what, &b.what).with_context(ctx)?;
168            Resolved::Constraint { kind, a, b }
169        }
170        MateKind::Joint(kind) => {
171            let [a, b] = mate
172                .pair()
173                .ok_or_else(|| GeopError::new("it needs two frames"))
174                .with_context(ctx)?;
175            kind.validate().with_context(ctx)?;
176            Resolved::Joint {
177                kind,
178                a: on(reader, a, connector_in)?,
179                b: on(reader, b, connector_in)?,
180            }
181        }
182        MateKind::Coupling(kind) => match mate.joints.as_slice() {
183            [a, b] => Resolved::Coupling {
184                kind,
185                joints: [a.clone(), b.clone()],
186            },
187            _ => return Err(GeopError::new("it needs two joints")).with_context(ctx),
188        },
189        MateKind::Anchor(_) => {
190            let [entity] = mate.entities.as_slice() else {
191                return Err(GeopError::new("it needs one element")).with_context(ctx);
192            };
193            if entity.part().is_empty() {
194                return Err(GeopError::new(format!(
195                    "{} is of this part itself, which never moves: a fixed mate holds a part placed in it",
196                    entity.label()
197                )))
198                .with_context(ctx);
199            }
200            Resolved::Fixed {
201                body: entity.part().to_string(),
202            }
203        }
204    };
205    Ok(resolved)
206}
207
208/// The mate `name` of the part `reader` reads, resolved.
209fn resolved<'a>(reader: &Reader<'a>, name: &str) -> GeopResult<&'a Resolved> {
210    reader.retrieve_element(&ElementReference::whole(mate(name)))
211}
212
213/// What a state is to a mate that reads it.
214#[derive(Clone, Copy, PartialEq)]
215enum Read {
216    /// A pose a placement reads.
217    Pose,
218    /// A joint's coordinate.
219    Coordinate,
220}
221
222/// The states a mate reads, as it is evaluated: each with what it is, and
223/// the one it is differentiated by, if any.
224struct Reading<'a> {
225    read: BTreeMap<String, Read>,
226    by: Option<&'a str>,
227    twist: Pose<D>,
228    known: &'a Known,
229}
230
231/// What a constraint's evaluations — one where the state is, one for each
232/// state it is differentiated by — read alike, read once: where each body
233/// is placed, as factors, and the bodies' sizes.
234#[derive(Default)]
235struct Known {
236    placements: RefCell<BTreeMap<String, Rc<Vec<Factor>>>>,
237    sizes: RefCell<BTreeMap<[Option<String>; 2], Scalar>>,
238}
239
240impl Reading<'_> {
241    /// Where the placed part `body` is, as the state has it — none for the
242    /// part itself, which never moves.
243    fn pose(&mut self, reader: &Reader, body: &Option<String>) -> GeopResult<Option<Pose<D>>> {
244        let Some(body) = body else {
245            return Ok(None);
246        };
247        let Reading {
248            read,
249            by,
250            twist,
251            known,
252        } = self;
253        let kept = known.placements.borrow().get(body).cloned();
254        let placement = match kept {
255            Some(kept) => kept,
256            None => {
257                let made = Rc::new(placement(reader, body)?);
258                (known.placements.borrow_mut()).insert(body.clone(), made.clone());
259                made
260            }
261        };
262        Ok(Some(compose(&placement, &mut |state, pose| {
263            read.insert(state.to_string(), Read::Pose);
264            let pose = pose.map(D::cst);
265            match *by == Some(state) {
266                true => pose.compose(twist),
267                false => pose,
268            }
269        })))
270    }
271
272    /// The coordinate `state`, as the state has it.
273    fn coordinate(&mut self, reader: &Reader, state: &str) -> GeopResult<D> {
274        self.read.insert(state.to_string(), Read::Coordinate);
275        let StateValue::Number(value) = reader.retrieve_state(state) else {
276            return Err(GeopError::new(format!(
277                "the joint coordinate {state:?} holds no number"
278            )));
279        };
280        Ok(match self.by == Some(state) {
281            true => D::var(value, 0),
282            false => D::cst(value),
283        })
284    }
285
286    /// The coordinates of the joint `name` of `kind`, by
287    /// [`Motion::index`]: zero for a motion it does not free.
288    fn coordinates(
289        &mut self,
290        reader: &Reader,
291        name: &str,
292        kind: &JointKind,
293    ) -> GeopResult<Coordinates> {
294        let mut out = [D::cst(Scalar::ZERO); MOTIONS];
295        for motion in kind.motions() {
296            out[motion.index()] = self.coordinate(reader, &joint_parameter(name, motion))?;
297        }
298        Ok(out)
299    }
300
301    /// The size of the bodies of `a` and `b` — what a mate between them
302    /// turns a turn into a length by (see [`size`]) — and where the state
303    /// has them.
304    fn ends<T>(
305        &mut self,
306        reader: &Reader,
307        a: &On<T>,
308        b: &On<T>,
309    ) -> GeopResult<(D, [Option<Pose<D>>; 2])> {
310        let bodies = [a.body.clone(), b.body.clone()];
311        let kept = self.known.sizes.borrow().get(&bodies).copied();
312        let scale = match kept {
313            Some(kept) => kept,
314            None => {
315                let made = size(reader, &[&a.body, &b.body])?;
316                self.known.sizes.borrow_mut().insert(bodies, made);
317                made
318            }
319        };
320        let scale = D::cst(scale);
321        let poses = [self.pose(reader, &a.body)?, self.pose(reader, &b.body)?];
322        Ok((scale, poses))
323    }
324}
325
326/// A joint resolved: its kind, and its two ends.
327type Joint<'a> = (&'a JointKind, &'a On<Connector>, &'a On<Connector>);
328
329/// The joint `name` of the part read, resolved.
330fn joint<'a>(reader: &Reader<'a>, name: &str) -> GeopResult<Joint<'a>> {
331    match resolved(reader, name)? {
332        Resolved::Joint { kind, a, b } => Ok((kind, a, b)),
333        _ => Err(GeopError::new(format!("the mate {name:?} is no joint"))),
334    }
335}
336
337/// The residuals of the mate `name`, `resolved`, where the state has its
338/// parts and joints (see [`constrain`]).
339fn residuals_of(
340    reader: &Reader,
341    name: &str,
342    resolved: &Resolved,
343    reading: &mut Reading,
344) -> GeopResult<Vec<D>> {
345    match resolved {
346        Resolved::Constraint { kind, a, b } => {
347            let (scale, [pa, pb]) = reading.ends(reader, a, b)?;
348            if kind.feet().is_empty() {
349                let (wa, wb) = (a.what.placed(pa.as_ref())?, b.what.placed(pb.as_ref())?);
350                return residuals(kind, &wa, &wb, scale);
351            }
352            let feet = (kind.feet().iter())
353                .map(|foot| reading.coordinate(reader, &foot_parameter(name, foot)))
354                .collect::<GeopResult<Vec<D>>>()?;
355            contact_residuals(kind, &a.what, pa.as_ref(), &b.what, pb.as_ref(), &feet)
356        }
357        Resolved::Joint { kind, a, b } => {
358            let (scale, [pa, pb]) = reading.ends(reader, a, b)?;
359            let at = reading.coordinates(reader, name, kind)?;
360            let (ca, cb) = (a.what.placed(pa.as_ref()), b.what.placed(pb.as_ref()));
361            joint_residuals(kind, &ca, &cb, &at, scale)
362        }
363        Resolved::Coupling { kind, joints } => {
364            let [ja, jb] = [&joints[0], &joints[1]].map(|j| joint(reader, j));
365            let ((ka, a1, a2), (kb, b1, b2)) = (ja?, jb?);
366            let [ma, mb] = kind.transmission.motions();
367            kind.validate(joints[0] == joints[1], [ka.moves(ma), kb.moves(mb)])
368                .map_err(|e| {
369                    e.with_context(format!("joints {:?} and {:?}", joints[0], joints[1]))
370                })?;
371            let scale = D::cst(size(reader, &[&a1.body, &a2.body, &b1.body, &b2.body])?);
372            let a = reading.coordinate(reader, &joint_parameter(&joints[0], ma))?;
373            let b = reading.coordinate(reader, &joint_parameter(&joints[1], mb))?;
374            Ok(vec![kind.residual(a, b, scale)?])
375        }
376        Resolved::Fixed { .. } => Ok(Vec::new()),
377    }
378}
379
380/// A constraint of the residuals `evaluate` gives, where the state is, and
381/// their derivatives by every state they read that holds what it is read
382/// as — a pose not solved for yet puts its part at the origin, and is not
383/// one to move.
384fn differentiated(
385    reader: &Reader,
386    bound: Bound,
387    evaluate: impl Fn(&Reader, &mut Reading) -> GeopResult<Vec<D>>,
388) -> GeopResult<Constraint> {
389    // The step of a pose by its six variables, in its own frame.
390    let twist = Pose::exp_with_derivatives(&Vector3::zero(), &Vector3::zero())?;
391    let known = Known::default();
392    let mut plain = Reading {
393        read: BTreeMap::new(),
394        by: None,
395        twist,
396        known: &known,
397    };
398    let values = evaluate(reader, &mut plain)?;
399    let mut jacobian = BTreeMap::new();
400    for (state, read) in plain.read {
401        let dimension = match (reader.retrieve_state(&state), read) {
402            (StateValue::Pose(_), Read::Pose) => 6,
403            (StateValue::Number(_), Read::Coordinate) => 1,
404            _ => continue,
405        };
406        let mut by = Reading {
407            read: BTreeMap::new(),
408            by: Some(&state),
409            twist,
410            known: &known,
411        };
412        let rows = (evaluate(reader, &mut by)?.into_iter())
413            .map(|r| r.d.into_iter().take(dimension).collect())
414            .collect();
415        jacobian.insert(state, rows);
416    }
417    Ok(Constraint {
418        residuals: values.into_iter().map(|r| r.v).collect(),
419        jacobian,
420        bound,
421    })
422}
423
424/// The constraint the mate `name` puts on the state. Its residuals are
425/// lengths — a turn's the distance it moves a point as far from its axis as
426/// its parts are large — that vanish where it holds.
427fn constrain(reader: &Reader, name: &str) -> GeopResult<Constraint> {
428    let resolved = resolved(reader, name)?;
429    differentiated(reader, Bound::Zero, |reader, reading| {
430        residuals_of(reader, name, resolved, reading)
431    })
432}
433
434/// The name of the constraint that keeps the coordinate `state` of a joint
435/// within its upper limit, or its lower: `….angle.max`.
436fn limit_name(state: &str, upper: bool) -> String {
437    format!("{state}.{}", if upper { "max" } else { "min" })
438}
439
440/// The constraint that keeps the coordinate of `motion` of the joint
441/// `name` within its upper limit, or its lower: a limit (see
442/// [`Bound::AtMost`]), as a length as [`constrain`]'s are.
443fn limit(reader: &Reader, name: &str, motion: Motion, upper: bool) -> GeopResult<Constraint> {
444    let (kind, a, b) = joint(reader, name)?;
445    let [min, max] = kind.limits(motion);
446    let bound = (if upper { max } else { min })
447        .ok_or_else(|| GeopError::new(format!("the joint {name:?} has no such limit")))?;
448    // An angle is turned into a length by the parts' size; a distance is
449    // one, and is not multiplied by an exact one.
450    let scale = match motion {
451        Motion::Turn => Some(size(reader, &[&a.body, &b.body])?),
452        Motion::Slide | Motion::SlideX | Motion::SlideY => None,
453    };
454    let state = joint_parameter(name, motion);
455    differentiated(reader, Bound::AtMost, |reader, reading| {
456        let value = reading.coordinate(reader, &state)?;
457        let past = match upper {
458            true => value.sub(D::cst(bound)),
459            false => D::cst(bound).sub(value),
460        };
461        let past = motion.natural(past)?;
462        Ok(vec![match scale {
463            Some(scale) => past.mul(D::cst(scale)),
464            None => past,
465        }])
466    })
467}
468
469/// The constraint that keeps the `k`th parameter of the foot point of the
470/// constraint `name` (see [`Kind::feet`]) within its domain's upper end,
471/// or its lower — where the curve or face does not close on itself along
472/// it: a limit, as a length — a step of the parameter turned into one by
473/// the parts' size per the domain's width, a free choice of scale.
474fn foot_limit(reader: &Reader, name: &str, k: usize, upper: bool) -> GeopResult<Constraint> {
475    let Resolved::Constraint { kind, a, b } = resolved(reader, name)? else {
476        return Err(GeopError::new(format!(
477            "the mate {name:?} keeps no foot point"
478        )));
479    };
480    let (Some(foot), Some(domain)) = (kind.feet().get(k), a.what.parameters().get(k).copied())
481    else {
482        return Err(GeopError::new(format!(
483            "the mate {name:?} has no foot parameter {k}"
484        )));
485    };
486    let bound = if upper { domain.hi } else { domain.lo };
487    let width = domain.hi.sub(domain.lo);
488    let scale = D::cst(size(reader, &[&a.body, &b.body])?.div(width)?);
489    let state = foot_parameter(name, foot);
490    differentiated(reader, Bound::AtMost, |reader, reading| {
491        let value = reading.coordinate(reader, &state)?;
492        let past = match upper {
493            true => value.sub(D::cst(bound)),
494            false => D::cst(bound).sub(value),
495        };
496        Ok(vec![past.mul(scale)])
497    })
498}
499
500/// The constraints the mates `names`, resolved already in the part, put on
501/// the state: each mate's own, named as the mate is, and its limits' — a
502/// joint's coordinates', a foot point's parameters' within their domain. A
503/// fixed mate puts none: it declares the part it holds fixed. The generator
504/// of a step's constraints.
505fn constraints_of_mates(
506    reader: &Reader,
507    names: &Vec<String>,
508) -> GeopResult<BTreeMap<String, Constraint>> {
509    let mut out = BTreeMap::new();
510    for name in names {
511        let resolved = resolved(reader, name)?;
512        if let Resolved::Fixed { .. } = resolved {
513            continue;
514        }
515        out.insert(name.clone(), constrain(reader, name)?);
516        if let Resolved::Constraint { kind, a, .. } = resolved {
517            let ends = a.what.parameters();
518            for (k, foot) in kind.feet().iter().enumerate() {
519                if ends.get(k).is_none_or(|end| end.closes) {
520                    continue;
521                }
522                let state = foot_parameter(name, foot);
523                for upper in [false, true] {
524                    let constraint = foot_limit(reader, name, k, upper)?;
525                    out.insert(limit_name(&state, upper), constraint);
526                }
527            }
528        }
529        if let Resolved::Joint { kind, .. } = resolved {
530            for motion in kind.motions() {
531                let state = joint_parameter(name, motion);
532                for (upper, given) in [false, true].into_iter().zip(kind.limits(motion)) {
533                    if given.is_some() {
534                        let constraint = limit(reader, name, motion, upper)?;
535                        out.insert(limit_name(&state, upper), constraint);
536                    }
537                }
538            }
539        }
540    }
541    Ok(out)
542}
543
544/// The unknowns a step declares: the pose state `placed` of the part it
545/// places, if it places one, held where it is as far as the mates let it;
546/// and those its mates `names` declare: the part a fixed mate holds, each
547/// joint's coordinates and each foot point's parameters. The generator of a
548/// step's unknowns.
549fn unknowns_of_mates(
550    reader: &Reader,
551    (placed, names): &(Option<String>, Vec<String>),
552) -> GeopResult<Vec<Unknown>> {
553    let mut out = Vec::new();
554    if let Some(placed) = placed {
555        out.push(Unknown {
556            state: placed.clone(),
557            mobility: Mobility::Held,
558            // Where the run has a part whose pose the state does not give.
559            seed: Some(StateValue::Pose(Pose::identity())),
560            unless: None,
561        });
562    }
563    for name in names {
564        match resolved(reader, name)? {
565            Resolved::Fixed { body } => out.push(fixed(reader, body)?),
566            Resolved::Joint { kind, .. } => {
567                for motion in kind.motions() {
568                    out.push(coordinate(reader, name, motion)?);
569                }
570            }
571            Resolved::Constraint { kind, a, b } => out.extend(feet(reader, name, kind, a, b)?),
572            Resolved::Coupling { .. } => {}
573        }
574    }
575    Ok(out)
576}
577
578/// The parameters of the foot point of the constraint `name` of `kind`
579/// between `a` and `b`, as values a solve moves: following their parts,
580/// and — where it does not hold — started at the foot point nearest the
581/// follower's point where the parts are: a seed, so a free choice, which
582/// the solve makes exact.
583fn feet(
584    reader: &Reader,
585    name: &str,
586    kind: &Kind,
587    a: &On<Geometry>,
588    b: &On<Geometry>,
589) -> GeopResult<Vec<Unknown>> {
590    if kind.feet().is_empty() {
591        return Ok(Vec::new());
592    }
593    let world = |on: &On<Geometry>| -> GeopResult<Option<Pose>> {
594        (on.body.as_ref())
595            .map(|body| Ok(locate(reader, body)?.world))
596            .transpose()
597    };
598    // Where the parts cannot be placed, there is nothing to start from.
599    let seeds = (world(a).and_then(|pa| Ok((pa, world(b)?))))
600        .and_then(|(pa, pb)| seed_feet(&a.what, pa.as_ref(), &b.what, pb.as_ref()))
601        .ok();
602    Ok((kind.feet().iter().enumerate())
603        .map(|(k, foot)| Unknown {
604            state: foot_parameter(name, foot),
605            mobility: Mobility::Follows,
606            seed: (seeds.as_ref())
607                .and_then(|s| s.get(k))
608                .map(|v| StateValue::Number(*v)),
609            unless: Some(name.to_string()),
610        })
611        .collect())
612}
613
614/// Defines what the mates `mates` of the step being run, each resolved
615/// already in the part and called as `labels` says, put on the state: their
616/// constraints and limits, the parts the fixed ones hold and the joints'
617/// coordinates as values a solve moves — and, as values a user sets, each
618/// joint's coordinates. The pose state `placed` of the part the step
619/// places, if it places one, is a value a solve moves too.
620pub(crate) fn define(
621    ctx: &mut StepContext,
622    placed: Option<&str>,
623    mates: &[(String, String)],
624) -> GeopResult<()> {
625    let names: Vec<String> = mates.iter().map(|(name, _)| name.clone()).collect();
626    let constrained = define_constraints(ctx, &names, constraints_of_mates);
627    let unknowns = (placed.map(str::to_string), names);
628    let declared = declare_unknowns(ctx, &unknowns, unknowns_of_mates);
629    let mut set = Ok(());
630    for (name, label) in mates {
631        let is_joint = matches!(resolved(&ctx.reader(), name), Ok(Resolved::Joint { .. }));
632        if is_joint {
633            let named = (name.clone(), label.clone());
634            set = set.and(ctx.produce(&settings_of(name), &named, settings));
635        }
636    }
637    constrained.and(declared).and(set)
638}
639
640/// The mates of the part `reader` reads, resolved, by name, in the order
641/// the steps made them.
642pub fn mates_of<'a>(reader: &Reader<'a>) -> Vec<(String, &'a Resolved)> {
643    (reader.targets_of::<Resolved>().into_iter())
644        .filter_map(|target| {
645            let name = mate_name(target.name())?.to_string();
646            let resolved = reader
647                .retrieve_element(&ElementReference::whole(target))
648                .ok()?;
649            Some((name, resolved))
650        })
651        .collect()
652}
653
654/// Where the connectors of the joint `a`–`b` are, as the part read has
655/// them: in the world.
656fn connectors_at(
657    reader: &Reader,
658    a: &On<Connector>,
659    b: &On<Connector>,
660) -> GeopResult<[Connector<D>; 2]> {
661    let world = |on: &On<Connector>| -> GeopResult<Option<Pose<D>>> {
662        (on.body.as_ref())
663            .map(|body| Ok(locate(reader, body)?.world.map(D::cst)))
664            .transpose()
665    };
666    let (wa, wb) = (world(a)?, world(b)?);
667    Ok([a.what.placed(wa.as_ref()), b.what.placed(wb.as_ref())])
668}
669
670/// The coordinates of a joint, by [`Motion::index`], measured where its
671/// parts are — an angle at the turn nearest `near`.
672fn measured(
673    reader: &Reader,
674    a: &On<Connector>,
675    b: &On<Connector>,
676    near: f64,
677) -> GeopResult<[f64; MOTIONS]> {
678    let [ca, cb] = connectors_at(reader, a, b)?;
679    let mut at = measure(&ca, &cb);
680    let angle = &mut at[Motion::Turn.index()];
681    *angle += 360.0 * ((near - *angle) / 360.0).round();
682    Ok(at)
683}
684
685/// What a fixed mate declares of the placed part `body` it holds: its
686/// state, fixed. A part no state moves is fixed already, and so is a state
687/// none has.
688fn fixed(reader: &Reader, body: &str) -> GeopResult<Unknown> {
689    Ok(Unknown {
690        state: moved_by(reader, body)?.unwrap_or_else(|| body.to_string()),
691        mobility: Mobility::Fixed,
692        seed: None,
693        unless: None,
694    })
695}
696
697/// The coordinate of `motion` of the joint `name`, as a value a solve
698/// moves: following its parts, and — where the joint does not hold —
699/// started where they are, measured from them at the turn nearest where it
700/// is: a seed, so a free choice, which the solve makes exact.
701fn coordinate(reader: &Reader, name: &str, motion: Motion) -> GeopResult<Unknown> {
702    let state = joint_parameter(name, motion);
703    let near = match reader.retrieve_state(&state) {
704        StateValue::Number(v) => v.to_f64(),
705        StateValue::Pose(_) => 0.0,
706    };
707    let (_, a, b) = joint(reader, name)?;
708    // Where the parts cannot be measured, there is nothing to start from.
709    let seed = (measured(reader, a, b, near).ok())
710        .map(|at| StateValue::Number(Scalar::from_f64(at[motion.index()])));
711    Ok(Unknown {
712        state,
713        mobility: Mobility::Follows,
714        seed,
715        unless: Some(name.to_string()),
716    })
717}
718
719/// The coordinates of the joint `name`, called `label`, as values a user
720/// sets, with their limits: the generator of its settings.
721fn settings(reader: &Reader, (name, label): &(String, String)) -> GeopResult<Settings> {
722    let (kind, a, b) = joint(reader, name)?;
723    Ok(Settings {
724        name: name.clone(),
725        label: label.clone(),
726        kind: kind.label(),
727        between: vec![a.entity.label(), b.entity.label()],
728        values: (kind.motions().into_iter())
729            .map(|motion| {
730                let [min, max] = kind.limits(motion);
731                Setting {
732                    state: joint_parameter(name, motion),
733                    measure: motion.measure(),
734                    min,
735                    max,
736                }
737            })
738            .collect(),
739    })
740}
741
742/// Whether the constraint of the mate `name`, as `reader` reads the part,
743/// holds.
744pub(crate) fn mate_holds(reader: &Reader, name: &str) -> bool {
745    let (Ok(constraints), Ok(tolerance)) = (
746        Constraints::of(reader),
747        scale_of(reader).and_then(tolerance),
748    ) else {
749        return false;
750    };
751    (constraints.get(name)).is_some_and(|constraint| constraint.holds(tolerance))
752}