From 31dcd0e5feca2d4ff7b80d69a4b996880c476198 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Wed, 4 Nov 2026 03:46:49 -0500 Subject: [PATCH] M6: a saved connector path going back into its object is routed instead (#1910) Review: the first and last runs were checked for lying at right angles to their sides, not for going out of the object, so a path whose first run went up through the start's box (or whose last arrived from inside the end's) was kept and drawn through it. Both runs now have to go the way the side faces; one along the side or back into the object leaves the connector to the router. Two more cases in the routed-here test. Co-Authored-By: Claude Fable 5.1 --- crates/format/src/fig/map/connector.rs | 12 ++++++++---- crates/format/src/fig/tail_tests.rs | 6 +++++- docs/file-format-parity.md | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/format/src/fig/map/connector.rs b/crates/format/src/fig/map/connector.rs index 01c07c00e..e89b875ed 100644 --- a/crates/format/src/fig/map/connector.rs +++ b/crates/format/src/fig/map/connector.rs @@ -440,12 +440,16 @@ fn saved_runs(v: &View<'_>, from: RouteEnd, to: RouteEnd) -> Option, p: Point, q: Point| s.is_none_or(|s| (q - p).dot(s.normal()) > 0.0); let (s0, s1) = attached_sides(from, to); - let fits = s0.is_none_or(|s| faces_across(s) == across) && s1.is_none_or(|s| faces_across(s) != last); + let fits = out_of(s0, p0, onto(across, p0, a0)) && out_of(s1, p1, onto(!last, p1, a1)); fits.then_some(ConnectorPath::Runs { across, at }) } diff --git a/crates/format/src/fig/tail_tests.rs b/crates/format/src/fig/tail_tests.rs index 09e6ed85a..773edd4d8 100644 --- a/crates/format/src/fig/tail_tests.rs +++ b/crates/format/src/fig/tail_tests.rs @@ -927,6 +927,10 @@ fn an_elbow_whose_saved_path_doesnt_fit_its_sides_is_routed_here() { elbow(67, "Sideways", vec![point(100.0, 650.0, 0.0, 1.0)]), elbow(68, "Askew", vec![point(150.0, 740.0, 1.0, 1.0)]), elbow(69, "Repeated", vec![point(150.0, 740.0, 1.0, 0.0), point(150.0, 760.0, 1.0, 0.0)]), + // The first run would go up from A's bottom, through A; the last would arrive at B's left + // from inside B. Each run is at right angles to its side, so only the way it goes is wrong. + elbow(70, "Backwards", vec![point(100.0, 650.0, 1.0, 0.0), point(200.0, 740.0, 0.0, 1.0)]), + elbow(71, "Inside", vec![point(150.0, 740.0, 1.0, 0.0), point(350.0, 800.0, 0.0, 1.0)]), ]); let (doc, warnings) = import(&canvas(&schema(), &message), "Unfit paths").unwrap(); let line = |name: &str| -> Vec<(f64, f64)> { @@ -935,7 +939,7 @@ fn an_elbow_whose_saved_path_doesnt_fit_its_sides_is_routed_here() { }; let plain = line("Plain"); assert!(plain.len() >= 3, "routed in right angles: {plain:?}"); - for name in ["Sideways", "Askew", "Repeated"] { + for name in ["Sideways", "Askew", "Repeated", "Backwards", "Inside"] { assert_eq!(line(name), plain, "{name} is routed as one without a path"); } assert!(warnings.iter().any(|w| w.contains("FigCraft's own routing")), "{warnings:?}"); diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 835e3ac00..d60a70c0d 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -187,7 +187,7 @@ source paint coordinates. | Date | Change | Summary | |---|---|---| -| 2026-11-04 | minor | `.fig` and `.jam` import, #1910: an elbowed connector saved with its path keeps it. FigJam stores `connectorControlPoints`, one per run between the first and the last, each a point on its run (in the connector's parent's coordinates) with the run's axis; the first run leaves the start's side and the last arrives at the end's. They become the `ConnectorPath::Runs` a board connector reshaped by hand has (`figcraft_doc::route_along`), so a connector drawn around a layer in FigJam stays around it instead of being routed through it. Points that don't make such a path (off both axes, two runs in a row the same way, a first or last run sideways to its side, more runs than a hand could make) leave the connector to the router as before, and only connectors routed here count in the "follow FigCraft's own routing" note. A curved connector's `connectorBezierControlPoints` are still not read. Tests: `tail_tests::an_elbow_connector_saved_with_its_path_keeps_it`, `tail_tests::an_elbow_whose_saved_path_doesnt_fit_its_sides_is_routed_here`. | +| 2026-11-04 | minor | `.fig` and `.jam` import, #1910: an elbowed connector saved with its path keeps it. FigJam stores `connectorControlPoints`, one per run between the first and the last, each a point on its run (in the connector's parent's coordinates) with the run's axis; the first run leaves the start's side and the last arrives at the end's. They become the `ConnectorPath::Runs` a board connector reshaped by hand has (`figcraft_doc::route_along`), so a connector drawn around a layer in FigJam stays around it instead of being routed through it. Points that don't make such a path (off both axes, two runs in a row the same way, a first or last run sideways to its side or going back into its object, more runs than a hand could make) leave the connector to the router as before, and only connectors routed here count in the "follow FigCraft's own routing" note. A curved connector's `connectorBezierControlPoints` are still not read. Tests: `tail_tests::an_elbow_connector_saved_with_its_path_keeps_it`, `tail_tests::an_elbow_whose_saved_path_doesnt_fit_its_sides_is_routed_here`. | | 2026-11-01 | patch | `.fig` and `.jam` import: a connector without a saved snapshot (elbowed and curved ones are routed here) took no stroke, so it was invisible (#1807); its line's paint, weight, cap and dashes now come from the file's generated line record, as a saved snapshot's do, and the connector's own values, when it has any, win. Test: `fig::connector_label_tests::a_routed_connector_takes_its_line_style_from_the_generated_record`. Numbers unchanged | | 2026-10-31 | patch | M4: Dash and Custom strokes have a Dash cap (None, Round, Square) in the advanced stroke settings and `node.set {dashCap}`, on both ends of every dash on the canvas and in PNG, PDF and SVG. Unset, dashes keep taking the end point caps, so older files look the same; the saved file writes it only when set. Numbers unchanged | | 2026-10-30 | minor | `.fig` import: a connector's label without a saved snapshot was skipped; now the connector imports as a frame holding its line and the label, as a connector with a saved snapshot does, the label centred on the line's middle by length (in a 200-wide box, laid out again with the fonts at hand), in the connector's fill paints or black. Its background isn't known without the snapshot and is left out. Not checked on real files. Test: `fig::tests::tail::a_labelled_connector_without_a_snapshot_keeps_its_label` |