basilisk-engine 0.1.22__py3-none-any.whl → 0.1.24__py3-none-any.whl
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.
Potentially problematic release.
This version of basilisk-engine might be problematic. Click here for more details.
- basilisk/__init__.py +1 -1
- basilisk/config.py +2 -1
- basilisk/draw/draw.py +6 -6
- basilisk/draw/draw_handler.py +4 -8
- basilisk/engine.py +74 -121
- basilisk/input_output/IO_handler.py +92 -0
- basilisk/input_output/clock.py +50 -0
- basilisk/input_output/keys.py +44 -0
- basilisk/input_output/mouse.py +90 -0
- basilisk/nodes/node.py +12 -10
- basilisk/nodes/node_handler.py +4 -3
- basilisk/particles/particle_handler.py +1 -1
- basilisk/particles/particle_renderer.py +1 -1
- basilisk/render/camera.py +84 -48
- basilisk/render/frame.py +8 -14
- basilisk/render/framebuffer.py +223 -65
- basilisk/render/image_handler.py +7 -9
- basilisk/render/light_handler.py +1 -1
- basilisk/render/material_handler.py +8 -12
- basilisk/render/shader.py +1 -2
- basilisk/render/shader_handler.py +18 -15
- basilisk/render/sky.py +4 -4
- basilisk/scene.py +40 -37
- {basilisk_engine-0.1.22.dist-info → basilisk_engine-0.1.24.dist-info}/METADATA +1 -1
- {basilisk_engine-0.1.22.dist-info → basilisk_engine-0.1.24.dist-info}/RECORD +29 -26
- basilisk/input/mouse.py +0 -62
- /basilisk/{input → input_output}/__init__.py +0 -0
- /basilisk/{input → input_output}/path.py +0 -0
- {basilisk_engine-0.1.22.dist-info → basilisk_engine-0.1.24.dist-info}/WHEEL +0 -0
- {basilisk_engine-0.1.22.dist-info → basilisk_engine-0.1.24.dist-info}/top_level.txt +0 -0
basilisk/nodes/node.py
CHANGED
|
@@ -97,9 +97,10 @@ class Node():
|
|
|
97
97
|
|
|
98
98
|
# parents
|
|
99
99
|
self.node_handler = None
|
|
100
|
-
self.scene
|
|
101
|
-
self.
|
|
102
|
-
self.
|
|
100
|
+
self.scene = None
|
|
101
|
+
self.engine = None
|
|
102
|
+
self.chunk = None
|
|
103
|
+
self.parent = None
|
|
103
104
|
|
|
104
105
|
# lazy update variables
|
|
105
106
|
self.needs_geometric_center = True # pos
|
|
@@ -194,18 +195,19 @@ class Node():
|
|
|
194
195
|
self.internal_scale.callback = scale_callback
|
|
195
196
|
self.internal_rotation.callback = rotation_callback
|
|
196
197
|
|
|
197
|
-
def init_scene(self, scene):
|
|
198
|
+
def init_scene(self, scene: ...) -> None:
|
|
198
199
|
"""
|
|
199
200
|
Updates the scene of the node
|
|
200
201
|
"""
|
|
201
202
|
self.scene = scene
|
|
203
|
+
self.engine = scene.engine
|
|
202
204
|
self.node_handler = scene.node_handler
|
|
203
205
|
|
|
204
206
|
# Update materials
|
|
205
207
|
self.write_materials()
|
|
206
208
|
|
|
207
209
|
# Update the mesh
|
|
208
|
-
self.mesh = self.mesh if self.mesh else self.
|
|
210
|
+
self.mesh = self.mesh if self.mesh else self.engine.cube
|
|
209
211
|
|
|
210
212
|
# Update physics and collider
|
|
211
213
|
if self.physics_body: self.physics_body.physics_engine = scene.physics_engine
|
|
@@ -387,14 +389,14 @@ class Node():
|
|
|
387
389
|
if isinstance(self.material, list):
|
|
388
390
|
mtl_index_list = []
|
|
389
391
|
for mtl in self._mtl_list:
|
|
390
|
-
self.
|
|
392
|
+
self.engine.material_handler.add(mtl)
|
|
391
393
|
mtl_index_list.append(mtl.index)
|
|
392
394
|
mtl_index_list.append(mtl.index)
|
|
393
395
|
mtl_index_list.append(mtl.index)
|
|
394
396
|
self._material = mtl_index_list
|
|
395
397
|
|
|
396
398
|
if isinstance(self.material, type(None)):
|
|
397
|
-
self.material = self.
|
|
399
|
+
self.material = self.engine.material_handler.base
|
|
398
400
|
|
|
399
401
|
|
|
400
402
|
def __repr__(self) -> str:
|
|
@@ -555,16 +557,16 @@ class Node():
|
|
|
555
557
|
else:
|
|
556
558
|
mtl_index_list = []
|
|
557
559
|
for mtl in self._mtl_list:
|
|
558
|
-
self.
|
|
560
|
+
self.engine.material_handler.add(mtl)
|
|
559
561
|
mtl_index_list.append(mtl.index)
|
|
560
562
|
mtl_index_list.append(mtl.index)
|
|
561
563
|
mtl_index_list.append(mtl.index)
|
|
562
564
|
self._material = mtl_index_list
|
|
563
565
|
elif isinstance(value, Material):
|
|
564
566
|
self._material = value
|
|
565
|
-
if self.node_handler: self.
|
|
567
|
+
if self.node_handler: self.engine.material_handler.add(value)
|
|
566
568
|
elif isinstance(value, type(None)):
|
|
567
|
-
if self.
|
|
569
|
+
if self.engine: self._material = self.engine.material_handler.base
|
|
568
570
|
else: self._material = None
|
|
569
571
|
|
|
570
572
|
else: raise TypeError(f'Node: Invalid material value type {type(value)}')
|
basilisk/nodes/node_handler.py
CHANGED
|
@@ -19,6 +19,7 @@ class NodeHandler():
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
self.scene = scene
|
|
22
|
+
self.engine = scene.engine
|
|
22
23
|
self.nodes = []
|
|
23
24
|
self.chunk_handler = ChunkHandler(scene)
|
|
24
25
|
|
|
@@ -49,9 +50,9 @@ class NodeHandler():
|
|
|
49
50
|
for n in node.get_all(): # gets all nodes including the node to be added
|
|
50
51
|
|
|
51
52
|
# Update scene Handlers
|
|
52
|
-
self.
|
|
53
|
-
if not n.material: n.material = self.
|
|
54
|
-
self.
|
|
53
|
+
self.engine.shader_handler.add(n.shader)
|
|
54
|
+
if not n.material: n.material = self.engine.material_handler.base
|
|
55
|
+
self.engine.material_handler.add(n.material)
|
|
55
56
|
|
|
56
57
|
# Update the node attributes
|
|
57
58
|
n.init_scene(self.scene)
|
|
@@ -43,7 +43,7 @@ class ParticleHandler:
|
|
|
43
43
|
# Get material ID
|
|
44
44
|
if material == None: material_index = 0
|
|
45
45
|
elif isinstance(material, Material):
|
|
46
|
-
self.scene.material_handler.add(material)
|
|
46
|
+
self.scene.engine.material_handler.add(material)
|
|
47
47
|
material_index = material.index
|
|
48
48
|
else: raise ValueError(f'particle_handler.add: Invalid particle material type: {type(material)}')
|
|
49
49
|
|
|
@@ -31,7 +31,7 @@ class ParticleRenderer:
|
|
|
31
31
|
root = scene.engine.root
|
|
32
32
|
if shader: self.shader = shader
|
|
33
33
|
else: self.shader = Shader(scene.engine, vert=root + '/shaders/particle.vert', frag=root + '/shaders/particle.frag')
|
|
34
|
-
scene.shader_handler.add(self.shader)
|
|
34
|
+
scene.engine.shader_handler.add(self.shader)
|
|
35
35
|
|
|
36
36
|
self.particle_cube_size = 25
|
|
37
37
|
|
basilisk/render/camera.py
CHANGED
|
@@ -12,7 +12,7 @@ FAR = 350
|
|
|
12
12
|
|
|
13
13
|
# Camera movement constants
|
|
14
14
|
SPEED = 10
|
|
15
|
-
SENSITIVITY = 0.15
|
|
15
|
+
SENSITIVITY = 0.15/180
|
|
16
16
|
|
|
17
17
|
class Camera:
|
|
18
18
|
engine: ...
|
|
@@ -24,7 +24,7 @@ class Camera:
|
|
|
24
24
|
position: glm.vec3
|
|
25
25
|
"""Location of the camera (maters)"""
|
|
26
26
|
|
|
27
|
-
def __init__(self, position=(0, 0, 20),
|
|
27
|
+
def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)) -> None:
|
|
28
28
|
"""
|
|
29
29
|
Camera object to get view and projection matricies. Movement built in
|
|
30
30
|
"""
|
|
@@ -32,21 +32,13 @@ class Camera:
|
|
|
32
32
|
# Back references
|
|
33
33
|
self.scene = None
|
|
34
34
|
self.engine = None
|
|
35
|
+
# transformations
|
|
36
|
+
self.rotation = glm.quat(rotation)
|
|
37
|
+
self.position = glm.vec3(position)
|
|
35
38
|
# fov
|
|
36
39
|
self.fov = 50
|
|
37
40
|
# The initial aspect ratio of the screen
|
|
38
41
|
self.aspect_ratio = 1.0
|
|
39
|
-
# Position
|
|
40
|
-
self.position = glm.vec3(position)
|
|
41
|
-
# k vector for vertical movement
|
|
42
|
-
self.UP = glm.vec3(0, 1, 0)
|
|
43
|
-
# Movement vectors
|
|
44
|
-
self.up = glm.vec3(0, 1, 0)
|
|
45
|
-
self.right = glm.vec3(1, 0, 0)
|
|
46
|
-
self.forward = glm.vec3(0, 0, -1)
|
|
47
|
-
# Look directions in degrees
|
|
48
|
-
self.yaw = yaw
|
|
49
|
-
self.pitch = pitch
|
|
50
42
|
# View matrix
|
|
51
43
|
self.m_view = self.get_view_matrix()
|
|
52
44
|
# Projection matrix
|
|
@@ -57,23 +49,9 @@ class Camera:
|
|
|
57
49
|
Updates the camera view matrix
|
|
58
50
|
"""
|
|
59
51
|
|
|
60
|
-
self.update_camera_vectors()
|
|
52
|
+
# self.update_camera_vectors()
|
|
61
53
|
self.m_view = self.get_view_matrix()
|
|
62
54
|
|
|
63
|
-
def update_camera_vectors(self) -> None:
|
|
64
|
-
"""
|
|
65
|
-
Computes the forward vector based on the pitch and yaw. Computes horizontal and vertical vectors with cross product.
|
|
66
|
-
"""
|
|
67
|
-
yaw, pitch = glm.radians(self.yaw), glm.radians(self.pitch)
|
|
68
|
-
|
|
69
|
-
self.forward.x = glm.cos(yaw) * glm.cos(pitch)
|
|
70
|
-
self.forward.y = glm.sin(pitch)
|
|
71
|
-
self.forward.z = glm.sin(yaw) * glm.cos(pitch)
|
|
72
|
-
|
|
73
|
-
self.forward = glm.normalize(self.forward)
|
|
74
|
-
self.right = glm.normalize(glm.cross(self.forward, self.UP))
|
|
75
|
-
self.up = glm.normalize(glm.cross(self.right, self.forward))
|
|
76
|
-
|
|
77
55
|
def use(self):
|
|
78
56
|
# Updated aspect ratio of the screen
|
|
79
57
|
self.aspect_ratio = self.engine.win_size[0] / self.engine.win_size[1]
|
|
@@ -104,11 +82,28 @@ class Camera:
|
|
|
104
82
|
@property
|
|
105
83
|
def position(self): return self._position
|
|
106
84
|
@property
|
|
107
|
-
def
|
|
85
|
+
def rotation(self) -> glm.quat: return self._rotation
|
|
86
|
+
@property
|
|
87
|
+
def direction(self): return self.rotation * (0, 0, -1)
|
|
88
|
+
@property
|
|
89
|
+
def forward(self): return self.rotation * (0, 0, -1)
|
|
90
|
+
@property
|
|
91
|
+
def pitch(self): return glm.pitch(self.rotation)
|
|
92
|
+
@property
|
|
93
|
+
def yaw(self): return glm.yaw(self.rotation)
|
|
94
|
+
@property
|
|
95
|
+
def roll(self): return glm.roll(self.rotation)
|
|
108
96
|
@property
|
|
109
|
-
def
|
|
97
|
+
def UP(self):
|
|
98
|
+
up = (self.rotation.x, self.rotation.y, self.rotation.z)
|
|
99
|
+
up = (0, 1, 0) # TODO ensure that this works with all up vectors
|
|
100
|
+
return glm.normalize(up) if glm.length2(up) > 1e-7 else glm.vec3(0, 1, 0)
|
|
110
101
|
@property
|
|
111
|
-
def
|
|
102
|
+
def right(self): return glm.normalize(glm.cross(self.forward, self.UP))
|
|
103
|
+
@property
|
|
104
|
+
def up(self): return glm.normalize(glm.cross(self.right, self.forward))
|
|
105
|
+
@property
|
|
106
|
+
def horizontal(self): return glm.normalize(glm.cross(self.UP, self.right))
|
|
112
107
|
@property
|
|
113
108
|
def fov(self): return self._fov
|
|
114
109
|
|
|
@@ -128,15 +123,48 @@ class Camera:
|
|
|
128
123
|
self._position = glm.vec3(value)
|
|
129
124
|
else: raise TypeError(f'Camera: Invalid position value type {type(value)}')
|
|
130
125
|
|
|
126
|
+
@rotation.setter
|
|
127
|
+
def rotation(self, value):
|
|
128
|
+
if isinstance(value, (glm.vec3, glm.quat)): self._rotation = glm.quat(value)
|
|
129
|
+
elif isinstance(value, (Vec3, Quat)): self._rotation = glm.quat(value.data)
|
|
130
|
+
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
131
|
+
if not (2 < len(value) < 5): raise ValueError(f'Camera: Invalid number of values for rotation. Expected 3 or 4, got {len(value)}')
|
|
132
|
+
self._position = glm.quat(value)
|
|
133
|
+
else:
|
|
134
|
+
try:
|
|
135
|
+
self._rotation = glm.quat(value)
|
|
136
|
+
except:
|
|
137
|
+
raise TypeError(f'Camera: Invalid rotation value type {type(value)}')
|
|
138
|
+
|
|
131
139
|
@direction.setter
|
|
132
140
|
def direction(self, value: tuple | list | glm.vec3 | np.ndarray | Vec3):
|
|
133
|
-
if isinstance(value, glm.vec3): self.forward = glm.normalize(
|
|
141
|
+
if isinstance(value, glm.vec3): self.forward = glm.normalize(value)
|
|
134
142
|
elif isinstance(value, Vec3): self.forward = glm.normalize(value.data)
|
|
135
143
|
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
136
144
|
if len(value) != 3: raise ValueError(f'Camera: Invalid number of values for direction. Expected 3, got {len(value)}')
|
|
137
|
-
self.forward = glm.normalize(
|
|
145
|
+
self.forward = glm.normalize(value)
|
|
138
146
|
else: raise TypeError(f'Camera: Invalid direction value type {type(value)}')
|
|
139
147
|
|
|
148
|
+
@forward.setter
|
|
149
|
+
def forward(self, value):
|
|
150
|
+
self._rotation = glm.quatLookAt(value, self.UP)
|
|
151
|
+
|
|
152
|
+
@pitch.setter
|
|
153
|
+
def pitch(self, value):
|
|
154
|
+
self._rotation = glm.quat((value, self.yaw, self.roll))
|
|
155
|
+
|
|
156
|
+
@yaw.setter
|
|
157
|
+
def yaw(self, value):
|
|
158
|
+
self._rotation = glm.quat((self.pitch, value, self.roll))
|
|
159
|
+
|
|
160
|
+
@roll.setter
|
|
161
|
+
def roll(self, value):
|
|
162
|
+
self._rotation = glm.quat((self.pitch, self.yaw, value))
|
|
163
|
+
|
|
164
|
+
@UP.setter
|
|
165
|
+
def UP(self, value):
|
|
166
|
+
self._rotation = glm.quatLookAt(self.forward, value)
|
|
167
|
+
|
|
140
168
|
@fov.setter
|
|
141
169
|
def fov(self, value):
|
|
142
170
|
self._fov = value
|
|
@@ -144,8 +172,8 @@ class Camera:
|
|
|
144
172
|
|
|
145
173
|
|
|
146
174
|
class FreeCamera(Camera):
|
|
147
|
-
def __init__(self, position=(0, 0, 20),
|
|
148
|
-
super().__init__(position,
|
|
175
|
+
def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)):
|
|
176
|
+
super().__init__(position, rotation)
|
|
149
177
|
|
|
150
178
|
def update(self) -> None:
|
|
151
179
|
"""
|
|
@@ -154,18 +182,22 @@ class FreeCamera(Camera):
|
|
|
154
182
|
|
|
155
183
|
self.move()
|
|
156
184
|
self.rotate()
|
|
157
|
-
self.update_camera_vectors()
|
|
185
|
+
# self.update_camera_vectors()
|
|
158
186
|
self.m_view = self.get_view_matrix()
|
|
159
187
|
|
|
160
188
|
def rotate(self) -> None:
|
|
161
189
|
"""
|
|
162
190
|
Rotates the camera based on the amount of mouse movement.
|
|
163
191
|
"""
|
|
164
|
-
rel_x, rel_y =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
192
|
+
rel_x, rel_y = self.engine.mouse.relative
|
|
193
|
+
|
|
194
|
+
yaw_rotation = glm.angleAxis(SENSITIVITY * rel_x, -self.UP)
|
|
195
|
+
pitch_rotation = glm.angleAxis(SENSITIVITY * rel_y, -self.right)
|
|
196
|
+
new_rotation = yaw_rotation * pitch_rotation * self.rotation
|
|
197
|
+
|
|
198
|
+
v_new = new_rotation * self.UP
|
|
199
|
+
pitch_angle = glm.degrees(glm.acos(glm.clamp(glm.dot(v_new, self.UP), -1.0, 1.0)))
|
|
200
|
+
self.rotation = new_rotation if pitch_angle < 89 else yaw_rotation * self.rotation
|
|
169
201
|
|
|
170
202
|
def move(self) -> None:
|
|
171
203
|
"""
|
|
@@ -185,11 +217,16 @@ class FreeCamera(Camera):
|
|
|
185
217
|
self.position += self.UP * velocity
|
|
186
218
|
if keys[pg.K_LSHIFT]:
|
|
187
219
|
self.position -= self.UP * velocity
|
|
220
|
+
|
|
221
|
+
class FixedCamera(FreeCamera):
|
|
222
|
+
def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)):
|
|
223
|
+
super().__init__(position, rotation)
|
|
188
224
|
|
|
225
|
+
def move(self): pass
|
|
189
226
|
|
|
190
227
|
class FollowCamera(FreeCamera):
|
|
191
|
-
def __init__(self, parent, position=(0, 0, 20),
|
|
192
|
-
super().__init__(position,
|
|
228
|
+
def __init__(self, parent, position=(0, 0, 20), rotation=(1, 0, 0, 0), offset=(0, 0, 0)):
|
|
229
|
+
super().__init__(position, rotation)
|
|
193
230
|
self.parent = parent
|
|
194
231
|
self.offest = glm.vec3(offset)
|
|
195
232
|
|
|
@@ -201,11 +238,11 @@ class FollowCamera(FreeCamera):
|
|
|
201
238
|
self.position = self.parent.position + self.offest
|
|
202
239
|
|
|
203
240
|
class OrbitCamera(FreeCamera):
|
|
204
|
-
def __init__(self, parent, position=(0, 0, 20),
|
|
241
|
+
def __init__(self, parent, position=(0, 0, 20), rotation=(1, 0, 0, 0), distance=5, offset=(0, 0)):
|
|
205
242
|
self.parent = parent
|
|
206
243
|
self.distance = distance
|
|
207
244
|
self.offset = glm.vec2(offset)
|
|
208
|
-
super().__init__(position,
|
|
245
|
+
super().__init__(position, rotation)
|
|
209
246
|
|
|
210
247
|
def get_view_matrix(self) -> glm.mat4x4:
|
|
211
248
|
return glm.lookAt(self.position, self.parent.position, self.up)
|
|
@@ -214,9 +251,8 @@ class OrbitCamera(FreeCamera):
|
|
|
214
251
|
"""
|
|
215
252
|
Moves the camera to the parent node
|
|
216
253
|
"""
|
|
217
|
-
|
|
218
254
|
self.position = self.parent.position - glm.normalize(self.forward) * self.distance
|
|
219
255
|
|
|
220
256
|
class StaticCamera(Camera):
|
|
221
|
-
def __init__(self, position=(0, 0, 20),
|
|
222
|
-
super().__init__(position,
|
|
257
|
+
def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)):
|
|
258
|
+
super().__init__(position, rotation)
|
basilisk/render/frame.py
CHANGED
|
@@ -11,26 +11,22 @@ class Frame:
|
|
|
11
11
|
vao: mgl.VertexArray=None
|
|
12
12
|
framebuffer: mgl.Framebuffer=None
|
|
13
13
|
|
|
14
|
-
def __init__(self,
|
|
14
|
+
def __init__(self, engine, scale: float=1.0, linear_filter: bool=False) -> None:
|
|
15
15
|
"""
|
|
16
16
|
Basilisk render destination.
|
|
17
17
|
Can be used to render to the screen or for headless rendering
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
self.
|
|
21
|
-
self.
|
|
22
|
-
self.ctx = scene.ctx
|
|
20
|
+
self.engine = engine
|
|
21
|
+
self.ctx = engine.ctx
|
|
23
22
|
|
|
24
23
|
# Load framebuffer
|
|
25
|
-
self.
|
|
26
|
-
self.
|
|
27
|
-
size = tuple(map(lambda x: int(x * self.resolution), self.engine.win_size))
|
|
28
|
-
self.framebuffer = Framebuffer(self.engine, size=size, filter=self.filter)
|
|
29
|
-
self.ping_pong_buffer = Framebuffer(self.engine, size=size, filter=self.filter)
|
|
24
|
+
self.framebuffer = Framebuffer(self.engine, scale=scale, linear_filter=linear_filter)
|
|
25
|
+
self.ping_pong_buffer = Framebuffer(self.engine, scale=scale, linear_filter=linear_filter)
|
|
30
26
|
|
|
31
27
|
# Load Shaders
|
|
32
28
|
self.shader = Shader(self.engine, self.engine.root + '/shaders/frame.vert', self.engine.root + '/shaders/frame.frag')
|
|
33
|
-
self.
|
|
29
|
+
self.engine.shader_handler.add(self.shader)
|
|
34
30
|
|
|
35
31
|
# Load VAO
|
|
36
32
|
self.vbo = self.ctx.buffer(np.array([[-1, -1, 0, 0, 0], [1, -1, 0, 1, 0], [1, 1, 0, 1, 1], [-1, 1, 0, 0, 1], [-1, -1, 0, 0, 0], [1, 1, 0, 1, 1]], dtype='f4'))
|
|
@@ -64,7 +60,6 @@ class Frame:
|
|
|
64
60
|
"""
|
|
65
61
|
|
|
66
62
|
self.framebuffer.use()
|
|
67
|
-
self.clear()
|
|
68
63
|
|
|
69
64
|
def add_post_process(self, post_process: PostProcess) -> PostProcess:
|
|
70
65
|
"""
|
|
@@ -89,9 +84,8 @@ class Frame:
|
|
|
89
84
|
Resize the frame to the given size. None for window size
|
|
90
85
|
"""
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
self.
|
|
94
|
-
self.ping_pong_buffer.resize(size)
|
|
87
|
+
self.framebuffer.resize()
|
|
88
|
+
self.ping_pong_buffer.resize()
|
|
95
89
|
|
|
96
90
|
def __del__(self) -> None:
|
|
97
91
|
"""
|