forked from HEL/circuiteria
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e6bfab744 | ||
|
|
018192db86 | ||
|
|
26229d8fc4 |
No files matched your search
+137
-3
@@ -91,7 +91,7 @@ for i in range(3) {
|
||||
draw.content((i * 3 + 1, -1), raw(wire.wire-styles.at(i)))
|
||||
}
|
||||
wire.wire("w1", ("p0", "p1"), style: "direct")
|
||||
wire.wire("w2", ("p2", "p3"), style: "zigzag")
|
||||
wire.wire("w2", ("p2", "p3"), style: "zigzag", zigzag-dir: "vertical")
|
||||
wire.wire("w3", ("p4", "p5"), style: "dodge",
|
||||
dodge-y: -0.5, dodge-margins: (0.5, 0.5))
|
||||
wire.wire("w4", ("p6","p7"), style: "guided",
|
||||
@@ -460,9 +460,9 @@ wire.wire("w-out", ("inner-port-b", "sh-port-out"))
|
||||
```)
|
||||
|
||||
#let intersection = example(```
|
||||
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag")
|
||||
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag", zigzag-dir: "vertical")
|
||||
wire.wire("w2", ((0, 0), (1, -.5)),
|
||||
style: "zigzag", zigzag-ratio: 80%)
|
||||
style: "zigzag", zigzag-dir: "vertical", zigzag-ratio: 80%)
|
||||
wire.intersection("w1.zig")
|
||||
```)
|
||||
|
||||
@@ -534,3 +534,137 @@ wire.intersection("w1.zig")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let u-at = example(
|
||||
```
|
||||
// `u-at` places the U-bend crossbar on the axis
|
||||
// of a referenced anchor, overriding `u-depth`.
|
||||
element.block(x: 0, y: 0, w: 1.5, h: 1.5, id: "eA",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "o1"), (id: "o2"))))
|
||||
element.block(x: 0, y: -3.5, w: 1.5, h: 1.5, id: "eB",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "i1"), (id: "i2"))))
|
||||
element.block(x: 4, y: -2.25, w: 0.6, h: 0.6, id: "ref",
|
||||
fill: util.colors.pink)
|
||||
// Align one U crossbar with ref.east.
|
||||
wire.wire("u1", ("eA-port-o1", "eB-port-i1"),
|
||||
style: "u-shape",
|
||||
u-at: "ref.east")
|
||||
// Ergonomic scalar offset: 1 right of ref.east.
|
||||
wire.wire("u2", ("eA-port-o2", "eB-port-i2"),
|
||||
style: "u-shape", color: util.colors.blue,
|
||||
u-at: (rel: 1, to: "ref.east"))
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let s-shape-wire = example(
|
||||
```
|
||||
// `s-shape` connects endpoints on opposite sides
|
||||
// via 5 segments joined at 4 corners (u1-start,
|
||||
// u1-end, u2-start, u2-end). Auto-detects axis
|
||||
// from port anchors.
|
||||
element.block(x: 0, y: 0, w: 1, h: 1, id: "A",
|
||||
fill: util.colors.orange,
|
||||
ports: (west: ((id: "out"),)))
|
||||
element.block(x: 2.5, y: -1.75, w: 1, h: 1, id: "B",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "in"),)))
|
||||
wire.wire("s1", ("A-port-out", "B-port-in"),
|
||||
style: "s-shape")
|
||||
|
||||
// Anchor-based override of the crossbar position.
|
||||
element.block(x: 5.5, y: 0, w: 1, h: 1, id: "C",
|
||||
fill: util.colors.yellow,
|
||||
ports: (west: ((id: "out"),)))
|
||||
element.block(x: 8.5, y: -1.75, w: 1, h: 1, id: "D",
|
||||
fill: util.colors.yellow,
|
||||
ports: (east: ((id: "in"),)))
|
||||
element.block(x: 7, y: -0.7, w: 0.5, h: 0.5, id: "ref",
|
||||
fill: util.colors.pink)
|
||||
wire.wire("s2", ("C-port-out", "D-port-in"),
|
||||
style: "s-shape",
|
||||
s-at-mid: "ref.south")
|
||||
|
||||
// C-shape degeneration: crossbar outside the
|
||||
// endpoint span (s-ratio-mid > 100%).
|
||||
element.block(x: 11, y: 0, w: 1, h: 1, id: "E",
|
||||
fill: util.colors.green,
|
||||
ports: (west: ((id: "out"),)))
|
||||
element.block(x: 14, y: -0.75, w: 1, h: 1, id: "F",
|
||||
fill: util.colors.green,
|
||||
ports: (east: ((id: "in"),)))
|
||||
wire.wire("s3", ("E-port-out", "F-port-in"),
|
||||
style: "s-shape",
|
||||
s-ratio-mid: 200%)
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let s-shape-wire-vertical = example(
|
||||
```
|
||||
// `s-shape` on the vertical axis (north <-> south):
|
||||
// seg1 and seg5 are vertical, the crossbar is
|
||||
// vertical, and the two horizontals connect them.
|
||||
element.block(x: 0, y: 0, w: 1, h: 1, id: "A",
|
||||
fill: util.colors.orange,
|
||||
ports: (north: ((id: "out"),)))
|
||||
element.block(x: 2, y: -3, w: 1, h: 1, id: "B",
|
||||
fill: util.colors.orange,
|
||||
ports: (south: ((id: "in"),)))
|
||||
wire.wire("s1", ("A-port-out", "B-port-in"),
|
||||
style: "s-shape")
|
||||
|
||||
// Anchor-based override of the vertical crossbar.
|
||||
element.block(x: 5, y: 0, w: 1, h: 1, id: "C",
|
||||
fill: util.colors.yellow,
|
||||
ports: (north: ((id: "out"),)))
|
||||
element.block(x: 7, y: -3, w: 1, h: 1, id: "D",
|
||||
fill: util.colors.yellow,
|
||||
ports: (south: ((id: "in"),)))
|
||||
element.block(x: 6.25, y: -1.5, w: 0.5, h: 0.5, id: "vref",
|
||||
fill: util.colors.pink)
|
||||
wire.wire("s2", ("C-port-out", "D-port-in"),
|
||||
style: "s-shape",
|
||||
s-at-mid: "vref.east")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let s-shape-wire-loopback = example(
|
||||
```
|
||||
// `s-shape` looping back on a single block that
|
||||
// has a west output and an east input. The wire
|
||||
// exits west, wraps around below the block via a
|
||||
// C-shape, and enters from the east.
|
||||
//
|
||||
// Since both endpoints share the same y, the
|
||||
// crossbar's position is set by `s-ratio-mid`
|
||||
// as an absolute distance below the endpoint y.
|
||||
element.block(x: 2, y: 0, w: 1.5, h: 1.5, id: "A",
|
||||
fill: util.colors.orange,
|
||||
ports: (
|
||||
west: ((id: "out"),),
|
||||
east: ((id: "in"),),
|
||||
))
|
||||
wire.wire("s1", ("A-port-out", "A-port-in"),
|
||||
style: "s-shape",
|
||||
s-depth-1: 1.5, s-depth-2: 1.5,
|
||||
s-ratio-mid: 150%)
|
||||
|
||||
// Negative `s-ratio-mid` routes the crossbar the
|
||||
// other way (above the block).
|
||||
element.block(x: 7, y: 0, w: 1.5, h: 1.5, id: "B",
|
||||
fill: util.colors.yellow,
|
||||
ports: (
|
||||
west: ((id: "out"),),
|
||||
east: ((id: "in"),),
|
||||
))
|
||||
wire.wire("s2", ("B-port-out", "B-port-in"),
|
||||
style: "s-shape",
|
||||
s-depth-1: 1.5, s-depth-2: 1.5,
|
||||
s-ratio-mid: -150%)
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
+9793
-8355
File diff suppressed because it is too large.
Load diff
+349
-23
@@ -6,7 +6,22 @@
|
||||
///
|
||||
/// The `"u-shape"` style connects two ports that face the same cardinal side.
|
||||
/// #examples.u-shape-wire
|
||||
#let wire-styles = ("direct", "zigzag", "dodge", "guided", "u-shape")
|
||||
///
|
||||
/// The `"u-shape"` crossbar can also be aligned to an anchor via `u-at`
|
||||
/// (analogous to `zigzag-at`).
|
||||
/// #examples.u-at
|
||||
///
|
||||
/// The `"s-shape"` style connects two ports that face opposite cardinal sides
|
||||
/// (east↔west or north↔south) via 5 segments.
|
||||
/// #examples.s-shape-wire
|
||||
///
|
||||
/// The `"s-shape"` style also supports the vertical axis (north↔south):
|
||||
/// #examples.s-shape-wire-vertical
|
||||
///
|
||||
/// The `"s-shape"` style can also loop a single block's output back to its
|
||||
/// own input by wrapping around above (or below) via a C-shape:
|
||||
/// #examples.s-shape-wire-loopback
|
||||
#let wire-styles = ("direct", "zigzag", "dodge", "guided", "u-shape", "s-shape")
|
||||
#let signal-width = 1pt
|
||||
#let bus-width = 1.5pt
|
||||
|
||||
@@ -27,24 +42,31 @@
|
||||
return (pts, anchors)
|
||||
}
|
||||
|
||||
// Normalize a `zigzag-at` value.
|
||||
// Normalize an anchor-based placement value used by `zigzag-at`, `u-at`, and
|
||||
// the `s-at-*` family.
|
||||
//
|
||||
// `axis` selects which cartesian component the offset applies to:
|
||||
// - `"x"` → offset in x (used when the placement fixes an x coordinate, e.g.
|
||||
// a vertical bend line for horizontal wire flow).
|
||||
// - `"y"` → offset in y (used when the placement fixes a y coordinate).
|
||||
//
|
||||
// Accepted forms:
|
||||
// - `none` → unset; ratio-based placement is used.
|
||||
// - `none` → unset; caller falls back to
|
||||
// depth/ratio-based placement.
|
||||
// - `str` / tuple / native CeTZ dict → passed through as a CeTZ coordinate.
|
||||
// - `(rel: <length>, to: <coord>)` with a *scalar* `rel`
|
||||
// → ergonomic axial offset, rewritten
|
||||
// to `(rel: (rel, 0), to: to)` for
|
||||
// `dir == "vertical"` or
|
||||
// `axis == "x"` or
|
||||
// `(rel: (0, rel), to: to)` for
|
||||
// `dir == "horizontal"`. If `rel` is
|
||||
// already an array/tuple, the value
|
||||
// is passed through unchanged.
|
||||
#let normalize-zigzag-at(at, dir) = {
|
||||
// `axis == "y"`. If `rel` is already
|
||||
// an array/tuple, the value is
|
||||
// passed through unchanged.
|
||||
#let normalize-scalar-offset(at, axis) = {
|
||||
if at == none { return none }
|
||||
if type(at) == dictionary and "rel" in at and "to" in at and type(at.rel) != array {
|
||||
let r = at.rel
|
||||
if dir == "vertical" {
|
||||
if axis == "x" {
|
||||
return (rel: (r, 0), to: at.to)
|
||||
} else {
|
||||
return (rel: (0, r), to: at.to)
|
||||
@@ -53,6 +75,14 @@
|
||||
return at
|
||||
}
|
||||
|
||||
// Backwards-compatible wrapper: translates the old `dir` vocabulary
|
||||
// (`"vertical"` / `"horizontal"`) to the `axis` vocabulary used by
|
||||
// `normalize-scalar-offset`.
|
||||
#let normalize-zigzag-at(at, dir) = {
|
||||
let axis = if dir == "vertical" { "x" } else { "y" }
|
||||
return normalize-scalar-offset(at, axis)
|
||||
}
|
||||
|
||||
#let get-zigzag-wire(pts, ratio, dir, at: none) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
@@ -193,7 +223,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
#let get-u-shape-wire(pts, u-depth, u-side, ctx) = {
|
||||
#let get-u-shape-wire(pts, u-depth, u-side, u-at, ctx) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
|
||||
@@ -227,22 +257,33 @@
|
||||
let (sx, sy, ..) = s
|
||||
let (ex, ey, ..) = e
|
||||
|
||||
// If `u-at` is set, resolve its coordinate and extract the relevant axis
|
||||
// component; use it as the crossbar position instead of computing from
|
||||
// `u-depth`.
|
||||
let bar-at = none
|
||||
if u-at != none {
|
||||
let axis = if side == "east" or side == "west" { "x" } else { "y" }
|
||||
let at-norm = normalize-scalar-offset(u-at, axis)
|
||||
let (ctx2, r) = coordinate.resolve(ctx, at-norm)
|
||||
bar-at = if axis == "x" { r.first() } else { r.at(1) }
|
||||
}
|
||||
|
||||
let p1
|
||||
let p2
|
||||
if side == "east" {
|
||||
let bx = calc.max(sx, ex) + u-depth
|
||||
let bx = if bar-at != none { bar-at } else { calc.max(sx, ex) + u-depth }
|
||||
p1 = (bx, sy, 0)
|
||||
p2 = (bx, ey, 0)
|
||||
} else if side == "west" {
|
||||
let bx = calc.min(sx, ex) - u-depth
|
||||
let bx = if bar-at != none { bar-at } else { calc.min(sx, ex) - u-depth }
|
||||
p1 = (bx, sy, 0)
|
||||
p2 = (bx, ey, 0)
|
||||
} else if side == "north" {
|
||||
let by = calc.max(sy, ey) + u-depth
|
||||
let by = if bar-at != none { bar-at } else { calc.max(sy, ey) + u-depth }
|
||||
p1 = (sx, by, 0)
|
||||
p2 = (ex, by, 0)
|
||||
} else if side == "south" {
|
||||
let by = calc.min(sy, ey) - u-depth
|
||||
let by = if bar-at != none { bar-at } else { calc.min(sy, ey) - u-depth }
|
||||
p1 = (sx, by, 0)
|
||||
p2 = (ex, by, 0)
|
||||
}
|
||||
@@ -257,6 +298,210 @@
|
||||
return (points, anchors)
|
||||
}
|
||||
|
||||
// Valid combinations of endpoint sides for an S-shape wire.
|
||||
// Keyed by (sorted) pair; value is the wire axis.
|
||||
#let _s-axis-from-sides(a, b) = {
|
||||
if a == none or b == none { return none }
|
||||
let pair = (a, b)
|
||||
if ("east" in pair) and ("west" in pair) { return "horizontal" }
|
||||
if ("north" in pair) and ("south" in pair) { return "vertical" }
|
||||
return none
|
||||
}
|
||||
|
||||
// S-shape wire: 5 segments, 4 corners; connects endpoints on opposite sides.
|
||||
//
|
||||
// Horizontal axis (east ↔ west): sequence exits/enters through horizontal
|
||||
// segments; two vertical segments join them via a horizontal crossbar.
|
||||
//
|
||||
// Corner layout (horizontal axis):
|
||||
// start ── c1
|
||||
// │
|
||||
// c2 ─────────── c3
|
||||
// │
|
||||
// c4 ── end
|
||||
//
|
||||
// (or mirrored, depending on which endpoint faces east vs west, and on the
|
||||
// relative y of the endpoints; when y_mid is outside the endpoint span the
|
||||
// shape degenerates into a C-shape.)
|
||||
#let get-s-shape-wire(
|
||||
pts,
|
||||
s-axis,
|
||||
s-depth-1,
|
||||
s-ratio-mid,
|
||||
s-depth-2,
|
||||
s-at-1,
|
||||
s-at-mid,
|
||||
s-at-2,
|
||||
strict,
|
||||
ctx,
|
||||
) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
|
||||
// Detect endpoint sides
|
||||
let side-start = _u-detect-side(start, ctx)
|
||||
let side-end = _u-detect-side(end, ctx)
|
||||
|
||||
// Resolve (auto|explicit) axis
|
||||
let axis = s-axis
|
||||
if axis == auto {
|
||||
let detected = _s-axis-from-sides(side-start, side-end)
|
||||
if detected == none {
|
||||
panic(
|
||||
"s-shape: could not auto-detect s-axis; endpoints must be port anchors "
|
||||
+ "facing opposite sides on the same axis (east+west or north+south). "
|
||||
+ "Detected start='" + repr(side-start) + "', end='" + repr(side-end)
|
||||
+ "'. Set s-axis explicitly to \"horizontal\" or \"vertical\"."
|
||||
)
|
||||
}
|
||||
axis = detected
|
||||
}
|
||||
if axis != "horizontal" and axis != "vertical" {
|
||||
panic("s-shape: invalid s-axis '" + repr(axis) + "'; must be \"horizontal\" or \"vertical\"")
|
||||
}
|
||||
|
||||
// Effective depth/ratio (auto → defaults)
|
||||
let depth-1 = if s-depth-1 == auto { 0.5 } else { s-depth-1 }
|
||||
let depth-2 = if s-depth-2 == auto { 0.5 } else { s-depth-2 }
|
||||
let ratio-mid-raw = if s-ratio-mid == auto { 50% } else { s-ratio-mid }
|
||||
let ratio-mid = ratio-mid-raw / 100%
|
||||
|
||||
let (ctx, s) = coordinate.resolve(ctx, start)
|
||||
let (ctx, e) = coordinate.resolve(ctx, end)
|
||||
let (sx, sy, ..) = s
|
||||
let (ex, ey, ..) = e
|
||||
|
||||
// Determine outward directions from each port
|
||||
let outward(side) = {
|
||||
if side == "east" { return (1, 0) }
|
||||
if side == "west" { return (-1, 0) }
|
||||
if side == "north" { return (0, 1) }
|
||||
if side == "south" { return (0, -1) }
|
||||
return none
|
||||
}
|
||||
let out-s = outward(side-start)
|
||||
let out-e = outward(side-end)
|
||||
// Fallback when a port side isn't known (e.g. non-port endpoint): infer from
|
||||
// the axis, assuming the wire flows toward the far endpoint.
|
||||
if out-s == none {
|
||||
if axis == "horizontal" { out-s = (if ex > sx { 1 } else { -1 }, 0) }
|
||||
else { out-s = (0, if ey > sy { 1 } else { -1 }) }
|
||||
}
|
||||
if out-e == none {
|
||||
if axis == "horizontal" { out-e = (if sx > ex { 1 } else { -1 }, 0) }
|
||||
else { out-e = (0, if sy > ey { 1 } else { -1 }) }
|
||||
}
|
||||
|
||||
// Resolve the three placement overrides
|
||||
let resolve-axial(at, ax) = {
|
||||
if at == none { return none }
|
||||
let at-norm = normalize-scalar-offset(at, ax)
|
||||
let (_, r) = coordinate.resolve(ctx, at-norm)
|
||||
return if ax == "x" { r.first() } else { r.at(1) }
|
||||
}
|
||||
|
||||
let c1x
|
||||
let c1y
|
||||
let c2x
|
||||
let c2y
|
||||
let c3x
|
||||
let c3y
|
||||
let c4x
|
||||
let c4y
|
||||
|
||||
if axis == "horizontal" {
|
||||
// seg1 and seg5 are horizontal; the two verticals sit at x = x1 and x = x2.
|
||||
let x1-at = resolve-axial(s-at-1, "x")
|
||||
let x2-at = resolve-axial(s-at-2, "x")
|
||||
let y-mid-at = resolve-axial(s-at-mid, "y")
|
||||
|
||||
let x1 = if x1-at != none { x1-at } else { sx + out-s.at(0) * depth-1 }
|
||||
let x2 = if x2-at != none { x2-at } else { ex + out-e.at(0) * depth-2 }
|
||||
// When the endpoints share the same y (degenerate span), the ratio-based
|
||||
// formula collapses to sy. Fall back to a unit offset scaled by ratio-mid
|
||||
// so the crossbar can still be pushed away from the endpoint axis
|
||||
// (e.g. for single-block loopbacks). Direction defaults to "down" (-y).
|
||||
let y-mid = if y-mid-at != none {
|
||||
y-mid-at
|
||||
} else if ey == sy {
|
||||
sy - ratio-mid
|
||||
} else {
|
||||
sy + ratio-mid * (ey - sy)
|
||||
}
|
||||
|
||||
c1x = x1; c1y = sy
|
||||
c2x = x1; c2y = y-mid
|
||||
c3x = x2; c3y = y-mid
|
||||
c4x = x2; c4y = ey
|
||||
|
||||
// Sanity checks (strict-only)
|
||||
if strict {
|
||||
// seg1 must extend outward: sign(x1 - sx) should match out-s.x
|
||||
if (x1 - sx) * out-s.at(0) <= 0 {
|
||||
panic("s-shape: seg1 does not extend outward from start port "
|
||||
+ "(x1=" + str(x1) + ", start.x=" + str(sx) + ", outward.x="
|
||||
+ str(out-s.at(0)) + "). Adjust s-depth-1 or s-at-1.")
|
||||
}
|
||||
if (x2 - ex) * out-e.at(0) <= 0 {
|
||||
panic("s-shape: seg5 does not extend outward from end port "
|
||||
+ "(x2=" + str(x2) + ", end.x=" + str(ex) + ", outward.x="
|
||||
+ str(out-e.at(0)) + "). Adjust s-depth-2 or s-at-2.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// vertical axis: seg1 and seg5 are vertical; the two horizontals sit at
|
||||
// y = y1 and y = y2; the middle crossbar is vertical at x = x-mid.
|
||||
let y1-at = resolve-axial(s-at-1, "y")
|
||||
let y2-at = resolve-axial(s-at-2, "y")
|
||||
let x-mid-at = resolve-axial(s-at-mid, "x")
|
||||
|
||||
let y1 = if y1-at != none { y1-at } else { sy + out-s.at(1) * depth-1 }
|
||||
let y2 = if y2-at != none { y2-at } else { ey + out-e.at(1) * depth-2 }
|
||||
// Degenerate-span fallback: see comment in the horizontal branch.
|
||||
let x-mid = if x-mid-at != none {
|
||||
x-mid-at
|
||||
} else if ex == sx {
|
||||
sx + ratio-mid
|
||||
} else {
|
||||
sx + ratio-mid * (ex - sx)
|
||||
}
|
||||
|
||||
c1x = sx; c1y = y1
|
||||
c2x = x-mid; c2y = y1
|
||||
c3x = x-mid; c3y = y2
|
||||
c4x = ex; c4y = y2
|
||||
|
||||
if strict {
|
||||
if (y1 - sy) * out-s.at(1) <= 0 {
|
||||
panic("s-shape: seg1 does not extend outward from start port "
|
||||
+ "(y1=" + str(y1) + ", start.y=" + str(sy) + ", outward.y="
|
||||
+ str(out-s.at(1)) + "). Adjust s-depth-1 or s-at-1.")
|
||||
}
|
||||
if (y2 - ey) * out-e.at(1) <= 0 {
|
||||
panic("s-shape: seg5 does not extend outward from end port "
|
||||
+ "(y2=" + str(y2) + ", end.y=" + str(ey) + ", outward.y="
|
||||
+ str(out-e.at(1)) + "). Adjust s-depth-2 or s-at-2.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let c1 = (c1x, c1y, 0)
|
||||
let c2 = (c2x, c2y, 0)
|
||||
let c3 = (c3x, c3y, 0)
|
||||
let c4 = (c4x, c4y, 0)
|
||||
|
||||
let points = (start, c1, c2, c3, c4, end)
|
||||
let anchors = (
|
||||
"start": start,
|
||||
"u1-start": c1,
|
||||
"u1-end": c2,
|
||||
"u2-start": c3,
|
||||
"u2-end": c4,
|
||||
"end": end,
|
||||
)
|
||||
return (points, anchors)
|
||||
}
|
||||
|
||||
#let get-guided-wire(pts, margins, sides, center-guides, ctx) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
@@ -353,7 +598,7 @@
|
||||
/// - directed (bool): If true, the wire will be directed, meaning an arrow will be drawn at the endpoint
|
||||
/// - rotate-name (bool): If true, the name will be rotated according to the wire's slope
|
||||
/// - zigzag-ratio (auto, ratio): Position of the zigzag middle bend segment relative to the horizontal (or vertical) span, as a fraction between the two endpoints (only with style "zigzag"). Defaults to `auto`, which resolves to `50%`. Panics if combined with an explicitly set `zigzag-at`.
|
||||
/// - zigzag-dir (str): The zigzag's direction. As either "vertical" or "horizontal" (only with dstyle "zigzag")
|
||||
/// - zigzag-dir (auto, str): The zigzag's direction, as either "vertical" or "horizontal" (only with style "zigzag"). If `auto`, it is deduced from the endpoints when they are port anchors of the form `<elmt-id>-port-<port-id>` by comparing each port's position with its element's `.center` anchor: east/west-facing ports yield `"vertical"` (wire exits horizontally) and north/south-facing ports yield `"horizontal"`. Auto-detection panics if the two endpoints resolve to conflicting directions, or if neither endpoint is a port anchor.
|
||||
/// - zigzag-at (none, str, array, dictionary): Optional. Anchor-based placement of the zigzag middle bend segment (only with style "zigzag"). When set, overrides `zigzag-ratio` (which must be left at its default `auto`). The middle segment sits on the axis extracted from the given coordinate: `x` for `zigzag-dir: "vertical"`, `y` for `"horizontal"`. Accepted forms: a raw CeTZ anchor name string (e.g. `"elmt.center"` or `"elmt-port-a"`); any native CeTZ coordinate (tuple, dict such as `(rel: (dx, dy), to: anchor)`); or the ergonomic scalar-offset form `(rel: <length>, to: <coord>)`, which is rewritten to a `(dx, 0)` offset for `"vertical"` and `(0, dy)` for `"horizontal"`. See #examples.zigzag-at
|
||||
/// - dodge-y (number): Y position to dodge the wire to (only with style "dodge")
|
||||
/// - dodge-sides (array): The start and end sides (going out of the connected element) of the wire (only with style "dodge")
|
||||
@@ -362,7 +607,16 @@
|
||||
/// - guided-margins (array): the start and end of guided margins of the wire (only with style "guided")
|
||||
/// - guided-sides (array): the side of start and end array (must be either "north", "south", "west", "east") (only work with style "guided")
|
||||
/// - u-side (auto, str): The cardinal side both endpoints face (one of "east", "west", "north", "south"). If `auto`, it is deduced from the endpoints when they are port anchors of the form `<elmt-id>-port-<port-id>` by comparing each port's position with its element's `.center` anchor. Auto-detection panics if the two endpoints resolve to different sides, or if neither endpoint is a port anchor (only with style "u-shape")
|
||||
/// - u-depth (number): Distance between the outermost endpoint (in the `u-side` direction) and the U-bend line (only with style "u-shape")
|
||||
/// - u-depth (auto, number): Distance between the outermost endpoint (in the `u-side` direction) and the U-bend line (only with style "u-shape"). Defaults to `auto`, which resolves to `0.5`. Panics if combined with an explicitly set `u-at`.
|
||||
/// - u-at (none, str, array, dictionary): Optional. Anchor-based placement of the U-bend crossbar (only with style "u-shape"). When set, overrides `u-depth` (which must be left at its default `auto`). The crossbar sits on the axis extracted from the given coordinate: `x` for `u-side` in `("east", "west")`, `y` for `("north", "south")`. Accepted forms mirror `zigzag-at`: raw CeTZ anchor name string, any native CeTZ coordinate, or the ergonomic scalar-offset form `(rel: <length>, to: <coord>)`, which is rewritten to `(dx, 0)` for east/west and `(0, dy)` for north/south.
|
||||
/// - s-axis (auto, str): The s-shape wire axis, one of "horizontal" or "vertical" (only with style "s-shape"). If `auto`, it is deduced from the endpoint port anchors: opposite east/west ports yield `"horizontal"`, opposite north/south ports yield `"vertical"`. Auto-detection panics on same-side, cross-axis, or non-port endpoints.
|
||||
/// - s-depth-1 (auto, number): Distance seg1 (the first outward segment) extends from the start port (only with style "s-shape"). Defaults to `auto`, which resolves to `0.5`. Panics if combined with an explicit `s-at-1`.
|
||||
/// - s-ratio-mid (auto, ratio): Position of the s-shape crossbar between the endpoints along the axis perpendicular to `s-axis` (only with style "s-shape"). Defaults to `auto`, which resolves to `50%`. Values outside `0%..100%` are allowed and yield a C-shape variant. When the two endpoints share the same perpendicular coordinate (e.g. a single-block loopback where both ports are at the same y), the endpoint span is zero and `s-ratio-mid` is instead interpreted as an absolute offset from that shared coordinate: the crossbar sits `s-ratio-mid` units below the endpoints for horizontal S, or `s-ratio-mid` units right for vertical S (using `1` = ratio unit). Panics if combined with an explicit `s-at-mid`.
|
||||
/// - s-depth-2 (auto, number): Distance seg5 (the last inward segment) extends from the end port (only with style "s-shape"). Defaults to `auto`, which resolves to `0.5`. Panics if combined with an explicit `s-at-2`.
|
||||
/// - s-at-1 (none, str, array, dictionary): Optional. Anchor-based placement overriding `s-depth-1` (only with style "s-shape"). Fixes the x (horizontal S) or y (vertical S) of the first vertical bend line. Accepted forms mirror `zigzag-at`.
|
||||
/// - s-at-mid (none, str, array, dictionary): Optional. Anchor-based placement overriding `s-ratio-mid` (only with style "s-shape"). Fixes the y (horizontal S) or x (vertical S) of the crossbar. Accepted forms mirror `zigzag-at`.
|
||||
/// - s-at-2 (none, str, array, dictionary): Optional. Anchor-based placement overriding `s-depth-2` (only with style "s-shape"). Fixes the x (horizontal S) or y (vertical S) of the second vertical bend line. Accepted forms mirror `zigzag-at`.
|
||||
/// - strict (bool): If true, s-shape performs sanity checks and panics when seg1/seg5 do not extend outward from their port (only with style "s-shape"). Defaults to `false`.
|
||||
#let wire(
|
||||
id, pts,
|
||||
bus: false,
|
||||
@@ -379,13 +633,22 @@
|
||||
directed: false,
|
||||
rotate-name: true,
|
||||
zigzag-ratio: auto,
|
||||
zigzag-dir: "vertical",
|
||||
zigzag-dir: auto,
|
||||
zigzag-at: none,
|
||||
dodge-y: 0,
|
||||
dodge-sides: ("east", "west"),
|
||||
dodge-margins: (5%, 5%),
|
||||
u-side: auto,
|
||||
u-depth: 0.5,
|
||||
u-depth: auto,
|
||||
u-at: none,
|
||||
s-axis: auto,
|
||||
s-depth-1: auto,
|
||||
s-ratio-mid: auto,
|
||||
s-depth-2: auto,
|
||||
s-at-1: none,
|
||||
s-at-mid: none,
|
||||
s-at-2: none,
|
||||
strict: false,
|
||||
) = draw.get-ctx(ctx => {
|
||||
if not style in wire-styles {
|
||||
panic("Invalid wire style '" + style + "'")
|
||||
@@ -408,6 +671,39 @@
|
||||
}
|
||||
let zigzag-ratio-effective = if zigzag-ratio == auto { 50% } else { zigzag-ratio }
|
||||
|
||||
// `u-at` validation.
|
||||
if u-at != none {
|
||||
if style != "u-shape" {
|
||||
panic("wire '" + id + "': `u-at` requires style: \"u-shape\" (got \""
|
||||
+ style + "\")")
|
||||
}
|
||||
if u-depth != auto {
|
||||
panic("wire '" + id
|
||||
+ "': cannot combine `u-at` with an explicit `u-depth`")
|
||||
}
|
||||
}
|
||||
let u-depth-effective = if u-depth == auto { 0.5 } else { u-depth }
|
||||
|
||||
// `s-at-*` validation.
|
||||
let s-params-set = (
|
||||
s-axis != auto or s-depth-1 != auto or s-ratio-mid != auto
|
||||
or s-depth-2 != auto or s-at-1 != none or s-at-mid != none
|
||||
or s-at-2 != none
|
||||
)
|
||||
if s-params-set and style != "s-shape" {
|
||||
panic("wire '" + id + "': `s-*` parameters require style: \"s-shape\" (got \""
|
||||
+ style + "\")")
|
||||
}
|
||||
if s-at-1 != none and s-depth-1 != auto {
|
||||
panic("wire '" + id + "': cannot combine `s-at-1` with an explicit `s-depth-1`")
|
||||
}
|
||||
if s-at-mid != none and s-ratio-mid != auto {
|
||||
panic("wire '" + id + "': cannot combine `s-at-mid` with an explicit `s-ratio-mid`")
|
||||
}
|
||||
if s-at-2 != none and s-depth-2 != auto {
|
||||
panic("wire '" + id + "': cannot combine `s-at-2` with an explicit `s-depth-2`")
|
||||
}
|
||||
|
||||
// Normalize user-provided coordinates: (x, y) -> (x, y, 0)
|
||||
let pts = pts.map(normalize-coord)
|
||||
|
||||
@@ -426,11 +722,27 @@
|
||||
(points, anchors) = get-direct-wire(pts)
|
||||
|
||||
} else if style == "zigzag" {
|
||||
let zz-dir = zigzag-dir
|
||||
if zz-dir == auto {
|
||||
let side-start = _u-detect-side(pts.first(), ctx)
|
||||
let side-end = _u-detect-side(pts.last(), ctx)
|
||||
let dir-from-side(s) = if s == none { none } else if s == "east" or s == "west" { "vertical" } else { "horizontal" }
|
||||
let d-start = dir-from-side(side-start)
|
||||
let d-end = dir-from-side(side-end)
|
||||
if d-start != none and d-end != none and d-start != d-end {
|
||||
panic("zigzag: start port faces '" + side-start
|
||||
+ "' but end port faces '" + side-end
|
||||
+ "'; cannot auto-detect `zigzag-dir`. Set it explicitly.")
|
||||
}
|
||||
if d-start != none { zz-dir = d-start }
|
||||
else if d-end != none { zz-dir = d-end }
|
||||
else { panic("zigzag: could not auto-detect `zigzag-dir` (no port anchor on either endpoint). Set it explicitly.") }
|
||||
}
|
||||
(points, anchors) = get-zigzag-wire(
|
||||
pts,
|
||||
zigzag-ratio-effective,
|
||||
zigzag-dir,
|
||||
at: normalize-zigzag-at(zigzag-at, zigzag-dir),
|
||||
zz-dir,
|
||||
at: normalize-zigzag-at(zigzag-at, zz-dir),
|
||||
)
|
||||
|
||||
} else if style == "dodge" {
|
||||
@@ -452,10 +764,24 @@
|
||||
} else if style == "u-shape" {
|
||||
(points, anchors) = get-u-shape-wire(
|
||||
pts,
|
||||
u-depth,
|
||||
u-depth-effective,
|
||||
u-side,
|
||||
u-at,
|
||||
ctx
|
||||
)
|
||||
} else if style == "s-shape" {
|
||||
(points, anchors) = get-s-shape-wire(
|
||||
pts,
|
||||
s-axis,
|
||||
s-depth-1,
|
||||
s-ratio-mid,
|
||||
s-depth-2,
|
||||
s-at-1,
|
||||
s-at-mid,
|
||||
s-at-2,
|
||||
strict,
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
|
||||
let mark = (fill: color)
|
||||
@@ -493,7 +819,7 @@
|
||||
if name != none {
|
||||
let names = ()
|
||||
|
||||
if type(name) == str {
|
||||
if type(name) == str or type(name) == content {
|
||||
names = ((name, name-pos),)
|
||||
|
||||
} else if type(name) == array {
|
||||
@@ -588,7 +914,7 @@
|
||||
/// point exposes a port anchor named `<id>-port-<id>` so wires can attach
|
||||
/// to it as if it were a block port. A synthesized `<id>.center` anchor
|
||||
/// is placed opposite `port-side` so #doc-ref("wire.wire") with
|
||||
/// `style: "u-shape"` can auto-detect the port side.
|
||||
/// `style: "u-shape"` (or `"zigzag"`) can auto-detect the port side.
|
||||
///
|
||||
/// #examples.breakout
|
||||
///
|
||||
|
||||
Reference in new issue
Block a user