diff --git a/crates/brush/src/lib.rs b/crates/brush/src/lib.rs index 91523163e..a53d20519 100644 --- a/crates/brush/src/lib.rs +++ b/crates/brush/src/lib.rs @@ -287,6 +287,8 @@ pub struct ArtBrush { pub width: f64, pub flip_along: bool, pub flip_across: bool, + /// When true, art is placed on each run between sharp corners so folds do not overlap. + pub overlap: bool, pub colorization: Colorization, } @@ -304,6 +306,7 @@ impl Default for ArtBrush { width: 100.0, flip_along: false, flip_across: false, + overlap: false, colorization: Colorization::Tints, } } diff --git a/crates/brush/src/tests.rs b/crates/brush/src/tests.rs index 94e9e6014..3dcdbf9bc 100644 --- a/crates/brush/src/tests.rs +++ b/crates/brush/src/tests.rs @@ -152,6 +152,28 @@ fn art_brush_between_guides_keeps_ends() { assert!(near(h.x0, 280.0, 1e-6) && near(h.x1, 300.0, 1e-6), "{h:?}"); } +#[test] +fn art_brush_overlap_splits_at_corners() { + let mut bp = BezPath::new(); + bp.move_to((0.0, 0.0)); + bp.line_to((100.0, 0.0)); + bp.line_to((100.0, 100.0)); + let brush = |overlap: bool| Brush { + name: "a".into(), + kind: BrushKind::Art(ArtBrush { + art: rect_art(10.0, 4.0), + scale: ArtScale::Stretch, + overlap, + colorization: Colorization::None, + ..Default::default() + }), + }; + let one = stroke_pieces(&Document::new(1.0, 1.0), &brush(false), &bp, &stroke(1.0)); + let two = stroke_pieces(&Document::new(1.0, 1.0), &brush(true), &bp, &stroke(1.0)); + assert_eq!(one.len(), 1, "one bent copy along the whole path"); + assert_eq!(two.len(), 2, "one copy per run between corners"); +} + #[test] fn art_brush_follows_curves() { let bp = shapes::ellipse(Rect::new(0.0, 0.0, 200.0, 200.0)).to_bezpath(); diff --git a/crates/brush/src/warp.rs b/crates/brush/src/warp.rs index 8eef08c04..35031741e 100644 --- a/crates/brush/src/warp.rs +++ b/crates/brush/src/warp.rs @@ -154,35 +154,65 @@ fn bend(t: &Track, art: &Node, b: Rect, along: AlongMap, cross: f64) -> Option Vec<(f64, f64)> { + let l = t.len(); + let corners = t.corner_positions(); + if corners.is_empty() { + return vec![(0.0, l)]; + } + if t.closed { + let first = corners[0].1; + let mut bounds = corners.iter().map(|c| c.1).collect::>(); + bounds.push(first + l); + return bounds.windows(2).map(|w| (w[0], w[1])).collect(); + } + let mut bounds = vec![0.0]; + bounds.extend(corners.iter().map(|c| c.1)); + bounds.push(l); + bounds.windows(2).map(|w| (w[0], w[1])).collect() +} + +fn art_on_run(a: &ArtBrush, t: &Track, s0: f64, s1: f64, art: &Node, b: Rect, cross: f64) -> Option { + let l = s1 - s0; + if l <= 1e-9 { + return None; + } + let w = b.width(); + let (along, cross) = match a.scale { + ArtScale::Stretch => (AlongMap::linear(w, s0, s1), cross), + ArtScale::Proportional => { + let k = l / w; + (AlongMap::linear(w, s0, s1), k * a.width / 100.0) + } + ArtScale::BetweenGuides { start, end } => { + let (g0, g1) = (start.clamp(0.0, 1.0) * w, end.clamp(0.0, 1.0) * w); + let (g0, g1) = (g0.min(g1), g0.max(g1)); + let fixed = (g0 + (w - g1)) * cross; + if fixed >= l || g1 - g0 < 1e-9 { + (AlongMap::linear(w, s0, s1), cross) + } else { + (AlongMap { xs: [0.0, g0, g1, w], ss: [s0, s0 + g0 * cross, s1 - (w - g1) * cross, s1] }, cross) + } + } + }; + bend(t, art, b, along, cross) +} + pub(crate) fn art(a: &ArtBrush, bp: &BezPath, weight: f64) -> Vec { let Some((art, b)) = oriented(&a.art, a.direction, a.flip_along, a.flip_across) else { return vec![] }; let cross = weight * a.width / 100.0; let mut out = vec![]; for t in tracks(bp, 0.05) { - let l = t.len(); - if l <= 1e-9 { + if t.len() <= 1e-9 { continue; } - let w = b.width(); - let (along, cross) = match a.scale { - ArtScale::Stretch => (AlongMap::linear(w, 0.0, l), cross), - ArtScale::Proportional => { - let k = l / w; - (AlongMap::linear(w, 0.0, l), k * a.width / 100.0) + let runs = if a.overlap { art_runs(&t) } else { vec![(0.0, t.len())] }; + for (s0, s1) in runs { + if let Some(n) = art_on_run(a, &t, s0, s1, &art, b, cross) { + out.push(n); } - ArtScale::BetweenGuides { start, end } => { - let (g0, g1) = (start.clamp(0.0, 1.0) * w, end.clamp(0.0, 1.0) * w); - let (g0, g1) = (g0.min(g1), g0.max(g1)); - let fixed = (g0 + (w - g1)) * cross; - if fixed >= l || g1 - g0 < 1e-9 { - (AlongMap::linear(w, 0.0, l), cross) - } else { - (AlongMap { xs: [0.0, g0, g1, w], ss: [0.0, g0 * cross, l - (w - g1) * cross, l] }, cross) - } - } - }; - if let Some(n) = bend(&t, &art, b, along, cross) { - out.push(n); } } out diff --git a/crates/engine/src/cmd/brushsym.rs b/crates/engine/src/cmd/brushsym.rs index ba6911408..61e994993 100644 --- a/crates/engine/src/cmd/brushsym.rs +++ b/crates/engine/src/cmd/brushsym.rs @@ -89,7 +89,7 @@ pub fn specs() -> Vec { "Brush Options…", [], None, - "{name, params?: {…fields to change (brush.get shows them; values outside their ranges are clamped). calligraphic: angle −180..180°, roundness 0..100%, size 0..1296 pt, variation: [angle°, roundness%, size pt], modes: [fixed|random|pressure for angle, roundness, size] (pressure goes from value − variation at the lightest pen pressure to value + variation at the heaviest). scatter: size [a, b] 1..10000%, spacing [a, b] 1..10000%, scatter [a, b] −1000..1000%, rotation [a, b] −180..180°, modes: [fixed|random|pressure for size, spacing, scatter, rotation] (fixed: a; random: between a and b; pressure: a at the lightest pen pressure to b at the heaviest), rotation_relative_to_path. art: width 1..1000%, scale: {mode: proportional|stretch|betweenGuides, start, end: 0..1 of the art length}, direction: leftToRight|rightToLeft|topToBottom|bottomToTop, flip_along, flip_across. pattern: scale 1..10000%, spacing 0..10000%, fit: stretch|addSpace|approximate, flip_along, flip_across. scatter, art and pattern: colorization: {method: none|tints|tintsAndShades|hueShift, key: colour (hueShift: the art colour that becomes the stroke colour)}}, newName? (trimmed; blank: the name is kept)} edit a brush definition (strokes using it update) → {name}", + "{name, params?: {…fields to change (brush.get shows them; values outside their ranges are clamped). calligraphic: angle −180..180°, roundness 0..100%, size 0..1296 pt, variation: [angle°, roundness%, size pt], modes: [fixed|random|pressure for angle, roundness, size] (pressure goes from value − variation at the lightest pen pressure to value + variation at the heaviest). scatter: size [a, b] 1..10000%, spacing [a, b] 1..10000%, scatter [a, b] −1000..1000%, rotation [a, b] −180..180°, modes: [fixed|random|pressure for size, spacing, scatter, rotation] (fixed: a; random: between a and b; pressure: a at the lightest pen pressure to b at the heaviest), rotation_relative_to_path. art: width 1..1000%, scale: {mode: proportional|stretch|betweenGuides, start, end: 0..1 of the art length}, direction: leftToRight|rightToLeft|topToBottom|bottomToTop, flip_along, flip_across, overlap. pattern: scale 1..10000%, spacing 0..10000%, fit: stretch|addSpace|approximate, flip_along, flip_across. scatter, art and pattern: colorization: {method: none|tints|tintsAndShades|hueShift, key: colour (hueShift: the art colour that becomes the stroke colour)}}, newName? (trimmed; blank: the name is kept)} edit a brush definition (strokes using it update) → {name}", has_doc, brush_options ), diff --git a/crates/ui-egui/src/dialogs/brush_art_options.rs b/crates/ui-egui/src/dialogs/brush_art_options.rs index cf2edf290..43e747d8b 100644 --- a/crates/ui-egui/src/dialogs/brush_art_options.rs +++ b/crates/ui-egui/src/dialogs/brush_art_options.rs @@ -8,7 +8,8 @@ //! Scatter: per value (`size`, `spacing`, `scatter` %, `rotation` °) `…Min`, `…Max` and `…Mode` //! (`fixed` | `random` | `pressure`), `rotationRelativeTo` (`page` | `path`). //! Art: `width` (%), `scaleMode` (`proportional` | `stretch` | `betweenGuides`), `guideStart` and -//! `guideEnd` (% of the art's length), `direction` (`leftToRight` …), `flipAlong`, `flipAcross`. +//! `guideEnd` (% of the art's length), `direction` (`leftToRight` …), `flipAlong`, `flipAcross`, +//! `overlap` (bend on each run between sharp corners). //! Pattern: `scale`, `spacing` (%), `fit` (`stretch` | `addSpace` | `approximate`), `flipAlong`, //! `flipAcross`, `__tiles` (slot → preview key, for the tiles the brush has). @@ -96,6 +97,7 @@ pub(super) fn fields(b: &Brush, def: Value, f: &mut Map) { f.insert("direction".into(), json!(id_of(&a.direction))); f.insert("flipAlong".into(), json!(a.flip_along)); f.insert("flipAcross".into(), json!(a.flip_across)); + f.insert("overlap".into(), json!(a.overlap)); } BrushKind::Pattern(p) => { f.insert("scale".into(), json!(p.scale)); @@ -168,6 +170,7 @@ pub(super) fn params(d: &Dialog) -> Value { "direction": id_of(&parse_field::(d, "direction")), "flip_along": d.bool("flipAlong"), "flip_across": d.bool("flipAcross"), + "overlap": d.bool("overlap"), }) } _ => json!({ @@ -284,6 +287,8 @@ fn art_body(ui: &mut egui::Ui, d: &mut Dialog) { } }); flips(ui, d); + ui.add_space(4.0); + super::form::check(ui, d, "overlap", tl!("Overlap")); } /// A toggle button showing an arrow pointing the way `dir` runs. Whether it was clicked. diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index a84c10959..85c7f28a8 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2112,6 +2112,7 @@ Output White Изходно бяло Output › Mode Изход › Режим Output: Изход: + Overlap Застъпване Overlap: Застъпване: Overlapping Застъпващи се Overlay Прекриване diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 9745ed8cf..847b759d7 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1788,6 +1788,7 @@ lighting Light Světlo Left in Front Levý vpředu No document Žádný dokument Open a document to edit its patterns. Otevřete dokument, abyste mohli upravit jeho vzorky. + Overlap Překrytí Overlap: Překrytí: Pattern options are available in pattern editing mode. Možnosti vzorku jsou dostupné v režimu úprav vzorku. Right in Front Pravý vpředu diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index c0c68c2c1..83bcfc641 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1607,6 +1607,7 @@ lighting Light Lys Left in Front Venstre forrest No document Intet dokument Open a document to edit its patterns. Åbn et dokument for at redigere dets mønstre. + Overlap Overlap Overlap: Overlap: Pattern options are available in pattern editing mode. Mønsterindstillinger er tilgængelige i mønsterredigeringstilstand. Right in Front Højre forrest diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index a8011cba1..53ef2d1b7 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2130,6 +2130,7 @@ file-filter Templates Vorlagen Output White Ausgabe Weiß Output › Mode Ausgabe › Modus Output: Ausgabe: + Overlap Überlappung Overlap: Überlappung: Overlapping Überlappend Overlay Ineinanderkopieren diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 6ec6d92c2..469f19ee8 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -2771,6 +2771,7 @@ theme Appearance Εμφάνιση Output intent profile: Προφίλ προορισμού εξόδου: Output White Λευκό εξόδου Output › Mode Έξοδος › Λειτουργία + Overlap Επικάλυψη Overlap: Επικάλυψη: Overlapping Επικαλυπτόμενα Overprint Υπερεκτύπωση diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index c85eeae22..422aa4c73 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2155,6 +2155,7 @@ Output White Blanco de salida Output › Mode Salida › Modo Output: Salida: + Overlap Superposición Overlap: Superposición: Overlapping Superpuesto Overlay Superponer diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 4720ba121..092762a31 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1751,6 +1751,7 @@ lighting Light Valo Left in Front Vasen edessä No document Ei asiakirjaa Open a document to edit its patterns. Avaa asiakirja, niin voit muokata sen kuvioita. + Overlap Limitys Overlap: Limitys: Pattern options are available in pattern editing mode. Kuvioasetukset ovat käytettävissä kuvion muokkaustilassa. Right in Front Oikea edessä diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 2364e13f8..316c84b0f 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2152,6 +2152,7 @@ Output White Blanc de sortie Output › Mode Sortie › Mode Output: Sortie : + Overlap Chevauchement Overlap: Chevauchement : Overlapping Superposés Overlay Incrustation diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 972d0e299..ebc796cf9 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2640,6 +2640,7 @@ file-filter Templates Sablonok Output › Mode Kimenet › Mód Output: Kimenet: Oval Ovális + Overlap Átfedés Overlap: Átfedés: Overlapping Átfedő Overlay Átfedés diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 2fa8c821a..d12d02b72 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1607,6 +1607,7 @@ lighting Light Cahaya Left in Front Kiri di depan No document Tidak ada dokumen Open a document to edit its patterns. Buka dokumen untuk mengedit polanya. + Overlap Tumpang tindih Overlap: Tumpang tindih: Pattern options are available in pattern editing mode. Opsi pola tersedia dalam mode pengeditan pola. Right in Front Kanan di depan diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 51578219c..c2df0063a 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2181,6 +2181,7 @@ file-filter Templates Modelli Output White Bianco di output Output › Mode Output › Modalità Output: Output: + Overlap Sovrapposizione Overlap: Sovrapposizione: Overlapping Sovrapposti Overlay Sovrapponi diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 03587316c..12bb4413c 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2245,6 +2245,7 @@ Output condition: 出力条件: Output intent profile: 出力インテントプロファイル: Output: 出力: + Overlap 重なり Overlap: 重なり: Overlapping 重なり Overlay オーバーレイ diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 46271dd56..8a5913c2a 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1509,6 +1509,7 @@ lighting Light სინათლე Left in Front მარცხენა წინ No document დოკუმენტი არ არის Open a document to edit its patterns. გახსენით დოკუმენტი მისი პატერნების რედაქტირებისთვის. + Overlap გადაფარვა Overlap: გადაფარვა: Pattern options are available in pattern editing mode. პატერნის პარამეტრები ხელმისაწვდომია პატერნის რედაქტირების რეჟიმში. Right in Front მარჯვენა წინ diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 8dfe464a8..b73f39c8f 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2240,6 +2240,7 @@ Output condition: 출력 조건: Output intent profile: 출력 의도 프로파일: Output: 출력: + Overlap 겹침 Overlap: 겹침: Overlapping 겹침 Overlay 오버레이 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index fa7b0c324..cad5aa8e2 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1621,6 +1621,7 @@ lighting Light Licht Left in Front Linkerkant vooraan No document Geen document Open a document to edit its patterns. Open een document om de patronen te bewerken. + Overlap Overlap Overlap: Overlap: Pattern options are available in pattern editing mode. Patroonopties zijn beschikbaar in de patroonbewerkingsmodus. Right in Front Rechterkant vooraan diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 925ce2a44..6890802c5 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1708,6 +1708,7 @@ lighting Light Światło Left in Front Lewy na wierzchu No document Brak dokumentu Open a document to edit its patterns. Otwórz dokument, aby edytować jego wzorki. + Overlap Nakładanie Overlap: Nakładanie: Pattern options are available in pattern editing mode. Opcje wzorka są dostępne w trybie edycji wzorka. Right in Front Prawy na wierzchu diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index e20f5f939..d03a04614 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1770,6 +1770,7 @@ Output condition: Condição de saída: Output intent profile: Perfil de intenção de saída: Output: Saída: + Overlap Sobreposição Overlap: Sobreposição: Overlapping Sobreposto Overlay Sobrepor diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index da2e97929..64fd768f7 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2177,6 +2177,7 @@ file-filter Templates Шаблоны Output White Выходной белый Output › Mode Вывод › Режим Output: Вывод: + Overlap Перекрытие Overlap: Перекрытие: Overlapping Перекрывающийся Overlay Перекрытие diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 2b21ab374..7e32d6905 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2112,6 +2112,7 @@ Output White Vitt i utgång Output › Mode Utgång › Läge Output: Utgång: + Overlap Överlappning Overlap: Överlappning: Overlapping Överlappande Overlay Överlägg diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 8f2e6324e..c60cf49ba 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2173,6 +2173,7 @@ file-filter Templates Шаблони Output White Вихідний білий Output › Mode Виведення › Режим Output: Виведення: + Overlap Перекриття Overlap: Перекриття: Overlapping Перекривається Overlay Накладання diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index d9c14a01b..0257cbea5 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2448,6 +2448,7 @@ file-filter Templates Mẫu Output › Mode Đầu ra › Chế độ Output: Đầu ra: Outside Bên ngoài + Overlap Chồng lấp Overlap: Chồng lấp: Overlapping Chồng lấp Overlay Phủ lên diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 25e05d8c7..d3338d2be 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2070,6 +2070,7 @@ file-filter Templates 模板 Output White 输出白色 Output › Mode 输出 › 模式 Output: 输出: + Overlap 重叠 Overlap: 重叠: Overlapping 重叠 Overlay 叠加 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index b982b9c39..caaf244b6 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2898,6 +2898,7 @@ file-filter Templates 範本 Output White 輸出白色 Output › Mode 輸出 › 模式 Output: 輸出: + Overlap 重疊 Overlap: 重疊: Overlapping 重疊 Overlay 覆蓋