diff --git a/src/cheatsheets/Schaltungstheorie.typ b/src/cheatsheets/Schaltungstheorie.typ index 39b77a7..e1c2161 100644 --- a/src/cheatsheets/Schaltungstheorie.typ +++ b/src/cheatsheets/Schaltungstheorie.typ @@ -1,14 +1,18 @@ #import "@preview/mannot:0.3.1" #import "@preview/zap:0.5.0" -#import "@preview/cetz:0.4.2" :* +#import "@preview/cetz:0.4.2" : * #import "@preview/cetz-plot:0.1.3" -#import "../lib/circuit.typ" : * #import "@preview/unify:0.7.1": num,qty,unit #import "@preview/cetz-plot:0.1.3" +#import "../lib/schaltungstheorie/opampTable.typ" : * +#import "../lib/schaltungstheorie/tumCustomSymbols.typ" as tumSymbols + +#import "../lib/circuit.typ" : * #import "../lib/common_rewrite.typ" : * #import "../lib/circuit.typ" : * + #set math.mat(delim: "[") #show math.equation.where(block: true): it => math.inline(it) #set math.mat(delim: "[") @@ -260,7 +264,7 @@ wire("I0.in", "R1.in") wire("R1.in", (rel: (0.5, 0)), i: (content: $i$, invert: true)) - cetz.draw.content((0.62, -0.75), [$R$]) + cetz.draw.content((0.62, -0.75), [$G_i$]) cetz.draw.set-style(mark: (end: ">", fill: black, scale: 0.6)) cetz.draw.content((1.7, -0.75), [$u$]) cetz.draw.line((1.5, -0.1), (1.5, -1.4), stroke: 0.5pt) @@ -278,7 +282,7 @@ ) wire((0, -1.5), (1.75, -1.5)) - cetz.draw.content((0.62, -0.75), [$R$]) + cetz.draw.content((0.62, -0.75), [$R_i$]) cetz.draw.set-style(mark: (end: ">", fill: black, scale: 0.6)) cetz.draw.content((1.95, -0.75), [$u$]) cetz.draw.line((1.75, -0.1), (1.75, -1.4), stroke: 0.5pt) @@ -286,23 +290,22 @@ ), [ - $u = R_i i + u_0$ \ + $u = R_i i + U_0$ \ ], [ - $i = 1/R_i u - I_0$ + $i = G_i u - I_0$ ], - table.cell(colspan: 2)[ - #align($-->$) - ], - table.cell(colspan: 2)[ - $<--$ - ], + align(center, text(size: 7mm, $-->$)), + [$G_i = 1/R_i \ I_0 = U_0 G_i$], + + [$R_i = 1/G_i \ U_0 = I_0 R_i$], + align(center, text(size: 7mm, $<--$)), ) ] - // Quell Wandlung + /*// Quell Wandlung #bgBlock(fill: colorEineTore)[ #subHeading(fill: colorEineTore)[Quelle Wandlung] @@ -369,6 +372,8 @@ ] ); ] + */ + // Bauelemente #bgBlock(fill: colorEineTore)[ #subHeading(fill: colorEineTore)[Bauelemente] @@ -1293,6 +1298,7 @@ #subHeading(fill: colorComplexAC)[Komplexe Komponent] #table( columns: (1fr, 2fr, 2fr, 2fr), + inset: (bottom: 2mm, top: 2mm), fill: (x, y) => if calc.rem(y, 2) == 1 { tableFillLow } else { tableFillHigh }, [], [*$Y = U/I$*], [*$Z = I/U$*], [*$phi$*], [], [*$Omega$*], [*$S$*], [*rad*], @@ -1347,11 +1353,20 @@ #bgBlock(fill: colorAllgemein, [ #subHeading(fill: colorAllgemein, [Sin-Table]) #sinTable + + #SineCircle() ]) ] #pagebreak() +#bgBlock(fill: colorZweiTore, width: 100%)[ + #subHeading(fill: colorZweiTore)[OpAmp Schaltungen] + + #scale(opampTable, 100%) +] + + #bgBlock(fill: colorZweiTore, width: 100%)[ #subHeading(fill: colorZweiTore)[Zwei-Tor-Übersichts] @@ -1579,12 +1594,12 @@ content((0.75, 0.75), "In") }) ]), - $bold(R)$, - $bold(G)$, - $bold(H)$, - $bold(H')$, - $bold(A)$, - $bold(A')$, + $bold(R) quad mat(Omega, Omega; Omega, Omega)$, + $bold(G) quad mat(S, S; S, S)$, + $bold(H) quad mat(Omega, 1; 1, S)$, + $bold(H') quad mat(S, 1; 1, Omega)$, + $bold(A) quad mat(S, 1; 1, Omega)$, + $bold(A') quad mat(S, 1; 1, Omega)$, $bold(R)$, $mat(r_11, r_12; r_21, r_22)$, diff --git a/src/lib/common_rewrite.typ b/src/lib/common_rewrite.typ index 1a68331..7ab30f0 100644 --- a/src/lib/common_rewrite.typ +++ b/src/lib/common_rewrite.typ @@ -1,3 +1,5 @@ +#import "@preview/cetz:0.4.2" + #let bgBlock(body, fill: color, width: 100%) = block(body, fill:fill.lighten(80%), width: width, inset: (bottom: 2mm, left: 2mm, right: 2mm,)) #let SeperatorLine = line(length: 100%, stroke: (paint: black, thickness: 0.3mm)) @@ -66,8 +68,51 @@ $z^* = a - #i b = r e^(-#i phi)$ + Konjungiert Erweitern:\ + $(a + b #i)/(c + d #i) = ((a + b #i)(c - d #i))/(c^2 + d² )$ + $r = abs(z) quad phi = cases( + arccos(a/r) space : space a >= 0, - arccos(a/r) space : space a < 0, )$ -] \ No newline at end of file +] + +#let SineCircle() = { + align(center+horizon, + cetz.canvas({ + import cetz.draw : * + line((-33mm, 0), (33mm, 0), stroke: (paint: rgb("#8e8e8e7f"))) + line((0, -33mm), (0, 33mm), stroke: (paint: rgb("#8e8e8e7f"))) + let labels = ( + (0deg, $0°$, $0$), + (30deg, $30°$, $pi/6$), + (45deg, $45°$, $pi/4$), + (60deg, $60°$, $pi/3$), + (90deg, $90°$, $pi/2$), + (120deg, $120°$, $2pi/3$), + (135deg, $135°$, $3pi/4$), + (150deg, $150°$, $5pi/6$), + (180deg, $180°$, $pi$), + (210deg, $210°$, $7pi/6$), + (225deg, $2250°$, $5pi/4$), + (240deg, $240°$, $4pi/3$), + (270deg, $270°$, $3pi/2$), + (300deg, $300°$, $5pi/3$), + (315deg, $3150°$, $7pi/4$), + (330deg, $330°$, $11pi/6$), + ) + + + circle((0, 0), radius: 25mm) + + + + for (pos, label1, label2) in labels { + line((radius: 24mm, angle: pos), (radius: 26mm, angle: pos)) + content((radius: 20.0mm, angle: pos), label1, anchor: "mid", angle: if(pos > 90deg and pos < 270deg) {pos + 180deg} else {pos}) + content((radius: 29.5mm, angle: pos), label2, anchor: "mid") + } + + }) + ) +} \ No newline at end of file diff --git a/src/lib/schaltungstheorie/opampTable.typ b/src/lib/schaltungstheorie/opampTable.typ new file mode 100644 index 0000000..ebb512e --- /dev/null +++ b/src/lib/schaltungstheorie/opampTable.typ @@ -0,0 +1,271 @@ +#import "@preview/zap:0.5.0" +#import "@preview/cetz:0.4.2" + +#import "tumCustomSymbols.typ" as joham +#import "../common_rewrite.typ" : * + + +#let opampTable = table( + columns: (auto, auto, auto, auto, auto, auto), + align: center + horizon, + inset: (x, y) => if(y > 0) {3mm} else {2mm}, + fill: (x, y) => if calc.rem(x, 2) == 1 { tableFillHigh } else { tableFillLow }, + + table.header( + [*Spannungsfolger*], + [*Invertierender Verstärker*], + [*Nicht invertierender Verstärker*], + [*Ideale Diode*], + [*NIK*], + [*Gyrator*]), + zap.circuit({ + import zap: * + + set-style(vsource: (radius: 0.3)) + + joham.ideal-opamp("O", (2, 0), scale: 0.4, invert: true) + + node("MU", (rel: (0.2, 0), to: "O.out")) + cetz.draw.anchor("MM", (rel: (0, -0.65), to: "MU")) + node("MB", (2, -1)) + + + node("A", (rel: (-0.7, 0), to: "O.plus"), fill: false) + node("C", (rel: (0.7, 0), to: "O.out"), fill: false) + node("D", (rel: (0.7, -1), to: "O.out"), fill: false) + node("B", ("A", "|-", "MB"), fill: false) + + + wire("O.ground", "MB") + wire("B", "MB") + wire("MB", "D") + + wire("A", "O.plus") + wire("O.out", "C") + + zwire("O.minus", "MM", ratio: -30%) + wire("MM", "MU") + + joham.ground("G1", "MB", scale: 0.7) + + joham.voltage("A", "B", $u_"in"$) + joham.voltage("C", "D", anchor: "west", $u_"out"$) + }), + zap.circuit({ + import zap: * + + set-style(vsource: (radius: 0.3)) + + let height = 2 / 3; + + node("A", (0, height), fill: false) + node("B", (0, -height), fill: false) + node("C", (3, height), fill: false) + node("D", (3, -height), fill: false) + + node("MU", (1.25, height)) + node("MB", (1.25, -height)) + + node("OU", (2.5, height)) + node("OB", (2, -height)) + + joham.ideal-opamp("O", (2, 0), scale: 0.4) + + resistor("R1", "A", "MU", scale: 0.4, label: (content: $R_1$, distance: 3pt), fill: none) + wire("B", "MB") + + zwire("MU", "O.minus", ratio: 0%) + zwire("MB", "O.plus", ratio: 0%) + + resistor("R0", "MU", "OU", scale: 0.4, label: (content: $R_0$, distance: 3pt)) + wire("OU", "C") + + wire("O.ground", "OB") + wire("MB", "OB") + wire("OB", "D") + + zwire("O.out", "OU", ratio: 100%) + + joham.ground("G1", "MB", scale: 0.7) + + joham.voltage("A", "B", $u_"in"$) + joham.voltage("C", "D", anchor: "west", $u_"out"$) + }), + zap.circuit({ + import zap: * + + set-style(vsource: (radius: 0.3)) + + joham.ideal-opamp("O", (2, 0), scale: 0.4, invert: true) + + node("MU", (rel: (0.2, 0), to: "O.out")) + node("MM", (rel: (0, -1), to: "MU")) + node("MB", (rel: (0, -1), to: "MM")) + + node("A", (rel: (-0.7, 0), to: "O.plus"), fill: false) + node("C", (rel: (1, 0), to: "O.out"), fill: false) + node("D", (rel: (1, -2), to: "O.out"), fill: false) + node("B", ("A", "|-", "MB"), fill: false) + + node("G", (2, -2)) + + wire("O.ground", "G") + + resistor("R0", "MU", "MM", scale: 0.4, label: (content: $R_0$, distance: 3pt), fill: none) + resistor("R1", "MM", "MB", scale: 0.4, label: (content: $R_1$, distance: 3pt), fill: none) + + wire("MB", "D") + + wire("A", "O.plus") + wire("O.out", "C") + wire("B", "MB") + + zwire("O.minus", "MM", ratio: -30%) + + joham.ground("G1", "G", scale: 0.7) + + joham.voltage("A", "B", $u_"in"$) + joham.voltage("C", "D", anchor: "west", $u_"out"$) + }), + zap.circuit({ + import zap: * + + set-style(vsource: (radius: 0.3)) + + joham.ideal-opamp("O", (2, 0), scale: 0.4) + + node("G", (2, -0.75)) + node("MU", (rel: (-0.4, 0), to: "O.minus")) + node("MB", ("MU", "|-", "G")) + + node("A", (rel: (-1.2, 0), to: "O.minus"), fill: false) + node("B", ("A", "|-", "G"), fill: false) + + joham.diode("D1", (rel: (0, 0.5), to: "MU"), (rel: (1.4, 0)), scale: 0.6, type: "real") + + wire("O.ground", "G") + wire("B", "G") + + wire("A", "O.minus") + + zwire("MB", "O.plus", ratio: 0%) + + wire("D1.in", "MU") + zwire("D1.out", "O.out", ratio: 0%) + + joham.ground("G1", "G", scale: 0.7) + + joham.voltage("A", "B", $u$) + }), + zap.circuit({ + import zap: * + + node("A", (-1.4, 0), fill: false) + node("C", (1.4, 0), fill: false) + node("B", (-1.4, -1.4), fill: false) + node("D", (1.4, -1.4), fill: false) + + node("E", (0.7, -1.4)) + node("F", (0, 0)) + + joham.ideal-opamp("O", (0, -0.6), scale: 0.4, angle: 90deg) + + wire("B", "E") + wire("D", "E") + + node("G", (to: "A", rel: (0.4, 0))) + node("H", (to: "C", rel: (-0.4, 0))) + + wire("A", "G", i: (content: $i_"in"$, distance: 3pt, scale: 0.7)) + wire("C", "H") + + resistor("R1", "G", "F", scale: 0.4, label: (content: $R$, anchor: "south", distance: 3pt), fill: none) + resistor("R2", "H", "F", scale: 0.4, label: (content: $R$, anchor: "north", distance: 3pt), fill: none) + + zwire("G", "O.minus", ratio: 115%, axis: "y") + zwire("H", "O.plus", ratio: 115%, axis: "y") + + wire("O.out", "F") + + zwire("E", "O.ground", ratio: 0%) + + joham.ground("G1", "E", scale: 0.7) + + resistor("R3", (rel: (0.2, 0), to: "C"), (rel: (0.2, 0), to: "D"), scale: 0.4, label: (content: $R_L$, distance: 3pt), fill: none) + + wire("R3.in", "C") + wire("R3.out", "D") + + joham.voltage("A", "B", $u_"in"$) + }), + scale(80%, zap.circuit({ + import zap: * + + set-style(vsource: (radius: 0.3)) + + joham.ideal-opamp("O1", (-1, 0), scale: 0.4, angle: 90deg, invert: true) + joham.ideal-opamp("O2", (1, 0), scale: 0.4, angle: 90deg) + + node("MM", (0, -0.6)) + node("MB", (0, -0.9)) + node("MBL", (rel: (-0.3, 0), to: "MB")) + node("MBR", (rel: (0.3, 0), to: "MB")) + node("MU", (0, 1)) + + node("RU", (1, 1)) + node("LU", (-1, 1)) + + node("LLU", (rel: (-1, 0), to: "LU")) + node("RRU", (rel: (1.3, 0), to: "RU")) + + node("A", (rel: (-0.5, 0), to: "LLU"), fill: false) + node("B", ("A", "|-", "MB"), fill: false) + + node("D", (rel: (0.2, 0.4), to: "RU"), fill: false) + node("C", (rel: (-0.2, 0.4), to: "RRU"), fill: false) + + resistor("R1", "MU", "LU", scale: 0.4, label: (content: $R$, distance: 3pt, anchor: "south")) + resistor("R2", "RU", "MU", scale: 0.4, label: (content: $R$, distance: 3pt, anchor: "south")) + + resistor("R3", "LU", "LLU", scale: 0.4, label: (content: $R$, distance: 3pt, anchor: "south")) + + resistor("R4", (rel: (0.3, 0), to: "RRU"), ((rel: (0.3, 0), to: "RRU"), "|-", "MB"), scale: 0.4, label: (content: $R$, distance: 3pt)) + + joham.ground("G", "MB", scale: 0.7) + + wire("O1.out", (rel: (0, 0.2)), (rel: (0, -0.2), to: "RU"), "RU") + wire("O2.out", (rel: (0, 0.2)), (rel: (0, -0.2), to: "LU"), "LU") + + wire("MM", "MU") + + zwire("O1.minus", "MM", ratio: 0%) + zwire("O2.minus", "MM", ratio: 0%) + + wire("O1.plus", ("O1.plus", "|-", "MM"), ("MM", "-|", "LLU"), "LLU") + + wire("LLU", "A") + wire("MBL", "B") + + wire("MBL", "MB") + wire("MBR", "MB") + + zwire("O1.ground", "MBL", ratio: 100%) + zwire("O2.vcc", "MBR", ratio: 100%) + + wire("O2.plus", ("O2.plus", "|-", "MM"), ("MM", "-|", "RRU"), "RRU") + + wire("R4.in", "RRU") + wire("R4.out", "MBR") + + zwire("D", "RU", ratio: 0%) + zwire("C", "RRU", ratio: 0%) + + joham.voltage("A", "B", $u_"in"$) + joham.voltage("D", "C", anchor: "south", $u_"out"$) + })), + $u_"out" = u_"in"$, + $u_"out" = - R_0/R_1 u_"in"$, + $u_"out" = (1 + R_0 / R_1) u_"in"$, + $$, + $u_"in" = -R_L i_"in"$ +) \ No newline at end of file diff --git a/src/lib/schaltungstheorie/tumCustomSymbols.typ b/src/lib/schaltungstheorie/tumCustomSymbols.typ new file mode 100644 index 0000000..570214f --- /dev/null +++ b/src/lib/schaltungstheorie/tumCustomSymbols.typ @@ -0,0 +1,430 @@ +#import "@preview/zap:0.4.0": * // TODO: Using wrong version, because 5.0.0 breaks custom stuff +#import "@preview/cetz:0.4.2": draw +#import cetz.draw: * + +#let norator(name, node, scale: 1.0, ..params) = { + let custom-style = ( + width: 1.41 * scale, + height: 1.41 * scale + ) + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: position.len() < 2) + + merge-path(close: true, { + arc((-custom-style.width / 2, 0), radius: custom-style.height / 5, start: 45deg, stop: 315deg, anchor: "arc-center", name: "right-arc") + arc((custom-style.width / 2, 0), radius: custom-style.height / 5, start: 135deg, stop: -135deg, anchor: "arc-center", name: "left-arc") + }) + } + + component("norator", name, node, draw: draw, ..params) +} + +#let nullator(name, node, scale: 1.0, ..params) = { + let custom-style = ( + width: 1.41 * scale, + height: 1.41 * scale + ) + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: position.len() < 2) + + circle((0, 0), radius: (custom-style.width / 2, custom-style.height / 6)) + } + + component("nullator", name, node, draw: draw, ..params) +} + +#let diode(name, node, type: none, ..params) = { + assert((type in ("ideal", "convex", "concave", "zener", "real") or type == none), message: "type must be ideal, convex, concave, zener, ...") + if type == none { + type = "ideal" + } + + + let custom-style = ( + radius: 0.3, + width: 0.28, + ) + + let draw(ctx, position, style) = { + translate((-custom-style.radius / 4, 0)) + interface((-custom-style.radius / 2, -custom-style.radius), (custom-style.radius, custom-style.radius), io: position.len() < 2) + + polygon((0, 0), 3, radius: custom-style.radius, fill: if type in ("real", "zener") { black } else { auto }) + + if type == "zener" { + merge-path({ + line((custom-style.radius, custom-style.width), (custom-style.radius, -custom-style.width)) + line((custom-style.radius, custom-style.width), (custom-style.radius + custom-style.width / 3, custom-style.width)) + }) + } else { // ideal, convex, concave + line((custom-style.radius, custom-style.width), (custom-style.radius, -custom-style.width)) + } + + if type == "concave" { + arc((-custom-style.radius/10,0), radius: (custom-style.radius / 4, custom-style.radius / 3), start: 90deg, stop: -90deg, anchor: "chord-center") + } else if type == "convex" { + circle((0, 0), radius: custom-style.radius / 3.5) + } + } + + component("custom-diode", name, node, draw: draw, ..params) +} + +#let inductor(name, node, ..params) = { + let custom-style = ( + width: 1, + height: 0.4 + ) + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: position.len() < 2) + + cetz.decorations.coil(line((-custom-style.width / 2, 0), (custom-style.width / 2, 0)), amplitude: custom-style.width / 3, segments: 5) + } + + component("indcutor", name, node, draw: draw, ..params) +} + +#let eintor(name, node, ..params) = { + let custom-style = ( + width: 1, + height: 0.4 + ) + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: position.len() < 2) + + rect((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), ..style) + rect((custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 4, custom-style.height / 2), fill: black, ..style) + } + + component("eintor", name, node, draw: draw, ..params) +} + +#let controlled-source(name, node, scale: 1.0, type: none, ..params) = { + assert((type in ("voltage", "current")), message: "type must be voltage or current, ...") + + let custom-style = ( + radius: 0.3 * scale, + width: 0.28 * scale, + ) + + let draw(ctx, position, style) = { + interface((-custom-style.radius, -custom-style.radius), (custom-style.radius, custom-style.radius), io: position.len() < 2) + + polygon((0, 0), 4, radius: custom-style.radius) + + if type == "voltage" { + line((-custom-style.radius, 0), (custom-style.radius, 0)) + } else { + line((0, -custom-style.radius), (0, custom-style.radius)) + } + + anchor("north", (0, custom-style.radius)) + anchor("south", (0, -custom-style.radius)) + } + + component("custom-diode", name, node, draw: draw, ..params) +} + +#let empty(name, node, ..params) = { + let draw(ctx, position, style) = { + interface((-0.5, -0.2), (0.5, 0.2), io: true) + } + + component("empty", name, node, draw: draw, ..params) +} + +#let blackbox(name, node, mirror: false, label: "", ..params) = { + assert(params.pos().len() == 0, message: "blackbox only supports one node") + + let custom-style = ( + width: 1, + height: 1, + length: 0.2, + ) + + let factor = if mirror { -1 } else { 1 } + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: false) + + rect((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), ..style) + content((0, 0), label) + + anchor("in", (factor * (custom-style.width / 2 + custom-style.length), custom-style.height / 3)) + anchor("out", (factor * (custom-style.width / 2 + custom-style.length), -custom-style.height / 3)) + + wire("in", (rel: (-factor * custom-style.length, 0))) + wire("out", (rel: (-factor * custom-style.length, 0))) + } + + component("blackbox", name, node, draw: draw, ..params) +} + +#let zweitor(name, node, label: "", ..params) = { + assert(params.pos().len() == 0, message: "zweitor only supports one node") + // assert(type(label) == content, message: "label has to be content") + + let custom-style = ( + width: 1, + height: 1, + length: 0.2, + ) + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: false) + + rect((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), ..style) + content((0, 0), label) + + anchor("11", (-custom-style.width / 2 - custom-style.length, custom-style.height / 3)) + anchor("12", (-custom-style.width / 2 - custom-style.length, -custom-style.height / 3)) + anchor("21", (custom-style.width / 2 + custom-style.length, custom-style.height / 3)) + anchor("22", (custom-style.width / 2 + custom-style.length, -custom-style.height / 3)) + + wire("11", (rel: (custom-style.length, 0))) + wire("12", (rel: (custom-style.length, 0))) + wire("21", (rel: (-custom-style.length, 0))) + wire("22", (rel: (-custom-style.length, 0))) + } + + component("zweitor", name, node, draw: draw, ..params) +} + +#let gyrator(name, node, scale: 1.0, constant: $R_d$, invert: false, ..params) = { + assert(params.pos().len() == 0, message: "gyrator only supports one node") + + let custom-style = ( + width: 5 * scale, + height: 5 * scale, + length: 0.5 * scale, + ) + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: true) + + anchor("11", (-custom-style.width / 2 - custom-style.length, custom-style.height / 3)) + anchor("12", (-custom-style.width / 2 - custom-style.length, -custom-style.height / 3)) + anchor("21", (custom-style.width / 2 + custom-style.length, custom-style.height / 3)) + anchor("22", (custom-style.width / 2 + custom-style.length, -custom-style.height / 3)) + + wire("11", (rel: (custom-style.length + custom-style.width / 3, 0)), (to: "12", rel: (custom-style.length + custom-style.width / 3, 0)), "12") + wire("21", (rel: (-custom-style.length - custom-style.width / 3, 0)), (to: "22", rel: (-custom-style.length - custom-style.width / 3, 0)), "22") + + arc((-custom-style.width / 6, 0), radius: custom-style.width / 9, start: 90deg, stop: -90deg, anchor: "chord-center") + arc((custom-style.width / 6, 0), radius: custom-style.width / 9, start: 90deg, stop: 270deg, anchor: "chord-center") + + line(name: "dual", (to: "11", rel: (custom-style.length + custom-style.width / 3, 0.1)), (to: "21", rel: (-custom-style.length - custom-style.width / 3, 0.1)), mark: (end: if not invert { ">" }, start: if invert { ">" }, scale: 0.7), stroke: 0.5pt, fill: black) + content((to: "dual.centroid", rel: (0, 0.1)), constant, anchor: "south") + } + + component("gyrator", name, node, draw: draw, ..params) +} + +#let nullor(name, node, scale: 1.0, ..params) = { + assert(params.pos().len() == 0, message: "nullor only supports one node") + + let custom-style = ( + width: 5 * scale, + height: 5 * scale, + length: 0.5 * scale, + ) + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: true) + + anchor("11", (-custom-style.width / 2 - custom-style.length, custom-style.height / 3)) + anchor("12", (-custom-style.width / 2 - custom-style.length, -custom-style.height / 3)) + anchor("21", (custom-style.width / 2 + custom-style.length, custom-style.height / 3)) + anchor("22", (custom-style.width / 2 + custom-style.length, -custom-style.height / 3)) + + nullator("N1", scale: scale, (to: "11", rel: (custom-style.length + custom-style.width / 3, 0)), (to: "12", rel: (custom-style.length + custom-style.width / 3, 0))) + wire("11", "N1.in") + wire("12", "N1.out") + + norator("N2", scale: scale, (to: "21", rel: (-custom-style.length - custom-style.width / 3, 0)), (to: "22", rel: (-custom-style.length - custom-style.width / 3, 0))) + wire("21", (rel: (-custom-style.length - custom-style.width / 3, 0))) + wire("22", (rel: (-custom-style.length - custom-style.width / 3, 0))) + } + + component("nullor", name, node, draw: draw, ..params) +} + +#let transformer(name, node, scale: 1.0, ..params) = { + assert(params.pos().len() == 0, message: "transformer only supports one node") + + let custom-style = ( + width: 5 * scale, + height: 5 * scale, + length: 0.5 * scale, + ) + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: true) + + anchor("11", (-custom-style.width / 2 - custom-style.length, custom-style.height / 3)) + anchor("12", (-custom-style.width / 2 - custom-style.length, -custom-style.height / 3)) + anchor("21", (custom-style.width / 2 + custom-style.length, custom-style.height / 3)) + anchor("22", (custom-style.width / 2 + custom-style.length, -custom-style.height / 3)) + + anchor("C1in", (to: "11", rel: (custom-style.length + custom-style.width / 3, -custom-style.height / 6))) + anchor("C1out", (to: "12", rel: (custom-style.length + custom-style.width / 3, custom-style.height / 6))) + cetz.decorations.coil(line("C1in", "C1out"), amplitude: custom-style.width / 10, segments: 5) + zwire("11", "C1in", ratio: 100%) + zwire("12", "C1out", ratio: 100%) + + anchor("C2in", (to: "21", rel: (-custom-style.length - custom-style.width / 3, -custom-style.height / 6))) + anchor("C2out", (to: "22", rel: (-custom-style.length - custom-style.width / 3, custom-style.height / 6))) + cetz.decorations.coil(line("C2out", "C2in"), amplitude: custom-style.width / 10, segments: 5) + zwire("21", "C2in", ratio: 100%) + zwire("22", "C2out", ratio: 100%) + + line((custom-style.width * 0.025, -custom-style.height / 5), (custom-style.width * 0.025, custom-style.height / 5)) + line((-custom-style.width * 0.025, -custom-style.height / 5), (-custom-style.width * 0.025, custom-style.height / 5)) + + content((0, custom-style.height / 3), anchor: "south", text(size: 0.75em)[$ü : 1$]) + } + + component("transformer", name, node, draw: draw, ..params) +} + +#let controlled-source(name, node, scale: 1.0, left: "VC", right: "VS", i: none, ..params) = { + assert(params.pos().len() == 0, message: "controlled source only supports one node") + + let custom-style = ( + width: 5 * scale, + height: 5 * scale, + length: 0.5 * scale, + ) + + let draw(ctx, position, style) = { + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: true) + + anchor("11", (-custom-style.width / 2 - custom-style.length, custom-style.height / 3)) + anchor("12", (-custom-style.width / 2 - custom-style.length, -custom-style.height / 3)) + anchor("21", (custom-style.width / 2 + custom-style.length, custom-style.height / 3)) + anchor("22", (custom-style.width / 2 + custom-style.length, -custom-style.height / 3)) + + if left == "VC" { + wire("11", (rel: (custom-style.length + custom-style.width / 3, 0))) + wire("12", (to: "12", rel: (custom-style.length + custom-style.width / 3, 0))) + } else if left == "CS" { + wire("11", (rel: (custom-style.length + custom-style.width / 3, 0)), (to: "12", rel: (custom-style.length + custom-style.width / 3, 0)), "12") + } else { + assert(false, message: "unknown value for left") + } + + if right == "CS" { + // isource("source", (rel: (-custom-style.length - custom-style.width / 3, 0), to: "21"), (rel: (-custom-style.length - custom-style.width / 3, 0), to: "22"), scale: scale, dependent: true, i: i) + + anchor("sp", (rel: (-custom-style.length - custom-style.width / 3, 0), to: "21")) + anchor("sn", (rel: (-custom-style.length - custom-style.width / 3, 0), to: "22")) + } else { + assert(false, message: "unknown value for right") + } + + wire("21", "sp") + wire("22", "sn") + + // arc((-custom-style.width / 6, 0), radius: custom-style.width / 9, start: 90deg, stop: -90deg, anchor: "chord-center") + // arc((custom-style.width / 6, 0), radius: custom-style.width / 9, start: 90deg, stop: 270deg, anchor: "chord-center") + } + + component("gyrator", name, node, draw: draw, ..params) +} + +#let ideal-opamp(name, node, invert: false, scale: 1.0, angle: 0deg, ..params) = { + assert(params.pos().len() == 0, message: "opamp supports only one node") + + let custom-style = ( + width: 1.8 * scale, + height: 1.75 * scale, + padding: .28 * scale, + sign-stroke: .55pt, + sign-size: .14 * scale, + sign-delta: .45 * scale, + ) + + let draw(ctx, position, style) = { + rotate(angle) + + interface((-custom-style.width / 2, -custom-style.height / 2), (custom-style.width / 2, custom-style.height / 2), io: true) + + let sgn = if invert { -1 } else { 1 } + anchor("minus", (-custom-style.width / 2, sgn * custom-style.sign-delta)) + anchor("plus", (-custom-style.width / 2, -sgn * custom-style.sign-delta)) + + scope({ + translate((-custom-style.width / 6, 0)) + polygon((0, 0), 3, radius: custom-style.width * 2 / 3, name: "poly") + }) + + // This is horrendous, don't look at it + anchor("ground", ((rel: (0deg, custom-style.width * 2 / 3), to: "poly.default"), 50%, (rel: (-120deg, custom-style.width * 2 / 3), to: "poly.default"))) + anchor("vcc", ((rel: (0deg, custom-style.width * 2 / 3), to: "poly.default"), 50%, (rel: (120deg, custom-style.width * 2 / 3), to: "poly.default"))) + + set-style(stroke: custom-style.sign-stroke) + line((rel: (custom-style.padding - custom-style.sign-size, 0), to: "minus"), (rel: (2 * custom-style.sign-size, 0))) + line((rel: (custom-style.padding - custom-style.sign-size, 0), to: "plus"), (rel: (2 * custom-style.sign-size, 0))) + line((rel: (custom-style.padding, -custom-style.sign-size), to: "plus"), (rel: (0, 2 * custom-style.sign-size))) + + content((custom-style.width / 2, 0), text(size: 12pt * scale)[$ infinity $], anchor: "east", padding: .45 * scale, angle: angle) + } + + component("ideal-opamp", name, node, draw: draw, ..params) +} + +#let ground(name, node, ..params) = { + assert(params.pos().len() == 0, message: "ground supports only one node") + + let custom-style = ( + width: 0.46, + distance: 0.28, + ) + + let draw(ctx, position, style) = { + wire((0, 0), (0, -custom-style.distance)) + let delta = custom-style.width / 2 + + line((-custom-style.width / 2, -custom-style.distance), (custom-style.width / 2, -custom-style.distance)) + + interface((-custom-style.width / 2, custom-style.distance), (custom-style.width / 2, -custom-style.distance)) + anchor("default", (0, 0)) + } + + component("ground", name, node, draw: draw, ..params) +} + +#let offset-line(from, to, dist, func) = { + import cetz: vector + + get-ctx(ctx => { + let (_, from, to) = cetz.coordinate.resolve(ctx, from, to) + + let line = vector.sub(to, from) + + let normal = (-line.at(1), line.at(0)) + + let offset = vector.scale(vector.norm(normal), dist) + + let c = vector.add(from, offset) + let d = vector.add(c, line) + + func(c, d) + }) +} + +#let voltage(from, to, distance: 0, anchor: "east", label) = { + offset-line(from, to, distance, (from, to) => { + line((from, 15%, to), (from, 85%, to), stroke: 0.5pt, mark: (end: (symbol: ">", scale: 0.5, fill: black)), name: "V1") + content("V1.centroid", padding: 0.1, anchor: anchor, label) + }) +} + +#let element-voltage(from, to, distance: 0.5, anchor: "east", label) = { + voltage(from, to, anchor: anchor, distance: if anchor in ("east", "north") { -distance } else { distance }, label) +} +