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 <[email protected]>
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
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

+43
View File
@@ -21,6 +21,19 @@ fn ellipse(x: f32, y: f32, rx: f32, ry: f32, a0: f32, a1: f32, n: usize) -> Vec<
(0..=n).map(|i| a0 + (a1 - a0) * i as f32 / n as f32).map(|a| (x + rx * a.cos(), y + ry * a.sin())).collect()
}
/// A gear's toothed outline about (x, y): `n` teeth from the root radius `root` out to `tip`,
/// turned by `phase` radians.
fn teeth(x: f32, y: f32, root: f32, tip: f32, n: usize, phase: f32) -> Vec<(f32, f32)> {
let step = std::f32::consts::TAU / n as f32;
let at = |a: f32, r: f32| (x + r * a.cos(), y - r * a.sin());
(0..n)
.flat_map(|k| {
let a = phase + step * k as f32;
[at(a - 0.3 * step, root), at(a - 0.15 * step, tip), at(a + 0.15 * step, tip), at(a + 0.3 * step, root)]
})
.collect()
}
struct Pen<'a> {
p: &'a Painter,
r: Rect,
@@ -1256,6 +1269,36 @@ pub(crate) fn paint_with(p: &Painter, r: Rect, name: &str, ink: Color32, fill: C
pen.dot(7.0, 14.0, a);
pen.dot(17.5, 9.0, a);
}
// Couplings: the driven part in the accent.
"gear" => {
pen.circle(9.0, 14.0, 5.2, pen.fill, Color32::TRANSPARENT);
pen.closed(&teeth(9.0, 14.0, 5.2, 6.8, 9, 0.0), ink);
pen.dot(9.0, 14.0, ink);
pen.circle(17.6, 7.4, 3.2, pen.fill, Color32::TRANSPARENT);
pen.closed(&teeth(17.6, 7.4, 3.2, 4.6, 6, 0.3), a);
pen.dot(17.6, 7.4, a);
}
"rack_pinion" => {
pen.circle(12.0, 7.4, 4.2, pen.fill, Color32::TRANSPARENT);
pen.closed(&teeth(12.0, 7.4, 4.2, 5.8, 8, 0.2), ink);
pen.dot(12.0, 7.4, ink);
let mut rack = vec![(2.0, 18.0)];
for k in 0..5 {
let x = 2.5 + 4.0 * k as f32;
rack.extend([(x, 14.3), (x + 1.0, 12.8), (x + 2.2, 12.8), (x + 3.2, 14.3)]);
}
rack.extend([(22.0, 14.3), (22.0, 18.0)]);
pen.closed(&rack, a);
pen.arrow((7.0, 21.5), (17.5, 21.5), a);
}
"screw" => {
pen.poly(&[(9.5, 2.0), (14.5, 2.0), (14.5, 22.0), (9.5, 22.0)], pen.fill, ink);
for y in [5.0, 8.0, 17.0, 20.0] {
pen.line(&[(9.5, y), (14.5, y - 2.0)], ink);
}
pen.poly(&[(6.0, 10.0), (18.0, 10.0), (18.0, 15.0), (6.0, 15.0)], pen.fill, a);
pen.arrow((21.0, 17.0), (21.0, 6.0), a);
}
"interference" => {
pen.closed(&[(3.0, 5.0), (14.0, 5.0), (14.0, 16.0), (3.0, 16.0)], ink);
pen.poly(&[(9.0, 10.0), (14.0, 10.0), (14.0, 16.0), (9.0, 16.0)], ic.interference, ic.interference);
+20 -1
View File
@@ -760,6 +760,9 @@
"cmd.joint_limits": "Gelenkgrenzen",
"cmd.joint_list": "Gelenke auflisten",
"cmd.joint_motion_link": "Bewegungsverknüpfung",
"cmd.joint_gear": "Zahnrad",
"cmd.joint_rack_pinion": "Zahnstange und Ritzel",
"cmd.joint_screw": "Schraube",
"cmd.joint_origin": "Gelenkursprung",
"cmd.joint_rigid_group": "Starrgruppe",
"cmd.joint_solve": "Gelenke lösen",
@@ -1122,5 +1125,21 @@
"Min": "Min",
"Max": "Max",
"Length": "Länge",
"Area": "Fläche"
"Area": "Fläche",
"coupling.gear_1": "Zahnrad 1",
"coupling.gear_2": "Zahnrad 2",
"coupling.pinion": "Ritzel",
"coupling.rack": "Zahnstange",
"coupling.nut": "Mutter",
"coupling.radius_1": "Radius 1",
"coupling.radius_2": "Radius 2",
"coupling.internal": "Innenzahnrad",
"coupling.pitch": "Steigung",
"coupling.picked": "gewählt",
"coupling.not_round": "keine runde Fläche",
"coupling.gear_1_pick": "Zahnrad 1 (optional)",
"coupling.gear_2_pick": "Zahnrad 2 (optional)",
"coupling.pinion_pick": "Ritzel (optional)",
"coupling.needs_turns": "braucht zwei Gelenke, die sich drehen",
"coupling.needs_turn_and_slide": "braucht ein Gelenk, das sich dreht, und eines, das gleitet"
}
+20 -1
View File
@@ -125,6 +125,9 @@
"cmd.joint_limits": "Joint Limits",
"cmd.joint_list": "List Joints",
"cmd.joint_motion_link": "Motion Link",
"cmd.joint_gear": "Gear",
"cmd.joint_rack_pinion": "Rack and Pinion",
"cmd.joint_screw": "Screw",
"cmd.joint_origin": "Joint Origin",
"cmd.joint_rigid_group": "Rigid Group",
"cmd.joint_solve": "Solve Joints",
@@ -1377,5 +1380,21 @@
"Centre to centre": "Centre to centre",
"Overall length": "Overall length",
"Min": "Min",
"Max": "Max"
"Max": "Max",
"coupling.gear_1": "Gear 1",
"coupling.gear_2": "Gear 2",
"coupling.pinion": "Pinion",
"coupling.rack": "Rack",
"coupling.nut": "Nut",
"coupling.radius_1": "Radius 1",
"coupling.radius_2": "Radius 2",
"coupling.internal": "Internal gear",
"coupling.pitch": "Pitch",
"coupling.picked": "picked",
"coupling.not_round": "not a round face",
"coupling.gear_1_pick": "Gear 1 (optional)",
"coupling.gear_2_pick": "Gear 2 (optional)",
"coupling.pinion_pick": "Pinion (optional)",
"coupling.needs_turns": "needs two joints that turn",
"coupling.needs_turn_and_slide": "needs a joint that turns and one that slides"
}
+20 -1
View File
@@ -125,6 +125,9 @@
"cmd.joint_limits": "Limites de liaison",
"cmd.joint_list": "Lister les liaisons",
"cmd.joint_motion_link": "Lien de mouvement",
"cmd.joint_gear": "Engrenage",
"cmd.joint_rack_pinion": "Pignon et crémaillère",
"cmd.joint_screw": "Vis",
"cmd.joint_origin": "Origine de liaison",
"cmd.joint_rigid_group": "Groupe rigide",
"cmd.joint_solve": "Résoudre les liaisons",
@@ -1230,5 +1233,21 @@
"RULED SURFACE": "SURFACE RÉGLÉE",
"Mitre": "Onglet",
"Reference face": "Face de référence",
"Round": "Arrondi"
"Round": "Arrondi",
"coupling.gear_1": "Engrenage 1",
"coupling.gear_2": "Engrenage 2",
"coupling.pinion": "Pignon",
"coupling.rack": "Crémaillère",
"coupling.nut": "Écrou",
"coupling.radius_1": "Rayon 1",
"coupling.radius_2": "Rayon 2",
"coupling.internal": "Engrenage intérieur",
"coupling.pitch": "Pas",
"coupling.picked": "sélectionné",
"coupling.not_round": "pas une face ronde",
"coupling.gear_1_pick": "Engrenage 1 (facultatif)",
"coupling.gear_2_pick": "Engrenage 2 (facultatif)",
"coupling.pinion_pick": "Pignon (facultatif)",
"coupling.needs_turns": "nécessite deux liaisons qui tournent",
"coupling.needs_turn_and_slide": "nécessite une liaison qui tourne et une qui glisse"
}
+20 -1
View File
@@ -125,6 +125,9 @@
"cmd.joint_limits": "ジョイントの制限",
"cmd.joint_list": "ジョイントを一覧表示",
"cmd.joint_motion_link": "モーション リンク",
"cmd.joint_gear": "ギア",
"cmd.joint_rack_pinion": "ラック アンド ピニオン",
"cmd.joint_screw": "ねじ",
"cmd.joint_origin": "ジョイント原点",
"cmd.joint_rigid_group": "リジッド グループ",
"cmd.joint_solve": "ジョイントを解決",
@@ -1377,5 +1380,21 @@
"Centre to centre": "中心間",
"Overall length": "全長",
"Min": "最小",
"Max": "最大"
"Max": "最大",
"coupling.gear_1": "ギア 1",
"coupling.gear_2": "ギア 2",
"coupling.pinion": "ピニオン",
"coupling.rack": "ラック",
"coupling.nut": "ナット",
"coupling.radius_1": "半径 1",
"coupling.radius_2": "半径 2",
"coupling.internal": "内歯車",
"coupling.pitch": "ピッチ",
"coupling.picked": "選択済み",
"coupling.not_round": "円筒面ではありません",
"coupling.gear_1_pick": "ギア 1 (オプション)",
"coupling.gear_2_pick": "ギア 2 (オプション)",
"coupling.pinion_pick": "ピニオン (オプション)",
"coupling.needs_turns": "回転するジョイントが 2 つ必要",
"coupling.needs_turn_and_slide": "回転するジョイントとスライドするジョイントが必要"
}
+20 -1
View File
@@ -760,6 +760,9 @@
"cmd.joint_limits": "관절 제한",
"cmd.joint_list": "관절 목록",
"cmd.joint_motion_link": "모션 링크",
"cmd.joint_gear": "기어",
"cmd.joint_rack_pinion": "랙 앤 피니언",
"cmd.joint_screw": "나사",
"cmd.joint_origin": "관절 원점",
"cmd.joint_rigid_group": "강체 그룹",
"cmd.joint_solve": "관절 해석",
@@ -1122,5 +1125,21 @@
"Min": "최소",
"Max": "최대",
"Length": "길이",
"Area": "면적"
"Area": "면적",
"coupling.gear_1": "기어 1",
"coupling.gear_2": "기어 2",
"coupling.pinion": "피니언",
"coupling.rack": "랙",
"coupling.nut": "너트",
"coupling.radius_1": "반지름 1",
"coupling.radius_2": "반지름 2",
"coupling.internal": "내접 기어",
"coupling.pitch": "피치",
"coupling.picked": "선택됨",
"coupling.not_round": "원통 면이 아님",
"coupling.gear_1_pick": "기어 1 (선택 사항)",
"coupling.gear_2_pick": "기어 2 (선택 사항)",
"coupling.pinion_pick": "피니언 (선택 사항)",
"coupling.needs_turns": "회전하는 관절 두 개가 필요함",
"coupling.needs_turn_and_slide": "회전하는 관절과 미끄러지는 관절이 필요함"
}
+20 -1
View File
@@ -114,6 +114,9 @@
"cmd.joint_limits": "Ограничения сопряжения",
"cmd.joint_list": "Список сопряжений",
"cmd.joint_motion_link": "Связь движений",
"cmd.joint_gear": "Зубчатая передача",
"cmd.joint_rack_pinion": "Реечная передача",
"cmd.joint_screw": "Винт",
"cmd.joint_origin": "Точка сопряжения",
"cmd.joint_rigid_group": "Жёсткая группа",
"cmd.joint_solve": "Решить сопряжения",
@@ -1142,5 +1145,21 @@
"render.opaque_image_summary": "Предпросмотр PNG · непрозрачный PBR · прозрачность через смешивание",
"joint.one_component": "Эти детали - тела одного компонента: {bodies}. Сопряжение двигает один компонент относительно другого, поэтому каждой детали нужен свой компонент.",
"joint.one_body": "Оба выбора на {body}. Выберите второй на другой детали.",
"joint.from_bodies_hint": "Каждое выбранное тело переходит в свой новый компонент и остаётся на месте."
"joint.from_bodies_hint": "Каждое выбранное тело переходит в свой новый компонент и остаётся на месте.",
"coupling.gear_1": "Шестерня 1",
"coupling.gear_2": "Шестерня 2",
"coupling.pinion": "Шестерня",
"coupling.rack": "Рейка",
"coupling.nut": "Гайка",
"coupling.radius_1": "Радиус 1",
"coupling.radius_2": "Радиус 2",
"coupling.internal": "Внутреннее зацепление",
"coupling.pitch": "Шаг",
"coupling.picked": "выбрано",
"coupling.not_round": "не круглая грань",
"coupling.gear_1_pick": "Шестерня 1 (необязательно)",
"coupling.gear_2_pick": "Шестерня 2 (необязательно)",
"coupling.pinion_pick": "Шестерня (необязательно)",
"coupling.needs_turns": "нужны два вращающихся сопряжения",
"coupling.needs_turn_and_slide": "нужны вращающееся и скользящее сопряжения"
}
+20 -1
View File
@@ -115,6 +115,9 @@
"cmd.joint_limits": "Обмеження з'єднання",
"cmd.joint_list": "Список з'єднань",
"cmd.joint_motion_link": "Зв'язок руху",
"cmd.joint_gear": "Зубчаста передача",
"cmd.joint_rack_pinion": "Рейкова передача",
"cmd.joint_screw": "Гвинт",
"cmd.joint_origin": "Початок з'єднання",
"cmd.joint_rigid_group": "Жорстка група",
"cmd.joint_solve": "Розв'язати з'єднання",
@@ -1061,5 +1064,21 @@
"drive.unconnected": "Нічого не рухається: {part} не з'єднано ні з чим закріпленим",
"joint.one_component": "Ці деталі - тіла одного компонента: {bodies}. З'єднання рухає один компонент відносно іншого, тому кожній деталі потрібен власний компонент.",
"joint.one_body": "Обидва вибори на {body}. Виберіть другий на іншій деталі.",
"joint.from_bodies_hint": "Кожне вибране тіло переходить у власний новий компонент і лишається на місці."
"joint.from_bodies_hint": "Кожне вибране тіло переходить у власний новий компонент і лишається на місці.",
"coupling.gear_1": "Шестерня 1",
"coupling.gear_2": "Шестерня 2",
"coupling.pinion": "Шестерня",
"coupling.rack": "Рейка",
"coupling.nut": "Гайка",
"coupling.radius_1": "Радіус 1",
"coupling.radius_2": "Радіус 2",
"coupling.internal": "Внутрішнє зачеплення",
"coupling.pitch": "Крок",
"coupling.picked": "вибрано",
"coupling.not_round": "не кругла грань",
"coupling.gear_1_pick": "Шестерня 1 (необов'язково)",
"coupling.gear_2_pick": "Шестерня 2 (необов'язково)",
"coupling.pinion_pick": "Шестерня (необов'язково)",
"coupling.needs_turns": "потрібні два обертові з'єднання",
"coupling.needs_turn_and_slide": "потрібні обертове й ковзне з'єднання"
}
+20 -1
View File
@@ -125,6 +125,9 @@
"cmd.joint_limits": "接头限制",
"cmd.joint_list": "列出接头",
"cmd.joint_motion_link": "运动关联",
"cmd.joint_gear": "齿轮",
"cmd.joint_rack_pinion": "齿轮齿条",
"cmd.joint_screw": "螺旋",
"cmd.joint_origin": "接头原点",
"cmd.joint_rigid_group": "刚性组",
"cmd.joint_solve": "解算接头",
@@ -1377,5 +1380,21 @@
"Centre to centre": "中心距",
"Overall length": "总长",
"Min": "最小",
"Max": "最大"
"Max": "最大",
"coupling.gear_1": "齿轮 1",
"coupling.gear_2": "齿轮 2",
"coupling.pinion": "小齿轮",
"coupling.rack": "齿条",
"coupling.nut": "螺母",
"coupling.radius_1": "半径 1",
"coupling.radius_2": "半径 2",
"coupling.internal": "内齿轮",
"coupling.pitch": "螺距",
"coupling.picked": "已拾取",
"coupling.not_round": "不是圆柱面",
"coupling.gear_1_pick": "齿轮 1(可选)",
"coupling.gear_2_pick": "齿轮 2(可选)",
"coupling.pinion_pick": "小齿轮(可选)",
"coupling.needs_turns": "需要两个旋转接头",
"coupling.needs_turn_and_slide": "需要一个旋转接头和一个滑动接头"
}