From 9313784304fdd8fbdbc6daaad94a67dd86bcf781 Mon Sep 17 00:00:00 2001 From: tylerjenningsw Date: Tue, 20 Oct 2026 08:47:51 -0400 Subject: [PATCH] Clipboard: Orientation and Rotation headers as After Effects writes them (#1908) --- crates/engine/src/keyframe_text.rs | 7 ++++--- crates/engine/src/tests_keyframe_text.rs | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/keyframe_text.rs b/crates/engine/src/keyframe_text.rs index 0fd6f9cc2..8fca3c343 100644 --- a/crates/engine/src/keyframe_text.rs +++ b/crates/engine/src/keyframe_text.rs @@ -48,13 +48,14 @@ pub(crate) fn text(project: &Project, comp: &Comp, keys: &[KeyRef], statics: &[( continue; }; let Some(dims) = rows.first().map(|(_, v)| v.len()).filter(|n| *n > 0) else { continue }; + // After Effects labels only the first of Orientation's three columns, and names Z + // Rotation plain Rotation (as AE2Blend expects). let columns = match dims { 1 => vec![unit.to_string()], + _ if *id == "orientation" => vec![format!("X {unit}")], _ => ["X", "Y", "Z"].iter().take(dims).map(|axis| format!("{axis} {unit}")).collect(), }; - // 3D layers show Rotation as Z Rotation. - let name = if *id == "rotation" && layer.is_3d() { "Z Rotation" } else { pr.name.as_str() }; - blocks.push_str(&format!("Transform\t{name}\n\tFrame\t{}\n", columns.join("\t"))); + blocks.push_str(&format!("Transform\t{}\n\tFrame\t{}\n", pr.name, columns.join("\t"))); for (frame, values) in rows { let values: Vec = values.into_iter().map(num).collect(); blocks.push_str(&format!("\t{frame}\t{}\n", values.join("\t"))); diff --git a/crates/engine/src/tests_keyframe_text.rs b/crates/engine/src/tests_keyframe_text.rs index ce3ec4899..34966269b 100644 --- a/crates/engine/src/tests_keyframe_text.rs +++ b/crates/engine/src/tests_keyframe_text.rs @@ -98,14 +98,15 @@ fn a_copied_transform_group_lists_what_the_layer_shows() { let names: Vec<&str> = text.lines().filter_map(|line| line.strip_prefix("Transform\t")).collect(); assert_eq!(names, ["Anchor Point", "Position", "Scale", "Rotation", "Opacity"], "a 2D layer has no Orientation or X / Y Rotation"); assert!(text.contains("\tFrame\tX percent\tY percent\tZ percent\n\t0\t50\t50\t100\n\t24\t150\t150\t100\n"), "{text}"); - // 3D: Orientation, X / Y Rotation, and Rotation is Z Rotation. + // 3D: Orientation and X / Y Rotation. Z Rotation stays Rotation, and Orientation's three + // values sit under one label, as After Effects writes them. edit_layer(&mut s, l, |l| l.switches.three_d = true); s.execute("prop.select", json!({"layer": l.0, "prop": transform, "selectKeys": false})).unwrap(); s.execute("edit.copy", json!({})).unwrap(); let text = s.state.keyframe_text.clone().unwrap(); let names: Vec<&str> = text.lines().filter_map(|line| line.strip_prefix("Transform\t")).collect(); - assert_eq!(names, ["Anchor Point", "Position", "Scale", "Orientation", "X Rotation", "Y Rotation", "Z Rotation", "Opacity"]); - assert!(text.contains("Transform\tOrientation\n\tFrame\tX degrees\tY degrees\tZ degrees\n\t\t0\t0\t0\n"), "{text}"); + assert_eq!(names, ["Anchor Point", "Position", "Scale", "Orientation", "X Rotation", "Y Rotation", "Rotation", "Opacity"]); + assert!(text.contains("Transform\tOrientation\n\tFrame\tX degrees\n\t\t0\t0\t0\n"), "{text}"); } #[test]