e2D 1.4.19__py3-none-any.whl → 1.4.21__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.
- e2D/__init__.py +58 -7
- e2D/__init__.pyi +294 -5
- e2D/colors.py +53 -23
- e2D/envs.py +71 -23
- e2D/utils.py +437 -51
- {e2d-1.4.19.dist-info → e2d-1.4.21.dist-info}/METADATA +3 -2
- e2d-1.4.21.dist-info/RECORD +13 -0
- {e2d-1.4.19.dist-info → e2d-1.4.21.dist-info}/WHEEL +1 -1
- {e2d-1.4.19.dist-info → e2d-1.4.21.dist-info/licenses}/LICENSE +21 -21
- e2d-1.4.19.dist-info/RECORD +0 -13
- {e2d-1.4.19.dist-info → e2d-1.4.21.dist-info}/top_level.txt +0 -0
e2D/__init__.py
CHANGED
|
@@ -10,6 +10,7 @@ PI_QUARTER = PI/4
|
|
|
10
10
|
PI_DOUBLE = PI*2
|
|
11
11
|
|
|
12
12
|
sign = lambda val: -1 if val < 0 else (1 if val > 0 else 0)
|
|
13
|
+
clamp = lambda x, minn, maxx: x if x > minn and x < maxx else (minn if x < minn else maxx)
|
|
13
14
|
|
|
14
15
|
class Vector2D:
|
|
15
16
|
round_values_on_print = 2
|
|
@@ -34,6 +35,22 @@ class Vector2D:
|
|
|
34
35
|
@angle.setter
|
|
35
36
|
def angle(self, new_angle) -> None:
|
|
36
37
|
self.rotate(new_angle - self.angle)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def aspect_x(self) -> float:
|
|
41
|
+
return self.x / self.y if self.y != 0 else 0
|
|
42
|
+
|
|
43
|
+
@aspect_x.setter
|
|
44
|
+
def aspect_x(self, new_aspect) -> None:
|
|
45
|
+
self.x = self.y * new_aspect
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def aspect_y(self) -> float:
|
|
49
|
+
return self.y / self.x if self.x != 0 else 0
|
|
50
|
+
|
|
51
|
+
@aspect_y.setter
|
|
52
|
+
def aspect_y(self, new_aspect) -> None:
|
|
53
|
+
self.y = self.x * new_aspect
|
|
37
54
|
|
|
38
55
|
@property
|
|
39
56
|
def copy(self) -> "Vector2D":
|
|
@@ -42,9 +59,16 @@ class Vector2D:
|
|
|
42
59
|
@property
|
|
43
60
|
def sign(self) -> "Vector2D":
|
|
44
61
|
return Vector2D(sign(self.x), sign(self.y))
|
|
62
|
+
|
|
63
|
+
def clamp(self, min_val: Vector2D, max_val: Vector2D) -> "Vector2D":
|
|
64
|
+
return Vector2D(clamp(self.x, min_val.x, max_val.x), clamp(self.y, min_val.y, max_val.y))
|
|
65
|
+
|
|
66
|
+
def iclamp(self, min_val: Vector2D, max_val: Vector2D) -> None:
|
|
67
|
+
self.x = clamp(self.x, min_val.x, max_val.x)
|
|
68
|
+
self.y = clamp(self.y, min_val.y, max_val.y)
|
|
45
69
|
|
|
46
70
|
@property
|
|
47
|
-
def
|
|
71
|
+
def normalized(self) -> "Vector2D":
|
|
48
72
|
if (mag:=self.length) == 0:
|
|
49
73
|
return self.copy
|
|
50
74
|
return Vector2D(self.x / mag, self.y / mag)
|
|
@@ -52,11 +76,31 @@ class Vector2D:
|
|
|
52
76
|
@property
|
|
53
77
|
def length(self) -> float:
|
|
54
78
|
return (self.x ** 2 + self.y ** 2) ** .5
|
|
55
|
-
|
|
79
|
+
|
|
80
|
+
@length.setter
|
|
81
|
+
def length(self, new_length: float) -> None:
|
|
82
|
+
current_length = self.length
|
|
83
|
+
if current_length == 0:
|
|
84
|
+
self.x = new_length
|
|
85
|
+
self.y = 0
|
|
86
|
+
else:
|
|
87
|
+
self.x *= new_length / current_length
|
|
88
|
+
self.y *= new_length / current_length
|
|
89
|
+
|
|
56
90
|
@property
|
|
57
91
|
def length_sqrd(self) -> float:
|
|
58
92
|
return self.x ** 2 + self.y ** 2
|
|
59
|
-
|
|
93
|
+
|
|
94
|
+
@length_sqrd.setter
|
|
95
|
+
def length_sqrd(self, new_length_sqrd: float) -> None:
|
|
96
|
+
current_length = self.length
|
|
97
|
+
if current_length == 0:
|
|
98
|
+
self.x = _mt.sqrt(new_length_sqrd)
|
|
99
|
+
self.y = 0
|
|
100
|
+
else:
|
|
101
|
+
self.x *= _mt.sqrt(new_length_sqrd) / current_length
|
|
102
|
+
self.y *= _mt.sqrt(new_length_sqrd) / current_length
|
|
103
|
+
|
|
60
104
|
@property
|
|
61
105
|
def inverse(self) -> "Vector2D":
|
|
62
106
|
return self.mult(-1)
|
|
@@ -111,6 +155,13 @@ class Vector2D:
|
|
|
111
155
|
def complex_to_cartesian(cls, complex_n) -> "Vector2D":
|
|
112
156
|
return cls(complex_n.real, complex_n.imag)
|
|
113
157
|
|
|
158
|
+
def cartesian_to_linear(self, size) -> int:
|
|
159
|
+
return int(self.x + self.y * size)
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def linear_to_cartesian(cls, linear, size) -> "Vector2D":
|
|
163
|
+
return cls(linear % size, linear // size)
|
|
164
|
+
|
|
114
165
|
def lerp(self, other, t=.1) -> "Vector2D":
|
|
115
166
|
return Vector2D(self.x + (other.x - self.x) * t, self.y + (other.y - self.y) * t)
|
|
116
167
|
|
|
@@ -487,10 +538,10 @@ V2down_right = Vector2D(1, -1)
|
|
|
487
538
|
V2up_left = Vector2D(-1, 1)
|
|
488
539
|
V2down_left = Vector2D(-1, -1)
|
|
489
540
|
|
|
490
|
-
V2up_right_norm = V2up_right.
|
|
491
|
-
V2down_right_norm = V2down_right.
|
|
492
|
-
V2up_left_norm = V2up_left.
|
|
493
|
-
V2down_left_norm = V2down_left.
|
|
541
|
+
V2up_right_norm = V2up_right.normalized
|
|
542
|
+
V2down_right_norm = V2down_right.normalized
|
|
543
|
+
V2up_left_norm = V2up_left.normalized
|
|
544
|
+
V2down_left_norm = V2down_left.normalized
|
|
494
545
|
|
|
495
546
|
VECTORS_4_DIRECTIONS = (V2right, V2down, V2left, V2up)
|
|
496
547
|
VECTORS_4_SEMIDIRECTIONS = (V2down_right, V2down_left, V2up_left, V2up_right)
|
e2D/__init__.pyi
CHANGED
|
@@ -12,6 +12,7 @@ PI_DOUBLE : float
|
|
|
12
12
|
#
|
|
13
13
|
|
|
14
14
|
sign : Callable[[int|float], Literal[-1,0,1]]
|
|
15
|
+
clamp: Callable[[int|float, int|float, int|float], int|float]
|
|
15
16
|
|
|
16
17
|
class Vector2D:
|
|
17
18
|
round_values_on_print : int|float
|
|
@@ -140,10 +141,59 @@ class Vector2D:
|
|
|
140
141
|
...
|
|
141
142
|
|
|
142
143
|
@property
|
|
143
|
-
def angle(self:"Vector2D") -> int|float:
|
|
144
|
+
def angle(self:"Vector2D") -> int|float:
|
|
145
|
+
"""
|
|
146
|
+
# Vector Angle
|
|
147
|
+
## Returns:
|
|
148
|
+
float: The angle (in radians) of the vector from the positive x-axis.
|
|
149
|
+
## Example:
|
|
150
|
+
v = Vector2D(1, 1)
|
|
151
|
+
angle = v.angle
|
|
152
|
+
print(angle) # Output: 0.7853981633974483
|
|
153
|
+
## Explanation:
|
|
154
|
+
This property calculates the angle of the vector from the positive x-axis using the `atan2` function.
|
|
155
|
+
"""
|
|
156
|
+
...
|
|
144
157
|
|
|
145
158
|
@angle.setter
|
|
146
|
-
def angle(self:"Vector2D", argv) -> None:
|
|
159
|
+
def angle(self:"Vector2D", argv) -> None:
|
|
160
|
+
...
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def aspect_x(self: "Vector2D") -> float:
|
|
164
|
+
"""
|
|
165
|
+
# Aspect Ratio (X over Y)
|
|
166
|
+
## Returns:
|
|
167
|
+
float: The aspect ratio of the vector, calculated as the x component divided by the y component. Returns 0 if the y component is zero.
|
|
168
|
+
## Example:
|
|
169
|
+
v = Vector2D(4, 2)
|
|
170
|
+
aspect = v.aspect_x
|
|
171
|
+
print(aspect) # Output: 2.0
|
|
172
|
+
## Explanation:
|
|
173
|
+
This property computes the ratio of the x component to the y component of the vector. If the y component is zero, it returns 0 to avoid division by zero.
|
|
174
|
+
"""
|
|
175
|
+
...
|
|
176
|
+
|
|
177
|
+
@aspect_x.setter
|
|
178
|
+
def aspect_x(self: "Vector2D", new_aspect) -> None: ...
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def aspect_y(self: "Vector2D") -> float:
|
|
182
|
+
"""
|
|
183
|
+
# Aspect Ratio (Y over X)
|
|
184
|
+
## Returns:
|
|
185
|
+
float: The aspect ratio of the vector, calculated as the y component divided by the x component. Returns 0 if the x component is zero.
|
|
186
|
+
## Example:
|
|
187
|
+
v = Vector2D(2, 4)
|
|
188
|
+
aspect = v.aspect_y
|
|
189
|
+
print(aspect) # Output: 2.0
|
|
190
|
+
## Explanation:
|
|
191
|
+
This property computes the ratio of the y component to the x component of the vector. If the x component is zero, it returns 0 to avoid division by zero.
|
|
192
|
+
"""
|
|
193
|
+
...
|
|
194
|
+
|
|
195
|
+
@aspect_y.setter
|
|
196
|
+
def aspect_y(self: "Vector2D", new_aspect) -> None: ...
|
|
147
197
|
|
|
148
198
|
@property
|
|
149
199
|
def copy(self:"Vector2D") -> "Vector2D":
|
|
@@ -211,8 +261,56 @@ class Vector2D:
|
|
|
211
261
|
"""
|
|
212
262
|
...
|
|
213
263
|
|
|
264
|
+
def clamp(self, min_val: Vector2D, max_val: Vector2D) -> "Vector2D":
|
|
265
|
+
"""
|
|
266
|
+
# Clamp the vector's components between the corresponding components of two other vectors.
|
|
267
|
+
|
|
268
|
+
## Parameters:
|
|
269
|
+
min_val (Vector2D): The minimum vector for clamping.
|
|
270
|
+
max_val (Vector2D): The maximum vector for clamping.
|
|
271
|
+
|
|
272
|
+
## Returns:
|
|
273
|
+
Vector2D: A new vector with its components clamped between the corresponding components of `min_val` and `max_val`.
|
|
274
|
+
|
|
275
|
+
## Example:
|
|
276
|
+
v = Vector2D(5, 10)
|
|
277
|
+
min_val = Vector2D(0, 8)
|
|
278
|
+
max_val = Vector2D(6, 12)
|
|
279
|
+
clamped_v = v.clamp(min_val, max_val)
|
|
280
|
+
print(clamped_v) # Output: (5, 10)
|
|
281
|
+
|
|
282
|
+
## Explanation:
|
|
283
|
+
This method clamps the x and y components of the current vector between the corresponding x and y components
|
|
284
|
+
of the `min_val` and `max_val` vectors. The resulting vector is returned as a new Vector2D instance.
|
|
285
|
+
"""
|
|
286
|
+
...
|
|
287
|
+
|
|
288
|
+
def iclamp(self, min_val: Vector2D, max_val: Vector2D) -> None:
|
|
289
|
+
"""
|
|
290
|
+
# Clamp the vector's components in place between the corresponding components of two other vectors.
|
|
291
|
+
|
|
292
|
+
## Parameters:
|
|
293
|
+
min_val (Vector2D): The minimum vector for clamping.
|
|
294
|
+
max_val (Vector2D): The maximum vector for clamping.
|
|
295
|
+
|
|
296
|
+
## Returns:
|
|
297
|
+
None
|
|
298
|
+
|
|
299
|
+
## Example:
|
|
300
|
+
v = Vector2D(5, 10)
|
|
301
|
+
min_val = Vector2D(0, 8)
|
|
302
|
+
max_val = Vector2D(6, 12)
|
|
303
|
+
v.iclamp(min_val, max_val)
|
|
304
|
+
print(v) # Output: (5, 10)
|
|
305
|
+
|
|
306
|
+
## Explanation:
|
|
307
|
+
This method clamps the x and y components of the current vector in place between the corresponding x and y components
|
|
308
|
+
of the `min_val` and `max_val` vectors. The method modifies the current vector directly.
|
|
309
|
+
"""
|
|
310
|
+
...
|
|
311
|
+
|
|
214
312
|
@property
|
|
215
|
-
def
|
|
313
|
+
def normalized(self:"Vector2D") -> "Vector2D":
|
|
216
314
|
"""
|
|
217
315
|
# Vector Normalization
|
|
218
316
|
|
|
@@ -224,7 +322,7 @@ class Vector2D:
|
|
|
224
322
|
|
|
225
323
|
## Example:
|
|
226
324
|
v = Vector2D(3, 4)
|
|
227
|
-
normalized_v = v.
|
|
325
|
+
normalized_v = v.normalized() # Normalize the vector (3, 4)
|
|
228
326
|
print(normalized_v) # Output: (0.6, 0.8)
|
|
229
327
|
|
|
230
328
|
## Explanation:
|
|
@@ -244,23 +342,123 @@ class Vector2D:
|
|
|
244
342
|
|
|
245
343
|
@property
|
|
246
344
|
def length(self:"Vector2D") -> float:
|
|
345
|
+
"""
|
|
346
|
+
# Vector Length
|
|
347
|
+
|
|
348
|
+
## Returns:
|
|
349
|
+
float: The length (magnitude) of the vector.
|
|
350
|
+
|
|
351
|
+
## Example:
|
|
352
|
+
v = Vector2D(3, 4)
|
|
353
|
+
length = v.length
|
|
354
|
+
print(length) # Output: 5.0
|
|
355
|
+
|
|
356
|
+
## Explanation:
|
|
357
|
+
This property calculates the length (magnitude) of the vector using the Pythagorean theorem.
|
|
358
|
+
"""
|
|
247
359
|
...
|
|
248
360
|
|
|
249
361
|
@property
|
|
250
362
|
def length_sqrd(self:"Vector2D") -> float:
|
|
363
|
+
"""
|
|
364
|
+
# Vector Length Squared
|
|
365
|
+
|
|
366
|
+
## Returns:
|
|
367
|
+
float: The squared length (magnitude) of the vector.
|
|
368
|
+
|
|
369
|
+
## Example:
|
|
370
|
+
v = Vector2D(3, 4)
|
|
371
|
+
length_sqrd = v.length_sqrd
|
|
372
|
+
print(length_sqrd) # Output: 25
|
|
373
|
+
|
|
374
|
+
## Explanation:
|
|
375
|
+
This property calculates the squared length (magnitude) of the vector.
|
|
376
|
+
It is more efficient than calculating the actual length, as it avoids the
|
|
377
|
+
square root calculation.
|
|
378
|
+
"""
|
|
251
379
|
...
|
|
252
380
|
|
|
253
381
|
@property
|
|
254
382
|
def inverse(self:"Vector2D") -> "Vector2D":
|
|
383
|
+
"""
|
|
384
|
+
# Vector Inversion
|
|
385
|
+
|
|
386
|
+
## Returns:
|
|
387
|
+
Vector2D: A new vector with inverted components.
|
|
388
|
+
|
|
389
|
+
## Example:
|
|
390
|
+
v = Vector2D(3, 4)
|
|
391
|
+
inverted_v = v.inverse()
|
|
392
|
+
print(inverted_v) # Output: (-3, -4)
|
|
393
|
+
|
|
394
|
+
## Explanation:
|
|
395
|
+
This method calculates the inverted version of the current vector, which means a new vector with the same magnitude
|
|
396
|
+
but opposite direction.
|
|
397
|
+
|
|
398
|
+
The inverted vector is obtained by negating each component of the current vector.
|
|
399
|
+
|
|
400
|
+
The resulting inverted vector is returned.
|
|
401
|
+
"""
|
|
255
402
|
...
|
|
256
403
|
|
|
257
404
|
def floor(self:"Vector2D", n:"int|float|Vector2D"=1) -> "Vector2D":
|
|
405
|
+
"""
|
|
406
|
+
# Round the Vector2D components down to the nearest integer or specified decimal place.
|
|
407
|
+
|
|
408
|
+
## Parameters:
|
|
409
|
+
n (int|float|Vector2D, optional): The decimal place to round down to. Default is 1.
|
|
410
|
+
|
|
411
|
+
## Returns:
|
|
412
|
+
Vector2D: A new vector with rounded-down components.
|
|
413
|
+
|
|
414
|
+
## Example:
|
|
415
|
+
v = Vector2D(3.14159, 2.71828)
|
|
416
|
+
rounded_v = v.floor(2)
|
|
417
|
+
print(rounded_v) # Output: (3.14, 2.71)
|
|
418
|
+
|
|
419
|
+
## Explanation:
|
|
420
|
+
This method rounds each component of the vector down to the nearest integer or specified decimal place.
|
|
421
|
+
"""
|
|
258
422
|
...
|
|
259
423
|
|
|
260
424
|
def ceil(self:"Vector2D", n:"int|float|Vector2D"=1) -> "Vector2D":
|
|
425
|
+
"""
|
|
426
|
+
# Round the Vector2D components up to the nearest integer or specified decimal place.
|
|
427
|
+
|
|
428
|
+
## Parameters:
|
|
429
|
+
n (int|float|Vector2D, optional): The decimal place to round up to. Default is 1.
|
|
430
|
+
|
|
431
|
+
## Returns:
|
|
432
|
+
Vector2D: A new vector with rounded-up components.
|
|
433
|
+
|
|
434
|
+
## Example:
|
|
435
|
+
v = Vector2D(3.14159, 2.71828)
|
|
436
|
+
rounded_v = v.ceil(2)
|
|
437
|
+
print(rounded_v) # Output: (3.15, 2.72)
|
|
438
|
+
|
|
439
|
+
## Explanation:
|
|
440
|
+
This method rounds each component of the vector up to the nearest integer or specified decimal place.
|
|
441
|
+
"""
|
|
261
442
|
...
|
|
262
443
|
|
|
263
444
|
def round(self:"Vector2D", n:"int|float|Vector2D"=1) -> "Vector2D":
|
|
445
|
+
"""
|
|
446
|
+
# Round the Vector2D components to the nearest integer or specified decimal place.
|
|
447
|
+
|
|
448
|
+
## Parameters:
|
|
449
|
+
n (int|float|Vector2D, optional): The decimal place to round to. Default is 1.
|
|
450
|
+
|
|
451
|
+
## Returns:
|
|
452
|
+
Vector2D: A new vector with rounded components.
|
|
453
|
+
|
|
454
|
+
## Example:
|
|
455
|
+
v = Vector2D(3.14159, 2.71828)
|
|
456
|
+
rounded_v = v.round(2)
|
|
457
|
+
print(rounded_v) # Output: (3.14, 2.72)
|
|
458
|
+
|
|
459
|
+
## Explanation:
|
|
460
|
+
This method rounds each component of the vector to the nearest integer or specified decimal place.
|
|
461
|
+
"""
|
|
264
462
|
...
|
|
265
463
|
|
|
266
464
|
@classmethod
|
|
@@ -451,7 +649,65 @@ class Vector2D:
|
|
|
451
649
|
def cartesian_to_complex(self:"Vector2D") -> complex: ...
|
|
452
650
|
|
|
453
651
|
@classmethod
|
|
454
|
-
def complex_to_cartesian(cls, complex_n: complex) -> "Vector2D":
|
|
652
|
+
def complex_to_cartesian(cls, complex_n: complex) -> "Vector2D":
|
|
653
|
+
"""
|
|
654
|
+
# Convert a Complex Number to Cartesian Coordinates
|
|
655
|
+
|
|
656
|
+
## Parameters:
|
|
657
|
+
complex_n (complex): The complex number to convert.
|
|
658
|
+
|
|
659
|
+
## Returns:
|
|
660
|
+
Vector2D: The corresponding (x, y) coordinates as a Vector2D.
|
|
661
|
+
|
|
662
|
+
## Example:
|
|
663
|
+
v = Vector2D.complex_to_cartesian(3 + 4j) # v = Vector2D(3, 4)
|
|
664
|
+
|
|
665
|
+
## Explanation:
|
|
666
|
+
This class method converts a complex number (a + bi) to Cartesian coordinates (x, y).
|
|
667
|
+
The real part 'a' becomes the x-coordinate, and the imaginary part 'b' becomes the y-coordinate.
|
|
668
|
+
"""
|
|
669
|
+
...
|
|
670
|
+
|
|
671
|
+
def cartesian_to_linear(self:"Vector2D", size:int|float) -> int:
|
|
672
|
+
"""
|
|
673
|
+
# Convert 2D Cartesian coordinates to a linear index.
|
|
674
|
+
|
|
675
|
+
## Parameters:
|
|
676
|
+
size (int|float): The width of the grid (number of columns).
|
|
677
|
+
|
|
678
|
+
## Returns:
|
|
679
|
+
int: The linear index corresponding to the (x, y) coordinates.
|
|
680
|
+
|
|
681
|
+
## Example:
|
|
682
|
+
v = Vector2D(3, 2)
|
|
683
|
+
idx = v.cartesian_to_linear(5) # idx = 3 + 2*5 = 13
|
|
684
|
+
|
|
685
|
+
## Explanation:
|
|
686
|
+
This method converts the (x, y) coordinates of the vector to a single linear index,
|
|
687
|
+
assuming row-major order in a 2D grid of the given width.
|
|
688
|
+
"""
|
|
689
|
+
...
|
|
690
|
+
|
|
691
|
+
@classmethod
|
|
692
|
+
def linear_to_cartesian(cls, linear:int, size:int|float) -> "Vector2D":
|
|
693
|
+
"""
|
|
694
|
+
# Convert a linear index to 2D Cartesian coordinates.
|
|
695
|
+
|
|
696
|
+
## Parameters:
|
|
697
|
+
linear (int): The linear index.
|
|
698
|
+
size (int|float): The width of the grid (number of columns).
|
|
699
|
+
|
|
700
|
+
## Returns:
|
|
701
|
+
Vector2D: The corresponding (x, y) coordinates as a Vector2D.
|
|
702
|
+
|
|
703
|
+
## Example:
|
|
704
|
+
v = Vector2D.linear_to_cartesian(13, 5) # v = Vector2D(3, 2)
|
|
705
|
+
|
|
706
|
+
## Explanation:
|
|
707
|
+
This class method converts a linear index to (x, y) coordinates,
|
|
708
|
+
assuming row-major order in a 2D grid of the given width.
|
|
709
|
+
"""
|
|
710
|
+
...
|
|
455
711
|
|
|
456
712
|
def lerp(self:"Vector2D", other:"int|float|Vector2D|list|tuple", t:float=.1) -> "Vector2D":
|
|
457
713
|
"""
|
|
@@ -1046,4 +1302,37 @@ def distance_line_point(line_point_a:Vector2D, line_point_b:Vector2D, point_c:Ve
|
|
|
1046
1302
|
...
|
|
1047
1303
|
|
|
1048
1304
|
def optimize_value_string(value: int|float, precision: int) -> str:
|
|
1305
|
+
"""
|
|
1306
|
+
# Optimize the string representation of a numeric value based on its magnitude and the specified precision.
|
|
1307
|
+
|
|
1308
|
+
## Parameters:
|
|
1309
|
+
value (int | float): The numeric value to be formatted.
|
|
1310
|
+
precision (int): The number of decimal places or significant digits to use in the formatted string.
|
|
1311
|
+
|
|
1312
|
+
## Returns:
|
|
1313
|
+
str: The optimized string representation of the value.
|
|
1314
|
+
|
|
1315
|
+
## Example:
|
|
1316
|
+
value = 0.000012345
|
|
1317
|
+
precision = 3
|
|
1318
|
+
result = optimize_value_string(value, precision)
|
|
1319
|
+
print(result)
|
|
1320
|
+
# Output: '1.235e-05'
|
|
1321
|
+
|
|
1322
|
+
value = 123.456789
|
|
1323
|
+
precision = 2
|
|
1324
|
+
result = optimize_value_string(value, precision)
|
|
1325
|
+
print(result)
|
|
1326
|
+
# Output: '123.46'
|
|
1327
|
+
|
|
1328
|
+
## Explanation:
|
|
1329
|
+
The function formats the input value as a string, choosing between fixed-point and scientific notation
|
|
1330
|
+
based on the magnitude of the value relative to the specified precision:
|
|
1331
|
+
- If the absolute value is very small (less than 1 divided by 10 to the power of precision, but not zero),
|
|
1332
|
+
scientific notation is used.
|
|
1333
|
+
- If the absolute value is less than 10 to the power of precision, fixed-point notation is used,
|
|
1334
|
+
with trailing zeros and decimal points removed for brevity.
|
|
1335
|
+
- Otherwise, scientific notation is used.
|
|
1336
|
+
This ensures that the string representation is concise and readable, while maintaining the requested precision.
|
|
1337
|
+
"""
|
|
1049
1338
|
...
|
e2D/colors.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from colorsys import hsv_to_rgb as __hsv_to_rgb_def__, hls_to_rgb as __hls_to_rgb_def__, rgb_to_hls as __rgb_to_hls__, rgb_to_hsv as __rgb_to_hsv__
|
|
2
3
|
from typing import Any, Callable, Generator, Literal
|
|
3
4
|
from pygame.color import Color as __color_pygame__
|
|
4
|
-
from random import
|
|
5
|
+
from random import random as __rand__
|
|
5
6
|
|
|
6
7
|
RGB_COLOR_MODE = "rgb"
|
|
7
8
|
RGBA_COLOR_MODE = "rgba"
|
|
@@ -15,11 +16,11 @@ HLS_COLOR_MODE = "hls"
|
|
|
15
16
|
|
|
16
17
|
__LITERAL_COLOR_MODES__ = Literal["rgb","rgba","bgr","bgra","g","hsv","hls"] #,"cmyk","lab"]
|
|
17
18
|
|
|
18
|
-
def __hsv_to_rgb__(h:int|float, s:int|float, v:int|float) -> tuple[int|float, int|float, int|float]:
|
|
19
|
+
def __hsv_to_rgb__(h:"int|float", s:"int|float", v:"int|float") -> tuple["int|float", "int|float", "int|float"]:
|
|
19
20
|
r,g,b = __hsv_to_rgb_def__(h, s, v)
|
|
20
21
|
return r*255, g*255, b*255
|
|
21
22
|
|
|
22
|
-
def __hls_to_rgb__(h:int|float, s:int|float, v:int|float) -> tuple[int|float, int|float, int|float]:
|
|
23
|
+
def __hls_to_rgb__(h:"int|float", s:"int|float", v:"int|float") -> tuple["int|float", "int|float", "int|float"]:
|
|
23
24
|
r,g,b = __hls_to_rgb_def__(h, s, v)
|
|
24
25
|
return r*255, g*255, b*255
|
|
25
26
|
|
|
@@ -82,6 +83,12 @@ __conversion_table__ :dict[__LITERAL_COLOR_MODES__, dict[__LITERAL_COLOR_MODES__
|
|
|
82
83
|
},
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
def pygamize_color(color: "__color_pygame__|Color") -> "__color_pygame__":
|
|
87
|
+
return color() if isinstance(color, Color) else color
|
|
88
|
+
|
|
89
|
+
def unpygamize_color(color: "__color_pygame__|Color") -> "Color":
|
|
90
|
+
return Color(*color[:], mode=RGBA_COLOR_MODE) if isinstance(color, __color_pygame__) else color
|
|
91
|
+
|
|
85
92
|
class Color:
|
|
86
93
|
def __init__(self, *values, mode:__LITERAL_COLOR_MODES__=RGB_COLOR_MODE) -> None:
|
|
87
94
|
self.__dict__ = dict(zip(mode, values))
|
|
@@ -104,35 +111,35 @@ class Color:
|
|
|
104
111
|
return (d ** .5) if rooted else d
|
|
105
112
|
|
|
106
113
|
@classmethod
|
|
107
|
-
def new_rgb(cls, r:int|float, g:int|float, b:int|float) -> "Color":
|
|
114
|
+
def new_rgb(cls, r:"int|float", g:"int|float", b:"int|float") -> "Color":
|
|
108
115
|
return Color(r,g,b, mode=RGB_COLOR_MODE)
|
|
109
116
|
@classmethod
|
|
110
|
-
def new_rgba(cls, r:int|float, g:int|float, b:int|float, a:int|float) -> "Color":
|
|
117
|
+
def new_rgba(cls, r:"int|float", g:"int|float", b:"int|float", a:"int|float") -> "Color":
|
|
111
118
|
return Color(r,g,b,a, mode=RGBA_COLOR_MODE)
|
|
112
119
|
@classmethod
|
|
113
|
-
def new_bgr(cls, b:int|float, g:int|float, r:int|float) -> "Color":
|
|
120
|
+
def new_bgr(cls, b:"int|float", g:"int|float", r:"int|float") -> "Color":
|
|
114
121
|
return Color(b,g,r, mode=BGR_COLOR_MODE)
|
|
115
122
|
@classmethod
|
|
116
|
-
def new_bgra(cls, b:int|float, g:int|float, r:int|float, a:int|float) -> "Color":
|
|
123
|
+
def new_bgra(cls, b:"int|float", g:"int|float", r:"int|float", a:"int|float") -> "Color":
|
|
117
124
|
return Color(b,g,r,a, mode=BGRA_COLOR_MODE)
|
|
118
125
|
@classmethod
|
|
119
126
|
def new_g(cls, g) -> "Color":
|
|
120
127
|
return Color(g, mode=GRAY_COLOR_MODE)
|
|
121
128
|
@classmethod
|
|
122
|
-
def new_hsv(cls, h:int|float, s:int|float, v:int|float) -> "Color":
|
|
129
|
+
def new_hsv(cls, h:"int|float", s:"int|float", v:"int|float") -> "Color":
|
|
123
130
|
return Color(h,s,v, mode=HSV_COLOR_MODE)
|
|
124
131
|
@classmethod
|
|
125
|
-
def new_hls(cls, h:int|float, l:int|float, s:int|float) -> "Color":
|
|
132
|
+
def new_hls(cls, h:"int|float", l:"int|float", s:"int|float") -> "Color":
|
|
126
133
|
return Color(h,l,s, mode=HLS_COLOR_MODE)
|
|
127
134
|
# @classmethod
|
|
128
|
-
# def new_cmyk(cls, c:int|float, m:int|float, y:int|float, k:int|float) -> Color:
|
|
135
|
+
# def new_cmyk(cls, c:"int|float", m:"int|float", y:"int|float", k:"int|float") -> Color:
|
|
129
136
|
# return Color(c,m,y,k, mode=CMYK_COLOR_MODE)
|
|
130
137
|
# @classmethod
|
|
131
|
-
# def new_lab(cls, l:int|float, a:int|float, b:int|float) -> Color:
|
|
138
|
+
# def new_lab(cls, l:"int|float", a:"int|float", b:"int|float") -> Color:
|
|
132
139
|
# return Color(l,a,b, mode=LAB_COLOR_MODE)
|
|
133
140
|
|
|
134
141
|
@property
|
|
135
|
-
def values(self) -> tuple[int|float, ...]:
|
|
142
|
+
def values(self) -> tuple["int|float", ...]:
|
|
136
143
|
return tuple(self.__dict__.values())[:-1]
|
|
137
144
|
|
|
138
145
|
@property
|
|
@@ -140,7 +147,7 @@ class Color:
|
|
|
140
147
|
return tuple(self.__dict__.keys())[:-1]
|
|
141
148
|
|
|
142
149
|
@property
|
|
143
|
-
def items(self) -> tuple[tuple[str, int|float], ...]:
|
|
150
|
+
def items(self) -> tuple[tuple[str, "int|float"], ...]:
|
|
144
151
|
return tuple(self.__dict__.items())[:-1]
|
|
145
152
|
|
|
146
153
|
def copy(self) -> "Color":
|
|
@@ -179,7 +186,7 @@ class Color:
|
|
|
179
186
|
return "Color(" + ", ".join(f"{k}:{v}" for k, v in self.items) + ")"
|
|
180
187
|
|
|
181
188
|
def __call__(self) -> __color_pygame__:
|
|
182
|
-
return __color_pygame__(int(self.r), int(self.g), int(self.b))
|
|
189
|
+
return __color_pygame__(int(self.r), int(self.g), int(self.b)) if self.mode == RGB_COLOR_MODE else __color_pygame__(int(self.r), int(self.g), int(self.b), int(self.a))
|
|
183
190
|
|
|
184
191
|
# fast operations Vector2D.operation(both,x,y)
|
|
185
192
|
def add(self, all3=.0, r=.0, g=.0, b=.0) -> "Color":
|
|
@@ -386,7 +393,7 @@ class Color:
|
|
|
386
393
|
def __float__(self) -> "Color":
|
|
387
394
|
return Color(float(self.r), float(self.g), float(self.b))
|
|
388
395
|
|
|
389
|
-
def __getitem__(self, n) -> int|float:
|
|
396
|
+
def __getitem__(self, n) -> "int|float":
|
|
390
397
|
return self.values[n] if isinstance(n, int) else self.values[self.keys.index(n)]
|
|
391
398
|
|
|
392
399
|
def __iter__(self) -> Generator[float, Any, None]:
|
|
@@ -406,8 +413,10 @@ class Color:
|
|
|
406
413
|
try:
|
|
407
414
|
return cls(*other.values, mode=other.mode)
|
|
408
415
|
except:
|
|
409
|
-
raise TypeError(f"The value {other} of type {type(other)} is not a num type: [{int|float}] nor an array type: [{list|tuple}]")
|
|
416
|
+
raise TypeError(f"The value {other} of type {type(other)} is not a num type: [{"int|float"}] nor an array type: [{list|tuple}]")
|
|
410
417
|
|
|
418
|
+
@classmethod
|
|
419
|
+
def transparent(cls) -> "Color": return Color(0,0,0,0, mode=RGBA_COLOR_MODE)
|
|
411
420
|
@classmethod
|
|
412
421
|
def white(cls) -> "Color": return Color(255,255,255)
|
|
413
422
|
@classmethod
|
|
@@ -418,23 +427,44 @@ class Color:
|
|
|
418
427
|
def green(cls) -> "Color": return Color(0,255,0)
|
|
419
428
|
@classmethod
|
|
420
429
|
def blue(cls) -> "Color": return Color(0,0,255)
|
|
421
|
-
|
|
422
|
-
# @classmethod
|
|
423
|
-
# def (cls) -> "Color": return Color(0,0,255)
|
|
424
|
-
|
|
425
430
|
@classmethod
|
|
426
|
-
def
|
|
427
|
-
|
|
428
|
-
|
|
431
|
+
def cyan(cls) -> "Color": return Color(0,255,255)
|
|
432
|
+
@classmethod
|
|
433
|
+
def magenta(cls) -> "Color": return Color(255,0,255)
|
|
434
|
+
@classmethod
|
|
435
|
+
def yellow(cls) -> "Color": return Color(255,255,0)
|
|
429
436
|
|
|
437
|
+
@classmethod
|
|
438
|
+
def randomize(cls, start=0, end=255, func=lambda val:val) -> "Color":
|
|
439
|
+
if not isinstance(start, Color):
|
|
440
|
+
if isinstance(start, (int, float)):
|
|
441
|
+
start = Color(start, start, start)
|
|
442
|
+
else:
|
|
443
|
+
raise Exception(f"\nArg start must be in [Color, int, float, tuple, list] not a [{type(start)}]\n")
|
|
444
|
+
if not isinstance(end, Color):
|
|
445
|
+
if isinstance(end, (int, float)):
|
|
446
|
+
end = Color(end, end, end)
|
|
447
|
+
else:
|
|
448
|
+
raise Exception(f"\nArg end must be in [Color, int, float, tuple, list] not a [{type(end)}]\n")
|
|
449
|
+
return start + Color(func(__rand__()), func(__rand__()), func(__rand__())) * (end - start)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
TRANSPARENT_COLOR = Color.transparent()
|
|
430
453
|
WHITE_COLOR = Color.white()
|
|
431
454
|
BLACK_COLOR = Color.black()
|
|
432
455
|
RED_COLOR = Color.red()
|
|
433
456
|
GREEN_COLOR = Color.green()
|
|
434
457
|
BLUE_COLOR = Color.blue()
|
|
458
|
+
CYAN_COLOR = Color.cyan()
|
|
459
|
+
MAGENTA_COLOR = Color.magenta()
|
|
460
|
+
YELLOW_COLOR = Color.yellow()
|
|
435
461
|
|
|
462
|
+
TRANSPARENT_COLOR_PYG = TRANSPARENT_COLOR()
|
|
436
463
|
WHITE_COLOR_PYG = WHITE_COLOR()
|
|
437
464
|
BLACK_COLOR_PYG = BLACK_COLOR()
|
|
438
465
|
RED_COLOR_PYG = RED_COLOR()
|
|
439
466
|
GREEN_COLOR_PYG = GREEN_COLOR()
|
|
440
467
|
BLUE_COLOR_PYG = BLUE_COLOR()
|
|
468
|
+
CYAN_COLOR_PYG = CYAN_COLOR()
|
|
469
|
+
MAGENTA_COLOR_PYG = MAGENTA_COLOR()
|
|
470
|
+
YELLOW_COLOR_PYG = YELLOW_COLOR()
|