forked from HEL/circuiteria
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26229d8fc4 | ||
|
|
3baf799dca | ||
|
|
a839dbb6d2 | ||
|
|
663614056e | ||
|
|
72eab1620c |
No files matched your search
+279
-192
@@ -23,30 +23,33 @@ wire.stub("block-port-in2", "west")
|
||||
wire.stub("block-port-out", "east")
|
||||
```)
|
||||
|
||||
#let align-with-ports = example(```
|
||||
// `align-with` reuses another port's coord
|
||||
// (E/W: y, N/S: x). Forms:
|
||||
// 1. "id" — local
|
||||
// 2. (element:"e", port:"p") — cross-elmt
|
||||
// 3. "raw-anchor" — CeTZ anchor
|
||||
element.block(x: 0, y: 0, w: 2, h: 2, id: "b1",
|
||||
ports: (
|
||||
west: ((id: "a", name: "A", y: 30%),),
|
||||
east: ((id: "oa", name: "A'",
|
||||
"align-with": "a"),),
|
||||
))
|
||||
element.block(x: 4, y: -.3, w: 2, h: 2.6, id: "b2",
|
||||
ports: (
|
||||
west: ((id: "ia", name: "A",
|
||||
"align-with":
|
||||
(element: "b1", port: "oa")),),
|
||||
east: ((id: "o", name: "C"),),
|
||||
))
|
||||
wire.stub("b1-port-a", "west")
|
||||
wire.wire("w", ("b1-port-oa", "b2-port-ia"),
|
||||
style: "direct")
|
||||
wire.stub("b2-port-o", "east")
|
||||
```, vertical: true)
|
||||
#let align-with-ports = example(
|
||||
```
|
||||
// `align-with` reuses another port's coord
|
||||
// (E/W: y, N/S: x). Forms:
|
||||
// 1. "id" — local
|
||||
// 2. (element:"e", port:"p") — cross-elmt
|
||||
// 3. "raw-anchor" — CeTZ anchor
|
||||
element.block(x: 0, y: 0, w: 2, h: 2, id: "b1",
|
||||
ports: (
|
||||
west: ((id: "a", name: "A", y: 30%),),
|
||||
east: ((id: "oa", name: "A'",
|
||||
"align-with": "a"),),
|
||||
))
|
||||
element.block(x: 4, y: -.3, w: 2, h: 2.6, id: "b2",
|
||||
ports: (
|
||||
west: ((id: "ia", name: "A",
|
||||
"align-with":
|
||||
(element: "b1", port: "oa")),),
|
||||
east: ((id: "o", name: "C"),),
|
||||
))
|
||||
wire.stub("b1-port-a", "west")
|
||||
wire.wire("w", ("b1-port-oa", "b2-port-ia"),
|
||||
style: "direct")
|
||||
wire.stub("b2-port-o", "east")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let extender = example(```
|
||||
element.extender(
|
||||
@@ -80,23 +83,26 @@ for i in range(3) {
|
||||
}
|
||||
```)
|
||||
|
||||
#let wires = example(```
|
||||
for i in range(5) {
|
||||
draw.circle((i * 3, 0), radius: .1, name: "p" + str(i * 2))
|
||||
draw.circle((i * 3 + 2, 1), radius: .1, name: "p" + str(i * 2 + 1))
|
||||
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("w3", ("p4", "p5"), style: "dodge",
|
||||
dodge-y: -0.5, dodge-margins: (0.5, 0.5))
|
||||
wire.wire("w4", ("p6","p7"), style: "guided",
|
||||
guided-center:(20%, 40%), guided-margins: (90%,87%),
|
||||
guided-sides: ("north","south"))
|
||||
wire.wire("w5", ("p8", "p9"), style: "u-shape",
|
||||
u-side: "east", u-depth: 0.5)
|
||||
#let wires = example(
|
||||
```
|
||||
for i in range(5) {
|
||||
draw.circle((i * 3, 0), radius: .1, name: "p" + str(i * 2))
|
||||
draw.circle((i * 3 + 2, 1), radius: .1, name: "p" + str(i * 2 + 1))
|
||||
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("w3", ("p4", "p5"), style: "dodge",
|
||||
dodge-y: -0.5, dodge-margins: (0.5, 0.5))
|
||||
wire.wire("w4", ("p6","p7"), style: "guided",
|
||||
guided-center:(20%, 40%), guided-margins: (90%,87%),
|
||||
guided-sides: ("north","south"))
|
||||
wire.wire("w5", ("p8", "p9"), style: "u-shape",
|
||||
u-side: "east", u-depth: 0.5)
|
||||
|
||||
```, vertical: true)
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let stub = example(```
|
||||
draw.circle((0, 0), radius: .1, name: "p")
|
||||
@@ -134,18 +140,21 @@ for (i, dir) in dirs.enumerate() {
|
||||
}
|
||||
```)
|
||||
|
||||
#let breakout-label = example(```
|
||||
// name-pos matches direction → label sits at the end point.
|
||||
wire.breakout("b1", x: 0, y: 0, direction: "north-east",
|
||||
port-side: "east",
|
||||
name: "at end", name-pos: "north-east")
|
||||
#let breakout-label = example(
|
||||
```
|
||||
// name-pos matches direction → label sits at the end point.
|
||||
wire.breakout("b1", x: 0, y: 0, direction: "north-east",
|
||||
port-side: "east",
|
||||
name: "at end", name-pos: "north-east")
|
||||
|
||||
// name-pos opposite to direction → label would cover the line, so
|
||||
// it falls back to the start point.
|
||||
wire.breakout("b2", x: 4, y: 0, direction: "north-east",
|
||||
port-side: "east",
|
||||
name: "at start", name-pos: "south-west")
|
||||
```, vertical: true)
|
||||
// name-pos opposite to direction → label would cover the line, so
|
||||
// it falls back to the start point.
|
||||
wire.breakout("b2", x: 4, y: 0, direction: "north-east",
|
||||
port-side: "east",
|
||||
name: "at start", name-pos: "south-west")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let breakout-wire = example(```
|
||||
// Bus fan-out: a horizontal wire from the left block to the lower
|
||||
@@ -181,85 +190,133 @@ wire.wire("wtap", ("tap-port-tap", "dstU-port-in"),
|
||||
zigzag-ratio: 100%)
|
||||
```)
|
||||
|
||||
#let gate-and = example(```
|
||||
gates.gate-and(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-and(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let gate-and = example(
|
||||
```
|
||||
gates.gate-and(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-and(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let gate-nand = example(```
|
||||
gates.gate-nand(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-nand(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let gate-nand = example(
|
||||
```
|
||||
gates.gate-nand(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-nand(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let gate-buf = example(```
|
||||
gates.gate-buf(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-buf(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let gate-buf = example(
|
||||
```
|
||||
gates.gate-buf(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-buf(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let gate-not = example(```
|
||||
gates.gate-not(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-not(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let gate-not = example(
|
||||
```
|
||||
gates.gate-not(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-not(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let gate-or = example(```
|
||||
gates.gate-or(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-or(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let gate-or = example(
|
||||
```
|
||||
gates.gate-or(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-or(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let gate-nor = example(```
|
||||
gates.gate-nor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-nor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let gate-nor = example(
|
||||
```
|
||||
gates.gate-nor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-nor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let gate-xor = example(```
|
||||
gates.gate-xor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-xor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let gate-xor = example(
|
||||
```
|
||||
gates.gate-xor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-xor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let gate-xnor = example(```
|
||||
gates.gate-xnor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let gate-xnor = example(
|
||||
```
|
||||
gates.gate-xnor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let iec-gate-and = example(```
|
||||
gates.iec-gate-and(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-and(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let iec-gate-and = example(
|
||||
```
|
||||
gates.iec-gate-and(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-and(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let iec-gate-nand = example(```
|
||||
gates.iec-gate-nand(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-nand(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let iec-gate-nand = example(
|
||||
```
|
||||
gates.iec-gate-nand(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-nand(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let iec-gate-or = example(```
|
||||
gates.iec-gate-or(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-or(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let iec-gate-or = example(
|
||||
```
|
||||
gates.iec-gate-or(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-or(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let iec-gate-nor = example(```
|
||||
gates.iec-gate-nor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-nor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let iec-gate-nor = example(
|
||||
```
|
||||
gates.iec-gate-nor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-nor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let iec-gate-xor = example(```
|
||||
gates.iec-gate-xor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-xor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let iec-gate-xor = example(
|
||||
```
|
||||
gates.iec-gate-xor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-xor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let iec-gate-xnor = example(```
|
||||
gates.iec-gate-xnor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let iec-gate-xnor = example(
|
||||
```
|
||||
gates.iec-gate-xnor(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-xnor(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let iec-gate-buf = example(```
|
||||
gates.iec-gate-buf(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-buf(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let iec-gate-buf = example(
|
||||
```
|
||||
gates.iec-gate-buf(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-buf(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let iec-gate-not = example(```
|
||||
gates.iec-gate-not(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-not(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```, vertical: true)
|
||||
#let iec-gate-not = example(
|
||||
```
|
||||
gates.iec-gate-not(x: 0, y: 0, w: 1.5, h: 1.5)
|
||||
gates.iec-gate-not(x: 3, y: 0, w: 1.5, h: 1.5, inverted: "all")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let group = example(```
|
||||
element.group(
|
||||
@@ -288,47 +345,50 @@ wire.wire("w2", ("b2-port-out", "b3-port-in2"),
|
||||
style: "zigzag")
|
||||
```)
|
||||
|
||||
#let group-ports = example(```
|
||||
// outside-port-names: true (default) — labels outside the border
|
||||
element.group(
|
||||
id: "g1", name: "outside (default)",
|
||||
stroke: (dash: "dashed"),
|
||||
fill: util.colors.blue.lighten(60%),
|
||||
padding: 2em,
|
||||
outside-port-names: true,
|
||||
port-name-padding: 4pt,
|
||||
ports: (
|
||||
west: ((id: "in", name: "D"),),
|
||||
east: ((id: "out", name: "Q"),),
|
||||
),
|
||||
{ element.block(id: "b1", w: 2, h: 1.5, x: 0, y: 0,
|
||||
fill: util.colors.green,
|
||||
ports: (west: ((id: "d", name: "d"),),
|
||||
east: ((id: "q", name: "q"),))) }
|
||||
)
|
||||
wire.wire("wG1in", ("g1-port-in", "b1-port-d"), directed: true)
|
||||
wire.wire("wG1out", ("b1-port-q", "g1-port-out"), directed: true)
|
||||
#let group-ports = example(
|
||||
```
|
||||
// outside-port-names: true (default) — labels outside the border
|
||||
element.group(
|
||||
id: "g1", name: "outside (default)",
|
||||
stroke: (dash: "dashed"),
|
||||
fill: util.colors.blue.lighten(60%),
|
||||
padding: 2em,
|
||||
outside-port-names: true,
|
||||
port-name-padding: 4pt,
|
||||
ports: (
|
||||
west: ((id: "in", name: "D"),),
|
||||
east: ((id: "out", name: "Q"),),
|
||||
),
|
||||
{ element.block(id: "b1", w: 2, h: 1.5, x: 0, y: 0,
|
||||
fill: util.colors.green,
|
||||
ports: (west: ((id: "d", name: "d"),),
|
||||
east: ((id: "q", name: "q"),))) }
|
||||
)
|
||||
wire.wire("wG1in", ("g1-port-in", "b1-port-d"), directed: true)
|
||||
wire.wire("wG1out", ("b1-port-q", "g1-port-out"), directed: true)
|
||||
|
||||
// outside-port-names: false — labels inside the border (block style)
|
||||
// with extra padding via port-name-padding
|
||||
element.group(
|
||||
id: "g2", name: "inside",
|
||||
stroke: (dash: "dashed"),
|
||||
fill: util.colors.orange.lighten(60%),
|
||||
padding: 2em,
|
||||
outside-port-names: false,
|
||||
port-name-padding: 0.4em,
|
||||
ports: (
|
||||
west: ((id: "in", name: "D"),),
|
||||
east: ((id: "out", name: "Q"),),
|
||||
),
|
||||
{ element.block(id: "b2", w: 2, h: 1.5, x: 6, y: 0,
|
||||
fill: util.colors.green,
|
||||
ports: (west: ((id: "d"),), east: ((id: "q"),))) }
|
||||
// outside-port-names: false — labels inside the border (block style)
|
||||
// with extra padding via port-name-padding
|
||||
element.group(
|
||||
id: "g2", name: "inside",
|
||||
stroke: (dash: "dashed"),
|
||||
fill: util.colors.orange.lighten(60%),
|
||||
padding: 2em,
|
||||
outside-port-names: false,
|
||||
port-name-padding: 0.4em,
|
||||
ports: (
|
||||
west: ((id: "in", name: "D"),),
|
||||
east: ((id: "out", name: "Q"),),
|
||||
),
|
||||
{ element.block(id: "b2", w: 2, h: 1.5, x: 6, y: 0,
|
||||
fill: util.colors.green,
|
||||
ports: (west: ((id: "d"),), east: ((id: "q"),))) }
|
||||
)
|
||||
wire.stub("g2-port-in", "west")
|
||||
wire.stub("g2-port-out", "east")
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
wire.stub("g2-port-in", "west")
|
||||
wire.stub("g2-port-out", "east")
|
||||
```, vertical: true)
|
||||
|
||||
#let port-names-outside = example(```
|
||||
element.block(
|
||||
@@ -406,44 +466,71 @@ wire.wire("w2", ((0, 0), (1, -.5)),
|
||||
wire.intersection("w1.zig")
|
||||
```)
|
||||
|
||||
#let u-shape-wire = example(```
|
||||
// east <-> east (u-side auto-detected)
|
||||
element.block(x: 0, y: 0, w: 1.5, h: 1.5, id: "eA",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "out"),)))
|
||||
element.block(x: 0.5, y: -2.5, w: 1.5, h: 1.5, id: "eB",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "out"),)))
|
||||
wire.wire("uEE", ("eA-port-out", "eB-port-out"),
|
||||
style: "u-shape", u-depth: 0.5)
|
||||
#let u-shape-wire = example(
|
||||
```
|
||||
// east <-> east (u-side auto-detected)
|
||||
element.block(x: 0, y: 0, w: 1.5, h: 1.5, id: "eA",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "out"),)))
|
||||
element.block(x: 0.5, y: -2.5, w: 1.5, h: 1.5, id: "eB",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "out"),)))
|
||||
wire.wire("uEE", ("eA-port-out", "eB-port-out"),
|
||||
style: "u-shape", u-depth: 0.5)
|
||||
|
||||
// west <-> west (auto-detected)
|
||||
element.block(x: 6, y: 0, w: 1.5, h: 1.5, id: "wA",
|
||||
fill: util.colors.yellow,
|
||||
ports: (west: ((id: "in"),)))
|
||||
element.block(x: 6.5, y: -2.5, w: 1.5, h: 1.5, id: "wB",
|
||||
fill: util.colors.yellow,
|
||||
ports: (west: ((id: "in"),)))
|
||||
wire.wire("uWW", ("wA-port-in", "wB-port-in"),
|
||||
style: "u-shape", u-depth: 0.5)
|
||||
// west <-> west (auto-detected)
|
||||
element.block(x: 6, y: 0, w: 1.5, h: 1.5, id: "wA",
|
||||
fill: util.colors.yellow,
|
||||
ports: (west: ((id: "in"),)))
|
||||
element.block(x: 6.5, y: -2.5, w: 1.5, h: 1.5, id: "wB",
|
||||
fill: util.colors.yellow,
|
||||
ports: (west: ((id: "in"),)))
|
||||
wire.wire("uWW", ("wA-port-in", "wB-port-in"),
|
||||
style: "u-shape", u-depth: 0.5)
|
||||
|
||||
// north <-> north (auto-detected)
|
||||
element.block(x: 10, y: -3, w: 1.5, h: 1.5, id: "nA",
|
||||
fill: util.colors.green,
|
||||
ports: (north: ((id: "out"),)))
|
||||
element.block(x: 12.5, y: -2.5, w: 1.5, h: 1.5, id: "nB",
|
||||
fill: util.colors.green,
|
||||
ports: (north: ((id: "out"),)))
|
||||
wire.wire("uNN", ("nA-port-out", "nB-port-out"),
|
||||
style: "u-shape", u-depth: 0.5)
|
||||
// north <-> north (auto-detected)
|
||||
element.block(x: 10, y: -3, w: 1.5, h: 1.5, id: "nA",
|
||||
fill: util.colors.green,
|
||||
ports: (north: ((id: "out"),)))
|
||||
element.block(x: 12.5, y: -2.5, w: 1.5, h: 1.5, id: "nB",
|
||||
fill: util.colors.green,
|
||||
ports: (north: ((id: "out"),)))
|
||||
wire.wire("uNN", ("nA-port-out", "nB-port-out"),
|
||||
style: "u-shape", u-depth: 0.5)
|
||||
|
||||
// south <-> south with explicit u-side override
|
||||
element.block(x: 16, y: -3, w: 1.5, h: 1.5, id: "sA",
|
||||
fill: util.colors.pink,
|
||||
ports: (south: ((id: "out"),)))
|
||||
element.block(x: 18.5, y: -3.5, w: 1.5, h: 1.5, id: "sB",
|
||||
fill: util.colors.pink,
|
||||
ports: (south: ((id: "out"),)))
|
||||
wire.wire("uSS", ("sA-port-out", "sB-port-out"),
|
||||
style: "u-shape", u-side: "south", u-depth: 0.5)
|
||||
```, vertical: true)
|
||||
// south <-> south with explicit u-side override
|
||||
element.block(x: 16, y: -3, w: 1.5, h: 1.5, id: "sA",
|
||||
fill: util.colors.pink,
|
||||
ports: (south: ((id: "out"),)))
|
||||
element.block(x: 18.5, y: -3.5, w: 1.5, h: 1.5, id: "sB",
|
||||
fill: util.colors.pink,
|
||||
ports: (south: ((id: "out"),)))
|
||||
wire.wire("uSS", ("sA-port-out", "sB-port-out"),
|
||||
style: "u-shape", u-side: "south", u-depth: 0.5)
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
|
||||
#let zigzag-at = example(
|
||||
```
|
||||
// `zigzag-at` places the middle bend of a zigzag wire
|
||||
// on the axis of a referenced anchor, overriding
|
||||
// `zigzag-ratio`.
|
||||
element.block(x: 0, y: 0, w: 1.5, h: 1.5, id: "src",
|
||||
ports: (east: ((id: "o", name: "O"),),))
|
||||
element.block(x: 3, y: -1.2, w: 1, h: 1, id: "ref",
|
||||
fill: util.colors.pink,
|
||||
ports: (west: ((id: "p", name: "R"),),))
|
||||
element.block(x: 5.5, y: -2, w: 1.5, h: 1.5, id: "dst",
|
||||
ports: (west: ((id: "i", name: "I"),),))
|
||||
// String form: anchor name of a port.
|
||||
wire.wire("w1", ("src-port-o", "dst-port-i"),
|
||||
style: "zigzag",
|
||||
zigzag-at: "ref-port-p")
|
||||
// Ergonomic offset: shift 8pt right of ref.east.
|
||||
wire.wire("w2", ("src-port-o", "dst-port-i"),
|
||||
style: "zigzag",
|
||||
zigzag-at: (rel: 8pt, to: "ref.east"))
|
||||
```,
|
||||
vertical: true,
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
// Demonstrates the `(from: <anchor>, to: <port-id>)` port-alignment shorthand
|
||||
// for the `x` parameter (analogous to the existing shorthand for `y`).
|
||||
#import "/src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
#circuit({
|
||||
// Anchor block: absolute placement, with a single "addr" port on its
|
||||
// north side (auto-centred horizontally by circuiteria).
|
||||
element.block(
|
||||
x: 0, y: 0, w: 2, h: 1.5,
|
||||
id: "vcemem",
|
||||
fill: util.colors.orange,
|
||||
ports: (north: ((id: "addr"),), east: ((id: "out"),))
|
||||
)
|
||||
|
||||
// This block's "addr" port (south side) is x-aligned with vcemem's "addr"
|
||||
// port (north side), even though B is wider than vcemem and not
|
||||
// horizontally aligned by default.
|
||||
element.block(
|
||||
x: (from: "vcemem-port-addr", to: "addr"),
|
||||
y: (rel: 2, to: "vcemem.north"),
|
||||
w: 4.5, h: 1,
|
||||
id: "fsm",
|
||||
fill: util.colors.yellow,
|
||||
ports: (south: ((id: "addr"),), east: ((id: "out"),))
|
||||
)
|
||||
|
||||
wire.wire("wAddr", ("vcemem-port-addr", "fsm-port-addr"), style: "zigzag")
|
||||
})
|
||||
+8152
-7760
File diff suppressed because it is too large.
Load diff
+58
-14
@@ -1,5 +1,5 @@
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "ports.typ": add-ports, add-port
|
||||
#import "/src/cetz.typ": coordinate, draw
|
||||
#import "ports.typ": add-port, add-ports
|
||||
#import "../util.typ"
|
||||
|
||||
#let find-port(ports, id) = {
|
||||
@@ -21,7 +21,9 @@
|
||||
/// - draw-shape (function): Draw function
|
||||
/// - x (number, dictionary): The x position (bottom-left corner).
|
||||
///
|
||||
/// If it is a dictionary, it should be in the format `(rel: number, to: str)`, where `rel` is the offset and `to` the base anchor
|
||||
/// If it is a dictionary, it can take one of two forms:
|
||||
/// - `(rel: number, to: str)`: `rel` is the x offset and `to` the base anchor
|
||||
/// - `(from: str, to: str)`: `from` is the base anchor and `to` is the id of the port to align with the anchor (analogous to `y`)
|
||||
/// - y (number, dictionary): The y position (bottom-left corner).
|
||||
///
|
||||
/// If it is a dictionary, it can take one of two forms:
|
||||
@@ -39,16 +41,22 @@
|
||||
/// - `align-with` (`str`, `dictionary`): Optional. Reuse another port's placement coordinate (east/west: y, north/south: x). Accepted forms: `"<local-id>"` (same-element port; axis must match, transitively resolved, cycles panic), `(element: "<id>", port: "<id>")` (cross-element; axis not validated; target element must be drawn first), or `"<raw-anchor>"` (any unrecognised string is used as a CeTZ anchor name). See #examples.align-with-ports
|
||||
/// - `x` (length, ratio, function): Optional. North/south ports only. Explicit x-offset override measured from the left corner of the side. A `ratio` is used as a percentage along the side; a length/number is used as an absolute offset; a function is called with the element width and must return a length/number. Panics if used together with `align-with` or on an east/west port.
|
||||
/// - `y` (length, ratio, function): Optional. East/west ports only. Analogous to `x`, measured from the top corner of the side and called with the element height when a function is passed.
|
||||
/// - `shape` (`none`, `str`): Optional. Per-port override of the element-level `port-shape` (e.g. `"pentagon"`). Lets a single port on a block or group opt into (or out of) a shape independently of the element default. See the `port-shape` parameter below.
|
||||
/// - `kind` (`str`): Optional. Per-port override of the element-level `port-kind` (`"in"` / `"out"`). Affects pentagon orientation.
|
||||
/// - `size` (length): Optional. Per-port override of the element-level `port-size` (unit size of pentagon-shaped ports).
|
||||
/// - `stroke` (stroke): Optional. Per-port override of the pentagon stroke. Defaults to the element stroke.
|
||||
/// - `fill` (none, color): Optional. Per-port override of the pentagon fill. Defaults to the element fill (or `gray` for clock ports with `shape: "pentagon"`).
|
||||
/// - ports-margins (dictionary): Dictionary of ports margins (used with automatic port placement). They keys are cardinal directions ("north", "east", "south", "west"). The values are tuples of (`<start>`, `<end>`) margins (numbers)
|
||||
/// - port-name-padding (number, length): Padding between the port name label and the port anchor point (default `2pt`)
|
||||
/// - port-names-outside (bool, auto): If true, port name labels are drawn on the outside of the block instead of the inside. When `auto` (default), follows the `shell` setting (outside if `shell: true`, inside otherwise)
|
||||
/// - port-shape (none, str): Default port shape for all ports of this element. Set to `"pentagon"` to draw pentagon-shaped ports. Overridable per port via the port's `shape` field. See #examples.port-shape
|
||||
/// - port-shape (none, str): Default port shape for all ports of this element. Set to `"pentagon"` to draw pentagon-shaped ports. Overridable per port via the port's `shape` field (see the ports dictionary above); a per-port `shape: none` also removes the shape for just that port. See #examples.port-shape
|
||||
/// - port-kind (str): Default direction of ports (`"in"` or `"out"`). Overridable per port via the port's `kind` field
|
||||
/// - port-size (length): Default unit size of pentagon-shaped ports (default `1em`). Overridable per port via the port's `size` field
|
||||
/// - fill (none, color): Fill color
|
||||
/// - stroke (stroke): Border stroke
|
||||
/// - id (str): The block id (for future reference)
|
||||
/// - auto-ports (bool): Whether to use auto port placements or not. If false, `draw-shape` is responsible for adding the appropiate ports
|
||||
/// - ports-x (dictionary): Dictionary of the ports x offsets (used with `auto-ports: false`)
|
||||
/// - ports-y (dictionary): Dictionary of the ports y offsets (used with `auto-ports: false`)
|
||||
/// - shell (bool): If true, the block behaves like a shell/container (analogous to a group): port name labels default to being drawn *outside* the block border, and wires attach to the *inside* tip of pentagon-shaped ports instead of the outside base. Useful for wrapping child elements without having ports overlap them (default `false`)
|
||||
/// - debug (dictionary): Dictionary of debug options.
|
||||
@@ -74,12 +82,13 @@
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
auto-ports: true,
|
||||
ports-x: (:),
|
||||
ports-y: (:),
|
||||
shell: false,
|
||||
body: none,
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
ports: false,
|
||||
),
|
||||
) = draw.get-ctx(ctx => {
|
||||
let width = w
|
||||
let height = h
|
||||
@@ -91,10 +100,42 @@
|
||||
if w == none { panic("Parameter w must be set") }
|
||||
if h == none { panic("Parameter h must be set") }
|
||||
|
||||
if (type(x) == dictionary) {
|
||||
if (type(x) == dictionary and "rel" in x) {
|
||||
x = util.resolve-rel-coord(ctx, x, 0)
|
||||
} else if (type(x) == dictionary) {
|
||||
let from = x.from
|
||||
let to = x.to
|
||||
let (to-side, i) = find-port(ports, to)
|
||||
let margins = (0%, 0%)
|
||||
if to-side in ports-margins {
|
||||
margins = ports-margins.at(to-side)
|
||||
}
|
||||
|
||||
let dx
|
||||
let left-margin
|
||||
if to-side in ("north", "south") {
|
||||
let used-pct = 100% - margins.at(0) - margins.at(1)
|
||||
let used-width = width * used-pct / 100%
|
||||
left-margin = width * margins.at(0) / 100%
|
||||
|
||||
dx = used-width * (i + 1) / (ports.at(to-side).len() + 1)
|
||||
|
||||
if not auto-ports {
|
||||
left-margin = 0
|
||||
dx = ports-x.at(to)(width)
|
||||
}
|
||||
} else if to-side == "west" {
|
||||
dx = 0
|
||||
left-margin = 0
|
||||
} else if to-side == "east" {
|
||||
dx = width
|
||||
left-margin = 0
|
||||
}
|
||||
|
||||
let (ctx, from-pos) = coordinate.resolve(ctx, from)
|
||||
x = from-pos.at(0) + dx - width + left-margin
|
||||
}
|
||||
|
||||
|
||||
if (type(y) == dictionary and "rel" in y) {
|
||||
y = util.resolve-rel-coord(ctx, y, 1)
|
||||
} else if (type(y) == dictionary) {
|
||||
@@ -112,7 +153,7 @@
|
||||
let used-pct = 100% - margins.at(0) - margins.at(1)
|
||||
let used-height = height * used-pct / 100%
|
||||
top-margin = height * margins.at(0) / 100%
|
||||
|
||||
|
||||
dy = used-height * (i + 1) / (ports.at(to-side).len() + 1)
|
||||
|
||||
if not auto-ports {
|
||||
@@ -126,7 +167,7 @@
|
||||
dy = height
|
||||
top-margin = 0
|
||||
}
|
||||
|
||||
|
||||
let (ctx, from-pos) = coordinate.resolve(ctx, from)
|
||||
y = from-pos.at(1) + dy - height + top-margin
|
||||
}
|
||||
@@ -144,9 +185,9 @@
|
||||
if (name != none) {
|
||||
draw.content(
|
||||
(name: id, anchor: name-anchor),
|
||||
anchor: if name-anchor in util.valid-anchors {name-anchor} else {"center"},
|
||||
anchor: if name-anchor in util.valid-anchors { name-anchor } else { "center" },
|
||||
padding: 0.5em,
|
||||
align(center)[*#name*]
|
||||
align(center)[*#name*],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,7 +195,10 @@
|
||||
let outside-name = if port-names-outside == auto { shell } else { port-names-outside }
|
||||
add-ports(
|
||||
id,
|
||||
tl, tr, br, bl,
|
||||
tl,
|
||||
tr,
|
||||
br,
|
||||
bl,
|
||||
ports,
|
||||
ports-margins,
|
||||
width: width,
|
||||
@@ -174,4 +218,4 @@
|
||||
if body != none {
|
||||
body
|
||||
}
|
||||
})
|
||||
})
|
||||
+116
-42
@@ -1,8 +1,11 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "../util.typ": rotate-anchor, opposite-anchor, port-shape-vertices
|
||||
#import "../util.typ": opposite-anchor, port-shape-vertices, rotate-anchor
|
||||
|
||||
#let add-port(
|
||||
elmt-id, side, port, pos,
|
||||
elmt-id,
|
||||
side,
|
||||
port,
|
||||
pos,
|
||||
prev: none,
|
||||
next: none,
|
||||
debug: false,
|
||||
@@ -21,14 +24,15 @@
|
||||
|
||||
// Per-port overrides fall back to element-level defaults.
|
||||
let shape = port.at("shape", default: port-shape)
|
||||
let kind = port.at("kind", default: port-kind)
|
||||
let size = port.at("size", default: port-size)
|
||||
let kind = port.at("kind", default: port-kind)
|
||||
let size = port.at("size", default: port-size)
|
||||
let shape-stroke = port.at("stroke", default: elmt-stroke)
|
||||
// Pentagon fill defaults to the element's fill (so the pentagon covers the
|
||||
// block-border segment it crosses). Clock ports default to gray. Per-port
|
||||
// `fill` overrides both.
|
||||
let shape-fill = port.at("fill",
|
||||
default: if is-clock and shape == "pentagon" { gray } else { elmt-fill })
|
||||
let shape-fill = port.at("fill", default: if is-clock and shape == "pentagon" { gray } else {
|
||||
elmt-fill
|
||||
})
|
||||
|
||||
// When shape is enabled, wire-connection anchor moves outside the block, and
|
||||
// inside-name labels reference the inner tip/base; otherwise both collapse to
|
||||
@@ -42,7 +46,11 @@
|
||||
// Draw pentagon on a higher layer so it always renders on top of wires
|
||||
// and neighbouring elements.
|
||||
draw.on-layer(1, draw.line(
|
||||
v.at(0), v.at(1), v.at(2), v.at(3), v.at(4),
|
||||
v.at(0),
|
||||
v.at(1),
|
||||
v.at(2),
|
||||
v.at(3),
|
||||
v.at(4),
|
||||
close: true,
|
||||
stroke: shape-stroke,
|
||||
fill: shape-fill,
|
||||
@@ -54,12 +62,13 @@
|
||||
panic("Clock port must have previous and next positions")
|
||||
}
|
||||
|
||||
let csize = if port.at("small", default: false) {8pt} else {1em}
|
||||
let csize = if port.at("small", default: false) { 8pt } else { 1em }
|
||||
let offset
|
||||
if (side == "north") { offset = ( 0, -csize) }
|
||||
else if (side == "east") { offset = (-csize, 0) }
|
||||
else if (side == "south") { offset = ( 0, csize) }
|
||||
else if (side == "west") { offset = ( csize, 0) }
|
||||
if (side == "north") { offset = (0, -csize) } else if (side == "east") {
|
||||
offset = (-csize, 0)
|
||||
} else if (side == "south") { offset = (0, csize) } else if (side == "west") {
|
||||
offset = (csize, 0)
|
||||
}
|
||||
|
||||
let pos1 = (rel: offset, to: pos)
|
||||
|
||||
@@ -72,8 +81,7 @@
|
||||
// Horizontal labels: anchor = opposite of the side (e.g. east port → west-anchored text, outside).
|
||||
// Vertical labels: same opposite side but rotated 90°, keeping text readable.
|
||||
let name-anchor = if outside-name {
|
||||
if name-rotate { rotate-anchor(opposite-anchor(side)) }
|
||||
else { opposite-anchor(side) }
|
||||
if name-rotate { rotate-anchor(opposite-anchor(side)) } else { opposite-anchor(side) }
|
||||
} else {
|
||||
if name-rotate { rotate-anchor(side) } else { side }
|
||||
}
|
||||
@@ -91,27 +99,46 @@
|
||||
label-pos,
|
||||
anchor: name-anchor,
|
||||
padding: name-padding,
|
||||
angle: if name-rotate {90deg} else {0deg},
|
||||
name
|
||||
angle: if name-rotate { 90deg } else { 0deg },
|
||||
name,
|
||||
)
|
||||
let id = elmt-id + "-port-" + port.at("id")
|
||||
|
||||
// Cardinal sub-anchor positions.
|
||||
// - Pentagon ports: the four world-cardinal midpoints of the pentagon's
|
||||
// bounding box (all lie on the pentagon boundary).
|
||||
// - Non-pentagon ports: all four collapse to the default attach point.
|
||||
let sub-east = anchor-pos
|
||||
let sub-west = anchor-pos
|
||||
let sub-north = anchor-pos
|
||||
let sub-south = anchor-pos
|
||||
if shape == "pentagon" {
|
||||
let sh = port-shape-vertices(side, kind, size)
|
||||
sub-east = (rel: sh.east, to: pos)
|
||||
sub-west = (rel: sh.west, to: pos)
|
||||
sub-north = (rel: sh.north, to: pos)
|
||||
sub-south = (rel: sh.south, to: pos)
|
||||
}
|
||||
|
||||
// Expose the port as a CeTZ group carrying the default attach point plus
|
||||
// the four cardinal sub-anchors (`.east`, `.west`, `.north`, `.south`).
|
||||
// The custom anchors override CeTZ's auto-generated bbox anchors of the
|
||||
// same name.
|
||||
draw.group(name: id, {
|
||||
draw.anchor("default", anchor-pos)
|
||||
draw.anchor("east", sub-east)
|
||||
draw.anchor("west", sub-west)
|
||||
draw.anchor("north", sub-north)
|
||||
draw.anchor("south", sub-south)
|
||||
})
|
||||
|
||||
if debug {
|
||||
draw.circle(
|
||||
anchor-pos,
|
||||
name: id,
|
||||
radius: .1,
|
||||
stroke: none,
|
||||
fill: red
|
||||
fill: red,
|
||||
)
|
||||
|
||||
} else {
|
||||
draw.hide(draw.circle(
|
||||
anchor-pos,
|
||||
radius: 0,
|
||||
stroke: none,
|
||||
name: id
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,8 +181,17 @@
|
||||
return (placements, (pct: p.pct))
|
||||
}
|
||||
if id in visiting {
|
||||
panic("align-with cycle involving port '" + id + "'" + where + " (path: "
|
||||
+ visiting.join(" -> ") + " -> " + id + ")")
|
||||
panic(
|
||||
"align-with cycle involving port '"
|
||||
+ id
|
||||
+ "'"
|
||||
+ where
|
||||
+ " (path: "
|
||||
+ visiting.join(" -> ")
|
||||
+ " -> "
|
||||
+ id
|
||||
+ ")",
|
||||
)
|
||||
}
|
||||
|
||||
let raw = p.raw
|
||||
@@ -171,12 +207,26 @@
|
||||
panic("Port '" + id + "'" + where + ": cannot combine `align-with` with `x`/`y` override")
|
||||
}
|
||||
if is-ew and has-x {
|
||||
panic("Port '" + id + "'" + where + " on side '" + side
|
||||
+ "': use `y` for placement override on east/west sides, not `x`")
|
||||
panic(
|
||||
"Port '"
|
||||
+ id
|
||||
+ "'"
|
||||
+ where
|
||||
+ " on side '"
|
||||
+ side
|
||||
+ "': use `y` for placement override on east/west sides, not `x`",
|
||||
)
|
||||
}
|
||||
if (not is-ew) and has-y {
|
||||
panic("Port '" + id + "'" + where + " on side '" + side
|
||||
+ "': use `x` for placement override on north/south sides, not `y`")
|
||||
panic(
|
||||
"Port '"
|
||||
+ id
|
||||
+ "'"
|
||||
+ where
|
||||
+ " on side '"
|
||||
+ side
|
||||
+ "': use `x` for placement override on north/south sides, not `y`",
|
||||
)
|
||||
}
|
||||
|
||||
let result = none
|
||||
@@ -205,8 +255,19 @@
|
||||
let ref-side = placements.at(ref-id).side
|
||||
let ref-ew = ref-side in ("east", "west")
|
||||
if is-ew != ref-ew {
|
||||
panic("Port '" + id + "'" + where + " on '" + side + "' cannot `align-with` port '"
|
||||
+ ref-id + "' on '" + ref-side + "': incompatible axis")
|
||||
panic(
|
||||
"Port '"
|
||||
+ id
|
||||
+ "'"
|
||||
+ where
|
||||
+ " on '"
|
||||
+ side
|
||||
+ "' cannot `align-with` port '"
|
||||
+ ref-id
|
||||
+ "' on '"
|
||||
+ ref-side
|
||||
+ "': incompatible axis",
|
||||
)
|
||||
}
|
||||
let new-visiting = visiting + (id,)
|
||||
let res = resolve-placement(placements, ref-id, new-visiting, elmt-id: elmt-id)
|
||||
@@ -230,7 +291,10 @@
|
||||
|
||||
#let add-ports(
|
||||
elmt-id,
|
||||
tl, tr, br, bl,
|
||||
tl,
|
||||
tr,
|
||||
br,
|
||||
bl,
|
||||
ports,
|
||||
ports-margins,
|
||||
width: 0,
|
||||
@@ -249,7 +313,7 @@
|
||||
"north": (tl, tr),
|
||||
"east": (tr, br),
|
||||
"south": (bl, br),
|
||||
"west": (tl, bl)
|
||||
"west": (tl, bl),
|
||||
)
|
||||
|
||||
if type(ports) != dictionary {
|
||||
@@ -282,9 +346,19 @@
|
||||
for (i, port) in side-ports.enumerate() {
|
||||
let pid = port.at("id")
|
||||
if pid in placements {
|
||||
panic("Duplicate port id '" + pid + "' in element '" + elmt-id
|
||||
+ "' on side '" + side + "' (existing side: '"
|
||||
+ placements.at(pid).side + "', new port: " + repr(port) + ")")
|
||||
panic(
|
||||
"Duplicate port id '"
|
||||
+ pid
|
||||
+ "' in element '"
|
||||
+ elmt-id
|
||||
+ "' on side '"
|
||||
+ side
|
||||
+ "' (existing side: '"
|
||||
+ placements.at(pid).side
|
||||
+ "', new port: "
|
||||
+ repr(port)
|
||||
+ ")",
|
||||
)
|
||||
}
|
||||
let pct-auto = (i + 1) * space
|
||||
placements.insert(pid, (
|
||||
@@ -329,19 +403,19 @@
|
||||
// north/south: same x as remote, y from this side (pt0.y == pt1.y)
|
||||
let ref = p.external-ref
|
||||
if is-ew {
|
||||
pos = (pt0, "|-", ref)
|
||||
pos = (pt0, "|-", ref)
|
||||
// Fallback prev/next: just the pos itself; clock ports do not
|
||||
// combine meaningfully with cross-element align.
|
||||
pos-prev = pos
|
||||
pos-next = pos
|
||||
} else {
|
||||
pos = (ref, "|-", pt0)
|
||||
pos = (ref, "|-", pt0)
|
||||
pos-prev = pos
|
||||
pos-next = pos
|
||||
}
|
||||
} else {
|
||||
let pct = p.pct
|
||||
pos = (pt0, pct, pt1)
|
||||
pos = (pt0, pct, pt1)
|
||||
pos-prev = (pt0, pct - 0.5 * space, pt1)
|
||||
pos-next = (pt0, pct + 0.5 * space, pt1)
|
||||
|
||||
|
||||
+64
-25
@@ -36,7 +36,7 @@
|
||||
green: rgb(127, 200, 172),
|
||||
pink: rgb(236, 127, 178),
|
||||
purple: rgb(189, 151, 255),
|
||||
blue: rgb(127, 203, 235)
|
||||
blue: rgb(127, 203, 235),
|
||||
)
|
||||
|
||||
/// Pads a string on the left with 0s to the given length
|
||||
@@ -67,7 +67,7 @@
|
||||
north-west: "south-east",
|
||||
north-east: "south-west",
|
||||
south-east: "north-west",
|
||||
south-west: "north-east"
|
||||
south-west: "north-east",
|
||||
).at(anchor)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
north-west: "north-east",
|
||||
north-east: "south-east",
|
||||
south-east: "south-west",
|
||||
south-west: "north-west"
|
||||
south-west: "north-west",
|
||||
).at(anchor)
|
||||
}
|
||||
|
||||
@@ -105,11 +105,11 @@
|
||||
// Base case: west port, input (tip east / into block).
|
||||
// Vertices as (dx, dy) offsets relative to the boundary anchor.
|
||||
let base-vertices = (
|
||||
(-u, -u/2),
|
||||
(-u, +u/2),
|
||||
(u/2, +u/2),
|
||||
(u, 0), // tip east (inner side)
|
||||
(u/2, -u/2),
|
||||
(-u, -u / 2),
|
||||
(-u, +u / 2),
|
||||
(u / 2, +u / 2),
|
||||
(u, 0), // tip east (inner side)
|
||||
(u / 2, -u / 2),
|
||||
)
|
||||
// For output, mirror horizontally so the tip points west (outside).
|
||||
if kind == "out" {
|
||||
@@ -126,22 +126,58 @@
|
||||
// north: (x, y) → (y, -x)
|
||||
// east: (x, y) → (-x, -y)
|
||||
// south: (x, y) → (-y, x)
|
||||
let rot = if side == "west" { p => p }
|
||||
else if side == "north" { p => (p.at(1), -p.at(0)) }
|
||||
else if side == "east" { p => (-p.at(0), -p.at(1)) }
|
||||
else if side == "south" { p => (-p.at(1), p.at(0)) }
|
||||
else { panic("Unknown side: " + side) }
|
||||
let rot = if side == "west" { p => p } else if side == "north" {
|
||||
p => (p.at(1), -p.at(0))
|
||||
} else if side == "east" { p => (-p.at(0), -p.at(1)) } else if side == "south" {
|
||||
p => (-p.at(1), p.at(0))
|
||||
} else { panic("Unknown side: " + side) }
|
||||
|
||||
// World-cardinal midpoints of the pentagon's bounding box. These are
|
||||
// named in *world* coordinates (not rotated with the shape), so `.east`
|
||||
// always sits at the world +x extent of the pentagon, `.north` at +y, etc.
|
||||
// Consequently the tip's cardinal name follows its world direction:
|
||||
// - west side, kind "in" → tip world +x → tip = .east
|
||||
// - west side, kind "out" → tip world -x → tip = .west
|
||||
// - east side, kind "in" → tip world -x → tip = .west
|
||||
// - east side, kind "out" → tip world +x → tip = .east
|
||||
// - north side, kind "in" → tip world -y → tip = .south
|
||||
// - north side, kind "out" → tip world +y → tip = .north
|
||||
// - south side, kind "in" → tip world +y → tip = .north
|
||||
// - south side, kind "out" → tip world -y → tip = .south
|
||||
//
|
||||
// The pentagon bounding box extents depend only on the side's orientation
|
||||
// (horizontal for west/east, vertical for north/south), not on `kind`.
|
||||
let (hx, hy) = if side == "west" or side == "east" {
|
||||
(u, u / 2)
|
||||
} else {
|
||||
(u / 2, u)
|
||||
}
|
||||
let world-east = (hx, 0)
|
||||
let world-west = (-hx, 0)
|
||||
let world-north = (0, hy)
|
||||
let world-south = (0, -hy)
|
||||
|
||||
return (
|
||||
vertices: base-vertices.map(rot),
|
||||
outer: rot(base-outer),
|
||||
inner: rot(base-inner),
|
||||
east: world-east,
|
||||
west: world-west,
|
||||
north: world-north,
|
||||
south: world-south,
|
||||
)
|
||||
}
|
||||
|
||||
#let valid-anchors = (
|
||||
"center", "north", "east", "west", "south",
|
||||
"north-east", "north-west", "south-east", "south-west"
|
||||
"center",
|
||||
"north",
|
||||
"east",
|
||||
"west",
|
||||
"south",
|
||||
"north-east",
|
||||
"north-west",
|
||||
"south-east",
|
||||
"south-west",
|
||||
)
|
||||
|
||||
/// Normalizes a coordinate so that 2D points (x, y) are padded
|
||||
@@ -162,16 +198,19 @@
|
||||
// Function-based coordinate: (fn, point)
|
||||
if type(a) == function {
|
||||
let orig = a
|
||||
return (v => {
|
||||
let result = orig(v)
|
||||
if type(result) == array and result.len() == 2 {
|
||||
let (rx, ry) = result
|
||||
if type(rx) in (int, float) and type(ry) in (int, float) {
|
||||
return (rx, ry, 0)
|
||||
return (
|
||||
v => {
|
||||
let result = orig(v)
|
||||
if type(result) == array and result.len() == 2 {
|
||||
let (rx, ry) = result
|
||||
if type(rx) in (int, float) and type(ry) in (int, float) {
|
||||
return (rx, ry, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}, b)
|
||||
return result
|
||||
},
|
||||
b,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,4 +222,4 @@
|
||||
return result
|
||||
}
|
||||
return coord
|
||||
}
|
||||
}
|
||||
+94
-23
@@ -27,29 +27,80 @@
|
||||
return (pts, anchors)
|
||||
}
|
||||
|
||||
#let get-zigzag-wire(pts, ratio, dir) = {
|
||||
// Normalize a `zigzag-at` value.
|
||||
//
|
||||
// Accepted forms:
|
||||
// - `none` → unset; ratio-based placement is used.
|
||||
// - `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
|
||||
// `(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) = {
|
||||
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" {
|
||||
return (rel: (r, 0), to: at.to)
|
||||
} else {
|
||||
return (rel: (0, r), to: at.to)
|
||||
}
|
||||
}
|
||||
return at
|
||||
}
|
||||
|
||||
#let get-zigzag-wire(pts, ratio, dir, at: none) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
let mid = if dir == "vertical" {
|
||||
(start, ratio, (horizontal: end, vertical: ()))
|
||||
} else {
|
||||
(start, ratio, (horizontal: (), vertical: end))
|
||||
}
|
||||
|
||||
let points = if dir == "vertical" {
|
||||
(
|
||||
start,
|
||||
(horizontal: mid, vertical: ()),
|
||||
(horizontal: (), vertical: end),
|
||||
end
|
||||
)
|
||||
let points
|
||||
if at == none {
|
||||
// Ratio-based placement (default).
|
||||
let mid = if dir == "vertical" {
|
||||
(start, ratio, (horizontal: end, vertical: ()))
|
||||
} else {
|
||||
(start, ratio, (horizontal: (), vertical: end))
|
||||
}
|
||||
|
||||
points = if dir == "vertical" {
|
||||
(
|
||||
start,
|
||||
(horizontal: mid, vertical: ()),
|
||||
(horizontal: (), vertical: end),
|
||||
end
|
||||
)
|
||||
} else {
|
||||
(
|
||||
start,
|
||||
(horizontal: (), vertical: mid),
|
||||
(horizontal: end, vertical: ()),
|
||||
end
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(
|
||||
start,
|
||||
(horizontal: (), vertical: mid),
|
||||
(horizontal: end, vertical: ()),
|
||||
end
|
||||
)
|
||||
// Anchor-based placement: the middle bend segment sits on the axis
|
||||
// extracted from `at` via CeTZ's perpendicular operator `|-`:
|
||||
// - vertical: middle segment at x = at.x
|
||||
// - horizontal: middle segment at y = at.y
|
||||
points = if dir == "vertical" {
|
||||
(
|
||||
start,
|
||||
(at, "|-", start),
|
||||
(at, "|-", end),
|
||||
end
|
||||
)
|
||||
} else {
|
||||
(
|
||||
start,
|
||||
(start, "|-", at),
|
||||
(end, "|-", at),
|
||||
end
|
||||
)
|
||||
}
|
||||
}
|
||||
let anchors = (
|
||||
"start": start,
|
||||
@@ -301,8 +352,9 @@
|
||||
/// - reverse (bool): If true, the start and end points will be swapped (useful in cases where the start point depends on the end point, for example with perpendiculars)
|
||||
/// - 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 (ratio): Position of the zigzag vertical relative to the horizontal span (only with style "zigzag")
|
||||
/// - 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-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")
|
||||
/// - dodge-margins (array): The start and end margins (i.e. space before dodging) of the wire (only with style "dodge")
|
||||
@@ -326,8 +378,9 @@
|
||||
guided-sides: ("east", "west"),
|
||||
directed: false,
|
||||
rotate-name: true,
|
||||
zigzag-ratio: 50%,
|
||||
zigzag-ratio: auto,
|
||||
zigzag-dir: "vertical",
|
||||
zigzag-at: none,
|
||||
dodge-y: 0,
|
||||
dodge-sides: ("east", "west"),
|
||||
dodge-margins: (5%, 5%),
|
||||
@@ -342,6 +395,19 @@
|
||||
panic("Wrong number of points (got " + str(pts.len()) + " instead of 2)")
|
||||
}
|
||||
|
||||
// `zigzag-at` validation.
|
||||
if zigzag-at != none {
|
||||
if style != "zigzag" {
|
||||
panic("wire '" + id + "': `zigzag-at` requires style: \"zigzag\" (got \""
|
||||
+ style + "\")")
|
||||
}
|
||||
if zigzag-ratio != auto {
|
||||
panic("wire '" + id
|
||||
+ "': cannot combine `zigzag-at` with an explicit `zigzag-ratio`")
|
||||
}
|
||||
}
|
||||
let zigzag-ratio-effective = if zigzag-ratio == auto { 50% } else { zigzag-ratio }
|
||||
|
||||
// Normalize user-provided coordinates: (x, y) -> (x, y, 0)
|
||||
let pts = pts.map(normalize-coord)
|
||||
|
||||
@@ -360,7 +426,12 @@
|
||||
(points, anchors) = get-direct-wire(pts)
|
||||
|
||||
} else if style == "zigzag" {
|
||||
(points, anchors) = get-zigzag-wire(pts, zigzag-ratio, zigzag-dir)
|
||||
(points, anchors) = get-zigzag-wire(
|
||||
pts,
|
||||
zigzag-ratio-effective,
|
||||
zigzag-dir,
|
||||
at: normalize-zigzag-at(zigzag-at, zigzag-dir),
|
||||
)
|
||||
|
||||
} else if style == "dodge" {
|
||||
(points, anchors) = get-dodge-wire(
|
||||
@@ -422,7 +493,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 {
|
||||
|
||||
Reference in new issue
Block a user