physicsLab 1.4.6__tar.gz → 1.4.8__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-1.4.6 → physicslab-1.4.8}/PKG-INFO +1 -1
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/__init__.py +6 -6
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elementXYZ.py +22 -23
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/_elementBase.py +26 -27
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/wire.py +9 -3
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/element.py +34 -26
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/errors.py +1 -1
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/experiment.py +116 -45
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/music/music.py +104 -74
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/PKG-INFO +1 -1
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/SOURCES.txt +5 -5
- {physicsLab-1.4.6 → physicslab-1.4.8}/setup.py +1 -1
- {physicsLab-1.4.6 → physicslab-1.4.8}/LICENSE +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/README.md +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/_colorUtils.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/_tools.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/celestial/__init__.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/celestial/elementsClass.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/__init__.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/__init__.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/artificialCircuit.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/basicCircuit.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/logicCircuit.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/otherCircuit.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/electromagnetism/__init__.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/electromagnetism/elements.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/elementBase.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/experimentType.py +0 -0
- {physicsLab-1.4.6/physicsLab/unit → physicslab-1.4.8/physicsLab/lib}/__init__.py +0 -0
- {physicsLab-1.4.6/physicsLab/unit → physicslab-1.4.8/physicsLab/lib}/_unionClassHead.py +0 -0
- {physicsLab-1.4.6/physicsLab/unit → physicslab-1.4.8/physicsLab/lib}/logic.py +0 -0
- {physicsLab-1.4.6/physicsLab/unit → physicslab-1.4.8/physicsLab/lib}/wires.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/music/__init__.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/savTemplate.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/typehint.py +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/dependency_links.txt +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/requires.txt +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/top_level.txt +0 -0
- {physicsLab-1.4.6 → physicslab-1.4.8}/setup.cfg +0 -0
|
@@ -17,8 +17,8 @@ from .element import *
|
|
|
17
17
|
# `physicsLab`自定义异常类
|
|
18
18
|
from .errors import *
|
|
19
19
|
# 模块化电路
|
|
20
|
-
from .
|
|
21
|
-
from physicsLab import
|
|
20
|
+
from .lib.wires import *
|
|
21
|
+
from physicsLab import lib
|
|
22
22
|
from physicsLab import music
|
|
23
23
|
|
|
24
24
|
# 检测操作系统
|
|
@@ -29,8 +29,8 @@ if platform.system() == "Windows":
|
|
|
29
29
|
if not os.path.exists(Experiment.FILE_HEAD):
|
|
30
30
|
raise RuntimeError("The folder does not exist, try launching Physics-Lab-AR and try it out")
|
|
31
31
|
else:
|
|
32
|
-
if not os.path.exists(
|
|
33
|
-
os.mkdir(
|
|
32
|
+
if not os.path.exists(Experiment.FILE_HEAD):
|
|
33
|
+
os.mkdir(Experiment.FILE_HEAD)
|
|
34
34
|
|
|
35
35
|
# 获取 Physics-Lab-AR 版本
|
|
36
36
|
def get_Physics_Lab_AR_version() -> Optional[str]:
|
|
@@ -61,7 +61,7 @@ __all__ = [
|
|
|
61
61
|
|
|
62
62
|
# errors.py
|
|
63
63
|
"OpenExperimentError", "WireColorError", "WireNotFoundError", "bitLengthError",
|
|
64
|
-
"experimentExistError", "ExperimentTypeError", "
|
|
64
|
+
"experimentExistError", "ExperimentTypeError", "ElementNotFound", "crtExperimentFailError",
|
|
65
65
|
"ExperimentError", "set_warning_status", "WarningError", "ElementNotExistError",
|
|
66
66
|
|
|
67
67
|
# experiment.py
|
|
@@ -89,7 +89,7 @@ __all__ = [
|
|
|
89
89
|
"Resistance_Law", "Solenoid",
|
|
90
90
|
|
|
91
91
|
# unionElements
|
|
92
|
-
"
|
|
92
|
+
"lib", "music",
|
|
93
93
|
|
|
94
94
|
# wires.py
|
|
95
95
|
"crt_Wires", "del_Wires",
|
|
@@ -24,25 +24,22 @@ def is_elementXYZ() -> bool:
|
|
|
24
24
|
return get_Experiment().is_elementXYZ
|
|
25
25
|
|
|
26
26
|
# 物实坐标系x, y, z单位1
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
_X_UNIT: float = 0.16
|
|
28
|
+
_Y_UNIT: float = 0.08
|
|
29
|
+
_Z_UNIT: float = 0.1
|
|
30
30
|
# big_element坐标修正
|
|
31
|
-
|
|
31
|
+
_Y_AMEND = 0.045
|
|
32
32
|
|
|
33
|
-
# 元件坐标系原点
|
|
34
|
-
_xOrigin, _yOrigin, _zOrigin = 0, 0, 0
|
|
35
|
-
|
|
36
|
-
### end define ###
|
|
37
|
-
|
|
38
|
-
# 将元件坐标系转换为物实支持的坐标系
|
|
39
33
|
def xyzTranslate(x: numType, y: numType, z: numType, is_bigElement: bool = False):
|
|
34
|
+
''' 将元件坐标系转换为物实支持的坐标系 '''
|
|
40
35
|
if get_Experiment().ExperimentType != experimentType.Circuit:
|
|
41
36
|
raise errors.ExperimentTypeError
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
_xOrigin, _yOrigin, _zOrigin = get_OriginPosition()
|
|
39
|
+
|
|
40
|
+
x *= _X_UNIT
|
|
41
|
+
y *= _Y_UNIT
|
|
42
|
+
z *= _Z_UNIT
|
|
46
43
|
# 修改元件坐标系原点
|
|
47
44
|
x += _xOrigin
|
|
48
45
|
y += _yOrigin
|
|
@@ -51,21 +48,23 @@ def xyzTranslate(x: numType, y: numType, z: numType, is_bigElement: bool = False
|
|
|
51
48
|
x, y, z = amend_big_Element(x, y, z)
|
|
52
49
|
return x, y, z
|
|
53
50
|
|
|
54
|
-
# 将物实支持的坐标系转换为元件坐标系
|
|
55
51
|
def translateXYZ(x: numType, y: numType, z: numType, is_bigElement: bool = False):
|
|
52
|
+
''' 将物实支持的坐标系转换为元件坐标系 '''
|
|
56
53
|
if get_Experiment().ExperimentType != experimentType.Circuit:
|
|
57
54
|
raise errors.ExperimentTypeError
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
_xOrigin, _yOrigin, _zOrigin = get_OriginPosition()
|
|
57
|
+
|
|
58
|
+
x /= _X_UNIT
|
|
59
|
+
y /= _Y_UNIT
|
|
60
|
+
z /= _Z_UNIT
|
|
62
61
|
# 修改元件坐标系原点
|
|
63
62
|
x -= _xOrigin
|
|
64
63
|
y -= _yOrigin
|
|
65
64
|
z -= _zOrigin
|
|
66
65
|
# 修改大体积逻辑电路原件的坐标
|
|
67
66
|
if is_bigElement:
|
|
68
|
-
y -=
|
|
67
|
+
y -= _Y_AMEND
|
|
69
68
|
return x, y, z
|
|
70
69
|
|
|
71
70
|
# 设置元件坐标系原点O,输入值为物实坐标系
|
|
@@ -81,11 +80,11 @@ def set_O(x: numType, y: numType, z: numType) -> None:
|
|
|
81
80
|
|
|
82
81
|
# 修正bigElement的坐标
|
|
83
82
|
def amend_big_Element(x: numType, y: numType, z: numType):
|
|
84
|
-
return x, y +
|
|
83
|
+
return x, y + _Y_AMEND, z
|
|
85
84
|
|
|
86
85
|
# 获取坐标原点
|
|
87
86
|
def get_OriginPosition() -> position:
|
|
88
|
-
return
|
|
87
|
+
return get_Experiment().elementXYZ_origin_position
|
|
89
88
|
|
|
90
89
|
# 输入"x" 返回_xUnit
|
|
91
90
|
# 输入"y", "z" 返回_yUnit, _zUnit
|
|
@@ -94,9 +93,9 @@ def get_xyzUnit(*args):
|
|
|
94
93
|
raise TypeError
|
|
95
94
|
|
|
96
95
|
index = {
|
|
97
|
-
"x":
|
|
98
|
-
"y":
|
|
99
|
-
"z":
|
|
96
|
+
"x": _X_UNIT,
|
|
97
|
+
"y": _Y_UNIT,
|
|
98
|
+
"z": _Z_UNIT
|
|
100
99
|
}
|
|
101
100
|
if len(args) == 1:
|
|
102
101
|
return index[args[0]]
|
|
@@ -2,20 +2,18 @@
|
|
|
2
2
|
import inspect
|
|
3
3
|
|
|
4
4
|
from physicsLab import errors
|
|
5
|
+
from physicsLab import _tools
|
|
5
6
|
from physicsLab.circuit import wire
|
|
6
7
|
from physicsLab.elementBase import ElementBase
|
|
7
8
|
import physicsLab.circuit.elementXYZ as _elementXYZ
|
|
8
9
|
|
|
9
10
|
from physicsLab.experimentType import experimentType
|
|
10
11
|
from physicsLab.typehint import Optional, Self, numType
|
|
11
|
-
from physicsLab._tools import roundData, randString
|
|
12
|
+
from physicsLab._tools import roundData, randString
|
|
12
13
|
from physicsLab.experiment import Experiment, stack_Experiment
|
|
13
14
|
|
|
14
15
|
# electricity class's metaClass
|
|
15
16
|
class CircuitMeta(type):
|
|
16
|
-
# element index
|
|
17
|
-
__index = 1
|
|
18
|
-
|
|
19
17
|
def __call__(cls,
|
|
20
18
|
x: numType = 0,
|
|
21
19
|
y: numType = 0,
|
|
@@ -31,6 +29,7 @@ class CircuitMeta(type):
|
|
|
31
29
|
):
|
|
32
30
|
raise TypeError('illegal argument')
|
|
33
31
|
_Expe: Experiment = stack_Experiment.top()
|
|
32
|
+
self.experiment = _Expe
|
|
34
33
|
|
|
35
34
|
if _Expe.ExperimentType != experimentType.Circuit:
|
|
36
35
|
raise errors.ExperimentTypeError
|
|
@@ -40,9 +39,9 @@ class CircuitMeta(type):
|
|
|
40
39
|
cls.is_bigElement = property(lambda self: False) # 2体积元件
|
|
41
40
|
|
|
42
41
|
x, y, z = roundData(x, y, z) # type: ignore -> result type: tuple
|
|
43
|
-
self._position = position(x, y, z) # type: ignore -> define _arguments in metaclass
|
|
42
|
+
self._position = _tools.position(x, y, z) # type: ignore -> define _arguments in metaclass
|
|
44
43
|
# 元件坐标系
|
|
45
|
-
if elementXYZ is True or
|
|
44
|
+
if elementXYZ is True or _elementXYZ.is_elementXYZ() is True and elementXYZ is None:
|
|
46
45
|
x, y, z = _elementXYZ.xyzTranslate(x, y, z)
|
|
47
46
|
self.is_elementXYZ = True
|
|
48
47
|
|
|
@@ -51,7 +50,7 @@ class CircuitMeta(type):
|
|
|
51
50
|
if self.is_elementXYZ and self.is_bigElement:
|
|
52
51
|
x, y, z = _elementXYZ.amend_big_Element(x, y, z)
|
|
53
52
|
|
|
54
|
-
self._arguments["Identifier"] =
|
|
53
|
+
self._arguments["Identifier"] = randString(32)
|
|
55
54
|
# x, z, y 物实采用欧拉坐标系
|
|
56
55
|
self._arguments["Position"] = f"{x},{z},{y}"
|
|
57
56
|
|
|
@@ -61,17 +60,14 @@ class CircuitMeta(type):
|
|
|
61
60
|
else:
|
|
62
61
|
_Expe.elements_Position[self._position] = [self]
|
|
63
62
|
self.set_Rotation()
|
|
64
|
-
|
|
65
|
-
self._index = CircuitMeta.__index
|
|
63
|
+
|
|
66
64
|
_Expe.Elements.append(self)
|
|
67
|
-
|
|
68
|
-
CircuitMeta.__index += 1
|
|
65
|
+
|
|
69
66
|
return self
|
|
70
67
|
|
|
71
68
|
# 所有电学元件的父类
|
|
72
69
|
class CircuitBase(ElementBase, metaclass=CircuitMeta):
|
|
73
70
|
def __repr__(self) -> str:
|
|
74
|
-
#TODO Simple_Instrument 的__repr__方法参数更多
|
|
75
71
|
return f"{self.__class__.__name__}" \
|
|
76
72
|
f"({self._position.x}, {self._position.y}, {self._position.z}, {self.is_elementXYZ})"
|
|
77
73
|
|
|
@@ -82,7 +78,7 @@ class CircuitBase(ElementBase, metaclass=CircuitMeta):
|
|
|
82
78
|
isinstance(yRotation, (int, float)) and
|
|
83
79
|
isinstance(zRotation, (int, float))
|
|
84
80
|
):
|
|
85
|
-
raise
|
|
81
|
+
raise TypeError
|
|
86
82
|
|
|
87
83
|
self._arguments["Rotation"] = f"{roundData(xRotation)},{roundData(zRotation)},{roundData(yRotation)}"
|
|
88
84
|
return self
|
|
@@ -90,26 +86,29 @@ class CircuitBase(ElementBase, metaclass=CircuitMeta):
|
|
|
90
86
|
# 重新设置元件的坐标
|
|
91
87
|
def set_Position(self, x: numType, y: numType, z: numType, elementXYZ: Optional[bool] = None) -> Self:
|
|
92
88
|
if not (isinstance(x, (int, float)) and isinstance(y, (int, float)) and isinstance(z, (int, float))):
|
|
93
|
-
raise
|
|
89
|
+
raise TypeError
|
|
94
90
|
x, y, z = roundData(x, y, z) # type: ignore -> result type: tuple
|
|
95
91
|
|
|
92
|
+
self._position = _tools.position(x, y, z)
|
|
93
|
+
|
|
96
94
|
#元件坐标系
|
|
97
|
-
if elementXYZ is True or
|
|
98
|
-
x, y, z = _elementXYZ.xyzTranslate(x, y, z)
|
|
95
|
+
if elementXYZ is True or _elementXYZ.is_elementXYZ() is True and elementXYZ is None:
|
|
96
|
+
x, y, z = _elementXYZ.xyzTranslate(x, y, z, self.is_bigElement)
|
|
99
97
|
self.is_elementXYZ = True
|
|
100
98
|
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
for _, self_list in stack_Experiment.top().elements_Position.items():
|
|
100
|
+
if self in self_list:
|
|
101
|
+
self_list.remove(self)
|
|
102
|
+
|
|
103
103
|
self._arguments['Position'] = f"{x},{z},{y}" # type: ignore -> define _arguments in metaclass
|
|
104
|
-
stack_Experiment.top().elements_Position[self._position] = self
|
|
105
|
-
return self
|
|
106
104
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
_Expe = stack_Experiment.top()
|
|
106
|
+
if self._position in _Expe.elements_Position.keys():
|
|
107
|
+
_Expe.elements_Position[self._position].append(self)
|
|
108
|
+
else:
|
|
109
|
+
_Expe.elements_Position[self._position] = [self]
|
|
110
|
+
|
|
111
|
+
return self
|
|
113
112
|
|
|
114
113
|
# 获取原件的坐标
|
|
115
114
|
def get_Position(self) -> tuple:
|
|
@@ -117,7 +116,7 @@ class CircuitBase(ElementBase, metaclass=CircuitMeta):
|
|
|
117
116
|
|
|
118
117
|
# 获取元件的index(每创建一个元件,index就加1)
|
|
119
118
|
def get_Index(self) -> int:
|
|
120
|
-
return self.
|
|
119
|
+
return self.experiment.Elements.index(self) + 1
|
|
121
120
|
|
|
122
121
|
# 获取子类的类型(也就是ModelID)
|
|
123
122
|
@property
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from physicsLab import errors
|
|
3
3
|
from physicsLab.experiment import get_Experiment
|
|
4
4
|
from physicsLab.experimentType import experimentType
|
|
5
|
-
from physicsLab.typehint import WireDict, Optional
|
|
5
|
+
from physicsLab.typehint import WireDict, Optional, Callable
|
|
6
6
|
|
|
7
7
|
# 电学元件引脚类, 模电元件引脚无明确的输入输出之分, 因此用这个
|
|
8
8
|
class Pin:
|
|
@@ -28,7 +28,7 @@ class Pin:
|
|
|
28
28
|
def export_str(self) -> str:
|
|
29
29
|
pin_name = self._get_pin_name_of_class()
|
|
30
30
|
if pin_name is None:
|
|
31
|
-
raise
|
|
31
|
+
raise errors.ExperimentError("Pin is not belong to any element")
|
|
32
32
|
return f"e{self.element_self.get_Index()}.{pin_name}"
|
|
33
33
|
|
|
34
34
|
def _get_pin_name_of_class(self) -> Optional[str]:
|
|
@@ -56,6 +56,12 @@ class Wire:
|
|
|
56
56
|
if not isinstance(Source, Pin) or not isinstance(Target, Pin):
|
|
57
57
|
raise TypeError
|
|
58
58
|
|
|
59
|
+
if Source.element_self.experiment is not Target.element_self.experiment:
|
|
60
|
+
raise errors.ExperimentError("can't link wire in two experiment")
|
|
61
|
+
|
|
62
|
+
if Source == Target:
|
|
63
|
+
raise errors.ExperimentError()
|
|
64
|
+
|
|
59
65
|
if color in ("black", "blue", "red", "green", "yellow"):
|
|
60
66
|
color = {"black": "黑", "blue": "蓝", "red": "红", "green": "绿", "yellow": "黄"}[color]
|
|
61
67
|
if color not in ('蓝', '绿', '黄', '红', '紫'):
|
|
@@ -98,7 +104,7 @@ class Wire:
|
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
# 检查函数参数是否是导线
|
|
101
|
-
def _check_typeWire(func:
|
|
107
|
+
def _check_typeWire(func: Callable):
|
|
102
108
|
def result(SourcePin: Pin, TargetPin: Pin, *args, **kwargs) -> None:
|
|
103
109
|
if not (
|
|
104
110
|
isinstance(SourcePin, Pin) and
|
|
@@ -9,76 +9,84 @@ from physicsLab.experiment import stack_Experiment
|
|
|
9
9
|
from physicsLab.typehint import numType, Optional, Union, List
|
|
10
10
|
from physicsLab.circuit.elements._elementBase import CircuitBase
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
def crt_Element(name: str,
|
|
13
|
+
x: numType = 0,
|
|
14
|
+
y: numType = 0,
|
|
15
|
+
z: numType = 0,
|
|
16
|
+
elementXYZ: Optional[bool] = None,
|
|
17
|
+
*args,
|
|
18
|
+
**kwargs
|
|
17
19
|
) -> CircuitBase:
|
|
20
|
+
''' 创建原件,本质上仍然是实例化 '''
|
|
18
21
|
if not (isinstance(name, str)
|
|
19
22
|
and isinstance(x, (int, float))
|
|
20
23
|
and isinstance(y, (int, float))
|
|
21
24
|
and isinstance(z, (int, float))
|
|
22
25
|
):
|
|
23
|
-
raise
|
|
26
|
+
raise TypeError
|
|
24
27
|
|
|
25
28
|
name = name.strip()
|
|
26
|
-
# 元件坐标系
|
|
27
|
-
if elementXYZ is True or (_elementXYZ.is_elementXYZ() is True and elementXYZ is None):
|
|
28
|
-
x, y, z = _elementXYZ.xyzTranslate(x, y, z)
|
|
29
29
|
x, y, z = _tools.roundData(x, y, z) # type: ignore
|
|
30
30
|
if (name == '555 Timer'):
|
|
31
|
-
return elements.NE555(x, y, z)
|
|
31
|
+
return elements.NE555(x, y, z, elementXYZ)
|
|
32
32
|
elif (name == '8bit Input'):
|
|
33
|
-
return elements.eight_bit_Input(x, y, z)
|
|
33
|
+
return elements.eight_bit_Input(x, y, z, elementXYZ)
|
|
34
34
|
elif (name == '8bit Display'):
|
|
35
|
-
return elements.eight_bit_Display(x, y, z)
|
|
35
|
+
return elements.eight_bit_Display(x, y, z, elementXYZ)
|
|
36
36
|
else:
|
|
37
|
-
return eval(f"elements.{name.replace(' ', '_').replace('-', '_')}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def get_Element(
|
|
41
|
-
|
|
37
|
+
return eval(f"elements.{name.replace(' ', '_').replace('-', '_')}"
|
|
38
|
+
f"({x}, {y}, {z}, {elementXYZ}, *{args}, **{kwargs})")
|
|
39
|
+
|
|
40
|
+
def get_Element(x: Optional[numType] = None,
|
|
41
|
+
y: Optional[numType] = None,
|
|
42
|
+
z: Optional[numType] = None,
|
|
43
|
+
*,
|
|
44
|
+
index: Optional[numType] = None,
|
|
45
|
+
**kwargs
|
|
42
46
|
) -> Union[CircuitBase, List[CircuitBase]]:
|
|
47
|
+
''' 获取对应坐标的id '''
|
|
43
48
|
# 通过坐标索引元件
|
|
44
|
-
def
|
|
49
|
+
def position_get(x: numType, y: numType, z: numType):
|
|
45
50
|
if not (
|
|
46
51
|
isinstance(x, (int, float))
|
|
47
52
|
and isinstance(y, (int, float))
|
|
48
53
|
and isinstance(z, (int, float))
|
|
49
54
|
):
|
|
50
|
-
raise TypeError
|
|
55
|
+
raise TypeError
|
|
51
56
|
|
|
52
57
|
position = _tools.roundData(x, y, z)
|
|
53
58
|
if position not in _Expe.elements_Position.keys():
|
|
54
|
-
|
|
59
|
+
if "defualt" in kwargs:
|
|
60
|
+
return kwargs["defualt"]
|
|
61
|
+
raise errors.ElementNotFound(f"{position} do not exist")
|
|
55
62
|
|
|
56
63
|
result: list = _Expe.elements_Position[position]
|
|
57
64
|
return result[0] if len(result) == 1 else result
|
|
58
65
|
|
|
59
66
|
# 通过index(元件生成顺序)索引元件
|
|
60
|
-
def
|
|
67
|
+
def index_get(index: int):
|
|
61
68
|
if not isinstance(index, int):
|
|
62
69
|
raise TypeError
|
|
63
70
|
|
|
64
71
|
if 0 < index <= len(_Expe.Elements):
|
|
65
72
|
return _Expe.Elements[index - 1]
|
|
66
73
|
else:
|
|
67
|
-
|
|
74
|
+
if "defualt" in kwargs:
|
|
75
|
+
return kwargs["defualt"]
|
|
76
|
+
raise errors.ElementNotFound
|
|
68
77
|
|
|
69
78
|
_Expe = stack_Experiment.top()
|
|
70
79
|
if None not in [x, y, z]:
|
|
71
|
-
return
|
|
80
|
+
return position_get(x, y, z)
|
|
72
81
|
elif index is not None:
|
|
73
|
-
return
|
|
82
|
+
return index_get(index)
|
|
74
83
|
else:
|
|
75
84
|
raise TypeError
|
|
76
85
|
|
|
77
|
-
# 删除原件
|
|
78
86
|
def del_Element(
|
|
79
87
|
self: CircuitBase # self是物实三大实验支持的所有元件
|
|
80
88
|
) -> None:
|
|
81
|
-
|
|
89
|
+
''' 删除原件 '''
|
|
82
90
|
if not isinstance(self, CircuitBase):
|
|
83
91
|
raise TypeError
|
|
84
92
|
|
|
@@ -60,7 +60,7 @@ class ExperimentTypeError(Exception):
|
|
|
60
60
|
return "The type of experiment does not match the element"
|
|
61
61
|
|
|
62
62
|
# 用于get_Element 获取元件引用失败
|
|
63
|
-
class
|
|
63
|
+
class ElementNotFound(Exception):
|
|
64
64
|
def __str__(self):
|
|
65
65
|
return "Index out of range"
|
|
66
66
|
|
|
@@ -13,7 +13,6 @@ from .savTemplate import Generate
|
|
|
13
13
|
from .experimentType import experimentType
|
|
14
14
|
from .typehint import Union, Optional, List, Dict, numType, Self
|
|
15
15
|
|
|
16
|
-
# 最新被操作的存档
|
|
17
16
|
class stack_Experiment:
|
|
18
17
|
data: List["Experiment"] = []
|
|
19
18
|
|
|
@@ -40,36 +39,41 @@ class stack_Experiment:
|
|
|
40
39
|
cls.data.pop()
|
|
41
40
|
return res
|
|
42
41
|
|
|
43
|
-
# 获取当前正在操作的存档
|
|
44
42
|
def get_Experiment() -> "Experiment":
|
|
43
|
+
''' 获取当前正在操作的存档 '''
|
|
45
44
|
return stack_Experiment.top()
|
|
46
45
|
|
|
47
|
-
# 实验(存档)类
|
|
48
46
|
class Experiment:
|
|
49
|
-
|
|
47
|
+
''' 实验(存档)类 '''
|
|
50
48
|
if platform.system() == "Windows":
|
|
51
49
|
from getpass import getuser
|
|
52
50
|
FILE_HEAD = f"C:/Users/{getuser()}/AppData/LocalLow/CIVITAS/Quantum Physics/Circuit"
|
|
51
|
+
else:
|
|
52
|
+
_home = os.environ.get('PHYSICSLAB_HOME_PATH')
|
|
53
|
+
if _home is None:
|
|
54
|
+
FILE_HEAD = "physicsLabSav"
|
|
55
|
+
else:
|
|
56
|
+
FILE_HEAD = f"{_home}/physicsLabSav"
|
|
53
57
|
|
|
54
58
|
def __init__(self, sav_name: Optional[str] = None) -> None:
|
|
55
59
|
self.is_open_or_crt: bool = False
|
|
56
60
|
self.is_open: bool = False
|
|
57
61
|
self.is_crt: bool = False
|
|
58
62
|
self.is_read: bool = False
|
|
59
|
-
self.is_elementXYZ: bool = False
|
|
60
63
|
|
|
61
64
|
self.FileName: Optional[str] = None # 存档的文件名
|
|
62
65
|
self.SavPath: Optional[str] = None # 存档的完整路径, 为 f"{experiment.FILE_HEAD}/{self.FileName}"
|
|
63
66
|
# 通过坐标索引元件
|
|
64
67
|
self.elements_Position: Dict[tuple, list] = {} # key: self._position, value: List[self...]
|
|
65
68
|
# 通过index(元件生成顺序)索引元件
|
|
66
|
-
|
|
69
|
+
from .circuit.elements._elementBase import CircuitBase
|
|
70
|
+
self.Elements: List[CircuitBase] = []
|
|
67
71
|
|
|
68
72
|
if sav_name is not None:
|
|
69
73
|
self.open_or_crt(sav_name)
|
|
70
74
|
|
|
71
|
-
# 通过_arguments["Identifier"]获取元件
|
|
72
75
|
def get_element_from_identifier(self, identifier: str):
|
|
76
|
+
''' 通过_arguments["Identifier"]获取元件 '''
|
|
73
77
|
for element in self.Elements:
|
|
74
78
|
if element._arguments["Identifier"] == identifier:
|
|
75
79
|
return element
|
|
@@ -93,6 +97,9 @@ class Experiment:
|
|
|
93
97
|
self.PlSav["Summary"] = savTemplate.Circuit["Summary"]
|
|
94
98
|
|
|
95
99
|
if self.ExperimentType == experimentType.Circuit:
|
|
100
|
+
self.is_elementXYZ: bool = False
|
|
101
|
+
# 元件坐标系的坐标原点
|
|
102
|
+
self.elementXYZ_origin_position: _tools.position = _tools.position(0, 0, 0)
|
|
96
103
|
self.Wires: set = set() # Set[Wire] # 存档对应的导线
|
|
97
104
|
# 存档对应的StatusSave, 存放实验元件,导线(如果是电学实验的话)
|
|
98
105
|
self.StatusSave: dict = {"SimulationSpeed": 1.0, "Elements": Generate, "Wires": Generate}
|
|
@@ -105,8 +112,8 @@ class Experiment:
|
|
|
105
112
|
elif self.ExperimentType == experimentType.Electromagnetism:
|
|
106
113
|
self.StatusSave: dict = {"SimulationSpeed": 1.0, "Elements": []}
|
|
107
114
|
|
|
108
|
-
# 打开一个指定的sav文件 (支持输入本地实验的名字或sav文件名)
|
|
109
115
|
def open(self, sav_name : str) -> Self:
|
|
116
|
+
''' 打开一个指定的sav文件 (支持输入本地实验的名字或sav文件名) '''
|
|
110
117
|
if self.is_open_or_crt:
|
|
111
118
|
raise errors.experimentExistError
|
|
112
119
|
self.is_open_or_crt = True
|
|
@@ -147,6 +154,9 @@ class Experiment:
|
|
|
147
154
|
self.SavPath = f"{Experiment.FILE_HEAD}/{self.FileName}"
|
|
148
155
|
|
|
149
156
|
if self.ExperimentType == experimentType.Circuit:
|
|
157
|
+
self.is_elementXYZ: bool = False
|
|
158
|
+
# 元件坐标系的坐标原点
|
|
159
|
+
self.elementXYZ_origin_position: _tools.position = _tools.position(0, 0, 0)
|
|
150
160
|
self.PlSav: dict = copy.deepcopy(savTemplate.Circuit)
|
|
151
161
|
self.Wires: set = set() # Set[Wire] # 存档对应的导线
|
|
152
162
|
# 存档对应的StatusSave, 存放实验元件,导线(如果是电学实验的话)
|
|
@@ -181,12 +191,15 @@ class Experiment:
|
|
|
181
191
|
|
|
182
192
|
self.entitle(sav_name)
|
|
183
193
|
|
|
184
|
-
# 创建存档,输入为存档名 sav_name: 存档名; experiment_type: 实验类型; force_crt: 不论实验是否已经存在,强制创建
|
|
185
194
|
def crt(self,
|
|
186
195
|
sav_name: str,
|
|
187
196
|
experiment_type: experimentType = experimentType.Circuit,
|
|
188
197
|
force_crt: bool=False
|
|
189
198
|
) -> Self:
|
|
199
|
+
''' 创建存档,输入为存档名 sav_name: 存档名;
|
|
200
|
+
experiment_type: 实验类型;
|
|
201
|
+
force_crt: 不论实验是否已经存在,强制创建
|
|
202
|
+
'''
|
|
190
203
|
if self.is_open_or_crt:
|
|
191
204
|
raise errors.experimentExistError
|
|
192
205
|
self.is_open_or_crt = True
|
|
@@ -208,11 +221,11 @@ class Experiment:
|
|
|
208
221
|
self.__crt(sav_name, experiment_type)
|
|
209
222
|
return self
|
|
210
223
|
|
|
211
|
-
# 先尝试打开实验, 若失败则创建实验
|
|
212
224
|
def open_or_crt(self,
|
|
213
225
|
savName: str,
|
|
214
226
|
experimentType: experimentType = experimentType.Circuit
|
|
215
227
|
) -> Self:
|
|
228
|
+
''' 先尝试打开实验, 若失败则创建实验 '''
|
|
216
229
|
if self.is_open_or_crt:
|
|
217
230
|
raise errors.experimentExistError
|
|
218
231
|
self.is_open_or_crt = True
|
|
@@ -230,8 +243,8 @@ class Experiment:
|
|
|
230
243
|
self.__crt(savName, experimentType)
|
|
231
244
|
return self
|
|
232
245
|
|
|
233
|
-
# 读取实验已有状态
|
|
234
246
|
def read(self) -> Self:
|
|
247
|
+
''' 读取实验已有状态 '''
|
|
235
248
|
if self.SavPath is None: # 是否已.open()或.crt()
|
|
236
249
|
raise TypeError
|
|
237
250
|
if self.is_read:
|
|
@@ -279,34 +292,47 @@ class Experiment:
|
|
|
279
292
|
obj.set_Rotation(r_x, r_y, r_z)
|
|
280
293
|
obj._arguments['Identifier'] = element['Identifier']
|
|
281
294
|
from .circuit.elements.logicCircuit import Logic_Input, eight_bit_Input
|
|
282
|
-
|
|
295
|
+
from .circuit.elements.basicCircuit import Simple_Switch, SPDT_Switch, DPDT_Switch, Air_Switch
|
|
296
|
+
|
|
283
297
|
if isinstance(obj, Logic_Input) and element['Properties'].get('开关') == 1:
|
|
284
298
|
obj.set_highLevel()
|
|
285
|
-
|
|
299
|
+
|
|
286
300
|
elif isinstance(obj, eight_bit_Input):
|
|
287
301
|
obj._arguments['Statistics'] = element['Statistics']
|
|
288
302
|
obj._arguments['Properties']['十进制'] = element['Properties']['十进制']
|
|
289
303
|
|
|
304
|
+
elif isinstance(obj, Simple_Switch) and element["Properties"]["开关"] == 1:
|
|
305
|
+
obj.turn_on_switch()
|
|
306
|
+
|
|
307
|
+
elif isinstance(obj, Air_Switch) and element["Properties"]["开关"] == 1:
|
|
308
|
+
obj.turn_on_switch()
|
|
309
|
+
|
|
310
|
+
elif isinstance(obj, SPDT_Switch) or isinstance(obj, DPDT_Switch):
|
|
311
|
+
if element["Properties"]["开关"] == 1:
|
|
312
|
+
obj.left_turn_on_switch()
|
|
313
|
+
elif element["Properties"]["开关"] == 2:
|
|
314
|
+
obj.right_turn_on_switch()
|
|
315
|
+
|
|
290
316
|
# 导线
|
|
291
317
|
if self.ExperimentType == experimentType.Circuit:
|
|
292
318
|
from .circuit.wire import Wire, Pin
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
319
|
+
for wire_dict in status_sav['Wires']:
|
|
320
|
+
self.Wires.add(
|
|
321
|
+
Wire(
|
|
322
|
+
Pin(self.get_element_from_identifier(wire_dict["Source"]), wire_dict["SourcePin"]),
|
|
323
|
+
Pin(self.get_element_from_identifier(wire_dict["Target"]), wire_dict["TargetPin"]),
|
|
324
|
+
wire_dict["ColorName"][0] # e.g. "蓝"
|
|
325
|
+
)
|
|
298
326
|
)
|
|
299
|
-
for wire_dict in status_sav['Wires']
|
|
300
|
-
]
|
|
301
327
|
|
|
302
328
|
return self
|
|
303
329
|
|
|
304
|
-
# 以物实存档的格式导出实验
|
|
305
330
|
def write(self,
|
|
306
331
|
extra_filepath: Optional[str] = None,
|
|
307
332
|
ln: bool = False,
|
|
308
333
|
no_pop: bool = False
|
|
309
334
|
) -> Self:
|
|
335
|
+
''' 以物实存档的格式导出实验 '''
|
|
310
336
|
def _format_StatusSave(stringJson: str) -> str:
|
|
311
337
|
stringJson = stringJson.replace( # format element json
|
|
312
338
|
"{\\\"ModelID', '\n {\\\"ModelID"
|
|
@@ -331,17 +357,14 @@ class Experiment:
|
|
|
331
357
|
self.PlSav["Experiment"]["CreationDate"] = int(time.time() * 1000)
|
|
332
358
|
self.PlSav["Summary"]["CreationDate"] = int(time.time() * 1000)
|
|
333
359
|
|
|
334
|
-
self.CameraSave["VisionCenter"] =
|
|
335
|
-
|
|
336
|
-
self.CameraSave["TargetRotation"] = \
|
|
337
|
-
f"{self.TargetRotation.x},{self.TargetRotation.z},{self.TargetRotation.y}"
|
|
360
|
+
self.CameraSave["VisionCenter"] = f"{self.VisionCenter.x},{self.VisionCenter.z},{self.VisionCenter.y}"
|
|
361
|
+
self.CameraSave["TargetRotation"] = f"{self.TargetRotation.x},{self.TargetRotation.z},{self.TargetRotation.y}"
|
|
338
362
|
self.PlSav["Experiment"]["CameraSave"] = json.dumps(self.CameraSave)
|
|
339
363
|
|
|
340
364
|
self.StatusSave["Elements"] = [a_element._arguments for a_element in self.Elements]
|
|
341
365
|
if self.ExperimentType == experimentType.Circuit:
|
|
342
366
|
self.StatusSave["Wires"] = [a_wire.release() for a_wire in self.Wires]
|
|
343
|
-
self.PlSav["Experiment"]["StatusSave"] =
|
|
344
|
-
json.dumps(self.StatusSave, ensure_ascii=False, separators=(',', ': '))
|
|
367
|
+
self.PlSav["Experiment"]["StatusSave"] = json.dumps(self.StatusSave, ensure_ascii=False, separators=(',', ': '))
|
|
345
368
|
|
|
346
369
|
context: str = json.dumps(self.PlSav, indent=2, ensure_ascii=False, separators=(',', ': '))
|
|
347
370
|
if ln:
|
|
@@ -375,8 +398,8 @@ class Experiment:
|
|
|
375
398
|
|
|
376
399
|
return self
|
|
377
400
|
|
|
378
|
-
# 删除存档
|
|
379
401
|
def delete(self, warning_status: Optional[bool]=None) -> None:
|
|
402
|
+
''' 删除存档 '''
|
|
380
403
|
if self.SavPath is None:
|
|
381
404
|
raise TypeError
|
|
382
405
|
|
|
@@ -399,12 +422,12 @@ class Experiment:
|
|
|
399
422
|
|
|
400
423
|
stack_Experiment.pop()
|
|
401
424
|
|
|
402
|
-
# 退出实验而不进行任何操作
|
|
403
425
|
def exit(self) -> None:
|
|
426
|
+
''' 退出实验而不进行任何操作 '''
|
|
404
427
|
stack_Experiment.pop()
|
|
405
428
|
|
|
406
|
-
# 对存档名进行重命名
|
|
407
429
|
def entitle(self, sav_name: str) -> Self:
|
|
430
|
+
''' 对存档名进行重命名 '''
|
|
408
431
|
if not isinstance(sav_name, str):
|
|
409
432
|
raise TypeError
|
|
410
433
|
|
|
@@ -413,8 +436,8 @@ class Experiment:
|
|
|
413
436
|
|
|
414
437
|
return self
|
|
415
438
|
|
|
416
|
-
# 使用notepad打开改存档
|
|
417
439
|
def show(self) -> Self:
|
|
440
|
+
''' 使用notepad打开改存档 '''
|
|
418
441
|
if self.SavPath is None:
|
|
419
442
|
raise TypeError
|
|
420
443
|
|
|
@@ -424,15 +447,15 @@ class Experiment:
|
|
|
424
447
|
os.popen(f'notepad {self.SavPath}')
|
|
425
448
|
return self
|
|
426
449
|
|
|
427
|
-
# 生成与发布实验有关的存档内容
|
|
428
450
|
def publish(self, title: Optional[str] = None, introduction: Optional[str] = None) -> Self:
|
|
429
|
-
|
|
451
|
+
''' 生成与发布实验有关的存档内容 '''
|
|
430
452
|
def introduce_Experiment(introduction: Union[str, None]) -> None:
|
|
453
|
+
''' 发布实验时输入实验介绍 '''
|
|
431
454
|
if introduction is not None:
|
|
432
455
|
self.PlSav['Summary']['Description'] = introduction.split('\n')
|
|
433
456
|
|
|
434
|
-
# 发布实验时输入实验标题
|
|
435
457
|
def name_Experiment(title: Union[str, None]) -> None:
|
|
458
|
+
''' 发布实验时输入实验标题 '''
|
|
436
459
|
if title is not None:
|
|
437
460
|
self.PlSav['Summary']['Subject'] = title
|
|
438
461
|
|
|
@@ -441,10 +464,6 @@ class Experiment:
|
|
|
441
464
|
|
|
442
465
|
return self
|
|
443
466
|
|
|
444
|
-
# 设置实验者的视角
|
|
445
|
-
# x, y, z : 实验者观察的坐标
|
|
446
|
-
# distance: 实验者到(x, y, z)的距离
|
|
447
|
-
# rotation: 实验者观察的角度
|
|
448
467
|
def observe(self,
|
|
449
468
|
x: Optional[numType] = None,
|
|
450
469
|
y: Optional[numType] = None,
|
|
@@ -454,6 +473,11 @@ class Experiment:
|
|
|
454
473
|
rotation_y: Optional[numType] = None,
|
|
455
474
|
rotation_z: Optional[numType] = None
|
|
456
475
|
) -> Self:
|
|
476
|
+
''' 设置实验者的视角
|
|
477
|
+
x, y, z : 实验者观察的坐标
|
|
478
|
+
distance: 实验者到(x, y, z)的距离
|
|
479
|
+
rotation: 实验者观察的角度
|
|
480
|
+
'''
|
|
457
481
|
if self.SavPath is None:
|
|
458
482
|
raise TypeError
|
|
459
483
|
|
|
@@ -493,16 +517,16 @@ class Experiment:
|
|
|
493
517
|
|
|
494
518
|
return self
|
|
495
519
|
|
|
496
|
-
# 与物实示波器图表有关的支持
|
|
497
520
|
def graph(self) -> Self:
|
|
521
|
+
''' 与物实示波器图表有关的支持 '''
|
|
498
522
|
if self.SavPath is None:
|
|
499
523
|
raise TypeError
|
|
500
524
|
|
|
501
525
|
pass
|
|
502
526
|
return self
|
|
503
527
|
|
|
504
|
-
# 以physicsLab代码的形式导出实验
|
|
505
528
|
def export(self, output_path: str = "temp.pl.py", sav_name: str = "temp") -> Self:
|
|
529
|
+
''' 以physicsLab代码的形式导出实验 '''
|
|
506
530
|
if self.SavPath is None:
|
|
507
531
|
raise TypeError
|
|
508
532
|
|
|
@@ -519,8 +543,56 @@ class Experiment:
|
|
|
519
543
|
|
|
520
544
|
return self
|
|
521
545
|
|
|
522
|
-
|
|
546
|
+
def merge(self,
|
|
547
|
+
other: "Experiment",
|
|
548
|
+
x: numType = 0,
|
|
549
|
+
y: numType = 0,
|
|
550
|
+
z: numType = 0,
|
|
551
|
+
elementXYZ: Optional[bool] = None
|
|
552
|
+
) -> Self:
|
|
553
|
+
''' 合并另一实验
|
|
554
|
+
x, y, z, elementXYZ为重新设置要合并的实验的坐标系原点在self的坐标系的位置
|
|
555
|
+
'''
|
|
556
|
+
if self.SavPath is None:
|
|
557
|
+
raise TypeError
|
|
558
|
+
if other.SavPath is None:
|
|
559
|
+
raise TypeError
|
|
560
|
+
|
|
561
|
+
if self is other:
|
|
562
|
+
return self
|
|
563
|
+
|
|
564
|
+
identifier_to_element: dict = {}
|
|
565
|
+
|
|
566
|
+
for a_element in other.Elements:
|
|
567
|
+
a_element = copy.deepcopy(a_element, memo={id(a_element.experiment): self})
|
|
568
|
+
e_x, e_y, e_z = a_element.get_Position()
|
|
569
|
+
if self.ExperimentType == experimentType.Circuit:
|
|
570
|
+
from .circuit.elementXYZ import xyzTranslate, translateXYZ
|
|
571
|
+
if elementXYZ and not a_element.is_elementXYZ:
|
|
572
|
+
e_x, e_y, e_z = translateXYZ(e_x, e_y, e_z, a_element.is_bigElement)
|
|
573
|
+
elif not elementXYZ and a_element.is_elementXYZ:
|
|
574
|
+
e_x, e_y, e_z = xyzTranslate(e_x, e_y, e_z, a_element.is_bigElement)
|
|
575
|
+
a_element.set_Position(e_x + x, e_y + y, e_z + z, elementXYZ)
|
|
576
|
+
# set_Position已处理与elements_Position有关的操作
|
|
577
|
+
self.Elements.append(a_element)
|
|
578
|
+
|
|
579
|
+
identifier_to_element[a_element._arguments["Identifier"]] = a_element
|
|
580
|
+
|
|
581
|
+
if self.ExperimentType == experimentType.Circuit and other.ExperimentType == experimentType.Circuit:
|
|
582
|
+
for a_wire in other.Wires:
|
|
583
|
+
a_wire = copy.deepcopy(
|
|
584
|
+
a_wire, memo={
|
|
585
|
+
id(a_wire.Source.element_self):
|
|
586
|
+
identifier_to_element[a_wire.Source.element_self._arguments["Identifier"]],
|
|
587
|
+
id(a_wire.Target.element_self):
|
|
588
|
+
identifier_to_element[a_wire.Target.element_self._arguments["Identifier"]],
|
|
589
|
+
})
|
|
590
|
+
self.Wires.add(a_wire)
|
|
591
|
+
|
|
592
|
+
return self
|
|
593
|
+
|
|
523
594
|
class experiment:
|
|
595
|
+
''' 仅提供通过with操作存档 '''
|
|
524
596
|
def __init__(self,
|
|
525
597
|
sav_name: str, # 实验名(非存档文件名)
|
|
526
598
|
read: bool = False, # 是否读取存档原有状态
|
|
@@ -555,7 +627,6 @@ class experiment:
|
|
|
555
627
|
self.force_crt = force_crt
|
|
556
628
|
self.is_exit = is_exit
|
|
557
629
|
|
|
558
|
-
# 上下文管理器,搭配with使用
|
|
559
630
|
def __enter__(self) -> Experiment:
|
|
560
631
|
if not self.force_crt:
|
|
561
632
|
self._Experiment: Experiment = Experiment().open_or_crt(self.savName, self.experimentType)
|
|
@@ -587,15 +658,15 @@ class experiment:
|
|
|
587
658
|
self._Experiment.delete()
|
|
588
659
|
return
|
|
589
660
|
|
|
590
|
-
# 获取所有物实存档的文件名
|
|
591
661
|
def getAllSav() -> List[str]:
|
|
662
|
+
''' 获取所有物实存档的文件名 '''
|
|
592
663
|
from os import walk
|
|
593
664
|
savs = [i for i in walk(Experiment.FILE_HEAD)][0]
|
|
594
665
|
savs = savs[savs.__len__() - 1]
|
|
595
666
|
return [aSav for aSav in savs if aSav.endswith('sav')]
|
|
596
667
|
|
|
597
|
-
# 打开一个存档, 返回存档对应的dict
|
|
598
668
|
def _open_sav(sav_name) -> Optional[dict]:
|
|
669
|
+
''' 打开一个存档, 返回存档对应的dict '''
|
|
599
670
|
def encode_sav(path: str, encoding: str) -> Optional[dict]:
|
|
600
671
|
try:
|
|
601
672
|
with open(path, encoding=encoding) as f:
|
|
@@ -621,8 +692,8 @@ def _open_sav(sav_name) -> Optional[dict]:
|
|
|
621
692
|
encoding = chardet.detect(f.read())["encoding"]
|
|
622
693
|
return encode_sav(f"{Experiment.FILE_HEAD}/{sav_name}", encoding)
|
|
623
694
|
|
|
624
|
-
# 检测实验是否存在,输入为存档名,若存在则返回存档对应的文件名,若不存在则返回None
|
|
625
695
|
def search_Experiment(sav_name: str) -> Optional[str]:
|
|
696
|
+
''' 检测实验是否存在, 输入为存档名, 若存在则返回存档对应的文件名, 若不存在则返回None'''
|
|
626
697
|
for aSav in getAllSav():
|
|
627
698
|
sav = _open_sav(aSav)
|
|
628
699
|
if sav["InternalName"] == sav_name:
|
|
@@ -9,12 +9,11 @@ from enum import Enum, unique
|
|
|
9
9
|
from physicsLab import errors
|
|
10
10
|
from physicsLab.circuit import elements
|
|
11
11
|
from physicsLab._tools import roundData
|
|
12
|
-
from physicsLab.
|
|
12
|
+
from physicsLab.lib import crt_Wires, D_WaterLamp
|
|
13
13
|
from physicsLab.typehint import Optional, Union, List, Iterator, Dict, Self, numType
|
|
14
14
|
|
|
15
15
|
def _format_velocity(velocity: float) -> float:
|
|
16
16
|
velocity = min(1, velocity)
|
|
17
|
-
velocity = max(0.05, velocity)
|
|
18
17
|
|
|
19
18
|
return velocity
|
|
20
19
|
|
|
@@ -120,6 +119,8 @@ class Midi:
|
|
|
120
119
|
|
|
121
120
|
# 使用pygame播放midi
|
|
122
121
|
def sound_by_pygame() -> bool:
|
|
122
|
+
import os
|
|
123
|
+
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
|
|
123
124
|
try:
|
|
124
125
|
from pygame import mixer, time
|
|
125
126
|
except ImportError:
|
|
@@ -186,31 +187,35 @@ class Midi:
|
|
|
186
187
|
return self
|
|
187
188
|
|
|
188
189
|
# 返回 [Note(...), Chord(...), ...]
|
|
189
|
-
def _get_notes_list(self,
|
|
190
|
-
|
|
190
|
+
def _get_notes_list(self,
|
|
191
|
+
div_time: numType, max_notes: Optional[int],
|
|
192
|
+
is_fix_strange_note: bool = False,
|
|
193
|
+
) -> List[Union["Note", "Chord"]]:
|
|
191
194
|
|
|
195
|
+
res: List[Union[Note, Chord]] = []
|
|
192
196
|
wait_time: int = 0
|
|
193
197
|
len_res: int = 0
|
|
198
|
+
|
|
194
199
|
for msg in self.messages:
|
|
195
|
-
if msg.type == "note_on":
|
|
200
|
+
if msg.type == "note_on":
|
|
201
|
+
velocity: float = _format_velocity(msg.velocity / 127) # 音符的响度
|
|
202
|
+
ins: int = self.channels[msg.channel]
|
|
203
|
+
|
|
204
|
+
if velocity == 0 or (is_fix_strange_note and ins == 0 and velocity >= 0.85):
|
|
205
|
+
if msg.time != 0:
|
|
206
|
+
wait_time += msg.time
|
|
207
|
+
continue
|
|
208
|
+
|
|
196
209
|
len_res += 1
|
|
197
210
|
note_time = round((msg.time + wait_time) / div_time)
|
|
198
211
|
|
|
199
|
-
velocity = _format_velocity(msg.velocity / 127) # 音符的响度
|
|
200
|
-
|
|
201
212
|
if note_time != 0 or len(res) == 0:
|
|
202
213
|
if note_time == 0:
|
|
203
214
|
note_time = 1
|
|
204
|
-
res.append(
|
|
205
|
-
Note(note_time,
|
|
206
|
-
instrument=self.channels[msg.channel],
|
|
207
|
-
pitch=msg.note, velocity=velocity)
|
|
208
|
-
)
|
|
215
|
+
res.append(Note(time=note_time, instrument=ins, pitch=msg.note, velocity=velocity))
|
|
209
216
|
else:
|
|
210
217
|
# res[-1]是`Note`或`Chord`且在赋值之后一定是Chord, 此时Note的time的值不重要(因为和弦的音符是同时播放的)
|
|
211
|
-
res[-1] = res[-1].append(
|
|
212
|
-
Note(time=1, instrument=self.channels[msg.channel], pitch=msg.note, velocity=velocity)
|
|
213
|
-
)
|
|
218
|
+
res[-1] = res[-1].append(Note(time=1, instrument=ins, pitch=msg.note, velocity=velocity))
|
|
214
219
|
wait_time = 0
|
|
215
220
|
elif msg.time != 0:
|
|
216
221
|
wait_time += msg.time
|
|
@@ -220,9 +225,15 @@ class Midi:
|
|
|
220
225
|
|
|
221
226
|
return res
|
|
222
227
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
228
|
+
def to_piece(self,
|
|
229
|
+
div_time: numType = 100,
|
|
230
|
+
max_notes: Optional[int] = 800,
|
|
231
|
+
is_optimize: bool = True, # 是否将多个音符优化为和弦
|
|
232
|
+
is_fix_strange_note: bool = False, # 是否修正一些奇怪的音符
|
|
233
|
+
) -> "Piece":
|
|
234
|
+
''' 转换为Piece类 '''
|
|
235
|
+
return Piece(self._get_notes_list(div_time, max_notes, is_fix_strange_note),
|
|
236
|
+
is_optimize=is_optimize)
|
|
226
237
|
|
|
227
238
|
''' *.pl.py文件:
|
|
228
239
|
pl即为 physicsLab file
|
|
@@ -236,8 +247,8 @@ class Midi:
|
|
|
236
247
|
而且是个Py文件, 大家想要自己修改也是很方便的
|
|
237
248
|
'''
|
|
238
249
|
|
|
239
|
-
# 导出一个 .mido.py 文件
|
|
240
250
|
def write_midopy(self, path: str="temp.mido.py") -> Self:
|
|
251
|
+
''' 导出一个 .mido.py 文件 '''
|
|
241
252
|
if not path.endswith(".mido.py"):
|
|
242
253
|
path += ".mido.py"
|
|
243
254
|
|
|
@@ -295,9 +306,9 @@ class Midi:
|
|
|
295
306
|
|
|
296
307
|
return self
|
|
297
308
|
|
|
298
|
-
# 音符类
|
|
299
309
|
# TODO 增加更多的设置pitch的方法 -> 参考Simple_Instrument
|
|
300
310
|
class Note:
|
|
311
|
+
''' 音符类 '''
|
|
301
312
|
def __init__(self,
|
|
302
313
|
time: int, # 间隔多少时间才播放此Note
|
|
303
314
|
playTime: int = 1, # 音符发出声音的时长 暂时不支持相关机制
|
|
@@ -328,8 +339,8 @@ class Note:
|
|
|
328
339
|
def append(self, other: "Note") -> "Chord":
|
|
329
340
|
return Chord(self, other, time=self.time)
|
|
330
341
|
|
|
331
|
-
# 和弦类
|
|
332
342
|
class Chord:
|
|
343
|
+
''' 和弦类 '''
|
|
333
344
|
def __init__(self, *notes: Note, time: int) -> None:
|
|
334
345
|
if len(notes) < 1 or time < 1:
|
|
335
346
|
raise TypeError
|
|
@@ -382,30 +393,50 @@ class Chord:
|
|
|
382
393
|
x: numType = 0,
|
|
383
394
|
y: numType = 0,
|
|
384
395
|
z: numType = 0,
|
|
385
|
-
elementXYZ: Optional[bool] = None
|
|
386
|
-
|
|
396
|
+
elementXYZ: Optional[bool] = None,
|
|
397
|
+
is_optimize: bool = True,
|
|
398
|
+
) -> elements.Simple_Instrument:
|
|
387
399
|
# 元件坐标系,如果输入坐标不是元件坐标系就强转为元件坐标系
|
|
388
400
|
if not (elementXYZ is True or (_elementXYZ.is_elementXYZ() is True and elementXYZ is None)):
|
|
389
401
|
x, y, z = _elementXYZ.translateXYZ(x, y, z)
|
|
390
402
|
x, y, z = roundData(x, y, z) # type: ignore -> result type: tuple
|
|
391
403
|
|
|
392
|
-
first_ins = None # 第一个音符
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
+
first_ins: Optional[elements.Simple_Instrument] = None # 第一个音符
|
|
405
|
+
if is_optimize:
|
|
406
|
+
for delta_z, ins in enumerate(self.ins_notes):
|
|
407
|
+
notes: List[Note] = self.ins_notes[ins]
|
|
408
|
+
temp: elements.Simple_Instrument = elements.Simple_Instrument(
|
|
409
|
+
x, y, z + delta_z, elementXYZ=True,instrument=ins,
|
|
410
|
+
pitch=notes[0].pitch, is_ideal_model=True, velocity=self._get_velocity(notes, is_average=True)
|
|
411
|
+
).set_Rotation(0, 0, 0)
|
|
412
|
+
|
|
413
|
+
if first_ins is None:
|
|
414
|
+
first_ins = temp
|
|
415
|
+
else:
|
|
416
|
+
temp.i - first_ins.i
|
|
417
|
+
temp.o - first_ins.o
|
|
404
418
|
|
|
405
|
-
|
|
406
|
-
|
|
419
|
+
for a_note in notes:
|
|
420
|
+
temp.add_note(a_note.pitch)
|
|
421
|
+
else:
|
|
422
|
+
delta_z = 0
|
|
423
|
+
for ins, notes in self.ins_notes.items():
|
|
424
|
+
for a_note in notes:
|
|
425
|
+
temp = elements.Simple_Instrument(
|
|
426
|
+
x, y, z + delta_z, elementXYZ=True,instrument=ins,
|
|
427
|
+
pitch=a_note.pitch, is_ideal_model=True, velocity=a_note.velocity
|
|
428
|
+
).set_Rotation(0, 0, 0)
|
|
429
|
+
|
|
430
|
+
if first_ins is None:
|
|
431
|
+
first_ins = temp
|
|
432
|
+
else:
|
|
433
|
+
temp.i - first_ins.i
|
|
434
|
+
temp.o - first_ins.o
|
|
407
435
|
|
|
408
|
-
|
|
436
|
+
delta_z += 1
|
|
437
|
+
|
|
438
|
+
assert first_ins is not None
|
|
439
|
+
return first_ins
|
|
409
440
|
|
|
410
441
|
# 循环类,用于创建一段循环的音乐片段
|
|
411
442
|
# TODO: 完善Loop存储的数据结构
|
|
@@ -435,38 +466,36 @@ class Loop:
|
|
|
435
466
|
def __next__(self):
|
|
436
467
|
pass
|
|
437
468
|
|
|
438
|
-
|
|
469
|
+
class Rest_symbol:
|
|
470
|
+
''' 休止符 '''
|
|
471
|
+
__singleton: Optional[Self] = None
|
|
472
|
+
def __new__(cls) -> Self:
|
|
473
|
+
if cls.__singleton is None:
|
|
474
|
+
cls.__singleton = super().__new__(cls)
|
|
475
|
+
return cls.__singleton
|
|
476
|
+
|
|
439
477
|
class Piece:
|
|
478
|
+
''' 乐曲类 '''
|
|
440
479
|
def __init__(self,
|
|
441
480
|
notes: Optional[List[Union[Note, Chord]]] = None, # TODO: support Loop
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
volume: float = 1.0 # 音量/响度
|
|
447
|
-
) -> None:
|
|
448
|
-
if not (
|
|
449
|
-
isinstance(instrument, int) and
|
|
450
|
-
isinstance(pitch, int) and
|
|
451
|
-
isinstance(bpm, int) and
|
|
452
|
-
0 < volume <= 1
|
|
453
|
-
) or (
|
|
454
|
-
( not isinstance(notes, (list, Loop))
|
|
481
|
+
is_optimize: bool = True, # 是否将多个音符优化为和弦
|
|
482
|
+
) -> None:
|
|
483
|
+
if (
|
|
484
|
+
( not isinstance(notes, list)
|
|
455
485
|
or not all(isinstance(val, (Note, Chord)) for val in notes) )
|
|
456
486
|
and notes is not None
|
|
457
487
|
):
|
|
458
488
|
raise TypeError
|
|
459
489
|
|
|
460
|
-
|
|
461
|
-
notes = []
|
|
490
|
+
self.is_optimize = is_optimize
|
|
462
491
|
|
|
463
|
-
self.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
492
|
+
# self.notes会将Note与Chord中用time表示的休止符展开为Rest_symbol
|
|
493
|
+
if notes is None:
|
|
494
|
+
self.notes = []
|
|
495
|
+
else:
|
|
496
|
+
self.notes: List[Union[Note, Chord, Rest_symbol]] = []
|
|
497
|
+
for a_note in notes:
|
|
498
|
+
self.append(a_note)
|
|
470
499
|
|
|
471
500
|
# 向Piece类添加数据成员
|
|
472
501
|
def append(self, other: Union[Note, Chord]) -> Self:
|
|
@@ -474,7 +503,7 @@ class Piece:
|
|
|
474
503
|
raise TypeError
|
|
475
504
|
|
|
476
505
|
while other.time > 1:
|
|
477
|
-
self.notes.append(
|
|
506
|
+
self.notes.append(Rest_symbol())
|
|
478
507
|
other.time -= 1
|
|
479
508
|
self.notes.append(other)
|
|
480
509
|
return self
|
|
@@ -519,7 +548,7 @@ class Piece:
|
|
|
519
548
|
# 500_000 / 100, 500_000是Midi.tempo的默认数字,100是self.bpm的默认数字
|
|
520
549
|
track.append(mido.MetaMessage("set_tempo", tempo=self.bpm * 5000, time=0))
|
|
521
550
|
for a_note in self.notes:
|
|
522
|
-
if a_note
|
|
551
|
+
if isinstance(a_note, Rest_symbol):
|
|
523
552
|
none_counter += 1
|
|
524
553
|
elif isinstance(a_note, Chord):
|
|
525
554
|
for note_list in a_note.ins_notes.values():
|
|
@@ -545,7 +574,7 @@ class Piece:
|
|
|
545
574
|
|
|
546
575
|
# 将Piece转换为物实对应的电路
|
|
547
576
|
def release(self, x: numType = 0, y: numType = 0, z: numType = 0, elementXYZ = None) -> "Player":
|
|
548
|
-
return Player(self, x, y, z, elementXYZ)
|
|
577
|
+
return Player(self, x, y, z, elementXYZ, self.is_optimize)
|
|
549
578
|
|
|
550
579
|
# Piece中所有Notes与Chord的数量
|
|
551
580
|
def count_notes(self) -> int:
|
|
@@ -558,7 +587,7 @@ class Piece:
|
|
|
558
587
|
def __len__(self) -> int:
|
|
559
588
|
return len(self.notes)
|
|
560
589
|
|
|
561
|
-
def __getitem__(self, item: int) ->
|
|
590
|
+
def __getitem__(self, item: int) -> Union[Note, Chord, Rest_symbol]:
|
|
562
591
|
if not isinstance(item, int):
|
|
563
592
|
raise TypeError
|
|
564
593
|
return self.notes[item]
|
|
@@ -585,15 +614,16 @@ class Player:
|
|
|
585
614
|
x: numType = 0,
|
|
586
615
|
y: numType = 0,
|
|
587
616
|
z: numType = 0,
|
|
588
|
-
elementXYZ = None
|
|
589
|
-
|
|
617
|
+
elementXYZ = None,
|
|
618
|
+
is_optimize: bool = True,
|
|
619
|
+
) -> None:
|
|
590
620
|
from physicsLab.element import count_Elements
|
|
591
621
|
count_elements_start: int = count_Elements()
|
|
592
622
|
|
|
593
623
|
if not (
|
|
594
|
-
isinstance(x, (int, float))
|
|
595
|
-
isinstance(y, (int, float))
|
|
596
|
-
isinstance(z, (int, float))
|
|
624
|
+
isinstance(x, (int, float)) and
|
|
625
|
+
isinstance(y, (int, float)) and
|
|
626
|
+
isinstance(z, (int, float)) and
|
|
597
627
|
isinstance(musicArray, Piece)
|
|
598
628
|
):
|
|
599
629
|
raise TypeError
|
|
@@ -602,9 +632,9 @@ class Player:
|
|
|
602
632
|
x, y, z = _elementXYZ.translateXYZ(x, y, z)
|
|
603
633
|
|
|
604
634
|
# 给乐器增加休止符
|
|
605
|
-
while musicArray.notes[-1]
|
|
635
|
+
while isinstance(musicArray.notes[-1], Rest_symbol):
|
|
606
636
|
musicArray.notes.pop()
|
|
607
|
-
while musicArray.notes[0]
|
|
637
|
+
while isinstance(musicArray.notes[0], Rest_symbol):
|
|
608
638
|
musicArray.notes.pop(0)
|
|
609
639
|
|
|
610
640
|
len_musicArray: int = len(musicArray)
|
|
@@ -659,7 +689,7 @@ class Player:
|
|
|
659
689
|
# main
|
|
660
690
|
xcor, ycor = -1, 0
|
|
661
691
|
for a_note in musicArray:
|
|
662
|
-
if a_note
|
|
692
|
+
if isinstance(a_note, Rest_symbol):
|
|
663
693
|
xcor += 1
|
|
664
694
|
if xcor == side:
|
|
665
695
|
xcor = 0
|
|
@@ -673,7 +703,7 @@ class Player:
|
|
|
673
703
|
xcor = 0
|
|
674
704
|
ycor += 2
|
|
675
705
|
if isinstance(a_note, Chord):
|
|
676
|
-
ins = a_note.release(1 + x + xcor, 4 + y + ycor, z, elementXYZ=True)
|
|
706
|
+
ins = a_note.release(1 + x + xcor, 4 + y + ycor, z, elementXYZ=True, is_optimize=is_optimize)
|
|
677
707
|
elif isinstance(a_note, Note):
|
|
678
708
|
ins = elements.Simple_Instrument(
|
|
679
709
|
1 + x + xcor, 4 + y + ycor, z, pitch=a_note.pitch,
|
|
@@ -29,9 +29,9 @@ physicsLab/circuit/elements/logicCircuit.py
|
|
|
29
29
|
physicsLab/circuit/elements/otherCircuit.py
|
|
30
30
|
physicsLab/electromagnetism/__init__.py
|
|
31
31
|
physicsLab/electromagnetism/elements.py
|
|
32
|
+
physicsLab/lib/__init__.py
|
|
33
|
+
physicsLab/lib/_unionClassHead.py
|
|
34
|
+
physicsLab/lib/logic.py
|
|
35
|
+
physicsLab/lib/wires.py
|
|
32
36
|
physicsLab/music/__init__.py
|
|
33
|
-
physicsLab/music/music.py
|
|
34
|
-
physicsLab/unit/__init__.py
|
|
35
|
-
physicsLab/unit/_unionClassHead.py
|
|
36
|
-
physicsLab/unit/logic.py
|
|
37
|
-
physicsLab/unit/wires.py
|
|
37
|
+
physicsLab/music/music.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|