# mathgraph_backend **Repository Path**: jmdvirus/mathgraph_backend ## Basic Information - **Project Name**: mathgraph_backend - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-05 - **Last Updated**: 2026-10-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MathGraph 后端 公式解析、维度判定与网格采样服务。输入数学公式文本,输出 Plotly 可直接消费的 `traces` / `layout` 数据结构。 ## 技术栈 | 组件 | 用途 | | --- | --- | | FastAPI | HTTP 接口框架 | | uvicorn | ASGI 服务器(`run.py` 中以 `reload=True` 启动) | | sympy | 公式解析、符号化简、解方程 | | numpy | 网格采样与数值计算 | 依赖见 [requirements.txt](./requirements.txt)。 ## 运行 ```bash cd backend python -m venv .venv .venv\Scripts\python -m pip install -r requirements.txt .venv\Scripts\python run.py ``` - 默认监听 `127.0.0.1:6500`,可用环境变量 `MATHGRAPH_PORT` / `MATHGRAPH_HOST` 覆盖。 - 接口文档(Swagger):。 - ⚠️ 请用 `run.py` 启动。它内部带 `reload=True`,改代码后自动热重载;直接 `uvicorn app.main:app` 且不加 `--reload` 会导致代码修改不生效。 ### Docker ```bash ./build.sh # 构建镜像 mathgraph-backend:(同时打 latest 标签) ./run.sh # 运行容器(默认宿主端口 6500,接入 mathgraph-net 网络) ``` - 版本号取自 [VERSION](./VERSION),可用 `VERSION=x.y.z ./build.sh` 覆盖;`APP_PORT` (默认 `6500`)、`PIP_INDEX_URL`、`IMAGE_NAME` 均可通过环境变量传入。 - 容器内固定 `MATHGRAPH_HOST=0.0.0.0`、`MATHGRAPH_PORT=${APP_PORT}`,`run.sh` 会按 `${HOST_PORT}:${APP_PORT}` 映射端口。 - 后端镜像与前端镜像的离线打包见仓库根目录的 `../package.sh`(`docker save` 到 `dist/*.tar`)。 ## 目录结构 ``` backend/ ├── VERSION # 版本号,被 config.py 读取并由 /api/health 返回 ├── requirements.txt ├── run.py # 开发启动脚本 ├── Dockerfile # 后端镜像(python:3.12-slim) ├── .dockerignore ├── build.sh # 构建镜像 mathgraph-backend: ├── run.sh # 运行容器(端口 6500,接入 mathgraph-net,挂载数据卷) ├── data/ # 本地 SQLite 数据库目录(已 gitignore,首次启动自动创建) └── app/ ├── main.py # 创建 FastAPI 应用、启动时初始化公式库、注册 CORS 与路由 ├── config.py # 版本号、监听地址、端口、数据库路径、CORS 白名单 ├── routers/ │ ├── health.py # GET /api/health │ ├── plot.py # POST /api/plot/analyze、POST /api/plot/compose │ └── library.py # /api/library 的增删改查与函数值表只读接口 └── services/ ├── plotter.py # 全部核心逻辑:解析、采样、求交、组图 ├── library_db.py # 公式库与函数值表的 SQLite 存储层(建表、seed、自动归类、CRUD) └── library_seed.py # 内置类别与公式、函数值表数据、归类映射 ``` ## 配置(app/config.py) | 变量 | 默认值 | 说明 | | --- | --- | --- | | `VERSION` | 读 `VERSION` 文件 | 版本号 | | `HOST` | `127.0.0.1` | 监听地址(`MATHGRAPH_HOST`) | | `PORT` | `6500` | 后端端口(`MATHGRAPH_PORT`) | | `FRONTEND_PORT` | `6501` | 用于生成 CORS 白名单(`MATHGRAPH_FRONTEND_PORT`) | | `DB_PATH` | `backend/data/mathgraph.db` | 本地 SQLite 路径(`MATHGRAPH_DB_PATH`,容器内为 `/app/data/mathgraph.db`) | | `ALLOWED_ORIGINS` | `http://localhost:6501`、`http://127.0.0.1:6501` | CORS 仅放行前端端口 | ## 接口 所有接口都在 `/api` 前缀下。 ### GET /api/health 健康检查,同时返回后端版本号。 **响应** ```json { "status": "ok", "version": "0.1.0" } ``` --- ### POST /api/plot/analyze 解析**单条**公式并返回可直接渲染的数据。 **请求体** | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `formula` | string | 是 | 公式文本,如 `y = sin(x)` | | `params` | object | 否 | 可调参数取值,如 `{"a": 2, "b": 3}` | ```json { "formula": "y = a * sin(b * x)", "params": { "a": 2, "b": 3 } } ``` **响应** | 字段 | 说明 | | --- | --- | | `kind` | 公式类别:`explicit2d` / `explicit3d` / `implicit2d` / `implicit3d` / `parametric2d` / `parametric3d` | | `dimension` | 维度:`2` 或 `3` | | `independent` | 自变量名列表,如 `["x"]`、`["x", "y"]`、`["t"]` | | `target` | 因变量名,如 `y`、`z` | | `formula` | 归一化后的公式文本 | | `parameters` | 识别出的可调参数:`[{"name": "a", "value": 2}]` | | `traces` | Plotly trace 数组 | | `layout` | Plotly layout(含坐标轴标题) | 公式语法错误或形式不支持时返回 **HTTP 400**,响应体为 `{"detail": "错误说明"}`。 --- ### POST /api/plot/compose 同时绘制**多条**公式,并对 2D 曲线两两求交、对 3D 图元求交线/交点。 **请求体** | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `formulas` | string[] | 是 | 公式列表,逐条绘制 | | `params` | object | 否 | 所有公式**共享**的参数取值,同名参数只保留一个值 | | `dimension` | string | 否 | `auto`(默认,按公式判定)、`3d`(强制把只涉及两个坐标的公式沿缺失坐标拉伸成平面/柱面)或 `2d`(强制把只涉及两个坐标的公式画成各自坐标平面上的曲线;公式用到的坐标合起来不超过两个时是一张二维图,超过两个时坐标体系仍为 3D) | | `ranges` | object | 否 | 各变量(`x` / `y` / `z` / `t`)的采样区间,如 `{"x": [-20, 20], "t": [0, 25.13]}`;某一端为 `null` 表示沿用默认值 | | `preview` | boolean | 否 | 拖动参数时的预览档(默认 `false`):按低密度采样,只求快不求细,松手后再按全精度重算 | | `axis_planes` | boolean | 否 | 是否高亮图形与三个坐标平面(`x = 0` / `y = 0` / `z = 0`)的交(默认 `false`),仅对 3D 结果有效。**必须是 snake_case**,写成 `axisPlanes` 会被当作未知字段静默忽略。隐式曲面的切面会在切面内按切片同档密度(`_SLICE_COUNT`)重新取网格,直接切 32³ 体积网格会把圆截面切成十几边形、把多面体的角削平。图形整体在某个坐标面一侧、与它无交时,该截面**自动移到图形在该轴范围的中部**(按 1-2-5 步进取整,trace 名标注实际位置如 `x = 3 截面`);跨着原点却无交(如双叶双曲面的 `z = 0`)保持无截面 | | `slices` | object | 否 | 自变量切片:把某些变量固定在给定取值上,如 `{"x": 1.5}`。**必须是 snake_case**。后端只算隐式 3D 曲面(切面内要重新取网格),显式曲面与参数曲线由前端本地切 | ```json { "formulas": ["x^2 + y^2 + z^2 = 9", "z + x = 1"], "params": {}, "dimension": "auto" } ``` ```json { "formulas": ["x = t*cos(t), y = t*sin(t)"], "ranges": { "t": [0, 25.132741] } } ``` ```json { "formulas": ["x^2 + y^2 + z^2 = 4"], "axis_planes": true } ``` ```json { "formulas": ["x^2 + y^2 + z^2 = 1"], "slices": { "x": 0 } } ``` ```json { "formulas": ["y - z + 1 = 0"], "dimension": "2d" } ``` 公式里可用的函数:三角(`sin`/`cos`/`tan`/`asin`/`acos`/`atan`)、双曲、`exp`/`log`/`sqrt`/ `abs`,以及分段函数 `Max`/`Min`(大小写均可,括号内逗号不会被当成公式分隔符)。 `Max`/`Min` 常用来写多边形与多面体,如正方形 `Max(abs(x), abs(y)) = 2`、 正方体 `Max(abs(x), abs(y), abs(z)) = 2`。 `ranges` 留空表示用默认窗口(2D 曲线 `x ∈ [-10, 10]`、3D 曲面/隐式曲面 `[-5, 5]`、参数方程含三角函数时 `t ∈ [0, 2π]`,否则 `[-10, 10]`)。跨度变大时采样点数 按比例增加以维持密度,上限按维度为 `4000`(一维曲线)/ `1280`(2D 隐式曲线,默认 `640 × 640`)/ `240`(3D 显式曲面)/ `56`(3D 隐式等值面,默认 `32³`)。区间非法 (最小值 ≥ 最大值)时该条公式返回错误,其余公式不受影响。 隐式图形有两条**精确路径**,识别不出来才用网格: - **多边形 / 多面体**:`Min` / `Max` / `abs` 拼出的方程本质是一组线性不等式的交,于是不解 方程也不采样,直接解出顶点——2D 输出闭合折线(`scatter`)、3D 输出多面体网格(`mesh3d`), 棱角绝对精确,响应体从 MB 级降到 KB 级(正方体 8 个顶点约 1KB,耗时 0.03s)。 - **旋转体**(圆柱、圆锥、圆台、球…):方程只依赖 `x² + y²` 与 `z`,即母线绕 z 轴旋转。 等值面是 32³ 规则体网格(Plotly 的 `isosurface` 需要整张网格),圆截面会被切成十几边形, 放大就是锯齿,而网格加密响应体按三次方涨。这里改为在 (r, z) 半平面提一次 2D 等值线拿到 母线,再按 96 份角度旋转成 `mesh3d`:轮廓与体网格密度无关,圆边是 96 段,体积只有 KB 级 (圆柱 34KB,球 425KB)。 其余隐式曲面(如 `x² + y² + z² + x = 1`,球心不在 z 轴上;以及各种非旋转、非多胞体的曲面) 仍走等值线 / 等值面网格:默认 2D `640 × 640`、3D `32³`。这类曲面没有尖角也没有规则母线, 网格采样已经足够;若为求余出的锯齿一味加密网格,只会白白撑大响应体(3D 隐式 48³ 单个等值面 约 8MB)。 **响应** | 字段 | 说明 | | --- | --- | | `dimension` | 整体维度:全部公式用到的坐标变量并起来达到 3 个(含 3D 曲面/参数曲线)即为 3,否则为 2 | | `dimension_mode` | 实际生效的维度模式,回显请求值(`auto` / `3d` / `2d`)。报 `2d` 而 `dimension` 为 3 时表示「公式按曲线画,但坐标体系仍是 3D」 | | `items` | 每条公式的结果,见下 | | `parameters` | 合并后的参数列表:`[{"name": "a", "value": 1}]` | | `ranges` | 本次**实际使用**的采样区间,如 `{"x": [-10, 10], "t": [0, 6.283]}`(同名变量取并集) | | `traces` | 所有公式的 trace + 交点/交线 trace + 2D 参数曲线(3D 场景下按自身所属的坐标平面抬到 3D)。只涉及两个坐标的方程在 3D 场景下已直接拉伸成 `surface` | | `layout` | Plotly layout | | `intersections` | 2D 交点数组 | | `intersections3d` | 3D 交线 / 交点 / 跳过说明 | | `slice_traces` | 隐式 3D 曲面的切片 trace(由 `slices` 触发;显式曲面与参数曲线的切片由前端本地算,不出现在这里) | `items[]` 字段: | 字段 | 说明 | | --- | --- | | `index` | 在 `formulas` 中的下标(用于与前端输入行对应) | | `formula` | 公式文本 | | `color` | 该条公式的配色(与图中曲线一致) | | `kind` / `dimension` / `independent` / `target` | 同 `analyze` | | `error` | 该条公式的解析错误(存在时表示这条失败,其它条不受影响) | | `note` | 求交降级说明,如"两个隐式曲面求交暂不支持" | | `ranges` | 该条公式**实际使用**的采样窗口,形如 `{"x": [最小值, 最大值]}`(顶层 `ranges` 是多公式合并后的,按条取这里才准) | | `axes` | 该条图形所在的坐标平面:2D 曲线给出两个坐标名(如 `["x", "z"]`、`["y", "z"]`),3D 曲面/参数曲线为 `["x", "y", "z"]` | | `eval` | 仅 `explicit2d` / `parametric2d` 且表达式在白名单内时给出,供前端本地求值,见下 | `eval` 字段(前端拖动参数时零请求重算曲线): | 字段 | 说明 | | --- | --- | | `vars` | 可调参数名列表,顺序即 JS 函数的前几个形参 | | `args` | 自变量名列表(恒为 1 个:`x` 或 `t`),排在形参最后 | | `out` | 输出名列表:显式曲线 1 个(目标变量),参数曲线 2 个(`x`、`y`) | | `exprs` | 与 `out` 一一对应的 JS 表达式字符串 | | `range` | 本次采样窗口 `[最小值, 最大值, 点数]` | | `axes` | 该曲线所在的坐标平面对(同 `items[].axes`) | | `lifted` | 仅当整体为 3D 且该曲线被抬到 3D 场景时给出 `true`,前端据此把重算结果摆回真正的坐标轴 | 表达式只含白名单函数(`sin`/`cos`/`tan`/`asin`/`acos`/`atan`/`sinh`/`cosh`/`tanh`/ `exp`/`log`/`sqrt`/`abs`)与四则、幂运算;遇到不支持的结构该字段整体省略,前端自动 回退到 `preview` 请求。 `intersections[]`(2D 交点): ```json { "x": 1.0, "y": 1.0, "items": [0, 1] } ``` `intersections3d`: | 字段 | 说明 | | --- | --- | | `curves[]` | 交线,形如 `{"type": "curve", "t": [...], "x": [...], "y": [...], "z": [...]}` | | `points[]` | 交点,形如 `{"x": 1.0, "y": 1.0, "z": 1.0, "items": [0, 1]}` | | `skipped[]` | 暂不支持的组合:`{"items": [0, 1], "reason": "..."}` | **错误处理**:单条公式出错只影响该条(错误随 `items[].error` 返回),不会导致整个 请求失败。 --- ## 常用公式库(本地 SQLite) 公式库存放在本地 SQLite,首次启动时建表并写入内置公式(内置条目 `isBuiltin: true`, 只读)。数据库位置由 `MATHGRAPH_DB_PATH` 决定,默认 `backend/data/mathgraph.db`。 ### 表结构 | 表 | 说明 | | --- | --- | | `categories` | 类别:`id` / `name`(唯一)/ `sort_order` | | `formulas` | 公式:`category_id` / `name` / `formula` / `formula_norm`(去空白后唯一,用于查重)/ `is_builtin` / 时间戳 | | `meta` | 键值对,`seed_version` 用于保证内置公式只写入一次(幂等) | ### 自动归类 新增公式时按解析结果归类,解析失败归到「其他」: | 公式形式 | 归入类别 | | --- | --- | | 隐式且含 `Max` / `Min` / `abs`,有 `z` | 立体图形 | | 隐式且含 `Max` / `Min` / `abs`,无 `z` | 平面图形 | | 隐式 `F(x, y) = 0` | 圆锥曲线(隐式) | | 隐式 `F(x, y, z) = 0` | 3D 隐式曲面 | | 显式 `z = f(x, y)` | 3D 曲面 | | 参数曲线(含 z) | 3D 参数曲线 | | 参数曲线(不含 z) | 参数曲线 | | 显式 2D 且含三角函数 | 三角与周期 | | 显式 2D 且含 `exp` / `log` / `e` | 指数与对数 | | 显式 2D 其余 | 多项式与有理式 | 内置类别共 11 个(三角与周期、多项式与有理式、指数与对数、圆锥曲线(隐式)、参数曲线、 **平面图形**、3D 曲面、3D 隐式曲面、3D 参数曲线、**立体图形**、其他)。其中平面图形 10 条 (三角形、正方形、长方形、菱形、平行四边形、梯形、正六边形、正八边形等的边界方程)、 立体图形 7 条(正方体、长方体、四面体、正四棱锥、圆柱、圆锥、圆台的表面方程),都可用 `Max` / `Min` 写成一条隐式方程——区域 {各约束 ≥ 0} 的边界就是 `Min(...) = 0`, 正多边形/正多面体是 `Max(各方向支撑函数) = 半径`。其中多边形与多面体(正方形、正六边形、 正方体、四面体等)直接解顶点,输出折线 / 多面体网格;圆柱、圆锥、圆台以及球这类只依赖 `x² + y²` 与 `z` 的旋转体,则把母线绕 z 轴旋转成网格——两类的棱角 / 轮廓都是精确的。 ### GET /api/library 按类别返回全部公式(内置 + 自建)。 ```json { "categories": [ { "id": 1, "name": "三角与周期", "items": [ { "id": 1, "name": "正弦曲线", "formula": "y = sin(x)", "categoryId": 1, "isBuiltin": true } ] } ] } ``` ### GET /api/library/values 按类别返回「常见函数取值表」——内置、只读,用于前端学习区展示,与绘图逻辑无关。 四类共 76 条:三角函数特殊角、常用数学常数、常用三角恒等式、指数与对数常用值。 ```json { "categories": [ { "id": 1, "name": "三角函数特殊角", "items": [ { "id": 1, "expression": "sin(0)", "value": "0" }, { "id": 5, "expression": "sin(π/2)", "value": "1" } ] } ] } ``` ### POST /api/library/items 新增一条自建公式,返回 **201** 与新建条目(额外带 `categoryName`)。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `formula` | string | 是 | 公式文本 | | `name` | string | 否 | 显示名,缺省时前端显示为「自建公式」 | | `categoryId` | integer | 否 | 指定类别;缺省按公式形式自动归类 | ### PATCH /api/library/items/{id} 修改自建公式的 `name` / `categoryId`,返回 **200** 与更新后的条目。 ### DELETE /api/library/items/{id} 删除自建公式,成功返回 **204**(无响应体)。 **错误约定** | 状态码 | 场景 | | --- | --- | | `400` | 公式为空,或 `categoryId` 指向不存在的类别 | | `403` | 内置公式不可修改 / 删除 | | `404` | 条目不存在 | | `409` | 该公式已在库中(按 `formula_norm` 去空白查重) | ## 核心逻辑(services/plotter.py) 处理流程: 1. **解析** `_build` —— 判断公式形式(显式 / 隐式 / 参数方程)。隐式方程若对某个 坐标是一次式,先用 `_try_explicit_form` 解出该变量转为显式(如 `z + x = 1` → `z = 1 - x`),以便复用显式求交逻辑。 2. **参数处理** —— `x/y/z/t` 之外的自由符号视为可调参数(`α`、`ω` 等希腊字母同样可以), 缺省值为 `1`,代入表达式。参数方程的自变量按 `t` 处理,书写 `θ` 或 `theta` 会在解析前 归一化为 `t`。`_preprocess` 还负责常见写法归一化:`π`→`pi`、`×`→`*`、`÷`→`/`、 `ln`/`arcsin`/`arccos`/`arctan`→`log`/`asin`/`acos`/`atan`、`√x`→`sqrt(x)`、 `30°`→`(30*pi/180)`。`°` 还可以跟在参数或自变量名后:`sin(a°*x)` 把参数 `a` 当 度数(拖滑块调的就是角度),`sin(x°)` 把 `x` 按度数理解(周期 360)。 显式形式里出现 `t`(或 `θ`)会返回 400 提示改用参数方程。 3. **采样** —— 按形式分别采样:显式曲线 / 曲面、隐式等值线(marching squares)、 隐式等值面(isosurface)、参数曲线。**隐式方程先试两条精确路径**——多胞体(`Min` / `Max` / `abs` 拼出的线性约束)解顶点输出折线 / `mesh3d`,旋转体(只依赖 `x² + y²` 与 `z`)把母线 旋转成 `mesh3d`——都不命中才退回网格。**维度由公式用到的 坐标变量个数判定**:只用到 两个坐标(如 `z = sin(x)`、`x^2 + z^2 = 1`)是落在对应坐标平面上的**曲线**,用满 三个坐标(如 `z = x^2 + y^2`)才是**曲面**。整体场景为 3D 时(同绘里有 3D 图元, 或强制 3D),只涉及两个坐标的方程会沿缺失的那个坐标**拉伸成平面/柱面**,这样两个 平面之间也能求出交线;**强制 2D 下不拉伸**,它们仍画成各自坐标平面上的曲线。 4. **求交** —— 全部是 2D 曲线时两两求交(同一坐标平面内直接求交,分处不同平面时只在 公共坐标轴上求交);含 3D 图元时统一在 3D 里求(曲面-曲面交线、曲线-曲面交点、 2D 参数曲线按自身所属坐标平面抬到 3D 后与曲面求交)。曲面求交以显式曲面为基准、 对另一曲面提有符号偏差的等值线,因此 `target` 为 `x`/`y`/`z` 三种显式曲面彼此之间、 以及与隐式曲面都能求交。 同一平面内的两条 2D 曲线先**沿对方点列对隐式方程求根**(`_signed_crossings`,与 曲线—曲面求交同一思路):符号变化处用二分(`_refine_root`)收敛到根,精度不受折线 密度限制;只触不穿的相切 / 尖点(心形线尖点、双纽线在原点)没有符号变化,改取 `|F|` 的**各个局部极小点**(只取全局最小会落在穿越点上而漏掉原点)再三分搜索压到极小 (`_refine_local_min`,相邻两条线段各报一次时用 `_dedupe_by_residual` 按 `|F|` 择优)。 最后再补一遍折线段两两求交作兜底——参数曲线没有隐式方程,只能走它。 5. **组图** —— 上色、加图例、生成 layout,把 2D 参数曲线按自身所属的坐标平面抬到 3D 场景,追加红色交点/交线 trace。 关键常量: | 常量 | 说明 | | --- | --- | | `_PALETTE` | 多公式配色(与前端色点一致) | | `_INTERSECTION_COLOR` | 交点/交线颜色(红色 `#dc2626`) | | `_SLICE_COLOR` / `_SLICE_COUNT` / `_SLICE_SOLVED_TOL` | 切片高亮色(琥珀 `#f59e0b`)/ 隐式切面内的重采样点数(180,坐标面交线也按同档密度重采样)/ 三坐标全固定时的解判定容差 | | `_INTERSECTION_TOL` / `_INTERSECTION_TOL_3D` | 交点合并容差 | | `_TOUCH_RATIO` | 相切(只触不穿)时的兜底判定比例:2D 曲线求交用它筛出 `\|F\|` 的接触点,曲面/曲线与平面相切时也用它 | | `_RANGE_2D` / `_RANGE_3D` / `_RANGE_IMPLICIT_2D` / `_RANGE_IMPLICIT_3D` | 默认采样窗口与点数 | | `_COUNT_CAP_CURVE` / `_COUNT_CAP_CONTOUR` / `_COUNT_CAP_SURFACE` / `_COUNT_CAP_SOLID` | 自定义采样区间时的点数上限 | | `_POLY_PIECE_CAP` / `_POLY_MAX_ARGS_CAP` / `_POLY_ACTIVE_TOL` / `_POLY_FEASIBLE_TOL` | 多胞体精确画法:分段项上限(4,符号组合 ≤ 2⁴)/ `Max`/`Min` 参数个数上限(8)/ 顶点判为落在约束上的容差 / 顶点判为可行的容差 | | `_REV_ANGLE_COUNT` / `_REV_PROFILE_COUNT` / `_REV_TOL` | 旋转体精确曲面:每圈角度分片数(96,决定轮廓圆滑度)/ (r, z) 半平面提母线的网格密度(600)/ 判为落在轴上的半径容差 | | `_resolve_range()` | 按变量名取自定义区间(未填的一端用默认值),并让点数随跨度等比放大 | | `_PREVIEW_FACTORS` | `preview` 档的降采样系数:`curve` 0.25 / `grid` 0.5 / `solid` 0.6 | | `_JS_FUNCS` | 导出前端本地求值时的 JS 函数白名单;参数名用 `str.isidentifier()` 校验(希腊字母也合法) | ## 已支持的求交组合 | 组合 | 结果 | | --- | --- | | 3D 显式曲面 × 3D 显式曲面 | 交线(`target` 为 `x`/`y`/`z` 的任意组合) | | 3D 显式曲面 × 3D 隐式曲面 | 交线 | | 3D 参数曲线 × 3D 曲面 | 交点 | | 2D 参数曲线(按自身所属坐标平面抬到 3D) × 3D 曲面 | 交点 | | 2D 曲线 × 2D 曲线 | 交点(同一坐标平面内直接求交;分处不同平面时在公共坐标轴上求交)。相切 / 尖点这类只触不穿的交点也能找到(心形线尖点、双纽线在原点) | ## 已知限制 - 两个**隐式**曲面之间的交线暂不支持(需三维等值面求交),会返回 `skipped` 说明。 - 两条 3D 参数曲线的交点暂不支持。 - 2D 参数曲线与 3D 参数曲线暂不支持求交(仅支持与曲面的交点)。 - 显式曲面除非能化成某个坐标 = f(另两个坐标)(即 `_is_explicit_surface`),否则不参与 求交,会提示「该曲面形式暂不支持求交」。 - 2D 参数曲线与 3D 同绘时被抬到**自身所属的坐标平面**(用到哪两个坐标就落在对应平面, 如 `y = cos(t), z = sin(t)` 落在 x = 0 平面)。 - 分处不同坐标平面的两条 2D 曲线只能在**公共坐标轴**上求交(两个坐标平面只共享一条 坐标轴),非公共轴上的接近不算相交。该限制只对 2D 场景、强制 2D 与 2D 参数曲线成立; 自动 / 强制 3D 下二变量方程已被拉伸成平面/柱面,按曲面求交处理。 ## 整体架构与代码仓库 MathGraph 前后端是两个**完全独立**的工程,没有相互依赖,前端只通过 HTTP 调用后端接口: ``` 浏览器 └── 前端 (Vite dev server, 端口 6501) │ /api/* 由 Vite 代理转发 ▼ 后端 (FastAPI + uvicorn, 端口 6500) │ sympy 解析 / 维度判定 / numpy 采样 ▼ Plotly traces + layout │ ▼ 前端 plotly.js 渲染 ``` - 开发时可用**上级目录**的 `start.bat` 一键启动前后端并自动打开浏览器(根目录只有 启动脚本,不放文档与仓库)。 - 后端 CORS 仅放行前端端口 `6501`;前端通过 Vite 代理把 `/api` 请求转发到 `127.0.0.1:6500`。 - 代码仓库(各自独立 git 仓库、可分别开发和发布,提交时请在各自工程内执行 git 操作): | 工程 | 仓库 | 分支 | | --- | --- | --- | | 后端(本工程) | | `master` | | 前端 | | `master` |