使用Python绘制电路图

引子

由于最近上网课,很多作业可以以电子版提交,需要绘制的电路图一下子变多了。之前一直采用手绘+circuitlab+multisim的绘图方式,但它们都有一些这样那样的缺点。手绘太累,multisim的排线以及符号功能有点难用,circuitlab应该是一种比较好的解决方案,但依然存在不支持Latex符号的缺陷,致使许多下标符号不能正常显示,且自定义芯片的功能比较差。

后来在逛知乎的时候发现了SchemDraw,只是一个Python下的电路图库,调用matplotlib库进行绘图,相比以上几个工具,其优点主要有下:

  • 较为丰富的图形库(相比circuitlab)
  • 方便的自定义元器件
  • 支持Latex符号
  • 元器件角度旋转调整

当然首先还是要劝退一波:

  • SchemDraw不支持自动布线,所有的线长都需要手动配置,因此只适合抄图而不适合直接作图。
  • SchemDraw适合键盘爱好者,对鼠标依赖者不友好。如果你偏爱word而不是latex/markdown,那么SchemDraw可能并不适合你。

安装

pip安装

1
pip install SchemDraw

基础使用

通用模板:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import SchemDraw
import SchemDraw.elements as e

d = SchemDraw.Drawing()

d.add(e.XXX, ...)
d.add(e.XXX, ...)

d.draw()
d.save('out.svg')

所有的绘制工作都需要通过d.add()接口完成:

1
schemdraw.Drawing.add(elm_def, **kwargs)

首先必须的是元件名称elm_def,既可以是elements包中包含的元器件,也可以是自定义的元器件,文后附有官方文档给出一些元器件。其后常用可选参数如下:

参数说明示例
label=(string)标注,可用top/bot/rgt/lft前缀设置位置botlabel="$U_i$"
d=(string)方向,默认值为上一元器件的方向d='left'
theta=(float)方向角,0表示向右theta=-45
l=(float)长度,一般用d.unit控制l=d.unit/2
xy=(ele.anchor)元器件起始点xy=op.out
anchor=(string)选择元器件的连接锚点anchor='out'
to=(ele.anchor)设置终点,可用tox/toy来设置单坐标tox=op.in1
reverse=(bool)反转元器件reverse=True

在绘图过程中,若有需要调用的元器件,则在添加该元器件时需要用变量记录记录:

1
op = d.add(e.OPAMP, anchor='in2')

推荐采用的绘图顺序是push()/pop()的连续绘图方式,d.push()是储存当前位置,d.pop()则可以回到上一储存位置,下面用一个实例来说明该绘图过程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
d.add(e.DOT_OPEN, label='$a$')
d.add(e.LINE, d='right', l=d.unit * 2)
d.add(e.DOT)
d.push()
d.add(e.LINE, theta=-45, l=d.unit / 2)
d.add(e.DOT)
d.push()
d.add(e.LINE, theta=45, l=d.unit / 4)
d.add(e.CAP, theta=-45, label='$C_1$')
d.add(e.LINE, theta=-135, l=d.unit / 4)
d.add(e.DOT)
d.pop()
d.add(e.LINE, theta=-135, l=d.unit / 4)
d.add(e.RES, theta=-45, label='$R_1$')
d.add(e.LINE, theta=45, l=d.unit / 4)
d.add(e.LINE, theta=-45, l=d.unit / 2)
d.add(e.DOT)
d.push()
d.add(e.LINE, d='down', l=d.unit * 2)
d.add(e.DOT_OPEN, botlabel='$d$')
d.pop()
d.add(e.CAP, theta=-135, label='$C_2$')
d.add(e.RES, theta=-135, label='$R_2$')
d.add(e.DOT)
d.push()
d.add(e.LINE, d='left', l=d.unit * 2)
d.add(e.DOT_OPEN, label='$b$')
d.pop()
d.add(e.LINE, theta=135, l=d.unit / 2)
d.add(e.RES, theta=135, label='$R_3$')
d.add(e.LINE, theta=135, l=d.unit / 2)
d.add(e.DOT)
d.push()
d.add(e.LINE, d='down', l=d.unit * 2)
d.add(e.DOT_OPEN, botlabel='$c$')
d.pop()
d.add(e.LINE, theta=45, l=d.unit / 2)
d.add(e.RES, theta=45, label='$R_4$')
d.add(e.LINE, theta=45, l=d.unit / 2)

