use super::super::mapping::{
effective_align, local_point, mate, require_direction, require_plane, ConstraintFailure, MappedConstraint,
ResolvedElement, FACING,
};
use crate::feature_pipeline::assembly::ConstraintEntry;
use super::{elements_field, id_field, pair_applicable, schema_entry, ConstraintTypeDef};
use crate::feature_pipeline::SelectionProbe;
use crate::{MateKind, SelectionGeometry};
pub(super) const DEF: ConstraintTypeDef = ConstraintTypeDef {
type_id: "coincident",
label: "Coincident",
short_name: "COIN",
icon: "\u{2261}",
long_name: "\u{2261} Coincident",
min_elements: 2,
max_elements: 2,
duplicate_family: false,
applicable,
};
fn applicable(probe: &SelectionProbe) -> bool {
pair_applicable(probe, probe.faces + probe.edges + probe.solids)
}
pub(super) fn schema() -> serde_json::Value {
schema_entry(&DEF, serde_json::json!({
"id": id_field(),
"elements": elements_field(&["VERTEX", "EDGE", "FACE", "COMPONENT"], 2, 2,
"Select two elements to make coincident"),
"facing": super::super::mapping::facing_field("Two faces: Aligned or Opposed normals on the shared plane. Unset, a new constraint takes the facing it is placed with"),
}))
}
pub(in crate::feature_pipeline::assembly) fn map_entry(
entry: &mut ConstraintEntry,
a: &ResolvedElement,
b: &ResolvedElement,
) -> Result<MappedConstraint, ConstraintFailure> {
let (SelectionGeometry::Plane { origin, normal }, SelectionGeometry::Plane { .. }) = (&a.world, &b.world) else {
return map(a, b);
};
let (wa, _) = require_direction(a)?;
let (wb, _) = require_direction(b)?;
let legacy = entry.input_params.get(FACING).is_none() && entry.persistent("kernel").is_some();
if legacy && wa.dot(wb).abs() < 1.0 - 1e-9 {
let mut mapped = map(a, b)?;
mapped.note = Some(
"Saved before Coincident made two faces coplanar, and these faces are not parallel: it still holds their points together. Pick the faces again to make them coplanar".into(),
);
return Ok(mapped);
}
let align = effective_align(entry, wa, wb, "reverse");
let height = b.world.representative_point().sub(*origin).dot(*normal);
Ok(MappedConstraint {
mates: vec![mate(a, b, MateKind::CoincidentPlanePlane {
plane_a: require_plane(a)?,
plane_b: require_plane(b)?,
align,
})],
measured: Some((height.abs(), "mm")),
..Default::default()
})
}
pub(in crate::feature_pipeline::assembly) fn map(
a: &ResolvedElement,
b: &ResolvedElement,
) -> Result<MappedConstraint, ConstraintFailure> {
let mapped = match (&a.world, &b.world) {
(SelectionGeometry::Plane { origin, normal }, other)
if !matches!(other, SelectionGeometry::Plane { .. }) =>
{
let height = (b.world.representative_point().sub(*origin)).dot(*normal);
MappedConstraint {
mates: vec![mate(b, a, MateKind::CoincidentPointPlane {
point_a: local_point(b),
plane_b: require_plane(a)?,
})],
measured: Some((height.abs(), "mm")),
..Default::default()
}
}
(other, SelectionGeometry::Plane { origin, normal })
if !matches!(other, SelectionGeometry::Plane { .. }) =>
{
let height = (a.world.representative_point().sub(*origin)).dot(*normal);
MappedConstraint {
mates: vec![mate(a, b, MateKind::CoincidentPointPlane {
point_a: local_point(a),
plane_b: require_plane(b)?,
})],
measured: Some((height.abs(), "mm")),
..Default::default()
}
}
_ => {
let gap = a
.world
.representative_point()
.sub(b.world.representative_point())
.length();
MappedConstraint {
mates: vec![mate(a, b, MateKind::CoincidentPointPoint {
point_a: local_point(a),
point_b: local_point(b),
})],
measured: Some((gap, "mm")),
..Default::default()
}
}
};
Ok(mapped)
}