physicsLab 2.0.4__tar.gz → 2.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {physicslab-2.0.4 → physicslab-2.0.5}/PKG-INFO +1 -1
- physicslab-2.0.5/README.md +121 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/__init__.py +1 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_colorUtils.py +16 -12
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/_circuit_core.py +29 -18
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/otherCircuit.py +12 -7
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/sensor.py +4 -3
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/errors.py +2 -2
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/wires.py +2 -2
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/music.py +3 -1
- physicslab-2.0.5/physicsLab/physicsLab_version.py +38 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/plAR.py +1 -2
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/_threadpool.py +22 -4
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/api.py +10 -4
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/webutils.py +111 -67
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/PKG-INFO +1 -1
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/SOURCES.txt +1 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/setup.py +8 -1
- physicslab-2.0.4/README.md +0 -119
- {physicslab-2.0.4 → physicslab-2.0.5}/LICENSE +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_core.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_tools.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_typing.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_unwind.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_warn.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/celestial/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/celestial/_planetbase.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/celestial/planets.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/chart.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/artificialCircuit.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/basicCircuit.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/logicCircuit.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/electromagnetism/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/electromagnetism/_electromagnetismBase.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/electromagnetism/elements.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/element.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/enums.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/analog_circuit/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/analog_circuit/analog.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/edge_trigger.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/logic.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/super_logic_gate.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/frozen.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/checks.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/decode.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/encode.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/messages.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/specs.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/strings.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/meta.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/midifiles.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/tracks.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/units.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/parser.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/ports.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/mido_connect.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/mido_play.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/mido_ports.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/mido_serve.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/sockets.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/syx.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/tokenizer.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/savTemplate.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/utils.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/__init__.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/_api.py +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/dependency_links.txt +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/requires.txt +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/top_level.txt +0 -0
- {physicslab-2.0.4 → physicslab-2.0.5}/setup.cfg +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# physicsLab
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/GoodenoughPhysicsLab/physicsLab/actions)
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 介绍
|
|
10
|
+
当我们在[物理实验室AR](https://www.turtlesim.com/)纯手动做实验的时候, 往往会遇到一些琐碎、麻烦但又不得不做的事情, 比如:重复的搭建某些电路, 调整元件的位置, 电路内部结构的重复。这些问题都可以通过使用`physicsLab`生成这些电路结构来轻易解决!于是我写了`physicsLab`, 让你能用`Python`在物实做实验。
|
|
11
|
+
|
|
12
|
+
而在参与物实社区的时候, 有时候又会遇到一些手动很麻烦的情况, 我们就可以使用`physicsLab.web`来通过网络api操作物实社区。
|
|
13
|
+
|
|
14
|
+
## 部分方便且惊艳的功能展示
|
|
15
|
+
* 同时支持通过存档的文件名与**存档名**访问存档
|
|
16
|
+
* 不受实验室大小地随意摆放元件 (比如在实验室外悬空的元件)
|
|
17
|
+
* 将midi转换为物实对应的电路
|
|
18
|
+
* 修改物实的实验封面
|
|
19
|
+
* 获取用户的所有头像或实验用过的所有封面
|
|
20
|
+
|
|
21
|
+
更多好用的功能等你来发现
|
|
22
|
+
|
|
23
|
+
## 功能支持
|
|
24
|
+
* 跨平台支持, 只要`Python3.8+`能在该平台上运行并且能够读写文件, 比如`Windows7+`, `Linux`, `MacOS`, `Android`
|
|
25
|
+
* 支持物实**所有**实验类型:电学, 天体物理, 电与磁
|
|
26
|
+
* 支持物实**全部**元件
|
|
27
|
+
* 大多数物实网络api封装的支持 (直接与物实服务器进行交互)
|
|
28
|
+
|
|
29
|
+
## 稳定&兼容
|
|
30
|
+
自版本`2.0.0`及之后, `physicsLab`会逐步增加兼容的考虑与支持。承诺`stable`的api将永远不会移除, 行为几乎不会改变, 除非物实更改了一些api的行为导致physicsLab的底层api的行为被迫发生改变或者更改几乎没有任何影响。
|
|
31
|
+
|
|
32
|
+
* v2.0.0: 承诺`ExperimentOpenedError, ExperimentClosedError, ExperimentExistError, ExperimentNotExistError`会`stable`
|
|
33
|
+
* v2.0.1: 承诺三大实验所有元件的类名会`stable`
|
|
34
|
+
* v2.0.3: 承诺`class Experiment`会`stable`
|
|
35
|
+
* v2.0.4: 承诺`class User, anonymous_login, email_login, token_login`会`stable`
|
|
36
|
+
* Note: v2.0.4 将部分函数抛出TypeError的行为替换为了abort(), 因为我认为这个异常永远不应该被捕获。这是一个极小的breaking change但我认为有利于让physicsLab变得更好, 并且对使用者的影响微乎其微。
|
|
37
|
+
* v2.0.5: 承诺`__version__`会`stable`
|
|
38
|
+
|
|
39
|
+
## 版本发布
|
|
40
|
+
`physicsLab`的版本发布采取快照的方式, `physicsLab`仅会维护`trunk`
|
|
41
|
+
|
|
42
|
+
## 安装教程
|
|
43
|
+
1. 请确保你的电脑有[Python](https://www.python.org)(>=3.8)与[物理实验室AR](https://www.turtlesim.com/)(简称`物实`)(也可以联系物理实验室的开发者[John-Chen](https://gitee.com/civitasjohn))
|
|
44
|
+
|
|
45
|
+
2. 在cmd或shell输入以下载physicsLab:
|
|
46
|
+
```shell
|
|
47
|
+
pip install physicsLab
|
|
48
|
+
```
|
|
49
|
+
在某些非正常情况, 你可能无法顺利使用`pip`, 此时你可以换为该命令来解决该问题:
|
|
50
|
+
```shell
|
|
51
|
+
python -m pip install physicsLab
|
|
52
|
+
```
|
|
53
|
+
> Note: 在`Windows`下你可以输入`py`来使用`Python`, `Linux, MacOS`下可能需要输入`python3`或者`python3.x`(`python`加上你的`Python`版本)来使用`python`
|
|
54
|
+
|
|
55
|
+
3. 有一个并非必需的功能:播放midi(仅在Windows下可用)。你可以输入下面命令的任意一条:
|
|
56
|
+
```shell
|
|
57
|
+
pip install plmidi
|
|
58
|
+
pip install pygame
|
|
59
|
+
```
|
|
60
|
+
点击跳转至[plmidi](https://github.com/GoodenoughPhysicsLab/plmidi)
|
|
61
|
+
|
|
62
|
+
4. 物实存档使用了中文字符, 默认编码为`utf-8`。但在一些非正常情况, 存档的编码可能被改变。虽然`physicsLab`有一定的处理存档编码问题的能力, 但如果还是出现了问题, 请输入该命令:
|
|
63
|
+
```bash
|
|
64
|
+
pip install chardet
|
|
65
|
+
```
|
|
66
|
+
此时`physicsLab`会自动调用`chardet`来处理更加棘手的文件编码问题。
|
|
67
|
+
|
|
68
|
+
5. 如果下载成功, 就可以查看[快速开始](docs/quick_start.md), 开始你的使用了
|
|
69
|
+
> Note: 每次通过`physicsLab`生成了一个新的存档之后, 都需要重新加载物实的本地存档, 即点击`从本地读取`, 再次点击进入对应存档
|
|
70
|
+
|
|
71
|
+
### 新手解惑: 为什么我明明安装了physicsLab, python却告诉我无法找到?
|
|
72
|
+
`pip`安装的包会被放在`site-packages`文件夹下
|
|
73
|
+
这大概率是因为pip安装的包所对应的`site-packages`与你使用的`Python`对应的`site-packages`不一样导致的
|
|
74
|
+
解决方案:找到ide调用的`python`对应的`site-packages`, 然后把`physicsLab`与`physicsLab.egg-info`复制过去
|
|
75
|
+
同时我推荐去学一下`Python`的虚拟环境`venv`, 有效解决此问题
|
|
76
|
+
|
|
77
|
+
如果此方法失效了, 虽然这一定不是这个方法的问题, 但你还可以在python的开头写上这两行代码来解决这个问题:
|
|
78
|
+
```python
|
|
79
|
+
import sys
|
|
80
|
+
sys.path.append("/your/path/of/physicsLab") # 将字符串替换为你想添加的路径
|
|
81
|
+
```
|
|
82
|
+
这个方法很丑陋但很简单好用, 可以帮你快速解决问题, 毕竟能跑起来就很不错了
|
|
83
|
+
其原理是`Python`会在`sys.path`这个列表里面的路径去寻找`Python Package`, 若未找到则会报错。因此该方法的原理就是把`Python`找不到的路径加进去, `Python`就找到了
|
|
84
|
+
注:每次运行的时候的`sys.path`都是临时的, 因此该方法必须让`Python`在每次运行的时候都执行一遍
|
|
85
|
+
|
|
86
|
+
## 特殊说明事项
|
|
87
|
+
* 如果`physicsLab`抛出`AssertionError`, 请**报告 bug** (请在issue中附上最小复现)
|
|
88
|
+
|
|
89
|
+
* 目前`physicsLab`在`windows`上的支持最好, 在其他操作系统上仅支持手动导入/导出存档(默认在`physicsLabSav`文件夹中)。
|
|
90
|
+
|
|
91
|
+
* 在安卓上要使用`physicsLab`的话, 可以通过`qpython`或者`Termux`(推荐) 进行使用
|
|
92
|
+
* 在`qpython v3.2.5`中大大削减了python在文件路径操作方面的权限, 这意味着在qpython上使用physicsLab生成的存档将很难被物实导入, 因为物实没权限访问不了, 但此问题在[qpython v3.2.3](https://github.com/qpython-android/qpython/releases/tag/v3.2.3)中不存在, 推荐下载该版本。
|
|
93
|
+
* `Termux`的话, 需要设置输出存档的路径到`/storage/emulated/0/` (不同安卓设备路径可能不同), 或者手动`mv`一下生成的存档, 这样才能让物实访问对应的存档。
|
|
94
|
+
* 不过由于安卓权限的问题, 用起来肯定没有电脑上方便。
|
|
95
|
+
|
|
96
|
+
* 由于`physicsLab`使用中文注释而且物实的存档也使用了中文, 因此我建议你手动在`Python`代码的第一行添加如下注释:
|
|
97
|
+
```Python
|
|
98
|
+
# -*- coding: utf-8 -*-
|
|
99
|
+
```
|
|
100
|
+
不过由于编码导致问题的情况似乎很少
|
|
101
|
+
|
|
102
|
+
## 优点
|
|
103
|
+
* `physicsLab`拥有优秀的与物实存档交互的能力, 你甚至可以使用程序完成部分工作之后你再继续完成或者让程序在你已完成的实验的基础上继续完成。
|
|
104
|
+
如此灵活的功能使得`physicsLab`即使是在`Python`的`shell`上也能出色的完成工作!
|
|
105
|
+
* `physicsLab`为纯`Python`库, 其c拓展部分(播放midi的部分)被放到了`plmidi`中, 但`plmidi`不是必须需要的。纯`Python`库通常意味着更容易使用, 更少的问题。
|
|
106
|
+
* 封装了物实里的大量元件, 即使是***未解锁的元件***也可以轻易用脚本生成, 甚至一些常用的电路也被封装好了!
|
|
107
|
+
* 物理实验室存档的位置有点隐蔽, 但用该脚本生成实验时, 你无须亲自寻找这个文件在哪里。
|
|
108
|
+
* 外部依赖少
|
|
109
|
+
* 相比于手动做实验, 代码复用率更高, 许多逻辑电路已经被封装, 只需简单的一行调用即可生成。
|
|
110
|
+
* 程序有利于大型实验的创作
|
|
111
|
+
* 改存档做出来的实验往往有十分惊艳的效果!
|
|
112
|
+
|
|
113
|
+
## 其他
|
|
114
|
+
* 一些零七八碎的内容: [other physicsLab](https://gitee.com/script2000/temporary-warehouse/tree/master/other%20physicsLab)
|
|
115
|
+
* 主仓库(github): https://github.com/GoodenoughPhysicsLab/physicsLab
|
|
116
|
+
* 备份仓库(gitee): https://gitee.com/script2000/physicsLab
|
|
117
|
+
|
|
118
|
+
## contribute
|
|
119
|
+
`physicsLab`没有强行要求代码风格, 但需要注意与上下文保持一致
|
|
120
|
+
|
|
121
|
+
你可以从更新文档、bugfix、写[测试代码](./test_pl)开始入手
|
|
@@ -30,8 +30,11 @@ sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding="utf-8")
|
|
|
30
30
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
31
31
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
# Windows 11 默认支持ANSI转义, 因此只有Windows 10及以下才使用Win32 API
|
|
34
|
+
_USE_WIN32_COLOR_API = platform.system() == "Windows" and (sys.getwindowsversion().major, sys.getwindowsversion().minor, sys.getwindowsversion().build) < (10, 0, 22000)
|
|
35
|
+
|
|
36
|
+
if _USE_WIN32_COLOR_API:
|
|
37
|
+
import ctypes
|
|
35
38
|
from ctypes import wintypes
|
|
36
39
|
|
|
37
40
|
class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
|
|
@@ -84,7 +87,8 @@ class _Color:
|
|
|
84
87
|
|
|
85
88
|
@final
|
|
86
89
|
def cprint(self, file):
|
|
87
|
-
if
|
|
90
|
+
if _USE_WIN32_COLOR_API:
|
|
91
|
+
import ctypes
|
|
88
92
|
# 临时更改终端打印字符的属性
|
|
89
93
|
csbi = _CONSOLE_SCREEN_BUFFER_INFO()
|
|
90
94
|
if file is sys.stdout:
|
|
@@ -104,52 +108,52 @@ class _Color:
|
|
|
104
108
|
else:
|
|
105
109
|
assert False
|
|
106
110
|
else:
|
|
107
|
-
print(f"\033[{self.fore}m{self.msg}\033[
|
|
111
|
+
print(f"\033[{self.fore}m{self.msg}\033[0m", end='', file=file)
|
|
108
112
|
|
|
109
113
|
class Black(_Color):
|
|
110
|
-
if
|
|
114
|
+
if _USE_WIN32_COLOR_API:
|
|
111
115
|
fore = 0
|
|
112
116
|
else:
|
|
113
117
|
fore = 30
|
|
114
118
|
|
|
115
119
|
class Red(_Color):
|
|
116
|
-
if
|
|
120
|
+
if _USE_WIN32_COLOR_API:
|
|
117
121
|
fore = 4
|
|
118
122
|
else:
|
|
119
123
|
fore = 31
|
|
120
124
|
|
|
121
125
|
class Green(_Color):
|
|
122
|
-
if
|
|
126
|
+
if _USE_WIN32_COLOR_API:
|
|
123
127
|
fore = 2
|
|
124
128
|
else:
|
|
125
129
|
fore = 32
|
|
126
130
|
|
|
127
131
|
class Yellow(_Color):
|
|
128
|
-
if
|
|
132
|
+
if _USE_WIN32_COLOR_API:
|
|
129
133
|
fore = 6
|
|
130
134
|
else:
|
|
131
135
|
fore = 33
|
|
132
136
|
|
|
133
137
|
class Blue(_Color):
|
|
134
|
-
if
|
|
138
|
+
if _USE_WIN32_COLOR_API:
|
|
135
139
|
fore = 1
|
|
136
140
|
else:
|
|
137
141
|
fore = 34
|
|
138
142
|
|
|
139
143
|
class Magenta(_Color):
|
|
140
|
-
if
|
|
144
|
+
if _USE_WIN32_COLOR_API:
|
|
141
145
|
fore = 5
|
|
142
146
|
else:
|
|
143
147
|
fore = 35
|
|
144
148
|
|
|
145
149
|
class Cyan(_Color):
|
|
146
|
-
if
|
|
150
|
+
if _USE_WIN32_COLOR_API:
|
|
147
151
|
fore = 3
|
|
148
152
|
else:
|
|
149
153
|
fore = 36
|
|
150
154
|
|
|
151
155
|
class White(_Color):
|
|
152
|
-
if
|
|
156
|
+
if _USE_WIN32_COLOR_API:
|
|
153
157
|
fore = 7
|
|
154
158
|
else:
|
|
155
159
|
fore = 37
|
|
@@ -71,10 +71,12 @@ class Wire:
|
|
|
71
71
|
__slots__ = ("Source", "Target", "color")
|
|
72
72
|
|
|
73
73
|
def __init__(self, source_pin: Pin, target_pin: Pin, color: WireColor = WireColor.blue) -> None:
|
|
74
|
-
if not isinstance(source_pin, Pin)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
if not isinstance(source_pin, Pin):
|
|
75
|
+
errors.type_error(f"Parameter source_pin must be of type `Pin`, but got value {source_pin} of type `{type(source_pin).__name__}`")
|
|
76
|
+
if not isinstance(target_pin, Pin):
|
|
77
|
+
errors.type_error(f"Parameter target_pin must be of type `Pin`, but got value {target_pin} of type `{type(target_pin).__name__}`")
|
|
78
|
+
if not isinstance(color, WireColor):
|
|
79
|
+
errors.type_error(f"Parameter color must be of type `WireColor`, but got value {color} of type `{type(color).__name__}`")
|
|
78
80
|
|
|
79
81
|
if source_pin.element_self.experiment is not target_pin.element_self.experiment:
|
|
80
82
|
raise errors.InvalidWireError("can't link wire in two experiment")
|
|
@@ -114,8 +116,10 @@ class Wire:
|
|
|
114
116
|
|
|
115
117
|
def crt_wire(*pins: Pin, color: WireColor = WireColor.blue) -> List[Wire]:
|
|
116
118
|
''' 连接导线 '''
|
|
117
|
-
if not all(isinstance(a_pin, Pin) for a_pin in pins)
|
|
118
|
-
|
|
119
|
+
if not all(isinstance(a_pin, Pin) for a_pin in pins):
|
|
120
|
+
errors.type_error(f"Parameter pins must be of type `tuple[Pin]`")
|
|
121
|
+
if not isinstance(color, WireColor):
|
|
122
|
+
errors.type_error(f"Parameter color must be of type `WireColor`, but got value {color} of type `{type(color).__name__}`")
|
|
119
123
|
if len(pins) <= 1:
|
|
120
124
|
raise ValueError("pins must be more than 1")
|
|
121
125
|
|
|
@@ -134,8 +138,10 @@ def crt_wire(*pins: Pin, color: WireColor = WireColor.blue) -> List[Wire]:
|
|
|
134
138
|
|
|
135
139
|
def del_wire(source_pin: Pin, target_pin: Pin) -> None:
|
|
136
140
|
''' 删除导线'''
|
|
137
|
-
if not isinstance(source_pin, Pin)
|
|
138
|
-
|
|
141
|
+
if not isinstance(source_pin, Pin):
|
|
142
|
+
errors.type_error(f"Parameter source_pin must be of type `Pin`, but got value {source_pin} of type `{type(source_pin).__name__}`")
|
|
143
|
+
if not isinstance(target_pin, Pin):
|
|
144
|
+
errors.type_error(f"Parameter target_pin must be of type `Pin`, but got value {target_pin} of type `{type(target_pin).__name__}`")
|
|
139
145
|
|
|
140
146
|
_expe = get_current_experiment()
|
|
141
147
|
if _expe.experiment_type != ExperimentType.Circuit:
|
|
@@ -217,10 +223,12 @@ class CircuitBase(ElementBase, metaclass=_CircuitMeta):
|
|
|
217
223
|
@final
|
|
218
224
|
def set_rotation(self, x_r: num_type = 0, y_r: num_type = 0, z_r: num_type = 180) -> Self:
|
|
219
225
|
''' 设置元件的角度 '''
|
|
220
|
-
if not isinstance(x_r, (int, float))
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
226
|
+
if not isinstance(x_r, (int, float)):
|
|
227
|
+
errors.type_error(f"Parameter x_r must be of type `int | float`, but got value {x_r} of type `{type(x_r).__name__}`")
|
|
228
|
+
if not isinstance(y_r, (int, float)):
|
|
229
|
+
errors.type_error(f"Parameter y_r must be of type `int | float`, but got value {y_r} of type `{type(y_r).__name__}`")
|
|
230
|
+
if not isinstance(z_r, (int, float)):
|
|
231
|
+
errors.type_error(f"Parameter z_r must be of type `int | float`, but got value {z_r} of type `{type(z_r).__name__}`")
|
|
224
232
|
|
|
225
233
|
x_r, y_r, z_r = round_data(x_r), round_data(y_r), round_data(z_r)
|
|
226
234
|
self.data["Rotation"] = f"{x_r},{z_r},{y_r}"
|
|
@@ -236,11 +244,14 @@ class CircuitBase(ElementBase, metaclass=_CircuitMeta):
|
|
|
236
244
|
) -> Self:
|
|
237
245
|
''' 设置元件的位置
|
|
238
246
|
'''
|
|
239
|
-
if not isinstance(x, (int, float))
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
247
|
+
if not isinstance(x, (int, float)):
|
|
248
|
+
errors.type_error(f"Parameter x must be of type `int | float`, but got value {x} of type `{type(x).__name__}`")
|
|
249
|
+
if not isinstance(y, (int, float)):
|
|
250
|
+
errors.type_error(f"Parameter y must be of type `int | float`, but got value {y} of type `{type(y).__name__}`")
|
|
251
|
+
if not isinstance(z, (int, float)):
|
|
252
|
+
errors.type_error(f"Parameter z must be of type `int | float`, but got value {z} of type `{type(z).__name__}`")
|
|
253
|
+
if not isinstance(elementXYZ, (bool, type(None))):
|
|
254
|
+
errors.type_error(f"Parameter elementXYZ must be of type `Optional[bool]`, but got value {elementXYZ} of type `{type(elementXYZ).__name__}`")
|
|
244
255
|
|
|
245
256
|
x, y, z = round_data(x), round_data(y), round_data(z)
|
|
246
257
|
self._position = _tools.position(x, y, z)
|
|
@@ -294,7 +305,7 @@ class CircuitBase(ElementBase, metaclass=_CircuitMeta):
|
|
|
294
305
|
@param name: 将元件重命名为name
|
|
295
306
|
'''
|
|
296
307
|
if not isinstance(name, str):
|
|
297
|
-
|
|
308
|
+
errors.type_error(f"Parameter name must be of type `str`, but got value {name} of type `{type(name).__name__}`")
|
|
298
309
|
|
|
299
310
|
self.data["Label"] = name
|
|
300
311
|
return self
|
|
@@ -455,13 +455,18 @@ class Simple_Instrument(CircuitBase):
|
|
|
455
455
|
is_ideal: Optional[bool] = None,
|
|
456
456
|
is_pulse: Optional[bool] = None,
|
|
457
457
|
) -> Self:
|
|
458
|
-
if not isinstance(rated_oltage, (int, float, type(None)))
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
458
|
+
if not isinstance(rated_oltage, (int, float, type(None))):
|
|
459
|
+
errors.type_error(f"Parameter rated_oltage must be of type `Optional[int | float]`, but got value {rated_oltage} of type `{type(rated_oltage).__name__}`")
|
|
460
|
+
if not isinstance(volume, (int, float, type(None))):
|
|
461
|
+
errors.type_error(f"Parameter volume must be of type `Optional[int | float]`, but got value {volume} of type `{type(volume).__name__}`")
|
|
462
|
+
if not isinstance(bpm, (int, type(None))):
|
|
463
|
+
errors.type_error(f"Parameter bpm must be of type `Optional[int]`, but got value {bpm} of type `{type(bpm).__name__}`")
|
|
464
|
+
if not isinstance(instrument, (int, type(None))):
|
|
465
|
+
errors.type_error(f"Parameter instrument must be of type `Optional[int]`, but got value {instrument} of type `{type(instrument).__name__}`")
|
|
466
|
+
if not isinstance(is_ideal, (bool, type(None))):
|
|
467
|
+
errors.type_error(f"Parameter is_ideal must be of type `Optional[bool]`, but got value {is_ideal} of type `{type(is_ideal).__name__}`")
|
|
468
|
+
if not isinstance(is_pulse, (bool, type(None))):
|
|
469
|
+
errors.type_error(f"Parameter is_pulse must be of type `Optional[bool]`, but got value {is_pulse} of type `{type(is_pulse).__name__}`")
|
|
465
470
|
|
|
466
471
|
if rated_oltage is not None:
|
|
467
472
|
self.properties["额定电压"] = rated_oltage
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
from physicsLab import errors
|
|
2
3
|
from .._circuit_core import _TwoPinMixIn, CircuitBase, Pin
|
|
3
4
|
from physicsLab._core import _Experiment
|
|
4
5
|
from .logicCircuit import _LogicBase
|
|
@@ -51,7 +52,7 @@ class _MemsBase(CircuitBase):
|
|
|
51
52
|
@final
|
|
52
53
|
def ranges(self, value: num_type) -> num_type:
|
|
53
54
|
if not isinstance(value, (int, float)):
|
|
54
|
-
|
|
55
|
+
errors.type_error(f"ranges must be of type `int | float`, but got value {value} of type `{type(value).__name__}`")
|
|
55
56
|
|
|
56
57
|
self.properties["量程"] = value
|
|
57
58
|
return value
|
|
@@ -69,7 +70,7 @@ class _MemsBase(CircuitBase):
|
|
|
69
70
|
@final
|
|
70
71
|
def shifting(self, value: num_type) -> num_type:
|
|
71
72
|
if not isinstance(value, (int, float)):
|
|
72
|
-
|
|
73
|
+
errors.type_error(f"shifting must be of type `int | float`, but got value {value} of type `{type(value).__name__}`")
|
|
73
74
|
|
|
74
75
|
self.properties["偏移"] = value
|
|
75
76
|
return value
|
|
@@ -87,7 +88,7 @@ class _MemsBase(CircuitBase):
|
|
|
87
88
|
''' 响应系数
|
|
88
89
|
'''
|
|
89
90
|
if not isinstance(value, (int, float)):
|
|
90
|
-
|
|
91
|
+
errors.type_error(f"response_factor must be of type `int | float`, but got value {value} of type `{type(value).__name__}`")
|
|
91
92
|
|
|
92
93
|
self.properties["响应系数"] = value
|
|
93
94
|
return value
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
* 不可恢复的错误:
|
|
8
8
|
当某些错误发生的时候, physicsLab认为程序抽象机已经崩溃, 无法继续运行
|
|
9
|
+
也就是说, 当该错误发生时, 仅表明程序出现了bug, 因此坚决不给用户捕获异常的可能
|
|
9
10
|
因此一旦这些错误发生, physicsLab会调用os.abort来终止程序, 而不是抛出一个异常
|
|
10
|
-
因为抽象机崩溃的情况下, 程序已经无法继续运行, 最需要的是修bug, 因此坚决不给用户捕获异常的可能
|
|
11
11
|
被视为 不可恢复的错误 的有:
|
|
12
12
|
* assertion_error: 断言错误, physicsLab认为其为不可恢复的错误, 因此请不要使用 AssertionError
|
|
13
13
|
* type_error: 断言错误, physicsLab认为其为不可恢复的错误, 因此请不要使用 TypeError
|
|
14
14
|
除此之外, physicsLab自定义了不可恢复错误发生时的打印输出格式
|
|
15
|
-
这些格式比Python
|
|
15
|
+
这些格式比Python自带的traceback可读性更好
|
|
16
16
|
'''
|
|
17
17
|
|
|
18
18
|
import os
|
|
@@ -23,13 +23,13 @@ class UnitPin:
|
|
|
23
23
|
def __getitem__(self, item: slice) -> "UnitPin":
|
|
24
24
|
...
|
|
25
25
|
|
|
26
|
-
def __getitem__(self, item):
|
|
26
|
+
def __getitem__(self, item: Union[int, slice]):
|
|
27
27
|
if isinstance(item, int):
|
|
28
28
|
return self.pins[item]
|
|
29
29
|
elif isinstance(item, slice):
|
|
30
30
|
return UnitPin(self.lib_self, *self.pins[item])
|
|
31
31
|
else:
|
|
32
|
-
|
|
32
|
+
errors.type_error(f"Parameter item must be of type `int | slice`, but got value {item} of type `{type(item).__name__}`")
|
|
33
33
|
|
|
34
34
|
def __iter__(self):
|
|
35
35
|
return iter(self.pins)
|
|
@@ -394,8 +394,10 @@ class Note:
|
|
|
394
394
|
isinstance(instrument, int) and
|
|
395
395
|
isinstance(velocity, (int, float)) and 0 < velocity <= 1 and
|
|
396
396
|
(rising_falling is None or isinstance(rising_falling, bool))
|
|
397
|
-
)
|
|
397
|
+
):
|
|
398
398
|
raise TypeError
|
|
399
|
+
if time <= 0:
|
|
400
|
+
raise ValueError
|
|
399
401
|
|
|
400
402
|
if isinstance(pitch, int):
|
|
401
403
|
if not 0 < pitch <= 128:
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
class _Version:
|
|
4
|
+
def __init__(self, major: int, minor: int, patch: int) -> None:
|
|
5
|
+
if major < 0 \
|
|
6
|
+
or minor < 0 \
|
|
7
|
+
or patch < 0:
|
|
8
|
+
raise ValueError
|
|
9
|
+
|
|
10
|
+
self.major = major
|
|
11
|
+
self.minor = minor
|
|
12
|
+
self.patch = patch
|
|
13
|
+
|
|
14
|
+
def __str__(self) -> str:
|
|
15
|
+
return f"{self.major}.{self.minor}.{self.patch}"
|
|
16
|
+
|
|
17
|
+
def to_tuple(self) -> Tuple[int, int, int]:
|
|
18
|
+
return self.major, self.minor, self.patch
|
|
19
|
+
|
|
20
|
+
def __eq__(self, value: object) -> bool:
|
|
21
|
+
return self.to_tuple() == value
|
|
22
|
+
|
|
23
|
+
def __ne__(self, value: object) -> bool:
|
|
24
|
+
return self.to_tuple() != value
|
|
25
|
+
|
|
26
|
+
def __gt__(self, value: object) -> bool:
|
|
27
|
+
return self.to_tuple() > value
|
|
28
|
+
|
|
29
|
+
def __ge__(self, value: object) -> bool:
|
|
30
|
+
return self.to_tuple() >= value
|
|
31
|
+
|
|
32
|
+
def __lt__(self, value: object) -> bool:
|
|
33
|
+
return self.to_tuple() < value
|
|
34
|
+
|
|
35
|
+
def __le__(self, value: object) -> bool:
|
|
36
|
+
return self.to_tuple() <= value
|
|
37
|
+
|
|
38
|
+
__version__ = _Version(2, 0, 5)
|
|
@@ -4,11 +4,10 @@ import os
|
|
|
4
4
|
import json
|
|
5
5
|
import platform
|
|
6
6
|
|
|
7
|
-
from getpass import getuser
|
|
8
7
|
from typing import Optional, Tuple
|
|
9
8
|
|
|
10
9
|
if platform.system() == "Windows":
|
|
11
|
-
WIN_PLAR_HOME_DIR = f"
|
|
10
|
+
WIN_PLAR_HOME_DIR = f"{os.environ['USERPROFILE']}\\AppData\\LocalLow\\CIVITAS\\Quantum Physics"
|
|
12
11
|
|
|
13
12
|
_plar_version_mem: Optional[Tuple[int, int, int]] = None
|
|
14
13
|
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
import queue
|
|
8
8
|
from threading import Thread
|
|
9
9
|
from enum import Enum, unique
|
|
10
|
+
from physicsLab import errors
|
|
10
11
|
from physicsLab._typing import List, Callable, Self, Any
|
|
11
12
|
|
|
12
13
|
class CanceledError(Exception):
|
|
13
14
|
''' Task have been canceled '''
|
|
14
|
-
|
|
15
|
+
def __repr__(self) -> str:
|
|
16
|
+
return "Task have been canceled"
|
|
15
17
|
|
|
16
18
|
class _EndOfQueue:
|
|
17
19
|
def __new__(cls):
|
|
@@ -53,7 +55,7 @@ class ThreadPool:
|
|
|
53
55
|
''' @param max_workers: 最大线程数
|
|
54
56
|
'''
|
|
55
57
|
if not isinstance(max_workers, int):
|
|
56
|
-
|
|
58
|
+
errors.type_error(f"Parameter `max_workers` must be of type `int`, but got value `{max_workers}` of type `{type(max_workers).__name__}`")
|
|
57
59
|
if max_workers <= 0:
|
|
58
60
|
raise ValueError
|
|
59
61
|
|
|
@@ -61,7 +63,7 @@ class ThreadPool:
|
|
|
61
63
|
self.task_queue = queue.SimpleQueue()
|
|
62
64
|
self.threads: List[Thread] = []
|
|
63
65
|
|
|
64
|
-
def _office(self):
|
|
66
|
+
def _office(self) -> None:
|
|
65
67
|
''' workers work here '''
|
|
66
68
|
while True:
|
|
67
69
|
try:
|
|
@@ -84,7 +86,7 @@ class ThreadPool:
|
|
|
84
86
|
@param func: function to be submitted
|
|
85
87
|
'''
|
|
86
88
|
if not callable(func):
|
|
87
|
-
|
|
89
|
+
errors.type_error(f"Parameter func must be of `callable`, but got value {func} of type `{type(func)}`")
|
|
88
90
|
|
|
89
91
|
task = _Task(func, args, kwargs)
|
|
90
92
|
self.task_queue.put_nowait(task)
|
|
@@ -100,6 +102,22 @@ class ThreadPool:
|
|
|
100
102
|
'''
|
|
101
103
|
self.task_queue.put_nowait(_EndOfQueue)
|
|
102
104
|
|
|
105
|
+
def cancel_all_pending_tasks(self) -> None:
|
|
106
|
+
''' cancel all pending tasks
|
|
107
|
+
'''
|
|
108
|
+
while True:
|
|
109
|
+
try:
|
|
110
|
+
task = self.task_queue.get_nowait()
|
|
111
|
+
except queue.Empty:
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
if task.status == _Status.wait:
|
|
115
|
+
task.status = _Status.cancelled
|
|
116
|
+
else:
|
|
117
|
+
errors.unreachable()
|
|
118
|
+
|
|
119
|
+
self.submit_end()
|
|
120
|
+
|
|
103
121
|
def wait(self) -> None:
|
|
104
122
|
''' blocking until all tasks are done
|
|
105
123
|
'''
|
|
@@ -211,6 +211,8 @@ class User(_User):
|
|
|
211
211
|
return await _async_wrapper(self.unban, target_id, reason)
|
|
212
212
|
|
|
213
213
|
def anonymous_login() -> User:
|
|
214
|
+
''' 匿名登录物实
|
|
215
|
+
'''
|
|
214
216
|
plar_version = plAR.get_plAR_version()
|
|
215
217
|
if plar_version is not None:
|
|
216
218
|
plar_version = int(f"{plar_version[0]}{plar_version[1]}{plar_version[2]}")
|
|
@@ -236,10 +238,12 @@ def anonymous_login() -> User:
|
|
|
236
238
|
return User(_check_response(response))
|
|
237
239
|
|
|
238
240
|
def email_login(email: str, password: str) -> User:
|
|
241
|
+
''' 通过邮箱登录物实
|
|
242
|
+
'''
|
|
239
243
|
if not isinstance(email, str):
|
|
240
|
-
|
|
244
|
+
errors.type_error(f"Parameter email must be of type `str`, but got value {email} of type `{type(email).__name__}`")
|
|
241
245
|
if not isinstance(password, str):
|
|
242
|
-
|
|
246
|
+
errors.type_error(f"Parameter password must be of type `str`, but got value {password} of type `{type(password).__name__}`")
|
|
243
247
|
|
|
244
248
|
plar_version = plAR.get_plAR_version()
|
|
245
249
|
if plar_version is not None:
|
|
@@ -266,10 +270,12 @@ def email_login(email: str, password: str) -> User:
|
|
|
266
270
|
return User(_check_response(response))
|
|
267
271
|
|
|
268
272
|
def token_login(token: str, auth_code: str) -> User:
|
|
273
|
+
''' 通过token登录物实
|
|
274
|
+
'''
|
|
269
275
|
if not isinstance(token, str):
|
|
270
|
-
|
|
276
|
+
errors.type_error(f"Parameter email must be of type `str`, but got value {token} of type `{type(token).__name__}`")
|
|
271
277
|
if not isinstance(auth_code, str):
|
|
272
|
-
|
|
278
|
+
errors.type_error(f"Parameter password must be of type `str`, but got value {auth_code} of type `{type(auth_code).__name__}`")
|
|
273
279
|
|
|
274
280
|
plar_version = plAR.get_plAR_version()
|
|
275
281
|
if plar_version is not None:
|