rushlib 0.1.0__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.
- rushlib-0.1.0/LICENSE.txt +21 -0
- rushlib-0.1.0/PKG-INFO +26 -0
- rushlib-0.1.0/README.md +1 -0
- rushlib-0.1.0/rushlib/__init__.py +6 -0
- rushlib-0.1.0/rushlib/args.py +12 -0
- rushlib-0.1.0/rushlib/color/__init__.py +203 -0
- rushlib-0.1.0/rushlib/color/_console/__init__.py +53 -0
- rushlib-0.1.0/rushlib/color/util.py +40 -0
- rushlib-0.1.0/rushlib/func.py +83 -0
- rushlib-0.1.0/rushlib/math.py +309 -0
- rushlib-0.1.0/rushlib/message.py +22 -0
- rushlib-0.1.0/rushlib/text.py +64 -0
- rushlib-0.1.0/rushlib.egg-info/PKG-INFO +26 -0
- rushlib-0.1.0/rushlib.egg-info/SOURCES.txt +16 -0
- rushlib-0.1.0/rushlib.egg-info/dependency_links.txt +1 -0
- rushlib-0.1.0/rushlib.egg-info/top_level.txt +1 -0
- rushlib-0.1.0/setup.cfg +4 -0
- rushlib-0.1.0/setup.py +26 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
GPL-3.0 License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 [Ndrzy]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rushlib-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rushlib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: python lib
|
|
5
|
+
Home-page: https://github.com/meatdumplings0019/rushlib
|
|
6
|
+
Author: ndrzy
|
|
7
|
+
Author-email: dandan0019@outlook.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires: colorama
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
Dynamic: requires
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
25
|
+
|
|
26
|
+
# RushLib
|
rushlib-0.1.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# RushLib
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
def parse_args(tokens: list[str], parser: argparse.ArgumentParser) -> Optional[argparse.Namespace]:
|
|
6
|
+
try:
|
|
7
|
+
return parser.parse_args(tokens)
|
|
8
|
+
except SystemExit:
|
|
9
|
+
return None
|
|
10
|
+
except Exception as e:
|
|
11
|
+
print(f"参数解析错误: {str(e)}")
|
|
12
|
+
return None
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from rushlib.color._console import *
|
|
2
|
+
from rushlib.color.util import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MColor:
|
|
6
|
+
CONSOLE_COLOR = ForeWhite()
|
|
7
|
+
|
|
8
|
+
def __init__(self, r: int = 255, g: int = 255, b: int = 255, a: int = 255):
|
|
9
|
+
"""
|
|
10
|
+
增强版的Color, 支持rgb转hex, 可以直接作为pygame的Color
|
|
11
|
+
:param r: R
|
|
12
|
+
:param g: G
|
|
13
|
+
:param b: B
|
|
14
|
+
:param a: A
|
|
15
|
+
"""
|
|
16
|
+
self._r, self._g, self._b, self._a = self.vali_rgba(r, g, b, a)
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def from_hex(hex_str: str = "#FFFFFF"):
|
|
20
|
+
"""
|
|
21
|
+
将16进制颜色代码转为MColor
|
|
22
|
+
:param hex_str: 16进制string
|
|
23
|
+
:return: new MColor
|
|
24
|
+
"""
|
|
25
|
+
r, g, b, a = hex_to_rgb(hex_str)
|
|
26
|
+
return MColor(r, g, b, a)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def vali_rgba(r, g, b, a=255):
|
|
30
|
+
return vali_rgba(r, g, b, a)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def r(self) -> int:
|
|
34
|
+
return self._r
|
|
35
|
+
|
|
36
|
+
@r.setter
|
|
37
|
+
def r(self, value: int) -> None:
|
|
38
|
+
if 0 < value <= 255:
|
|
39
|
+
self._r = value
|
|
40
|
+
|
|
41
|
+
self.r = 255
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def float_r(self) -> float:
|
|
45
|
+
return self.r / 255.0
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def g(self) -> int:
|
|
49
|
+
return self._g
|
|
50
|
+
|
|
51
|
+
@g.setter
|
|
52
|
+
def g(self, value: int) -> None:
|
|
53
|
+
if 0 < value <= 255:
|
|
54
|
+
self._g = value
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
self.g = 255
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def float_g(self) -> float:
|
|
61
|
+
return self.g / 255.0
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def b(self) -> int:
|
|
65
|
+
return self._b
|
|
66
|
+
|
|
67
|
+
@b.setter
|
|
68
|
+
def b(self, value: int) -> None:
|
|
69
|
+
if 0 < value <= 255:
|
|
70
|
+
self._b = value
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
self.b = 255
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def float_b(self) -> float:
|
|
77
|
+
return self.b / 255.0
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def a(self) -> int:
|
|
81
|
+
return self._a
|
|
82
|
+
|
|
83
|
+
@a.setter
|
|
84
|
+
def a(self, value: int) -> None:
|
|
85
|
+
if 0 < value <= 255:
|
|
86
|
+
self._a = value
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
self.a = 255
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def float_a(self) -> float:
|
|
93
|
+
return self.a / 255.0
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def rgb(self) -> tuple[int, int, int]:
|
|
97
|
+
return self.r, self.g, self.b
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def float_rgb(self) -> tuple[float, float, float]:
|
|
101
|
+
return self.float_r, self.float_g, self.float_b
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def rgba(self) -> tuple[int, int, int, int]:
|
|
105
|
+
return self.rgb[0], self.rgb[1], self.rgb[2], self.a
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def float_rgba(self) -> tuple[float, float, float, float]:
|
|
109
|
+
return self.float_rgb[0], self.float_rgb[1], self.float_rgb[2], self.float_a
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def hex(self) -> str:
|
|
113
|
+
return rgb_to_hex(self.r, self.g, self.b)
|
|
114
|
+
|
|
115
|
+
def __iter__(self):
|
|
116
|
+
yield self.r
|
|
117
|
+
yield self.g
|
|
118
|
+
yield self.b
|
|
119
|
+
yield self.a
|
|
120
|
+
|
|
121
|
+
def __len__(self):
|
|
122
|
+
return 4
|
|
123
|
+
|
|
124
|
+
def __getitem__(self, item):
|
|
125
|
+
prop = self.r, self.g, self.b, self.a
|
|
126
|
+
|
|
127
|
+
return prop[item]
|
|
128
|
+
|
|
129
|
+
def __add__(self, other):
|
|
130
|
+
if isinstance(other, MColor):
|
|
131
|
+
r, g, b, a = other
|
|
132
|
+
return MColor(self.r + r, self.g + g, self.b + b, self.a + a)
|
|
133
|
+
|
|
134
|
+
raise TypeError(other)
|
|
135
|
+
|
|
136
|
+
def __sub__(self, other):
|
|
137
|
+
if isinstance(other, MColor):
|
|
138
|
+
r, g, b, a = other
|
|
139
|
+
return MColor(self.r - r, self.g - g, self.b - b, self.a - a)
|
|
140
|
+
|
|
141
|
+
raise TypeError(other)
|
|
142
|
+
|
|
143
|
+
def __repr__(self):
|
|
144
|
+
return f"[MColor r={self.r} g={self.g} b={self.b} a={self.a}]"
|
|
145
|
+
|
|
146
|
+
def __str__(self):
|
|
147
|
+
return f'{self.CONSOLE_COLOR}'
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Black(MColor):
|
|
151
|
+
CONSOLE_COLOR = ForeBlack()
|
|
152
|
+
|
|
153
|
+
def __init__(self, a: int = 255):
|
|
154
|
+
super().__init__(0, 0, 0, a)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class Red(MColor):
|
|
158
|
+
CONSOLE_COLOR = ForeRed()
|
|
159
|
+
|
|
160
|
+
def __init__(self, a: int = 255):
|
|
161
|
+
super().__init__(255, 0, 0, a)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Green(MColor):
|
|
165
|
+
CONSOLE_COLOR = ForeGreen()
|
|
166
|
+
|
|
167
|
+
def __init__(self, a: int = 255):
|
|
168
|
+
super().__init__(0, 255, 0, a)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class Yellow(MColor):
|
|
172
|
+
CONSOLE_COLOR = ForeYellow()
|
|
173
|
+
|
|
174
|
+
def __init__(self, a: int = 255):
|
|
175
|
+
super().__init__(255, 255, 0, a)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Blue(MColor):
|
|
179
|
+
CONSOLE_COLOR = ForeBlue()
|
|
180
|
+
|
|
181
|
+
def __init__(self, a: int = 255):
|
|
182
|
+
super().__init__(0, 0, 255, a)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class Magenta(MColor):
|
|
186
|
+
CONSOLE_COLOR = ForeMagenta()
|
|
187
|
+
|
|
188
|
+
def __init__(self, a: int = 255):
|
|
189
|
+
super().__init__(255, 0, 255, a)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Cyan(MColor):
|
|
193
|
+
CONSOLE_COLOR = ForeCyan()
|
|
194
|
+
|
|
195
|
+
def __init__(self, a: int = 255):
|
|
196
|
+
super().__init__(0, 255, 255, a)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class White(MColor):
|
|
200
|
+
CONSOLE_COLOR = ForeWhite()
|
|
201
|
+
|
|
202
|
+
def __init__(self, a: int = 255):
|
|
203
|
+
super().__init__(255, 255, 255, a)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from colorama import Fore
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ConsoleColor:
|
|
5
|
+
def __init__(self, fore):
|
|
6
|
+
self._fore = fore
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def fore(self):
|
|
10
|
+
return self._fore
|
|
11
|
+
|
|
12
|
+
def __str__(self):
|
|
13
|
+
return self.fore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ForeBlack(ConsoleColor):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__(Fore.BLACK)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ForeRed(ConsoleColor):
|
|
22
|
+
def __init__(self):
|
|
23
|
+
super().__init__(Fore.RED)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ForeGreen(ConsoleColor):
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__(Fore.GREEN)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ForeYellow(ConsoleColor):
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super().__init__(Fore.YELLOW)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ForeBlue(ConsoleColor):
|
|
37
|
+
def __init__(self):
|
|
38
|
+
super().__init__(Fore.BLUE)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ForeMagenta(ConsoleColor):
|
|
42
|
+
def __init__(self):
|
|
43
|
+
super().__init__(Fore.MAGENTA)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ForeCyan(ConsoleColor):
|
|
47
|
+
def __init__(self):
|
|
48
|
+
super().__init__(Fore.CYAN)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ForeWhite(ConsoleColor):
|
|
52
|
+
def __init__(self):
|
|
53
|
+
super().__init__(Fore.WHITE)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
def vali_rgba(r, g, b, a=255):
|
|
2
|
+
return (
|
|
3
|
+
min(255, max(0, r)),
|
|
4
|
+
min(255, max(0, g)),
|
|
5
|
+
min(255, max(0, b)),
|
|
6
|
+
min(255, max(0, a))
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
def hex_to_rgb(hex_str: str) -> tuple[int, int, int, int]:
|
|
10
|
+
r, g, b, a = 0, 0, 0, 255
|
|
11
|
+
|
|
12
|
+
hex_str = hex_str.lstrip('#')
|
|
13
|
+
|
|
14
|
+
if len(hex_str) not in (3, 4, 6, 8):
|
|
15
|
+
raise ValueError("十六进制颜色代码必须是3, 4, 6或8个字符")
|
|
16
|
+
|
|
17
|
+
if len(hex_str) == 3:
|
|
18
|
+
hex_str = ''.join([c * 2 for c in hex_str])
|
|
19
|
+
elif len(hex_str) == 4:
|
|
20
|
+
hex_str = ''.join([c * 2 for c in hex_str])
|
|
21
|
+
alpha_hex = hex_str[6:8]
|
|
22
|
+
a = int(alpha_hex, 16)
|
|
23
|
+
hex_str = hex_str[0:6]
|
|
24
|
+
|
|
25
|
+
if len(hex_str) == 8:
|
|
26
|
+
alpha_hex = hex_str[6:8]
|
|
27
|
+
a = int(alpha_hex, 16)
|
|
28
|
+
hex_str = hex_str[0:6]
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
r = int(hex_str[0:2], 16)
|
|
32
|
+
g = int(hex_str[2:4], 16)
|
|
33
|
+
b = int(hex_str[4:6], 16)
|
|
34
|
+
except ValueError:
|
|
35
|
+
raise ValueError("无效的十六进制颜色代码")
|
|
36
|
+
|
|
37
|
+
return r, g, b, a
|
|
38
|
+
|
|
39
|
+
def rgb_to_hex(r, g, b) -> str:
|
|
40
|
+
return '#%02x%02x%02x' % vali_rgba(r, g, b)[0:3]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from inspect import Parameter
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Callable, Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def smart_call(func: Callable, *args: Any, **kwargs: Any) -> Any:
|
|
8
|
+
"""
|
|
9
|
+
智能调用函数,根据目标函数的参数签名自动匹配传入的参数
|
|
10
|
+
|
|
11
|
+
:param func: 要调用的目标函数
|
|
12
|
+
:param *args: 位置参数
|
|
13
|
+
:param **kwargs: 关键字参数
|
|
14
|
+
|
|
15
|
+
:return: 目标函数的执行结果
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
# 获取函数签名
|
|
19
|
+
sig = inspect.signature(func)
|
|
20
|
+
except (ValueError, TypeError):
|
|
21
|
+
# 无法获取签名时直接尝试调用
|
|
22
|
+
return func(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
# 准备参数绑定
|
|
25
|
+
bound_args = {}
|
|
26
|
+
params = sig.parameters
|
|
27
|
+
|
|
28
|
+
# 处理位置参数
|
|
29
|
+
args_iter = iter(args)
|
|
30
|
+
for name, param in params.items():
|
|
31
|
+
if param.kind in (Parameter.POSITIONAL_ONLY,
|
|
32
|
+
Parameter.POSITIONAL_OR_KEYWORD,
|
|
33
|
+
Parameter.KEYWORD_ONLY):
|
|
34
|
+
# 尝试从位置参数获取值
|
|
35
|
+
if args_iter:
|
|
36
|
+
try:
|
|
37
|
+
bound_args[name] = next(args_iter)
|
|
38
|
+
continue
|
|
39
|
+
except StopIteration:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
# 如果位置参数用完,尝试从关键字参数获取
|
|
43
|
+
if name in kwargs:
|
|
44
|
+
bound_args[name] = kwargs[name]
|
|
45
|
+
elif param.default is not Parameter.empty:
|
|
46
|
+
# 使用默认值
|
|
47
|
+
bound_args[name] = param.default
|
|
48
|
+
else:
|
|
49
|
+
# 必需参数缺失
|
|
50
|
+
raise TypeError(f"Missing required argument: {name}")
|
|
51
|
+
|
|
52
|
+
elif param.kind == Parameter.VAR_POSITIONAL:
|
|
53
|
+
# 处理 *args 参数
|
|
54
|
+
bound_args[name] = tuple(args_iter)
|
|
55
|
+
args_iter = None # 标记位置参数已耗尽
|
|
56
|
+
|
|
57
|
+
# 处理剩余的关键字参数
|
|
58
|
+
for name, param in params.items():
|
|
59
|
+
if param.kind == Parameter.VAR_KEYWORD:
|
|
60
|
+
# 处理 **kwargs 参数
|
|
61
|
+
bound_args[name] = {
|
|
62
|
+
k: v for k, v in kwargs.items()
|
|
63
|
+
if k not in bound_args
|
|
64
|
+
}
|
|
65
|
+
break
|
|
66
|
+
elif name not in bound_args and param.kind == Parameter.KEYWORD_ONLY:
|
|
67
|
+
# 处理仅关键字参数
|
|
68
|
+
if name in kwargs:
|
|
69
|
+
bound_args[name] = kwargs[name]
|
|
70
|
+
elif param.default is not Parameter.empty:
|
|
71
|
+
bound_args[name] = param.default
|
|
72
|
+
else:
|
|
73
|
+
raise TypeError(f"Missing required keyword argument: {name}")
|
|
74
|
+
|
|
75
|
+
return func(**bound_args)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def adapt_args(func):
|
|
79
|
+
@wraps(func)
|
|
80
|
+
def wrapper(*args, **kwargs):
|
|
81
|
+
return smart_call(func, *args, **kwargs)
|
|
82
|
+
|
|
83
|
+
return wrapper
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
Number = Union[int, float]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Vector2:
|
|
8
|
+
"""二维向量类"""
|
|
9
|
+
|
|
10
|
+
__slots__ = ('x', 'y')
|
|
11
|
+
|
|
12
|
+
def __init__(self, x: Number = 0, y: Number = 0):
|
|
13
|
+
self.x = float(x)
|
|
14
|
+
self.y = float(y)
|
|
15
|
+
|
|
16
|
+
def __repr__(self) -> str:
|
|
17
|
+
return f"Vec2({self.x}, {self.y})"
|
|
18
|
+
|
|
19
|
+
def __str__(self) -> str:
|
|
20
|
+
return f"({self.x}, {self.y})"
|
|
21
|
+
|
|
22
|
+
def __eq__(self, other: object) -> bool:
|
|
23
|
+
if not isinstance(other, Vector2):
|
|
24
|
+
return False
|
|
25
|
+
return math.isclose(self.x, other.x) and math.isclose(self.y, other.y)
|
|
26
|
+
|
|
27
|
+
def __ne__(self, other: object) -> bool:
|
|
28
|
+
return not self.__eq__(other)
|
|
29
|
+
|
|
30
|
+
def __hash__(self) -> int:
|
|
31
|
+
return hash((self.x, self.y))
|
|
32
|
+
|
|
33
|
+
def __add__(self, other: 'Vector2') -> 'Vector2':
|
|
34
|
+
return Vector2(self.x + other.x, self.y + other.y)
|
|
35
|
+
|
|
36
|
+
def __sub__(self, other: 'Vector2') -> 'Vector2':
|
|
37
|
+
return Vector2(self.x - other.x, self.y - other.y)
|
|
38
|
+
|
|
39
|
+
def __mul__(self, scalar: Number) -> 'Vector2':
|
|
40
|
+
return Vector2(self.x * scalar, self.y * scalar)
|
|
41
|
+
|
|
42
|
+
def __rmul__(self, scalar: Number) -> 'Vector2':
|
|
43
|
+
return self.__mul__(scalar)
|
|
44
|
+
|
|
45
|
+
def __truediv__(self, scalar: Number) -> 'Vector2':
|
|
46
|
+
if abs(scalar) < 1e-10:
|
|
47
|
+
raise ZeroDivisionError("Division by near-zero scalar")
|
|
48
|
+
return Vector2(self.x / scalar, self.y / scalar)
|
|
49
|
+
|
|
50
|
+
def __neg__(self) -> 'Vector2':
|
|
51
|
+
return Vector2(-self.x, -self.y)
|
|
52
|
+
|
|
53
|
+
def __abs__(self) -> float:
|
|
54
|
+
return math.sqrt(self.x * self.x + self.y * self.y)
|
|
55
|
+
|
|
56
|
+
def dot(self, other: 'Vector2') -> float:
|
|
57
|
+
"""点积"""
|
|
58
|
+
return self.x * other.x + self.y * other.y
|
|
59
|
+
|
|
60
|
+
def cross(self, other: 'Vector2') -> float:
|
|
61
|
+
"""叉积(标量)"""
|
|
62
|
+
return self.x * other.y - self.y * other.x
|
|
63
|
+
|
|
64
|
+
def length(self) -> float:
|
|
65
|
+
"""向量长度"""
|
|
66
|
+
return abs(self)
|
|
67
|
+
|
|
68
|
+
def length_squared(self) -> float:
|
|
69
|
+
"""向量长度的平方"""
|
|
70
|
+
return self.x * self.x + self.y * self.y
|
|
71
|
+
|
|
72
|
+
def normalized(self) -> 'Vector2':
|
|
73
|
+
"""单位向量"""
|
|
74
|
+
length = self.length()
|
|
75
|
+
if length < 1e-10:
|
|
76
|
+
return Vector2(0, 0)
|
|
77
|
+
return self / length
|
|
78
|
+
|
|
79
|
+
def normalize(self) -> None:
|
|
80
|
+
"""将向量单位化(原地操作)"""
|
|
81
|
+
length = self.length()
|
|
82
|
+
if length < 1e-10:
|
|
83
|
+
self.x, self.y = 0, 0
|
|
84
|
+
else:
|
|
85
|
+
self.x /= length
|
|
86
|
+
self.y /= length
|
|
87
|
+
|
|
88
|
+
def distance_to(self, other: 'Vector2') -> float:
|
|
89
|
+
"""到另一个向量的距离"""
|
|
90
|
+
return (self - other).length()
|
|
91
|
+
|
|
92
|
+
def angle(self) -> float:
|
|
93
|
+
"""向量的角度(弧度)"""
|
|
94
|
+
return math.atan2(self.y, self.x)
|
|
95
|
+
|
|
96
|
+
def rotated(self, angle: float) -> 'Vector2':
|
|
97
|
+
"""旋转指定弧度后的向量"""
|
|
98
|
+
cos_a = math.cos(angle)
|
|
99
|
+
sin_a = math.sin(angle)
|
|
100
|
+
return Vector2(
|
|
101
|
+
self.x * cos_a - self.y * sin_a,
|
|
102
|
+
self.x * sin_a + self.y * cos_a
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def rotate(self, angle: float) -> None:
|
|
106
|
+
"""旋转指定弧度(原地操作)"""
|
|
107
|
+
cos_a = math.cos(angle)
|
|
108
|
+
sin_a = math.sin(angle)
|
|
109
|
+
x = self.x * cos_a - self.y * sin_a
|
|
110
|
+
y = self.x * sin_a + self.y * cos_a
|
|
111
|
+
self.x, self.y = x, y
|
|
112
|
+
|
|
113
|
+
def lerp(self, other: 'Vector2', t: float) -> 'Vector2':
|
|
114
|
+
"""线性插值"""
|
|
115
|
+
t = max(0.0, min(1.0, t)) # 限制t在[0,1]范围内
|
|
116
|
+
return Vector2(
|
|
117
|
+
self.x + (other.x - self.x) * t,
|
|
118
|
+
self.y + (other.y - self.y) * t
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def copy(self) -> 'Vector2':
|
|
122
|
+
"""返回向量的副本"""
|
|
123
|
+
return Vector2(self.x, self.y)
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def from_angle(cls, angle: float, length: float = 1.0) -> 'Vector2':
|
|
127
|
+
"""从角度和长度创建向量"""
|
|
128
|
+
return cls(math.cos(angle) * length, math.sin(angle) * length)
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def zero(cls) -> 'Vector2':
|
|
132
|
+
"""零向量"""
|
|
133
|
+
return cls(0, 0)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def one(cls) -> 'Vector2':
|
|
137
|
+
"""全1向量"""
|
|
138
|
+
return cls(1, 1)
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def up(cls) -> 'Vector2':
|
|
142
|
+
"""上向量"""
|
|
143
|
+
return cls(0, 1)
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def down(cls) -> 'Vector2':
|
|
147
|
+
"""下向量"""
|
|
148
|
+
return cls(0, -1)
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def left(cls) -> 'Vector2':
|
|
152
|
+
"""左向量"""
|
|
153
|
+
return cls(-1, 0)
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def right(cls) -> 'Vector2':
|
|
157
|
+
"""右向量"""
|
|
158
|
+
return cls(1, 0)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class Vector3:
|
|
162
|
+
"""三维向量类"""
|
|
163
|
+
|
|
164
|
+
__slots__ = ('x', 'y', 'z')
|
|
165
|
+
|
|
166
|
+
def __init__(self, x: Number = 0, y: Number = 0, z: Number = 0):
|
|
167
|
+
self.x = float(x)
|
|
168
|
+
self.y = float(y)
|
|
169
|
+
self.z = float(z)
|
|
170
|
+
|
|
171
|
+
def __repr__(self) -> str:
|
|
172
|
+
return f"Vec3({self.x}, {self.y}, {self.z})"
|
|
173
|
+
|
|
174
|
+
def __str__(self) -> str:
|
|
175
|
+
return f"({self.x}, {self.y}, {self.z})"
|
|
176
|
+
|
|
177
|
+
def __eq__(self, other: object) -> bool:
|
|
178
|
+
if not isinstance(other, Vector3):
|
|
179
|
+
return False
|
|
180
|
+
return (math.isclose(self.x, other.x) and
|
|
181
|
+
math.isclose(self.y, other.y) and
|
|
182
|
+
math.isclose(self.z, other.z))
|
|
183
|
+
|
|
184
|
+
def __ne__(self, other: object) -> bool:
|
|
185
|
+
return not self.__eq__(other)
|
|
186
|
+
|
|
187
|
+
def __hash__(self) -> int:
|
|
188
|
+
return hash((self.x, self.y, self.z))
|
|
189
|
+
|
|
190
|
+
def __add__(self, other: 'Vector3') -> 'Vector3':
|
|
191
|
+
return Vector3(self.x + other.x, self.y + other.y, self.z + other.z)
|
|
192
|
+
|
|
193
|
+
def __sub__(self, other: 'Vector3') -> 'Vector3':
|
|
194
|
+
return Vector3(self.x - other.x, self.y - other.y, self.z - other.z)
|
|
195
|
+
|
|
196
|
+
def __mul__(self, scalar: Number) -> 'Vector3':
|
|
197
|
+
return Vector3(self.x * scalar, self.y * scalar, self.z * scalar)
|
|
198
|
+
|
|
199
|
+
def __rmul__(self, scalar: Number) -> 'Vector3':
|
|
200
|
+
return self.__mul__(scalar)
|
|
201
|
+
|
|
202
|
+
def __truediv__(self, scalar: Number) -> 'Vector3':
|
|
203
|
+
if abs(scalar) < 1e-10:
|
|
204
|
+
raise ZeroDivisionError("Division by near-zero scalar")
|
|
205
|
+
return Vector3(self.x / scalar, self.y / scalar, self.z / scalar)
|
|
206
|
+
|
|
207
|
+
def __neg__(self) -> 'Vector3':
|
|
208
|
+
return Vector3(-self.x, -self.y, -self.z)
|
|
209
|
+
|
|
210
|
+
def __abs__(self) -> float:
|
|
211
|
+
return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
|
|
212
|
+
|
|
213
|
+
def dot(self, other: 'Vector3') -> float:
|
|
214
|
+
"""点积"""
|
|
215
|
+
return self.x * other.x + self.y * other.y + self.z * other.z
|
|
216
|
+
|
|
217
|
+
def cross(self, other: 'Vector3') -> 'Vector3':
|
|
218
|
+
"""叉积(向量)"""
|
|
219
|
+
return Vector3(
|
|
220
|
+
self.y * other.z - self.z * other.y,
|
|
221
|
+
self.z * other.x - self.x * other.z,
|
|
222
|
+
self.x * other.y - self.y * other.x
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def length(self) -> float:
|
|
226
|
+
"""向量长度"""
|
|
227
|
+
return abs(self)
|
|
228
|
+
|
|
229
|
+
def length_squared(self) -> float:
|
|
230
|
+
"""向量长度的平方"""
|
|
231
|
+
return self.x * self.x + self.y * self.y + self.z * self.z
|
|
232
|
+
|
|
233
|
+
def normalized(self) -> 'Vector3':
|
|
234
|
+
"""单位向量"""
|
|
235
|
+
length = self.length()
|
|
236
|
+
if length < 1e-10:
|
|
237
|
+
return Vector3(0, 0, 0)
|
|
238
|
+
return self / length
|
|
239
|
+
|
|
240
|
+
def normalize(self) -> None:
|
|
241
|
+
"""将向量单位化(原地操作)"""
|
|
242
|
+
length = self.length()
|
|
243
|
+
if length < 1e-10:
|
|
244
|
+
self.x, self.y, self.z = 0, 0, 0
|
|
245
|
+
else:
|
|
246
|
+
self.x /= length
|
|
247
|
+
self.y /= length
|
|
248
|
+
self.z /= length
|
|
249
|
+
|
|
250
|
+
def distance_to(self, other: 'Vector3') -> float:
|
|
251
|
+
"""到另一个向量的距离"""
|
|
252
|
+
return (self - other).length()
|
|
253
|
+
|
|
254
|
+
def lerp(self, other: 'Vector3', t: float) -> 'Vector3':
|
|
255
|
+
"""线性插值"""
|
|
256
|
+
t = max(0.0, min(1.0, t)) # 限制t在[0,1]范围内
|
|
257
|
+
return Vector3(
|
|
258
|
+
self.x + (other.x - self.x) * t,
|
|
259
|
+
self.y + (other.y - self.y) * t,
|
|
260
|
+
self.z + (other.z - self.z) * t
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def copy(self) -> 'Vector3':
|
|
264
|
+
"""返回向量的副本"""
|
|
265
|
+
return Vector3(self.x, self.y, self.z)
|
|
266
|
+
|
|
267
|
+
@classmethod
|
|
268
|
+
def zero(cls) -> 'Vector3':
|
|
269
|
+
"""零向量"""
|
|
270
|
+
return cls(0, 0, 0)
|
|
271
|
+
|
|
272
|
+
@classmethod
|
|
273
|
+
def one(cls) -> 'Vector3':
|
|
274
|
+
"""全1向量"""
|
|
275
|
+
return cls(1, 1, 1)
|
|
276
|
+
|
|
277
|
+
@classmethod
|
|
278
|
+
def up(cls) -> 'Vector3':
|
|
279
|
+
"""上向量"""
|
|
280
|
+
return cls(0, 1, 0)
|
|
281
|
+
|
|
282
|
+
@classmethod
|
|
283
|
+
def down(cls) -> 'Vector3':
|
|
284
|
+
"""下向量"""
|
|
285
|
+
return cls(0, -1, 0)
|
|
286
|
+
|
|
287
|
+
@classmethod
|
|
288
|
+
def left(cls) -> 'Vector3':
|
|
289
|
+
"""左向量"""
|
|
290
|
+
return cls(-1, 0, 0)
|
|
291
|
+
|
|
292
|
+
@classmethod
|
|
293
|
+
def right(cls) -> 'Vector3':
|
|
294
|
+
"""右向量"""
|
|
295
|
+
return cls(1, 0, 0)
|
|
296
|
+
|
|
297
|
+
@classmethod
|
|
298
|
+
def forward(cls) -> 'Vector3':
|
|
299
|
+
"""前向量"""
|
|
300
|
+
return cls(0, 0, 1)
|
|
301
|
+
|
|
302
|
+
@classmethod
|
|
303
|
+
def back(cls) -> 'Vector3':
|
|
304
|
+
"""后向量"""
|
|
305
|
+
return cls(0, 0, -1)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
vec2 = Vector2
|
|
309
|
+
vec3 = Vector3
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import TypeVar, Generic
|
|
2
|
+
|
|
3
|
+
from rushlib.text import Error
|
|
4
|
+
|
|
5
|
+
T = TypeVar('T')
|
|
6
|
+
|
|
7
|
+
class Message(Generic[T]):
|
|
8
|
+
def __init__(self, var: T, msg: Exception | str | Error = None):
|
|
9
|
+
self.var: T = var
|
|
10
|
+
|
|
11
|
+
if isinstance(msg, Error):
|
|
12
|
+
tmp = msg
|
|
13
|
+
elif isinstance(msg, (self, Exception)):
|
|
14
|
+
tmp = Error(msg)
|
|
15
|
+
else:
|
|
16
|
+
raise TypeError(msg)
|
|
17
|
+
|
|
18
|
+
self.msg: Error = tmp
|
|
19
|
+
|
|
20
|
+
def __iter__(self):
|
|
21
|
+
yield self.var
|
|
22
|
+
yield self.msg
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from colorama import Style
|
|
2
|
+
|
|
3
|
+
from rushlib.color import MColor, White, Red, Yellow
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Text:
|
|
7
|
+
def __init__(self, text="", color: MColor = White()):
|
|
8
|
+
self._text = text
|
|
9
|
+
self._color = color
|
|
10
|
+
|
|
11
|
+
def sub_string(self, start: int, end: int) -> str:
|
|
12
|
+
return self.text[start:end]
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def text(self):
|
|
16
|
+
return self._text
|
|
17
|
+
|
|
18
|
+
@text.setter
|
|
19
|
+
def text(self, text):
|
|
20
|
+
self._text = text
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def color(self):
|
|
24
|
+
return self._color
|
|
25
|
+
|
|
26
|
+
@color.setter
|
|
27
|
+
def color(self, color):
|
|
28
|
+
self._color = color
|
|
29
|
+
|
|
30
|
+
def __add__(self, other):
|
|
31
|
+
if isinstance(other, Text):
|
|
32
|
+
return Text(self.text + other.text, self.color)
|
|
33
|
+
|
|
34
|
+
if isinstance(other, str):
|
|
35
|
+
return Text(self.text + other, self.color)
|
|
36
|
+
|
|
37
|
+
raise TypeError(other)
|
|
38
|
+
|
|
39
|
+
def __len__(self):
|
|
40
|
+
return len(self.text)
|
|
41
|
+
|
|
42
|
+
def __getitem__(self, item):
|
|
43
|
+
return self.text[item]
|
|
44
|
+
|
|
45
|
+
def __iter__(self):
|
|
46
|
+
return self.text.__iter__()
|
|
47
|
+
|
|
48
|
+
def __str__(self):
|
|
49
|
+
return f'{self.color}{self.text}{Style.RESET_ALL}'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Error(Text):
|
|
53
|
+
def __init__(self, text):
|
|
54
|
+
super().__init__(text, Red())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Info(Text):
|
|
58
|
+
def __init__(self, text):
|
|
59
|
+
super().__init__(text, White())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Warn(Text):
|
|
63
|
+
def __init__(self, text):
|
|
64
|
+
super().__init__(text, Yellow())
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rushlib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: python lib
|
|
5
|
+
Home-page: https://github.com/meatdumplings0019/rushlib
|
|
6
|
+
Author: ndrzy
|
|
7
|
+
Author-email: dandan0019@outlook.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires: colorama
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
Dynamic: requires
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
25
|
+
|
|
26
|
+
# RushLib
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
rushlib/__init__.py
|
|
5
|
+
rushlib/args.py
|
|
6
|
+
rushlib/func.py
|
|
7
|
+
rushlib/math.py
|
|
8
|
+
rushlib/message.py
|
|
9
|
+
rushlib/text.py
|
|
10
|
+
rushlib.egg-info/PKG-INFO
|
|
11
|
+
rushlib.egg-info/SOURCES.txt
|
|
12
|
+
rushlib.egg-info/dependency_links.txt
|
|
13
|
+
rushlib.egg-info/top_level.txt
|
|
14
|
+
rushlib/color/__init__.py
|
|
15
|
+
rushlib/color/util.py
|
|
16
|
+
rushlib/color/_console/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rushlib
|
rushlib-0.1.0/setup.cfg
ADDED
rushlib-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name='rushlib',
|
|
8
|
+
version='0.1.0',
|
|
9
|
+
packages=find_packages(),
|
|
10
|
+
requires=[
|
|
11
|
+
'colorama'
|
|
12
|
+
],
|
|
13
|
+
description='python lib',
|
|
14
|
+
author='ndrzy',
|
|
15
|
+
author_email='dandan0019@outlook.com',
|
|
16
|
+
python_requires='>=3.10',
|
|
17
|
+
long_description=long_description,
|
|
18
|
+
long_description_content_type="text/markdown",
|
|
19
|
+
url="https://github.com/meatdumplings0019/rushlib",
|
|
20
|
+
|
|
21
|
+
classifiers=[
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
]
|
|
26
|
+
)
|