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.
Files changed (39) hide show
  1. {physicsLab-1.4.6 → physicslab-1.4.8}/PKG-INFO +1 -1
  2. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/__init__.py +6 -6
  3. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elementXYZ.py +22 -23
  4. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/_elementBase.py +26 -27
  5. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/wire.py +9 -3
  6. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/element.py +34 -26
  7. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/errors.py +1 -1
  8. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/experiment.py +116 -45
  9. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/music/music.py +104 -74
  10. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/PKG-INFO +1 -1
  11. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/SOURCES.txt +5 -5
  12. {physicsLab-1.4.6 → physicslab-1.4.8}/setup.py +1 -1
  13. {physicsLab-1.4.6 → physicslab-1.4.8}/LICENSE +0 -0
  14. {physicsLab-1.4.6 → physicslab-1.4.8}/README.md +0 -0
  15. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/_colorUtils.py +0 -0
  16. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/_tools.py +0 -0
  17. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/celestial/__init__.py +0 -0
  18. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/celestial/elementsClass.py +0 -0
  19. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/__init__.py +0 -0
  20. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/__init__.py +0 -0
  21. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/artificialCircuit.py +0 -0
  22. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/basicCircuit.py +0 -0
  23. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/logicCircuit.py +0 -0
  24. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/circuit/elements/otherCircuit.py +0 -0
  25. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/electromagnetism/__init__.py +0 -0
  26. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/electromagnetism/elements.py +0 -0
  27. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/elementBase.py +0 -0
  28. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/experimentType.py +0 -0
  29. {physicsLab-1.4.6/physicsLab/unit → physicslab-1.4.8/physicsLab/lib}/__init__.py +0 -0
  30. {physicsLab-1.4.6/physicsLab/unit → physicslab-1.4.8/physicsLab/lib}/_unionClassHead.py +0 -0
  31. {physicsLab-1.4.6/physicsLab/unit → physicslab-1.4.8/physicsLab/lib}/logic.py +0 -0
  32. {physicsLab-1.4.6/physicsLab/unit → physicslab-1.4.8/physicsLab/lib}/wires.py +0 -0
  33. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/music/__init__.py +0 -0
  34. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/savTemplate.py +0 -0
  35. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab/typehint.py +0 -0
  36. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/dependency_links.txt +0 -0
  37. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/requires.txt +0 -0
  38. {physicsLab-1.4.6 → physicslab-1.4.8}/physicsLab.egg-info/top_level.txt +0 -0
  39. {physicsLab-1.4.6 → physicslab-1.4.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: physicsLab
3
- Version: 1.4.6
3
+ Version: 1.4.8
4
4
  Summary: Python API for Physics-Lab-AR
5
5
  Home-page: https://gitee.com/script2000/physicsLab
6
6
  Author: Goodenough
@@ -17,8 +17,8 @@ from .element import *
17
17
  # `physicsLab`自定义异常类
18
18
  from .errors import *
19
19
  # 模块化电路
20
- from .unit.wires import *
21
- from physicsLab import unit
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("physicsLabSav"):
33
- os.mkdir("physicsLabSav")
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", "getElementError", "crtExperimentFailError",
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
- "unit", "music",
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
- _xUnit: numType = 0.16
28
- _yUnit: numType = 0.08
29
- _zUnit: numType = 0.1
27
+ _X_UNIT: float = 0.16
28
+ _Y_UNIT: float = 0.08
29
+ _Z_UNIT: float = 0.1
30
30
  # big_element坐标修正
31
- _yAmend = 0.045
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
- x *= _xUnit
44
- y *= _yUnit
45
- z *= _zUnit
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
- x /= _xUnit
60
- y /= _yUnit
61
- z /= _zUnit
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 -= _yAmend
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 + _yAmend, z
83
+ return x, y + _Y_AMEND, z
85
84
 
86
85
  # 获取坐标原点
87
86
  def get_OriginPosition() -> position:
88
- return position(_xOrigin, _yOrigin, _zOrigin)
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": _xUnit,
98
- "y": _yUnit,
99
- "z": _zUnit
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, position
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 (_elementXYZ.is_elementXYZ() is True and elementXYZ is None):
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"] = f"pl{CircuitBase.__index}"
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
- # 元件index索引加1
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 RuntimeError('illegal argument')
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 RuntimeError('illegal argument')
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 (_elementXYZ.is_elementXYZ() is True and elementXYZ is None):
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
- del stack_Experiment.top().elements_Position[self._position]
102
- self._position = position(x, y, z) # type: ignore -> define _arguments in metaclass
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
- def format_Position(self) -> tuple:
109
- if not isinstance(self._position, tuple) or self._position.__len__() != 3:
110
- raise RuntimeError("Position must be a tuple of length three but gets some other value")
111
- self._position = position(*roundData(self._position.x, self._position.y, self._position.z))
112
- return self._position
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._index # type: ignore -> define self._index in metaclass
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 RuntimeError("Pin is not belong to any element")
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: callable):
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
- NnumType = Optional[numType]
13
-
14
- # 创建原件,本质上仍然是实例化
15
- def crt_Element(
16
- name: str, x: numType = 0, y: numType = 0, z: numType = 0, elementXYZ: Optional[bool] = None
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 RuntimeError("Wrong parameter type")
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('-', '_')}({x},{y},{z})")
38
-
39
- # 获取对应坐标的self
40
- def get_Element(
41
- x: NnumType=None, y: NnumType=None, z: NnumType=None, *, index: NnumType=None
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 position_Element(x: numType, y: numType, z: numType):
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('illegal argument')
55
+ raise TypeError
51
56
 
52
57
  position = _tools.roundData(x, y, z)
53
58
  if position not in _Expe.elements_Position.keys():
54
- raise errors.ElementNotExistError(f"{position} do not exist")
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 index_Element(index: int):
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
- raise errors.getElementError
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 position_Element(x, y, z)
80
+ return position_get(x, y, z)
72
81
  elif index is not None:
73
- return index_Element(index)
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 getElementError(Exception):
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
- FILE_HEAD = "physicsLabSav"
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
- self.Elements: list = [] # List[CircuitBase]
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
- # 如果obj是逻辑输入
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
- # 如果obj是8位输入器
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
- self.Wires = [
294
- Wire(
295
- Pin(self.get_element_from_identifier(wire_dict["Source"]), wire_dict["SourcePin"]),
296
- Pin(self.get_element_from_identifier(wire_dict["Target"]), wire_dict["TargetPin"]),
297
- wire_dict["ColorName"][0] # e.g. ""
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
- f"{self.VisionCenter.x},{self.VisionCenter.z},{self.VisionCenter.y}"
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
- # 仅供with时使用
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.unit import crt_Wires, D_WaterLamp
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, div_time: numType, max_notes: Optional[int]) -> List[Union["Note", "Chord"]]:
190
- res: List[Union[Note, Chord]] = []
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": # type: ignore -> Message/MetaMessage must have attr type
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
- # 转换为physicsLab的piece类
224
- def to_piece(self, div_time: numType = 100, max_notes: Optional[int] = 800) -> "Piece":
225
- return Piece(self._get_notes_list(div_time, max_notes))
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
- ) -> elements.Simple_Instrument:
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
- for delta_z, ins in enumerate(self.ins_notes):
394
- notes: List[Note] = self.ins_notes[ins]
395
- temp: elements.Simple_Instrument = elements.Simple_Instrument(
396
- x, y, z + delta_z, elementXYZ=True,instrument=ins,
397
- pitch=notes[0].pitch, is_ideal_model=True, velocity=self._get_velocity(notes, is_average=True)
398
- ).set_Rotation(0, 0, 0)
399
- if first_ins is None:
400
- first_ins = temp
401
- else:
402
- temp.i - first_ins.i
403
- temp.o - first_ins.o
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
- for a_note in notes:
406
- temp.add_note(a_note.pitch) # type: ignore
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
- return first_ins # type: ignore -> first_ins 不会是None
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
- # 设置整个音轨的默认参数 Track global variable
443
- instrument: int = 0, # 演奏的乐器,暂时只支持传入数字
444
- pitch: int = 60, # 音高/音调
445
- bpm: int = 100, # 节奏
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
- if notes is None:
461
- notes = []
490
+ self.is_optimize = is_optimize
462
491
 
463
- self.instrument = instrument
464
- self.pitch = pitch
465
- self.bpm = bpm
466
- self.volume = volume
467
- self.notes: List[Optional[Union[Note, Chord]]] = []
468
- for a_note in notes:
469
- self.append(a_note)
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(None)
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 is None:
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) -> Optional[Union[Note, Chord]]:
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
- ) -> None:
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)) or
595
- isinstance(y, (int, float)) or
596
- isinstance(z, (int, float)) or
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] is None:
635
+ while isinstance(musicArray.notes[-1], Rest_symbol):
606
636
  musicArray.notes.pop()
607
- while musicArray.notes[0] is None:
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 is None:
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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: physicsLab
3
- Version: 1.4.6
3
+ Version: 1.4.8
4
4
  Summary: Python API for Physics-Lab-AR
5
5
  Home-page: https://gitee.com/script2000/physicsLab
6
6
  Author: Goodenough
@@ -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
@@ -3,7 +3,7 @@ import setuptools
3
3
 
4
4
  setuptools.setup(
5
5
  name="physicsLab",
6
- version="1.4.6",
6
+ version="1.4.8",
7
7
  license="MIT",
8
8
  author="Goodenough",
9
9
  author_email="2381642961@qq.com",
File without changes
File without changes
File without changes