Joints: gears, rack and pinion, and screws (#1524)

Motion links can carry a coupling (gear pair, rack and pinion, screw) whose ratio follows radii, picked round faces or pitch. Design format 23.

Co-authored-by: SychPL <SychPL@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-15 18:47:38 -04:00
1 parent a519f3cf74
commit f7967b1ff6
29 files changed
+1476 -54

No files matched your search

+27 -4
View File
@@ -1,5 +1,5 @@
//! Assembly dialogs: New Component, Joint, As-built Joint, Joint Origin, Rigid Group, Drive
//! Joints, Motion Link and Interference. Joint origins are picked by hovering geometry: a face
//! Joints, Motion Link, Gear, Rack and Pinion, Screw (`dialogs_coupling`) and Interference. Joint origins are picked by hovering geometry: a face
//! snaps to its centre (or a round face to the centre of its nearer end), an edge to its middle
//! (a round edge to its centre), a vertex to itself; the snap shows as a glyph with its z axis
//! before the click. Joints and drives preview on the placed (world) model: the moving
@@ -27,8 +27,19 @@ const TYPE_LABELS: [&str; 7] = ["Rigid", "Revolute", "Slider", "Cylindrical", "P
/// What a joint origin input accepts (snaps on faces, edges and vertices).
const SNAPS: u16 = FACES | EDGES | VERTICES;
/// Commands whose effect is where occurrences sit (previewed on the placed model).
const MOVES: [&str; 8] =
["joint.create", "joint.as_built", "joint.drive", "joint.edit", "joint.limits", "joint.rigid_group", "parts.insert", "parts.fastener"];
const MOVES: [&str; 11] = [
"joint.create",
"joint.as_built",
"joint.drive",
"joint.edit",
"joint.limits",
"joint.rigid_group",
"joint.gear",
"joint.rack_pinion",
"joint.screw",
"parts.insert",
"parts.fastener",
];
#[derive(Clone, Debug)]
pub enum Asm {
@@ -56,6 +67,7 @@ pub enum Asm {
ratio: String,
offset: String,
},
Coupling(Box<crate::dialogs_coupling::CouplingForm>),
Interference {
result: Option<Value>,
overlaps: Vec<Mesh>,
@@ -128,12 +140,17 @@ impl Asm {
Asm::RigidGroup => "RIGID GROUP",
Asm::Drive { .. } => "DRIVE JOINTS",
Asm::MotionLink { .. } => "MOTION LINK",
Asm::Coupling(f) => match f.command {
"joint.gear" => "GEAR",
"joint.rack_pinion" => "RACK AND PINION",
_ => "SCREW",
},
Asm::Interference { .. } => "INTERFERENCE",
}
}
pub fn previews(&self) -> bool {
matches!(self, Asm::Joint(_) | Asm::Drive { .. } | Asm::RigidGroup)
matches!(self, Asm::Joint(_) | Asm::Drive { .. } | Asm::RigidGroup | Asm::Coupling(_))
}
}
@@ -166,6 +183,7 @@ pub fn start(app: &SolveApp, id: &str) -> Option<(Kind, Vec<SelInput>)> {
let b = usize::from(movable(s).len() > 1);
(Asm::MotionLink { a: 0, b, ia: 0, ib: 0, ratio: "1".into(), offset: "0".into() }, vec![])
}
"joint.gear" | "joint.rack_pinion" | "joint.screw" => crate::dialogs_coupling::start(app, id)?,
"inspect.interference" => {
(Asm::Interference { result: None, overlaps: Vec::new(), of: Vec::new() }, vec![SelInput::new("Bodies (all if none)", BODIES, true)])
}
@@ -657,6 +675,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
enter |= field(ui, offset);
ui.end_row();
}
Asm::Coupling(f) => enter |= crate::dialogs_coupling::rows(app, ui, f, inputs),
Asm::Interference { result, overlaps, of } => {
let picked: Vec<Sel> = inputs.first().map(|i| i.items.clone()).unwrap_or_default();
row_label(ui, "");
@@ -883,6 +902,7 @@ pub fn commands(app: &SolveApp, k: &Asm, inputs: &[SelInput]) -> Result<Vec<(Str
let ratio = eval(ratio, ValueKind::Unitless)?;
cmd("joint.motion_link", json!({"a": ja.id, "b": jb.id, "ia": ia, "ib": ib, "ratio": ratio, "offset": offset.trim()}))
}
Asm::Coupling(f) => return crate::dialogs_coupling::commands(app, f, inputs),
// The check runs from its Compute button; OK only closes.
Asm::Interference { .. } => Vec::new(),
})
@@ -890,6 +910,9 @@ pub fn commands(app: &SolveApp, k: &Asm, inputs: &[SelInput]) -> Result<Vec<(Str
/// The preview's commands: OK's, with the joint's values playing its motion while animating.
pub fn animate(app: &SolveApp, k: &Asm, cmds: &mut Vec<(String, Value)>) {
if let Asm::Coupling(f) = k {
crate::dialogs_coupling::animate(&app.session, f, cmds);
}
let Asm::Joint(f) = k else { return };
if !f.animate {
return;