1use 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
67pub fn mates_resolved() -> usize {
72 MATES_RESOLVED.get()
73}
74
75fn 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
94fn 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
124fn 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
138fn 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
152pub(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 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
208fn resolved<'a>(reader: &Reader<'a>, name: &str) -> GeopResult<&'a Resolved> {
210 reader.retrieve_element(&ElementReference::whole(mate(name)))
211}
212
213#[derive(Clone, Copy, PartialEq)]
215enum Read {
216 Pose,
218 Coordinate,
220}
221
222struct Reading<'a> {
225 read: BTreeMap<String, Read>,
226 by: Option<&'a str>,
227 twist: Pose<D>,
228 known: &'a Known,
229}
230
231#[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 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 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 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 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
326type Joint<'a> = (&'a JointKind, &'a On<Connector>, &'a On<Connector>);
328
329fn 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
337fn 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
380fn differentiated(
385 reader: &Reader,
386 bound: Bound,
387 evaluate: impl Fn(&Reader, &mut Reading) -> GeopResult<Vec<D>>,
388) -> GeopResult<Constraint> {
389 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
424fn 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
434fn limit_name(state: &str, upper: bool) -> String {
437 format!("{state}.{}", if upper { "max" } else { "min" })
438}
439
440fn 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 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
469fn 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
500fn 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
544fn 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 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
578fn 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 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
614pub(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
640pub 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
654fn 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
670fn 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
685fn 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
697fn 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 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
719fn 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
742pub(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}