diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index f859671..bc000eb 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -31,7 +31,8 @@ jobs: - name: Test with pytest run: | - pdm run pytest --junitxml=report/report.xml + pdm run pytest --junitxml=report/report.xml. + 0 - name: Archive Pytest test report uses: actions/upload-artifact@v3 diff --git a/.gitignore b/.gitignore index ef5ce8f..a8698e9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,10 @@ .pdm-build .pdm-python pdm.lock -.pdm-build/ \ No newline at end of file +.pdm-build/ + +# node.js +node_modules/ + +docs/.vitepress/dist +docs/.vitepress/cache \ No newline at end of file diff --git a/README.md b/README.md index 9fba32b..2a00fd1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ -# mcpe -MinecraftParticleEffect +# More Basic Change Particle - 更多基础变换粒子 +A Minecraft particle production library + +## 介绍 +该库用于一些Minecraft粒子特效的计算和制作,集成了常用的numpy,sympy,scipy等科学计算库 + +## 文档 +[MBCP docs](https://mbcp.liteyuki.icu) + +## 示例 +- [【特效红石音乐】童话镇~「总有一条蜿蜒在童话镇里...」](https://www.bilibili.com/video/BV1xE4m1d72j) + +- [【特效红石音乐】使一颗心免于哀伤 If I Can Stop One Heart From Breaking「崩坏:星穹铁道 EP」](https://www.bilibili.com/video/BV1B1421t7i3) + diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 0000000..75104cd --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,28 @@ +import { defineConfig } from 'vitepress' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "MBCP docs", + description: "MBCP library docs", + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Home', link: '/' }, + { text: 'Examples', link: '/markdown-examples' } + ], + + sidebar: [ + { + text: 'Examples', + items: [ + { text: 'Markdown Examples', link: '/markdown-examples' }, + { text: 'Runtime API Examples', link: '/api-examples' } + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/vuejs/vitepress' } + ] + } +}) diff --git a/docs/api-examples.md b/docs/api-examples.md new file mode 100644 index 0000000..6bd8bb5 --- /dev/null +++ b/docs/api-examples.md @@ -0,0 +1,49 @@ +--- +outline: deep +--- + +# Runtime API Examples + +This page demonstrates usage of some of the runtime APIs provided by VitePress. + +The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: + +```md + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+``` + + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+ +## More + +Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). diff --git a/docs/api/indedx.md b/docs/api/indedx.md new file mode 100644 index 0000000..daef73b --- /dev/null +++ b/docs/api/indedx.md @@ -0,0 +1,7 @@ +--- +title: mbcp.\n\ninit\n\n +order: 1 +icon: laptop-code +category: API +--- + diff --git a/docs/api/mp_math/angle.md b/docs/api/mp_math/angle.md new file mode 100644 index 0000000..a88b240 --- /dev/null +++ b/docs/api/mp_math/angle.md @@ -0,0 +1,303 @@ +--- +title: mbcp.mp\nmath.angle +order: 1 +icon: laptop-code +category: API +--- + +### ***class*** `AnyAngle` + + + +###   ***def*** `__init__(self, value: float, is_radian: bool) -> None` + + 任意角度。 + +Args: + + value: 角度或弧度值 + + is_radian: 是否为弧度,默认为否 + +
+源代码 + +```python +def __init__(self, value: float, is_radian: bool=False): + """ + 任意角度。 + Args: + value: 角度或弧度值 + is_radian: 是否为弧度,默认为否 + """ + if is_radian: + self.radian = value + else: + self.radian = value * PI / 180 +``` +
+ +###   ***@property*** +###   ***def*** `complementary(self: Any) -> 'AnyAngle'` + + 余角:两角的和为90°。 + +Returns: + + 余角 + +
+源代码 + +```python +@property +def complementary(self) -> 'AnyAngle': + """ + 余角:两角的和为90°。 + Returns: + 余角 + """ + return AnyAngle(PI / 2 - self.minimum_positive.radian, is_radian=True) +``` +
+ +###   ***@property*** +###   ***def*** `supplementary(self: Any) -> 'AnyAngle'` + + 补角:两角的和为180°。 + +Returns: + + 补角 + +
+源代码 + +```python +@property +def supplementary(self) -> 'AnyAngle': + """ + 补角:两角的和为180°。 + Returns: + 补角 + """ + return AnyAngle(PI - self.minimum_positive.radian, is_radian=True) +``` +
+ +###   ***@property*** +###   ***def*** `degree(self: Any) -> float` + + 角度。 + +Returns: + + 弧度 + +
+源代码 + +```python +@property +def degree(self) -> float: + """ + 角度。 + Returns: + 弧度 + """ + return self.radian * 180 / PI +``` +
+ +###   ***@property*** +###   ***def*** `minimum_positive(self: Any) -> 'AnyAngle'` + + 最小正角。 + +Returns: + + 最小正角度 + +
+源代码 + +```python +@property +def minimum_positive(self) -> 'AnyAngle': + """ + 最小正角。 + Returns: + 最小正角度 + """ + return AnyAngle(self.radian % (2 * PI)) +``` +
+ +###   ***@property*** +###   ***def*** `maximum_negative(self: Any) -> 'AnyAngle'` + + 最大负角。 + +Returns: + + 最大负角度 + +
+源代码 + +```python +@property +def maximum_negative(self) -> 'AnyAngle': + """ + 最大负角。 + Returns: + 最大负角度 + """ + return AnyAngle(-self.radian % (2 * PI), is_radian=True) +``` +
+ +###   ***@property*** +###   ***def*** `sin(self: Any) -> float` + + 正弦值。 + +Returns: + + 正弦值 + +
+源代码 + +```python +@property +def sin(self) -> float: + """ + 正弦值。 + Returns: + 正弦值 + """ + return math.sin(self.radian) +``` +
+ +###   ***@property*** +###   ***def*** `cos(self: Any) -> float` + + 余弦值。 + +Returns: + + 余弦值 + +
+源代码 + +```python +@property +def cos(self) -> float: + """ + 余弦值。 + Returns: + 余弦值 + """ + return math.cos(self.radian) +``` +
+ +###   ***@property*** +###   ***def*** `tan(self: Any) -> float` + + 正切值。 + +Returns: + + 正切值 + +
+源代码 + +```python +@property +def tan(self) -> float: + """ + 正切值。 + Returns: + 正切值 + """ + return math.tan(self.radian) +``` +
+ +###   ***@property*** +###   ***def*** `cot(self: Any) -> float` + + 余切值。 + +Returns: + + 余切值 + +
+源代码 + +```python +@property +def cot(self) -> float: + """ + 余切值。 + Returns: + 余切值 + """ + return 1 / math.tan(self.radian) +``` +
+ +###   ***@property*** +###   ***def*** `sec(self: Any) -> float` + + 正割值。 + +Returns: + + 正割值 + +
+源代码 + +```python +@property +def sec(self) -> float: + """ + 正割值。 + Returns: + 正割值 + """ + return 1 / math.cos(self.radian) +``` +
+ +###   ***@property*** +###   ***def*** `csc(self: Any) -> float` + + 余割值。 + +Returns: + + 余割值 + +
+源代码 + +```python +@property +def csc(self) -> float: + """ + 余割值。 + Returns: + 余割值 + """ + return 1 / math.sin(self.radian) +``` +
+ diff --git a/docs/api/mp_math/const.md b/docs/api/mp_math/const.md new file mode 100644 index 0000000..5e9f287 --- /dev/null +++ b/docs/api/mp_math/const.md @@ -0,0 +1,31 @@ +--- +title: mbcp.mp\nmath.const +order: 1 +icon: laptop-code +category: API +--- + +### ***var*** `PI = math.pi` + + + +### ***var*** `E = math.e` + + + +### ***var*** `GOLDEN_RATIO = (1 + math.sqrt(5)) / 2` + + + +### ***var*** `GAMMA = 0.5772156649015329` + + + +### ***var*** `EPSILON = 0.0001` + + + +### ***var*** `APPROX = 0.001` + + + diff --git a/docs/api/mp_math/equation.md b/docs/api/mp_math/equation.md new file mode 100644 index 0000000..cbdba56 --- /dev/null +++ b/docs/api/mp_math/equation.md @@ -0,0 +1,145 @@ +--- +title: mbcp.mp\nmath.equation +order: 1 +icon: laptop-code +category: API +--- + +### ***def*** `get_partial_derivative_func(func: MultiVarsFunc, var: int | tuple[int, ...], epsilon: Number) -> MultiVarsFunc` + +求N元函数一阶偏导函数。这玩意不太稳定,慎用。 + +Args: + + func: 函数 + + var: 变量位置,可为整数(一阶偏导)或整数元组(高阶偏导) + + epsilon: 偏移量 + +Returns: + + 偏导函数 + +Raises: + + ValueError: 无效变量类型 + +
+源代码 + +```python +def get_partial_derivative_func(func: MultiVarsFunc, var: int | tuple[int, ...], epsilon: Number=EPSILON) -> MultiVarsFunc: + """ + 求N元函数一阶偏导函数。这玩意不太稳定,慎用。 + Args: + func: 函数 + var: 变量位置,可为整数(一阶偏导)或整数元组(高阶偏导) + epsilon: 偏移量 + Returns: + 偏导函数 + Raises: + ValueError: 无效变量类型 + """ + if isinstance(var, int): + + def partial_derivative_func(*args: Var) -> Var: + args_list_plus = list(args) + args_list_plus[var] += epsilon + args_list_minus = list(args) + args_list_minus[var] -= epsilon + return (func(*args_list_plus) - func(*args_list_minus)) / (2 * epsilon) + return partial_derivative_func + elif isinstance(var, tuple): + + def high_order_partial_derivative_func(*args: Var) -> Var: + result_func = func + for v in var: + result_func = get_partial_derivative_func(result_func, v, epsilon) + return result_func(*args) + return high_order_partial_derivative_func + else: + raise ValueError('Invalid var type') +``` +
+ +### ***def*** `partial_derivative_func() -> Var` + + + +
+源代码 + +```python +def partial_derivative_func(*args: Var) -> Var: + args_list_plus = list(args) + args_list_plus[var] += epsilon + args_list_minus = list(args) + args_list_minus[var] -= epsilon + return (func(*args_list_plus) - func(*args_list_minus)) / (2 * epsilon) +``` +
+ +### ***def*** `high_order_partial_derivative_func() -> Var` + + + +
+源代码 + +```python +def high_order_partial_derivative_func(*args: Var) -> Var: + result_func = func + for v in var: + result_func = get_partial_derivative_func(result_func, v, epsilon) + return result_func(*args) +``` +
+ +### ***class*** `CurveEquation` + + + +###   ***def*** `__init__(self, x_func: OneVarFunc, y_func: OneVarFunc, z_func: OneVarFunc) -> None` + + 曲线方程。 + +:param x_func: + +:param y_func: + +:param z_func: + +
+源代码 + +```python +def __init__(self, x_func: OneVarFunc, y_func: OneVarFunc, z_func: OneVarFunc): + """ + 曲线方程。 + :param x_func: + :param y_func: + :param z_func: + """ + self.x_func = x_func + self.y_func = y_func + self.z_func = z_func +``` +
+ +### ***var*** `args_list_plus = list(args)` + + + +### ***var*** `args_list_minus = list(args)` + + + +### ***var*** `result_func = func` + + + +### ***var*** `result_func = get_partial_derivative_func(result_func, v, epsilon)` + + + diff --git a/docs/api/mp_math/indedx.md b/docs/api/mp_math/indedx.md new file mode 100644 index 0000000..21d2307 --- /dev/null +++ b/docs/api/mp_math/indedx.md @@ -0,0 +1,7 @@ +--- +title: mbcp.mp\nmath.\n\ninit\n\n +order: 1 +icon: laptop-code +category: API +--- + diff --git a/docs/api/mp_math/line.md b/docs/api/mp_math/line.md new file mode 100644 index 0000000..3ae3642 --- /dev/null +++ b/docs/api/mp_math/line.md @@ -0,0 +1,489 @@ +--- +title: mbcp.mp\nmath.line +order: 1 +icon: laptop-code +category: API +--- + +### ***class*** `Line3` + + + +###   ***def*** `__init__(self, point: 'Point3', direction: 'Vector3') -> None` + + 三维空间中的直线。由一个点和一个方向向量确定。 + +Args: + + point: 直线上的一点 + + direction: 直线的方向向量 + +
+源代码 + +```python +def __init__(self, point: 'Point3', direction: 'Vector3'): + """ + 三维空间中的直线。由一个点和一个方向向量确定。 + Args: + point: 直线上的一点 + direction: 直线的方向向量 + """ + self.point = point + self.direction = direction +``` +
+ +###   ***def*** `approx(self, other: 'Line3', epsilon: float) -> bool` + + 判断两条直线是否近似相等。 + +Args: + + other: 另一条直线 + + epsilon: 误差 + +Returns: + + 是否近似相等 + +
+源代码 + +```python +def approx(self, other: 'Line3', epsilon: float=APPROX) -> bool: + """ + 判断两条直线是否近似相等。 + Args: + other: 另一条直线 + epsilon: 误差 + Returns: + 是否近似相等 + """ + return self.is_approx_parallel(other, epsilon) and (self.point - other.point).is_approx_parallel(self.direction, epsilon) +``` +
+ +###   ***def*** `cal_angle(self, other: 'Line3') -> 'AnyAngle'` + + 计算直线和直线之间的夹角。 + +Args: + + other: 另一条直线 + +Returns: + + 夹角弧度 + +Raises: + + TypeError: 不支持的类型 + +
+源代码 + +```python +def cal_angle(self, other: 'Line3') -> 'AnyAngle': + """ + 计算直线和直线之间的夹角。 + Args: + other: 另一条直线 + Returns: + 夹角弧度 + Raises: + TypeError: 不支持的类型 + """ + return self.direction.cal_angle(other.direction) +``` +
+ +###   ***def*** `cal_distance(self, other: 'Line3 | Point3') -> float` + + 计算直线和直线或点之间的距离。 + +Args: + + other: 平行直线或点 + + + +Returns: + + 距离 + +Raises: + + TypeError: 不支持的类型 + +
+源代码 + +```python +def cal_distance(self, other: 'Line3 | Point3') -> float: + """ + 计算直线和直线或点之间的距离。 + Args: + other: 平行直线或点 + + Returns: + 距离 + Raises: + TypeError: 不支持的类型 + """ + if isinstance(other, Line3): + if self == other: + return 0 + elif self.is_parallel(other): + return (other.point - self.point).cross(self.direction).length / self.direction.length + elif not self.is_coplanar(other): + return abs(self.direction.cross(other.direction) @ (self.point - other.point) / self.direction.cross(other.direction).length) + else: + return 0 + elif isinstance(other, Point3): + return (other - self.point).cross(self.direction).length / self.direction.length + else: + raise TypeError('Unsupported type.') +``` +
+ +###   ***def*** `cal_intersection(self, other: 'Line3') -> 'Point3'` + + 计算两条直线的交点。 + +Args: + + other: 另一条直线 + +Returns: + + 交点 + +Raises: + + ValueError: 直线平行 + + ValueError: 直线不共面 + +
+源代码 + +```python +def cal_intersection(self, other: 'Line3') -> 'Point3': + """ + 计算两条直线的交点。 + Args: + other: 另一条直线 + Returns: + 交点 + Raises: + ValueError: 直线平行 + ValueError: 直线不共面 + """ + if self.is_parallel(other): + raise ValueError('Lines are parallel and do not intersect.') + if not self.is_coplanar(other): + raise ValueError('Lines are not coplanar and do not intersect.') + return self.point + self.direction.cross(other.direction) @ other.direction.cross(self.point - other.point) / self.direction.cross(other.direction).length ** 2 * self.direction +``` +
+ +###   ***def*** `cal_perpendicular(self, point: 'Point3') -> 'Line3'` + + 计算直线经过指定点p的垂线。 + +Args: + + point: 指定点 + +Returns: + + 垂线 + +
+源代码 + +```python +def cal_perpendicular(self, point: 'Point3') -> 'Line3': + """ + 计算直线经过指定点p的垂线。 + Args: + point: 指定点 + Returns: + 垂线 + """ + return Line3(point, self.direction.cross(point - self.point)) +``` +
+ +###   ***def*** `get_point(self, t: RealNumber) -> 'Point3'` + + 获取直线上的点。同一条直线,但起始点和方向向量不同,则同一个t对应的点不同。 + +Args: + + t: 参数t + +Returns: + + 点 + +
+源代码 + +```python +def get_point(self, t: RealNumber) -> 'Point3': + """ + 获取直线上的点。同一条直线,但起始点和方向向量不同,则同一个t对应的点不同。 + Args: + t: 参数t + Returns: + 点 + """ + return self.point + t * self.direction +``` +
+ +###   ***def*** `get_parametric_equations(self) -> tuple[OneSingleVarFunc, OneSingleVarFunc, OneSingleVarFunc]` + + 获取直线的参数方程。 + +Returns: + + x(t), y(t), z(t) + +
+源代码 + +```python +def get_parametric_equations(self) -> tuple[OneSingleVarFunc, OneSingleVarFunc, OneSingleVarFunc]: + """ + 获取直线的参数方程。 + Returns: + x(t), y(t), z(t) + """ + return (lambda t: self.point.x + self.direction.x * t, lambda t: self.point.y + self.direction.y * t, lambda t: self.point.z + self.direction.z * t) +``` +
+ +###   ***def*** `is_approx_parallel(self, other: 'Line3', epsilon: float) -> bool` + + 判断两条直线是否近似平行。 + +Args: + + other: 另一条直线 + + epsilon: 误差 + +Returns: + + 是否近似平行 + +
+源代码 + +```python +def is_approx_parallel(self, other: 'Line3', epsilon: float=1e-06) -> bool: + """ + 判断两条直线是否近似平行。 + Args: + other: 另一条直线 + epsilon: 误差 + Returns: + 是否近似平行 + """ + return self.direction.is_approx_parallel(other.direction, epsilon) +``` +
+ +###   ***def*** `is_parallel(self, other: 'Line3') -> bool` + + 判断两条直线是否平行。 + +Args: + + other: 另一条直线 + +Returns: + + 是否平行 + +
+源代码 + +```python +def is_parallel(self, other: 'Line3') -> bool: + """ + 判断两条直线是否平行。 + Args: + other: 另一条直线 + Returns: + 是否平行 + """ + return self.direction.is_parallel(other.direction) +``` +
+ +###   ***def*** `is_collinear(self, other: 'Line3') -> bool` + + 判断两条直线是否共线。 + +Args: + + other: 另一条直线 + +Returns: + + 是否共线 + +
+源代码 + +```python +def is_collinear(self, other: 'Line3') -> bool: + """ + 判断两条直线是否共线。 + Args: + other: 另一条直线 + Returns: + 是否共线 + """ + return self.is_parallel(other) and (self.point - other.point).is_parallel(self.direction) +``` +
+ +###   ***def*** `is_point_on(self, point: 'Point3') -> bool` + + 判断点是否在直线上。 + +Args: + + point: 点 + +Returns: + + 是否在直线上 + +
+源代码 + +```python +def is_point_on(self, point: 'Point3') -> bool: + """ + 判断点是否在直线上。 + Args: + point: 点 + Returns: + 是否在直线上 + """ + return (point - self.point).is_parallel(self.direction) +``` +
+ +###   ***def*** `is_coplanar(self, other: 'Line3') -> bool` + + 判断两条直线是否共面。 + +充要条件:两直线方向向量的叉乘与两直线上任意一点的向量的点积为0。 + +Args: + + other: 另一条直线 + +Returns: + + 是否共面 + +
+源代码 + +```python +def is_coplanar(self, other: 'Line3') -> bool: + """ + 判断两条直线是否共面。 + 充要条件:两直线方向向量的叉乘与两直线上任意一点的向量的点积为0。 + Args: + other: 另一条直线 + Returns: + 是否共面 + """ + return self.direction.cross(other.direction) @ (self.point - other.point) == 0 +``` +
+ +###   ***def*** `simplify(self) -> None` + + 简化直线方程,等价相等。 + +自体简化,不返回值。 + + + +按照可行性一次对x y z 化 0 处理,并对向量单位化 + +
+源代码 + +```python +def simplify(self): + """ + 简化直线方程,等价相等。 + 自体简化,不返回值。 + + 按照可行性一次对x y z 化 0 处理,并对向量单位化 + """ + self.direction.normalize() + if self.direction.x == 0: + self.point.x = 0 + if self.direction.y == 0: + self.point.y = 0 + if self.direction.z == 0: + self.point.z = 0 +``` +
+ +###   ***@classmethod*** +###   ***def*** `from_two_points(cls: Any, p1: 'Point3', p2: 'Point3') -> 'Line3'` + + 工厂函数 由两点构造直线。 + +Args: + + p1: 点1 + + p2: 点2 + +Returns: + + 直线 + +
+源代码 + +```python +@classmethod +def from_two_points(cls, p1: 'Point3', p2: 'Point3') -> 'Line3': + """ + 工厂函数 由两点构造直线。 + Args: + p1: 点1 + p2: 点2 + Returns: + 直线 + """ + direction = p2 - p1 + return cls(p1, direction) +``` +
+ +### ***var*** `direction = p2 - p1` + + + +### ***var*** `s = 'Line3: '` + + + diff --git a/docs/api/mp_math/mp_math_typing.md b/docs/api/mp_math/mp_math_typing.md new file mode 100644 index 0000000..b1bb7d5 --- /dev/null +++ b/docs/api/mp_math/mp_math_typing.md @@ -0,0 +1,15 @@ +--- +title: mbcp.mp\nmath.mp\nmath\ntyping +order: 1 +icon: laptop-code +category: API +--- + +### ***var*** `SingleVar = TypeVar('SingleVar', bound=Number)` + + + +### ***var*** `ArrayVar = TypeVar('ArrayVar', bound=Iterable[Number])` + + + diff --git a/docs/api/mp_math/plane.md b/docs/api/mp_math/plane.md new file mode 100644 index 0000000..3832cd3 --- /dev/null +++ b/docs/api/mp_math/plane.md @@ -0,0 +1,552 @@ +--- +title: mbcp.mp\nmath.plane +order: 1 +icon: laptop-code +category: API +--- + +### ***class*** `Plane3` + + + +###   ***def*** `__init__(self, a: float, b: float, c: float, d: float) -> None` + + 平面方程:ax + by + cz + d = 0 + +Args: + + a: + + b: + + c: + + d: + +
+源代码 + +```python +def __init__(self, a: float, b: float, c: float, d: float): + """ + 平面方程:ax + by + cz + d = 0 + Args: + a: + b: + c: + d: + """ + self.a = a + self.b = b + self.c = c + self.d = d +``` +
+ +###   ***def*** `approx(self, other: 'Plane3', epsilon: float) -> bool` + + 判断两个平面是否近似相等。 + +Args: + + other: + + epsilon: + + + +Returns: + + 是否近似相等 + +
+源代码 + +```python +def approx(self, other: 'Plane3', epsilon: float=APPROX) -> bool: + """ + 判断两个平面是否近似相等。 + Args: + other: + epsilon: + + Returns: + 是否近似相等 + """ + if self.a != 0: + k = other.a / self.a + return approx(other.b, self.b * k) and approx(other.c, self.c * k) and approx(other.d, self.d * k) + elif self.b != 0: + k = other.b / self.b + return approx(other.a, self.a * k) and approx(other.c, self.c * k) and approx(other.d, self.d * k) + elif self.c != 0: + k = other.c / self.c + return approx(other.a, self.a * k) and approx(other.b, self.b * k) and approx(other.d, self.d * k) + else: + return False +``` +
+ +###   ***def*** `cal_angle(self, other: 'Line3 | Plane3') -> 'AnyAngle'` + + 计算平面与平面之间的夹角。 + +Args: + + other: 另一个平面 + +Returns: + + 夹角弧度 + +Raises: + + TypeError: 不支持的类型 + +
+源代码 + +```python +def cal_angle(self, other: 'Line3 | Plane3') -> 'AnyAngle': + """ + 计算平面与平面之间的夹角。 + Args: + other: 另一个平面 + Returns: + 夹角弧度 + Raises: + TypeError: 不支持的类型 + """ + if isinstance(other, Line3): + return self.normal.cal_angle(other.direction).complementary + elif isinstance(other, Plane3): + return AnyAngle(math.acos(self.normal @ other.normal / (self.normal.length * other.normal.length)), is_radian=True) + else: + raise TypeError(f'Unsupported type: {type(other)}') +``` +
+ +###   ***def*** `cal_distance(self, other: 'Plane3 | Point3') -> float` + + 计算平面与平面或点之间的距离。 + +Args: + + other: 另一个平面或点 + +Returns: + + 距离 + +Raises: + + TypeError: 不支持的类型 + +
+源代码 + +```python +def cal_distance(self, other: 'Plane3 | Point3') -> float: + """ + 计算平面与平面或点之间的距离。 + Args: + other: 另一个平面或点 + Returns: + 距离 + Raises: + TypeError: 不支持的类型 + """ + if isinstance(other, Plane3): + return 0 + elif isinstance(other, Point3): + return abs(self.a * other.x + self.b * other.y + self.c * other.z + self.d) / (self.a ** 2 + self.b ** 2 + self.c ** 2) ** 0.5 + else: + raise TypeError(f'Unsupported type: {type(other)}') +``` +
+ +###   ***def*** `cal_intersection_line3(self, other: 'Plane3') -> 'Line3'` + + 计算两平面的交线。该方法有问题,待修复。 + +Args: + + other: 另一个平面 + +Returns: + + 交线 + +Raises: + +
+源代码 + +```python +def cal_intersection_line3(self, other: 'Plane3') -> 'Line3': + """ + 计算两平面的交线。该方法有问题,待修复。 + Args: + other: 另一个平面 + Returns: + 交线 + Raises: + """ + if self.normal.is_parallel(other.normal): + raise ValueError('Planes are parallel and have no intersection.') + direction = self.normal.cross(other.normal) + x, y, z = (0, 0, 0) + if self.a != 0 and other.a != 0: + A = np.array([[self.b, self.c], [other.b, other.c]]) + B = np.array([-self.d, -other.d]) + y, z = np.linalg.solve(A, B) + elif self.b != 0 and other.b != 0: + A = np.array([[self.a, self.c], [other.a, other.c]]) + B = np.array([-self.d, -other.d]) + x, z = np.linalg.solve(A, B) + elif self.c != 0 and other.c != 0: + A = np.array([[self.a, self.b], [other.a, other.b]]) + B = np.array([-self.d, -other.d]) + x, y = np.linalg.solve(A, B) + return Line3(Point3(x, y, z), direction) +``` +
+ +###   ***def*** `cal_intersection_point3(self, other: 'Line3') -> 'Point3'` + + 计算平面与直线的交点。 + +Args: + + other: 不与平面平行或在平面上的直线 + +Returns: + + 交点 + +Raises: + + ValueError: 平面与直线平行或重合 + +
+源代码 + +```python +def cal_intersection_point3(self, other: 'Line3') -> 'Point3': + """ + 计算平面与直线的交点。 + Args: + other: 不与平面平行或在平面上的直线 + Returns: + 交点 + Raises: + ValueError: 平面与直线平行或重合 + """ + if self.normal @ other.direction == 0: + raise ValueError('The plane and the line are parallel or coincident.') + x, y, z = other.get_parametric_equations() + t = -(self.a * other.point.x + self.b * other.point.y + self.c * other.point.z + self.d) / (self.a * other.direction.x + self.b * other.direction.y + self.c * other.direction.z) + return Point3(x(t), y(t), z(t)) +``` +
+ +###   ***def*** `cal_parallel_plane3(self, point: 'Point3') -> 'Plane3'` + + 计算平行于该平面且过指定点的平面。 + +Args: + + point: 指定点 + +Returns: + + 平面 + +
+源代码 + +```python +def cal_parallel_plane3(self, point: 'Point3') -> 'Plane3': + """ + 计算平行于该平面且过指定点的平面。 + Args: + point: 指定点 + Returns: + 平面 + """ + return Plane3.from_point_and_normal(point, self.normal) +``` +
+ +###   ***def*** `is_parallel(self, other: 'Plane3') -> bool` + + 判断两个平面是否平行。 + +Args: + + other: 另一个平面 + +Returns: + + 是否平行 + +
+源代码 + +```python +def is_parallel(self, other: 'Plane3') -> bool: + """ + 判断两个平面是否平行。 + Args: + other: 另一个平面 + Returns: + 是否平行 + """ + return self.normal.is_parallel(other.normal) +``` +
+ +###   ***@property*** +###   ***def*** `normal(self: Any) -> 'Vector3'` + + 平面的法向量。 + +Returns: + + 法向量 + +
+源代码 + +```python +@property +def normal(self) -> 'Vector3': + """ + 平面的法向量。 + Returns: + 法向量 + """ + return Vector3(self.a, self.b, self.c) +``` +
+ +###   ***@classmethod*** +###   ***def*** `from_point_and_normal(cls: Any, point: 'Point3', normal: 'Vector3') -> 'Plane3'` + + 工厂函数 由点和法向量构造平面(点法式构造)。 + +Args: + + point: 平面上的一点 + + normal: 法向量 + +Returns: + + 平面 + +
+源代码 + +```python +@classmethod +def from_point_and_normal(cls, point: 'Point3', normal: 'Vector3') -> 'Plane3': + """ + 工厂函数 由点和法向量构造平面(点法式构造)。 + Args: + point: 平面上的一点 + normal: 法向量 + Returns: + 平面 + """ + a, b, c = (normal.x, normal.y, normal.z) + d = -a * point.x - b * point.y - c * point.z + return cls(a, b, c, d) +``` +
+ +###   ***@classmethod*** +###   ***def*** `from_three_points(cls: Any, p1: 'Point3', p2: 'Point3', p3: 'Point3') -> 'Plane3'` + + 工厂函数 由三点构造平面。 + +Args: + + p1: 点1 + + p2: 点2 + + p3: 点3 + +Returns: + + 平面 + +
+源代码 + +```python +@classmethod +def from_three_points(cls, p1: 'Point3', p2: 'Point3', p3: 'Point3') -> 'Plane3': + """ + 工厂函数 由三点构造平面。 + Args: + p1: 点1 + p2: 点2 + p3: 点3 + Returns: + 平面 + """ + v1 = p2 - p1 + v2 = p3 - p1 + normal = v1.cross(v2) + return cls.from_point_and_normal(p1, normal) +``` +
+ +###   ***@classmethod*** +###   ***def*** `from_two_lines(cls: Any, l1: 'Line3', l2: 'Line3') -> 'Plane3'` + + 工厂函数 由两直线构造平面。 + +Args: + + l1: 直线1 + + l2: 直线2 + +Returns: + + 平面 + +
+源代码 + +```python +@classmethod +def from_two_lines(cls, l1: 'Line3', l2: 'Line3') -> 'Plane3': + """ + 工厂函数 由两直线构造平面。 + Args: + l1: 直线1 + l2: 直线2 + Returns: + 平面 + """ + v1 = l1.direction + v2 = l2.point - l1.point + if v2 == zero_vector3: + v2 = l2.get_point(1) - l1.point + return cls.from_point_and_normal(l1.point, v1.cross(v2)) +``` +
+ +###   ***@classmethod*** +###   ***def*** `from_point_and_line(cls: Any, point: 'Point3', line: 'Line3') -> 'Plane3'` + + 工厂函数 由点和直线构造平面。 + +Args: + + point: 面上一点 + + line: 面上直线,不包含点 + +Returns: + + 平面 + +
+源代码 + +```python +@classmethod +def from_point_and_line(cls, point: 'Point3', line: 'Line3') -> 'Plane3': + """ + 工厂函数 由点和直线构造平面。 + Args: + point: 面上一点 + line: 面上直线,不包含点 + Returns: + 平面 + """ + return cls.from_point_and_normal(point, line.direction) +``` +
+ +### ***var*** `direction = self.normal.cross(other.normal)` + + + +### ***var*** `t = -(self.a * other.point.x + self.b * other.point.y + self.c * other.point.z + self.d) / (self.a * other.direction.x + self.b * other.direction.y + self.c * other.direction.z)` + + + +### ***var*** `d = -a * point.x - b * point.y - c * point.z` + + + +### ***var*** `v1 = p2 - p1` + + + +### ***var*** `v2 = p3 - p1` + + + +### ***var*** `normal = v1.cross(v2)` + + + +### ***var*** `v1 = l1.direction` + + + +### ***var*** `v2 = l2.point - l1.point` + + + +### ***var*** `s = 'Plane3: '` + + + +### ***var*** `k = other.a / self.a` + + + +### ***var*** `A = np.array([[self.b, self.c], [other.b, other.c]])` + + + +### ***var*** `B = np.array([-self.d, -other.d])` + + + +### ***var*** `v2 = l2.get_point(1) - l1.point` + + + +### ***var*** `k = other.b / self.b` + + + +### ***var*** `A = np.array([[self.a, self.c], [other.a, other.c]])` + + + +### ***var*** `B = np.array([-self.d, -other.d])` + + + +### ***var*** `k = other.c / self.c` + + + +### ***var*** `A = np.array([[self.a, self.b], [other.a, other.b]])` + + + +### ***var*** `B = np.array([-self.d, -other.d])` + + + diff --git a/docs/api/mp_math/point.md b/docs/api/mp_math/point.md new file mode 100644 index 0000000..53e79f7 --- /dev/null +++ b/docs/api/mp_math/point.md @@ -0,0 +1,75 @@ +--- +title: mbcp.mp\nmath.point +order: 1 +icon: laptop-code +category: API +--- + +### ***class*** `Point3` + + + +###   ***def*** `__init__(self, x: float, y: float, z: float) -> None` + + 笛卡尔坐标系中的点。 + +Args: + + x: x 坐标 + + y: y 坐标 + + z: z 坐标 + +
+源代码 + +```python +def __init__(self, x: float, y: float, z: float): + """ + 笛卡尔坐标系中的点。 + Args: + x: x 坐标 + y: y 坐标 + z: z 坐标 + """ + self.x = x + self.y = y + self.z = z +``` +
+ +###   ***def*** `approx(self, other: 'Point3', epsilon: float) -> bool` + + 判断两个点是否近似相等。 + +Args: + + other: + + epsilon: + + + +Returns: + + 是否近似相等 + +
+源代码 + +```python +def approx(self, other: 'Point3', epsilon: float=APPROX) -> bool: + """ + 判断两个点是否近似相等。 + Args: + other: + epsilon: + + Returns: + 是否近似相等 + """ + return all([abs(self.x - other.x) < epsilon, abs(self.y - other.y) < epsilon, abs(self.z - other.z) < epsilon]) +``` +
+ diff --git a/docs/api/mp_math/segment.md b/docs/api/mp_math/segment.md new file mode 100644 index 0000000..7da8df6 --- /dev/null +++ b/docs/api/mp_math/segment.md @@ -0,0 +1,40 @@ +--- +title: mbcp.mp\nmath.segment +order: 1 +icon: laptop-code +category: API +--- + +### ***class*** `Segment3` + + + +###   ***def*** `__init__(self, p1: 'Point3', p2: 'Point3') -> None` + + 三维空间中的线段。 + +:param p1: + +:param p2: + +
+源代码 + +```python +def __init__(self, p1: 'Point3', p2: 'Point3'): + """ + 三维空间中的线段。 + :param p1: + :param p2: + """ + self.p1 = p1 + self.p2 = p2 + '方向向量' + self.direction = self.p2 - self.p1 + '长度' + self.length = self.direction.length + '中心点' + self.midpoint = Point3((self.p1.x + self.p2.x) / 2, (self.p1.y + self.p2.y) / 2, (self.p1.z + self.p2.z) / 2) +``` +
+ diff --git a/docs/api/mp_math/utils.md b/docs/api/mp_math/utils.md new file mode 100644 index 0000000..93cc597 --- /dev/null +++ b/docs/api/mp_math/utils.md @@ -0,0 +1,194 @@ +--- +title: mbcp.mp\nmath.utils +order: 1 +icon: laptop-code +category: API +--- + +### ***def*** `clamp(x: float, min_: float, max_: float) -> float` + +区间截断函数。 + +Args: + + x: + + min_: + + max_: + + + +Returns: + + 限制后的值 + +
+源代码 + +```python +def clamp(x: float, min_: float, max_: float) -> float: + """ + 区间截断函数。 + Args: + x: + min_: + max_: + + Returns: + 限制后的值 + """ + return max(min(x, max_), min_) +``` +
+ +### ***def*** `approx(x: float, y: float, epsilon: float) -> bool` + +判断两个数是否近似相等。或包装一个实数,用于判断是否近似于0。 + +Args: + + x: + + y: + + epsilon: + + + +Returns: + + 是否近似相等 + +
+源代码 + +```python +def approx(x: float, y: float=0.0, epsilon: float=APPROX) -> bool: + """ + 判断两个数是否近似相等。或包装一个实数,用于判断是否近似于0。 + Args: + x: + y: + epsilon: + + Returns: + 是否近似相等 + """ + return abs(x - y) < epsilon +``` +
+ +### ***def*** `sign(x: float, only_neg: bool) -> str` + +获取数的符号。 + +Args: + + x: 数 + + only_neg: 是否只返回负数的符号 + +Returns: + + 符号 + - "" + +
+源代码 + +```python +def sign(x: float, only_neg: bool=False) -> str: + """获取数的符号。 + Args: + x: 数 + only_neg: 是否只返回负数的符号 + Returns: + 符号 + - "" + """ + if x > 0: + return '+' if not only_neg else '' + elif x < 0: + return '-' + else: + return '' +``` +
+ +### ***def*** `sign_format(x: float, only_neg: bool) -> str` + +格式化符号数 + +-1 -> -1 + +1 -> +1 + +0 -> "" + +Args: + + x: 数 + + only_neg: 是否只返回负数的符号 + +Returns: + + 符号 + - "" + +
+源代码 + +```python +def sign_format(x: float, only_neg: bool=False) -> str: + """格式化符号数 + -1 -> -1 + 1 -> +1 + 0 -> "" + Args: + x: 数 + only_neg: 是否只返回负数的符号 + Returns: + 符号 + - "" + """ + if x > 0: + return f'+{x}' if not only_neg else f'{x}' + elif x < 0: + return f'-{abs(x)}' + else: + return '' +``` +
+ +### ***class*** `Approx` + +用于近似比较对象 + + + +已实现对象 实数 Vector3 Point3 Plane3 Line3 + +###   ***def*** `__init__(self, value: RealNumber) -> None` + +  + +
+源代码 + +```python +def __init__(self, value: RealNumber): + self.value = value +``` +
+ +###   ***def*** `raise_type_error(self, other: Any) -> None` + +  + +
+源代码 + +```python +def raise_type_error(self, other): + raise TypeError(f'Unsupported type: {type(self.value)} and {type(other)}') +``` +
+ diff --git a/docs/api/mp_math/vector.md b/docs/api/mp_math/vector.md new file mode 100644 index 0000000..73d7f22 --- /dev/null +++ b/docs/api/mp_math/vector.md @@ -0,0 +1,340 @@ +--- +title: mbcp.mp\nmath.vector +order: 1 +icon: laptop-code +category: API +--- + +### ***class*** `Vector3` + + + +###   ***def*** `__init__(self, x: float, y: float, z: float) -> None` + + 3维向量 + +Args: + + x: x轴分量 + + y: y轴分量 + + z: z轴分量 + +
+源代码 + +```python +def __init__(self, x: float, y: float, z: float): + """ + 3维向量 + Args: + x: x轴分量 + y: y轴分量 + z: z轴分量 + """ + self.x = x + self.y = y + self.z = z +``` +
+ +###   ***def*** `approx(self, other: 'Vector3', epsilon: float) -> bool` + + 判断两个向量是否近似相等。 + +Args: + + other: + + epsilon: + + + +Returns: + + 是否近似相等 + +
+源代码 + +```python +def approx(self, other: 'Vector3', epsilon: float=APPROX) -> bool: + """ + 判断两个向量是否近似相等。 + Args: + other: + epsilon: + + Returns: + 是否近似相等 + """ + return all([abs(self.x - other.x) < epsilon, abs(self.y - other.y) < epsilon, abs(self.z - other.z) < epsilon]) +``` +
+ +###   ***def*** `cal_angle(self, other: 'Vector3') -> 'AnyAngle'` + + 计算两个向量之间的夹角。 + +Args: + + other: 另一个向量 + +Returns: + + 夹角 + +
+源代码 + +```python +def cal_angle(self, other: 'Vector3') -> 'AnyAngle': + """ + 计算两个向量之间的夹角。 + Args: + other: 另一个向量 + Returns: + 夹角 + """ + return AnyAngle(math.acos(self @ other / (self.length * other.length)), is_radian=True) +``` +
+ +###   ***def*** `cross(self, other: 'Vector3') -> 'Vector3'` + + 向量积 叉乘:v1 cross v2 -> v3 + + + +叉乘为0,则两向量平行。 + +其余结果的模为平行四边形的面积。 + + + +返回如下行列式的结果: + + + +``i j k`` + + + +``x1 y1 z1`` + + + +``x2 y2 z2`` + + + +Args: + + other: + +Returns: + + 行列式的结果 + +
+源代码 + +```python +def cross(self, other: 'Vector3') -> 'Vector3': + """ + 向量积 叉乘:v1 cross v2 -> v3 + + 叉乘为0,则两向量平行。 + 其余结果的模为平行四边形的面积。 + + 返回如下行列式的结果: + + ``i j k`` + + ``x1 y1 z1`` + + ``x2 y2 z2`` + + Args: + other: + Returns: + 行列式的结果 + """ + return Vector3(self.y * other.z - self.z * other.y, self.z * other.x - self.x * other.z, self.x * other.y - self.y * other.x) +``` +
+ +###   ***def*** `is_approx_parallel(self, other: 'Vector3', epsilon: float) -> bool` + + 判断两个向量是否近似平行。 + +Args: + + other: 另一个向量 + + epsilon: 允许的误差 + +Returns: + + 是否近似平行 + +
+源代码 + +```python +def is_approx_parallel(self, other: 'Vector3', epsilon: float=APPROX) -> bool: + """ + 判断两个向量是否近似平行。 + Args: + other: 另一个向量 + epsilon: 允许的误差 + Returns: + 是否近似平行 + """ + return self.cross(other).length < epsilon +``` +
+ +###   ***def*** `is_parallel(self, other: 'Vector3') -> bool` + + 判断两个向量是否平行。 + +Args: + + other: 另一个向量 + +Returns: + + 是否平行 + +
+源代码 + +```python +def is_parallel(self, other: 'Vector3') -> bool: + """ + 判断两个向量是否平行。 + Args: + other: 另一个向量 + Returns: + 是否平行 + """ + return self.cross(other).approx(zero_vector3) +``` +
+ +###   ***def*** `normalize(self) -> None` + + 将向量归一化。 + + + +自体归一化,不返回值。 + +
+源代码 + +```python +def normalize(self): + """ + 将向量归一化。 + + 自体归一化,不返回值。 + """ + length = self.length + self.x /= length + self.y /= length + self.z /= length +``` +
+ +###   ***@property*** +###   ***def*** `np_array(self: Any) -> 'np.ndarray'` + + 返回numpy数组 + +Returns: + +
+源代码 + +```python +@property +def np_array(self) -> 'np.ndarray': + """ + 返回numpy数组 + Returns: + """ + return np.array([self.x, self.y, self.z]) +``` +
+ +###   ***@property*** +###   ***def*** `length(self: Any) -> float` + + 向量的模。 + +Returns: + + 模 + +
+源代码 + +```python +@property +def length(self) -> float: + """ + 向量的模。 + Returns: + 模 + """ + return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) +``` +
+ +###   ***@property*** +###   ***def*** `unit(self: Any) -> 'Vector3'` + + 获取该向量的单位向量。 + +Returns: + + 单位向量 + +
+源代码 + +```python +@property +def unit(self) -> 'Vector3': + """ + 获取该向量的单位向量。 + Returns: + 单位向量 + """ + return self / self.length +``` +
+ +### ***var*** `zero_vector3 = Vector3(0, 0, 0)` + + + +### ***var*** `x_axis = Vector3(1, 0, 0)` + + + +### ***var*** `y_axis = Vector3(0, 1, 0)` + + + +### ***var*** `z_axis = Vector3(0, 0, 1)` + + + +### ***var*** `length = self.length` + + + diff --git a/docs/api/particle/indedx.md b/docs/api/particle/indedx.md new file mode 100644 index 0000000..1669e90 --- /dev/null +++ b/docs/api/particle/indedx.md @@ -0,0 +1,7 @@ +--- +title: mbcp.particle.\n\ninit\n\n +order: 1 +icon: laptop-code +category: API +--- + diff --git a/docs/api/presets/indedx.md b/docs/api/presets/indedx.md new file mode 100644 index 0000000..85b4964 --- /dev/null +++ b/docs/api/presets/indedx.md @@ -0,0 +1,7 @@ +--- +title: mbcp.presets.\n\ninit\n\n +order: 1 +icon: laptop-code +category: API +--- + diff --git a/docs/api/presets/model/indedx.md b/docs/api/presets/model/indedx.md new file mode 100644 index 0000000..5986544 --- /dev/null +++ b/docs/api/presets/model/indedx.md @@ -0,0 +1,118 @@ +--- +title: mbcp.presets.model.\n\ninit\n\n +order: 1 +icon: laptop-code +category: API +--- + +### ***def*** `sphere(radius: float, density: float) -> None` + +生成球体上的点集。 + +Args: + + radius: + + density: + +Returns: + + List[Point3]: 球体上的点集。 + +
+源代码 + +```python +@staticmethod +def sphere(radius: float, density: float): + """ + 生成球体上的点集。 + Args: + radius: + density: + Returns: + List[Point3]: 球体上的点集。 + """ + area = 4 * np.pi * radius ** 2 + num = int(area * density) + phi_list = np.arccos([clamp(-1 + (2.0 * _ - 1.0) / num, -1, 1) for _ in range(num)]) + theta_list = np.sqrt(num * np.pi) * phi_list + x_array = radius * np.sin(phi_list) * np.cos(theta_list) + y_array = radius * np.sin(phi_list) * np.sin(theta_list) + z_array = radius * np.cos(phi_list) + return [Point3(x_array[i], y_array[i], z_array[i]) for i in range(num)] +``` +
+ +### ***class*** `GeometricModels` + + + +###   ***@staticmethod*** +###   ***def*** `sphere(radius: float, density: float) -> None` + + 生成球体上的点集。 + +Args: + + radius: + + density: + +Returns: + + List[Point3]: 球体上的点集。 + +
+源代码 + +```python +@staticmethod +def sphere(radius: float, density: float): + """ + 生成球体上的点集。 + Args: + radius: + density: + Returns: + List[Point3]: 球体上的点集。 + """ + area = 4 * np.pi * radius ** 2 + num = int(area * density) + phi_list = np.arccos([clamp(-1 + (2.0 * _ - 1.0) / num, -1, 1) for _ in range(num)]) + theta_list = np.sqrt(num * np.pi) * phi_list + x_array = radius * np.sin(phi_list) * np.cos(theta_list) + y_array = radius * np.sin(phi_list) * np.sin(theta_list) + z_array = radius * np.cos(phi_list) + return [Point3(x_array[i], y_array[i], z_array[i]) for i in range(num)] +``` +
+ +### ***var*** `area = 4 * np.pi * radius ** 2` + + + +### ***var*** `num = int(area * density)` + + + +### ***var*** `phi_list = np.arccos([clamp(-1 + (2.0 * _ - 1.0) / num, -1, 1) for _ in range(num)])` + + + +### ***var*** `theta_list = np.sqrt(num * np.pi) * phi_list` + + + +### ***var*** `x_array = radius * np.sin(phi_list) * np.cos(theta_list)` + + + +### ***var*** `y_array = radius * np.sin(phi_list) * np.sin(theta_list)` + + + +### ***var*** `z_array = radius * np.cos(phi_list)` + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..51d63d6 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,25 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "MBCP docs" + text: "More basic change particle" + tagline: 用于几何运算和粒子制作的库 + actions: + - theme: brand + text: 快速开始 + link: /guide + - theme: alt + text: API + link: api + +features: + - title: 高可用性 + details: 通过简单的接口,实现了大部分几何运算和粒子制作的需求 + - title: 高集成度 + details: 对numpyscipysumpy进行了封装和集成,使脚本编写像使用Geogebra一样简单 + - title: 内置预设 + details: 提供了大量的预设,包括常见的几何图形、粒子效果等,方便快速制作 +--- + diff --git a/docs/markdown-examples.md b/docs/markdown-examples.md new file mode 100644 index 0000000..f9258a5 --- /dev/null +++ b/docs/markdown-examples.md @@ -0,0 +1,85 @@ +# Markdown Extension Examples + +This page demonstrates some of the built-in markdown extensions provided by VitePress. + +## Syntax Highlighting + +VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: + +**Input** + +````md +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` +```` + +**Output** + +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` + +## Custom Containers + +**Input** + +```md +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: +``` + +**Output** + +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: + +## More + +Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). diff --git a/docs/mkdoc.py b/docs/mkdoc.py new file mode 100644 index 0000000..67083da --- /dev/null +++ b/docs/mkdoc.py @@ -0,0 +1,352 @@ +# -*- coding: utf-8 -*- +""" +MkDocs文档生成脚本 +用法 python docs/mkdoc.py +""" + +import ast +import os +import shutil +from typing import Any +from enum import Enum +from pydantic import BaseModel + +NO_TYPE_ANY = "Any" +NO_TYPE_HINT = "NoTypeHint" + + +class DefType(Enum): + FUNCTION = "function" + METHOD = "method" + STATIC_METHOD = "staticmethod" + CLASS_METHOD = "classmethod" + PROPERTY = "property" + + +class FunctionInfo(BaseModel): + name: str + args: list[tuple[str, str]] + return_type: str + docstring: str + source_code: str = "" + + type: DefType + """若为类中def,则有""" + is_async: bool + + +class AttributeInfo(BaseModel): + name: str + type: str + value: Any = None + docstring: str = "" + + +class ClassInfo(BaseModel): + name: str + docstring: str + methods: list[FunctionInfo] + attributes: list[AttributeInfo] + inherit: list[str] + + +class ModuleInfo(BaseModel): + module_path: str + """点分割模块路径 例如 liteyuki.bot""" + + functions: list[FunctionInfo] + classes: list[ClassInfo] + attributes: list[AttributeInfo] + docstring: str + + +def get_relative_path(base_path: str, target_path: str) -> str: + """ + 获取相对路径 + Args: + base_path: 基础路径 + target_path: 目标路径 + """ + return os.path.relpath(target_path, base_path) + + +def write_to_files(file_data: dict[str, str]): + """ + 输出文件 + Args: + file_data: 文件数据 相对路径 + """ + + for rp, data in file_data.items(): + + if not os.path.exists(os.path.dirname(rp)): + os.makedirs(os.path.dirname(rp)) + with open(rp, 'w', encoding='utf-8') as f: + f.write(data) + + +def get_file_list(module_folder: str): + file_list = [] + for root, dirs, files in os.walk(module_folder): + for file in files: + if file.endswith((".py", ".pyi")): + file_list.append(os.path.join(root, file)) + return file_list + + +def get_module_info_normal(file_path: str, ignore_private: bool = True) -> ModuleInfo: + """ + 获取函数和类 + Args: + file_path: Python 文件路径 + ignore_private: 忽略私有函数和类 + Returns: + 模块信息 + """ + + with open(file_path, 'r', encoding='utf-8') as file: + file_content = file.read() + tree = ast.parse(file_content) + + dot_sep_module_path = file_path.replace(os.sep, '.').replace(".py", "").replace(".pyi", "") + module_docstring = ast.get_docstring(tree) + + module_info = ModuleInfo( + module_path=dot_sep_module_path, + functions=[], + classes=[], + attributes=[], + docstring=module_docstring if module_docstring else "" + ) + + for node in ast.walk(tree): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + # 模块函数 且不在类中 若ignore_private=True则忽略私有函数 + if not any(isinstance(parent, ast.ClassDef) for parent in ast.iter_child_nodes(node)) and (not ignore_private or not node.name.startswith('_')): + + # 判断第一个参数是否为self或cls,后期用其他办法优化 + if node.args.args: + first_arg = node.args.args[0] + if first_arg.arg in ("self", "cls"): + continue + + function_docstring = ast.get_docstring(node) + + func_info = FunctionInfo( + name=node.name, + args=[(arg.arg, ast.unparse(arg.annotation) if arg.annotation else NO_TYPE_ANY) for arg in node.args.args], + return_type=ast.unparse(node.returns) if node.returns else "None", + docstring=function_docstring if function_docstring else "", + type=DefType.FUNCTION, + is_async=isinstance(node, ast.AsyncFunctionDef), + source_code=ast.unparse(node) + ) + module_info.functions.append(func_info) + + elif isinstance(node, ast.ClassDef): + # 模块类 + class_docstring = ast.get_docstring(node) + + class_info = ClassInfo( + name=node.name, + docstring=class_docstring if class_docstring else "", + methods=[], + attributes=[], + inherit=[ast.unparse(base) for base in node.bases] + ) + + for class_node in node.body: + # methods [instance, static, class, property],保留__init__方法 + if isinstance(class_node, ast.FunctionDef) and (not ignore_private or not class_node.name.startswith('_') or class_node.name == "__init__"): + method_docstring = ast.get_docstring(class_node) + def_type = DefType.METHOD + if class_node.decorator_list: + if any(isinstance(decorator, ast.Name) and decorator.id == "staticmethod" for decorator in class_node.decorator_list): + def_type = DefType.STATIC_METHOD + elif any(isinstance(decorator, ast.Name) and decorator.id == "classmethod" for decorator in class_node.decorator_list): + def_type = DefType.CLASS_METHOD + elif any(isinstance(decorator, ast.Name) and decorator.id == "property" for decorator in class_node.decorator_list): + def_type = DefType.PROPERTY + class_info.methods.append(FunctionInfo( + name=class_node.name, + args=[(arg.arg, ast.unparse(arg.annotation) if arg.annotation else NO_TYPE_ANY) for arg in class_node.args.args], + return_type=ast.unparse(class_node.returns) if class_node.returns else "None", + docstring=method_docstring if method_docstring else "", + type=def_type, + is_async=isinstance(class_node, ast.AsyncFunctionDef), + source_code=ast.unparse(class_node) + )) + # attributes + elif isinstance(class_node, ast.Assign): + for target in class_node.targets: + if isinstance(target, ast.Name): + class_info.attributes.append(AttributeInfo( + name=target.id, + type=ast.unparse(class_node.value) + )) + module_info.classes.append(class_info) + + elif isinstance(node, ast.Assign): + # 检查是否在类或函数中 + if not any(isinstance(parent, (ast.ClassDef, ast.FunctionDef)) for parent in ast.iter_child_nodes(node)): + # 模块属性变量 + for target in node.targets: + if isinstance(target, ast.Name) and (not ignore_private or not target.id.startswith('_')): + attr_type = NO_TYPE_HINT + if isinstance(node.value, ast.AnnAssign) and node.value.annotation: + attr_type = ast.unparse(node.value.annotation) + module_info.attributes.append(AttributeInfo( + name=target.id, + type=attr_type, + value=ast.unparse(node.value) if node.value else None + )) + + return module_info + + +def generate_markdown(module_info: ModuleInfo, front_matter=None, lang: str = "zh-CN") -> str: + """ + 生成模块的Markdown + 你可在此自定义生成的Markdown格式 + Args: + module_info: 模块信息 + front_matter: 自定义选项title, index, icon, category + lang: 语言 + Returns: + Markdown 字符串 + """ + + content = "" + + front_matter = "---\n" + "\n".join([f"{k}: {v}" for k, v in front_matter.items()]) + "\n---\n\n" + + content += front_matter + + # 模块函数 + for func in module_info.functions: + args_with_type = [f"{arg[0]}: {arg[1]}" if arg[1] else arg[0] for arg in func.args] + content += f"### ***{'async ' if func.is_async else ''}def*** `{func.name}({', '.join(args_with_type)}) -> {func.return_type}`\n\n" + + func.docstring = func.docstring.replace("\n", "\n\n") + content += f"{func.docstring}\n\n" + + # 函数源代码可展开区域 + content += f"
\n源代码\n\n```python\n{func.source_code}\n```\n
\n\n" + + # 类 + for cls in module_info.classes: + if cls.inherit: + inherit = f"({', '.join(cls.inherit)})" if cls.inherit else "" + content += f"### ***class*** `{cls.name}{inherit}`\n\n" + else: + content += f"### ***class*** `{cls.name}`\n\n" + + cls.docstring = cls.docstring.replace("\n", "\n\n") + content += f"{cls.docstring}\n\n" + for method in cls.methods: + # 类函数 + + if method.type != DefType.METHOD: + args_with_type = [f"{arg[0]}: {arg[1]}" if arg[1] else arg[0] for arg in method.args] + content += f"###   ***@{method.type.value}***\n" + else: + # self不加类型提示 + args_with_type = [f"{arg[0]}: {arg[1]}" if arg[1] and arg[0] != "self" else arg[0] for arg in method.args] + content += f"###   ***{'async ' if method.is_async else ''}def*** `{method.name}({', '.join(args_with_type)}) -> {method.return_type}`\n\n" + + method.docstring = method.docstring.replace("\n", "\n\n") + content += f" {method.docstring}\n\n" + # 函数源代码可展开区域 + + if lang == "zh-CN": + TEXT_SOURCE_CODE = "源代码" + else: + TEXT_SOURCE_CODE = "Source Code" + + content += f"
\n{TEXT_SOURCE_CODE}\n\n```python\n{method.source_code}\n```\n
\n\n" + for attr in cls.attributes: + content += f"###   ***attr*** `{attr.name}: {attr.type}`\n\n" + + # 模块属性 + for attr in module_info.attributes: + if attr.type == NO_TYPE_HINT: + content += f"### ***var*** `{attr.name} = {attr.value}`\n\n" + else: + content += f"### ***var*** `{attr.name}: {attr.type} = {attr.value}`\n\n" + + attr.docstring = attr.docstring.replace("\n", "\n\n") + content += f"{attr.docstring}\n\n" + + return content + + +def generate_docs(module_folder: str, output_dir: str, with_top: bool = False, lang: str = "zh-CN", ignored_paths=None): + """ + 生成文档 + Args: + module_folder: 模块文件夹 + output_dir: 输出文件夹 + with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md + ignored_paths: 忽略的路径 + lang: 语言 + """ + if ignored_paths is None: + ignored_paths = [] + file_data: dict[str, str] = {} # 路径 -> 字串 + + file_list = get_file_list(module_folder) + + # 清理输出目录 + shutil.rmtree(output_dir, ignore_errors=True) + os.mkdir(output_dir) + + replace_data = { + "__init__": "indedx", + ".py" : ".md", + } + + for pyfile_path in file_list: + if any(ignored_path.replace("\\", "/") in pyfile_path.replace("\\", "/") for ignored_path in ignored_paths): + continue + + no_module_name_pyfile_path = get_relative_path(module_folder, pyfile_path) # 去头路径 + + # markdown相对路径 + rel_md_path = pyfile_path if with_top else no_module_name_pyfile_path + for rk, rv in replace_data.items(): + rel_md_path = rel_md_path.replace(rk, rv) + + abs_md_path = os.path.join(output_dir, rel_md_path) + + # 获取模块信息 + module_info = get_module_info_normal(pyfile_path) + + # 生成markdown + + if "README" in abs_md_path: + front_matter = { + "title" : module_info.module_path.replace(".__init__", "").replace("_", "\\n"), + "index" : "true", + "icon" : "laptop-code", + "category": "API" + } + else: + front_matter = { + "title" : module_info.module_path.replace("_", "\\n"), + "order" : "1", + "icon" : "laptop-code", + "category": "API" + } + + md_content = generate_markdown(module_info, front_matter) + print(f"Generate {pyfile_path} -> {abs_md_path}") + file_data[abs_md_path] = md_content + + write_to_files(file_data) + + +# 入口脚本 +if __name__ == '__main__': + # 这里填入你的模块路径 + generate_docs('mbcp', 'docs/api', with_top=False, ignored_paths=["liteyuki/plugins"], lang="zh-CN") + # generate_docs('mbcp', 'docs/en/api', with_top=False, ignored_paths=["liteyuki/plugins"], lang="en") diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..a65c678 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,10 @@ +{ + "devDependencies": { + "vitepress": "^1.3.4" + }, + "scripts": { + "docs:dev": "vitepress dev", + "docs:build": "vitepress build", + "docs:preview": "vitepress preview" + } +} \ No newline at end of file diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml new file mode 100644 index 0000000..841c47c --- /dev/null +++ b/docs/pnpm-lock.yaml @@ -0,0 +1,1172 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + vitepress: + specifier: ^1.3.4 + version: 1.3.4(@algolia/client-search@5.1.1)(search-insights@2.17.0) + +packages: + + /@algolia/autocomplete-core@1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0)(search-insights@2.17.0): + resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0)(search-insights@2.17.0) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + dev: true + + /@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0)(search-insights@2.17.0): + resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} + peerDependencies: + search-insights: '>= 1 < 3' + dependencies: + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0) + search-insights: 2.17.0 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + dev: true + + /@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0): + resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + dependencies: + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0) + '@algolia/client-search': 5.1.1 + algoliasearch: 4.24.0 + dev: true + + /@algolia/autocomplete-shared@1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0): + resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + dependencies: + '@algolia/client-search': 5.1.1 + algoliasearch: 4.24.0 + dev: true + + /@algolia/cache-browser-local-storage@4.24.0: + resolution: {integrity: sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==} + dependencies: + '@algolia/cache-common': 4.24.0 + dev: true + + /@algolia/cache-common@4.24.0: + resolution: {integrity: sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==} + dev: true + + /@algolia/cache-in-memory@4.24.0: + resolution: {integrity: sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==} + dependencies: + '@algolia/cache-common': 4.24.0 + dev: true + + /@algolia/client-account@4.24.0: + resolution: {integrity: sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==} + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/client-analytics@4.24.0: + resolution: {integrity: sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==} + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/client-common@4.24.0: + resolution: {integrity: sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==} + dependencies: + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/client-common@5.1.1: + resolution: {integrity: sha512-jkQNQbGY+XQB3Eln7wqqdUZKBzG8lETcsaUk5gcMc6iIwyN/qW0v0fhpKPH+Kli+BImLxo0CWk12CvVvx2exWA==} + engines: {node: '>= 14.0.0'} + dev: true + + /@algolia/client-personalization@4.24.0: + resolution: {integrity: sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==} + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/client-search@4.24.0: + resolution: {integrity: sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==} + dependencies: + '@algolia/client-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/client-search@5.1.1: + resolution: {integrity: sha512-SFpb3FI/VouGou/vpuS7qeCA5Y/KpV42P6CEA/1MZQtl/xJkl6PVjikb+Q9YadeHi2jtDV/aQ6PyiVDnX4PQcw==} + engines: {node: '>= 14.0.0'} + dependencies: + '@algolia/client-common': 5.1.1 + '@algolia/requester-browser-xhr': 5.1.1 + '@algolia/requester-node-http': 5.1.1 + dev: true + + /@algolia/logger-common@4.24.0: + resolution: {integrity: sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==} + dev: true + + /@algolia/logger-console@4.24.0: + resolution: {integrity: sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==} + dependencies: + '@algolia/logger-common': 4.24.0 + dev: true + + /@algolia/recommend@4.24.0: + resolution: {integrity: sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==} + dependencies: + '@algolia/cache-browser-local-storage': 4.24.0 + '@algolia/cache-common': 4.24.0 + '@algolia/cache-in-memory': 4.24.0 + '@algolia/client-common': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/logger-console': 4.24.0 + '@algolia/requester-browser-xhr': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/requester-node-http': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /@algolia/requester-browser-xhr@4.24.0: + resolution: {integrity: sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==} + dependencies: + '@algolia/requester-common': 4.24.0 + dev: true + + /@algolia/requester-browser-xhr@5.1.1: + resolution: {integrity: sha512-NXmN1ujJCj5GlJQaMK6DbdiXdcf6nhRef/X40lu9TYi71q9xTo/5RPMI0K2iOp6g07S26BrXFOz6RSV3Ny4LLw==} + engines: {node: '>= 14.0.0'} + dependencies: + '@algolia/client-common': 5.1.1 + dev: true + + /@algolia/requester-common@4.24.0: + resolution: {integrity: sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==} + dev: true + + /@algolia/requester-node-http@4.24.0: + resolution: {integrity: sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==} + dependencies: + '@algolia/requester-common': 4.24.0 + dev: true + + /@algolia/requester-node-http@5.1.1: + resolution: {integrity: sha512-xwrgnNTIzgxDEx6zuCKSKTPzQLA8fL/WZiVB6fRpIu5agLMjoAi0cWA5YSDbo+2FFxqVgLqKY/Jz6mKmWtY15Q==} + engines: {node: '>= 14.0.0'} + dependencies: + '@algolia/client-common': 5.1.1 + dev: true + + /@algolia/transporter@4.24.0: + resolution: {integrity: sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==} + dependencies: + '@algolia/cache-common': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/requester-common': 4.24.0 + dev: true + + /@babel/helper-string-parser@7.24.8: + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.24.7: + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/parser@7.25.4: + resolution: {integrity: sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.25.4 + dev: true + + /@babel/types@7.25.4: + resolution: {integrity: sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + dev: true + + /@docsearch/css@3.6.1: + resolution: {integrity: sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==} + dev: true + + /@docsearch/js@3.6.1(@algolia/client-search@5.1.1)(search-insights@2.17.0): + resolution: {integrity: sha512-erI3RRZurDr1xES5hvYJ3Imp7jtrXj6f1xYIzDzxiS7nNBufYWPbJwrmMqWC5g9y165PmxEmN9pklGCdLi0Iqg==} + dependencies: + '@docsearch/react': 3.6.1(@algolia/client-search@5.1.1)(search-insights@2.17.0) + preact: 10.23.2 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + dev: true + + /@docsearch/react@3.6.1(@algolia/client-search@5.1.1)(search-insights@2.17.0): + resolution: {integrity: sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + dependencies: + '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0)(search-insights@2.17.0) + '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@5.1.1)(algoliasearch@4.24.0) + '@docsearch/css': 3.6.1 + algoliasearch: 4.24.0 + search-insights: 2.17.0 + transitivePeerDependencies: + - '@algolia/client-search' + dev: true + + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + dev: true + + /@rollup/rollup-android-arm-eabi@4.21.1: + resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.21.1: + resolution: {integrity: sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.21.1: + resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.21.1: + resolution: {integrity: sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.21.1: + resolution: {integrity: sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.21.1: + resolution: {integrity: sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==} + cpu: [arm] + os: [linux] + libc: [musl] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.21.1: + resolution: {integrity: sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==} + cpu: [arm64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.21.1: + resolution: {integrity: sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==} + cpu: [arm64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.21.1: + resolution: {integrity: sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.21.1: + resolution: {integrity: sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.21.1: + resolution: {integrity: sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.21.1: + resolution: {integrity: sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==} + cpu: [x64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.21.1: + resolution: {integrity: sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==} + cpu: [x64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.21.1: + resolution: {integrity: sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.21.1: + resolution: {integrity: sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.21.1: + resolution: {integrity: sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@shikijs/core@1.14.1: + resolution: {integrity: sha512-KyHIIpKNaT20FtFPFjCQB5WVSTpLR/n+jQXhWHWVUMm9MaOaG9BGOG0MSyt7yA4+Lm+4c9rTc03tt3nYzeYSfw==} + dependencies: + '@types/hast': 3.0.4 + dev: true + + /@shikijs/transformers@1.14.1: + resolution: {integrity: sha512-JJqL8QBVCJh3L61jqqEXgFq1cTycwjcGj7aSmqOEsbxnETM9hRlaB74QuXvY/fVJNjbNt8nvWo0VwAXKvMSLRg==} + dependencies: + shiki: 1.14.1 + dev: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/hast@3.0.4: + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + dependencies: + '@types/unist': 3.0.3 + dev: true + + /@types/linkify-it@5.0.0: + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + dev: true + + /@types/markdown-it@14.1.2: + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + dev: true + + /@types/mdurl@2.0.0: + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + dev: true + + /@types/unist@3.0.3: + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + dev: true + + /@types/web-bluetooth@0.0.20: + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + dev: true + + /@vitejs/plugin-vue@5.1.2(vite@5.4.2)(vue@3.4.38): + resolution: {integrity: sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 + vue: ^3.2.25 + dependencies: + vite: 5.4.2 + vue: 3.4.38 + dev: true + + /@vue/compiler-core@3.4.38: + resolution: {integrity: sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==} + dependencies: + '@babel/parser': 7.25.4 + '@vue/shared': 3.4.38 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.0 + dev: true + + /@vue/compiler-dom@3.4.38: + resolution: {integrity: sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==} + dependencies: + '@vue/compiler-core': 3.4.38 + '@vue/shared': 3.4.38 + dev: true + + /@vue/compiler-sfc@3.4.38: + resolution: {integrity: sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==} + dependencies: + '@babel/parser': 7.25.4 + '@vue/compiler-core': 3.4.38 + '@vue/compiler-dom': 3.4.38 + '@vue/compiler-ssr': 3.4.38 + '@vue/shared': 3.4.38 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.41 + source-map-js: 1.2.0 + dev: true + + /@vue/compiler-ssr@3.4.38: + resolution: {integrity: sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==} + dependencies: + '@vue/compiler-dom': 3.4.38 + '@vue/shared': 3.4.38 + dev: true + + /@vue/devtools-api@7.3.9: + resolution: {integrity: sha512-D+GTYtFg68bqSu66EugQUydsOqaDlPLNmYw5oYk8k81uBu9/bVTUrqlAJrAA9Am7MXhKz2gWdDkopY6sOBf/Bg==} + dependencies: + '@vue/devtools-kit': 7.3.9 + dev: true + + /@vue/devtools-kit@7.3.9: + resolution: {integrity: sha512-Gr17nA+DaQzqyhNx1DUJr1CJRzTRfbIuuC80ZgU8MD/qNO302tv9la+ROi+Uaw+ULVwU9T71GnwLy4n8m9Lspg==} + dependencies: + '@vue/devtools-shared': 7.3.9 + birpc: 0.2.17 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.1 + dev: true + + /@vue/devtools-shared@7.3.9: + resolution: {integrity: sha512-CdfMRZKXyI8vw+hqOcQIiLihB6Hbbi7WNZGp7LsuH1Qe4aYAFmTaKjSciRZ301oTnwmU/knC/s5OGuV6UNiNoA==} + dependencies: + rfdc: 1.4.1 + dev: true + + /@vue/reactivity@3.4.38: + resolution: {integrity: sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==} + dependencies: + '@vue/shared': 3.4.38 + dev: true + + /@vue/runtime-core@3.4.38: + resolution: {integrity: sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==} + dependencies: + '@vue/reactivity': 3.4.38 + '@vue/shared': 3.4.38 + dev: true + + /@vue/runtime-dom@3.4.38: + resolution: {integrity: sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==} + dependencies: + '@vue/reactivity': 3.4.38 + '@vue/runtime-core': 3.4.38 + '@vue/shared': 3.4.38 + csstype: 3.1.3 + dev: true + + /@vue/server-renderer@3.4.38(vue@3.4.38): + resolution: {integrity: sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==} + peerDependencies: + vue: 3.4.38 + dependencies: + '@vue/compiler-ssr': 3.4.38 + '@vue/shared': 3.4.38 + vue: 3.4.38 + dev: true + + /@vue/shared@3.4.38: + resolution: {integrity: sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==} + dev: true + + /@vueuse/core@11.0.3(vue@3.4.38): + resolution: {integrity: sha512-RENlh64+SYA9XMExmmH1a3TPqeIuJBNNB/63GT35MZI+zpru3oMRUA6cEFr9HmGqEgUisurwGwnIieF6qu3aXw==} + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 11.0.3 + '@vueuse/shared': 11.0.3(vue@3.4.38) + vue-demi: 0.14.10(vue@3.4.38) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + + /@vueuse/integrations@11.0.3(focus-trap@7.5.4)(vue@3.4.38): + resolution: {integrity: sha512-w6CDisaxs19S5Fd+NPPLFaA3GoX5gxuxrbTTBu0EYap7oH13w75L6C/+7e9mcoF9akhcR6GyYajwVMQEjdapJg==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + dependencies: + '@vueuse/core': 11.0.3(vue@3.4.38) + '@vueuse/shared': 11.0.3(vue@3.4.38) + focus-trap: 7.5.4 + vue-demi: 0.14.10(vue@3.4.38) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + + /@vueuse/metadata@11.0.3: + resolution: {integrity: sha512-+FtbO4SD5WpsOcQTcC0hAhNlOid6QNLzqedtquTtQ+CRNBoAt9GuV07c6KNHK1wCmlq8DFPwgiLF2rXwgSHX5Q==} + dev: true + + /@vueuse/shared@11.0.3(vue@3.4.38): + resolution: {integrity: sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==} + dependencies: + vue-demi: 0.14.10(vue@3.4.38) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + + /algoliasearch@4.24.0: + resolution: {integrity: sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==} + dependencies: + '@algolia/cache-browser-local-storage': 4.24.0 + '@algolia/cache-common': 4.24.0 + '@algolia/cache-in-memory': 4.24.0 + '@algolia/client-account': 4.24.0 + '@algolia/client-analytics': 4.24.0 + '@algolia/client-common': 4.24.0 + '@algolia/client-personalization': 4.24.0 + '@algolia/client-search': 4.24.0 + '@algolia/logger-common': 4.24.0 + '@algolia/logger-console': 4.24.0 + '@algolia/recommend': 4.24.0 + '@algolia/requester-browser-xhr': 4.24.0 + '@algolia/requester-common': 4.24.0 + '@algolia/requester-node-http': 4.24.0 + '@algolia/transporter': 4.24.0 + dev: true + + /birpc@0.2.17: + resolution: {integrity: sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==} + dev: true + + /copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + dependencies: + is-what: 4.1.16 + dev: true + + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true + + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + dev: true + + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + + /focus-trap@7.5.4: + resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} + dependencies: + tabbable: 6.2.0 + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + dev: true + + /is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + dev: true + + /magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + dev: true + + /mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + dev: true + + /minisearch@7.1.0: + resolution: {integrity: sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==} + dev: true + + /mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + dev: true + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + dev: true + + /picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + dev: true + + /postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + dev: true + + /preact@10.23.2: + resolution: {integrity: sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==} + dev: true + + /rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + dev: true + + /rollup@4.21.1: + resolution: {integrity: sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.21.1 + '@rollup/rollup-android-arm64': 4.21.1 + '@rollup/rollup-darwin-arm64': 4.21.1 + '@rollup/rollup-darwin-x64': 4.21.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.1 + '@rollup/rollup-linux-arm-musleabihf': 4.21.1 + '@rollup/rollup-linux-arm64-gnu': 4.21.1 + '@rollup/rollup-linux-arm64-musl': 4.21.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1 + '@rollup/rollup-linux-riscv64-gnu': 4.21.1 + '@rollup/rollup-linux-s390x-gnu': 4.21.1 + '@rollup/rollup-linux-x64-gnu': 4.21.1 + '@rollup/rollup-linux-x64-musl': 4.21.1 + '@rollup/rollup-win32-arm64-msvc': 4.21.1 + '@rollup/rollup-win32-ia32-msvc': 4.21.1 + '@rollup/rollup-win32-x64-msvc': 4.21.1 + fsevents: 2.3.3 + dev: true + + /search-insights@2.17.0: + resolution: {integrity: sha512-AskayU3QNsXQzSL6v4LTYST7NNfs2HWyHHB+sdORP9chsytAhro5XRfToAMI/LAVYgNbzowVZTMfBRodgbUHKg==} + dev: true + + /shiki@1.14.1: + resolution: {integrity: sha512-FujAN40NEejeXdzPt+3sZ3F2dx1U24BY2XTY01+MG8mbxCiA2XukXdcbyMyLAHJ/1AUUnQd1tZlvIjefWWEJeA==} + dependencies: + '@shikijs/core': 1.14.1 + '@types/hast': 3.0.4 + dev: true + + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + dev: true + + /speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + dev: true + + /superjson@2.2.1: + resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} + engines: {node: '>=16'} + dependencies: + copy-anything: 3.0.5 + dev: true + + /tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + dev: true + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + + /vite@5.4.2: + resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.21.5 + postcss: 8.4.41 + rollup: 4.21.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitepress@1.3.4(@algolia/client-search@5.1.1)(search-insights@2.17.0): + resolution: {integrity: sha512-I1/F6OW1xl3kW4PaIMC6snxjWgf3qfziq2aqsDoFc/Gt41WbcRv++z8zjw8qGRIJ+I4bUW7ZcKFDHHN/jkH9DQ==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + dependencies: + '@docsearch/css': 3.6.1 + '@docsearch/js': 3.6.1(@algolia/client-search@5.1.1)(search-insights@2.17.0) + '@shikijs/core': 1.14.1 + '@shikijs/transformers': 1.14.1 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.1.2(vite@5.4.2)(vue@3.4.38) + '@vue/devtools-api': 7.3.9 + '@vue/shared': 3.4.38 + '@vueuse/core': 11.0.3(vue@3.4.38) + '@vueuse/integrations': 11.0.3(focus-trap@7.5.4)(vue@3.4.38) + focus-trap: 7.5.4 + mark.js: 8.11.1 + minisearch: 7.1.0 + shiki: 1.14.1 + vite: 5.4.2 + vue: 3.4.38 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - '@vue/composition-api' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + dev: true + + /vue-demi@0.14.10(vue@3.4.38): + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: 3.4.38 + dev: true + + /vue@3.4.38: + resolution: {integrity: sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/compiler-dom': 3.4.38 + '@vue/compiler-sfc': 3.4.38 + '@vue/runtime-dom': 3.4.38 + '@vue/server-renderer': 3.4.38(vue@3.4.38) + '@vue/shared': 3.4.38 + dev: true diff --git a/tests/test_angle.py b/tests/test_angle.py index 6ed71f8..f0943e8 100644 --- a/tests/test_angle.py +++ b/tests/test_angle.py @@ -16,7 +16,7 @@ from tests.answer import output_ans class TestAngle: def test_radian_to_degree(self): angle = AnyAngle(1, is_radian=True) - output_ans(190 / PI, angle.degree, question="弧度转角度1") + output_ans(180 / PI, angle.degree, question="弧度转角度1") angle = AnyAngle(2, is_radian=True) output_ans(360 / PI, angle.degree, question="弧度转角度2")