在这张电路图中,绘图起点选择了左上角的A点,按照顺时针的顺序进行绘制,当遇到结点时,即使用d.push()记录该节点,随后绘制支路,绘制完一条支路后使用d.pop()返回节点继续绘制其他支路,直至图形完全绘制。

自定义元器件

暂时还没有这方面的例子,等我用到了再来补充QAQ

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
op = d.add(e.OPAMP)
d.add(e.LINE, d='left', xy=op.in1, l=d.unit / 6)
d.add(e.LINE, d='up', l=d.unit / 3)
d.add(e.DOT)
d.push()
d.add(e.LINE, d='right')
d.add(e.LINE, d='down', toy=op.out)
d.add(e.DOT)
d.push()
d.add(e.LINE, d='left', tox=op.out)
d.pop()
d.add(e.LINE, d='right', l=d.unit / 6)
d.add(e.DOT_OPEN, label='$U_o$')
d.pop()
d.add(e.CAP, d='left', label='$C_3$')
d.add(e.LINE, d='down', toy=op.in2)
d.add(e.DOT, botlabel='$a$')
d.push()
d.add(e.RES, d='left', label='$R_1$')
d.add(e.DOT_OPEN, label='$U_i$')
d.pop()
d.add(e.RES, d='right', label='$R_2$')
d.add(e.DOT)
d.push()
d.add(e.LINE, tox=op.in2)
d.pop()
d.add(e.CAP, d='down', label='$C_4$')
d.add(e.GND)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
d.add(e.DOT_OPEN, label='$U_i$')
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.DOT)
d.push()
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.CAP, label='$C$')
d.add(e.DOT)
d.push()
d.add(e.CAP, label='$C$')
d.add(e.DOT)
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.DOT_OPEN, label='$U_o$')
d.pop()
d.add(e.LINE, d='down', l=d.unit / 2)
d.add(e.RES, d='down', botlabel='$R/2$')
d.add(e.LINE, d='left', l=d.unit / 8)
d.add(e.GND)
d.pop()
d.add(e.LINE, d='down', l=d.unit / 2)
d.add(e.RES, d='right', label='R')
d.add(e.DOT)
d.push()
d.add(e.RES, label='R')
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.LINE, d='up', l=d.unit / 2)
d.pop()
d.add(e.CAP, d='down', label='$2C$')
d.add(e.LINE, d='right', l=d.unit / 8)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
d.add(e.DOT_OPEN, label='$U_i$')
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.DOT)
d.push()
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.CAP, label='$C$')
d.add(e.DOT)
d.push()
d.add(e.CAP, label='$C$')
d.add(e.DOT)
d.add(e.LINE, d='right', l=d.unit / 4)
op1 = d.add(e.OPAMP, anchor='in2')
d.pop()
d.add(e.LINE, d='down', l=d.unit / 2)
d.add(e.RES, d='down', botlabel='$R/2$')
d.pop()
d.add(e.LINE, d='down', l=d.unit / 2)
d.add(e.RES, d='right', label='R')
d.add(e.DOT)
d.push()
d.add(e.RES, label='R')
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.LINE, d='up', l=d.unit / 2)
d.pop()
d.add(e.CAP, d='down', label='$2C$')
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.DOT)
d.add(e.LINE, d='right', tox=op1.in1)
op2 = d.add(e.OPAMP, anchor='out', reverse=True)
d.add(e.LINE, d='left', xy=op1.in1, l=d.unit / 4)
d.add(e.LINE, d='up', l=d.unit / 2)
d.add(e.LINE, d='right', l=d.unit * 1.6)
d.add(e.LINE, d='down', toy=op1.out)
d.add(e.DOT)
d.add(e.RES, d='down', label='$R_1$')
d.add(e.LINE, d='down', toy=op2.in2)
d.add(e.DOT)
d.add(e.RES, d='down', label='$R_2$')
d.add(e.GND)

