use super::super::mapping::{
local_point, require_axis, require_plane, ConstraintFailure, MappedConstraint, MappedMate,
ResolvedElement,
};
use super::{elements_field, id_field, schema_entry, ConstraintTypeDef};
use crate::feature_pipeline::SelectionProbe;
use crate::{MateKind, SelectionGeometry, SymmetricElement};
pub(super) const DEF: ConstraintTypeDef = ConstraintTypeDef {
type_id: "symmetric",
label: "Symmetric",
short_name: "SYMM",
icon: "\u{22C8}",
long_name: "\u{22C8} Symmetric",
min_elements: 3,
max_elements: 3,
duplicate_family: false,
applicable,
};
fn applicable(probe: &SelectionProbe) -> bool {
probe.all_component
&& (2..=3).contains(&probe.components)
&& probe.faces >= 1
&& probe.faces + probe.edges == 3
}
pub(super) fn schema() -> serde_json::Value {
schema_entry(&DEF, serde_json::json!({
"id": id_field(),
"elements": elements_field(&["FACE", "EDGE", "VERTEX"], 3, 3,
"Two like elements on two components, then the planar face they are symmetric about"),
}))
}
fn element(resolved: &ResolvedElement) -> Result<SymmetricElement, ConstraintFailure> {
Ok(match resolved.world {
SelectionGeometry::Plane { .. } => SymmetricElement::Plane { plane: require_plane(resolved)? },
SelectionGeometry::Line { .. } | SelectionGeometry::Axis { .. } => {
SymmetricElement::Axis { axis: require_axis(resolved)? }
}
SelectionGeometry::Circle { .. }
| SelectionGeometry::Sphere { .. }
| SelectionGeometry::Point { .. } => SymmetricElement::Point { point: local_point(resolved) },
})
}
pub(in crate::feature_pipeline::assembly) fn map(
resolved: &[ResolvedElement],
) -> Result<MappedConstraint, ConstraintFailure> {
let [a, b, mirror] = resolved else {
return Err(ConstraintFailure::new("incomplete", "Select two elements and a mirror face."));
};
if a.component == b.component {
return Err(ConstraintFailure::new(
"invalid-selection",
format!(
"Both symmetric elements belong to component '{}' — pick one on each of two components.",
a.component
),
));
}
let plane_c = require_plane(mirror).map_err(|_| {
ConstraintFailure::unsupported(format!(
"the mirror '{}' (the third pick) must be a planar face",
mirror.name
))
})?;
let (element_a, element_b) = (element(a)?, element(b)?);
if std::mem::discriminant(&element_a) != std::mem::discriminant(&element_b) {
return Err(ConstraintFailure::unsupported(
"symmetric elements must be alike: two planar faces, two lines or two points",
));
}
Ok(MappedConstraint {
mates: vec![MappedMate {
body_a: a.component.clone(),
body_b: b.component.clone(),
body_c: Some(mirror.component.clone()),
kind: MateKind::Symmetric { element_a, element_b, plane_c },
}],
..Default::default()
})
}