diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 29e7fff..e3c2e1c 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -13,7 +13,7 @@ on: # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages permissions: - contents: read + contents: write pages: write id-token: write @@ -42,7 +42,9 @@ jobs: - name: Setup API markdown run: |- python -m pip install pydantic - python docs/mkdoc.py + python liteyuki_autodoc mbcp -o docs/api -l zh-Hans + python liteyuki_autodoc mbcp -o docs/en/api -l en + python liteyuki_autodoc mbcp -o docs/ja/api -l ja - name: 安装 pnpm uses: pnpm/action-setup@v2 @@ -67,5 +69,5 @@ jobs: uses: JamesIves/github-pages-deploy-action@v4 with: # 这是文档部署到的分支名称 - branch: gh-pages + branch: docs folder: docs/.vitepress/dist \ No newline at end of file diff --git a/README.md b/README.md index 2a00fd1..38edea8 100644 --- a/README.md +++ b/README.md @@ -10,5 +10,4 @@ A Minecraft particle production library ## 示例 - [【特效红石音乐】童话镇~「总有一条蜿蜒在童话镇里...」](https://www.bilibili.com/video/BV1xE4m1d72j) -- [【特效红石音乐】使一颗心免于哀伤 If I Can Stop One Heart From Breaking「崩坏:星穹铁道 EP」](https://www.bilibili.com/video/BV1B1421t7i3) - +- [【特效红石音乐】使一颗心免于哀伤 If I Can Stop One Heart From Breaking「崩坏:星穹铁道 EP」](https://www.bilibili.com/video/BV1B1421t7i3) \ No newline at end of file diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts deleted file mode 100644 index 72532fd..0000000 --- a/docs/.vitepress/config.mts +++ /dev/null @@ -1,39 +0,0 @@ -import { defineConfig } from 'vitepress' - -// https://vitepress.dev/reference/site-config -export default defineConfig({ - title: "MBCP docs", - description: "MBCP library docs", - locales: { - root: { - label: '简体中文', - lang: 'zh-CN' - }, - en: { - label: 'English', - lang: 'en-US' - } - }, - 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/snowykami/mbcp' } - ] - }, - srcDir: '.' -}) diff --git a/docs/.vitepress/config/common.ts b/docs/.vitepress/config/common.ts index 68cac75..7d91400 100644 --- a/docs/.vitepress/config/common.ts +++ b/docs/.vitepress/config/common.ts @@ -1,8 +1,23 @@ import {defineConfig} from 'vitepress' +import AutoSidebarPlugin from 'vitepress-auto-sidebar-plugin' + export const common = defineConfig({ title: "MBCP docs", description: "MBCP library docs", + vite: { + plugins: [ + AutoSidebarPlugin({ + // 如果不指定 `srcDir`,则默认使用 `vitepress` 的 `srcDir` + ignoreList: [ + 'README.md' + ], + title: { + mode: text => text.toLowerCase() + } + }), + ], + }, themeConfig: { // https://vitepress.dev/reference/default-theme-config socialLinks: [ diff --git a/docs/.vitepress/config/zh.ts b/docs/.vitepress/config/zh.ts index 3b98a70..8c5003f 100644 --- a/docs/.vitepress/config/zh.ts +++ b/docs/.vitepress/config/zh.ts @@ -1,10 +1,21 @@ import {defineConfig} from 'vitepress' export const zh = defineConfig({ - lang: "zh-Hans", description: "一个用于Minecraft粒子计算和生成的库", themeConfig: { + nav: [ + {text: '快速开始', link: '/guide'}, + {text: 'API文档', link: '/api/'}, + {text: '实例', link: '/demo/'}, + ], + // sidebar: { + // '/api/': { + // base: '/api/', + // items: [ + // ] + // } + // } }, }) \ No newline at end of file diff --git a/docs/api/index.md b/docs/api/index.md index f28b6d1..6d251e8 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -1,6 +1,3 @@ --- -title: mbcp.\n\ninit\n\n -order: 1 -icon: laptop-code -category: API ---- \ No newline at end of file +title: mbcp +--- diff --git a/docs/api/mp_math/angle.md b/docs/api/mp_math/angle.md index a88b240..042a44d 100644 --- a/docs/api/mp_math/angle.md +++ b/docs/api/mp_math/angle.md @@ -1,26 +1,25 @@ --- -title: mbcp.mp\nmath.angle -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.angle --- +### ***class*** `Angle` ### ***class*** `AnyAngle` +- #### *def* `__init__(self, value: float, is_radian: bool = False)` -###   ***def*** `__init__(self, value: float, is_radian: bool) -> None` +任意角度。 - 任意角度。 +参数: -Args: +value: 角度或弧度值 - value: 角度或弧度值 +is_radian: 是否为弧度,默认为否 - is_radian: 是否为弧度,默认为否 +- #
-源代码 +源码 ```python def __init__(self, value: float, is_radian: bool=False): @@ -37,17 +36,20 @@ def __init__(self, value: float, is_radian: bool=False): ```
-###   ***@property*** -###   ***def*** `complementary(self: Any) -> 'AnyAngle'` +- #### `@property` +- #### *def* `complementary(self)` - 余角:两角的和为90°。 -Returns: +余角:两角的和为90°。 - 余角 +返回: +余角 + + +- #
-源代码 +源码 ```python @property @@ -61,17 +63,20 @@ def complementary(self) -> 'AnyAngle': ```
-###   ***@property*** -###   ***def*** `supplementary(self: Any) -> 'AnyAngle'` +- #### `@property` +- #### *def* `supplementary(self)` - 补角:两角的和为180°。 -Returns: +补角:两角的和为180°。 - 补角 +返回: +补角 + + +- #
-源代码 +源码 ```python @property @@ -85,17 +90,20 @@ def supplementary(self) -> 'AnyAngle': ```
-###   ***@property*** -###   ***def*** `degree(self: Any) -> float` +- #### `@property` +- #### *def* `degree(self)` - 角度。 -Returns: +角度。 - 弧度 +返回: +弧度 + + +- #
-源代码 +源码 ```python @property @@ -109,17 +117,20 @@ def degree(self) -> float: ```
-###   ***@property*** -###   ***def*** `minimum_positive(self: Any) -> 'AnyAngle'` +- #### `@property` +- #### *def* `minimum_positive(self)` - 最小正角。 -Returns: +最小正角。 - 最小正角度 +返回: +最小正角度 + + +- #
-源代码 +源码 ```python @property @@ -133,17 +144,20 @@ def minimum_positive(self) -> 'AnyAngle': ```
-###   ***@property*** -###   ***def*** `maximum_negative(self: Any) -> 'AnyAngle'` +- #### `@property` +- #### *def* `maximum_negative(self)` - 最大负角。 -Returns: +最大负角。 - 最大负角度 +返回: +最大负角度 + + +- #
-源代码 +源码 ```python @property @@ -157,17 +171,20 @@ def maximum_negative(self) -> 'AnyAngle': ```
-###   ***@property*** -###   ***def*** `sin(self: Any) -> float` +- #### `@property` +- #### *def* `sin(self)` - 正弦值。 -Returns: +正弦值。 - 正弦值 +返回: +正弦值 + + +- #
-源代码 +源码 ```python @property @@ -181,17 +198,20 @@ def sin(self) -> float: ```
-###   ***@property*** -###   ***def*** `cos(self: Any) -> float` +- #### `@property` +- #### *def* `cos(self)` - 余弦值。 -Returns: +余弦值。 - 余弦值 +返回: +余弦值 + + +- #
-源代码 +源码 ```python @property @@ -205,17 +225,20 @@ def cos(self) -> float: ```
-###   ***@property*** -###   ***def*** `tan(self: Any) -> float` +- #### `@property` +- #### *def* `tan(self)` - 正切值。 -Returns: +正切值。 - 正切值 +返回: +正切值 + + +- #
-源代码 +源码 ```python @property @@ -229,17 +252,20 @@ def tan(self) -> float: ```
-###   ***@property*** -###   ***def*** `cot(self: Any) -> float` +- #### `@property` +- #### *def* `cot(self)` - 余切值。 -Returns: +余切值。 - 余切值 +返回: +余切值 + + +- #
-源代码 +源码 ```python @property @@ -253,17 +279,20 @@ def cot(self) -> float: ```
-###   ***@property*** -###   ***def*** `sec(self: Any) -> float` +- #### `@property` +- #### *def* `sec(self)` - 正割值。 -Returns: +正割值。 - 正割值 +返回: +正割值 + + +- #
-源代码 +源码 ```python @property @@ -277,17 +306,20 @@ def sec(self) -> float: ```
-###   ***@property*** -###   ***def*** `csc(self: Any) -> float` +- #### `@property` +- #### *def* `csc(self)` - 余割值。 -Returns: +余割值。 - 余割值 +返回: +余割值 + + +- #
-源代码 +源码 ```python @property @@ -301,3 +333,117 @@ def csc(self) -> float: ```
+- #### *def* `__add__(self, other: 'AnyAngle')` + +- # +
+源码 + +```python +def __add__(self, other: 'AnyAngle') -> 'AnyAngle': + return AnyAngle(self.radian + other.radian, is_radian=True) +``` +
+ +- #### *def* `__eq__(self, other)` + +- # +
+源码 + +```python +def __eq__(self, other): + return approx(self.radian, other.radian) +``` +
+ +- #### *def* `__sub__(self, other: 'AnyAngle')` + +- # +
+源码 + +```python +def __sub__(self, other: 'AnyAngle') -> 'AnyAngle': + return AnyAngle(self.radian - other.radian, is_radian=True) +``` +
+ +- #### *def* `__mul__(self, other: float)` + +- # +
+源码 + +```python +def __mul__(self, other: float) -> 'AnyAngle': + return AnyAngle(self.radian * other, is_radian=True) +``` +
+ +- #### *def* `__repr__(self)` + +- # +
+源码 + +```python +def __repr__(self): + return f'AnyAngle({self.radian}, is_radian=True)' +``` +
+ +- #### *def* `__str__(self)` + +- # +
+源码 + +```python +def __str__(self): + return f'AnyAngle({self.degree}° or {self.radian} rad)' +``` +
+ +- #### `@overload` +- #### *def* `__truediv__(self, other: float)` + +- # +
+源码 + +```python +@overload +def __truediv__(self, other: float) -> 'AnyAngle': + ... +``` +
+ +- #### `@overload` +- #### *def* `__truediv__(self, other: 'AnyAngle')` + +- # +
+源码 + +```python +@overload +def __truediv__(self, other: 'AnyAngle') -> float: + ... +``` +
+ +- #### *def* `__truediv__(self, other)` + +- # +
+源码 + +```python +def __truediv__(self, other): + if isinstance(other, AnyAngle): + return self.radian / other.radian + return AnyAngle(self.radian / other, is_radian=True) +``` +
+ diff --git a/docs/api/mp_math/const.md b/docs/api/mp_math/const.md index 5e9f287..888edf3 100644 --- a/docs/api/mp_math/const.md +++ b/docs/api/mp_math/const.md @@ -1,31 +1,15 @@ --- -title: mbcp.mp\nmath.const -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.const --- - ### ***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 index cbdba56..a051e91 100644 --- a/docs/api/mp_math/equation.md +++ b/docs/api/mp_math/equation.md @@ -1,32 +1,25 @@ --- -title: mbcp.mp\nmath.equation -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.equation --- +### ***var*** `result_func = get_partial_derivative_func(result_func, v, epsilon)` + +### *def* `get_partial_derivative_func(func: MultiVarsFunc = EPSILON)` -### ***def*** `get_partial_derivative_func(func: MultiVarsFunc, var: int | tuple[int, ...], epsilon: Number) -> MultiVarsFunc` 求N元函数一阶偏导函数。这玩意不太稳定,慎用。 -Args: +参数: - func: 函数 +func: 函数 - var: 变量位置,可为整数(一阶偏导)或整数元组(高阶偏导) +var: 变量位置,可为整数(一阶偏导)或整数元组(高阶偏导) - epsilon: 偏移量 +epsilon: 偏移量 -Returns: - 偏导函数 - -Raises: - - ValueError: 无效变量类型
-源代码 +源码 ```python def get_partial_derivative_func(func: MultiVarsFunc, var: int | tuple[int, ...], epsilon: Number=EPSILON) -> MultiVarsFunc: @@ -63,12 +56,11 @@ def get_partial_derivative_func(func: MultiVarsFunc, var: int | tuple[int, ...], ```
-### ***def*** `partial_derivative_func() -> Var` - +### *def* `partial_derivative_func()`
-源代码 +源码 ```python def partial_derivative_func(*args: Var) -> Var: @@ -80,12 +72,11 @@ def partial_derivative_func(*args: Var) -> Var: ```
-### ***def*** `high_order_partial_derivative_func() -> Var` - +### *def* `high_order_partial_derivative_func()`
-源代码 +源码 ```python def high_order_partial_derivative_func(*args: Var) -> Var: @@ -98,28 +89,32 @@ def high_order_partial_derivative_func(*args: Var) -> Var: ### ***class*** `CurveEquation` +- #### *def* `__init__(self, x_func: OneVarFunc, y_func: OneVarFunc, z_func: OneVarFunc)` -###   ***def*** `__init__(self, x_func: OneVarFunc, y_func: OneVarFunc, z_func: OneVarFunc) -> None` +曲线方程。 - 曲线方程。 +参数: -:param x_func: +x_func: x函数 -:param y_func: +y_func: y函数 -:param z_func: +z_func: z函数 + +- #
-源代码 +源码 ```python def __init__(self, x_func: OneVarFunc, y_func: OneVarFunc, z_func: OneVarFunc): """ 曲线方程。 - :param x_func: - :param y_func: - :param z_func: + Args: + x_func: x函数 + y_func: y函数 + z_func: z函数 """ self.x_func = x_func self.y_func = y_func @@ -127,19 +122,48 @@ def __init__(self, x_func: OneVarFunc, y_func: OneVarFunc, z_func: OneVarFunc): ```
-### ***var*** `args_list_plus = list(args)` +- #### *def* `__call__(self)` +计算曲线上的点。 -### ***var*** `args_list_minus = list(args)` +参数: + +*t: + +参数: +- # +
+源码 -### ***var*** `result_func = func` +```python +def __call__(self, *t: Var) -> Point3 | tuple[Point3, ...]: + """ + 计算曲线上的点。 + Args: + *t: + 参数 + Returns: + """ + if len(t) == 1: + return Point3(self.x_func(t[0]), self.y_func(t[0]), self.z_func(t[0])) + else: + return tuple([Point3(x, y, z) for x, y, z in zip(self.x_func(t), self.y_func(t), self.z_func(t))]) +``` +
+- #### *def* `__str__(self)` -### ***var*** `result_func = get_partial_derivative_func(result_func, v, epsilon)` - +- # +
+源码 +```python +def __str__(self): + return 'CurveEquation()' +``` +
diff --git a/docs/api/mp_math/index.md b/docs/api/mp_math/index.md index 21d2307..6e3261c 100644 --- a/docs/api/mp_math/index.md +++ b/docs/api/mp_math/index.md @@ -1,7 +1,3 @@ --- -title: mbcp.mp\nmath.\n\ninit\n\n -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math --- - diff --git a/docs/api/mp_math/line.md b/docs/api/mp_math/line.md index 3ae3642..e564aa8 100644 --- a/docs/api/mp_math/line.md +++ b/docs/api/mp_math/line.md @@ -1,26 +1,23 @@ --- -title: mbcp.mp\nmath.line -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.line --- - ### ***class*** `Line3` +- #### *def* `__init__(self, point: 'Point3', direction: 'Vector3')` -###   ***def*** `__init__(self, point: 'Point3', direction: 'Vector3') -> None` +三维空间中的直线。由一个点和一个方向向量确定。 - 三维空间中的直线。由一个点和一个方向向量确定。 +参数: -Args: +point: 直线上的一点 - point: 直线上的一点 +direction: 直线的方向向量 - direction: 直线的方向向量 +- #
-源代码 +源码 ```python def __init__(self, point: 'Point3', direction: 'Vector3'): @@ -35,22 +32,21 @@ def __init__(self, point: 'Point3', direction: 'Vector3'): ```
-###   ***def*** `approx(self, other: 'Line3', epsilon: float) -> bool` +- #### *def* `approx(self, other: 'Line3', epsilon: float = APPROX)` - 判断两条直线是否近似相等。 -Args: +判断两条直线是否近似相等。 - other: 另一条直线 +参数: - epsilon: 误差 +other: 另一条直线 -Returns: +epsilon: 误差 - 是否近似相等 +- #
-源代码 +源码 ```python def approx(self, other: 'Line3', epsilon: float=APPROX) -> bool: @@ -66,24 +62,19 @@ def approx(self, other: 'Line3', epsilon: float=APPROX) -> bool: ```
-###   ***def*** `cal_angle(self, other: 'Line3') -> 'AnyAngle'` +- #### *def* `cal_angle(self, other: 'Line3')` - 计算直线和直线之间的夹角。 -Args: +计算直线和直线之间的夹角。 - other: 另一条直线 +参数: -Returns: +other: 另一条直线 - 夹角弧度 - -Raises: - - TypeError: 不支持的类型 +- #
-源代码 +源码 ```python def cal_angle(self, other: 'Line3') -> 'AnyAngle': @@ -100,26 +91,19 @@ def cal_angle(self, other: 'Line3') -> 'AnyAngle': ```
-###   ***def*** `cal_distance(self, other: 'Line3 | Point3') -> float` - - 计算直线和直线或点之间的距离。 - -Args: - - other: 平行直线或点 +- #### *def* `cal_distance(self, other: 'Line3 | Point3')` +计算直线和直线或点之间的距离。 -Returns: +参数: - 距离 +other: 平行直线或点 -Raises: - - TypeError: 不支持的类型 +- #
-源代码 +源码 ```python def cal_distance(self, other: 'Line3 | Point3') -> float: @@ -149,26 +133,19 @@ def cal_distance(self, other: 'Line3 | Point3') -> float: ```
-###   ***def*** `cal_intersection(self, other: 'Line3') -> 'Point3'` +- #### *def* `cal_intersection(self, other: 'Line3')` - 计算两条直线的交点。 -Args: +计算两条直线的交点。 - other: 另一条直线 +参数: -Returns: +other: 另一条直线 - 交点 - -Raises: - - ValueError: 直线平行 - - ValueError: 直线不共面 +- #
-源代码 +源码 ```python def cal_intersection(self, other: 'Line3') -> 'Point3': @@ -190,20 +167,19 @@ def cal_intersection(self, other: 'Line3') -> 'Point3': ```
-###   ***def*** `cal_perpendicular(self, point: 'Point3') -> 'Line3'` +- #### *def* `cal_perpendicular(self, point: 'Point3')` - 计算直线经过指定点p的垂线。 -Args: +计算直线经过指定点p的垂线。 - point: 指定点 +参数: -Returns: +point: 指定点 - 垂线 +- #
-源代码 +源码 ```python def cal_perpendicular(self, point: 'Point3') -> 'Line3': @@ -218,20 +194,19 @@ def cal_perpendicular(self, point: 'Point3') -> 'Line3': ```
-###   ***def*** `get_point(self, t: RealNumber) -> 'Point3'` +- #### *def* `get_point(self, t: RealNumber)` - 获取直线上的点。同一条直线,但起始点和方向向量不同,则同一个t对应的点不同。 -Args: +获取直线上的点。同一条直线,但起始点和方向向量不同,则同一个t对应的点不同。 - t: 参数t +参数: -Returns: +t: 参数t - 点 +- #
-源代码 +源码 ```python def get_point(self, t: RealNumber) -> 'Point3': @@ -246,16 +221,19 @@ def get_point(self, t: RealNumber) -> 'Point3': ```
-###   ***def*** `get_parametric_equations(self) -> tuple[OneSingleVarFunc, OneSingleVarFunc, OneSingleVarFunc]` +- #### *def* `get_parametric_equations(self)` - 获取直线的参数方程。 -Returns: +获取直线的参数方程。 - x(t), y(t), z(t) +返回: +x(t), y(t), z(t) + + +- #
-源代码 +源码 ```python def get_parametric_equations(self) -> tuple[OneSingleVarFunc, OneSingleVarFunc, OneSingleVarFunc]: @@ -268,22 +246,21 @@ def get_parametric_equations(self) -> tuple[OneSingleVarFunc, OneSingleVarFunc, ```
-###   ***def*** `is_approx_parallel(self, other: 'Line3', epsilon: float) -> bool` +- #### *def* `is_approx_parallel(self, other: 'Line3', epsilon: float = 1e-06)` - 判断两条直线是否近似平行。 -Args: +判断两条直线是否近似平行。 - other: 另一条直线 +参数: - epsilon: 误差 +other: 另一条直线 -Returns: +epsilon: 误差 - 是否近似平行 +- #
-源代码 +源码 ```python def is_approx_parallel(self, other: 'Line3', epsilon: float=1e-06) -> bool: @@ -299,20 +276,19 @@ def is_approx_parallel(self, other: 'Line3', epsilon: float=1e-06) -> bool: ```
-###   ***def*** `is_parallel(self, other: 'Line3') -> bool` +- #### *def* `is_parallel(self, other: 'Line3')` - 判断两条直线是否平行。 -Args: +判断两条直线是否平行。 - other: 另一条直线 +参数: -Returns: +other: 另一条直线 - 是否平行 +- #
-源代码 +源码 ```python def is_parallel(self, other: 'Line3') -> bool: @@ -327,20 +303,19 @@ def is_parallel(self, other: 'Line3') -> bool: ```
-###   ***def*** `is_collinear(self, other: 'Line3') -> bool` +- #### *def* `is_collinear(self, other: 'Line3')` - 判断两条直线是否共线。 -Args: +判断两条直线是否共线。 - other: 另一条直线 +参数: -Returns: +other: 另一条直线 - 是否共线 +- #
-源代码 +源码 ```python def is_collinear(self, other: 'Line3') -> bool: @@ -355,20 +330,19 @@ def is_collinear(self, other: 'Line3') -> bool: ```
-###   ***def*** `is_point_on(self, point: 'Point3') -> bool` +- #### *def* `is_point_on(self, point: 'Point3')` - 判断点是否在直线上。 -Args: +判断点是否在直线上。 - point: 点 +参数: -Returns: +point: 点 - 是否在直线上 +- #
-源代码 +源码 ```python def is_point_on(self, point: 'Point3') -> bool: @@ -383,22 +357,20 @@ def is_point_on(self, point: 'Point3') -> bool: ```
-###   ***def*** `is_coplanar(self, other: 'Line3') -> bool` +- #### *def* `is_coplanar(self, other: 'Line3')` - 判断两条直线是否共面。 +判断两条直线是否共面。 充要条件:两直线方向向量的叉乘与两直线上任意一点的向量的点积为0。 -Args: +参数: - other: 另一条直线 +other: 另一条直线 -Returns: - - 是否共面 +- #
-源代码 +源码 ```python def is_coplanar(self, other: 'Line3') -> bool: @@ -414,18 +386,18 @@ def is_coplanar(self, other: 'Line3') -> bool: ```
-###   ***def*** `simplify(self) -> None` +- #### *def* `simplify(self)` - 简化直线方程,等价相等。 +简化直线方程,等价相等。 自体简化,不返回值。 - - 按照可行性一次对x y z 化 0 处理,并对向量单位化 + +- #
-源代码 +源码 ```python def simplify(self): @@ -445,23 +417,22 @@ def simplify(self): ```
-###   ***@classmethod*** -###   ***def*** `from_two_points(cls: Any, p1: 'Point3', p2: 'Point3') -> 'Line3'` +- #### `@classmethod` +- #### *def* `from_two_points(cls, p1: 'Point3', p2: 'Point3')` - 工厂函数 由两点构造直线。 -Args: +工厂函数 由两点构造直线。 - p1: 点1 +参数: - p2: 点2 +p1: 点1 -Returns: +p2: 点2 - 直线 +- #
-源代码 +源码 ```python @classmethod @@ -479,11 +450,107 @@ def from_two_points(cls, p1: 'Point3', p2: 'Point3') -> 'Line3': ```
-### ***var*** `direction = p2 - p1` +- #### *def* `__and__(self, other: 'Line3')` + + +计算两条直线点集合的交集。重合线返回自身,平行线返回None,交线返回交点。 + +参数: + +other: 另一条直线 + + +- # +
+源码 + +```python +def __and__(self, other: 'Line3') -> 'Line3 | Point3 | None': + """ + 计算两条直线点集合的交集。重合线返回自身,平行线返回None,交线返回交点。 + Args: + other: 另一条直线 + Returns: + 交点 + """ + if self.is_collinear(other): + return self + elif self.is_parallel(other) or not self.is_coplanar(other): + return None + else: + return self.cal_intersection(other) +``` +
+ +- #### *def* `__eq__(self, other)` + + +判断两条直线是否等价。 + +v1 // v2 ∧ (p1 - p2) // v1 + +参数: + +other: + + +- # +
+源码 + +```python +def __eq__(self, other) -> bool: + """ + 判断两条直线是否等价。 + + v1 // v2 ∧ (p1 - p2) // v1 + Args: + other: + + Returns: + + """ + return self.direction.is_parallel(other.direction) and (self.point - other.point).is_parallel(self.direction) +``` +
+ +- #### *def* `__str__(self)` -### ***var*** `s = 'Line3: '` +- # +
+源码 + +```python +def __str__(self): + """ + 返回点向式(x-x0) + Returns: + + """ + s = 'Line3: ' + if self.direction.x != 0: + s += f'(x{sign_format(-self.point.x)})/{self.direction.x}' + if self.direction.y != 0: + s += f' = (y{sign_format(-self.point.y)})/{self.direction.y}' + if self.direction.z != 0: + s += f' = (z{sign_format(-self.point.z)})/{self.direction.z}' + return s +``` +
+ +- #### *def* `__repr__(self)` + +- # +
+源码 + +```python +def __repr__(self): + return f'Line3({self.point}, {self.direction})' +``` +
diff --git a/docs/api/mp_math/mp_math_typing.md b/docs/api/mp_math/mp_math_typing.md index b1bb7d5..b166130 100644 --- a/docs/api/mp_math/mp_math_typing.md +++ b/docs/api/mp_math/mp_math_typing.md @@ -1,15 +1,37 @@ --- -title: mbcp.mp\nmath.mp\nmath\ntyping -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.mp_math_typing --- +### ***var*** `RealNumber: TypeAlias = int | float` + +### ***var*** `Number: TypeAlias = RealNumber | complex` ### ***var*** `SingleVar = TypeVar('SingleVar', bound=Number)` - - ### ***var*** `ArrayVar = TypeVar('ArrayVar', bound=Iterable[Number])` +### ***var*** `Var: TypeAlias = SingleVar | ArrayVar` +### ***var*** `OneSingleVarFunc: TypeAlias = Callable[[SingleVar], SingleVar]` + +### ***var*** `OneArrayFunc: TypeAlias = Callable[[ArrayVar], ArrayVar]` + +### ***var*** `OneVarFunc: TypeAlias = OneSingleVarFunc | OneArrayFunc` + +### ***var*** `TwoSingleVarsFunc: TypeAlias = Callable[[SingleVar, SingleVar], SingleVar]` + +### ***var*** `TwoArraysFunc: TypeAlias = Callable[[ArrayVar, ArrayVar], ArrayVar]` + +### ***var*** `TwoVarsFunc: TypeAlias = TwoSingleVarsFunc | TwoArraysFunc` + +### ***var*** `ThreeSingleVarsFunc: TypeAlias = Callable[[SingleVar, SingleVar, SingleVar], SingleVar]` + +### ***var*** `ThreeArraysFunc: TypeAlias = Callable[[ArrayVar, ArrayVar, ArrayVar], ArrayVar]` + +### ***var*** `ThreeVarsFunc: TypeAlias = ThreeSingleVarsFunc | ThreeArraysFunc` + +### ***var*** `MultiSingleVarsFunc: TypeAlias = Callable[..., SingleVar]` + +### ***var*** `MultiArraysFunc: TypeAlias = Callable[..., ArrayVar]` + +### ***var*** `MultiVarsFunc: TypeAlias = MultiSingleVarsFunc | MultiArraysFunc` diff --git a/docs/api/mp_math/plane.md b/docs/api/mp_math/plane.md index 3832cd3..9a598b7 100644 --- a/docs/api/mp_math/plane.md +++ b/docs/api/mp_math/plane.md @@ -1,30 +1,47 @@ --- -title: mbcp.mp\nmath.plane -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.plane --- +### ***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])` ### ***class*** `Plane3` +- #### *def* `__init__(self, a: float, b: float, c: float, d: float)` -###   ***def*** `__init__(self, a: float, b: float, c: float, d: float) -> None` +平面方程:ax + by + cz + d = 0 - 平面方程:ax + by + cz + d = 0 +参数: -Args: +a: - a: +b: - b: +c: - c: +d: - d: +- #
-源代码 +源码 ```python def __init__(self, a: float, b: float, c: float, d: float): @@ -43,32 +60,26 @@ def __init__(self, a: float, b: float, c: float, d: float): ```
-###   ***def*** `approx(self, other: 'Plane3', epsilon: float) -> bool` - - 判断两个平面是否近似相等。 - -Args: - - other: - - epsilon: +- #### *def* `approx(self, other: 'Plane3')` +判断两个平面是否近似相等。 -Returns: +参数: - 是否近似相等 +other: + +- #
-源代码 +源码 ```python -def approx(self, other: 'Plane3', epsilon: float=APPROX) -> bool: +def approx(self, other: 'Plane3') -> bool: """ 判断两个平面是否近似相等。 Args: other: - epsilon: Returns: 是否近似相等 @@ -87,24 +98,19 @@ def approx(self, other: 'Plane3', epsilon: float=APPROX) -> bool: ```
-###   ***def*** `cal_angle(self, other: 'Line3 | Plane3') -> 'AnyAngle'` +- #### *def* `cal_angle(self, other: 'Line3 | Plane3')` - 计算平面与平面之间的夹角。 -Args: +计算平面与平面之间的夹角。 - other: 另一个平面 +参数: -Returns: +other: 另一个平面 - 夹角弧度 - -Raises: - - TypeError: 不支持的类型 +- #
-源代码 +源码 ```python def cal_angle(self, other: 'Line3 | Plane3') -> 'AnyAngle': @@ -126,24 +132,19 @@ def cal_angle(self, other: 'Line3 | Plane3') -> 'AnyAngle': ```
-###   ***def*** `cal_distance(self, other: 'Plane3 | Point3') -> float` +- #### *def* `cal_distance(self, other: 'Plane3 | Point3')` - 计算平面与平面或点之间的距离。 -Args: +计算平面与平面或点之间的距离。 - other: 另一个平面或点 +参数: -Returns: +other: 另一个平面或点 - 距离 - -Raises: - - TypeError: 不支持的类型 +- #
-源代码 +源码 ```python def cal_distance(self, other: 'Plane3 | Point3') -> float: @@ -165,22 +166,19 @@ def cal_distance(self, other: 'Plane3 | Point3') -> float: ```
-###   ***def*** `cal_intersection_line3(self, other: 'Plane3') -> 'Line3'` +- #### *def* `cal_intersection_line3(self, other: 'Plane3')` - 计算两平面的交线。该方法有问题,待修复。 -Args: +计算两平面的交线。该方法有问题,待修复。 - other: 另一个平面 +参数: -Returns: +other: 另一个平面 - 交线 - -Raises: +- #
-源代码 +源码 ```python def cal_intersection_line3(self, other: 'Plane3') -> 'Line3': @@ -212,24 +210,19 @@ def cal_intersection_line3(self, other: 'Plane3') -> 'Line3': ```
-###   ***def*** `cal_intersection_point3(self, other: 'Line3') -> 'Point3'` +- #### *def* `cal_intersection_point3(self, other: 'Line3')` - 计算平面与直线的交点。 -Args: +计算平面与直线的交点。 - other: 不与平面平行或在平面上的直线 +参数: -Returns: +other: 不与平面平行或在平面上的直线 - 交点 - -Raises: - - ValueError: 平面与直线平行或重合 +- #
-源代码 +源码 ```python def cal_intersection_point3(self, other: 'Line3') -> 'Point3': @@ -250,20 +243,19 @@ def cal_intersection_point3(self, other: 'Line3') -> 'Point3': ```
-###   ***def*** `cal_parallel_plane3(self, point: 'Point3') -> 'Plane3'` +- #### *def* `cal_parallel_plane3(self, point: 'Point3')` - 计算平行于该平面且过指定点的平面。 -Args: +计算平行于该平面且过指定点的平面。 - point: 指定点 +参数: -Returns: +point: 指定点 - 平面 +- #
-源代码 +源码 ```python def cal_parallel_plane3(self, point: 'Point3') -> 'Plane3': @@ -278,20 +270,19 @@ def cal_parallel_plane3(self, point: 'Point3') -> 'Plane3': ```
-###   ***def*** `is_parallel(self, other: 'Plane3') -> bool` +- #### *def* `is_parallel(self, other: 'Plane3')` - 判断两个平面是否平行。 -Args: +判断两个平面是否平行。 - other: 另一个平面 +参数: -Returns: +other: 另一个平面 - 是否平行 +- #
-源代码 +源码 ```python def is_parallel(self, other: 'Plane3') -> bool: @@ -306,17 +297,20 @@ def is_parallel(self, other: 'Plane3') -> bool: ```
-###   ***@property*** -###   ***def*** `normal(self: Any) -> 'Vector3'` +- #### `@property` +- #### *def* `normal(self)` - 平面的法向量。 -Returns: +平面的法向量。 - 法向量 +返回: +法向量 + + +- #
-源代码 +源码 ```python @property @@ -330,23 +324,22 @@ def normal(self) -> 'Vector3': ```
-###   ***@classmethod*** -###   ***def*** `from_point_and_normal(cls: Any, point: 'Point3', normal: 'Vector3') -> 'Plane3'` +- #### `@classmethod` +- #### *def* `from_point_and_normal(cls, point: 'Point3', normal: 'Vector3')` - 工厂函数 由点和法向量构造平面(点法式构造)。 -Args: +工厂函数 由点和法向量构造平面(点法式构造)。 - point: 平面上的一点 +参数: - normal: 法向量 +point: 平面上的一点 -Returns: +normal: 法向量 - 平面 +- #
-源代码 +源码 ```python @classmethod @@ -365,25 +358,24 @@ def from_point_and_normal(cls, point: 'Point3', normal: 'Vector3') -> 'Plane3': ```
-###   ***@classmethod*** -###   ***def*** `from_three_points(cls: Any, p1: 'Point3', p2: 'Point3', p3: 'Point3') -> 'Plane3'` +- #### `@classmethod` +- #### *def* `from_three_points(cls, p1: 'Point3', p2: 'Point3', p3: 'Point3')` - 工厂函数 由三点构造平面。 -Args: +工厂函数 由三点构造平面。 - p1: 点1 +参数: - p2: 点2 +p1: 点1 - p3: 点3 +p2: 点2 -Returns: +p3: 点3 - 平面 +- #
-源代码 +源码 ```python @classmethod @@ -404,23 +396,22 @@ def from_three_points(cls, p1: 'Point3', p2: 'Point3', p3: 'Point3') -> 'Plane3' ```
-###   ***@classmethod*** -###   ***def*** `from_two_lines(cls: Any, l1: 'Line3', l2: 'Line3') -> 'Plane3'` +- #### `@classmethod` +- #### *def* `from_two_lines(cls, l1: 'Line3', l2: 'Line3')` - 工厂函数 由两直线构造平面。 -Args: +工厂函数 由两直线构造平面。 - l1: 直线1 +参数: - l2: 直线2 +l1: 直线1 -Returns: +l2: 直线2 - 平面 +- #
-源代码 +源码 ```python @classmethod @@ -441,23 +432,22 @@ def from_two_lines(cls, l1: 'Line3', l2: 'Line3') -> 'Plane3': ```
-###   ***@classmethod*** -###   ***def*** `from_point_and_line(cls: Any, point: 'Point3', line: 'Line3') -> 'Plane3'` +- #### `@classmethod` +- #### *def* `from_point_and_line(cls, point: 'Point3', line: 'Line3')` - 工厂函数 由点和直线构造平面。 -Args: +工厂函数 由点和直线构造平面。 - point: 面上一点 +参数: - line: 面上直线,不包含点 +point: 面上一点 -Returns: +line: 面上直线,不包含点 - 平面 +- #
-源代码 +源码 ```python @classmethod @@ -474,79 +464,124 @@ def from_point_and_line(cls, point: 'Point3', line: 'Line3') -> 'Plane3': ```
-### ***var*** `direction = self.normal.cross(other.normal)` +- #### *def* `__repr__(self)` + +- # +
+源码 + +```python +def __repr__(self): + return f'Plane3({self.a}, {self.b}, {self.c}, {self.d})' +``` +
+ +- #### *def* `__str__(self)` + +- # +
+源码 + +```python +def __str__(self): + s = 'Plane3: ' + if self.a != 0: + s += f'{sign(self.a, only_neg=True)}{abs(self.a)}x' + if self.b != 0: + s += f' {sign(self.b)} {abs(self.b)}y' + if self.c != 0: + s += f' {sign(self.c)} {abs(self.c)}z' + if self.d != 0: + s += f' {sign(self.d)} {abs(self.d)}' + return s + ' = 0' +``` +
+ +- #### `@overload` +- #### *def* `__and__(self, other: 'Line3')` + +- # +
+源码 + +```python +@overload +def __and__(self, other: 'Line3') -> 'Point3 | None': + ... +``` +
+ +- #### `@overload` +- #### *def* `__and__(self, other: 'Plane3')` + +- # +
+源码 + +```python +@overload +def __and__(self, other: 'Plane3') -> 'Line3 | None': + ... +``` +
+ +- #### *def* `__and__(self, other)` +取两平面的交集(人话:交线) -### ***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)` +参数: + +other: +- # +
+源码 -### ***var*** `d = -a * point.x - b * point.y - c * point.z` +```python +def __and__(self, other): + """ + 取两平面的交集(人话:交线) + Args: + other: + Returns: + 不平行平面的交线,平面平行返回None + """ + if isinstance(other, Plane3): + if self.normal.is_parallel(other.normal): + return None + return self.cal_intersection_line3(other) + elif isinstance(other, Line3): + if self.normal @ other.direction == 0: + return None + return self.cal_intersection_point3(other) + else: + raise TypeError(f"unsupported operand type(s) for &: 'Plane3' and '{type(other)}'") +``` +
+- #### *def* `__eq__(self, other)` +- # +
+源码 -### ***var*** `v1 = p2 - p1` +```python +def __eq__(self, other) -> bool: + return self.approx(other) +``` +
+- #### *def* `__rand__(self, other: 'Line3')` +- # +
+源码 -### ***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])` - - +```python +def __rand__(self, other: 'Line3') -> 'Point3': + return self.cal_intersection_point3(other) +``` +
diff --git a/docs/api/mp_math/point.md b/docs/api/mp_math/point.md index 53e79f7..f040766 100644 --- a/docs/api/mp_math/point.md +++ b/docs/api/mp_math/point.md @@ -1,28 +1,25 @@ --- -title: mbcp.mp\nmath.point -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.point --- - ### ***class*** `Point3` +- #### *def* `__init__(self, x: float, y: float, z: float)` -###   ***def*** `__init__(self, x: float, y: float, z: float) -> None` +笛卡尔坐标系中的点。 - 笛卡尔坐标系中的点。 +参数: -Args: +x: x 坐标 - x: x 坐标 +y: y 坐标 - y: y 坐标 +z: z 坐标 - z: z 坐标 +- #
-源代码 +源码 ```python def __init__(self, x: float, y: float, z: float): @@ -39,24 +36,21 @@ def __init__(self, x: float, y: float, z: float): ```
-###   ***def*** `approx(self, other: 'Point3', epsilon: float) -> bool` - - 判断两个点是否近似相等。 - -Args: - - other: - - epsilon: +- #### *def* `approx(self, other: 'Point3', epsilon: float = APPROX)` +判断两个点是否近似相等。 -Returns: +参数: - 是否近似相等 +other: +epsilon: + + +- #
-源代码 +源码 ```python def approx(self, other: 'Point3', epsilon: float=APPROX) -> bool: @@ -73,3 +67,97 @@ def approx(self, other: 'Point3', epsilon: float=APPROX) -> bool: ```
+- #### *def* `__str__(self)` + +- # +
+源码 + +```python +def __str__(self): + return f'Point3({self.x}, {self.y}, {self.z})' +``` +
+ +- #### `@overload` +- #### *def* `__add__(self, other: 'Vector3')` + +- # +
+源码 + +```python +@overload +def __add__(self, other: 'Vector3') -> 'Point3': + ... +``` +
+ +- #### `@overload` +- #### *def* `__add__(self, other: 'Point3')` + +- # +
+源码 + +```python +@overload +def __add__(self, other: 'Point3') -> 'Point3': + ... +``` +
+ +- #### *def* `__add__(self, other)` + + +P + V -> P +P + P -> P + +参数: + +other: + + +- # +
+源码 + +```python +def __add__(self, other): + """ + P + V -> P + P + P -> P + Args: + other: + Returns: + """ + return Point3(self.x + other.x, self.y + other.y, self.z + other.z) +``` +
+ +- #### *def* `__eq__(self, other)` + + +判断两个点是否相等。 + +参数: + +other: + + +- # +
+源码 + +```python +def __eq__(self, other): + """ + 判断两个点是否相等。 + Args: + other: + Returns: + """ + return approx(self.x, other.x) and approx(self.y, other.y) and approx(self.z, other.z) +``` +
+ diff --git a/docs/api/mp_math/segment.md b/docs/api/mp_math/segment.md index 7da8df6..13a897a 100644 --- a/docs/api/mp_math/segment.md +++ b/docs/api/mp_math/segment.md @@ -1,24 +1,19 @@ --- -title: mbcp.mp\nmath.segment -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.segment --- - ### ***class*** `Segment3` +- #### *def* `__init__(self, p1: 'Point3', p2: 'Point3')` -###   ***def*** `__init__(self, p1: 'Point3', p2: 'Point3') -> None` - - 三维空间中的线段。 - +三维空间中的线段。 :param p1: - :param p2: + +- #
-源代码 +源码 ```python def __init__(self, p1: 'Point3', p2: 'Point3'): @@ -38,3 +33,27 @@ def __init__(self, p1: 'Point3', p2: 'Point3'): ```
+- #### *def* `__repr__(self)` + +- # +
+源码 + +```python +def __repr__(self): + return f'Segment3({self.p1}, {self.p2})' +``` +
+ +- #### *def* `__str__(self)` + +- # +
+源码 + +```python +def __str__(self): + return f'Segment3({self.p1} -> {self.p2})' +``` +
+ diff --git a/docs/api/mp_math/utils.md b/docs/api/mp_math/utils.md index 93cc597..9406a53 100644 --- a/docs/api/mp_math/utils.md +++ b/docs/api/mp_math/utils.md @@ -1,30 +1,23 @@ --- -title: mbcp.mp\nmath.utils -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.utils --- +### *def* `clamp()` -### ***def*** `clamp(x: float, min_: float, max_: float) -> float` 区间截断函数。 -Args: +参数: - x: +x: - min_: +min_: - max_: +max_: -Returns: - - 限制后的值 -
-源代码 +源码 ```python def clamp(x: float, min_: float, max_: float) -> float: @@ -42,26 +35,23 @@ def clamp(x: float, min_: float, max_: float) -> float: ```
-### ***def*** `approx(x: float, y: float, epsilon: float) -> bool` +### *def* `approx(x: float = 0.0, y: float = APPROX)` + 判断两个数是否近似相等。或包装一个实数,用于判断是否近似于0。 -Args: +参数: - x: +x: - y: +y: - epsilon: +epsilon: -Returns: - - 是否近似相等 -
-源代码 +源码 ```python def approx(x: float, y: float=0.0, epsilon: float=APPROX) -> bool: @@ -79,22 +69,21 @@ def approx(x: float, y: float=0.0, epsilon: float=APPROX) -> bool: ```
-### ***def*** `sign(x: float, only_neg: bool) -> str` +### *def* `sign(x: float = False)` + 获取数的符号。 -Args: +参数: - x: 数 +x: 数 - only_neg: 是否只返回负数的符号 +only_neg: 是否只返回负数的符号 -Returns: - 符号 + - ""
-源代码 +源码 ```python def sign(x: float, only_neg: bool=False) -> str: @@ -114,28 +103,24 @@ def sign(x: float, only_neg: bool=False) -> str: ```
-### ***def*** `sign_format(x: float, only_neg: bool) -> str` +### *def* `sign_format(x: float = False)` + 格式化符号数 - -1 -> -1 - 1 -> +1 - 0 -> "" -Args: +参数: - x: 数 +x: 数 - only_neg: 是否只返回负数的符号 +only_neg: 是否只返回负数的符号 -Returns: - 符号 + - ""
-源代码 +源码 ```python def sign_format(x: float, only_neg: bool=False) -> str: @@ -160,18 +145,11 @@ def sign_format(x: float, only_neg: bool=False) -> str: ### ***class*** `Approx` -用于近似比较对象 - - - -已实现对象 实数 Vector3 Point3 Plane3 Line3 - -###   ***def*** `__init__(self, value: RealNumber) -> None` - -  +- #### *def* `__init__(self, value: RealNumber)` +- #
-源代码 +源码 ```python def __init__(self, value: RealNumber): @@ -179,12 +157,32 @@ def __init__(self, value: RealNumber): ```
-###   ***def*** `raise_type_error(self, other: Any) -> None` - -  +- #### *def* `__eq__(self, other)` +- #
-源代码 +源码 + +```python +def __eq__(self, other): + if isinstance(self.value, (float, int)): + if isinstance(other, (float, int)): + return abs(self.value - other) < APPROX + else: + self.raise_type_error(other) + elif isinstance(self.value, Vector3): + if isinstance(other, (Vector3, Point3, Plane3, Line3)): + return all([approx(self.value.x, other.x), approx(self.value.y, other.y), approx(self.value.z, other.z)]) + else: + self.raise_type_error(other) +``` +
+ +- #### *def* `raise_type_error(self, other)` + +- # +
+源码 ```python def raise_type_error(self, other): @@ -192,3 +190,15 @@ def raise_type_error(self, other): ```
+- #### *def* `__ne__(self, other)` + +- # +
+源码 + +```python +def __ne__(self, other): + return not self.__eq__(other) +``` +
+ diff --git a/docs/api/mp_math/vector.md b/docs/api/mp_math/vector.md index 73d7f22..988ae57 100644 --- a/docs/api/mp_math/vector.md +++ b/docs/api/mp_math/vector.md @@ -1,28 +1,33 @@ --- -title: mbcp.mp\nmath.vector -order: 1 -icon: laptop-code -category: API +title: mbcp.mp_math.vector --- +### ***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)` ### ***class*** `Vector3` +- #### *def* `__init__(self, x: float, y: float, z: float)` -###   ***def*** `__init__(self, x: float, y: float, z: float) -> None` +3维向量 - 3维向量 +参数: -Args: +x: x轴分量 - x: x轴分量 +y: y轴分量 - y: y轴分量 +z: z轴分量 - z: z轴分量 +- #
-源代码 +源码 ```python def __init__(self, x: float, y: float, z: float): @@ -39,24 +44,21 @@ def __init__(self, x: float, y: float, z: float): ```
-###   ***def*** `approx(self, other: 'Vector3', epsilon: float) -> bool` - - 判断两个向量是否近似相等。 - -Args: - - other: - - epsilon: +- #### *def* `approx(self, other: 'Vector3', epsilon: float = APPROX)` +判断两个向量是否近似相等。 -Returns: +参数: - 是否近似相等 +other: +epsilon: + + +- #
-源代码 +源码 ```python def approx(self, other: 'Vector3', epsilon: float=APPROX) -> bool: @@ -73,20 +75,19 @@ def approx(self, other: 'Vector3', epsilon: float=APPROX) -> bool: ```
-###   ***def*** `cal_angle(self, other: 'Vector3') -> 'AnyAngle'` +- #### *def* `cal_angle(self, other: 'Vector3')` - 计算两个向量之间的夹角。 -Args: +计算两个向量之间的夹角。 - other: 另一个向量 +参数: -Returns: +other: 另一个向量 - 夹角 +- #
-源代码 +源码 ```python def cal_angle(self, other: 'Vector3') -> 'AnyAngle': @@ -101,44 +102,19 @@ def cal_angle(self, other: 'Vector3') -> 'AnyAngle': ```
-###   ***def*** `cross(self, other: 'Vector3') -> 'Vector3'` - - 向量积 叉乘:v1 cross v2 -> v3 +- #### *def* `cross(self, other: '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': @@ -165,22 +141,21 @@ def cross(self, other: 'Vector3') -> 'Vector3': ```
-###   ***def*** `is_approx_parallel(self, other: 'Vector3', epsilon: float) -> bool` +- #### *def* `is_approx_parallel(self, other: 'Vector3', epsilon: float = APPROX)` - 判断两个向量是否近似平行。 -Args: +判断两个向量是否近似平行。 - other: 另一个向量 +参数: - epsilon: 允许的误差 +other: 另一个向量 -Returns: +epsilon: 允许的误差 - 是否近似平行 +- #
-源代码 +源码 ```python def is_approx_parallel(self, other: 'Vector3', epsilon: float=APPROX) -> bool: @@ -196,20 +171,19 @@ def is_approx_parallel(self, other: 'Vector3', epsilon: float=APPROX) -> bool: ```
-###   ***def*** `is_parallel(self, other: 'Vector3') -> bool` +- #### *def* `is_parallel(self, other: 'Vector3')` - 判断两个向量是否平行。 -Args: +判断两个向量是否平行。 - other: 另一个向量 +参数: -Returns: +other: 另一个向量 - 是否平行 +- #
-源代码 +源码 ```python def is_parallel(self, other: 'Vector3') -> bool: @@ -224,16 +198,17 @@ def is_parallel(self, other: 'Vector3') -> bool: ```
-###   ***def*** `normalize(self) -> None` - - 将向量归一化。 +- #### *def* `normalize(self)` +将向量归一化。 自体归一化,不返回值。 + +- #
-源代码 +源码 ```python def normalize(self): @@ -249,15 +224,16 @@ def normalize(self): ```
-###   ***@property*** -###   ***def*** `np_array(self: Any) -> 'np.ndarray'` +- #### `@property` +- #### *def* `np_array(self)` - 返回numpy数组 -Returns: + + +- #
-源代码 +源码 ```python @property @@ -270,17 +246,20 @@ def np_array(self) -> 'np.ndarray': ```
-###   ***@property*** -###   ***def*** `length(self: Any) -> float` +- #### `@property` +- #### *def* `length(self)` - 向量的模。 -Returns: +向量的模。 - 模 +返回: +模 + + +- #
-源代码 +源码 ```python @property @@ -294,17 +273,20 @@ def length(self) -> float: ```
-###   ***@property*** -###   ***def*** `unit(self: Any) -> 'Vector3'` +- #### `@property` +- #### *def* `unit(self)` - 获取该向量的单位向量。 -Returns: +获取该向量的单位向量。 - 单位向量 +返回: +单位向量 + + +- #
-源代码 +源码 ```python @property @@ -318,23 +300,372 @@ def unit(self) -> 'Vector3': ```
-### ***var*** `zero_vector3 = Vector3(0, 0, 0)` +- #### *def* `__abs__(self)` + +- # +
+源码 + +```python +def __abs__(self): + return self.length +``` +
+ +- #### `@overload` +- #### *def* `__add__(self, other: 'Vector3')` + +- # +
+源码 + +```python +@overload +def __add__(self, other: 'Vector3') -> 'Vector3': + ... +``` +
+ +- #### `@overload` +- #### *def* `__add__(self, other: 'Point3')` + +- # +
+源码 + +```python +@overload +def __add__(self, other: 'Point3') -> 'Point3': + ... +``` +
+ +- #### *def* `__add__(self, other)` +V + P -> P -### ***var*** `x_axis = Vector3(1, 0, 0)` +V + V -> V + +参数: + +other: +- # +
+源码 -### ***var*** `y_axis = Vector3(0, 1, 0)` +```python +def __add__(self, other): + """ + V + P -> P + + V + V -> V + Args: + other: + Returns: + + """ + if isinstance(other, Vector3): + return Vector3(self.x + other.x, self.y + other.y, self.z + other.z) + elif isinstance(other, Point3): + return Point3(self.x + other.x, self.y + other.y, self.z + other.z) + else: + raise TypeError(f"unsupported operand type(s) for +: 'Vector3' and '{type(other)}'") +``` +
+ +- #### *def* `__eq__(self, other)` +判断两个向量是否相等。 -### ***var*** `z_axis = Vector3(0, 0, 1)` +参数: + +other: +- # +
+源码 -### ***var*** `length = self.length` +```python +def __eq__(self, other): + """ + 判断两个向量是否相等。 + Args: + other: + Returns: + 是否相等 + """ + return approx(self.x, other.x) and approx(self.y, other.y) and approx(self.z, other.z) +``` +
+ +- #### *def* `__radd__(self, other: 'Point3')` +P + V -> P + +别去点那边实现了。 +:param other: +:return: + + +- # +
+源码 + +```python +def __radd__(self, other: 'Point3') -> 'Point3': + """ + P + V -> P + + 别去点那边实现了。 + :param other: + :return: + """ + return Point3(self.x + other.x, self.y + other.y, self.z + other.z) +``` +
+ +- #### `@overload` +- #### *def* `__sub__(self, other: 'Vector3')` + +- # +
+源码 + +```python +@overload +def __sub__(self, other: 'Vector3') -> 'Vector3': + ... +``` +
+ +- #### `@overload` +- #### *def* `__sub__(self, other: 'Point3')` + +- # +
+源码 + +```python +@overload +def __sub__(self, other: 'Point3') -> 'Point3': + ... +``` +
+ +- #### *def* `__sub__(self, other)` + + +V - P -> P + +V - V -> V + +参数: + +other: + + +- # +
+源码 + +```python +def __sub__(self, other): + """ + V - P -> P + + V - V -> V + Args: + other: + Returns: + """ + if isinstance(other, Vector3): + return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) + elif isinstance(other, Point3): + return Point3(self.x - other.x, self.y - other.y, self.z - other.z) + else: + raise TypeError(f'unsupported operand type(s) for -: "Vector3" and "{type(other)}"') +``` +
+ +- #### *def* `__rsub__(self, other: 'Point3')` + + +P - V -> P + +参数: + +other: + + +- # +
+源码 + +```python +def __rsub__(self, other: 'Point3'): + """ + P - V -> P + Args: + other: + Returns: + + """ + if isinstance(other, Point3): + return Point3(other.x - self.x, other.y - self.y, other.z - self.z) + else: + raise TypeError(f"unsupported operand type(s) for -: '{type(other)}' and 'Vector3'") +``` +
+ +- #### `@overload` +- #### *def* `__mul__(self, other: 'Vector3')` + +- # +
+源码 + +```python +@overload +def __mul__(self, other: 'Vector3') -> 'Vector3': + ... +``` +
+ +- #### `@overload` +- #### *def* `__mul__(self, other: RealNumber)` + +- # +
+源码 + +```python +@overload +def __mul__(self, other: RealNumber) -> 'Vector3': + ... +``` +
+ +- #### *def* `__mul__(self, other: 'int | float | Vector3')` + + +数组运算 非点乘。点乘使用@,叉乘使用cross。 + +参数: + +other: + + +- # +
+源码 + +```python +def __mul__(self, other: 'int | float | Vector3') -> 'Vector3': + """ + 数组运算 非点乘。点乘使用@,叉乘使用cross。 + Args: + other: + + Returns: + """ + if isinstance(other, Vector3): + return Vector3(self.x * other.x, self.y * other.y, self.z * other.z) + elif isinstance(other, (float, int)): + return Vector3(self.x * other, self.y * other, self.z * other) + else: + raise TypeError(f"unsupported operand type(s) for *: 'Vector3' and '{type(other)}'") +``` +
+ +- #### *def* `__rmul__(self, other: 'RealNumber')` + +- # +
+源码 + +```python +def __rmul__(self, other: 'RealNumber') -> 'Vector3': + return self.__mul__(other) +``` +
+ +- #### *def* `__matmul__(self, other: 'Vector3')` + + +点乘。 + +参数: + +other: + + +- # +
+源码 + +```python +def __matmul__(self, other: 'Vector3') -> 'RealNumber': + """ + 点乘。 + Args: + other: + Returns: + """ + return self.x * other.x + self.y * other.y + self.z * other.z +``` +
+ +- #### *def* `__truediv__(self, other: RealNumber)` + +- # +
+源码 + +```python +def __truediv__(self, other: RealNumber) -> 'Vector3': + return Vector3(self.x / other, self.y / other, self.z / other) +``` +
+ +- #### *def* `__neg__(self)` + +- # +
+源码 + +```python +def __neg__(self): + return Vector3(-self.x, -self.y, -self.z) +``` +
+ +- #### *def* `__repr__(self)` + +- # +
+源码 + +```python +def __repr__(self): + return f'Vector3({self.x}, {self.y}, {self.z})' +``` +
+ +- #### *def* `__str__(self)` + +- # +
+源码 + +```python +def __str__(self): + return f'Vector3({self.x}, {self.y}, {self.z})' +``` +
diff --git a/docs/api/particle/index.md b/docs/api/particle/index.md index 1669e90..4b514d5 100644 --- a/docs/api/particle/index.md +++ b/docs/api/particle/index.md @@ -1,7 +1,3 @@ --- -title: mbcp.particle.\n\ninit\n\n -order: 1 -icon: laptop-code -category: API +title: mbcp.particle --- - diff --git a/docs/api/presets/index.md b/docs/api/presets/index.md index 85b4964..472366f 100644 --- a/docs/api/presets/index.md +++ b/docs/api/presets/index.md @@ -1,7 +1,3 @@ --- -title: mbcp.presets.\n\ninit\n\n -order: 1 -icon: laptop-code -category: API +title: mbcp.presets --- - diff --git a/docs/api/presets/model/index.md b/docs/api/presets/model/index.md index 5986544..fee2a50 100644 --- a/docs/api/presets/model/index.md +++ b/docs/api/presets/model/index.md @@ -1,26 +1,24 @@ --- -title: mbcp.presets.model.\n\ninit\n\n -order: 1 -icon: laptop-code -category: API +title: mbcp.presets.model --- +### ***class*** `GeometricModels` + +- #### `@staticmethod` +- #### *def* `sphere(radius: float, density: float)` -### ***def*** `sphere(radius: float, density: float) -> None` 生成球体上的点集。 -Args: +参数: - radius: +radius: - density: +density: -Returns: - - List[Point3]: 球体上的点集。 +- #
-源代码 +源码 ```python @staticmethod @@ -44,75 +42,3 @@ def sphere(radius: float, density: float): ```
-### ***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/package.json b/docs/package.json index 5ecc3a5..ae45d0a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,7 @@ { "devDependencies": { - "vitepress": "^1.3.4" + "vitepress": "^1.3.4", + "vitepress-auto-sidebar-plugin": "^0.2.4" }, "scripts": { "docs:dev": "vitepress dev", diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 841c47c..f876605 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -8,6 +8,9 @@ devDependencies: vitepress: specifier: ^1.3.4 version: 1.3.4(@algolia/client-search@5.1.1)(search-insights@2.17.0) + vitepress-auto-sidebar-plugin: + specifier: ^0.2.4 + version: 0.2.4(vite@5.4.2)(vitepress@1.3.4) packages: @@ -470,6 +473,27 @@ packages: resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} dev: true + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + dev: true + /@rollup/rollup-android-arm-eabi@4.21.1: resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==} cpu: [arm] @@ -856,10 +880,28 @@ packages: '@algolia/transporter': 4.24.0 dev: true + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + /birpc@0.2.17: resolution: {integrity: sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==} dev: true + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.1.1 + dev: true + + /consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + dev: true + /copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -907,10 +949,47 @@ packages: '@esbuild/win32-x64': 0.21.5 dev: true + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true + /extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + dependencies: + is-extendable: 0.1.1 + dev: true + + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + dev: true + + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + dependencies: + reusify: 1.0.4 + dev: true + + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + /focus-trap@7.5.4: resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} dependencies: @@ -925,15 +1004,67 @@ packages: dev: true optional: true + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + dependencies: + js-yaml: 3.14.1 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + dev: true + /hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} dev: true + /is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + /is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} engines: {node: '>=12.13'} dev: true + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: true + + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: true + /magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} dependencies: @@ -944,6 +1075,19 @@ packages: resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} dev: true + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + dev: true + /minisearch@7.1.0: resolution: {integrity: sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==} dev: true @@ -958,6 +1102,10 @@ packages: hasBin: true dev: true + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dev: true + /perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} dev: true @@ -966,6 +1114,11 @@ packages: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} dev: true + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + /postcss@8.4.41: resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} engines: {node: ^10 || ^12 || >=14} @@ -979,6 +1132,15 @@ packages: resolution: {integrity: sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==} dev: true + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + /rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} dev: true @@ -1009,10 +1171,24 @@ packages: fsevents: 2.3.3 dev: true + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + /search-insights@2.17.0: resolution: {integrity: sha512-AskayU3QNsXQzSL6v4LTYST7NNfs2HWyHHB+sdORP9chsytAhro5XRfToAMI/LAVYgNbzowVZTMfBRodgbUHKg==} dev: true + /section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + dev: true + /shiki@1.14.1: resolution: {integrity: sha512-FujAN40NEejeXdzPt+3sZ3F2dx1U24BY2XTY01+MG8mbxCiA2XukXdcbyMyLAHJ/1AUUnQd1tZlvIjefWWEJeA==} dependencies: @@ -1030,6 +1206,15 @@ packages: engines: {node: '>=0.10.0'} dev: true + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true + + /strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + dev: true + /superjson@2.2.1: resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} engines: {node: '>=16'} @@ -1046,6 +1231,13 @@ packages: engines: {node: '>=4'} dev: true + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + /vite@5.4.2: resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -1084,6 +1276,21 @@ packages: fsevents: 2.3.3 dev: true + /vitepress-auto-sidebar-plugin@0.2.4(vite@5.4.2)(vitepress@1.3.4): + resolution: {integrity: sha512-s2EcnBAi6zTDKSODTKl1dtmq/0Wl/WSVT0QWS4/6Q8j2+LIL9s4tV6swVhffrGii/zQGouIlMyQflqDFGGZJIw==} + peerDependencies: + vite: ^5.2.9 + vitepress: ^1.1.0 + dependencies: + consola: 3.2.3 + fast-glob: 3.3.2 + gray-matter: 4.0.3 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + vite: 5.4.2 + vitepress: 1.3.4(@algolia/client-search@5.1.1)(search-insights@2.17.0) + 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 diff --git a/liteyuki_autodoc/__init__.py b/liteyuki_autodoc/__init__.py new file mode 100644 index 0000000..31a9761 --- /dev/null +++ b/liteyuki_autodoc/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午12:52 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" \ No newline at end of file diff --git a/liteyuki_autodoc/__main__.py b/liteyuki_autodoc/__main__.py new file mode 100644 index 0000000..dff40b1 --- /dev/null +++ b/liteyuki_autodoc/__main__.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午4:08 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __main__.py +@Software: PyCharm +""" +# command line tool +# args[0] path +# -o|--output output path +# -l|--lang zh-Hans en jp default zh-Hans + + +import argparse +import os +import sys + +from liteyuki_autodoc.output import generate_from_module + + +def main(): + parser = argparse.ArgumentParser(description="Generate documentation from Python modules.") + parser.add_argument("path", type=str, help="Path to the Python module or package.") + parser.add_argument("-o", "--output", default="doc-output", type=str, help="Output directory.") + parser.add_argument("-c", "--contain-top", action="store_true", help="Whether to contain top-level dir in output dir.") + parser.add_argument("-l", "--lang", nargs='+', default=["zh-Hans"], type=str, help="Languages of the document.") + + args = parser.parse_args() + + if not os.path.exists(args.path): + print(f"Error: The path {args.path} does not exist.") + sys.exit(1) + + if not os.path.exists(args.output): + os.makedirs(args.output) + + langs = args.lang + for lang in langs: + generate_from_module(args.path, args.output, with_top=args.contain_top, lang=lang) + + +if __name__ == '__main__': + main() diff --git a/liteyuki_autodoc/docstring/__init__.py b/liteyuki_autodoc/docstring/__init__.py new file mode 100644 index 0000000..323b116 --- /dev/null +++ b/liteyuki_autodoc/docstring/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午1:46 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" \ No newline at end of file diff --git a/liteyuki_autodoc/docstring/docstring.py b/liteyuki_autodoc/docstring/docstring.py new file mode 100644 index 0000000..f1e9855 --- /dev/null +++ b/liteyuki_autodoc/docstring/docstring.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午1:46 +@Author : snowykami +@Email : snowykami@outlook.com +@File : docstring.py +@Software: PyCharm +""" +from typing import Optional + +from pydantic import BaseModel, Field + +from liteyuki_autodoc.i18n import get_text + + +class Attr(BaseModel): + name: str + type: str = "" + desc: str = "" + + +class Args(BaseModel): + name: str + type: str = "" + desc: str = "" + + +class Return(BaseModel): + desc: str = "" + + +class Exception_(BaseModel): + name: str + desc: str = "" + + +class Raise(BaseModel): + exceptions: list[Exception_] = [] + + +class Example(BaseModel): + desc: str = "" + input: str = "" + output: str = "" + + +class Docstring(BaseModel): + desc: str = "" + args: list[Args] = [] + attrs: list[Attr] = [] + return_: Optional[Return] = None + raise_: list[Exception_] = [] + example: list[Example] = [] + + def add_desc(self, desc: str): + if self.desc == "": + self.desc = desc + else: + self.desc += "\n" + desc + + def add_arg(self, name: str, type_: str = "", desc: str = ""): + self.args.append(Args(name=name, type=type_, desc=desc)) + + def add_attrs(self, name: str, type_: str = "", desc: str = ""): + self.attrs.append(Attr(name=name, type=type_, desc=desc)) + + def add_return(self, desc: str = ""): + self.return_ = Return(desc=desc) + + def add_raise(self, name: str, desc: str = ""): + self.raise_.append(Exception_(name=name, desc=desc)) + + def add_example(self, desc: str = "", input_: str = "", output: str = ""): + self.example.append(Example(desc=desc, input=input_, output=output)) + + def reduction(self) -> str: + """ + 通过解析结果还原docstring + Args: + + Returns: + + """ + ret = "" + ret += self.desc + "\n" + if self.args: + ret += "Args:\n" + for arg in self.args: + ret += f" {arg.name}: {arg.type}\n {arg.desc}\n" + if self.attrs: + ret += "Attributes:\n" + for attr in self.attrs: + ret += f" {attr.name}: {attr.type}\n {attr.desc}\n" + if self.return_: + ret += "Returns:\n" + ret += f" {self.return_.desc}\n" + + if self.raise_: + ret += "Raises:\n" + for exception in self.raise_: + ret += f" {exception.name}\n {exception.desc}\n" + + if self.example: + ret += "Examples:\n" + for example in self.example: + ret += f" {example.desc}\n Input: {example.input}\n Output: {example.output}\n" + + return ret + + def markdown(self, lang: str, indent: int = 4, is_classmethod: bool = False) -> str: + """ + 生成markdown文档 + Args: + is_classmethod: + lang: + indent: + + Returns: + + """ + PREFIX = "" * indent + if is_classmethod: + PREFIX = " -" + ret = "" + ret += self.desc + "\n\n" + if self.args: + ret += PREFIX + f"{get_text(lang, 'docstring.args')}:\n\n" + for arg in self.args: + ret += PREFIX + f"{arg.name}: {arg.type} {arg.desc}\n\n" + if self.attrs: + ret += PREFIX + f"{get_text(lang, 'docstring.attrs')}:\n\n" + for attr in self.attrs: + ret += PREFIX + f"{attr.name}: {attr.type} {attr.desc}\n\n" + if self.return_: + ret += PREFIX + f"{get_text(lang, 'docstring.return')}:\n\n" + ret += PREFIX + f"{self.return_.desc}\n\n" + if self.raise_: + ret += PREFIX + f"{get_text(lang, 'docstring.raises')}:\n\n" + for exception in self.raise_: + ret += PREFIX + f"{exception.name} {exception.desc}\n\n" + if self.example: + ret += PREFIX + f"{get_text(lang, 'docstring.example')}:\n\n" + for example in self.example: + ret += PREFIX + f"{example.desc}\n Input: {example.input}\n Output: {example.output}\n\n" + return ret + + def __str__(self): + return self.desc diff --git a/liteyuki_autodoc/docstring/parser.py b/liteyuki_autodoc/docstring/parser.py new file mode 100644 index 0000000..35d05c3 --- /dev/null +++ b/liteyuki_autodoc/docstring/parser.py @@ -0,0 +1,178 @@ +""" +Google docstring parser for Python. +""" +from typing import Optional + +from liteyuki_autodoc.docstring.docstring import Docstring + + +class Parser: + ... + + +class GoogleDocstringParser(Parser): + _tokens = { + "Args" : "args", + "Arguments" : "args", + "参数" : "args", + + "Return" : "return", + "Returns" : "return", + "返回" : "return", + + "Attribute" : "attribute", + "Attributes" : "attribute", + "属性" : "attribute", + + "Raises" : "raises", + "Raise" : "raises", + "引发" : "raises", + + "Example" : "example", + "Examples" : "example", + "示例" : "example", + + "Yields" : "yields", + "Yield" : "yields", + "产出" : "yields", + + "Requires" : "requires", + "Require" : "requires", + "需要" : "requires", + + "FrontMatter": "front_matter", + "前言" : "front_matter", + } + + def __init__(self, docstring: str, indent: int = 4): + self.lines = docstring.splitlines() + self.indent = indent + self.lineno = 0 # Current line number + self.char = 0 # Current character position + + self.docstring = Docstring() + + def match_token(self) -> Optional[str]: + for token in self._tokens: + if self.lines[self.lineno][self.char:].startswith(token): + self.char += len(token) + return self._tokens[token] + return None + + def parse_args(self): + """ + 依次解析后面的参数行,直到缩进小于等于当前行的缩进 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_arg(name.strip(), desc.strip()) + else: + self.docstring.add_arg(line.strip()) + + def parse_return(self): + """ + 解析返回值行 + """ + if line := self.match_next_line(): + self.docstring.add_return(line.strip()) + + def parse_raises(self): + """ + 解析异常行 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_raise(name.strip(), desc.strip()) + else: + self.docstring.add_raise(line.strip()) + + def parse_example(self): + """ + 解析示例行 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_example(name.strip(), desc.strip()) + else: + self.docstring.add_example(line.strip()) + + def parse_attrs(self): + """ + 解析属性行 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_attrs(name.strip(), desc.strip()) + else: + self.docstring.add_attrs(line.strip()) + + def match_next_line(self) -> Optional[str]: + """ + 在一个子解析器中,解析下一行,直到缩进小于等于当前行的缩进 + Returns: + """ + while self.lineno + 1 < len(self.lines): + line = self.lines[self.lineno + 1] + + if line.startswith(" " * self.indent): + line = line[self.indent:] + else: + return None + if not line: + return None + self.lineno += 1 + return line + + def parse(self) -> Docstring: + """ + 逐行解析,直到遇到EOS + + 最开始未解析到的内容全部加入desc + + Returns: + + """ + add_desc = True + while self.lineno < len(self.lines): + token = self.match_token() + if token is None and add_desc: + self.docstring.add_desc(self.lines[self.lineno].strip()) + + if token is not None: + add_desc = False + + match token: + + case "args": + self.parse_args() + case "return": + self.parse_return() + case "attribute": + self.parse_attrs() + case "raises": + self.parse_raises() + case "example": + self.parse_example() + case _: + self.lineno += 1 + + return self.docstring + + +class NumpyDocstringParser(Parser): + ... + + +class ReStructuredParser(Parser): + ... + + +def parse(docstring: str, parser: str = "google", indent: int = 4) -> Docstring: + if parser == "google": + return GoogleDocstringParser(docstring, indent).parse() + else: + raise ValueError(f"Unknown parser: {parser}") diff --git a/liteyuki_autodoc/i18n.py b/liteyuki_autodoc/i18n.py new file mode 100644 index 0000000..3430a0a --- /dev/null +++ b/liteyuki_autodoc/i18n.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +""" +Internationalization module. +""" +from typing import Optional, TypeAlias + +NestedDict: TypeAlias = dict[str, 'str | NestedDict'] + +i18n_dict: dict[str, NestedDict] = { + "en" : { + "docstring": { + "args" : "Args", + "return" : "Return", + "attribute": "Attribute", + "raises" : "Raises", + "example" : "Examples", + "yields" : "Yields", + }, + "src": "Source code", + }, + "zh-Hans": { + "docstring": { + "args" : "参数", + "return" : "返回", + "attribute": "属性", + "raises" : "引发", + "example" : "示例", + "yields" : "产出", + }, + "src": "源码", + }, + "zh-Hant": { + "docstring": { + "args" : "參數", + "return" : "返回", + "attribute": "屬性", + "raises" : "引發", + "example" : "示例", + "yields" : "產出", + }, + "src": "源碼", + }, + "ja" : { + "docstring": { + "args" : "引数", + "return" : "戻り値", + "attribute": "属性", + "raises" : "例外", + "example" : "例", + "yields" : "生成", + }, + "src": "ソースコード", + }, +} + + +def flat_i18n_dict(data: dict[str, NestedDict]) -> dict[str, dict[str, str]]: + """ + Flatten i18n_dict. + Examples: + ```python + { + "en": { + "docs": { + "key1": "val1", + "key2": "val2", + } + } + } + ``` + + to + + ```python + { + "en": { + "docs.key1": "val1", + "docs.key2": "val2", + } + } + ``` + Returns: + """ + ret: dict[str, dict[str, str]] = {} + + def _flat(_lang_data: NestedDict) -> dict[str, str]: + res = {} + for k, v in _lang_data.items(): + if isinstance(v, dict): + for kk, vv in _flat(v).items(): + res[f"{k}.{kk}"] = vv + else: + res[k] = v + return res + + for lang, lang_data in data.items(): + ret[lang] = _flat(lang_data) + + return ret + + +i18n_flat_dict = flat_i18n_dict(i18n_dict) + + +def get_text(lang: str, key: str, default: Optional[str] = None, fallback: Optional[str] = "en") -> str: + """ + Get text from i18n_dict. + Args: + lang: language name + key: text key + default: default text, if None return fallback language or key + fallback: fallback language, priority is higher than default + Returns: + str: text + """ + if lang in i18n_flat_dict: + if key in i18n_flat_dict[lang]: + return i18n_flat_dict[lang][key] + + if fallback is not None: + return i18n_flat_dict.get(fallback, {}).get(key, default or key) + else: + return default or key diff --git a/liteyuki_autodoc/output.py b/liteyuki_autodoc/output.py new file mode 100644 index 0000000..0d9a2d5 --- /dev/null +++ b/liteyuki_autodoc/output.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午3:59 +@Author : snowykami +@Email : snowykami@outlook.com +@File : output.py +@Software: PyCharm +""" +import os.path + +from liteyuki_autodoc.style.markdown import generate +from liteyuki_autodoc.syntax.astparser import AstParser + + +def write_to_file(content: str, output: str) -> None: + """ + Write content to file. + + Args: + content: str, content to write. + output: str, path to output file. + """ + if not os.path.exists(os.path.dirname(output)): + os.makedirs(os.path.dirname(output)) + + with open(output, "w", encoding="utf-8") as f: + f.write(content) + + +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_relative_path(base_path: str, target_path: str) -> str: + """ + 获取相对路径 + Args: + base_path: 基础路径 + target_path: 目标路径 + """ + return os.path.relpath(target_path, base_path) + + +def generate_from_module(module_folder: str, output_dir: str, with_top: bool = False, lang: str = "zh-Hans", 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) + + # 清理输出目录 + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + replace_data = { + "__init__": "index", + ".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) + + # 获取模块信息 + ast_parser = AstParser(open(pyfile_path, "r", encoding="utf-8").read()) + + # 生成markdown + + front_matter = { + "title" : pyfile_path.replace("\\", "/"). + replace("/", "."). + replace(".py", ""). + replace(".__init__", ""), + + } + + md_content = generate(ast_parser, lang=lang, frontmatter=front_matter) + print(f"Generate {pyfile_path} -> {abs_md_path}") + file_data[abs_md_path] = md_content + + for fn, content in file_data.items(): + write_to_file(content, fn) diff --git a/liteyuki_autodoc/style/__init__.py b/liteyuki_autodoc/style/__init__.py new file mode 100644 index 0000000..fb9c65f --- /dev/null +++ b/liteyuki_autodoc/style/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午3:39 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" \ No newline at end of file diff --git a/liteyuki_autodoc/style/markdown.py b/liteyuki_autodoc/style/markdown.py new file mode 100644 index 0000000..730becf --- /dev/null +++ b/liteyuki_autodoc/style/markdown.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午3:39 +@Author : snowykami +@Email : snowykami@outlook.com +@File : markdown.py +@Software: PyCharm +""" +from typing import Optional + +from liteyuki_autodoc.syntax.astparser import AstParser +from liteyuki_autodoc.syntax.node import * +from liteyuki_autodoc.i18n import get_text + + +def generate(parser: AstParser, lang: str, frontmatter: Optional[dict] = None) -> str: + """ + Generate markdown style document from ast + You can modify this function to generate markdown style that enjoys you + Args: + parser: + lang: language + frontmatter: + Returns: + markdown style document + """ + print(parser.variables) + if frontmatter is not None: + md = "---\n" + for k, v in frontmatter.items(): + md += f"{k}: {v}\n" + md += "---\n" + else: + md = "" + + # var > func > class + for var in parser.variables: + if var.type == TypeHint.NO_TYPEHINT: + md += f"### ***var*** `{var.name} = {var.value}`\n\n" + else: + md += f"### ***var*** `{var.name}: {var.type} = {var.value}`\n\n" + + for func in parser.functions: + + md += func.markdown(lang) + + for cls in parser.classes: + md += f"### ***class*** `{cls.name}`\n\n" + for mtd in cls.methods: + + md += mtd.markdown(lang, 2, True) + + for attr in cls.attrs: + if attr.type == TypeHint.NO_TYPEHINT: + md += f"#### ***attr*** `{attr.name} = {attr.value}`\n\n" + else: + md += f"#### ***attr*** `{attr.name}: {attr.type} = {attr.value}`\n\n" + + return md diff --git a/liteyuki_autodoc/syntax/astparser.py b/liteyuki_autodoc/syntax/astparser.py new file mode 100644 index 0000000..c94e240 --- /dev/null +++ b/liteyuki_autodoc/syntax/astparser.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午2:13 +@Author : snowykami +@Email : snowykami@outlook.com +@File : astparser.py +@Software: PyCharm +""" +import ast + +from .node import * +from ..docstring.parser import parse + + +class AstParser: + def __init__(self, code: str): + self.code = code + self.tree = ast.parse(code) + + self.classes: list[ClassNode] = [] + self.functions: list[FunctionNode] = [] + self.variables: list[AssignNode] = [] + + self.parse() + + def parse(self): + for node in ast.walk(self.tree): + if isinstance(node, ast.ClassDef): + if not self._is_module_level_class(node): + continue + + class_node = ClassNode( + name=node.name, + docs=parse(ast.get_docstring(node)) if ast.get_docstring(node) else None, + inherits=[ast.unparse(base) for base in node.bases] + ) + self.classes.append(class_node) + + # 继续遍历类内部的函数 + for sub_node in node.body: + if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)): + class_node.methods.append(FunctionNode( + name=sub_node.name, + docs=parse(ast.get_docstring(sub_node)) if ast.get_docstring(sub_node) else None, + posonlyargs=[ + ArgNode( + name=arg.arg, + type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in sub_node.args.posonlyargs + ], + args=[ + ArgNode( + name=arg.arg, + type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in sub_node.args.args + ], + kwonlyargs=[ + ArgNode( + name=arg.arg, + type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in sub_node.args.kwonlyargs + ], + kw_defaults=[ + ConstantNode( + value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT + ) + for default in sub_node.args.kw_defaults + ], + defaults=[ + ConstantNode( + value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT + ) + for default in sub_node.args.defaults + ], + return_=ast.unparse(sub_node.returns).strip() if sub_node.returns else TypeHint.NO_RETURN, + decorators=[ast.unparse(decorator).strip() for decorator in sub_node.decorator_list], + is_async=isinstance(sub_node, ast.AsyncFunctionDef), + src=ast.unparse(sub_node).strip() + )) + elif isinstance(sub_node, (ast.Assign, ast.AnnAssign)): + if isinstance(sub_node, ast.Assign): + class_node.attrs.append(AttrNode( + name=sub_node.targets[0].id, # type: ignore + type=TypeHint.NO_TYPEHINT, + value=ast.unparse(sub_node.value).strip() + )) + elif isinstance(sub_node, ast.AnnAssign): + class_node.attrs.append(AttrNode( + name=sub_node.target.id, + type=ast.unparse(sub_node.annotation).strip(), + value=ast.unparse(sub_node.value).strip() if sub_node.value else TypeHint.NO_DEFAULT + )) + else: + raise ValueError(f"Unsupported node type: {type(sub_node)}") + + elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + # 仅打印模块级别的函数 + if not self._is_module_level_function(node): + continue + + self.functions.append(FunctionNode( + name=node.name, + docs=parse(ast.get_docstring(node)) if ast.get_docstring(node) else None, + posonlyargs=[ + ArgNode( + name=arg.arg, + type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in node.args.posonlyargs + ], + args=[ + ArgNode( + name=arg.arg, + type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg, default in zip(node.args.args, node.args.defaults) + ], + kwonlyargs=[ + ArgNode( + name=arg.arg, + type=ast.unparse(arg.annotation).strip() if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in node.args.kwonlyargs + ], + kw_defaults=[ + ConstantNode( + value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT + ) + for default in node.args.kw_defaults + ], + defaults=[ + ConstantNode( + value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT + ) + for default in node.args.defaults + ], + return_=ast.unparse(node.returns).strip() if node.returns else TypeHint.NO_TYPEHINT, + decorators=[ast.unparse(decorator).strip() for decorator in node.decorator_list], + is_async=isinstance(node, ast.AsyncFunctionDef), + src=ast.unparse(node).strip() + )) + + elif isinstance(node, (ast.Assign, ast.AnnAssign)): + if not self._is_module_level_variable(node): + # print("变量不在模块级别", ast.unparse(node)) + continue + else: + print("变量在模块级别", ast.unparse(node)) + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name): + self.variables.append(AssignNode( + name=target.id, + value=ast.unparse(node.value).strip(), + type=ast.unparse(node.annotation).strip() if isinstance(node, ast.AnnAssign) else TypeHint.NO_TYPEHINT + )) + elif isinstance(node, ast.AnnAssign): + self.variables.append(AssignNode( + name=node.target.id, + value=ast.unparse(node.value).strip() if node.value else TypeHint.NO_DEFAULT, + type=ast.unparse(node.annotation).strip() + )) + + def _is_module_level_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef): + for parent in ast.walk(self.tree): + if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + if node in parent.body: + return False + return True + + def _is_module_level_class(self, node: ast.ClassDef): + for parent in ast.walk(self.tree): + if isinstance(parent, ast.ClassDef): + if node in parent.body: + return False + return True + + def _is_module_level_variable(self, node: ast.Assign): + for parent in ast.walk(self.tree): + if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + if node in parent.body: + return False + return True + + def __str__(self): + s = "" + for cls in self.classes: + s += f"class {cls.name}:\n" + for func in self.functions: + s += f"def {func.name}:\n" + for var in self.variables: + s += f"{var.name} = {var.value}\n" + return s diff --git a/liteyuki_autodoc/syntax/node.py b/liteyuki_autodoc/syntax/node.py new file mode 100644 index 0000000..aecb3a5 --- /dev/null +++ b/liteyuki_autodoc/syntax/node.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午2:14 +@Author : snowykami +@Email : snowykami@outlook.com +@File : node.py +@Software: PyCharm +""" +from typing import Literal, Optional +from enum import Enum + +from pydantic import BaseModel, Field + +from liteyuki_autodoc.docstring.docstring import Docstring +from liteyuki_autodoc.i18n import get_text + + +class TypeHint: + NO_TYPEHINT = "NO_TYPE_HINT" + NO_DEFAULT = "NO_DEFAULT" + NO_RETURN = "NO_RETURN" + + +class AssignNode(BaseModel): + """ + AssignNode is a pydantic model that represents an assignment. + Attributes: + name: str + The name of the assignment. + type: str = "" + The type of the assignment. + value: str + The value of the assignment. + """ + name: str + type: str = "" + value: str + + +class ArgNode(BaseModel): + """ + ArgNode is a pydantic model that represents an argument. + Attributes: + name: str + The name of the argument. + type: str = "" + The type of the argument. + default: str = "" + The default value of the argument. + """ + name: str + type: str = TypeHint.NO_TYPEHINT + + +class AttrNode(BaseModel): + """ + AttrNode is a pydantic model that represents an attribute. + Attributes: + name: str + The name of the attribute. + type: str = "" + The type of the attribute. + value: str = "" + The value of the attribute + """ + name: str + type: str = "" + value: str = "" + + +class ImportNode(BaseModel): + """ + ImportNode is a pydantic model that represents an import statement. + Attributes: + name: str + The name of the import statement. + as_: str = "" + The alias of the import + """ + name: str + as_: str = "" + + +class ConstantNode(BaseModel): + """ + ConstantNode is a pydantic model that represents a constant. + Attributes: + value: str + The value of the constant. + """ + value: str + + +class FunctionNode(BaseModel): + """ + FunctionNode is a pydantic model that represents a function. + Attributes: + name: str + The name of the function. + docs: str = "" + The docstring of the function. + args: list[ArgNode] = [] + The arguments of the function. + return_: ReturnNode = None + The return value of the function. + decorators: list[str] = [] + The decorators of the function. + is_async: bool = False + Whether the function is asynchronous. + """ + name: str + docs: Optional[Docstring] = None + + posonlyargs: list[ArgNode] = [] + args: list[ArgNode] = [] + kwonlyargs: list[ArgNode] = [] + kw_defaults: list[ConstantNode] = [] + defaults: list[ConstantNode] = [] + + return_: str = Field(TypeHint.NO_RETURN, alias="return") + decorators: list[str] = [] + src: str + is_async: bool = False + + def is_private(self): + """ + Check if the function or method is private. + Returns: + bool: True if the function or method is private, False otherwise. + """ + return self.name.startswith("_") + + def is_builtin(self): + """ + Check if the function or method is a builtin function or method. + Returns: + bool: True if the function or method is a builtin function or method, False otherwise. + """ + return self.name.startswith("__") and self.name.endswith("__") + + def markdown(self, lang: str, indent: int = 0, is_classmethod: bool = False) -> str: + """ + Args: + indent: int + The number of spaces to indent the markdown. + is_classmethod: bool + lang: str + The language of the + Returns: + markdown style document + """ + self.complete_default_args() + PREFIX = "" * indent + if is_classmethod: + PREFIX = "- #" + + md = "" + + # 装饰器部分 + if len(self.decorators) > 0: + for decorator in self.decorators: + md += PREFIX + f"### `@{decorator}`\n" + + if self.is_async: + md += PREFIX + "### *async def* " + else: + md += PREFIX + "### *def* " + + md += f"`{self.name}(" # code start + + # 配对位置参数和位置参数默认值,无默认值用TypeHint.NO_DEFAULT + args: list[str] = [] # 可直接", ".join(args)得到位置参数部分 + arg_i = 0 + + if len(self.posonlyargs) > 0: + for arg in self.posonlyargs: + arg_text = f"{arg.name}" + if arg.type != TypeHint.NO_TYPEHINT: + arg_text += f": {arg.type}" + arg_default = self.defaults[arg_i].value + if arg_default != TypeHint.NO_DEFAULT: + arg_text += f" = {arg_default}" + args.append(arg_text) + arg_i += 1 + # 加位置参数分割符 / + args.append("/") + + for arg in self.args: + arg_text = f"{arg.name}" + if arg.type != TypeHint.NO_TYPEHINT: + arg_text += f": {arg.type}" + arg_default = self.defaults[arg_i].value + if arg_default != TypeHint.NO_DEFAULT: + arg_text += f" = {arg_default}" + args.append(arg_text) + arg_i += 1 + + if len(self.kwonlyargs) > 0: + # 加关键字参数分割符 * + args.append("*") + for arg, kw_default in zip(self.kwonlyargs, self.kw_defaults): + arg_text = f"{arg.name}" + if arg.type != TypeHint.NO_TYPEHINT: + arg_text += f": {arg.type}" + + if kw_default.value != TypeHint.NO_DEFAULT: + arg_text += f" = {kw_default.value}" + args.append(arg_text) + + md += ", ".join(args) + ")" + + md += "`\n\n" # code end + + """此处预留docstring""" + if self.docs is not None: + md += f"\n{self.docs.markdown(lang, indent)}\n" + else: + pass + # 源码展示 + md += PREFIX + f"\n
\n{get_text(lang, 'src')}\n\n```python\n{self.src}\n```\n
\n\n" + + return md + + def complete_default_args(self): + """ + 补全位置参数默认值,用无默认值插入 + Returns: + + """ + num = len(self.args) + len(self.posonlyargs) - len(self.defaults) + self.defaults = [ConstantNode(value=TypeHint.NO_DEFAULT) for _ in range(num)] + self.defaults + + def __str__(self): + return f"def {self.name}({', '.join([f'{arg.name}: {arg.type} = {arg.default}' for arg in self.args])}) -> {self.return_}" + + +class ClassNode(BaseModel): + """ + ClassNode is a pydantic model that represents a class. + Attributes: + name: str + The name of the class. + docs: str = "" + The docstring of the class. + attrs: list[AttrNode] = [] + The attributes of the class. + methods: list[MethodNode] = [] + The methods of the class. + inherit: list["ClassNode"] = [] + The classes that the class inherits from + """ + name: str + docs: Optional[Docstring] = None + attrs: list[AttrNode] = [] + methods: list[FunctionNode] = [] + inherit: list["ClassNode"] = [] diff --git a/make_docs.py b/make_docs.py new file mode 100644 index 0000000..54a1301 --- /dev/null +++ b/make_docs.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午12:50 +@Author : snowykami +@Email : snowykami@outlook.com +@File : make_docs.py +@Software: PyCharm +""" \ No newline at end of file diff --git a/mbcp/mp_math/angle.py b/mbcp/mp_math/angle.py index 2cd02ec..a7fc861 100644 --- a/mbcp/mp_math/angle.py +++ b/mbcp/mp_math/angle.py @@ -15,7 +15,11 @@ from .const import PI # type: ignore from .utils import approx -class AnyAngle: +class Angle: + ... + + +class AnyAngle(Angle): def __init__(self, value: float, is_radian: bool = False): """ 任意角度。 diff --git a/mbcp/mp_math/equation.py b/mbcp/mp_math/equation.py index 9352af0..e6e5ce8 100644 --- a/mbcp/mp_math/equation.py +++ b/mbcp/mp_math/equation.py @@ -18,9 +18,10 @@ class CurveEquation: def __init__(self, x_func: OneVarFunc, y_func: OneVarFunc, z_func: OneVarFunc): """ 曲线方程。 - :param x_func: - :param y_func: - :param z_func: + Args: + x_func: x函数 + y_func: y函数 + z_func: z函数 """ self.x_func = x_func self.y_func = y_func @@ -31,7 +32,7 @@ class CurveEquation: 计算曲线上的点。 Args: *t: - + 参数 Returns: """ diff --git a/mbcp/mp_math/plane.py b/mbcp/mp_math/plane.py index e87deff..dc260cb 100644 --- a/mbcp/mp_math/plane.py +++ b/mbcp/mp_math/plane.py @@ -32,12 +32,11 @@ class Plane3: self.c = c self.d = d - def approx(self, other: 'Plane3', epsilon: float = APPROX) -> bool: + def approx(self, other: 'Plane3') -> bool: """ 判断两个平面是否近似相等。 Args: other: - epsilon: Returns: 是否近似相等 diff --git a/mbcp/mp_math/point.py b/mbcp/mp_math/point.py index 7a1bed6..35facf1 100644 --- a/mbcp/mp_math/point.py +++ b/mbcp/mp_math/point.py @@ -62,15 +62,15 @@ class Point3: """ return approx(self.x, other.x) and approx(self.y, other.y) and approx(self.z, other.z) - def __sub__(self, other: "Point3") -> "Vector3": - """ - P - P -> V - - P - V -> P 已在 :class:`Vector3` 中实现 - Args: - other: - Returns: - - """ - from .vector import Vector3 - return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) + # def __sub__(self, other: "Point3") -> "Vector3": + # """ + # P - P -> V + # + # P - V -> P 已在 :class:`Vector3` 中实现 + # Args: + # other: + # Returns: + # + # """ + # from .vector import Vector3 + # return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) diff --git a/docs/mkdoc.py b/mkdoc.py similarity index 96% rename from docs/mkdoc.py rename to mkdoc.py index 2bd10ee..72bf946 100644 --- a/docs/mkdoc.py +++ b/mkdoc.py @@ -323,19 +323,13 @@ def generate_docs(module_folder: str, output_dir: str, with_top: bool = False, l # 生成markdown - if "README" in abs_md_path: + if "index" in abs_md_path: front_matter = { - "title" : module_info.module_path.replace(".__init__", "").replace("_", "\\n"), - "index" : "true", - "icon" : "laptop-code", - "category": "API" + "title" : module_info.module_path.replace(".__init__", ""), } else: front_matter = { - "title" : module_info.module_path.replace("_", "\\n"), - "order" : "1", - "icon" : "laptop-code", - "category": "API" + "title" : module_info.module_path } md_content = generate_markdown(module_info, front_matter)