Update require and some format

This commit is contained in:
shenjack 2024-05-26 14:01:48 +08:00
parent 03426cd263
commit 4e858f6af3
Signed by: shenjack
GPG Key ID: 7B1134A979775551
10 changed files with 231 additions and 145 deletions

View File

@ -10,7 +10,7 @@ from Difficult_Rocket.api.types import Options, Version
sdk_version = Version("0.9.1.0") # SDK 版本
build_version = Version("3.0.0.0") # 编译文件版本(与游戏本体无关)
api_version = Version("0.1.2.1") # API 版本
api_version = Version("0.1.2.2") # API 版本
__version__ = sdk_version
__all__ = [

View File

@ -16,7 +16,6 @@ from decimal import Decimal
from typing import Callable, Dict, List, TYPE_CHECKING, Type
# third function
import tomli
import tomli_w
import pyglet
@ -210,7 +209,7 @@ def _call_screen_after(func: Callable) -> Callable:
traceback.print_exc()
return result
warped.__signature__ = inspect.signature(func) # type: ignore
warped.__signature__ = inspect.signature(func) # type: ignore
return warped
@ -237,7 +236,7 @@ def _call_screen_before(func: Callable) -> Callable:
result = func(self, *args, **kwargs)
return result
warped.__signature__ = inspect.signature(func) # type: ignore
warped.__signature__ = inspect.signature(func) # type: ignore
return warped
@ -359,7 +358,6 @@ class ClientWindow(Window):
def draw_call(self, dt: float):
self.switch_to()
# self.logger.debug(f"draw call {dt}")
self.on_draw(dt)
self.flip()
@ -436,7 +434,8 @@ class ClientWindow(Window):
self.logger.info(tr().language_set_to())
except LanguageNotFound:
self.logger.info(
tr().language_available().format(os.listdir("./config/lang")), tag="command"
tr().language_available().format(os.listdir("./config/lang")),
tag="command",
)
self.save_info()
elif command.find("mods"):
@ -444,14 +443,17 @@ class ClientWindow(Window):
self.logger.info(tr().mod.list())
for mod in self.game.mod_manager.loaded_mod_modules.values():
self.logger.info(
f"mod: {mod.name} id: {mod.mod_id} version: {mod.version}", tag="command"
f"mod: {mod.name} id: {mod.mod_id} version: {mod.version}",
tag="command",
)
elif command.find("reload"):
if not len(command.text) == 0:
print(f"reload mod: |{command.text}|")
self.game.mod_manager.reload_mod(command.text, game=self.game)
else:
self.logger.info(tr().window.command.mods.reload.no_mod_id(), tag="command")
self.logger.info(
tr().window.command.mods.reload.no_mod_id(), tag="command"
)
@_call_screen_after
def on_message(self, message: line.CommandText):
@ -498,7 +500,8 @@ class ClientWindow(Window):
self.logger.debug(
tr()
.window.mouse.press()
.format([x, y], tr().window.mouse[mouse.buttons_string(button)]()), tag="mouse"
.format([x, y], tr().window.mouse[mouse.buttons_string(button)]()),
tag="mouse",
)
@_call_screen_after
@ -506,13 +509,14 @@ class ClientWindow(Window):
self.logger.debug(
tr()
.window.mouse.release()
.format([x, y], tr().window.mouse[mouse.buttons_string(button)]()), tag="mouse"
.format([x, y], tr().window.mouse[mouse.buttons_string(button)]()),
tag="mouse",
)
@_call_screen_after
def on_key_press(self, symbol, modifiers) -> None:
if symbol == key.ESCAPE and not (
modifiers & ~(key.MOD_NUMLOCK | key.MOD_CAPSLOCK | key.MOD_SCROLLLOCK)
modifiers & ~(key.MOD_NUMLOCK | key.MOD_CAPSLOCK | key.MOD_SCROLLLOCK)
):
self.dispatch_event("on_close", "window")
if symbol == key.SLASH:
@ -520,7 +524,8 @@ class ClientWindow(Window):
self.logger.debug(
tr()
.window.key.press()
.format(key.symbol_string(symbol), key.modifiers_string(modifiers)), tag="key"
.format(key.symbol_string(symbol), key.modifiers_string(modifiers)),
tag="key",
)
@_call_screen_after
@ -528,7 +533,8 @@ class ClientWindow(Window):
self.logger.debug(
tr()
.window.key.release()
.format(key.symbol_string(symbol), key.modifiers_string(modifiers)), tag="key"
.format(key.symbol_string(symbol), key.modifiers_string(modifiers)),
tag="key",
)
@_call_screen_after
@ -552,14 +558,18 @@ class ClientWindow(Window):
@_call_screen_after
def on_text_motion_select(self, motion):
motion_string = key.motion_string(motion)
self.logger.debug(tr().window.text.motion_select().format(motion_string), tag="text")
self.logger.debug(
tr().window.text.motion_select().format(motion_string), tag="text"
)
@_call_screen_before
def on_close(self, source: str = "window") -> None:
self.game.dispatch_mod_event(
"on_close", game=self.game, client=self, source=source
)
self.logger.info(tr().window.game.stop_get().format(tr().game[source]()), tag="window")
self.logger.info(
tr().window.game.stop_get().format(tr().game[source]()), tag="window"
)
self.logger.info(tr().window.game.stop(), tag="window")
# self.fps_log.check_list = False
DR_runtime.running = False

View File

@ -30,11 +30,12 @@ class BaseButtonTheme:
"""
按钮的风格
"""
name = "BaseButtonTheme"
def __init__(self, x: int, y: int,
width: int, height: int,
batch: Batch, group: Group):
def __init__(
self, x: int, y: int, width: int, height: int, batch: Batch, group: Group
):
self.batch = batch
self.group = group
self.x = x
@ -74,10 +75,16 @@ class MinecraftWikiButtonTheme(BaseButtonTheme):
name = "MinecraftWikiButtonTheme"
def __init__(self, x: int, y: int,
width: int, height: int,
batch: Batch, group: Group,
theme: dict = None):
def __init__(
self,
x: int,
y: int,
width: int,
height: int,
batch: Batch,
group: Group,
theme: dict = None,
):
super().__init__(x, y, width, height, batch, group)
self.batch = batch
self.group = group
@ -94,38 +101,81 @@ class MinecraftWikiButtonTheme(BaseButtonTheme):
list_pad = 4 # 下巴 4px
if theme is None:
theme = {}
pop_out = theme.get('pop_out', False)
pop_out = theme.get("pop_out", False)
if pop_out:
# 主背景
self.back_ground = Rectangle(x=x + (pad * 2), y=y + (pad * 2) + list_pad,
width=width - (pad * 4), height=height - (pad * 4) - list_pad,
color=a, batch=batch, group=Group(order=3, parent=group))
self.back_ground = Rectangle(
x=x + (pad * 2),
y=y + (pad * 2) + list_pad,
width=width - (pad * 4),
height=height - (pad * 4) - list_pad,
color=a,
batch=batch,
group=Group(order=3, parent=group),
)
# 左上方向的覆盖
self.cover_back = Rectangle(x=x + pad, y=y + pad + list_pad,
width=width - (pad * 2), height=height - (pad * 2) - list_pad,
color=b, batch=batch, group=Group(order=1, parent=group))
self.cover_back = Rectangle(
x=x + pad,
y=y + pad + list_pad,
width=width - (pad * 2),
height=height - (pad * 2) - list_pad,
color=b,
batch=batch,
group=Group(order=1, parent=group),
)
# 右下方向的覆盖
self.cover_back2 = Rectangle(x=x + (pad * 2), y=y + pad + list_pad,
width=width - (pad * 3), height=height - (pad * 3) - list_pad,
color=c, batch=batch, group=Group(order=2, parent=group))
self.cover_back2 = Rectangle(
x=x + (pad * 2),
y=y + pad + list_pad,
width=width - (pad * 3),
height=height - (pad * 3) - list_pad,
color=c,
batch=batch,
group=Group(order=2, parent=group),
)
else:
# 主背景
self.back_ground = Rectangle(x=x + (pad * 2), y=y + (pad * 2) + list_pad,
width=width - (pad * 4), height=height - (pad * 4) - list_pad,
color=c, batch=batch, group=Group(order=3, parent=group))
self.back_ground = Rectangle(
x=x + (pad * 2),
y=y + (pad * 2) + list_pad,
width=width - (pad * 4),
height=height - (pad * 4) - list_pad,
color=c,
batch=batch,
group=Group(order=3, parent=group),
)
# 左上方向的覆盖
self.cover_back = Rectangle(x=x + pad, y=y + pad + list_pad,
width=width - (pad * 2), height=height - (pad * 2) - list_pad,
color=a, batch=batch, group=Group(order=2, parent=group))
self.cover_back = Rectangle(
x=x + pad,
y=y + pad + list_pad,
width=width - (pad * 2),
height=height - (pad * 2) - list_pad,
color=a,
batch=batch,
group=Group(order=2, parent=group),
)
# 右下方向的覆盖
self.cover_back2 = Rectangle(x=x + pad, y=y + (pad * 2) + list_pad,
width=width - (pad * 3), height=height - (pad * 3) - list_pad,
color=b, batch=batch, group=Group(order=1, parent=group))
self.cover_back2 = Rectangle(
x=x + pad,
y=y + (pad * 2) + list_pad,
width=width - (pad * 3),
height=height - (pad * 3) - list_pad,
color=b,
batch=batch,
group=Group(order=1, parent=group),
)
# 下巴的框
self.list_back = BorderedRectangle(x=x, y=y,
width=width, height=height,
border=pad, border_color=(0, 0, 0, 255),
color=e, batch=batch, group=Group(order=0, parent=group))
self.list_back = BorderedRectangle(
x=x,
y=y,
width=width,
height=height,
border=pad,
border_color=(0, 0, 0, 255),
color=e,
batch=batch,
group=Group(order=0, parent=group),
)
self.a = a
self.b = b
self.c = c
@ -138,7 +188,7 @@ class MinecraftWikiButtonTheme(BaseButtonTheme):
self.pad = pad
self.list_pad = list_pad
self.pop_out = pop_out
self.drag_list = theme.get('drag_list', False)
self.drag_list = theme.get("drag_list", False)
def on_enable(self, x: int, y: int, button):
if self.pop_out:
@ -150,8 +200,12 @@ class MinecraftWikiButtonTheme(BaseButtonTheme):
self.cover_back.color = self.touch_a
self.cover_back2.color = self.touch_b
if self.drag_list:
button.text_label.y = (self.y + (self.height - button.font_height) // 2 + (button.font_height * 0.2) +
(self.list_pad // 2))
button.text_label.y = (
self.y
+ (self.height - button.font_height) // 2
+ (button.font_height * 0.2)
+ (self.list_pad // 2)
)
self.back_ground.y = self.y + (self.pad * 2)
self.back_ground.height = self.height - (self.pad * 4)
self.cover_back.y = self.y + self.pad
@ -159,8 +213,12 @@ class MinecraftWikiButtonTheme(BaseButtonTheme):
self.cover_back2.y = self.y + self.pad
self.cover_back2.height = self.height - (self.pad * 3)
else:
button.text_label.y = (self.y + (self.height - button.font_height) // 2 + (button.font_height * 0.2)
+ self.list_pad)
button.text_label.y = (
self.y
+ (self.height - button.font_height) // 2
+ (button.font_height * 0.2)
+ self.list_pad
)
self.enable = True
def on_disable(self, button) -> None:
@ -179,15 +237,23 @@ class MinecraftWikiButtonTheme(BaseButtonTheme):
self.cover_back.height = self.height - (self.pad * 2) - self.list_pad
self.cover_back2.y = self.y + self.pad + self.list_pad
self.cover_back2.height = self.height - (self.pad * 3) - self.list_pad
button.text_label.y = (self.y + (self.height - button.font_height) // 2 + (button.font_height * 0.2)
+ self.list_pad)
button.text_label.y = (
self.y
+ (self.height - button.font_height) // 2
+ (button.font_height * 0.2)
+ self.list_pad
)
self.enable = False
def on_update(self, button) -> None:
super().on_update(button)
if self.enable and self.drag_list:
button.text_label.y = (self.y + (self.height - button.font_height) // 2 + (button.font_height * 0.2)
+ self.list_pad // 2)
button.text_label.y = (
self.y
+ (self.height - button.font_height) // 2
+ (button.font_height * 0.2)
+ self.list_pad // 2
)
self.back_ground.position = self.x + (self.pad * 2), self.y + (self.pad * 2)
self.back_ground.height = self.height - (self.pad * 4)
self.cover_back.position = self.x + self.pad, self.y + self.pad
@ -195,15 +261,31 @@ class MinecraftWikiButtonTheme(BaseButtonTheme):
self.cover_back2.position = self.x + self.pad, self.y + self.pad
self.cover_back2.height = self.height - (self.pad * 3)
else:
button.text_label.y = (self.y + (self.height - button.font_height) // 2 + (button.font_height * 0.2)
+ self.list_pad)
self.back_ground.position = self.x + (self.pad * 2), self.y + (self.pad * 2) + self.list_pad
button.text_label.y = (
self.y
+ (self.height - button.font_height) // 2
+ (button.font_height * 0.2)
+ self.list_pad
)
self.back_ground.position = (
self.x + (self.pad * 2),
self.y + (self.pad * 2) + self.list_pad,
)
self.back_ground.height = self.height - (self.pad * 4) - self.list_pad
self.cover_back.position = self.x + self.pad, self.y + self.pad + self.list_pad
self.cover_back.position = (
self.x + self.pad,
self.y + self.pad + self.list_pad,
)
self.cover_back.height = self.height - (self.pad * 2) - self.list_pad
self.cover_back2.position = self.x + self.pad, self.y + self.pad + self.list_pad
self.cover_back2.position = (
self.x + self.pad,
self.y + self.pad + self.list_pad,
)
self.cover_back2.height = self.height - (self.pad * 3) - self.list_pad
self.back_ground.position = self.x + (self.pad * 2), self.y + (self.pad * 2) + self.list_pad
self.back_ground.position = (
self.x + (self.pad * 2),
self.y + (self.pad * 2) + self.list_pad,
)
self.back_ground.height = self.height - (self.pad * 4) - self.list_pad
@ -226,17 +308,17 @@ class PressTextButton(widgets.WidgetBase):
"""
def __init__(
self,
x: int,
y: int,
width: int,
height: int,
text: str,
batch: Optional[Batch] = None,
group: Optional[Group] = None,
theme: Optional[ButtonThemeOptions] = None,
draw_theme: Optional[type(BaseButtonTheme)] = None,
dict_theme: Optional[dict] = None,
self,
x: int,
y: int,
width: int,
height: int,
text: str,
batch: Optional[Batch] = None,
group: Optional[Group] = None,
theme: Optional[ButtonThemeOptions] = None,
draw_theme: Optional[type(BaseButtonTheme)] = None,
dict_theme: Optional[dict] = None,
):
super().__init__(x, y, width, height)
self.main_batch = batch or Batch()
@ -310,7 +392,7 @@ class PressTextButton(widgets.WidgetBase):
text_width = self.text_label.content_width
self.text_label.x = self._x + (self.width - text_width) // 2
self.text_label.y = (
self._y + (self.height - self.font_height) // 2 + (self.font_height * 0.2)
self._y + (self.height - self.font_height) // 2 + (self.font_height * 0.2)
) # 修正一下位置
@property

View File

@ -4,13 +4,6 @@
# All rights reserved
# -------------------------------
"""
writen by shenjackyuanjie
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
# system function
import warnings
from typing import Tuple, List, Optional, TypeVar, TYPE_CHECKING
@ -71,12 +64,14 @@ class ModInfo(Options):
def __init__(self, **kwargs):
if not self.DR_version[0] <= DR_status.DR_version <= self.DR_version[1]:
warnings.warn(
f"mod {self.mod_id} version {self.version} is not support by DR {DR_status.DR_version}\n"
f"mod {self.mod_id} version {self.version} "
f"is not support by DR {DR_status.DR_version}\n"
f"DR {self.DR_version} is required"
)
if not self.DR_Api_version[0] <= DR_status.API_version <= self.DR_Api_version[1]:
warnings.warn(
f"mod {self.mod_id} version {self.version} is not support by DR {DR_status.API_version}\n"
f"mod {self.mod_id} version {self.version} "
f"is not support by DR {DR_status.API_version}\n"
f"DR {self.DR_Api_version} is required"
)
super().__init__(**kwargs)

View File

@ -87,14 +87,14 @@ class ModManager(Options):
return None
logger.info(tr().mod.load.loading().format(mod_path), tag="load")
if (
mod_path.is_dir()
or mod_path.suffix in PACKAGE_SUFFIX
or mod_path.suffix in ONE_FILE_SUFFIX
mod_path.is_dir()
or mod_path.suffix in PACKAGE_SUFFIX
or mod_path.suffix in ONE_FILE_SUFFIX
):
# 文件夹 mod
loading_mod = importlib.import_module(mod_path.name)
if not hasattr(loading_mod, "mod_class") or not issubclass(
loading_mod.mod_class, ModInfo
loading_mod.mod_class, ModInfo
):
logger.warn(
tr().mod.load.faild.no_mod_class().format(mod_path), tag="load"
@ -112,7 +112,7 @@ class ModManager(Options):
return None
def find_mods_in_path(
self, extra_mods_path: Optional[List[Path]] = None
self, extra_mods_path: Optional[List[Path]] = None
) -> List[Path]:
"""
查找所有 mod 路径
@ -132,9 +132,9 @@ class ModManager(Options):
# 忽略 __pycache__ 文件夹 (Python 编译文件)
continue
if (
mod.is_dir()
or mod.suffix in PACKAGE_SUFFIX
or mod.suffix in ONE_FILE_SUFFIX
mod.is_dir()
or mod.suffix in PACKAGE_SUFFIX
or mod.suffix in ONE_FILE_SUFFIX
):
# 文件夹 mod
mods_path.append(mod)
@ -144,9 +144,9 @@ class ModManager(Options):
return mods_path
def load_mods(
self,
extra_path: Optional[List[Path]] = None,
extra_mod_path: Optional[List[Path]] = None,
self,
extra_path: Optional[List[Path]] = None,
extra_mod_path: Optional[List[Path]] = None,
) -> List[type(ModInfo)]:
"""
加载所有 mod (可提供额外的 mod 路径)
@ -170,7 +170,9 @@ class ModManager(Options):
for path in extra_mod_path:
if (cache := self.load_mod(path)) is not None:
mods.append(cache)
self.logger.info(tr().mod.load.use_time().format(time.time() - start_time), tag="load")
self.logger.info(
tr().mod.load.use_time().format(time.time() - start_time), tag="load"
)
return mods
def init_mods(self, mods: List[type(ModInfo)]):
@ -193,7 +195,9 @@ class ModManager(Options):
tag="init",
)
continue
self.logger.info(tr().mod.init.use_time().format(time.time() - start_time), tag="init")
self.logger.info(
tr().mod.init.use_time().format(time.time() - start_time), tag="init"
)
def unload_mod(self, mod_id: str, game: Game) -> Optional[ModInfo]:
"""
@ -203,10 +207,12 @@ class ModManager(Options):
:return: 卸载的 mod ModInfo
"""
if (
not (mod_class := self.loaded_mod_modules.get(mod_id))
and (mod_class := self.get_mod_module(mod_id)) is None
not (mod_class := self.loaded_mod_modules.get(mod_id))
and (mod_class := self.get_mod_module(mod_id)) is None
):
self.logger.warn(tr().mod.unload.faild.not_find().format(mod_id), tag="unload")
self.logger.warn(
tr().mod.unload.faild.not_find().format(mod_id), tag="unload"
)
return None
try:
mod_class.on_unload(game=game)

View File

@ -4,13 +4,6 @@
# All rights reserved
# -------------------------------
"""
writen by shenjackyuanjie
mail: 3695888@qq.com
github: @shenjackyuanjie
gitee: @shenjackyuanjie
"""
import os
import time
# import multiprocessing
@ -21,7 +14,7 @@ from Difficult_Rocket.utils.translate import tr
from lib_not_dr import loggers
# TODO 改变服务端启动逻辑 0.6.0(划掉 0.8.0)会写完的(
# TODO 改变服务端启动逻辑 0.6.0(划掉 0.8.0)(划掉 0.10.0)会写完的(
class Server:

View File

@ -29,13 +29,13 @@ class Camera:
"""
def __init__(
self,
window,
zoom: Optional[float] = 1.0,
dx: Optional[float] = 1.0,
dy: Optional[float] = 1.0,
min_zoom: Optional[float] = 1.0,
max_zoom: Optional[float] = 1.0,
self,
window,
zoom: Optional[float] = 1.0,
dx: Optional[float] = 1.0,
dy: Optional[float] = 1.0,
min_zoom: Optional[float] = 1.0,
max_zoom: Optional[float] = 1.0,
) -> None:
self.window = window
self.dx = dx or 1.0
@ -128,15 +128,15 @@ class GroupCamera(Group):
"""
def __init__(
self,
window,
order: int = 0,
parent: Optional[Group] = None,
view_x: Optional[int] = 0,
view_y: Optional[int] = 0,
zoom: Optional[float] = 1.0,
min_zoom: Optional[float] = 1.0,
max_zoom: Optional[float] = 1.0,
self,
window,
order: int = 0,
parent: Optional[Group] = None,
view_x: Optional[int] = 0,
view_y: Optional[int] = 0,
zoom: Optional[float] = 1.0,
min_zoom: Optional[float] = 1.0,
max_zoom: Optional[float] = 1.0,
):
super().__init__(order=order, parent=parent)
self._window = window
@ -221,17 +221,17 @@ class CenterGroupFrame(Group):
"""
def __init__(
self,
window,
order: int = 0,
parent: Optional[Group] = None,
dx: Optional[int] = 0,
dy: Optional[int] = 0,
width: Optional[int] = 0,
height: Optional[int] = 0,
zoom: Optional[float] = 1.0,
min_zoom: Optional[float] = 1.0,
max_zoom: Optional[float] = 1.0,
self,
window,
order: int = 0,
parent: Optional[Group] = None,
dx: Optional[int] = 0,
dy: Optional[int] = 0,
width: Optional[int] = 0,
height: Optional[int] = 0,
zoom: Optional[float] = 1.0,
min_zoom: Optional[float] = 1.0,
max_zoom: Optional[float] = 1.0,
):
super().__init__(order=order, parent=parent)
self.window = window

View File

@ -120,9 +120,9 @@ class FunctionThread(threading.Thread):
def new_thread(
arg: Optional[Union[str, Callable]] = None,
daemon: bool = False,
log_thread: bool = True,
arg: Optional[Union[str, Callable]] = None,
daemon: bool = False,
log_thread: bool = True,
):
"""
This is a one line solution to make your function executes in parallels.

View File

@ -37,20 +37,20 @@ file config
file_error = {
FileNotFoundError: "no {filetype} file was founded!:\n file name: {filename}\n file_type: {filetype}\n stack: {stack}",
KeyError: "no stack in {filetype} file {filename} was found! \n file type: {} \n file name: {} \n stack: {stack}",
Exception: "get some {error_type} when read {filetype} file {filename}! \n file type: {} \n file name: {} \n stack: {stack}",
KeyError: "no stack in {filetype} file {filename} was found! \n file type: {} \n file name: {} \n stack: {stack}",
Exception: "get some {error_type} when read {filetype} file {filename}! \n file type: {} \n file name: {} \n stack: {stack}",
}
def load_file(
file_name: Union[str, Path],
stack: Optional[Union[str, list, dict]] = None,
raise_error: Optional[bool] = True,
encoding: Optional[str] = "utf-8",
file_name: Union[str, Path],
stack: Optional[Union[str, list, dict]] = None,
raise_error: Optional[bool] = True,
encoding: Optional[str] = "utf-8",
) -> Union[dict, ElementTree.ElementTree]:
if isinstance(file_name, Path):
file_name = str(file_name)
f_type = file_name[file_name.rfind(".") + 1:] # 从最后一个.到末尾 (截取文件格式)
f_type = file_name[file_name.rfind(".") + 1 :] # 从最后一个.到末尾 (截取文件格式)
get_file = NotImplementedError("解析失败,请检查文件类型/文件内容/文件是否存在!")
try:
if f_type == "xml":
@ -97,7 +97,7 @@ def load_file(
def save_dict_file(file_name: str, data: dict, encoding: str = "utf-8") -> bool:
f_type = file_name[file_name.rfind(".") + 1:] # 从最后一个.到末尾 (截取文件格式)
f_type = file_name[file_name.rfind(".") + 1 :] # 从最后一个.到末尾 (截取文件格式)
try:
if (f_type == "config") or (f_type == "conf") or (f_type == "ini"):
return False

View File

@ -24,11 +24,11 @@ basic = {
build = {
"compile": [
"nuitka >= 2.2.2",
"nuitka >= 2.2.3",
"imageio >= 2.34.1",
"setuptools >= 69",
"setuptools-rust >= 1.9.0",
"wheel >= 0.37.0",
"wheel >= 0.43.0",
]
}