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.
Files changed (79) hide show
  1. {physicslab-2.0.4 → physicslab-2.0.5}/PKG-INFO +1 -1
  2. physicslab-2.0.5/README.md +121 -0
  3. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/__init__.py +1 -0
  4. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_colorUtils.py +16 -12
  5. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/_circuit_core.py +29 -18
  6. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/otherCircuit.py +12 -7
  7. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/sensor.py +4 -3
  8. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/errors.py +2 -2
  9. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/wires.py +2 -2
  10. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/music.py +3 -1
  11. physicslab-2.0.5/physicsLab/physicsLab_version.py +38 -0
  12. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/plAR.py +1 -2
  13. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/_threadpool.py +22 -4
  14. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/api.py +10 -4
  15. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/webutils.py +111 -67
  16. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/PKG-INFO +1 -1
  17. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/SOURCES.txt +1 -0
  18. {physicslab-2.0.4 → physicslab-2.0.5}/setup.py +8 -1
  19. physicslab-2.0.4/README.md +0 -119
  20. {physicslab-2.0.4 → physicslab-2.0.5}/LICENSE +0 -0
  21. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_core.py +0 -0
  22. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_tools.py +0 -0
  23. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_typing.py +0 -0
  24. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_unwind.py +0 -0
  25. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/_warn.py +0 -0
  26. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/celestial/__init__.py +0 -0
  27. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/celestial/_planetbase.py +0 -0
  28. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/celestial/planets.py +0 -0
  29. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/chart.py +0 -0
  30. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/__init__.py +0 -0
  31. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/__init__.py +0 -0
  32. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/artificialCircuit.py +0 -0
  33. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/basicCircuit.py +0 -0
  34. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/circuit/elements/logicCircuit.py +0 -0
  35. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/electromagnetism/__init__.py +0 -0
  36. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/electromagnetism/_electromagnetismBase.py +0 -0
  37. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/electromagnetism/elements.py +0 -0
  38. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/element.py +0 -0
  39. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/enums.py +0 -0
  40. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/__init__.py +0 -0
  41. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/analog_circuit/__init__.py +0 -0
  42. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/analog_circuit/analog.py +0 -0
  43. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/__init__.py +0 -0
  44. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/edge_trigger.py +0 -0
  45. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/logic.py +0 -0
  46. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/lib/logic_circuit/super_logic_gate.py +0 -0
  47. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/__init__.py +0 -0
  48. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/__init__.py +0 -0
  49. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/frozen.py +0 -0
  50. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/__init__.py +0 -0
  51. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/checks.py +0 -0
  52. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/decode.py +0 -0
  53. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/encode.py +0 -0
  54. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/messages.py +0 -0
  55. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/specs.py +0 -0
  56. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/messages/strings.py +0 -0
  57. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/__init__.py +0 -0
  58. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/meta.py +0 -0
  59. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/midifiles.py +0 -0
  60. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/tracks.py +0 -0
  61. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/midifiles/units.py +0 -0
  62. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/parser.py +0 -0
  63. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/ports.py +0 -0
  64. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/__init__.py +0 -0
  65. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/mido_connect.py +0 -0
  66. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/mido_play.py +0 -0
  67. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/mido_ports.py +0 -0
  68. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/scripts/mido_serve.py +0 -0
  69. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/sockets.py +0 -0
  70. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/syx.py +0 -0
  71. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/music/mido/tokenizer.py +0 -0
  72. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/savTemplate.py +0 -0
  73. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/utils.py +0 -0
  74. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/__init__.py +0 -0
  75. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab/web/_api.py +0 -0
  76. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/dependency_links.txt +0 -0
  77. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/requires.txt +0 -0
  78. {physicslab-2.0.4 → physicslab-2.0.5}/physicsLab.egg-info/top_level.txt +0 -0
  79. {physicslab-2.0.4 → physicslab-2.0.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: physicsLab
3
- Version: 2.0.4
3
+ Version: 2.0.5
4
4
  Summary: Python API for Quantum-Physics App
5
5
  Home-page: https://github.com/GoodenoughPhysicsLab/physicsLab
6
6
  Author: Arendelle
@@ -0,0 +1,121 @@
1
+ # physicsLab
2
+
3
+ ![输入图片说明](./cover.jpg)
4
+
5
+ [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6
+ [![build](https://github.com/gaogaotiantian/viztracer/workflows/build/badge.svg)](https://github.com/GoodenoughPhysicsLab/physicsLab/actions)
7
+ ![support-version](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)
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)开始入手
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  ''' Python API for Physics-Lab-AR '''
3
3
 
4
+ from .physicsLab_version import __version__
4
5
  # 操作实验
5
6
  from .element import search_experiment, Experiment
6
7
  from ._core import (
@@ -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
- import ctypes
34
- if platform.system() == "Windows":
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 platform.system() == "Windows":
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[39m", end='', file=file)
111
+ print(f"\033[{self.fore}m{self.msg}\033[0m", end='', file=file)
108
112
 
109
113
  class Black(_Color):
110
- if platform.system() == "Windows":
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 platform.system() == "Windows":
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 platform.system() == "Windows":
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 platform.system() == "Windows":
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 platform.system() == "Windows":
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 platform.system() == "Windows":
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 platform.system() == "Windows":
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 platform.system() == "Windows":
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
- or not isinstance(target_pin, Pin) \
76
- or not isinstance(color, WireColor):
77
- raise TypeError
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) or not isinstance(color, WireColor):
118
- raise TypeError
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) or not isinstance(target_pin, Pin):
138
- raise TypeError
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
- or not isinstance(y_r, (int, float)) \
222
- or not isinstance(z_r, (int, float)):
223
- raise TypeError
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
- or not isinstance(y, (int, float)) \
241
- or not isinstance(z, (int, float)) \
242
- or not isinstance(elementXYZ, (bool, type(None))):
243
- raise TypeError
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
- raise TypeError
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
- or not isinstance(volume, (int, float, type(None))) \
460
- or not isinstance(bpm, (int, type(None))) \
461
- or not isinstance(instrument, (int, type(None))) \
462
- or not isinstance(is_ideal, (bool, type(None))) \
463
- or not isinstance(is_pulse, (bool, type(None))):
464
- raise TypeError
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
- raise TypeError(f"ranges must be of type `int | float`, but got {type(value).__name__}")
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
- raise TypeError(f"shifting must be of type `int | float`, but got {type(valure).__name__}")
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
- raise TypeError(f"response_factor must be of type `int | float`, but got {type(value).__name__}")
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
- 这些格式比Pythontraceback可读性更好
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
- raise TypeError
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
- ) or time <= 0:
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"C:\\Users\\{getuser()}\\AppData\\LocalLow\\CIVITAS\\Quantum Physics"
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
- pass
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
- raise TypeError
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
- raise TypeError
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
- raise TypeError(f"Parameter email must be of type `str`, but got {type(email).__name__}")
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
- raise TypeError(f"Parameter password must be of type `str`, but got {type(password).__name__}")
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
- raise TypeError(f"Parameter email must be of type `str`, but got {type(token).__name__}")
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
- raise TypeError(f"Parameter password must be of type `str`, but got {type(auth_code).__name__}")
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: