forked from HEL/circuiteria
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19a48951df |
No files matched your search
@@ -165,6 +165,116 @@ wire.wire("w5", ("p8", "p9"), style: "u-shape",
|
||||
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,
|
||||
)
|
||||
|
||||
#let stub = example(```
|
||||
draw.circle((0, 0), radius: .1, name: "p")
|
||||
wire.stub("p", "north", name: "north", length: 1)
|
||||
|
||||
+7785
-6796
File diff suppressed because it is too large.
Load diff
+265
-1
@@ -10,7 +10,18 @@
|
||||
/// The `"u-shape"` crossbar can also be aligned to an anchor via `u-at`
|
||||
/// (analogous to `zigzag-at`).
|
||||
/// #examples.u-at
|
||||
#let wire-styles = ("direct", "zigzag", "dodge", "guided", "u-shape")
|
||||
///
|
||||
/// 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
|
||||
|
||||
@@ -287,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 = detect-port-side(start, ctx)
|
||||
let side-end = detect-port-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()
|
||||
@@ -394,6 +609,14 @@
|
||||
/// - 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 (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,
|
||||
@@ -418,6 +641,14 @@
|
||||
u-side: auto,
|
||||
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 + "'")
|
||||
@@ -453,6 +684,26 @@
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -516,6 +767,19 @@
|
||||
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)
|
||||
|
||||
Reference in new issue
Block a user