Skip to content

Commit aba1c99

Browse files
add transistors
1 parent 9c2c6b2 commit aba1c99

File tree

5 files changed

+131
-14
lines changed

5 files changed

+131
-14
lines changed

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"cSpell.words": [
33
"Cbox",
4+
"MOSFET",
5+
"NFET",
6+
"PFET",
47
"polylinegon",
58
"rendec",
69
"schemascii",

format.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ Components are able to accept "flags", which are other punctuation characters an
5151

5252
To include component values, pin numbers, etc, somewhere in the drawing but not touching any wires or other components, you can write component values - these are formatted as the reference designator (same as in circuit) followed by a `:` and the value parameter (which stops at the first whitespace). *I usually put these in a "BOM section" below the circuit itself but you could also put them right next to the component.*
5353

54-
For simple components, this is usually just a value rating, but *without* the units (only the Metric prefix). For more specific components (mostly semiconductor devices) this is usually the part number.
54+
For simple components, this is usually just a value rating, but *without* the units (only the Metric prefix). For more specific components (mostly semiconductor devices) this is usually the part number and/or some string to determine how to draw the component.
5555

5656
Examples:
5757

5858
* `C33:2.2u` -- "2.2 µF"
59-
* `Q1001:TIP102` -- just the part number; printed verbatim
59+
* `Q1001:pnp:TIP102` -- "pnp" or "npn" to determine what kind of transistor, just the part number; printed verbatim
6060
* `L51:0.33` -- this is rewritten to "330 mH" so that it has no decimal point.
6161
* `F3:1500m` -- rewritten: "1.5 A"
6262
* `D7:1N4001` -- again, part number

schemascii/components_render.py

+56-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
from cmath import phase, rect
33
from math import pi
44
from warnings import warn
5-
from .utils import (Cbox, Terminal, BOMData, XML, Side,
5+
from .utils import (Cbox, Terminal, BOMData, XML, Side, arrow_points,
66
polylinegon, id_text, make_text_point,
77
bunch_o_lines, deep_transform, make_plus, make_variable,
8-
sort_counterclockwise, light_arrows)
8+
sort_counterclockwise, light_arrows, sort_for_flags, is_clockwise)
99
from .errors import TerminalsError, BOMError, UnsupportedComponentError
1010

1111
RENDERERS = {}
@@ -291,6 +291,60 @@ def jack(
291291
+ id_text(box, bom_data, terminals, None, sc_text_pt, **options))
292292

293293

294+
@component("Q", "MOSFET", "MOS", "FET")
295+
@n_terminal(3)
296+
@no_ambiguous
297+
def transistor(
298+
box: Cbox,
299+
terminals: list[Terminal],
300+
bom_data: BOMData,
301+
**options):
302+
"Draw a bipolar transistor (PNP/NPN) or FET (NFET/PFET)"
303+
if all(x not in bom_data.data.lower() for x in ("pnp:", "npn:", "nfet:", "pfet:")):
304+
raise BOMError(
305+
f"Need type of transistor for {box.type}{box.id}")
306+
silicon_type, part_num = bom_data.data.split(":")
307+
silicon_type = silicon_type.lower()
308+
bom_data = BOMData(bom_data.type, bom_data.id, part_num)
309+
if 'fet' in silicon_type:
310+
ae, se, ctl = sort_for_flags(terminals, box, "s", "d", "g")
311+
else:
312+
ae, se, ctl = sort_for_flags(terminals, box, "e", "c", "b")
313+
ap, sp = ae.pt, se.pt
314+
mid = (ap + sp) / 2 # TODO: slide this to line up with middle
315+
theta = phase(ap - sp)
316+
backwards = 1 if is_clockwise([ae, se, ctl]) else -1
317+
thetaquarter = theta + (backwards * pi / 2)
318+
out_lines = [
319+
(ap, mid + rect(.8, theta)), # Lead in
320+
(sp, mid - rect(.8, theta)), # Lead out
321+
]
322+
if 'fet' in silicon_type:
323+
arr = mid + rect(.8, theta), mid + rect(.8, theta) + rect(.7, thetaquarter)
324+
if 'nfet' == silicon_type:
325+
arr = arr[1], arr[0]
326+
out_lines.extend([
327+
*arrow_points(*arr),
328+
(mid - rect(.8, theta), mid - rect(.8, theta) + rect(.7, thetaquarter)),
329+
(mid + rect(1, theta) + rect(.7, thetaquarter),
330+
mid - rect(1, theta) + rect(.7, thetaquarter)),
331+
(mid + rect(.5, theta) + rect(1, thetaquarter),
332+
mid - rect(.5, theta) + rect(1, thetaquarter)),
333+
])
334+
else:
335+
arr = mid + rect(.8, theta), mid + rect(.4, theta) + rect(1, thetaquarter)
336+
if 'npn' == silicon_type:
337+
arr = arr[1], arr[0]
338+
out_lines.extend([
339+
*arrow_points(*arr),
340+
(mid - rect(.8, theta), mid - rect(.4, theta) + rect(1, thetaquarter)),
341+
(mid + rect(1, theta) + rect(1, thetaquarter),
342+
mid - rect(1, theta) + rect(1, thetaquarter)),
343+
])
344+
text_pt = make_text_point(ap, sp, **options)
345+
return (id_text(box, bom_data, [ae, se], None, text_pt, **options)
346+
+ bunch_o_lines(out_lines, **options))
347+
294348
# code for drawing stuff
295349
# https://github.com/pfalstad/circuitjs1/tree/master/src/com/lushprojects/circuitjs1/client
296350
# https://github.com/KenKundert/svg_schematic/blob/0abb5dc/svg_schematic.py

schemascii/utils.py

+38-6
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
from math import pi
55
from cmath import phase, rect
66
from typing import Callable
7-
from .metric import format_metric_unit
87
import re
8+
from .metric import format_metric_unit
9+
from .errors import TerminalsError
910

1011
Cbox = namedtuple('Cbox', 'p1 p2 type id')
1112
BOMData = namedtuple('BOMData', 'type id data')
@@ -114,7 +115,8 @@ def mk_tag(*contents, **attrs) -> str:
114115
if isinstance(v, float):
115116
v = fix_number(v)
116117
elif isinstance(v, str):
117-
v = re.sub(r"\d+(\.\d+)", lambda m: fix_number(float(m.group())), v)
118+
v = re.sub(r"\d+(\.\d+)",
119+
lambda m: fix_number(float(m.group())), v)
118120
out += f'{k.removesuffix("_").replace("__", "-")}="{v}" '
119121
out = out.rstrip() + '>' + ''.join(contents)
120122
return out + f'</{tag}>'
@@ -293,7 +295,37 @@ def sort_counterclockwise(terminals: list[Terminal]) -> list[Terminal]:
293295
terminals,
294296
lambda t: t.side)}
295297
return list(chain(
296-
sorted(partitioned[Side.LEFT], key=lambda t: t.pt.imag),
297-
sorted(partitioned[Side.BOTTOM], key=lambda t: t.pt.real),
298-
sorted(partitioned[Side.RIGHT], key=lambda t: -t.pt.imag),
299-
sorted(partitioned[Side.TOP], key=lambda t: -t.pt.real)))
298+
sorted(partitioned.get(Side.LEFT, []), key=lambda t: t.pt.imag),
299+
sorted(partitioned.get(Side.BOTTOM, []), key=lambda t: t.pt.real),
300+
sorted(partitioned.get(Side.RIGHT, []), key=lambda t: -t.pt.imag),
301+
sorted(partitioned.get(Side.TOP, []), key=lambda t: -t.pt.real)))
302+
303+
304+
def is_clockwise(terminals: list[Terminal]) -> bool:
305+
"Return true if the terminals are clockwise order."
306+
sort = sort_counterclockwise(terminals)
307+
for _ in range(len(sort)):
308+
if sort == terminals:
309+
return True
310+
sort = sort[1:] + [sort[0]]
311+
return False
312+
313+
314+
def sort_for_flags(terminals: list[Terminal], box: Cbox, *flags: list[str]) -> list[Terminal]:
315+
"""Sorts out the terminals in the specified order using the flags.
316+
Raises and error if the flags are absent."""
317+
out = ()
318+
for flag in flags:
319+
matching_terminals = list(filter(lambda t: t.flag == flag, terminals))
320+
if len(matching_terminals) > 1:
321+
raise TerminalsError(
322+
f"Multiple terminals with the same flag {flag} "
323+
f"on component {box.type}{box.id}")
324+
if len(matching_terminals) == 0:
325+
raise TerminalsError(
326+
f"Need a terminal with the flag {flag} "
327+
f"on component {box.type}{box.id}")
328+
terminal, = matching_terminals
329+
out = *out, terminal
330+
terminals.remove(terminal)
331+
return out

test_data/test1.txt

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,32 @@
1-
-----LED1+-----
2-
LED1:red
3-
-----VR1###------
4-
VR1:10k
1+
-------sMOSFET1d-----
2+
g MOSFET1:nfet:2N7000
3+
*--------
4+
5+
-------eQ1c-----
6+
b Q1:pnp:TIP102
7+
*------
8+
9+
-------dMOSFET2s-----
10+
g MOSFET2:pfet:2N7001
11+
*--------
12+
13+
-------cQ2e-----
14+
b Q2:npn:TIP103
15+
*------
16+
17+
-------dMOSFET3s-----
18+
g MOSFET3:nfet:2N7000
19+
*--------
20+
21+
-------cQ3e-----
22+
b Q3:pnp:TIP102
23+
*------
24+
25+
-------sMOSFET4d-----
26+
g MOSFET4:pfet:2N7001
27+
*--------
28+
29+
-------eQ4c-----
30+
b Q4:npn:TIP103
31+
*------
32+
!padding=30!

0 commit comments

Comments
 (0)