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 <noreply@anthropic.com>
This commit is contained in:
Trevor BrownandClaude Fable 5.1 committed 2026-11-04 03:46:49 -05:00
1 parent 5855c8705c
commit 31dcd0e5fe
3 files changed
+14 -6

No files matched your search

+8 -4
View File
@@ -440,12 +440,16 @@ fn saved_runs(v: &View<'_>, from: RouteEnd, to: RouteEnd) -> Option<ConnectorPat
last = Some(sideways);
at.push(if sideways { pos.y - mid.y } else { pos.x - mid.x });
}
// The run through each end goes the way its side faces: the first at right angles to the first
// saved run, the last to the last.
// The run through each end goes the way its side faces, out of the object: the first leaves
// the start's side for the first saved run, the last arrives at the end's from the last saved
// run. One along the side, or back into the object, doesn't.
let (across, last) = (first?, last?);
let faces_across = |s: Side| matches!(s, Side::Left | Side::Right);
let (&a0, &a1) = (at.first()?, at.last()?);
// Where the run from `p` (`sideways`: going across) meets the saved run at `v`.
let onto = |sideways: bool, p: Point, v: f64| if sideways { Point::new(mid.x + v, p.y) } else { Point::new(p.x, mid.y + v) };
let out_of = |s: Option<Side>, 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 })
}
+5 -1
View File
@@ -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:?}");
+1 -1
View File
@@ -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` |