d.add(e.LINE, d='right', xy=op1.out, l=d.unit * 0.6)
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.DOT_OPEN, label='$U_o$')
d.add(e.LINE, d='right', xy=op2.in2, l=d.unit * 0.6)
d.add(e.LINE, d='right', xy=op2.in1, l=d.unit * 0.2)
d.add(e.LINE, d='up', l=d.unit / 2)
d.add(e.LINE, d='left', l=d.unit * 1.2)
d.add(e.LINE, d='down', toy=op2.out)
d.add(e.DOT)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
d.add(e.DOT_OPEN, label='$U_i$')
d.add(e.RES, label='$R_4$')
d.add(e.DOT)
d.push()
d.add(e.LINE, l=d.unit / 4)
op1 = d.add(e.OPAMP, anchor='in1')
d.pop()
d.add(e.LINE, d='up', l=d.unit * 0.8)
d.add(e.DOT)
d.push()
d.add(e.LINE, d='right', l=d.unit / 10)
d.add(e.RES, d='right', label='$R_2$')
d.add(e.DOT, label='b')
d.push()
d.add(e.LINE, d='down', toy=op1.out)
d.add(e.DOT)
d.push()
d.add(e.LINE, d='left', tox=op1.out)
d.pop()
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.DOT)
d.add(e.LINE, d='up', l=d.unit / 6)
d.add(e.CAP, d='right', label='$C$')
d.add(e.LINE, d='down', l=d.unit / 6)
d.add(e.DOT)
d.push()
d.add(e.LINE, d='down', l=d.unit / 6)
d.add(e.RES, d='left', botlabel='$R$')
d.add(e.LINE, d='up', l=d.unit / 6)
d.pop()
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.DOT, label='$d$')
d.push()
d.add(e.RES, d='down', label='$R$')
d.add(e.CAP, d='down', label='$C$')
d.add(e.GND)
d.pop()
d.add(e.LINE, d='right', l=d.unit / 4)
op2 = d.add(e.OPAMP, anchor='in2')
d.pop()
d.add(e.LINE, d='right', l=d.unit / 4)
R = d.add(e.RES, d='right', label='$R_1$')
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.DOT, label='$c$')
d.push()
d.add(e.LINE, d='down', toy=op2.in1)
d.add(e.LINE, d='right', tox=op2.in1)
d.pop()
d.add(e.LINE, d='right', l=d.unit / 10)
d.add(e.RES, d='right', label='$2R_1$')
d.add(e.DOT, rgtlabel='$a$')
d.add(e.LINE, d='down', toy=op2.out)
d.push()
d.add(e.LINE, d='left', tox=op2.out)
d.pop()
d.add(e.LINE, d='right', l=d.unit / 4)
d.add(e.DOT_OPEN, label='$U_o$')
d.pop()
d.add(e.LINE, d='up', l=d.unit / 2)
d.add(e.LINE, d='right', tox=R.start)
d.add(e.RES, label='$R_3$')
d.add(e.LINE, l=d.unit * 1.35)
d.add(e.LINE, d='down', l=d.unit / 2)
d.add(e.LINE, d='left', xy=op1.in2, l=d.unit / 4)
d.add(e.GND)

更多……

元件速查表

◎ 单接口

◎ 双接口

◎ 电源和电动机

◎ 开关

◎ 点线

更多详见官方文档