Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7dd2cb718
|
||
|
|
ac3e577020
|
||
|
|
ba0f138ef2
|
||
|
|
60898d1e60
|
||
|
|
b8a7adb58f
|
||
|
|
cc8478a3c7
|
No files matched your search
@@ -3,7 +3,7 @@
|
||||
Circuiteria is a [Typst](https://typst.app) package for drawing block circuit diagrams using the [CeTZ](https://typst.app/universe/package/cetz) package.
|
||||
|
||||
<p align="center">
|
||||
<img src="./gallery/platypus.png" alt="A platypus / Perry the Platypus meme made with colored rounded rectangles">
|
||||
<img src="./gallery/platypus.png" alt="Perry the platypus">
|
||||
</p>
|
||||
|
||||
## Examples
|
||||
@@ -11,17 +11,17 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a href="./gallery/test.typ">
|
||||
<img src="./gallery/test.png" width="500px" alt="Block diagram of a multi-cycle RISC-V FPGA processor">
|
||||
<img src="./gallery/test.png" width="500px">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">A bit of everything</td>
|
||||
<td colspan="2">A bit of eveything</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a href="./gallery/test5.typ">
|
||||
<img src="./gallery/test5.png" width="500px" alt="Block diagram of a single-cycle RISC-V processor">
|
||||
<img src="./gallery/test5.png" width="500px">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -31,12 +31,12 @@ Circuiteria is a [Typst](https://typst.app) package for drawing block circuit di
|
||||
<tr>
|
||||
<td>
|
||||
<a href="./gallery/test4.typ">
|
||||
<img src="./gallery/test4.png" width="250px" alt="Top-level block diagram of a single-cycle RISC-V processor">
|
||||
<img src="./gallery/test4.png" width="250px">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="./gallery/test6.typ">
|
||||
<img src="./gallery/test6.png" width="250px" alt="Block diagram of a simple single-cycle RISC-V ALU">
|
||||
<img src="./gallery/test6.png" width="250px">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -56,7 +56,7 @@ For more information, see the [manual](manual.pdf)
|
||||
|
||||
To use this package, simply import [circuiteria](https://typst.app/universe/package/circuiteria) and call the `circuit` function:
|
||||
```typ
|
||||
#import "@preview/circuiteria:0.2.1"
|
||||
#import "@preview/circuiteria:0.2.0"
|
||||
#circuiteria.circuit({
|
||||
import circuiteria: *
|
||||
...
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "../src/circuit.typ": circuit
|
||||
#import "../src/util.typ"
|
||||
|
||||
|
||||
@@ -23,34 +23,6 @@ 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 extender = example(```
|
||||
element.extender(
|
||||
x: 0, y: 0, w: 3, h: 1,
|
||||
@@ -83,26 +55,17 @@ 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", 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",
|
||||
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,
|
||||
)
|
||||
#let wires = example(```
|
||||
for i in range(3) {
|
||||
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))
|
||||
```, vertical: true)
|
||||
|
||||
#let stub = example(```
|
||||
draw.circle((0, 0), radius: .1, name: "p")
|
||||
@@ -112,211 +75,45 @@ wire.stub("p", "south", name: "south", length: 15pt)
|
||||
wire.stub("p", "west", name: "west", length: 3em)
|
||||
```)
|
||||
|
||||
#let breakout = example(```
|
||||
// 16 breakouts: 4 directions × 4 label positions, laid out
|
||||
// two per row so the labels have room to breathe.
|
||||
let dirs = ("north-east", "north-west", "south-east", "south-west")
|
||||
let poss = ("north-east", "north-west", "south-east", "south-west")
|
||||
let short = (
|
||||
"north-east": "NE", "north-west": "NW",
|
||||
"south-east": "SE", "south-west": "SW",
|
||||
)
|
||||
let cols = 2
|
||||
let dx = 5
|
||||
let dy = 2
|
||||
for (i, dir) in dirs.enumerate() {
|
||||
for (j, pos) in poss.enumerate() {
|
||||
let n = i * poss.len() + j
|
||||
let r = calc.floor(n / cols)
|
||||
let c = calc.rem(n, cols)
|
||||
draw.circle((c * dx, -r * dy), radius: .05)
|
||||
wire.breakout("b" + str(n),
|
||||
x: c * dx, y: -r * dy,
|
||||
direction: dir,
|
||||
port-side: if dir.ends-with("east") { "west" } else { "east" },
|
||||
name: short.at(dir) + "/" + short.at(pos),
|
||||
name-pos: pos)
|
||||
}
|
||||
}
|
||||
```)
|
||||
#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 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 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)
|
||||
|
||||
// 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 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 breakout-wire = example(```
|
||||
// Bus fan-out: a horizontal wire from the left block to the lower
|
||||
// right block carries the full 10-bit bus. A breakout in the middle
|
||||
// of that wire taps bits [9:5] and routes them to the upper right
|
||||
// block via an L-shaped zigzag (up, then right).
|
||||
element.block(x: 0, y: 0, w: 2, h: 2, id: "src",
|
||||
fill: util.colors.blue,
|
||||
ports: (east: ((id: "out"),)))
|
||||
element.block(x: 6, y: 0, w: 2, h: 2, id: "dstL",
|
||||
fill: util.colors.green,
|
||||
ports: (west: ((id: "in"),)))
|
||||
element.block(x: 6, y: 4, w: 2, h: 2, id: "dstU",
|
||||
fill: util.colors.orange,
|
||||
ports: (west: ((id: "in"),)))
|
||||
#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)
|
||||
|
||||
// Full bus wire, labelled "[9:0]" on the left.
|
||||
wire.wire("wbus", ("src-port-out", "dstL-port-in"),
|
||||
name: "[9:0]", name-pos: "start")
|
||||
#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)
|
||||
|
||||
// Breakout tapped from the midpoint of the bus.
|
||||
wire.breakout("tap",
|
||||
x: (rel: 2, to: "src-port-out"),
|
||||
y: (from: "src-port-out"),
|
||||
direction: "north-east",
|
||||
port-side: "west",
|
||||
name: "[9:5]", name-pos: "north-east")
|
||||
#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)
|
||||
|
||||
// L-shaped wire from the breakout up and then right to the upper
|
||||
// block's input.
|
||||
wire.wire("wtap", ("tap-port-tap", "dstU-port-in"),
|
||||
style: "zigzag", zigzag-dir: "horizontal",
|
||||
zigzag-ratio: 100%)
|
||||
```)
|
||||
#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-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-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-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-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 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-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-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-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 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 group = example(```
|
||||
element.group(
|
||||
@@ -345,326 +142,27 @@ 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)
|
||||
|
||||
// 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,
|
||||
)
|
||||
|
||||
#let port-names-outside = example(```
|
||||
element.block(
|
||||
x: 0, y: 0, w: 2, h: 2, id: "block",
|
||||
port-names-outside: true,
|
||||
port-name-padding: 4pt,
|
||||
ports: (
|
||||
north: ((id: "clk", name: "CLK", clock: true),),
|
||||
west: ((id: "in1", name: "A"),
|
||||
(id: "in2", name: "B")),
|
||||
east: ((id: "out", name: "Y"),)
|
||||
)
|
||||
)
|
||||
```)
|
||||
|
||||
#let port-shape = example(```
|
||||
// Block with pentagon-shaped ports.
|
||||
// - port-shape / port-kind / port-size are element-level defaults.
|
||||
// - Any port field (shape, kind, size, stroke, fill) overrides per port.
|
||||
// - kind: "in" → tip points into the block (default)
|
||||
// kind: "out" → tip points away from the block
|
||||
// - clock ports with shape: "pentagon" drop their triangle and default
|
||||
// to a gray fill.
|
||||
element.block(
|
||||
id: "b", w: 3, h: 3, x: 0, y: 0,
|
||||
fill: util.colors.yellow.lighten(60%),
|
||||
port-shape: "pentagon",
|
||||
port-size: 1em,
|
||||
ports: (
|
||||
// west: default input
|
||||
west: ((id: "in", name: "D"),),
|
||||
// east: output — tip points away
|
||||
east: ((id: "out", name: "Q", kind: "out"),),
|
||||
// north: clock port — pentagon replaces the triangle, gray fill
|
||||
north: ((id: "clk", name: "CLK", clock: true),),
|
||||
// south: styled output with per-port fill and stroke
|
||||
south: ((id: "rdy", name: "RDY", kind: "out",
|
||||
fill: util.colors.blue, stroke: red + 1.5pt),),
|
||||
),
|
||||
)
|
||||
// Wires connect at the outer end of each pentagon.
|
||||
wire.stub("b-port-in", "west")
|
||||
wire.stub("b-port-out", "east")
|
||||
wire.stub("b-port-clk", "north")
|
||||
wire.stub("b-port-rdy", "south")
|
||||
```)
|
||||
|
||||
#let shell = example(```
|
||||
// A shell block acts as a container: port name labels are drawn
|
||||
// outside by default, and wires attach at the *inside* tip of
|
||||
// pentagon-shaped ports so child elements can sit within.
|
||||
element.block(
|
||||
id: "sh", w: 5, h: 3, x: 0, y: 0,
|
||||
shell: true,
|
||||
port-shape: "pentagon",
|
||||
fill: util.colors.blue.lighten(80%),
|
||||
ports: (
|
||||
west: ((id: "in", name: "IN"),),
|
||||
east: ((id: "out", name: "OUT", kind: "out"),),
|
||||
),
|
||||
{
|
||||
element.block(id: "inner", w: 2, h: 1.5, x: 1.5, y: 0.75,
|
||||
fill: util.colors.yellow,
|
||||
ports: (west: ((id: "a"),), east: ((id: "b"),)))
|
||||
}
|
||||
)
|
||||
wire.wire("w-in", ("sh-port-in", "inner-port-a"))
|
||||
wire.wire("w-out", ("inner-port-b", "sh-port-out"))
|
||||
```)
|
||||
|
||||
#let intersection = example(```
|
||||
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag", zigzag-dir: "vertical")
|
||||
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag")
|
||||
wire.wire("w2", ((0, 0), (1, -.5)),
|
||||
style: "zigzag", zigzag-dir: "vertical", zigzag-ratio: 80%)
|
||||
style: "zigzag", zigzag-ratio: 80%)
|
||||
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)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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 capacitor = example(```
|
||||
electrical.capacitor(
|
||||
x: 0, y: 0, w: 2, h: 1, id: "a",
|
||||
scales: (100%, 80%), gap: 0.3
|
||||
)
|
||||
|
||||
#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,
|
||||
electrical.capacitor(
|
||||
x: 4, y: -0.5, w: 1, h: 2, id: "b",
|
||||
vertical: true, symbols: ([+], none)
|
||||
)
|
||||
```, 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,
|
||||
)
|
||||
#let resistor = example(```
|
||||
electrical.resistor(x: 0, y: 0, w: 2, h: 0.5, id: "a", zigzags: 8)
|
||||
electrical.resistor(x: 4, y: -0.5, w: 0.5, h: 2, id: "b", vertical: true)
|
||||
electrical.resistor(x: 6.5, y: 0, w: 2, h: 0.5, id: "c", zigzags: none)
|
||||
```, vertical: true)
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 45 KiB |
@@ -1,4 +1,4 @@
|
||||
#import "@preview/circuiteria:0.2.1": *
|
||||
#import "../src/lib.typ": *
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 142 KiB |
@@ -1,4 +1,4 @@
|
||||
#import "@preview/circuiteria:0.2.1": circuit, element, util, wire
|
||||
#import "../src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 142 KiB |
@@ -1,4 +1,4 @@
|
||||
#import "@preview/circuiteria:0.2.1": circuit, element, util, wire
|
||||
#import "../src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 81 KiB |
@@ -1,4 +1,5 @@
|
||||
#import "@preview/circuiteria:0.2.1": circuit, element, util, wire
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "../src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
@@ -80,4 +81,57 @@
|
||||
element.gate-xnor(
|
||||
x: 9, y: -6, w: 2, h: 2, id: "xnor"
|
||||
)
|
||||
|
||||
element.resistor(
|
||||
x: 0, y: -8, w: 2, h: 0.5, id: "res1"
|
||||
)
|
||||
|
||||
element.capacitor(
|
||||
x: 3, y: (from: "res1-port-1", to: "0"),
|
||||
w: 2, h: 0.6,
|
||||
id: "cap1",
|
||||
scales: (100%, 80%),
|
||||
symbols: ([+], [-])
|
||||
)
|
||||
|
||||
element.resistor(
|
||||
x: (rel: 1, to: "cap1-port-1"),
|
||||
y: (from: "cap1-port-1", to: "0"),
|
||||
w: 0.5, h: 2,
|
||||
id: "res2",
|
||||
vertical: true,
|
||||
zigzags: 8
|
||||
)
|
||||
|
||||
element.capacitor(
|
||||
x: (rel: 1, to: "res2.east"),
|
||||
y: (from: "res2-port-1", to: "1"),
|
||||
w: 0.5, h: 2,
|
||||
id: "cap2",
|
||||
vertical: true,
|
||||
symbols: ([a], [b])
|
||||
)
|
||||
|
||||
element.resistor(
|
||||
x: (rel: 1, to: "cap2-port-0"),
|
||||
y: (from: "cap2-port-0", to: "0"),
|
||||
w: 2, h: 0.5,
|
||||
id: "res3",
|
||||
zigzags: none
|
||||
)
|
||||
|
||||
element.resistor(
|
||||
x: (rel: 1, to: "res3-port-1"),
|
||||
y: (from: "res3-port-1", to: "0"),
|
||||
w: 0.5, h: 2,
|
||||
id: "res4",
|
||||
zigzags: none,
|
||||
vertical: true
|
||||
)
|
||||
|
||||
wire.wire("w4", ("res1-port-1", "cap1-port-0"))
|
||||
wire.wire("w5", ("cap1-port-1", "res2-port-0"))
|
||||
wire.wire("w6", ("res2-port-1", "cap2-port-1"))
|
||||
wire.wire("w7", ("cap2-port-0", "res3-port-0"))
|
||||
wire.wire("w8", ("res3-port-1", "res4-port-0"))
|
||||
})
|
||||
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 159 KiB |
@@ -1,5 +1,5 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/circuiteria:0.2.1": *
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "../src/lib.typ": *
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 275 KiB |
@@ -1,5 +1,5 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/circuiteria:0.2.1": *
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "../src/lib.typ": *
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 76 KiB |
@@ -1,5 +1,5 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/circuiteria:0.2.1": *
|
||||
#import "@preview/cetz:0.3.2": draw, vector
|
||||
#import "../src/lib.typ": *
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 5.8 KiB |
@@ -1,97 +0,0 @@
|
||||
#import "@preview/circuiteria:0.2.1": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
#circuit({
|
||||
element.iec-gate-buf(
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 2,
|
||||
h: 2,
|
||||
id: "iec-buf",
|
||||
inputs: 1,
|
||||
)
|
||||
wire.stub("iec-buf-port-in0", "west")
|
||||
|
||||
element.iec-gate-not(
|
||||
x: 3,
|
||||
y: 0,
|
||||
w: 2,
|
||||
h: 2,
|
||||
id: "iec-not",
|
||||
inputs: 1,
|
||||
)
|
||||
wire.stub("iec-not-port-in0", "west")
|
||||
|
||||
element.iec-gate-and(
|
||||
id: "iec-and",
|
||||
x: 0,
|
||||
y: -3,
|
||||
w: 2,
|
||||
h: 2,
|
||||
inputs: 2,
|
||||
)
|
||||
for i in range(2) {
|
||||
wire.stub("iec-and-port-in" + str(i), "west")
|
||||
}
|
||||
|
||||
element.iec-gate-nand(
|
||||
id: "iec-nand",
|
||||
x: 3,
|
||||
y: -3,
|
||||
w: 2,
|
||||
h: 2,
|
||||
inputs: 2,
|
||||
)
|
||||
for i in range(2) {
|
||||
wire.stub("iec-nand-port-in" + str(i), "west")
|
||||
}
|
||||
|
||||
element.iec-gate-or(
|
||||
id: "iec-or",
|
||||
x: 0,
|
||||
y: -6,
|
||||
w: 2,
|
||||
h: 2,
|
||||
inputs: 2,
|
||||
)
|
||||
for i in range(2) {
|
||||
wire.stub("iec-or-port-in" + str(i), "west")
|
||||
}
|
||||
|
||||
element.iec-gate-nor(
|
||||
id: "iec-nor",
|
||||
x: 3,
|
||||
y: -6,
|
||||
w: 2,
|
||||
h: 2,
|
||||
inputs: 2,
|
||||
)
|
||||
for i in range(2) {
|
||||
wire.stub("iec-nor-port-in" + str(i), "west")
|
||||
}
|
||||
|
||||
element.iec-gate-xor(
|
||||
id: "iec-xor",
|
||||
x: 0,
|
||||
y: -9,
|
||||
w: 2,
|
||||
h: 2,
|
||||
inputs: 2,
|
||||
)
|
||||
for i in range(2) {
|
||||
wire.stub("iec-xor-port-in" + str(i), "west")
|
||||
}
|
||||
|
||||
element.iec-gate-xnor(
|
||||
id: "iec-nxor",
|
||||
x: 3,
|
||||
y: -9,
|
||||
w: 2,
|
||||
h: 2,
|
||||
inputs: 2,
|
||||
)
|
||||
for i in range(2) {
|
||||
wire.stub("iec-nxor-port-in" + str(i), "west")
|
||||
}
|
||||
})
|
||||
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,117 +0,0 @@
|
||||
// Demonstrates the wire.breakout element used to split off slices of a
|
||||
// bus that connects blocks. The main bus wires are always laid out
|
||||
// orthogonally; breakouts diagonally tap intermediate points on those
|
||||
// wires and route the sub-bus to another block via an L-shaped zigzag.
|
||||
#import "/src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
#circuit({
|
||||
// ---- Source block on the left, main sink on the right ----
|
||||
element.block(x: 0, y: 0, w: 2, h: 2, id: "src", fill: util.colors.blue, ports: (
|
||||
east: ((id: "out"),),
|
||||
))
|
||||
|
||||
element.block(x: 15, y: 0, w: 2, h: 2, id: "dstMain", fill: util.colors.green, ports: (
|
||||
west: ((id: "in"),),
|
||||
))
|
||||
|
||||
// Full bus from src to dstMain, labelled [15:0] at the start.
|
||||
wire.wire("wbus", ("src-port-out", "dstMain-port-in"), name: "[15:0]", name-pos: "start")
|
||||
|
||||
// ---- Upper-left sink: taps bits [11:8] via NW breakout ----
|
||||
element.block(x: 0, y: 4, w: 2, h: 2, id: "dstNW", fill: util.colors.yellow, ports: (
|
||||
east: ((id: "in"),),
|
||||
))
|
||||
|
||||
wire.breakout(
|
||||
"tapNE",
|
||||
x: (rel: 3, to: "src-port-out"),
|
||||
y: (from: "src-port-out"),
|
||||
direction: "north-east",
|
||||
port-side: "west",
|
||||
name: "[15:12]",
|
||||
name-pos: "north-east",
|
||||
)
|
||||
|
||||
// L-shape: up first, then left into dstNW.
|
||||
wire.wire(
|
||||
"wNW",
|
||||
("tapNE-port-tapNE", "dstNW-port-in"),
|
||||
style: "zigzag",
|
||||
zigzag-dir: "horizontal",
|
||||
zigzag-ratio: 100%,
|
||||
)
|
||||
|
||||
// ---- Upper-right sink: taps bits [15:12] via NE breakout ----
|
||||
element.block(x: 15, y: 4, w: 2, h: 2, id: "dstNE", fill: util.colors.orange, ports: (
|
||||
west: ((id: "in"),),
|
||||
))
|
||||
|
||||
wire.breakout(
|
||||
"tapNW",
|
||||
x: (rel: 7, to: "src-port-out"),
|
||||
y: (from: "src-port-out"),
|
||||
direction: "north-west",
|
||||
port-side: "east",
|
||||
name: "[11:8]",
|
||||
name-pos: "north-east",
|
||||
)
|
||||
|
||||
// L-shape: up first, then right into dstNE.
|
||||
wire.wire(
|
||||
"wNE",
|
||||
("tapNW-port-tapNW", "dstNE-port-in"),
|
||||
style: "zigzag",
|
||||
zigzag-dir: "horizontal",
|
||||
zigzag-ratio: 100%,
|
||||
)
|
||||
|
||||
// ---- Lower-left sink: taps bits [3:0] via SW breakout ----
|
||||
element.block(x: 0, y: -4, w: 2, h: 2, id: "dstSW", fill: util.colors.purple, ports: (
|
||||
east: ((id: "in"),),
|
||||
))
|
||||
|
||||
wire.breakout(
|
||||
"tapSE",
|
||||
x: (rel: 5, to: "src-port-out"),
|
||||
y: (from: "src-port-out"),
|
||||
direction: "south-east",
|
||||
port-side: "west",
|
||||
name: "[7:4]",
|
||||
name-pos: "south-east",
|
||||
)
|
||||
|
||||
// L-shape: down first, then left into dstSW.
|
||||
wire.wire(
|
||||
"wSW",
|
||||
("tapSE-port-tapSE", "dstSW-port-in"),
|
||||
style: "zigzag",
|
||||
zigzag-dir: "horizontal",
|
||||
zigzag-ratio: 100%,
|
||||
)
|
||||
|
||||
// ---- Lower-right sink: taps bits [7:4] via SE breakout ----
|
||||
element.block(x: 15, y: -4, w: 2, h: 2, id: "dstSE", fill: util.colors.pink, ports: (
|
||||
west: ((id: "in"),),
|
||||
))
|
||||
|
||||
wire.breakout(
|
||||
"tapSW",
|
||||
x: (rel: 11, to: "src-port-out"),
|
||||
y: (from: "src-port-out"),
|
||||
direction: "south-west",
|
||||
port-side: "east",
|
||||
name: "[3:0]",
|
||||
name-pos: "south-west",
|
||||
)
|
||||
|
||||
// L-shape: down first, then right into dstSE.
|
||||
wire.wire(
|
||||
"wSE",
|
||||
("tapSW-port-tapSW", "dstSE-port-in"),
|
||||
style: "zigzag",
|
||||
zigzag-dir: "horizontal",
|
||||
zigzag-ratio: 100%,
|
||||
)
|
||||
})
|
||||
|
Before Width: | Height: | Size: 23 KiB |
@@ -1,86 +0,0 @@
|
||||
// Demonstrates ports on a group element.
|
||||
// outside-port-names: true (default) — labels outside the group border
|
||||
// outside-port-names: false — labels inside (block-style)
|
||||
#import "/src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
#circuit({
|
||||
// --- Group with port labels INSIDE (outside-port-names: false) ---
|
||||
element.group(
|
||||
id: "cpu",
|
||||
name: "CPU (labels outside)",
|
||||
name-anchor: "north",
|
||||
stroke: (dash: "dashed"),
|
||||
fill: util.colors.yellow.lighten(60%),
|
||||
padding: 1em,
|
||||
port-name-padding: .5em,
|
||||
outside-port-names: true,
|
||||
ports: (
|
||||
west: (
|
||||
(id: "in1", name: "D1"),
|
||||
(id: "in2", name: "D2"),
|
||||
),
|
||||
east: ((id: "out", name: "Q"),),
|
||||
south: (
|
||||
(id: "rst", name: "RST", vertical: true),
|
||||
(id: "done", name: "DONE", vertical: true),
|
||||
),
|
||||
),
|
||||
{
|
||||
element.block(
|
||||
id: "reg2",
|
||||
w: 2.5,
|
||||
h: 3,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: util.colors.green,
|
||||
ports: (
|
||||
west: ((id: "d1"), (id: "d2")),
|
||||
east: ((id: "q"),),
|
||||
north: ((id: "clk", clock: true),),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
// --- Group with port labels INSIDE (outside-port-names: false) ---
|
||||
element.group(
|
||||
id: "cpu2",
|
||||
name: "CPU (labels inside)",
|
||||
name-anchor: "north",
|
||||
stroke: (dash: "dashed"),
|
||||
fill: util.colors.yellow.lighten(60%),
|
||||
padding: 4em,
|
||||
outside-port-names: false,
|
||||
port-name-padding: 0.5em,
|
||||
ports: (
|
||||
west: (
|
||||
(id: "in1", name: "D1"),
|
||||
(id: "in2", name: "D2"),
|
||||
),
|
||||
east: ((id: "out", name: "Q"),),
|
||||
south: (
|
||||
(id: "rst", name: "RST", vertical: true),
|
||||
(id: "done", name: "DONE", vertical: true),
|
||||
),
|
||||
),
|
||||
{
|
||||
element.block(
|
||||
id: "reg2",
|
||||
w: 2.5,
|
||||
h: 3,
|
||||
x: 10,
|
||||
y: 0,
|
||||
fill: util.colors.green,
|
||||
ports: (
|
||||
west: ((id: "d1"), (id: "d2")),
|
||||
east: ((id: "q"),),
|
||||
north: ((id: "clk", clock: true),),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
wire.stub("cpu2-port-in1", "west", name: "A")
|
||||
wire.stub("cpu2-port-in2", "west", name: "B")
|
||||
wire.stub("cpu2-port-out", "east", name: "Q")
|
||||
})
|
||||
|
Before Width: | Height: | Size: 39 KiB |
@@ -1,117 +0,0 @@
|
||||
// Demonstrates pentagon-shaped ports.
|
||||
//
|
||||
// - Element-level defaults: `port-shape`, `port-kind`, `port-size`.
|
||||
// - Per-port overrides: `shape`, `kind`, `size`, `stroke`, `fill`.
|
||||
// - Wire endpoints connect at the outer end of each pentagon.
|
||||
// - Clock ports with pentagon shape drop the clock triangle and default
|
||||
// to a gray fill.
|
||||
#import "/src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
#circuit({
|
||||
// -----------------------------------------------------------------
|
||||
// 1) All four sides × input/output
|
||||
// -----------------------------------------------------------------
|
||||
element.block(
|
||||
id: "grid", w: 4, h: 4, x: 0, y: 0,
|
||||
name: "sides × in/out", name-anchor: "center",
|
||||
fill: util.colors.yellow.lighten(60%),
|
||||
port-shape: "pentagon",
|
||||
ports: (
|
||||
west: (
|
||||
(id: "wi", name: "W-in"),
|
||||
(id: "wo", name: "W-out", kind: "out"),
|
||||
),
|
||||
east: (
|
||||
(id: "ei", name: "E-in"),
|
||||
(id: "eo", name: "E-out", kind: "out"),
|
||||
),
|
||||
north: (
|
||||
(id: "ni", name: "N-in", vertical: true),
|
||||
(id: "no", name: "N-out", kind: "out", vertical: true),
|
||||
),
|
||||
south: (
|
||||
(id: "si", name: "S-in", vertical: true),
|
||||
(id: "so", name: "S-out", kind: "out", vertical: true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 2) Per-port styling: fill and stroke overrides + size override
|
||||
// -----------------------------------------------------------------
|
||||
element.block(
|
||||
id: "styled", w: 3, h: 3, x: 10, y: 0,
|
||||
name: "per-port styling", name-anchor: "center",
|
||||
fill: util.colors.green.lighten(40%),
|
||||
port-shape: "pentagon",
|
||||
ports: (
|
||||
west: ((id: "a", name: "A", fill: util.colors.blue),),
|
||||
east: ((id: "b", name: "B", kind: "out",
|
||||
stroke: red + 1.5pt, fill: util.colors.pink),),
|
||||
north: ((id: "c", name: "C", size: 1.5em),),
|
||||
south: ((id: "d", name: "D", kind: "out",
|
||||
fill: util.colors.orange),),
|
||||
),
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 3) Clock port with pentagon → gray fill, no triangle
|
||||
// -----------------------------------------------------------------
|
||||
element.block(
|
||||
id: "clk", w: 3, h: 2.5, x: 16, y: 0,
|
||||
name: "clock + pentagon", name-anchor: "center",
|
||||
fill: util.colors.orange.lighten(60%),
|
||||
port-shape: "pentagon",
|
||||
ports: (
|
||||
west: ((id: "d", name: "D"),),
|
||||
east: ((id: "q", name: "Q", kind: "out"),),
|
||||
north: ((id: "clk", name: "CLK", clock: true),),
|
||||
),
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 4) Two pentagon-ported blocks connected by a wire
|
||||
// (wire terminates at outer edge of each pentagon)
|
||||
// -----------------------------------------------------------------
|
||||
element.block(
|
||||
id: "src", w: 2, h: 1.5, x: 0, y: -5,
|
||||
name: "src", name-anchor: "center",
|
||||
fill: util.colors.pink,
|
||||
port-shape: "pentagon",
|
||||
ports: (east: ((id: "q", name: "Q", kind: "out"),)),
|
||||
)
|
||||
element.block(
|
||||
id: "dst", w: 2, h: 1.5, x: 6, y: -5,
|
||||
name: "dst", name-anchor: "center",
|
||||
fill: util.colors.blue,
|
||||
port-shape: "pentagon",
|
||||
ports: (west: ((id: "d", name: "D"),)),
|
||||
)
|
||||
wire.wire("w-sd", ("src-port-q", "dst-port-d"))
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 5) Group with pentagon ports enclosing a child block
|
||||
// -----------------------------------------------------------------
|
||||
element.group(
|
||||
id: "g", name: "group w/ pentagon ports", name-anchor: "north",
|
||||
stroke: (dash: "dashed"),
|
||||
fill: util.colors.purple.lighten(70%),
|
||||
padding: 2em,
|
||||
port-shape: "pentagon",
|
||||
ports: (
|
||||
west: ((id: "in", name: "IN"),),
|
||||
east: ((id: "out", name: "OUT", kind: "out"),),
|
||||
),
|
||||
{
|
||||
element.block(
|
||||
id: "inner", w: 2, h: 1.5, x: 12, y: -5,
|
||||
fill: util.colors.green,
|
||||
ports: (west: ((id: "d"),), east: ((id: "q"),)),
|
||||
)
|
||||
},
|
||||
)
|
||||
wire.wire("wgi", ("g-port-in", "inner-port-d"))
|
||||
wire.wire("wgo", ("inner-port-q", "g-port-out"))
|
||||
})
|
||||
|
Before Width: | Height: | Size: 9.3 KiB |
@@ -1,53 +0,0 @@
|
||||
// Demonstrates the u-shape wire style.
|
||||
// The u-side is auto-detected from the port anchors when both endpoints
|
||||
// belong to elements (deduced from the port position vs the element's
|
||||
// center anchor). It can also be set explicitly with u-side.
|
||||
#import "/src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
#circuit({
|
||||
// ---- east <-> east (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.6,
|
||||
name: "E-E")
|
||||
|
||||
// ---- 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.6,
|
||||
name: "W-W")
|
||||
|
||||
// ---- 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.6,
|
||||
name: "N-N")
|
||||
|
||||
// ---- south <-> south (explicit u-side override) ----
|
||||
element.block(x: 16, y: 0, w: 1.5, h: 1.5, id: "sA",
|
||||
fill: util.colors.pink,
|
||||
ports: (south: ((id: "out"),)))
|
||||
element.block(x: 18.5, y: -0.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.6,
|
||||
name: "S-S")
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
// 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")
|
||||
})
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1,46 +0,0 @@
|
||||
// Demonstrates the `(rel: <number>, to: <anchor>)` shorthand for the `y`
|
||||
// parameter (analogous to the existing shorthand for `x`).
|
||||
#import "/src/lib.typ": circuit, element, util, wire
|
||||
|
||||
#set page(width: auto, height: auto, margin: .5cm)
|
||||
|
||||
#circuit({
|
||||
// Anchor block: absolute placement.
|
||||
element.block(
|
||||
x: 0, y: 0, w: 2, h: 1.5,
|
||||
id: "A",
|
||||
fill: util.colors.orange,
|
||||
ports: (east: ((id: "out"),))
|
||||
)
|
||||
|
||||
// X relative, Y absolute (existing feature).
|
||||
element.block(
|
||||
x: (rel: 1, to: "A.east"), y: 0, w: 2, h: 1.5,
|
||||
id: "B",
|
||||
fill: util.colors.yellow,
|
||||
ports: (west: ((id: "in"),), east: ((id: "out"),))
|
||||
)
|
||||
|
||||
// Both X and Y relative to another element's anchor (new feature).
|
||||
element.block(
|
||||
x: (rel: 1, to: "B.east"),
|
||||
y: (rel: 2, to: "B.south-west"),
|
||||
w: 2, h: 1.5,
|
||||
id: "C",
|
||||
fill: util.colors.green,
|
||||
ports: (west: ((id: "in"),))
|
||||
)
|
||||
|
||||
// Y relative only, X absolute.
|
||||
element.block(
|
||||
x: 0,
|
||||
y: (rel: -2.5, to: "A.south-west"),
|
||||
w: 2, h: 1.5,
|
||||
id: "D",
|
||||
fill: util.colors.blue,
|
||||
ports: (east: ((id: "out"),))
|
||||
)
|
||||
|
||||
wire.wire("wAB", ("A-port-out", "B-port-in"))
|
||||
wire.wire("wBC", ("B-port-out", "C-port-in"), style: "zigzag")
|
||||
})
|
||||
@@ -1,9 +1,10 @@
|
||||
#import "@preview/tidy:0.4.3"
|
||||
#import "/src/cetz.typ": draw, canvas
|
||||
#import "@preview/tidy:0.4.1"
|
||||
#import "@preview/cetz:0.3.2": draw, canvas
|
||||
#import "src/lib.typ"
|
||||
#import "doc/examples.typ"
|
||||
#import "src/circuit.typ": circuit
|
||||
#import "src/element.typ"
|
||||
#import "src/electrical.typ"
|
||||
#import "src/gates.typ"
|
||||
#import "src/util.typ"
|
||||
#import "src/wire.typ"
|
||||
@@ -105,7 +106,7 @@ This package provides a way to make beautiful block circuit diagrams using the C
|
||||
|
||||
Simply import Circuiteria and call the `circuit` function:
|
||||
#pad(left: 1em)[```typ
|
||||
#import "@preview/circuiteria:0.2.1"
|
||||
#import "@preview/circuiteria:0.2.0"
|
||||
#circuiteria.circuit({
|
||||
import circuiteria: *
|
||||
...
|
||||
@@ -166,24 +167,8 @@ If you have installed Circuiteria directly in your project, import #link("src/li
|
||||
|
||||
#pagebreak()
|
||||
|
||||
#let element-scope = (
|
||||
element: element,
|
||||
circuit: circuit,
|
||||
draw: draw,
|
||||
wire: wire,
|
||||
tidy: tidy,
|
||||
examples: examples,
|
||||
doc-ref: doc-ref
|
||||
)
|
||||
|
||||
#let elmt-docs = tidy.parse-module(
|
||||
read("src/elements/element.typ"),
|
||||
name: "element",
|
||||
old-syntax: true,
|
||||
scope: element-scope
|
||||
)
|
||||
|
||||
#let element-docs = tidy.parse-module(
|
||||
read("src/elements/element.typ") + "\n" +
|
||||
read("src/elements/alu.typ") + "\n" +
|
||||
read("src/elements/block.typ") + "\n" +
|
||||
read("src/elements/extender.typ") + "\n" +
|
||||
@@ -191,19 +176,18 @@ If you have installed Circuiteria directly in your project, import #link("src/li
|
||||
read("src/elements/group.typ"),
|
||||
name: "element",
|
||||
old-syntax: true,
|
||||
scope: element-scope
|
||||
scope: (
|
||||
element: element,
|
||||
circuit: circuit,
|
||||
draw: draw,
|
||||
wire: wire,
|
||||
tidy: tidy,
|
||||
examples: examples,
|
||||
doc-ref: doc-ref
|
||||
)
|
||||
)
|
||||
|
||||
#tidy.show-module(elmt-docs, sort-functions: false, show-module-name: true)
|
||||
|
||||
#figure(
|
||||
examples.align-with-ports,
|
||||
caption: [Example of the `align-with` port field (see #doc-ref("element.elmt")).],
|
||||
kind: "example",
|
||||
supplement: [Example],
|
||||
) <elmt-align-with-example>
|
||||
|
||||
#tidy.show-module(element-docs, sort-functions: false, show-module-name: false)
|
||||
#tidy.show-module(element-docs, sort-functions: false)
|
||||
|
||||
#pagebreak()
|
||||
|
||||
@@ -212,12 +196,7 @@ If you have installed Circuiteria directly in your project, import #link("src/li
|
||||
read("src/elements/logic/and.typ") + "\n" +
|
||||
read("src/elements/logic/buf.typ") + "\n" +
|
||||
read("src/elements/logic/or.typ") + "\n" +
|
||||
read("src/elements/logic/xor.typ") + "\n" +
|
||||
read("src/elements/logic/iec_gate.typ") + "\n" +
|
||||
read("src/elements/logic/iec_and.typ") + "\n" +
|
||||
read("src/elements/logic/iec_buf.typ") + "\n" +
|
||||
read("src/elements/logic/iec_or.typ") + "\n" +
|
||||
read("src/elements/logic/iec_xor.typ"),
|
||||
read("src/elements/logic/xor.typ"),
|
||||
name: "gates",
|
||||
old-syntax: true,
|
||||
scope: (
|
||||
@@ -232,4 +211,24 @@ If you have installed Circuiteria directly in your project, import #link("src/li
|
||||
)
|
||||
)
|
||||
|
||||
#tidy.show-module(gates-docs, sort-functions: false)
|
||||
#tidy.show-module(gates-docs, sort-functions: false)
|
||||
|
||||
#pagebreak()
|
||||
|
||||
#let electrical-docs = tidy.parse-module(
|
||||
read("src/elements/electrical/capacitor.typ") + "\n" +
|
||||
read("src/elements/electrical/resistor.typ") + "\n",
|
||||
name: "electrical",
|
||||
scope: (
|
||||
element: element,
|
||||
circuit: circuit,
|
||||
electrical: electrical,
|
||||
draw: draw,
|
||||
wire: wire,
|
||||
tidy: tidy,
|
||||
examples: examples,
|
||||
doc-ref: doc-ref
|
||||
)
|
||||
)
|
||||
|
||||
#tidy.show-module(electrical-docs, sort-functions: false)
|
||||
@@ -1 +0,0 @@
|
||||
#import "@preview/cetz:0.5.2": *
|
||||
@@ -1,5 +1,5 @@
|
||||
#import "/src/cetz.typ": canvas
|
||||
#import "@preview/tidy:0.4.3"
|
||||
#import "@preview/cetz:0.3.2": canvas
|
||||
#import "@preview/tidy:0.3.0"
|
||||
|
||||
/// Draws a block circuit diagram
|
||||
///
|
||||
@@ -9,5 +9,6 @@
|
||||
/// - length (length, ratio): Optional base unit
|
||||
/// -> none
|
||||
#let circuit(body, length: 2em) = {
|
||||
set text(font: "Source Sans 3")
|
||||
canvas(length: length, body)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#import "elements/electrical/capacitor.typ": capacitor
|
||||
#import "elements/electrical/resistor.typ": resistor
|
||||
@@ -11,10 +11,8 @@
|
||||
#import "elements/logic/or.typ": gate-or, gate-nor
|
||||
#import "elements/logic/xor.typ": gate-xor, gate-xnor
|
||||
#import "elements/logic/buf.typ": gate-buf, gate-not
|
||||
#import "elements/logic/iec_gate.typ": iec-gate
|
||||
#import "elements/logic/iec_and.typ": iec-gate-and, iec-gate-nand
|
||||
#import "elements/logic/iec_buf.typ": iec-gate-buf, iec-gate-not
|
||||
#import "elements/logic/iec_or.typ": iec-gate-or, iec-gate-nor
|
||||
#import "elements/logic/iec_xor.typ": iec-gate-xor, iec-gate-xnor
|
||||
|
||||
#import "elements/electrical/resistor.typ": resistor
|
||||
#import "elements/electrical/capacitor.typ": capacitor
|
||||
|
||||
#import "elements/group.typ": group
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "element.typ"
|
||||
#import "ports.typ": add-port
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "element.typ"
|
||||
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke, radius: 0.5em) = {
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
|
||||
let f = draw.rect(
|
||||
radius: radius,
|
||||
radius: 0.5em,
|
||||
inset: 0.5em,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
@@ -16,22 +16,8 @@
|
||||
/// Draws a block element
|
||||
///
|
||||
/// #examples.block
|
||||
///
|
||||
/// Ports can be given a pentagon shape (with configurable direction, size,
|
||||
/// stroke and fill) via `port-shape` and per-port overrides:
|
||||
/// #examples.port-shape
|
||||
///
|
||||
/// Port name labels can be placed outside the block via `port-names-outside`:
|
||||
/// #examples.port-names-outside
|
||||
/// For other parameters description, see #doc-ref("element.elmt")
|
||||
/// - radius (number, length, ratio, dictionary): The corner radius of the block. See CeTZ documentation for more information
|
||||
/// - 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, and wires attach to the inside tip of pentagon-shaped ports. See #doc-ref("element.elmt")
|
||||
///
|
||||
/// Use `shell: true` to wrap child elements inside a bordered container without
|
||||
/// having ports overlap the interior:
|
||||
/// #examples.shell
|
||||
/// For parameters description, see #doc-ref("element.elmt")
|
||||
#let block(
|
||||
..args,
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
@@ -40,22 +26,14 @@
|
||||
name-anchor: "center",
|
||||
ports: (),
|
||||
ports-margins: (),
|
||||
port-name-padding: 2pt,
|
||||
port-names-outside: auto,
|
||||
port-shape: none,
|
||||
port-kind: "in",
|
||||
port-size: 1em,
|
||||
fill: none,
|
||||
radius: 0.5em,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
shell: false,
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = element.elmt(
|
||||
draw-shape: draw-shape.with(radius: radius),
|
||||
body: if args.pos().len() > 0 { args.pos().first() } else { none },
|
||||
draw-shape: draw-shape,
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
@@ -64,14 +42,8 @@
|
||||
name-anchor: name-anchor,
|
||||
ports: ports,
|
||||
ports-margins: ports-margins,
|
||||
port-name-padding: port-name-padding,
|
||||
port-names-outside: port-names-outside,
|
||||
port-shape: port-shape,
|
||||
port-kind: port-kind,
|
||||
port-size: port-size,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
shell: shell,
|
||||
debug: debug
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,138 @@
|
||||
#import "@preview/cetz:0.2.2": draw
|
||||
#import "../element.typ"
|
||||
#import "../ports.typ": add-port
|
||||
|
||||
#let draw-shape(
|
||||
id, tl, tr, br, bl,
|
||||
fill, stroke,
|
||||
vertical: false,
|
||||
gap: 0.2,
|
||||
scales: (100%, 100%),
|
||||
symbols: (none, none)
|
||||
) = {
|
||||
let (x0, y0) = tl
|
||||
let (x1, y1) = br
|
||||
|
||||
let w = x1 - x0
|
||||
let h = y1 - y0
|
||||
|
||||
let (o0, s0) = if vertical {(y0, h)} else {(x0, w)}
|
||||
let (o1, s1) = if vertical {(x0, w)} else {(y0, h)}
|
||||
let m1 = o1 + s1 / 2
|
||||
|
||||
let pt(i, j) = if vertical {
|
||||
(j, i)
|
||||
} else {
|
||||
(i, j)
|
||||
}
|
||||
|
||||
let size0 = s1 * scales.first() / 100%
|
||||
let size1 = s1 * scales.last() / 100%
|
||||
|
||||
if type(gap) == ratio {
|
||||
gap = gap / 100%
|
||||
} else {
|
||||
gap = gap / calc.abs(s0)
|
||||
}
|
||||
let r0 = 0.5 - gap / 2
|
||||
let r1 = 0.5 + gap / 2
|
||||
|
||||
// Coordinates in (main axis, secondary axis) format
|
||||
let p0 = pt(o0, m1)
|
||||
let p1 = pt(o0 + r0 * s0, m1)
|
||||
let p2 = pt(o0 + r1 * s0, m1)
|
||||
let p3 = pt(o0 + s0, m1)
|
||||
|
||||
let p4 = pt(o0 + r0 * s0, m1 - size0 / 2)
|
||||
let p5 = pt(o0 + r0 * s0, m1 + size0 / 2)
|
||||
|
||||
let p6 = pt(o0 + r1 * s0, m1 - size1 / 2)
|
||||
let p7 = pt(o0 + r1 * s0, m1 + size1 / 2)
|
||||
|
||||
let line = draw.line.with(stroke: stroke)
|
||||
let f = draw.group(name: id, {
|
||||
line(p0, p1)
|
||||
line(p2, p3)
|
||||
line(p4, p5)
|
||||
line(p6, p7)
|
||||
|
||||
if symbols.first() != none {
|
||||
draw.content(
|
||||
p1,
|
||||
symbols.first(),
|
||||
anchor: if vertical {"south-west"} else {"south-east"},
|
||||
padding: 2pt
|
||||
)
|
||||
}
|
||||
|
||||
if symbols.last() != none {
|
||||
draw.content(
|
||||
p2,
|
||||
symbols.last(),
|
||||
anchor: if vertical {"north-west"} else {"south-west"},
|
||||
padding: 2pt
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return (f, tl, tr, br, bl)
|
||||
}
|
||||
|
||||
/// Draws a capacitor
|
||||
///
|
||||
/// #examples.capacitor
|
||||
/// For other parameters description, see #doc-ref("element.elmt")
|
||||
/// - vertical (bool): Whether the element is vertical or horizontal.
|
||||
/// - If false, port 0 is placed on the west side and port 1 on the east.\
|
||||
/// - If true, they are on the north, respectively the south sides
|
||||
/// - gap (number, ratio): The gap between both sides
|
||||
/// - if it is a number (int or float), it is interpreted as an absolute canvas-unit length
|
||||
/// - if it is a ratio, it is interpreted as proportional to the capacitor's length (width if horizontal, height if vertical)
|
||||
/// - scales (array): A pair of ratios, the sizes of the sides relative to the capacitor's height (width if vertical).
|
||||
/// - symbols (array): A pair of content or strings (or none values) to attach on the sides of the capacitor
|
||||
#let capacitor(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
name: none,
|
||||
name-anchor: "center",
|
||||
vertical: false,
|
||||
gap: 0.2,
|
||||
scales: (100%, 100%),
|
||||
symbols: (none, none),
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = {
|
||||
let ports = if vertical {(
|
||||
north: ((id: "0"),),
|
||||
south: ((id: "1"),)
|
||||
)} else {(
|
||||
west: ((id: "0"),),
|
||||
east: ((id: "1"),)
|
||||
)}
|
||||
|
||||
element.elmt(
|
||||
draw-shape: draw-shape.with(
|
||||
vertical: vertical,
|
||||
gap: gap,
|
||||
scales: scales,
|
||||
symbols: symbols
|
||||
),
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
name: name,
|
||||
name-anchor: name-anchor,
|
||||
ports: ports,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
debug: debug
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#import "@preview/cetz:0.2.2": draw
|
||||
#import "../element.typ"
|
||||
#import "../ports.typ": add-port
|
||||
|
||||
#let draw-shape(
|
||||
id, tl, tr, br, bl,
|
||||
fill, stroke,
|
||||
zigzags: 6,
|
||||
vertical: false
|
||||
) = {
|
||||
let (x0, y0) = tl
|
||||
let (x1, y1) = br
|
||||
|
||||
let w = x1 - x0
|
||||
let h = y1 - y0
|
||||
|
||||
let (o0, s0) = if vertical {(y0, h)} else {(x0, w)}
|
||||
let (o1, s1) = if vertical {(x0, w)} else {(y0, h)}
|
||||
let m1 = o1 + s1 / 2
|
||||
|
||||
let pt(i, j) = if vertical {
|
||||
(j, i)
|
||||
} else {
|
||||
(i, j)
|
||||
}
|
||||
|
||||
let p0 = pt(o0, m1)
|
||||
let p1 = pt(o0 + 0.2 * s0, m1)
|
||||
let p2 = pt(o0 + 0.8 * s0, m1)
|
||||
let p3 = pt(o0 + s0, m1)
|
||||
|
||||
if zigzags == none {
|
||||
let p4 = pt(o0 + 0.2 * s0, o1)
|
||||
let p5 = pt(o0 + 0.8 * s0, o1 + s1)
|
||||
let f = draw.group(name: id, {
|
||||
draw.line(p0, p1)
|
||||
draw.line(p2, p3)
|
||||
draw.rect(p4, p5, stroke: stroke, fill: fill)
|
||||
})
|
||||
return (f, tl, tr, br, bl)
|
||||
}
|
||||
|
||||
let pts = (p0, p1)
|
||||
|
||||
for i in range(zigzags) {
|
||||
let r = ((i+0.5) / zigzags * 0.6 + 0.2)
|
||||
let pos = pt(o0 + r * s0, o1 + s1 * calc.rem(i, 2))
|
||||
pts.push(pos)
|
||||
}
|
||||
|
||||
pts += (p2, p3)
|
||||
|
||||
let f = draw.group(name: id, {
|
||||
draw.line(..pts, stroke: stroke)
|
||||
})
|
||||
return (f, tl, tr, br, bl)
|
||||
}
|
||||
|
||||
/// Draws a resistor
|
||||
///
|
||||
/// #examples.resistor
|
||||
/// For other parameters description, see #doc-ref("element.elmt")
|
||||
/// - vertical (bool): Whether the element is vertical or horizontal. If false, port 0 is placed on the west side and port 1 on the east. If true, they are on the north, respectively the south sides
|
||||
/// - zigzags (number, none): Number of zigzags to draw. If none, a rectangle is drawn
|
||||
#let resistor(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
name: none,
|
||||
name-anchor: "center",
|
||||
vertical: false,
|
||||
zigzags: 6,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = {
|
||||
let ports = if vertical {(
|
||||
north: ((id: "0"),),
|
||||
south: ((id: "1"),)
|
||||
)} else {(
|
||||
west: ((id: "0"),),
|
||||
east: ((id: "1"),)
|
||||
)}
|
||||
|
||||
element.elmt(
|
||||
draw-shape: draw-shape.with(
|
||||
vertical: vertical,
|
||||
zigzags: zigzags
|
||||
),
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
name: name,
|
||||
name-anchor: name-anchor,
|
||||
ports: ports,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
debug: debug
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#import "/src/cetz.typ": coordinate, draw
|
||||
#import "ports.typ": add-port, add-ports
|
||||
#import "@preview/cetz:0.3.2": draw, coordinate
|
||||
#import "ports.typ": add-ports, add-port
|
||||
#import "../util.typ"
|
||||
|
||||
#let find-port(ports, id) = {
|
||||
@@ -21,14 +21,10 @@
|
||||
/// - draw-shape (function): Draw function
|
||||
/// - x (number, dictionary): The x position (bottom-left corner).
|
||||
///
|
||||
/// 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`)
|
||||
/// 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
|
||||
/// - y (number, dictionary): The y position (bottom-left corner).
|
||||
///
|
||||
/// If it is a dictionary, it can take one of two forms:
|
||||
/// - `(rel: number, to: str)`: `rel` is the y offset and `to` the base anchor (analogous to `x`)
|
||||
/// - `(from: str, to: str)`: `from` is the base anchor and `to` is the id of the port to align with the anchor
|
||||
/// If it is a dictionary, it should be in the format `(from: str, to: str)`, where `from` is the base anchor and `to` is the id of the port to align with the anchor
|
||||
/// - w (number): Width of the element
|
||||
/// - h (number): Height of the element
|
||||
/// - name (none, str): Optional name of the block
|
||||
@@ -38,27 +34,12 @@
|
||||
/// - `name` (`str`): Optional name displayed *in* the block
|
||||
/// - `clock` (`bool`): Whether it is a clock port (triangle symbol)
|
||||
/// - `vertical` (`bool`): Whether the name should be drawn vertically
|
||||
/// - `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 @elmt-align-with-example.
|
||||
/// - `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 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.
|
||||
///
|
||||
/// Supported fields include:
|
||||
@@ -73,22 +54,14 @@
|
||||
name-anchor: "center",
|
||||
ports: (:),
|
||||
ports-margins: (:),
|
||||
port-name-padding: 2pt,
|
||||
port-names-outside: auto,
|
||||
port-shape: none,
|
||||
port-kind: "in",
|
||||
port-size: 1em,
|
||||
fill: none,
|
||||
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
|
||||
@@ -100,45 +73,14 @@
|
||||
if w == none { panic("Parameter w must be set") }
|
||||
if h == none { panic("Parameter h must be set") }
|
||||
|
||||
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
|
||||
if (type(x) == dictionary) {
|
||||
let offset = x.rel
|
||||
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
|
||||
let (ctx, to-pos) = coordinate.resolve(ctx, (rel: (offset, 0), to: to))
|
||||
x = to-pos.at(0)
|
||||
}
|
||||
|
||||
if (type(y) == dictionary and "rel" in y) {
|
||||
y = util.resolve-rel-coord(ctx, y, 1)
|
||||
} else if (type(y) == dictionary) {
|
||||
|
||||
if (type(y) == dictionary) {
|
||||
let from = y.from
|
||||
let to = y.to
|
||||
let (to-side, i) = find-port(ports, to)
|
||||
@@ -153,7 +95,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 {
|
||||
@@ -167,7 +109,7 @@
|
||||
dy = height
|
||||
top-margin = 0
|
||||
}
|
||||
|
||||
|
||||
let (ctx, from-pos) = coordinate.resolve(ctx, from)
|
||||
y = from-pos.at(1) + dy - height + top-margin
|
||||
}
|
||||
@@ -185,37 +127,19 @@
|
||||
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*]
|
||||
)
|
||||
}
|
||||
|
||||
if auto-ports {
|
||||
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,
|
||||
height: height,
|
||||
debug: debug.ports,
|
||||
name-padding: port-name-padding,
|
||||
outside-name: outside-name,
|
||||
port-shape: port-shape,
|
||||
port-kind: port-kind,
|
||||
port-size: port-size,
|
||||
elmt-stroke: stroke,
|
||||
elmt-fill: fill,
|
||||
wire-inside: shell,
|
||||
debug: debug.ports
|
||||
)
|
||||
}
|
||||
|
||||
if body != none {
|
||||
body
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "element.typ"
|
||||
#import "ports.typ": add-port
|
||||
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "@preview/cetz:0.3.2": draw, coordinate
|
||||
#import "../util.typ"
|
||||
#import "ports.typ": add-ports
|
||||
|
||||
/// Draws a group of elements
|
||||
///
|
||||
/// #examples.group
|
||||
///
|
||||
/// Ports can also be defined on a group, just like on a block. By default
|
||||
/// port labels are drawn *outside* the group border so they do not overlap
|
||||
/// child elements. Set `outside-port-names: false` to draw them inside.
|
||||
/// #examples.group-ports
|
||||
/// - body (elements, function): Elements to group
|
||||
/// - id (str): see #doc-ref("element.elmt")
|
||||
/// - name (str): The group's name
|
||||
@@ -22,14 +16,6 @@
|
||||
/// - array: either (`<all>`,), (`<vertical>`, `<horizontal>`) or (`<top>`, `<right>`, `<bottom>`, `<left>`)
|
||||
/// - dictionary: valid keys are "top", "right", "bottom" and "left"
|
||||
/// - radius (number): The corner radius
|
||||
/// - ports (dictionary): see #doc-ref("element.elmt")
|
||||
/// - ports-margins (dictionary): see #doc-ref("element.elmt")
|
||||
/// - outside-port-names (bool): If `true` (default), port name labels are drawn outside the group border so they do not overlap child elements. Set to `false` to draw labels inside, matching the block behaviour
|
||||
/// - port-name-padding (number, length): Padding between the port name label and the group border (default `2pt`). Accepts anything CeTZ accepts as `padding` (number or length)
|
||||
/// - port-shape (none, str): see #doc-ref("element.elmt")
|
||||
/// - port-kind (str): see #doc-ref("element.elmt")
|
||||
/// - port-size (length): see #doc-ref("element.elmt")
|
||||
/// - debug (dictionary): see #doc-ref("element.elmt")
|
||||
#let group(
|
||||
body,
|
||||
id: "",
|
||||
@@ -38,17 +24,7 @@
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
padding: 0.5em,
|
||||
radius: 0.5em,
|
||||
ports: (:),
|
||||
ports-margins: (:),
|
||||
outside-port-names: true,
|
||||
port-name-padding: 2pt,
|
||||
port-shape: none,
|
||||
port-kind: "in",
|
||||
port-size: 1em,
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
radius: 0.5em
|
||||
) = {
|
||||
let min-x = none
|
||||
let max-x = none
|
||||
@@ -92,25 +68,6 @@
|
||||
stroke: stroke,
|
||||
fill: fill
|
||||
)
|
||||
|
||||
add-ports(
|
||||
id,
|
||||
id + ".north-west",
|
||||
id + ".north-east",
|
||||
id + ".south-east",
|
||||
id + ".south-west",
|
||||
ports,
|
||||
ports-margins,
|
||||
debug: debug.ports,
|
||||
outside-name: outside-port-names,
|
||||
name-padding: port-name-padding,
|
||||
port-shape: port-shape,
|
||||
port-kind: port-kind,
|
||||
port-size: port-size,
|
||||
elmt-stroke: stroke,
|
||||
elmt-fill: fill,
|
||||
)
|
||||
|
||||
if name != none {
|
||||
draw.content(
|
||||
id + "." + name-anchor,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "gate.typ"
|
||||
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "gate.typ"
|
||||
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "/src/util.typ"
|
||||
#import "@preview/cetz:0.3.2": draw, coordinate
|
||||
#import "../ports.typ": add-ports, add-port
|
||||
#import "../element.typ"
|
||||
|
||||
@@ -50,12 +49,13 @@
|
||||
if h == none { panic("Parameter h must be set") }
|
||||
|
||||
if (type(x) == dictionary) {
|
||||
x = util.resolve-rel-coord(ctx, x, 0)
|
||||
let offset = x.rel
|
||||
let to = x.to
|
||||
let (ctx, to-pos) = coordinate.resolve(ctx, (rel: (offset, 0), to: to))
|
||||
x = to-pos.at(0)
|
||||
}
|
||||
|
||||
if (type(y) == dictionary and "rel" in y) {
|
||||
y = util.resolve-rel-coord(ctx, y, 1)
|
||||
} else if (type(y) == dictionary) {
|
||||
if (type(y) == dictionary) {
|
||||
let from = y.from
|
||||
let to = y.to
|
||||
|
||||
@@ -63,12 +63,6 @@
|
||||
if to == "out" {
|
||||
dy = height / 2
|
||||
} else {
|
||||
let all-ports = range(inputs).map(i => "in" + str(i))
|
||||
let i = all-ports.position(p => p == to)
|
||||
assert(
|
||||
i != none,
|
||||
message: "Unknown port '" + y.to + "' must be one of " + repr(all-ports)
|
||||
)
|
||||
dy = height * (i + 0.5) / inputs
|
||||
}
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
// #import "iec_gate.typ" as iec-gate
|
||||
#import "iec_gate.typ" as iec-gate
|
||||
|
||||
|
||||
/// Draws an IEC-AND gate. This function is also available as `element.iec-gate-and()`
|
||||
///
|
||||
/// For parameters, see #doc-ref("gates.iec-gate")
|
||||
/// #examples.iec-gate-and
|
||||
#let iec-gate-and(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
inputs: 2,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
inverted: (),
|
||||
debug: (
|
||||
ports: false
|
||||
),
|
||||
) = {
|
||||
iec-gate.iec-gate(
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
inputs: inputs,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
inverted: inverted,
|
||||
debug: debug,
|
||||
symbol: $amp$,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
/// Draws an IEC-NAND gate. This function is also available as `element.iec-gate-nand()`
|
||||
///
|
||||
/// For parameters, see #doc-ref("gates.iec-gate")
|
||||
/// #examples.iec-gate-nand
|
||||
#let iec-gate-nand(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
inputs: 2,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
inverted: (),
|
||||
debug: (
|
||||
ports: false
|
||||
),
|
||||
) = {
|
||||
iec-gate-and(
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
inputs: inputs,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
inverted: if inverted != "all" {inverted + ("out",)} else {inverted},
|
||||
debug: debug,
|
||||
)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "iec_gate.typ" as iec-gate
|
||||
|
||||
|
||||
/// Draws an IEC buffer gate. This function is also available as `element.iec-gate-buf()`
|
||||
///
|
||||
/// For parameters, see #doc-ref("gates.iec-gate")
|
||||
/// #examples.iec-gate-buf
|
||||
#let iec-gate-buf(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
inputs: 2,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
inverted: (),
|
||||
debug: (
|
||||
ports: false,
|
||||
),
|
||||
) = {
|
||||
iec-gate.iec-gate(
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
inputs: inputs,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
inverted: inverted,
|
||||
debug: debug,
|
||||
symbol: "1",
|
||||
)
|
||||
}
|
||||
|
||||
/// Draws an IEC NOT gate. This function is also available as `element.iec-gate-not()`
|
||||
///
|
||||
/// For parameters, see #doc-ref("gates.iec-gate")
|
||||
/// #examples.iec-gate-not
|
||||
#let iec-gate-not(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
inputs: 2,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
inverted: (),
|
||||
debug: (
|
||||
ports: false,
|
||||
),
|
||||
) = {
|
||||
iec-gate-buf(
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
inputs: inputs,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
inverted: if inverted != "all" { inverted + ("out",) } else { inverted },
|
||||
debug: debug,
|
||||
)
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "/src/util.typ"
|
||||
#import "../ports.typ": add-ports, add-port
|
||||
#import "../element.typ"
|
||||
|
||||
#let default-draw-shape(id, tl, tr, br, bl, fill, stroke, symbol) = {
|
||||
let shapes = draw.rect(
|
||||
inset: 0.5em,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
name: id,
|
||||
bl, tr
|
||||
)
|
||||
shapes += draw.content(
|
||||
id + ".center",
|
||||
[*$ symbol $*]
|
||||
)
|
||||
return (shapes, tl, tr, br, bl)
|
||||
}
|
||||
|
||||
/// Draws a logic gate. This function is also available as `element.iec-gate()`
|
||||
///
|
||||
/// - draw-shape (function): see #doc-ref("element.elmt")
|
||||
/// - x (number, dictionary): see #doc-ref("element.elmt")
|
||||
/// - y (number, dictionary): see #doc-ref("element.elmt")
|
||||
/// - w (number): see #doc-ref("element.elmt")
|
||||
/// - h (number): see #doc-ref("element.elmt")
|
||||
/// - inputs (int): The number of inputs
|
||||
/// - fill (none, color): see #doc-ref("element.elmt")
|
||||
/// - stroke (stroke): see #doc-ref("element.elmt")
|
||||
/// - id (str): see #doc-ref("element.elmt")
|
||||
/// - inverted (str, array): Either "all" or an array of port ids to display as inverted
|
||||
/// - inverted-radius (number): The radius of inverted ports dot
|
||||
/// - debug (dictionary): see #doc-ref("element.elmt")
|
||||
/// - symbol (str): The symbol to display at the center of the gate
|
||||
#let iec-gate(
|
||||
draw-shape: default-draw-shape,
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
inputs: 2,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
inverted: (),
|
||||
inverted-radius: 0.1,
|
||||
debug: (
|
||||
ports: false
|
||||
),
|
||||
symbol: "",
|
||||
) = draw.get-ctx(ctx => {
|
||||
let width = w
|
||||
let height = h
|
||||
|
||||
let x = x
|
||||
let y = y
|
||||
if x == none { panic("Parameter x must be set") }
|
||||
if y == none { panic("Parameter y must be set") }
|
||||
if w == none { panic("Parameter w must be set") }
|
||||
if h == none { panic("Parameter h must be set") }
|
||||
|
||||
if (type(x) == dictionary) {
|
||||
x = util.resolve-rel-coord(ctx, x, 0)
|
||||
}
|
||||
|
||||
if (type(y) == dictionary and "rel" in y) {
|
||||
y = util.resolve-rel-coord(ctx, y, 1)
|
||||
} else if (type(y) == dictionary) {
|
||||
let from = y.from
|
||||
let to = y.to
|
||||
|
||||
let dy
|
||||
if to == "out" {
|
||||
dy = height / 2
|
||||
} else {
|
||||
dy = height * (i + 0.5) / inputs
|
||||
}
|
||||
|
||||
let (ctx, from-pos) = coordinate.resolve(ctx, from)
|
||||
y = from-pos.at(1) + dy - height
|
||||
}
|
||||
|
||||
let tl = (x, y + height)
|
||||
let tr = (x + width, y + height)
|
||||
let br = (x + width, y)
|
||||
let bl = (x, y)
|
||||
|
||||
// Workaround because CeTZ needs to have all draw functions in the body
|
||||
let func = {}
|
||||
(func, tl, tr, br, bl) = draw-shape(id, tl, tr, br, bl, fill, stroke, symbol)
|
||||
func
|
||||
|
||||
let space = 100% / inputs
|
||||
for i in range(inputs) {
|
||||
let pct = (i + 0.5) * space
|
||||
let port-pos = (tl, pct, bl)
|
||||
let port-name = "in" + str(i)
|
||||
if inverted == "all" or port-name in inverted {
|
||||
draw.circle(
|
||||
port-pos,
|
||||
radius: inverted-radius,
|
||||
anchor: "east",
|
||||
stroke: stroke
|
||||
)
|
||||
port-pos = (rel: (-2 * inverted-radius, 0), to: port-pos)
|
||||
}
|
||||
add-port(
|
||||
id, "west",
|
||||
(id: port-name), port-pos,
|
||||
debug: debug.ports
|
||||
)
|
||||
}
|
||||
|
||||
let out-pos = id + ".east"
|
||||
if inverted == "all" or "out" in inverted {
|
||||
draw.circle(out-pos, radius: inverted-radius, anchor: "west", stroke: stroke)
|
||||
out-pos = (rel: (2 * inverted-radius, 0), to: out-pos)
|
||||
}
|
||||
add-port(
|
||||
id, "east",
|
||||
(id: "out"), out-pos,
|
||||
debug: debug.ports
|
||||
)
|
||||
})
|
||||
@@ -1,67 +0,0 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "iec_gate.typ" as iec-gate
|
||||
|
||||
/// Draws an IEC-OR gate. This function is also available as `element.iec-gate-or()`
|
||||
///
|
||||
/// For parameters, see #doc-ref("gates.iec-gate")
|
||||
/// #examples.iec-gate-or
|
||||
#let iec-gate-or(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
inputs: 2,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
inverted: (),
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = {
|
||||
iec-gate.iec-gate(
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
inputs: inputs,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
inverted: inverted,
|
||||
debug: debug,
|
||||
symbol: $>= 1$,
|
||||
)
|
||||
}
|
||||
|
||||
/// Draws an IEC-NOR gate. This function is also available as `element.iec-gate-nor()`
|
||||
///
|
||||
/// For parameters, see #doc-ref("gates.iec-gate")
|
||||
/// #examples.iec-gate-nor
|
||||
#let iec-gate-nor(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
inputs: 2,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
inverted: (),
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = {
|
||||
iec-gate-or(
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
inputs: inputs,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
inverted: if inverted != "all" {inverted + ("out",)} else {inverted},
|
||||
debug: debug
|
||||
)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "iec_gate.typ" as iec-gate
|
||||
|
||||
/// Draws an IEC-XOR gate. This function is also available as `element.iec-gate-xor()`
|
||||
///
|
||||
/// For parameters, see #doc-ref("gates.iec-gate")
|
||||
/// #examples.iec-gate-xor
|
||||
#let iec-gate-xor(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
inputs: 2,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
inverted: (),
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = {
|
||||
iec-gate.iec-gate(
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
inputs: inputs,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
inverted: inverted,
|
||||
debug: debug,
|
||||
symbol: $= 1$,
|
||||
)
|
||||
}
|
||||
|
||||
/// Draws an IEC-XNOR gate. This function is also available as `element.iec-gate-xnor()`
|
||||
///
|
||||
/// For parameters, see #doc-ref("gates.iec-gate")
|
||||
/// #examples.iec-gate-xnor
|
||||
#let iec-gate-xnor(
|
||||
x: none,
|
||||
y: none,
|
||||
w: none,
|
||||
h: none,
|
||||
inputs: 2,
|
||||
fill: none,
|
||||
stroke: black + 1pt,
|
||||
id: "",
|
||||
inverted: (),
|
||||
debug: (
|
||||
ports: false
|
||||
)
|
||||
) = {
|
||||
iec-gate-xor(
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
inputs: inputs,
|
||||
fill: fill,
|
||||
stroke: stroke,
|
||||
id: id,
|
||||
inverted: if inverted != "all" {inverted + ("out",)} else {inverted},
|
||||
debug: debug
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "gate.typ"
|
||||
|
||||
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "gate.typ"
|
||||
|
||||
#let space = 10%
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "../util.typ"
|
||||
#import "element.typ"
|
||||
#import "ports.typ": add-port
|
||||
|
||||
@@ -1,446 +1,116 @@
|
||||
#import "/src/cetz.typ": draw
|
||||
#import "../util.typ": opposite-anchor, port-shape-vertices, rotate-anchor
|
||||
#import "@preview/cetz:0.3.2": draw
|
||||
#import "../util.typ": rotate-anchor
|
||||
|
||||
#let add-port(
|
||||
elmt-id,
|
||||
side,
|
||||
port,
|
||||
pos,
|
||||
elmt-id, side, port, pos,
|
||||
prev: none,
|
||||
next: none,
|
||||
debug: false,
|
||||
outside-name: false,
|
||||
name-padding: 2pt,
|
||||
port-shape: none,
|
||||
port-kind: "in",
|
||||
port-size: 1em,
|
||||
elmt-stroke: black + 1pt,
|
||||
elmt-fill: none,
|
||||
wire-inside: false,
|
||||
debug: false
|
||||
) = {
|
||||
let name = port.at("name", default: "")
|
||||
let name-rotate = port.at("vertical", default: false)
|
||||
let is-clock = port.at("clock", default: false)
|
||||
|
||||
// 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 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
|
||||
})
|
||||
|
||||
// When shape is enabled, wire-connection anchor moves outside the block, and
|
||||
// inside-name labels reference the inner tip/base; otherwise both collapse to
|
||||
// the boundary point (unchanged behaviour).
|
||||
let outer-pos = pos
|
||||
let inner-pos = pos
|
||||
|
||||
if shape == "pentagon" {
|
||||
let sh = port-shape-vertices(side, kind, size)
|
||||
let v = sh.vertices.map(off => (rel: off, to: pos))
|
||||
// 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),
|
||||
close: true,
|
||||
stroke: shape-stroke,
|
||||
fill: shape-fill,
|
||||
))
|
||||
outer-pos = (rel: sh.outer, to: pos)
|
||||
inner-pos = (rel: sh.inner, to: pos)
|
||||
} else if is-clock {
|
||||
if (port.at("clock", default: false)) {
|
||||
if prev == none or next == none {
|
||||
panic("Clock port must have previous and next positions")
|
||||
}
|
||||
|
||||
let csize = if port.at("small", default: false) { 8pt } else { 1em }
|
||||
|
||||
let size = 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, -size) }
|
||||
else if (side == "east") { offset = (-size, 0) }
|
||||
else if (side == "south") { offset = ( 0, size) }
|
||||
else if (side == "west") { offset = ( size, 0) }
|
||||
|
||||
let pos1 = (rel: offset, to: pos)
|
||||
|
||||
// TODO: use context or vectors to have the height relative to the width
|
||||
draw.line(prev, pos1, next)
|
||||
}
|
||||
|
||||
// When outside-name is true (e.g. for groups) the label is placed on the
|
||||
// exterior of the element border so it does not overlap child elements.
|
||||
// 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) }
|
||||
} else {
|
||||
if name-rotate { rotate-anchor(side) } else { side }
|
||||
}
|
||||
|
||||
// Label sits at the outer edge of the shape for outside-name, inner edge for
|
||||
// inside-name. `name-padding` is thus naturally measured from the shape edge
|
||||
// (or from the boundary when shape is disabled).
|
||||
let label-pos = if outside-name { outer-pos } else { inner-pos }
|
||||
|
||||
// For "shell" containers, wires must attach at the inside of the pentagon
|
||||
// (the tip that points inward), not at the exterior base.
|
||||
let anchor-pos = if wire-inside and shape == "pentagon" { inner-pos } else { outer-pos }
|
||||
|
||||
draw.content(
|
||||
label-pos,
|
||||
anchor: name-anchor,
|
||||
padding: name-padding,
|
||||
angle: if name-rotate { 90deg } else { 0deg },
|
||||
name,
|
||||
pos,
|
||||
anchor: if name-rotate {rotate-anchor(side)} else {side},
|
||||
padding: 2pt,
|
||||
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,
|
||||
pos,
|
||||
name: id,
|
||||
radius: .1,
|
||||
stroke: none,
|
||||
fill: red,
|
||||
fill: red
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize an `align-with` value to either:
|
||||
// - `(local: <id>)` → reference to another port of the *same* element
|
||||
// - `(external: <anchor-name>)` → CeTZ anchor name of a port in another element
|
||||
// Accepted input forms:
|
||||
// - `str` matching a local port id → local
|
||||
// - `str` not matching any local port id → external, used as-is as CeTZ anchor
|
||||
// - `(element: <str>, port: <str>)` → external, anchor = `<element>-port-<port>`
|
||||
#let classify-align-with(raw-val, placements) = {
|
||||
if type(raw-val) == dictionary {
|
||||
if "element" in raw-val and "port" in raw-val {
|
||||
return (external: raw-val.element + "-port-" + raw-val.port)
|
||||
}
|
||||
panic("`align-with` dictionary must contain `element` and `port` keys, got: " + repr(raw-val))
|
||||
}
|
||||
if type(raw-val) != str {
|
||||
panic("`align-with` must be a string or `(element:, port:)` dictionary, got: " + repr(raw-val))
|
||||
}
|
||||
if raw-val in placements {
|
||||
return (local: raw-val)
|
||||
}
|
||||
return (external: raw-val)
|
||||
}
|
||||
|
||||
// Resolve a single port's placement, following `align-with` references
|
||||
// transitively and detecting cycles.
|
||||
//
|
||||
// Returns `(placements, result)` where `result` is either:
|
||||
// - `(pct: <ratio>)` → percentage along the side (local placement)
|
||||
// - `(external: <anchor-name>)` → align with the given remote CeTZ anchor
|
||||
#let resolve-placement(placements, id, visiting, elmt-id: "") = {
|
||||
let where = if elmt-id != "" { " (element '" + elmt-id + "')" } else { "" }
|
||||
let p = placements.at(id)
|
||||
if p.at("resolved", default: false) {
|
||||
if "external-ref" in p {
|
||||
return (placements, (external: p.external-ref))
|
||||
}
|
||||
return (placements, (pct: p.pct))
|
||||
}
|
||||
if id in visiting {
|
||||
panic(
|
||||
"align-with cycle involving port '"
|
||||
+ id
|
||||
+ "'"
|
||||
+ where
|
||||
+ " (path: "
|
||||
+ visiting.join(" -> ")
|
||||
+ " -> "
|
||||
+ id
|
||||
+ ")",
|
||||
)
|
||||
}
|
||||
|
||||
let raw = p.raw
|
||||
let side = p.side
|
||||
let is-ew = side in ("east", "west")
|
||||
|
||||
let has-align = "align-with" in raw
|
||||
let has-x = "x" in raw
|
||||
let has-y = "y" in raw
|
||||
let has-num = has-x or has-y
|
||||
|
||||
if has-align and has-num {
|
||||
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`",
|
||||
)
|
||||
}
|
||||
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`",
|
||||
)
|
||||
}
|
||||
|
||||
let result = none
|
||||
if has-num {
|
||||
let val = if is-ew { raw.y } else { raw.x }
|
||||
let dim = if is-ew { p.height } else { p.width }
|
||||
if type(val) == function {
|
||||
val = val(dim)
|
||||
}
|
||||
let pct
|
||||
if type(val) == ratio {
|
||||
pct = val
|
||||
} else {
|
||||
// Numeric length: measured from `pt0` along the side.
|
||||
pct = (val / dim) * 100%
|
||||
}
|
||||
result = (pct: pct)
|
||||
} else if has-align {
|
||||
let classified = classify-align-with(raw.at("align-with"), placements)
|
||||
if "external" in classified {
|
||||
// Cross-element / raw anchor reference: axis cannot be validated here
|
||||
// (remote side unknown at eval time). Propagate as external.
|
||||
result = (external: classified.external)
|
||||
} else {
|
||||
let ref-id = classified.local
|
||||
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",
|
||||
)
|
||||
}
|
||||
let new-visiting = visiting + (id,)
|
||||
let res = resolve-placement(placements, ref-id, new-visiting, elmt-id: elmt-id)
|
||||
placements = res.at(0)
|
||||
result = res.at(1)
|
||||
}
|
||||
|
||||
} else {
|
||||
result = (pct: p.pct-auto)
|
||||
draw.hide(draw.circle(
|
||||
pos,
|
||||
radius: 0,
|
||||
stroke: none,
|
||||
name: id
|
||||
))
|
||||
}
|
||||
|
||||
if "external" in result {
|
||||
p.insert("external-ref", result.external)
|
||||
p.insert("pct", none)
|
||||
} else {
|
||||
p.insert("pct", result.pct)
|
||||
}
|
||||
p.insert("resolved", true)
|
||||
placements.insert(id, p)
|
||||
return (placements, result)
|
||||
}
|
||||
|
||||
#let add-ports(
|
||||
elmt-id,
|
||||
tl,
|
||||
tr,
|
||||
br,
|
||||
bl,
|
||||
tl, tr, br, bl,
|
||||
ports,
|
||||
ports-margins,
|
||||
width: 0,
|
||||
height: 0,
|
||||
debug: false,
|
||||
outside-name: false,
|
||||
name-padding: 2pt,
|
||||
port-shape: none,
|
||||
port-kind: "in",
|
||||
port-size: 1em,
|
||||
elmt-stroke: black + 1pt,
|
||||
elmt-fill: none,
|
||||
wire-inside: false,
|
||||
debug: false
|
||||
) = {
|
||||
let sides = (
|
||||
"north": (tl, tr),
|
||||
"east": (tr, br),
|
||||
"south": (bl, br),
|
||||
"west": (tl, bl),
|
||||
"west": (tl, bl)
|
||||
)
|
||||
|
||||
if type(ports) != dictionary {
|
||||
return
|
||||
}
|
||||
|
||||
// ---- Phase 1: build placement table with per-port baseline pct. ----
|
||||
let placements = (:)
|
||||
// Preserve iteration order for phase 3.
|
||||
let order = ()
|
||||
// Per-side (pt0, pt1) after margin adjustment, and per-side spacing.
|
||||
let side-info = (:)
|
||||
|
||||
for (side, props) in sides {
|
||||
let side-ports = ports.at(side, default: ())
|
||||
let pt0 = props.at(0)
|
||||
let pt1 = props.at(1)
|
||||
|
||||
if side in ports-margins {
|
||||
let margins = ports-margins.at(side)
|
||||
let a = (pt0, margins.at(0), pt1)
|
||||
let b = (pt0, 100% - margins.at(1), pt1)
|
||||
pt0 = a
|
||||
pt1 = b
|
||||
}
|
||||
|
||||
let space = 100% / (side-ports.len() + 1)
|
||||
side-info.insert(side, (pt0: pt0, pt1: pt1, space: space))
|
||||
|
||||
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)
|
||||
+ ")",
|
||||
)
|
||||
let pct = (i + 1) * space
|
||||
let pt0 = props.at(0)
|
||||
let pt1 = props.at(1)
|
||||
|
||||
if side in ports-margins {
|
||||
let (a, b) = (pt0, pt1)
|
||||
let margins = ports-margins.at(side)
|
||||
a = (pt0, margins.at(0), pt1)
|
||||
b = (pt0, 100% - margins.at(1), pt1)
|
||||
pt0 = a
|
||||
pt1 = b
|
||||
}
|
||||
let pct-auto = (i + 1) * space
|
||||
placements.insert(pid, (
|
||||
side: side,
|
||||
raw: port,
|
||||
index: i,
|
||||
pct-auto: pct-auto,
|
||||
width: width,
|
||||
height: height,
|
||||
resolved: false,
|
||||
))
|
||||
order.push(pid)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Phase 2: resolve overrides & align-with references. ----
|
||||
for pid in order {
|
||||
let res = resolve-placement(placements, pid, (), elmt-id: elmt-id)
|
||||
placements = res.at(0)
|
||||
}
|
||||
|
||||
// ---- Phase 3: draw. ----
|
||||
for pid in order {
|
||||
let p = placements.at(pid)
|
||||
let side = p.side
|
||||
let info = side-info.at(side)
|
||||
let pt0 = info.pt0
|
||||
let pt1 = info.pt1
|
||||
let space = info.space
|
||||
let port = p.raw
|
||||
let is-ew = side in ("east", "west")
|
||||
|
||||
let pos
|
||||
let pos-prev
|
||||
let pos-next
|
||||
|
||||
if "external-ref" in p {
|
||||
// Cross-element alignment: build pos via CeTZ perpendicular so this
|
||||
// port shares one coordinate with the remote anchor and stays on this
|
||||
// element's side line for the other coordinate.
|
||||
// east/west: same y as remote, x from this side (pt0.x == pt1.x)
|
||||
// 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)
|
||||
// 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-prev = pos
|
||||
pos-next = pos
|
||||
}
|
||||
} else {
|
||||
let pct = p.pct
|
||||
pos = (pt0, pct, pt1)
|
||||
pos-prev = (pt0, pct - 0.5 * space, pt1)
|
||||
pos-next = (pt0, pct + 0.5 * space, pt1)
|
||||
|
||||
let pos = (pt0, pct, pt1)
|
||||
let pct-prev = (i + 0.5) * space
|
||||
let pct-next = (i + 1.5) * space
|
||||
let pos-prev = (pt0, pct-prev, pt1)
|
||||
let pos-next = (pt0, pct-next, pt1)
|
||||
|
||||
if port.at("small", default: false) {
|
||||
pos-prev = (pos, 4pt, pt0)
|
||||
pos-next = (pos, 4pt, pt1)
|
||||
}
|
||||
}
|
||||
|
||||
add-port(
|
||||
elmt-id,
|
||||
side,
|
||||
port,
|
||||
pos,
|
||||
prev: pos-prev,
|
||||
next: pos-next,
|
||||
debug: debug,
|
||||
outside-name: outside-name,
|
||||
name-padding: name-padding,
|
||||
port-shape: port-shape,
|
||||
port-kind: port-kind,
|
||||
port-size: port-size,
|
||||
elmt-stroke: elmt-stroke,
|
||||
elmt-fill: elmt-fill,
|
||||
wire-inside: wire-inside,
|
||||
)
|
||||
add-port(
|
||||
elmt-id,
|
||||
side,
|
||||
port,
|
||||
pos,
|
||||
prev: pos-prev,
|
||||
next: pos-next,
|
||||
debug: debug
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,4 @@
|
||||
#import "elements/logic/and.typ": gate-and, gate-nand
|
||||
#import "elements/logic/or.typ": gate-or, gate-nor
|
||||
#import "elements/logic/xor.typ": gate-xor, gate-xnor
|
||||
#import "elements/logic/buf.typ": gate-buf, gate-not
|
||||
#import "elements/logic/iec_gate.typ": iec-gate
|
||||
#import "elements/logic/iec_and.typ": iec-gate-and, iec-gate-nand
|
||||
#import "elements/logic/iec_or.typ": iec-gate-or, iec-gate-nor
|
||||
#import "elements/logic/iec_buf.typ": iec-gate-buf, iec-gate-not
|
||||
#import "elements/logic/iec_xor.typ": iec-gate-xor, iec-gate-xnor
|
||||
#import "elements/logic/buf.typ": gate-buf, gate-not
|
||||
@@ -1,7 +1,7 @@
|
||||
#let version = version(0, 2, 1)
|
||||
#let name = "circuiteria"
|
||||
#let version = version(0, 2, 0)
|
||||
|
||||
#import "circuit.typ": circuit
|
||||
#import "electrical.typ"
|
||||
#import "element.typ"
|
||||
#import "gates.typ"
|
||||
#import "util.typ"
|
||||
|
||||
@@ -1,23 +1,3 @@
|
||||
#import "cetz.typ": coordinate
|
||||
|
||||
/// Resolves a relative coordinate shorthand of the form `(rel: <number>, to: <anchor>)`
|
||||
/// to a single scalar along the requested axis.
|
||||
///
|
||||
/// Used to implement the shared `(rel:, to:)` shorthand accepted by the `x` and
|
||||
/// `y` parameters of `elmt`, `gate` and `iec-gate`.
|
||||
///
|
||||
/// - ctx (dictionary): The current CeTZ context
|
||||
/// - val (dictionary): A dictionary with fields `rel` (number) and `to` (anchor)
|
||||
/// - axis (int): `0` for x, `1` for y
|
||||
/// -> number
|
||||
#let resolve-rel-coord(ctx, val, axis) = {
|
||||
let offset = val.rel
|
||||
let to = val.to
|
||||
let delta = if axis == 0 { (offset, 0) } else { (0, offset) }
|
||||
let (_, to-pos) = coordinate.resolve(ctx, (rel: delta, to: to))
|
||||
return to-pos.at(axis)
|
||||
}
|
||||
|
||||
/// Predefined color palette
|
||||
/// #let w = 4
|
||||
/// #box(width: 100%, align(center)[
|
||||
@@ -36,7 +16,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 +47,7 @@
|
||||
north-west: "south-east",
|
||||
north-east: "south-west",
|
||||
south-east: "north-west",
|
||||
south-west: "north-east",
|
||||
south-west: "north-east"
|
||||
).at(anchor)
|
||||
}
|
||||
|
||||
@@ -86,140 +66,11 @@
|
||||
north-west: "north-east",
|
||||
north-east: "south-east",
|
||||
south-east: "south-west",
|
||||
south-west: "north-west",
|
||||
south-west: "north-west"
|
||||
).at(anchor)
|
||||
}
|
||||
|
||||
/// Returns the pentagon port shape vertices and the outer/inner anchor
|
||||
/// offsets for the given side and direction.
|
||||
///
|
||||
/// The pentagon straddles the block boundary: 1 unit outside, 1 unit inside;
|
||||
/// height ±0.5 units. For `kind: "in"` the tip points into the block; for
|
||||
/// `kind: "out"` it points away.
|
||||
///
|
||||
/// - side (str): One of `"north"`, `"east"`, `"south"`, `"west"`
|
||||
/// - kind (str): `"in"` or `"out"`
|
||||
/// - u (length,number): The pentagon unit size
|
||||
/// -> dictionary: `(vertices: (..5 pairs..), outer: (dx, dy), inner: (dx, dy))`
|
||||
#let port-shape-vertices(side, kind, u) = {
|
||||
// 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),
|
||||
)
|
||||
// For output, mirror horizontally so the tip points west (outside).
|
||||
if kind == "out" {
|
||||
base-vertices = base-vertices.map(p => (-p.at(0), p.at(1)))
|
||||
}
|
||||
// Outer (wire-connection) and inner (inside-label) anchors for a west port.
|
||||
// Outer is always 1u outside the boundary along the port normal; inner is 1u
|
||||
// inside. Independent of `kind` (only the pentagon fill/tip direction change).
|
||||
let base-outer = (-u, 0)
|
||||
let base-inner = (u, 0)
|
||||
|
||||
// Rotation from west's frame to target side:
|
||||
// west: identity (x, y)
|
||||
// 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) }
|
||||
|
||||
// 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",
|
||||
)
|
||||
|
||||
/// Normalizes a coordinate so that 2D points (x, y) are padded
|
||||
/// to (x, y, 0) as required by CeTZ's coordinate resolver.
|
||||
/// Also handles function-based and dictionary-based coordinates
|
||||
/// recursively.
|
||||
///
|
||||
/// - coord (any): A CeTZ-compatible coordinate
|
||||
/// -> any
|
||||
#let normalize-coord(coord) = {
|
||||
if type(coord) == array {
|
||||
if coord.len() == 2 {
|
||||
let (a, b) = coord
|
||||
// Raw 2D point: (x, y) -> (x, y, 0)
|
||||
if type(a) in (int, float) and type(b) in (int, float) {
|
||||
return (a, b, 0)
|
||||
}
|
||||
// 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 result
|
||||
},
|
||||
b,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if type(coord) == dictionary {
|
||||
let result = (:)
|
||||
for (key, value) in coord {
|
||||
result.insert(key, normalize-coord(value))
|
||||
}
|
||||
return result
|
||||
}
|
||||
return coord
|
||||
}
|
||||
"center", "north", "east", "west", "south",
|
||||
"north-east", "north-west", "south-east", "south-west"
|
||||
)
|
||||
@@ -1,27 +1,9 @@
|
||||
#import "/src/cetz.typ": draw, coordinate
|
||||
#import "util.typ": opposite-anchor, normalize-coord, resolve-rel-coord
|
||||
#import "@preview/cetz:0.3.2": draw, coordinate
|
||||
#import "util.typ": opposite-anchor
|
||||
|
||||
/// List of valid wire styles
|
||||
/// #examples.wires
|
||||
///
|
||||
/// The `"u-shape"` style connects two ports that face the same cardinal side.
|
||||
/// #examples.u-shape-wire
|
||||
///
|
||||
/// 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 wire-styles = ("direct", "zigzag", "dodge")
|
||||
#let signal-width = 1pt
|
||||
#let bus-width = 1.5pt
|
||||
|
||||
@@ -42,95 +24,29 @@
|
||||
return (pts, anchors)
|
||||
}
|
||||
|
||||
// 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; 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
|
||||
// `axis == "x"` or
|
||||
// `(rel: (0, rel), to: to)` for
|
||||
// `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 axis == "x" {
|
||||
return (rel: (r, 0), to: at.to)
|
||||
} else {
|
||||
return (rel: (0, r), to: at.to)
|
||||
}
|
||||
}
|
||||
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 get-zigzag-wire(pts, ratio, dir) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
let mid = if dir == "vertical" {
|
||||
(start, ratio, (horizontal: end, vertical: ()))
|
||||
} else {
|
||||
// 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
|
||||
)
|
||||
}
|
||||
(start, ratio, (horizontal: (), vertical: end))
|
||||
}
|
||||
|
||||
let points = if dir == "vertical" {
|
||||
(
|
||||
start,
|
||||
(horizontal: mid, vertical: ()),
|
||||
(horizontal: (), vertical: end),
|
||||
end
|
||||
)
|
||||
} else {
|
||||
(
|
||||
start,
|
||||
(horizontal: (), vertical: mid),
|
||||
(horizontal: end, vertical: ()),
|
||||
end
|
||||
)
|
||||
}
|
||||
let anchors = (
|
||||
"start": start,
|
||||
@@ -152,8 +68,8 @@
|
||||
|
||||
let (ctx, p0) = coordinate.resolve(ctx, start)
|
||||
let (ctx, p3) = coordinate.resolve(ctx, end)
|
||||
p0 = (x: p0.first(), y: p0.at(1))
|
||||
p3 = (x: p3.first(), y: p3.at(1))
|
||||
p0 = (x: p0.first(), y: p0.last())
|
||||
p3 = (x: p3.first(), y: p3.last())
|
||||
|
||||
let dx1 = margin-start
|
||||
let dx2 = margin-end
|
||||
@@ -170,13 +86,13 @@
|
||||
if side-end == "east" {
|
||||
dx2 *= -1
|
||||
}
|
||||
p1 = (p0.x + dx1, p0.y, 0)
|
||||
p2 = (p3.x - dx2, p0.y, 0)
|
||||
p1 = (p0.x + dx1, p0.y)
|
||||
p2 = (p3.x - dx2, p0.y)
|
||||
|
||||
let points = (
|
||||
start,
|
||||
(horizontal: p1, vertical: ()),
|
||||
(horizontal: (), vertical: (0, dodge-y, 0)),
|
||||
(horizontal: (), vertical: (0, dodge-y)),
|
||||
(horizontal: p2, vertical: ()),
|
||||
(horizontal: (), vertical: end),
|
||||
end
|
||||
@@ -193,397 +109,6 @@
|
||||
return (points, anchors)
|
||||
}
|
||||
|
||||
#let _u-valid-sides = ("east", "west", "north", "south")
|
||||
|
||||
// Try to deduce the cardinal side a port anchor faces by comparing its
|
||||
// position to the center of the element it belongs to. Returns none if the
|
||||
// anchor is not a port-style anchor (i.e. does not contain "-port-") or if
|
||||
// resolution fails.
|
||||
#let _u-detect-side(anchor, ctx) = {
|
||||
if type(anchor) != str { return none }
|
||||
let sep = "-port-"
|
||||
if not anchor.contains(sep) { return none }
|
||||
let elmt-id = anchor.split(sep).first()
|
||||
let port-pos = none
|
||||
let center-pos = none
|
||||
// coordinate.resolve panics on unknown anchors; there is no try/catch in
|
||||
// Typst, so we assume port anchors created by add-port always come with a
|
||||
// matching group of the same name. If not, the user must set u-side
|
||||
// explicitly.
|
||||
let (_, p1) = coordinate.resolve(ctx, anchor)
|
||||
let (_, p2) = coordinate.resolve(ctx, elmt-id + ".center")
|
||||
port-pos = p1
|
||||
center-pos = p2
|
||||
let dx = port-pos.at(0) - center-pos.at(0)
|
||||
let dy = port-pos.at(1) - center-pos.at(1)
|
||||
if calc.abs(dx) >= calc.abs(dy) {
|
||||
return if dx >= 0 { "east" } else { "west" }
|
||||
} else {
|
||||
return if dy >= 0 { "north" } else { "south" }
|
||||
}
|
||||
}
|
||||
|
||||
#let get-u-shape-wire(pts, u-depth, u-side, u-at, ctx) = {
|
||||
let start = pts.first()
|
||||
let end = pts.last()
|
||||
|
||||
// Resolve the (auto|explicit) side
|
||||
let side = u-side
|
||||
if side == auto {
|
||||
let side-start = _u-detect-side(start, ctx)
|
||||
let side-end = _u-detect-side(end, ctx)
|
||||
if side-start != none and side-end != none and side-start != side-end {
|
||||
panic(
|
||||
"u-shape: start port faces '" + side-start
|
||||
+ "' but end port faces '" + side-end
|
||||
+ "'; both must face the same side, or set u-side explicitly"
|
||||
)
|
||||
}
|
||||
if side-start != none { side = side-start }
|
||||
else if side-end != none { side = side-end }
|
||||
else {
|
||||
panic(
|
||||
"u-shape: could not auto-detect u-side (endpoints are not port anchors); "
|
||||
+ "please set u-side explicitly to one of " + repr(_u-valid-sides)
|
||||
)
|
||||
}
|
||||
}
|
||||
if not side in _u-valid-sides {
|
||||
panic("u-shape: invalid u-side '" + repr(side) + "'; must be one of " + repr(_u-valid-sides))
|
||||
}
|
||||
|
||||
let (ctx, s) = coordinate.resolve(ctx, start)
|
||||
let (ctx, e) = coordinate.resolve(ctx, end)
|
||||
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 = 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 = 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 = 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 = if bar-at != none { bar-at } else { calc.min(sy, ey) - u-depth }
|
||||
p1 = (sx, by, 0)
|
||||
p2 = (ex, by, 0)
|
||||
}
|
||||
|
||||
let points = (start, p1, p2, end)
|
||||
let anchors = (
|
||||
"start": start,
|
||||
"u-start": p1,
|
||||
"u-end": p2,
|
||||
"end": end
|
||||
)
|
||||
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()
|
||||
let (margin-start, margin-end) = margins
|
||||
let (side-start, side-end) = sides
|
||||
let (center_horizontal, center_vertical) = center-guides
|
||||
|
||||
let (ctx, p0) = coordinate.resolve(ctx, start)
|
||||
let (ctx, p6) = coordinate.resolve(ctx, end)
|
||||
p0 = (x: p0.first(), y: p0.at(1))
|
||||
p6 = (x: p6.first(), y: p6.at(1))
|
||||
|
||||
let box_width = calc.abs(p6.x - p0.x)
|
||||
let box_height = calc.abs(p6.y - p0.y)
|
||||
|
||||
// finding correct dx and dy
|
||||
let dx1 = box_width * margin-start / 100%
|
||||
if side-start == "west" {
|
||||
dx1 *= -1
|
||||
} else if side-start == "north" or side-start == "south" { dx1 = 0}
|
||||
|
||||
let dx2 = box_width * margin-end / 100%
|
||||
if side-end == "west" {
|
||||
dx2 *= -1
|
||||
} else if side-end == "north" or side-end == "south" { dx2 = 0}
|
||||
|
||||
let dy1 = box_height * margin-start / 100%
|
||||
if side-start == "south" {
|
||||
dy1 *= -1
|
||||
} else if side-start == "west" or side-start == "east" { dy1 = 0}
|
||||
|
||||
let dy2 = box_height * margin-end / 100%
|
||||
if side-end == "south" {
|
||||
dy2 *= -1
|
||||
} else if side-end == "west" or side-end == "east" { dy2 = 0}
|
||||
|
||||
|
||||
// points that are closest to the edge points
|
||||
let p1 = (p0.x + dx1, p0.y + dy1, 0)
|
||||
let p5 = (p6.x + dx2, p6.y + dy2, 0)
|
||||
|
||||
|
||||
// middle point
|
||||
let center_x = p0.x + box_width * center_horizontal / 100%
|
||||
let center_y = p0.y + box_height * center_vertical / 100%
|
||||
let p3 = (center_x, center_y, 0)
|
||||
|
||||
// setting up the points for that touch the guides
|
||||
let p2 = (0, 0, 0)
|
||||
let p4 = (0, 0, 0)
|
||||
if side-start in ("north", "south") {
|
||||
p2 = (horizontal: p3, vertical: p1)
|
||||
} else {
|
||||
p2 = (horizontal: p1, vertical: p3)
|
||||
}
|
||||
if side-end in ("north", "south") {
|
||||
p4 = (horizontal: p3, vertical: p5)
|
||||
} else if side-end in ("east", "west") {
|
||||
p4 = (horizontal: p5, vertical: p3)
|
||||
}
|
||||
|
||||
// returning
|
||||
let points = (
|
||||
start,
|
||||
p1,
|
||||
p2,
|
||||
p3,
|
||||
p4,
|
||||
p5,
|
||||
end
|
||||
)
|
||||
let anchors = (
|
||||
"start": start,
|
||||
"start2": points.at(1),
|
||||
"guide-start": points.at(2),
|
||||
"center": points.at(3),
|
||||
"guide-end": points.at(4),
|
||||
"end2": points.at(5),
|
||||
"end": end
|
||||
)
|
||||
return (points, anchors)
|
||||
}
|
||||
/// Draws a wire between two points
|
||||
/// - id (str): The wire's id, for future reference (anchors)
|
||||
/// - pts (array): The two points (as CeTZ compatible coordinates, i.e. XY, relative positions, ids, etc.)
|
||||
@@ -597,26 +122,11 @@
|
||||
/// - 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 (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 (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
|
||||
/// - zigzag-ratio (ratio): Position of the zigzag vertical relative to the horizontal span (only with style "zigzag")
|
||||
/// - zigzag-dir (str): The zigzag's direction. As either "vertical" or "horizontal" (only with dstyle "zigzag")
|
||||
/// - 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")
|
||||
/// - guided-center (array): the horizontal and vertical guides of the center guides (only with style "guided")
|
||||
/// - 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 (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,
|
||||
@@ -627,28 +137,13 @@
|
||||
dashed: false,
|
||||
style: "direct",
|
||||
reverse: false,
|
||||
guided-center: (50%, 50%),
|
||||
guided-margins: (5%, 5%),
|
||||
guided-sides: ("east", "west"),
|
||||
directed: false,
|
||||
rotate-name: true,
|
||||
zigzag-ratio: auto,
|
||||
zigzag-dir: auto,
|
||||
zigzag-at: none,
|
||||
zigzag-ratio: 50%,
|
||||
zigzag-dir: "vertical",
|
||||
dodge-y: 0,
|
||||
dodge-sides: ("east", "west"),
|
||||
dodge-margins: (5%, 5%),
|
||||
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,
|
||||
dodge-margins: (5%, 5%)
|
||||
) = draw.get-ctx(ctx => {
|
||||
if not style in wire-styles {
|
||||
panic("Invalid wire style '" + style + "'")
|
||||
@@ -657,55 +152,6 @@
|
||||
if pts.len() != 2 {
|
||||
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 }
|
||||
|
||||
// `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)
|
||||
|
||||
let stroke = (
|
||||
paint: color,
|
||||
@@ -722,28 +168,7 @@
|
||||
(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,
|
||||
zz-dir,
|
||||
at: normalize-zigzag-at(zigzag-at, zz-dir),
|
||||
)
|
||||
(points, anchors) = get-zigzag-wire(pts, zigzag-ratio, zigzag-dir)
|
||||
|
||||
} else if style == "dodge" {
|
||||
(points, anchors) = get-dodge-wire(
|
||||
@@ -753,35 +178,6 @@
|
||||
dodge-sides,
|
||||
ctx
|
||||
)
|
||||
} else if style == "guided" {
|
||||
(points, anchors) = get-guided-wire(
|
||||
pts,
|
||||
guided-margins,
|
||||
guided-sides,
|
||||
guided-center,
|
||||
ctx
|
||||
)
|
||||
} else if style == "u-shape" {
|
||||
(points, anchors) = get-u-shape-wire(
|
||||
pts,
|
||||
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)
|
||||
@@ -819,7 +215,7 @@
|
||||
if name != none {
|
||||
let names = ()
|
||||
|
||||
if type(name) == str or type(name) == content {
|
||||
if type(name) == str {
|
||||
names = ((name, name-pos),)
|
||||
|
||||
} else if type(name) == array {
|
||||
@@ -907,142 +303,4 @@
|
||||
name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a wire breakout: a 45° diagonal line starting at (x, y) and
|
||||
/// ending 1 unit away in one of the four diagonal directions. The end
|
||||
/// 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"` (or `"zigzag"`) can auto-detect the port side.
|
||||
///
|
||||
/// #examples.breakout
|
||||
///
|
||||
/// Label positions are anchored so that the label sits neatly next to the
|
||||
/// end point of the breakout. When the label direction matches the
|
||||
/// breakout direction (which would overlap the line) the label is
|
||||
/// automatically placed at the start point instead so the text does not
|
||||
/// cover the line.
|
||||
///
|
||||
/// #examples.breakout-label
|
||||
///
|
||||
/// The port anchor makes it easy to fan out a bus:
|
||||
///
|
||||
/// #examples.breakout-wire
|
||||
///
|
||||
/// - id (str): The breakout id (also used as the port id)
|
||||
/// - x (number, dictionary): The starting x position (same forms as `element.block`)
|
||||
/// - y (number, dictionary): The starting y position (same forms as `element.block`)
|
||||
/// - direction (str): One of `"north-east"`, `"north-west"`, `"south-east"`, `"south-west"`
|
||||
/// - length (length, number): The diagonal length (default `1em`, matches port size)
|
||||
/// - port-side (str): Cardinal side used for wire-attach behavior (`"north"`, `"east"`, `"south"`, `"west"`)
|
||||
/// - stroke (stroke): Line stroke (paint + thickness)
|
||||
/// - name (none, str): Optional label displayed near the breakout
|
||||
/// - name-pos (none, str): Label position, one of `"north-west"`, `"north-east"`, `"south-west"`, `"south-east"`
|
||||
/// - debug (bool): If true, shows a red dot at the port anchor
|
||||
#let breakout(
|
||||
id,
|
||||
x: none, y: none,
|
||||
direction: "north-east",
|
||||
length: 1em,
|
||||
port-side: "east",
|
||||
stroke: black + 1pt,
|
||||
name: none,
|
||||
name-pos: none,
|
||||
debug: false,
|
||||
) = draw.get-ctx(ctx => {
|
||||
if x == none { panic("breakout: parameter x must be set") }
|
||||
if y == none { panic("breakout: parameter y must be set") }
|
||||
|
||||
let valid-directions = ("north-east", "north-west", "south-east", "south-west")
|
||||
if not direction in valid-directions {
|
||||
panic("breakout: invalid direction '" + repr(direction)
|
||||
+ "'; must be one of " + repr(valid-directions))
|
||||
}
|
||||
let valid-sides = ("north", "east", "south", "west")
|
||||
if not port-side in valid-sides {
|
||||
panic("breakout: invalid port-side '" + repr(port-side)
|
||||
+ "'; must be one of " + repr(valid-sides))
|
||||
}
|
||||
|
||||
// Diagonal component: length / sqrt(2). Resolve to a canvas-space
|
||||
// number so we can do arithmetic with anchor coordinates.
|
||||
let d-raw = length / calc.sqrt(2)
|
||||
let (_, d-vec) = coordinate.resolve(ctx, (d-raw, 0, 0))
|
||||
let d = d-vec.at(0)
|
||||
|
||||
// Direction sign vector for (dx, dy).
|
||||
let sx = if direction.ends-with("east") { 1 } else { -1 }
|
||||
let sy = if direction.starts-with("north") { 1 } else { -1 }
|
||||
|
||||
// Resolve x.
|
||||
let sx0 = x
|
||||
if type(x) == dictionary {
|
||||
if "rel" in x {
|
||||
sx0 = resolve-rel-coord(ctx, x, 0)
|
||||
} else if "from" in x {
|
||||
let (_, from-pos) = coordinate.resolve(ctx, x.from)
|
||||
sx0 = from-pos.at(0)
|
||||
} else {
|
||||
panic("breakout: unsupported x dictionary: " + repr(x))
|
||||
}
|
||||
}
|
||||
// Resolve y.
|
||||
let sy0 = y
|
||||
if type(y) == dictionary {
|
||||
if "rel" in y {
|
||||
sy0 = resolve-rel-coord(ctx, y, 1)
|
||||
} else if "from" in y {
|
||||
let (_, from-pos) = coordinate.resolve(ctx, y.from)
|
||||
sy0 = from-pos.at(1)
|
||||
} else {
|
||||
panic("breakout: unsupported y dictionary: " + repr(y))
|
||||
}
|
||||
}
|
||||
|
||||
let start = (sx0, sy0)
|
||||
let end = (rel: (sx * d, sy * d), to: start)
|
||||
|
||||
// Offset used for the synthesized center anchor: opposite of port-side.
|
||||
let center-offset = (
|
||||
"north": (0, -1em),
|
||||
"east": (-1em, 0),
|
||||
"south": (0, 1em),
|
||||
"west": (1em, 0),
|
||||
).at(port-side)
|
||||
|
||||
let port-anchor-name = id + "-port-" + id
|
||||
|
||||
draw.group(name: id, {
|
||||
draw.line(start, end, stroke: stroke)
|
||||
draw.anchor("start", start)
|
||||
draw.anchor("end", end)
|
||||
draw.anchor("center", (rel: center-offset, to: end))
|
||||
|
||||
if name != none and name-pos != none {
|
||||
let label-anchor = (
|
||||
"north-west": "south-east",
|
||||
"north-east": "south-west",
|
||||
"south-west": "north-east",
|
||||
"south-east": "north-west",
|
||||
).at(name-pos)
|
||||
|
||||
// Overlap case: label direction is opposite to breakout direction,
|
||||
// so the label at the end point would extend back over the line.
|
||||
// Anchor it at the start point instead.
|
||||
let label-pos = if name-pos == opposite-anchor(direction) { start } else { end }
|
||||
|
||||
draw.content(label-pos, anchor: label-anchor, padding: 3pt, name)
|
||||
}
|
||||
})
|
||||
|
||||
// Port anchor emitted at top level so it can be referenced by its
|
||||
// bare name (mirrors add-port's approach in ports.typ).
|
||||
if debug {
|
||||
draw.circle(end, name: port-anchor-name,
|
||||
radius: .1, stroke: none, fill: red)
|
||||
} else {
|
||||
draw.hide(draw.circle(end, name: port-anchor-name,
|
||||
radius: 0, stroke: none))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "circuiteria"
|
||||
version = "0.2.1"
|
||||
compiler = "0.15.0"
|
||||
version = "0.2.0"
|
||||
compiler = "0.13.0"
|
||||
repository = "https://git.kb28.ch/HEL/circuiteria"
|
||||
entrypoint = "src/lib.typ"
|
||||
authors = [
|
||||
@@ -9,6 +9,6 @@ authors = [
|
||||
]
|
||||
categories = [ "visualization" ]
|
||||
license = "Apache-2.0"
|
||||
description = "Drawing block circuits made easy, using CeTZ"
|
||||
description = "Drawing block circuits with Typst made easy, using CeTZ"
|
||||
keywords = [ "circuit", "block", "draw" ]
|
||||
exclude = [ "gallery", "justfile", "doc" ]
|
||||