Joints mate by default (Fusion's convention): origin B's z is turned against origin A's, so two picked faces meet face to face and B sits on A instead of inside it; Flip now means don't mate. File format 4 inverts older joints' flip so they keep their placement; as-built joints and rigid groups stay where they are

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 11:37:33 +00:00
1 parent d12c955220
commit 2c383460e9
5 files changed
+46 -14

No files matched your search

+1 -1
View File
@@ -303,7 +303,7 @@ fn as_built(s: &mut Session, p: &Value) -> Result<Value> {
b: JointOrigin { occurrence: ob, snap: fb },
values: vec![0.0; n],
limits: limits_param(s, p, kind, cmd)?,
flip: false,
flip: true, // as built: the origins coincide as they are, not mated
offset: 0.0,
angle: 0.0,
suppressed: false,
+17 -8
View File
@@ -45,7 +45,7 @@ fn revolute_hinge_turns_about_the_knuckle() {
&mut s,
"JointAssembleCmdNew",
json!({
"type": "revolute", "a": {"occurrence": "LeafA", "circle": [1, 37, 4]}, "b": {"occurrence": "LeafB", "circle": [1, 37, 4]}, "name": "Hinge"}),
"type": "revolute", "a": {"occurrence": "LeafA", "circle": [1, 37, 4]}, "b": {"occurrence": "LeafB", "circle": [1, 37, 4]}, "flip": true, "name": "Hinge"}),
);
assert_eq!(r["conflicts"].as_array().map(Vec::len), Some(0), "{r}");
// Same frames: B stays put at angle 0.
@@ -89,7 +89,7 @@ fn slider_limits_clamp() {
let r = run(
&mut s,
"JointAssembleCmdNew",
json!({"type": "slider", "a": {"face": [5, 5, 10]}, "b": {"face": [25, 5, 0]}, "flip": true, "limits": [[0, 10]], "name": "Rail"}),
json!({"type": "slider", "a": {"face": [5, 5, 10]}, "b": {"face": [25, 5, 0]}, "limits": [[0, 10]], "name": "Rail"}),
);
assert_eq!(r["conflicts"].as_array().map(Vec::len), Some(0), "{r}");
// B now sits on A.
@@ -154,11 +154,7 @@ fn rigid_cycles_solve_and_conflicts_report() {
#[test]
fn motion_links_and_interference() {
let mut s = two_cubes();
run(
&mut s,
"JointAssembleCmdNew",
json!({"type": "cylindrical", "a": {"face": [5, 5, 10]}, "b": {"face": [25, 5, 0]}, "flip": true, "name": "Screw"}),
);
run(&mut s, "JointAssembleCmdNew", json!({"type": "cylindrical", "a": {"face": [5, 5, 10]}, "b": {"face": [25, 5, 0]}, "name": "Screw"}));
run(&mut s, "FusionMoveJointsCommand", json!({"joint": "Screw", "values": [0, 0]}));
// No overlap while B sits on A; then push B 4 mm into A.
let i = run(&mut s, "InterferenceCheckCommand", json!({}));
@@ -174,7 +170,7 @@ fn motion_links_and_interference() {
run(
&mut s,
"JointAssembleCmdNew",
json!({"type": "slider", "a": {"occurrence": "A", "face": [5, 5, 10]}, "b": {"occurrence": "D", "face": [65, 5, 0]}, "flip": true, "name": "Follower"}),
json!({"type": "slider", "a": {"occurrence": "A", "face": [5, 5, 10]}, "b": {"occurrence": "D", "face": [65, 5, 0]}, "name": "Follower"}),
);
run(&mut s, "FusionMotionRelationshipCommand", json!({"a": "Screw", "ia": 1, "b": "Follower", "ratio": 2}));
run(&mut s, "FusionMoveJointsCommand", json!({"joint": "Screw", "values": [0, 3]}));
@@ -186,3 +182,16 @@ fn motion_links_and_interference() {
let l = run(&mut t, "joint.list", json!({}));
assert_eq!(l["joints"].as_array().map(Vec::len), Some(2));
}
/// Two faces joined mate: B's bottom face lands on A's top face, B on top of A (not inside
/// it); Flip turns B over instead.
#[test]
fn faces_mate_by_default() {
let mut s = two_cubes();
run(&mut s, "JointAssembleCmdNew", json!({"type": "rigid", "a": {"face": [5, 5, 10]}, "b": {"face": [25, 5, 0]}, "name": "Stack"}));
// B's bottom centre (25, 5, 0) at A's top centre, B above it.
assert!(near(place(&s, "B", Vec3::new(25.0, 5.0, 0.0)), Vec3::new(5.0, 5.0, 10.0)));
assert!(near(place(&s, "B", Vec3::new(25.0, 5.0, 10.0)), Vec3::new(5.0, 5.0, 20.0)), "{:?}", place(&s, "B", Vec3::new(25.0, 5.0, 10.0)));
run(&mut s, "joint.edit", json!({"joint": "Stack", "flip": true}));
assert!(near(place(&s, "B", Vec3::new(25.0, 5.0, 10.0)), Vec3::new(5.0, 5.0, 0.0)), "{:?}", place(&s, "B", Vec3::new(25.0, 5.0, 10.0)));
}
+1 -1
View File
@@ -32,7 +32,7 @@ fn rail() -> Session {
run(&mut s, "component.activate", json!({"component": "root"}));
let a = occ(&s, "A");
run(&mut s, "occurrence.ground", json!({"occurrence": a, "grounded": true}));
run(&mut s, "JointAssembleCmdNew", json!({"type": "slider", "a": {"face": [10, 5, 5]}, "b": {"face": [20, 5, 5]}, "flip": true, "name": "Rail"}));
run(&mut s, "JointAssembleCmdNew", json!({"type": "slider", "a": {"face": [10, 5, 5]}, "b": {"face": [20, 5, 5]}, "name": "Rail"}));
run(&mut s, "FusionMoveJointsCommand", json!({"joint": "Rail", "value": 10}));
assert!((b_x(&s) - 20.0).abs() < 1e-6, "{}", b_x(&s));
s