diff --git a/crates/engine/src/cmd/menucmds.rs b/crates/engine/src/cmd/menucmds.rs index 0aa19b6ae..465e38851 100644 --- a/crates/engine/src/cmd/menucmds.rs +++ b/crates/engine/src/cmd/menucmds.rs @@ -76,7 +76,7 @@ pub fn specs() -> Vec { "Transform Each…", ["Object", "Transform"], Some("Cmd+Alt+Shift+D"), - "{scaleH?: % (100), scaleV?: % (100), moveH?: pt, moveV?: pt (down = +), rotate?: deg (counter-clockwise), reflectX?: bool (flip vertically), reflectY?: bool (flip horizontally), random?: bool, seed?: n, reference?: 0..8 (9-point grid, 4 = centre), copy?: bool, strokes?: bool (Scale Strokes & Effects), corners?: bool (Scale Corners; both default to the preferences), objects?: bool (Transform Objects, default true; false: the art stays and, with patterns, only its pattern tiles transform), patterns?: bool (Transform Patterns; default: prefs transformPatternTiles)} transform every selected object about its own reference point → {ids}", + "{scaleH?: % (100), scaleV?: % (100), moveH?: pt, moveV?: pt (down = +), rotate?: deg (counter-clockwise), reflectX?: bool (flip left-right, as Reflect X in the Transform Each dialog), reflectY?: bool (flip top-bottom, as Reflect Y), random?: bool, seed?: n, reference?: 0..8 (9-point grid, 4 = centre), copy?: bool, strokes?: bool (Scale Strokes & Effects), corners?: bool (Scale Corners; both default to the preferences), objects?: bool (Transform Objects, default true; false: the art stays and, with patterns, only its pattern tiles transform), patterns?: bool (Transform Patterns; default: prefs transformPatternTiles)} transform every selected object about its own reference point → {ids}", has_selection, transform_each ), @@ -363,7 +363,9 @@ fn transform_each_as(s: &mut Session, p: &Value, label: &str) -> Result { k_rot = rot * (rng.next() * 2.0 - 1.0); } let o = vectorcraft_geom::reference_point(b, refi); - let refl = Affine::scale_non_uniform(if ry { -1.0 } else { 1.0 }, if rx { -1.0 } else { 1.0 }); + // Reflect X / Reflect Y match Object › Transform › Transform Each and Effect › Distort › Transform: + // Reflect X flips left-right (negate x), Reflect Y flips top-bottom (negate y). + let refl = Affine::scale_non_uniform(if rx { -1.0 } else { 1.0 }, if ry { -1.0 } else { 1.0 }); let m = Affine::translate((k_mh, k_mv)) * about(o, Affine::rotate(-k_rot.to_radians()) * refl * Affine::scale_non_uniform(k_sh / 100.0, k_sv / 100.0)); if let Some(n) = d.node_mut(*id) { diff --git a/crates/engine/src/tests_menucmds.rs b/crates/engine/src/tests_menucmds.rs index a6ae6ebe5..e1edfebf5 100644 --- a/crates/engine/src/tests_menucmds.rs +++ b/crates/engine/src/tests_menucmds.rs @@ -357,6 +357,36 @@ fn transform_each_random_is_deterministic_with_seed() { assert!(x.x0.abs() <= 50.0 && x.width() == 100.0); } +/// #1910: Transform Each's Reflect X flips left-right; Reflect Y flips top-bottom (same as +/// Effect › Distort › Transform and Illustrator's Transform Each checkboxes). +#[test] +fn transform_each_reflect_x_flips_left_right_reflect_y_flips_top_bottom() { + let mut s = session(); + // A right-pointing triangle: tip at x=100, base on the left. + let id = + id_of(&s.execute("path.create", &json!({"anchors": [{"x": 0, "y": 0}, {"x": 100, "y": 50}, {"x": 0, "y": 100}], "closed": true})).unwrap()); + sel(&mut s, &[id]); + s.execute("object.transformEach", &json!({"reflectX": true})).unwrap(); + let tip_x = match &node(&s, id).kind { + // The tip is the only vertex at y=50; Reflect X about the centre maps (100, 50) → (0, 50). + NodeKind::Path { path, .. } => path.subpaths[0].anchors.iter().find(|a| (a.p.y - 50.0).abs() < 1e-6).map(|a| a.p.x).unwrap_or(f64::NAN), + _ => panic!("expected a path"), + }; + assert!((tip_x - 0.0).abs() < 1e-6, "reflectX must move the tip from x=100 to x=0, got {tip_x}"); + + s.execute("edit.undo", &json!({})).unwrap(); + // A down-pointing tip at (50, 100); Reflect Y about the centre (50, 50) maps it to (50, 0). + let id2 = + id_of(&s.execute("path.create", &json!({"anchors": [{"x": 0, "y": 0}, {"x": 100, "y": 0}, {"x": 50, "y": 100}], "closed": true})).unwrap()); + sel(&mut s, &[id2]); + s.execute("object.transformEach", &json!({"reflectY": true})).unwrap(); + let tip_y = match &node(&s, id2).kind { + NodeKind::Path { path, .. } => path.subpaths[0].anchors.iter().find(|a| (a.p.x - 50.0).abs() < 1e-6).map(|a| a.p.y).unwrap_or(f64::NAN), + _ => panic!("expected a path"), + }; + assert!((tip_y - 0.0).abs() < 1e-6, "reflectY must move the tip from y=100 to y=0, got {tip_y}"); +} + /// Transform Each with its default values, or with any values that move nothing (both scales 100%, /// no move, rotation or reflection, Random or not), records no undo step and replies with the /// objects. With only a layer selected there is no object to transform: with Copy or without, it diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 1fba70cf9..fc1a25203 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1083,8 +1083,6 @@ Flattener Preview Преглед на сплескването Flip Across Обръщане напречно Flip Along Обръщане надлъжно - Flip Along Horizontal Axis Обръщане по хоризонталната ос - Flip Along Vertical Axis Обръщане по вертикалната ос Flip Horizontal Хоризонтално обръщане Flip Row Order Обръщане реда на редовете Flip Vertical Вертикално обръщане diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 8d16b4fab..86badfdfc 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1642,8 +1642,6 @@ lighting Light Světlo Arrange: Bring to Front Uspořádat: přenést do popředí Draw: Kreslení: Edit Artboards Upravit pracovní plochy - Flip Along Horizontal Axis Překlopit podle vodorovné osy - Flip Along Vertical Axis Překlopit podle svislé osy Hyphenate Dělit slova Isolate Group Izolovat skupinu Landscape Na šířku diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 5f085fbce..7692adb70 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1079,8 +1079,6 @@ Flattener Preview Reduzierungsvorschau Flip Across Quer spiegeln Flip Along Längs spiegeln - Flip Along Horizontal Axis An horizontaler Achse spiegeln - Flip Along Vertical Axis An vertikaler Achse spiegeln Flip Horizontal Horizontal spiegeln Flip Row Order Zeilenreihenfolge umkehren Flip Vertical Vertikal spiegeln diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 95eab7c38..c31f03400 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1096,8 +1096,6 @@ Flattener Preview Vista previa del acoplador Flip Across Voltear a través Flip Along Voltear a lo largo - Flip Along Horizontal Axis Voltear sobre el eje horizontal - Flip Along Vertical Axis Voltear sobre el eje vertical Flip Horizontal Voltear horizontalmente Flip Row Order Invertir orden de filas Flip Vertical Voltear verticalmente diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index a529e8d75..081925708 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1095,8 +1095,6 @@ Flattener Preview Aperçu de l’aplatissement Flip Across Retourner en travers Flip Along Retourner dans la longueur - Flip Along Horizontal Axis Retourner selon l’axe horizontal - Flip Along Vertical Axis Retourner selon l’axe vertical Flip Horizontal Retourner horizontalement Flip Row Order Inverser l’ordre des lignes Flip Vertical Retourner verticalement diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index f90ed837e..8ad3f6b00 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1384,8 +1384,6 @@ Flip Tükrözés Flip Across Tükrözés keresztben Flip Along Tükrözés hosszában - Flip Along Horizontal Axis Tükrözés a vízszintes tengelyre - Flip Along Vertical Axis Tükrözés a függőleges tengelyre Flip Horizontal Vízszintes tükrözés Flip Row Order Sorok sorrendjének megfordítása Flip Vertical Függőleges tükrözés diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index ecda156b5..a0360a01c 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1095,8 +1095,6 @@ Flattener Preview Anteprima appiattimento Flip Across Capovolgi trasversalmente Flip Along Capovolgi longitudinalmente - Flip Along Horizontal Axis Capovolgi sull'asse orizzontale - Flip Along Vertical Axis Capovolgi sull'asse verticale Flip Horizontal Capovolgi orizzontalmente Flip Row Order Inverti ordine delle righe Flip Vertical Capovolgi verticalmente diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 8730f32c1..2c5caebf5 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1423,8 +1423,6 @@ Flattener preset: 分割・統合プリセット: Flip Across 反転 (横方向) Flip Along 反転 (縦方向) - Flip Along Horizontal Axis 水平軸に沿って反転 - Flip Along Vertical Axis 垂直軸に沿って反転 Flip Horizontal 左右反転 Flip Row Order 行の順序を反転 Flip Vertical 上下反転 diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index cbe931fd7..a9a69ffe4 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1418,8 +1418,6 @@ Flattener preset: 병합 프리셋: Flip Across 기준으로 뒤집기 Flip Along 축을 따라 뒤집기 - Flip Along Horizontal Axis 가로 축 기준 뒤집기 - Flip Along Vertical Axis 세로 축 기준 뒤집기 Flip Horizontal 가로로 뒤집기 Flip Row Order 행 순서 뒤집기 Flip Vertical 세로로 뒤집기 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index c1c74e1aa..9dc5940a2 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1554,8 +1554,6 @@ lighting Light Światło Arrange: Bring to Front Ułóż: przenieś na wierzch Draw: Rysuj: Edit Artboards Edytuj obszary kompozycji - Flip Along Horizontal Axis Odbij względem osi poziomej - Flip Along Vertical Axis Odbij względem osi pionowej Hyphenate Dziel wyrazy Isolate Group Izoluj grupę Landscape Pozioma diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 4ea720d4b..6134770d7 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -964,8 +964,6 @@ Flattener preset: Predefinido de achatamento: Flip Across Inverter na Horizontal Flip Along Inverter ao Longo - Flip Along Horizontal Axis Inverter ao Longo do Eixo Horizontal - Flip Along Vertical Axis Inverter ao Longo do Eixo Vertical Flip Horizontal Espelhar Horizontalmente Flip Row Order Inverter Ordem das Linhas Flip Vertical Espelhar Verticalmente diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 02546d0b1..1aac992e8 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1093,8 +1093,6 @@ Flattener Preview Предварительный просмотр объединения Flip Across Отразить поперёк Flip Along Отразить вдоль - Flip Along Horizontal Axis Отразить по горизонтальной оси - Flip Along Vertical Axis Отразить по вертикальной оси Flip Horizontal Отразить по горизонтали Flip Row Order Изменить порядок строк Flip Vertical Отразить по вертикали diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index da49c8f80..1d51cec36 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1089,8 +1089,6 @@ Flattener Preview Попередній перегляд зведення Flip Across Відобразити поперек Flip Along Відобразити вздовж - Flip Along Horizontal Axis Відобразити по горизонтальній осі - Flip Along Vertical Axis Відобразити по вертикальній осі Flip Horizontal Відобразити по горизонталі Flip Row Order Змінити порядок рядків Flip Vertical Відобразити по вертикалі diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 1cf7e634c..12f9c97cc 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1261,8 +1261,6 @@ Flip Lật Flip Across Lật qua Flip Along Lật theo - Flip Along Horizontal Axis Lật theo trục ngang - Flip Along Vertical Axis Lật theo trục dọc Flip Horizontal Lật ngang Flip Row Order Đảo thứ tự hàng Flip Vertical Lật dọc diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index e258ef8e1..b9eb52569 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1046,8 +1046,6 @@ Flattener Preview 拼合预览 Flip Across 跨轴翻转 Flip Along 沿轴翻转 - Flip Along Horizontal Axis 沿水平轴翻转 - Flip Along Vertical Axis 沿垂直轴翻转 Flip Horizontal 水平翻转 Flip Row Order 反转行顺序 Flip Vertical 垂直翻转 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 427a0b1c3..f435042f0 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1070,8 +1070,6 @@ Flattener Preview 平面化預視 Flip Across 跨軸翻轉 Flip Along 沿軸翻轉 - Flip Along Horizontal Axis 沿水平軸翻轉 - Flip Along Vertical Axis 沿垂直軸翻轉 Flip Horizontal 水平翻轉 Flip Row Order 反轉列順序 Flip Vertical 垂直翻轉 diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index 775a5a171..4375e610a 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -454,12 +454,12 @@ pub fn transform_section(app: &mut VectorcraftApp, ui: &mut Ui) { app.run("object.rotate", json!({"angle": a, "absolute": true})).ok(); } ui.add_space(10.0); - // Lucide's flip icons are named for their mirror line: flip-vertical-2 draws a vertical - // line (the left-right flip), flip-horizontal-2 a horizontal one (top-bottom). - if widgets::icon_button(ui, "flip-vertical-2", tl!("Flip Along Horizontal Axis"), false, 24.0).clicked() { + // Same mapping as the Transform panel (#1910): Lucide flip-vertical-2 is the vertical + // mirror line (Flip Horizontal / left-right); flip-horizontal-2 is Flip Vertical. + if widgets::icon_button(ui, "flip-vertical-2", tl!("Flip Horizontal"), false, 24.0).clicked() { app.run("object.reflect", json!({"axis": "vertical"})).ok(); } - if widgets::icon_button(ui, "flip-horizontal-2", tl!("Flip Along Vertical Axis"), false, 24.0).clicked() { + if widgets::icon_button(ui, "flip-horizontal-2", tl!("Flip Vertical"), false, 24.0).clicked() { app.run("object.reflect", json!({"axis": "horizontal"})).ok(); } // More Options: the Transform panel's Scale Corners and Scale Strokes & Effects, open diff --git a/docs/gaps.md b/docs/gaps.md index cc1a5782c..759a6fd7a 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -151,6 +151,9 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall closed, the document left modified (#1865, #1685). - **Done:** the Shape Builder offers the empty areas closed shapes enclose as regions, such as the middle of four circles that touch, and merges them with the regions around them (#1691). Gap Detection is still missing. +- **Done (#1910):** Transform Each's Reflect X flips left-right and Reflect Y flips top-bottom (aligned with + Effect › Distort › Transform). Properties flip buttons use the same Flip Horizontal / Flip Vertical labels as + the Transform panel. - **Missing (noted for reporters, 2026-10-14):** deleting an interior anchor of an open path joins across the gap, where the documented behaviour removes the point and its connecting segments. Inside the estimate below. - **Missing:** Shape Builder misses planar regions and targets whole shapes (#937); Transform effect dialog's invisible checkboxes and missing options (#885); @@ -292,6 +295,7 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall | Date | Change | Summary | |---|---|---| +| 2026-10-15 | minor | G11: Transform Each Reflect X/Y and Properties flip labels match left-right / top-bottom (#1910). Estimates unchanged. | | 2026-10-14 | minor | Quick Image Trace presets reveal the adjustment panel with Advanced open; Properties reopens it. Sliders keep their preset row until release so the re-trace is committed. UI regressions added; scores and remaining estimates unchanged. | | 2026-10-14 | minor | Revolve: automatic smooth vector shading, default-on Cap with continuous curved rims, and bounded seam joins; angular cut sides remain open. Scores and hour estimates unchanged at this rounding | | 2026-10-14 | minor | No entry closed. G8 and G11 record the later merges of 2026-10-14 (#1852, #1862–#1865, #1871–#1873, #1839, #1840, #1856); #1683 (Dim Images) and #1685 (failed-save dialog) are done by #1864 and #1865. Gaps noted for reporters: G1 Effect › Warp through Warp Options (#1760), Color Picker Tab order (#1692), aligning to a key anchor (#1636; the public Align page does not say how the key anchor is chosen); G7 custom toolbars (#1759), workspace panel widths (#1753); G11 Shape Builder regions between closed shapes (#1691), deleting an interior anchor of an open path; G13 Intertwine (#1763), gradient mesh from an embedded image (#1676), Image Trace radial gradients (draft #1849). Average and Join with a corner or smooth choice was already listed (G1). G12: a line two merges had left twice is there once, without Appearance of Black for output, which acts since #1750. Estimates unchanged |