use crate::feature_pipeline::SelectionProbe;
pub(crate) mod angle;
pub(crate) mod cam;
pub(crate) mod center;
pub(crate) mod coincident;
pub(crate) mod concentric;
pub(crate) mod distance;
pub(crate) mod parallel;
pub(crate) mod path;
pub(crate) mod gear;
pub(super) mod mechanical;
pub(crate) mod perpendicular;
pub(crate) mod rack_pinion;
pub(crate) mod screw;
pub(crate) mod slot;
pub(crate) mod symmetric;
pub(crate) mod tangent;
pub(crate) mod touch_align;
pub struct ConstraintTypeDef {
pub type_id: &'static str,
pub short_name: &'static str,
pub icon: &'static str,
pub long_name: &'static str,
pub label: &'static str,
pub min_elements: usize,
pub max_elements: usize,
pub duplicate_family: bool,
pub applicable: fn(&SelectionProbe) -> bool,
}
pub const CONSTRAINT_TYPES: [ConstraintTypeDef; 16] = [
coincident::DEF,
touch_align::DEF,
parallel::DEF,
distance::DEF,
angle::DEF,
concentric::DEF,
perpendicular::DEF,
tangent::DEF,
center::DEF,
symmetric::DEF,
gear::DEF,
rack_pinion::DEF,
screw::DEF,
path::DEF,
slot::DEF,
cam::DEF,
];
pub(super) fn limit_fields(unit: &str, step: f64, default_max: f64) -> [(&'static str, serde_json::Value); 3] {
[
("limit", serde_json::json!({
"type": "boolean",
"default_value": false,
"hint": "Limit mate: hold the value anywhere between Minimum and Maximum instead of at the target; a drag stops at the ends"
})),
("minimum", serde_json::json!({
"type": "number",
"step": step,
"default_value": 0,
"hint": format!("Limit mate: the smallest allowed value ({unit}), in the same convention as the target")
})),
("maximum", serde_json::json!({
"type": "number",
"step": step,
"default_value": default_max,
"hint": format!("Limit mate: the largest allowed value ({unit}), in the same convention as the target")
})),
]
}
pub(super) fn apply_limit(
entry: &super::ConstraintEntry,
mapped: &mut super::mapping::MappedConstraint,
env: &crate::feature_pipeline::Env,
to_solver: impl Fn(f64) -> f64,
) -> Result<(), super::mapping::ConstraintFailure> {
if !entry.flag("limit") {
return Ok(());
}
let read = |key: &str, default: f64| {
entry
.number(key, env, default)
.map_err(|error| super::mapping::ConstraintFailure::new("error", error))
};
let (minimum, maximum) = (read("minimum", 0.0)?, read("maximum", 0.0)?);
if minimum > maximum {
return Err(super::mapping::ConstraintFailure::new(
"error",
format!("Limit minimum {minimum} is above the maximum {maximum}."),
));
}
let (a, b) = (to_solver(minimum), to_solver(maximum));
let (min, max) = if a <= b { (a, b) } else { (b, a) };
for mapped_mate in &mut mapped.mates {
let inner = std::mem::replace(&mut mapped_mate.kind, crate::MateKind::Parallel {
direction_a: [0.0, 0.0, 1.0],
direction_b: [0.0, 0.0, 1.0],
align: crate::MateAlign::Any,
});
mapped_mate.kind = crate::MateKind::Limit { mate: Box::new(inner), min, max };
}
mapped.target = None;
mapped.note = Some(format!("Limit {minimum} to {maximum}"));
Ok(())
}
pub fn constraint_hidden_params(type_id: &str, values: &serde_json::Value) -> Vec<&'static str> {
let target = match type_id {
"distance" => "distance",
"angle" => "angle",
_ => return Vec::new(),
};
if values.get("limit").and_then(|limit| limit.as_bool()).unwrap_or(false) {
vec![target]
} else {
vec!["minimum", "maximum"]
}
}
pub fn constraint_type(type_id: &str) -> Option<&'static ConstraintTypeDef> {
CONSTRAINT_TYPES.iter().find(|def| def.type_id == type_id)
}
pub fn constraint_schema_catalogue() -> serde_json::Value {
serde_json::Value::Array(vec![
coincident::schema(),
touch_align::schema(),
parallel::schema(),
distance::schema(),
angle::schema(),
concentric::schema(),
perpendicular::schema(),
tangent::schema(),
center::schema(),
symmetric::schema(),
gear::schema(),
rack_pinion::schema(),
screw::schema(),
path::schema(),
slot::schema(),
cam::schema(),
])
}
pub(super) fn pair_applicable(probe: &SelectionProbe, seedable: usize) -> bool {
probe.all_component && probe.components == 2 && seedable == 2
}
pub(super) fn id_field() -> serde_json::Value {
serde_json::json!({
"type": "string",
"default_value": null,
"hint": "Unique identifier for the constraint"
})
}
pub(super) fn elements_field(
filter: &[&str],
min: usize,
max: usize,
hint: &str,
) -> serde_json::Value {
serde_json::json!({
"type": "reference_selection",
"selectionFilter": filter,
"multiple": max > 1,
"minSelections": min,
"maxSelections": max,
"default_value": null,
"hint": hint
})
}
pub(super) fn schema_entry(
def: &ConstraintTypeDef,
params: serde_json::Value,
) -> serde_json::Value {
serde_json::json!({
"type": def.type_id,
"shortName": def.short_name,
"longName": def.long_name,
"label": def.label,
"icon": def.icon,
"inputParamsSchema": params,
})
}
pub fn flip_rule(constraint_type: &str, params: &serde_json::Value, geoms: &[&str]) -> Result<(serde_json::Value, &'static str), String> {
let toggle = |key: &str| {
let on = params.get(key).and_then(serde_json::Value::as_bool).unwrap_or(false);
serde_json::json!({ key: !on })
};
let negate = |key: &str| params.get(key).and_then(serde_json::Value::as_f64).map(|value| -value);
let limited = params.get("limit").and_then(serde_json::Value::as_bool).unwrap_or(false);
let mirrored_range = || {
let min = params.get("minimum").and_then(serde_json::Value::as_f64).unwrap_or(0.0);
let max = params.get("maximum").and_then(serde_json::Value::as_f64).unwrap_or(0.0);
serde_json::json!({ "minimum": -max, "maximum": -min })
};
match constraint_type {
"touch_align" | "parallel" | "concentric" | "center" => {
let opposed = params.get("facing").and_then(serde_json::Value::as_str) == Some("opposed");
Ok((serde_json::json!({ "facing": if opposed { "aligned" } else { "opposed" } }), "turn"))
}
"gear" | "rack_pinion" | "screw" => Ok((toggle("reverse"), "none")),
"path" | "slot" => Ok((toggle("reverse"), "none")),
"cam" => Ok((toggle("flip"), "turn")),
"distance" => {
if geoms.first() != Some(&"plane") {
return Err("a distance to a line or point has no side to flip to".into());
}
if limited {
return Ok((mirrored_range(), "mirror"));
}
let flipped = negate("distance").ok_or("the distance has no value to flip")?;
Ok((serde_json::json!({ "distance": flipped }), "mirror"))
}
"angle" => {
if limited {
return Ok((mirrored_range(), "rotate"));
}
let flipped = negate("angle").ok_or("the angle has no value to flip")?;
Ok((serde_json::json!({ "angle": flipped }), "rotate"))
}
"tangent" => Ok((serde_json::json!({}), "mirror")),
"perpendicular" => Ok((serde_json::json!({}), "turn-about-reference")),
"symmetric" => Err("a symmetric pair has one solution".into()),
"coincident" => Err("coincident elements have one solution".into()),
other => Err(format!("'{other}' has no flip")),
}
}