# dsh-origin-bridge 中文文档

规范地址：https://npmcn.com/package/dsh-origin-bridge/

版本：0\.4\.10
npm 元数据更新时间：2026-11-03T12:49:46.433Z
中文译文更新时间：2026-11-03T12:58:51.769Z
软件包许可：MIT

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/dsh-origin-bridge
原文对照：https://npmcn.com/package/dsh-origin-bridge/?lang=original
项目主页：https://github.com/angelky7/dsh-origin-bridge#readme
源码仓库：https://github.com/angelky7/dsh-origin-bridge

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：647 kB，60 个文件
- 模块格式：ES Module
- TypeScript 类型：未声明
- 运行依赖：0 个
- peer 依赖：@deepseek-ai/dsh-mcp-client 0\.2\.0-rc\.2 \|\| \>=0\.1\.0-rc\.1 \<1
- 首次发布：2026-10-09T15:20:13.981Z
- 最新版本发布：2026-11-03T12:49:46.265Z
- 已发布版本数：12（近 12 个月 12 个）

## 中文简介

在对话里驱动本机 Origin / OriginPro：导入 .dat/.csv/.txt 与电化学工作站 .bin，绘图、拟合、改样式，导出图片并保存可在 Origin 里继续编辑的 .opju 工程。dsh 插件，27 个 MCP 工具。第三方工具，与 OriginLab Corporation 无关联、未获认可。

## 安装

```sh
npm install dsh-origin-bridge
```

也可以使用：`pnpm add dsh-origin-bridge`、`yarn add dsh-origin-bridge`、`bun add dsh-origin-bridge`

## README

![Origin Bridge](https://npmcn.com/package/dsh-origin-bridge/icon.svg)

# Origin Bridge

用自然语言驱动**本机** Origin / OriginPro：  
丢给它一个 `.dat` / `.csv` / Excel / 电化学工作站 `.bin`，  
拿回一张发表级图片和一个**能在 Origin 里继续编辑**的 `.opju` 工程。

27 个工具 · **1135 例全链路测试全部通过** · 数据不出本机 · 支持工作站 `.txt` / `.bin`

[![platform](https://img.shields.io/badge/platform-Windows%20%2B%20Origin%202021%2B-blue)](https://npmcn.com/package/dsh-origin-bridge/#readme-%E5%AE%89%E8%A3%85) ![tests](https://img.shields.io/badge/tests-1135%2F1135%20passing-brightgreen) ![license](https://img.shields.io/badge/license-MIT-lightgrey) [![offline checks](https://github.com/angelky7/dsh-origin-bridge/actions/workflows/offline-checks.yml/badge.svg)](https://github.com/angelky7/dsh-origin-bridge/actions/workflows/offline-checks.yml)

![两条曲线、轴标题齐全的 Origin 成图](https://npmcn.com/package/dsh-origin-bridge/docs/preview.png)

> 上图由 `scripts/make_preview.py` 生成：导入 → 画两条曲线 → 配色与符号 → 关掉图例 → 导出 PNG，全部走本插件自己的引擎调用，轴标题与图例可见性都读回校验后才导出。数据是 `sample.dat` 的合成序列，不含任何真实实验数据。

* * *

## 为什么是它

市面上把 Origin 接进聊天的尝试，要么要求把数据上传到云端，要么是个 200 行的玩具。Origin Bridge 反着来：

-   **数据不出本机。** 没有 API key、没有云端组件，dsh 拉起一个本地 Python 进程，通过 OriginLab 官方的 [`originpro`](https://pypi.org/project/originpro/) 驱动你机器上的 Origin。
-   **每一步都有证据。** Origin 的 X-Function 普遍"返回成功但什么都没发生"。这里画图后读回轴范围与数据跨度比对，导出后校验文件存在/体积/文件头魔数，样式逐项读回——读不回的进 `failed` 列表，不会假装成功。
-   **不是 74 个工具，是 27 个。** 每个都有实测用途，工具描述里写着实测限制，模型不会踩坑。
-   **1000+ 次真实测试，不是自称。** **1135 例**全链路用例跑在真实 stdio 服务器 + 本机 OriginPro 上，**1135 通过 / 0 失败**：27 个工具每个至少 15 例、最多 135 例，按副作用分五层（L0 纯解析 301、L1 只读 COM 63、L2 改会话 530、L3 落盘 163、L4 进程级 78）。提示词全部按人话写，跑之前先用在线 `tools/list` schema 校验用例本身，所以失败只会是产品问题，不会是题目写错。另有 **420 项**离线/单元/边界检查（asciiio 84、binio 30、lifecycle 42、smoke 129、edge 26、paths 35、json 29、readfile 12、labtalk 33）**随包发布，你可以直接复跑**；那 1135 例题库含提示词与模拟数据，是开发侧资产，不随包分发。originpro 的十几个文档陷阱全部记录在案并绕开。

## 它能做什么

| 你说 | 它做 |
| --- | --- |
| "把 `D:\data\spectrum.dat` 画成散点连线，X 轴 Time (s)，导出到 `D:\out`" | `origin_figure`：导入 → 画图 → 轴标题 → PNG + .opju |
| "这两个文件画在一张图上，双 Y 轴" | `origin_layer`（右 Y 轴）+ `origin_plot`（往指定层画） |
| "给这条谱图加个图例，放右上角" | `origin_legend`（文字/位置/可见性全部读回） |
| "这组 EIS 数据拟合一下" | `origin_fit`（gauss / lorentz / voigt / expdec1… 预设，参数可固定） |
| "这是 CHI 工作站导出的 cv.bin" | `origin_import` 直接读二进制，电势轴按头部方法参数重建 |
| "这是工作站导出的 txt，前面一大段参数" | `origin_import` 自动跳过参数头，紧贴数据的那行（`Potential/V, Current/A`）当表头，单位拆到列的 Units 里，参数进 `metadata` |
| "做一张 XPS 图" | `origin_chart_render`（40 个科研预设：轴标题/图类型/建议拟合） |
| "热力图显示这个矩阵" | `origin_matrix`（heat\_map / cmap / mesh / contline，Origin 自带图模） |

数据源支持：`.dat` `.csv` `.txt` `.tsv`（`origin_read_file` 只读这些文本类；`.xls/.xlsx/.wks` 会明确报 `unsupported_file_type` 让你改用 `origin_import`。自动嗅探分隔符；编码覆盖 UTF-8 / GBK / **UTF-16（有无 BOM 都行）** / cp1252；多行表头、Fortran 指数、`NA`/`-`；还有 `<0.001` 检出限、`2.5±0.1`、`25 mV`、`1,234.5`、`(500)`、`2.5*` 这类被装饰过的数值——每解一种都会在 `warnings` 里写清代入了什么）、`.xls` `.xlsx`、电化学工作站（CH Instruments 等）导出的 `.txt`（几十行仪器说明自动收进 `metadata`，不会把首行日期当成列名），以及头部为 `80 F2 1B 00` 的电化学工作站二进制 `.bin`。

## 安装

要求：Windows、Origin/OriginPro **2021 或更高**（`originpro` 的硬性下限，实测 2018 不可用）、Python 3.10+。

### 方式 A：用 `dsh plugin` 从 npm 装（推荐）

```bash
dsh plugin --profile desktop add dsh-origin-bridge
```

> 只多装 **1 个包**（隔离 profile 实测 pnpm `Packages: +1`，620ms）。宿主自带的 `@deepseek-ai/dsh-mcp-client` 声明成**可选 peer**，不会在插件目录里再塞一份 loader 副本。

> 装的时候报 `ERR_PNPM_NO_MATCHING_VERSION`、提示 "latest release is 0.4.x"：那是**镜像滞后**，不是包没发出去。dsh 安装完全沿用你本机的 npm 配置（`npm config get registry` 看一眼），实测 0.4.5 发布 2.5 小时后 `registry.npmmirror.com` 上仍只有 0.4.4。两种解法都验证过：
> 
> ```bash
> # 1) 显式走官方源（这个参数会透传给 pnpm；24 小时新鲜度门禁会自己写进 minimumReleaseAgeExclude）
> dsh plugin --profile desktop add dsh-origin-bridge --registry=https://registry.npmjs.org/
> # 2) 离线包，完全不经过 registry
> dsh plugin --profile desktop add file:C:\path\to\dsh-origin-bridge
> ```

```bash
dsh plugin --profile desktop add dsh-origin-bridge
```

> 旧账号下的转发壳 `@caob23/dsh-origin-bridge` 已弃用（deprecated），请直接安装上面的正式包。

本包是 bundle 包（`package.json` 的 `dsh.bundle.patch` 指向根目录的 `cordis.patch.yml`），`dsh plugin` 装完会自动把它加进 profile 的 `dsh.profile.bundles`，**重启 dsh 即加载**，不用再手写补丁。`dsh plugin` 转发给 pnpm，所以 pnpm 要在 PATH 上。

**npm 只管分发，不管你的 Python 环境**——Origin 依赖必须自己装进 dsh 实际使用的那个解释器：

```powershell
pip install originpro numpy        # 国内网络慢就加 -i https://pypi.tuna.tsinghua.edu.cn/simple
```

> **前置缺了会怎样，两种情况不一样**（2026-10-19 实测，别猜）：
> 
> -   **解释器在、`originpro` 不在**：27 个工具**照常注册**，每一次调用都返回 `code=missing_originpro` 和原因（`No module named 'originpro'`），`isError: true`——看得见、知道修什么。
> -   **PATH 上没有 `python`**（补丁默认命令就是 `python`）：桥进程起不来，`failOnStartupError: false` 保证 dsh 照常启动，代价是那一轮**工具完全不注册**、也没有报错。这时先设 `ORIGIN_BRIDGE_PYTHON`。
> 
> Origin 没开**不影响**注册：COM 连接是第一次工具调用时才建立的。

包内补丁的默认取值是 `python`（PATH 上那个）和 `profiles/desktop`。依赖装在 venv 里、或者你用的是别的 profile，就设这两个用户环境变量（设完要重开终端、再彻底重启 dsh）：

```powershell
setx ORIGIN_BRIDGE_PYTHON "C:\path\to\.venv\Scripts\python.exe"
setx ORIGIN_BRIDGE_HOME   "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-origin-bridge"
```

装完自检（路径按你的 profile 名改）：

```powershell
Test-Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-origin-bridge\server.py"
```

彻底退出 dsh 再打开，新会话里说"列出 origin 开头的工具"，应看到 27 个 `mcp__origin_bridge__origin_*`。卸载：

```bash
dsh plugin --profile desktop remove dsh-origin-bridge
```

> `failOnStartupError: false` 是刻意的：Origin 没开或有模态框卡住 COM 时，只让工具这一轮不注册，不把 dsh 一起拖崩。

### 方式 B：本地 clone（要跑测试或改代码时）

```powershell
cd C:\path\to\dsh-origin-bridge
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt      # 国内网络慢就加 -i https://pypi.tuna.tsinghua.edu.cn/simple
```

先确认能独立跑通（会自己拉起 Origin）：

```powershell
.\.venv\Scripts\python.exe smoke_test.py              # 129 项，看到 ALL GOOD 才算过
.\.venv\Scripts\python.exe edge_checks.py             # 26 项边界
.\.venv\Scripts\python.exe test_asciiio.py            # 84 项，不需要 Origin
.\.venv\Scripts\python.exe test_binio.py              # 30 项，不需要 Origin；有真实 .bin 时会跟厂商 .txt 逐点对账
.\.venv\Scripts\python.exe test_lifecycle.py          # 42 项，不需要 Origin：实例分类/回收/掉线重连的安全边界
.\.venv\Scripts\python.exe test_paths.py              # 35 项，不需要 Origin：导出/保存目标路径的规范化与拒绝
.\.venv\Scripts\python.exe test_json_safety.py        # 29 项，不需要 Origin：返回值必须是严格 JSON
.\.venv\Scripts\python.exe test_readfile.py        # 12 项，不需要 Origin：read_file 的 sample 与二进制表格拒绝
.\.venv\Scripts\python.exe test_labtalk_guard.py    # 33 项，不需要 Origin：LabTalk 黑名单匹配
```

上面这 420 项随包发布，你可以自己复跑。另外那 **1135 例全链路题库是开发侧资产**（`cases/` + `run_cases.py`），跑的是真实 stdio 服务器 + 本机 Origin，用它的提示词和你的原始数据都不适合公开，所以没有进仓库、也没有进 npm 包；本文里的覆盖数字都来自它的实际运行结果。

### 不是 dsh，是别的 MCP 客户端

npm 装完会生成 `origin-bridge` 这个可执行入口（`bin/origin-bridge.mjs`），stdio 直连，配置里把 `command` 指向它或指向 `node_modules/dsh-origin-bridge/bin/origin-bridge.mjs`，`serverName` 保持 `origin_bridge` 即可，工具名仍是 `mcp__origin_bridge__origin_*`。

## 挂进 dsh（只针对方式 B）

走方式 A 的话这一段可以整个跳过——`dsh plugin` 已经替你写好了。下面是本地 clone 时的两种接法。

自动（推荐，会先备份再写入）：

```powershell
powershell -ExecutionPolicy Bypass -File .\install_dsh.ps1 -Profile desktop
```

手动：编辑 `C:\Users\<你>\.dsh\profiles\desktop\cordis.patch.yml`（**先备份**），在文件**末尾**追加：

```yaml
- insert:
    - id: mcp-origin-bridge
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        serverName: origin_bridge
        transport: stdio
        command: 'C:\path\to\dsh-origin-bridge\.venv\Scripts\python.exe'
        args: ['-u', '-X', 'utf8', 'C:\path\to\dsh-origin-bridge\server.py']
        env:
          PYTHONIOENCODING: utf-8
          PYTHONUNBUFFERED: '1'
        failOnStartupError: false
        toolCallTimeoutMs: 180000
```

然后**彻底退出 dsh 再打开**（配置只在启动时读一次）。新会话里说"列出 origin 开头的工具"应能看到 27 个 `mcp__origin_bridge__origin_*`。

> 三个必须对的点，都是踩过的坑：
> 
> 1.  **必须以 `- insert:` 开头。** 直接写 `- id: 新名字` 是"覆盖下层已存在条目"的语法，对全新 id 会被 dsh **静默忽略**——症状就是改了配置、重启了、什么都没发生。
> 2.  `name:` 那行必须是 `@deepseek-ai/dsh-mcp-client`（dsh 的 app 包里自带这个 loader），少了它条目不会被任何加载器接管。
> 3.  `serverName` 只能是标识符：工具名是 `mcp__<serverName>__<tool>`，写成 `origin-plot` 这种带连字符的名字会得到没法寻址的工具。
> 
> `failOnStartupError: false` 别省：Origin 没装或卡在弹窗时，它保证 dsh 照常启动，只是那一次不注册工具。

不想用 `install_dsh.ps1` 也不用 npm 的话，可以把整个目录拷进 `<DSH_HOME>\profiles\<profile>\node_modules\dsh-origin-bridge\`，仓库自带的 `cordis.patch.yml` 就是 bundle 层补丁——但这只是方式 A 的手动等价物，路径与解释器仍靠 `ORIGIN_BRIDGE_HOME` / `ORIGIN_BRIDGE_PYTHON` 指定，正常情况请直接走方式 A。卡片图标与显示名来自 `icon.svg` 和 `locale/en.json`、`locale/zh.json`。

## 工具（27 个）

| 工具 | 用途 |
| --- | --- |
| `origin_status` | 连接检查、Origin 版本、已开页面。**动手前先调它** |
| `origin_read_file` | 只解析文件不碰 Origin：编码、分隔符、表头行、每列长名/单位/有效行数、**前 3 个原始值（`sample`，缺值为 null，用来抽查没串列）**；`.xls/.xlsx/.wks` 明确拒绝并让你改用 `origin_import`；`.bin` 走另一条路线给出技术/点数/方法参数/轴来源 |
| `origin_import` | 导入 `.dat/.csv/.txt/.tsv/.xls/.xlsx/.wks` 和工作站二进制 `.bin`，返回 `ws-N` 句柄和列清单 |
| `origin_write` | 内联数据建表：窄表 `columns={列名:[值…]}`，宽表 `headers`+`rows`（一次写完） |
| `origin_formula` | 给列设计算公式（Fx）做派生列，读回算出的行数与首末值 |
| `origin_plot` | 单条或**多条**曲线；`template` 走 Origin 自带图模；`graph`+`layer` 往指定层画 |
| `origin_layer` | 加层（右 Y 轴、上下多面板），返回 Origin 给这层起的名字作为证据 |
| `origin_legend` | 图例：按列长名自动生成或写死，13 个位置，文字/可见性/坐标都读回 |
| `origin_style` | 轴标题、**对数轴**、范围与步长、逐曲线颜色/线宽/符号形状/大小/填充/透明度，**每项读回验证** |
| `origin_annotate` | 图上文字标注与参考线（数据坐标） |
| `origin_matrix` | 网格数据 → 矩阵 → **热力图 / 等高线 / 3D 彩色映射面**（模板驱动） |
| `origin_fit` | `linear`（标准版可用）；预设 `gauss/lorentz/voigt/expdec1/expdec2/sine/power/logistic/boltzmann/doseresp/cubic`；或 `nlfitsing`+`func` |
| `origin_chart_presets` | 40 个科研图表预设（XPS/XRD/UV-Vis/荧光/循环伏安/Arrhenius…） |
| `origin_chart_preset` | 单个预设的完整约定：要哪些列、默认轴标题、建议拟合、轴向提示 |
| `origin_chart_check` | 拿数据列名对预设做 dry-run，不碰 Origin |
| `origin_chart_render` | 按预设一步出图（轴标题/图类型/图例/图片/opju） |
| `origin_export` | 出图，带文件存在/体积/文件头校验，PNG 还回读宽高 |
| `origin_view` | 把图渲染成小图以 **MCP image content 回传给模型自己看** |
| `origin_save_project` | 存 `.opju`（可编辑工程，不是截图） |
| `origin_open` | 打开已有工程 |
| `origin_inspect` | 读回页面/图层/曲线数与已分配句柄——**改图前先看它，别猜索引** |
| `origin_close_pages` | 清理攒下来的页面（长会话里这是"无效指针"的根因），支持 dry\_run |
| `origin_labtalk` | 逃生舱：原始 LabTalk + 强制读回 |
| `origin_exit` | 关 Origin，可选先存盘 |
| `origin_instances` | 清点机器上的 Origin 实例：后台无窗口（客户端退出后的残留）vs 前台有窗口（可能是用户自己的项目），并标出哪些是本进程起的 |
| `origin_reclaim` | 回收残留实例：**只关没有窗口的**；有窗口的一律不动，只在返回里提示用户自己关。`close_background:false` 只看不动手 |
| `origin_figure` | 主路径：导入→画图→轴标题→图例→图片+opju 一次完成 |

## 工作站 `.bin` 是怎么回事

那种头部以 `80 F2 1B 00` 开头、写着 `Cyclic Voltammetry` / `A.C. Impedance` / `Amperometric i-t Curve` 的文件（CHI660E 一类工作站导出），尾部是**一个** float32 数组 = 测量通道（电流 A），**电势轴不在文件里**。

Origin Bridge 的做法：

1.  从头部"数据起点前 600 字节"的参数块读 Init/High/Low E、步长、段数；
2.  按"正向起步的三角波"重建电势轴，**重建点数必须与文件点数完全相等**才采信，否则退回 `axis=none` 并说明原因，绝不拿错的 X 轴画图；
3.  i-t / CA / CP 的时间轴直接由采样间隔展开；EIS 的频率表是另一套参数，明说拿不到，不猜。

这条重建规则对着厂商自己导出的 `.txt` 逐点核过：5 个文件、每个 600 点，电势和电流 **0 处不符**。

## 设计取舍

**为什么不用 `mcp` SDK 的 stdio 传输。** SDK 建的是 asyncio `ProactorEventLoop`，其 self-pipe 在 Windows 上回落到 `socketpair()`（127.0.0.1 listen+connect+accept）。防火墙拦回环 accept 的机器上这一步永久阻塞 → initialize 永不返回 → 客户端等满 60 秒超时。`server.py` 用同步换行分隔 JSON-RPC，握手毫秒级，零事件循环依赖。

**宿主注入的包走 peerDependencies，不走 dependencies。** 以前 `@deepseek-ai/dsh-mcp-client` 写在 `dependencies` 里，`>=0.1.0-rc.1` 这个范围在 pnpm/npm 的默认语义下**够不到** 0.2.0-rc.2 这种新预发布（同元组才允许比预发布），于是安装器只会去挑 0.1.0-rc.8 那份老 loader：隔离 profile 实测旧形状 `Packages: +101` 并落下 `@deepseek-ai/dsh-mcp-client@0.1.0-rc.8`，新形状 `Packages: +1`、`@deepseek-ai` 目录为空。范围写成 `0.2.0-rc.2 || >=0.1.0-rc.1 <1` 是必要的——宿主门禁读 peer 时带 `includePrerelease`（拿运行中的宿主版本比），pnpm 读同一个字符串时不带，只有这种"精确 + 宽松"的并集两边都满足；改成单一 `>=0.1.0-rc.1` 会在安装器侧退回过期版本。`peerDependenciesMeta.optional: true` 则是让 npm 7+ 连自动装 peer 都不做。**别把它"简化"回 dependencies。**

**为什么返回句柄而不是 Origin 引用字符串。** `[Book1]Sheet1!` 这类引用对大小写、空格、活动页状态都敏感，写错了不报错只静默失败。引擎内部持有真实对象，把 `ws-1` / `gr-2` 交给模型原样传回。

**为什么进程探测失败要报 `probe: "unavailable"` 而不是空列表。** `origin_instances` / `origin_reclaim` 靠读进程表区分"没人看的后台实例"和"用户正开着的窗口"。如果 powershell 不可用或超时就让它们返回空集合，调用方读到的是"没有残留、没有前台窗口"——一个假的清白结论。宁可说"我没看清、这次什么都没做"，也不能把"未知"报成"没事"。

**为什么连接时不自动回收既存的无窗口实例。** 被 dsh 驱动的那个 Origin **本身就是无窗口的**：可见性只说明"没有人在屏幕前看它"，不说明"没有客户端在用它"。开机时顺手杀掉所有无窗口实例，会直接砍掉用户当前会话正在跑的任务。所以只有显式 `origin_reclaim` 才动手，且只动无窗口的；有窗口的一律留给用户自己关。

**两条画图路径，各自实测过。** 单 x/单 y 走 LabTalk `plotxy`——那是 Origin 工具栏自己的路径，主题、配色、自动缩放都对。多条曲线 / 指定层 / 指定模板走 `layer.add_plot()` + `layer.rescale()`：`add_plot` 不 rescale 就是 0..10 空轴，但 rescale 之后颜色/符号/线宽全部正确，且**只有它能精确投到某一层**——`plotxy` 给限定范围时返回 True 却画到别的页/层上去了。

**为什么每次写入都读回。** `plot()` 读回轴范围和数据跨度比对，不符就报 `plot_axis_degenerate`；`origin_export` 校验文件存在、体积、文件头魔数；`style()` / `legend()` 每项读回，读不进的进 `failed`。

**缺失值消毒。** Origin 用 `-1.23456789e-300` 表示 missing，直接吐给模型会被当成真实拟合参数。`sanitize()` 统一转成 `null`。

## originpro 1.1.15 / OriginPro 2024 实测陷阱（维护者必读）

这几条都是文档和实际行为不一致，靠读 API 文档写会静默出错：

| 陷阱 | 实际行为 | 处置 |
| --- | --- | --- |
| `lay.add_plot(...)` | 返回 Plot 对象、不报错，但轴停在 0..10 的**空图** | 只在 `rescale()` 之后使用，并用轴范围读回反证 |
| `plotxy` 的 `plot:=` 代码 | 200 线、201 散点、202 线+符号、203 柱（**不是**常见的 201=line） | `PLOT_CODES` 常量，逐个导图目视确认 |
| `plotxy` 往指定层画 | 给限定范围返回 True，曲线却不在目标层（甚至多出一层重叠轴） | 指定层一律用 `add_plot` + `rescale` |
| `add_layer(t)` | t=0 BottomXLeftY、1 TopX、**2 RightY**、3 LeftY、4 TopXRightY、5 BottomXRightY、6 Inset | `LAYER_TYPES` 名字表 + 读回 `layer.name` 当证据 |
| `plot.set_cmd("w=2.5")` | 设线宽**静默无效**（`get_float('line.width')` 仍 0.5） | 用 `set_float('line.width', v)` 并读回比对 |
| `legend` X-Function | `legend 1;` 把图例写成 "111"；`legend -s Graph1` 写成 "Graph1Graph1Graph1"；`legend -p br` 不挪图例反而多出写着 "br" 的文本框 | `legend -r;` 自动生成，文字走 `layer.label('Legend').text`，位置写 `Legend.x/Legend.y`（该层**数据坐标**，锚点是图例中心） |
| `obj.is_open` | 含义是"没被最小化/隐藏"，**不是**"页面还存在" | `close_pages` 用关闭后重新枚举页面名单作为证据 |
| `op.pages()` | 是**生成器**：迭代第二次静默返回空 | 一律 `list(...)` 落地 |
| `WSheet.set_formula(col, …)` | 文档写 1-offset，实际 **0-based**；传 `idx+1` 在 `SetStrProp` 上抛 NoneType | 传 0-based `idx`，列不够先扩列 |
| `WSheet.from_list2(data)` | 内层列表是**列**不是行 | `write_block` 内部转置 |
| `Axis.scale` / `lay.xscale` | setter 收 `'log10'`，getter 返回**整数** 2 | `SCALE_TYPES` 映射后按整数比对 |
| `lt_exec('layer.x.title$="…"')` | 返回 **False**；字符串赋值类 LabTalk 走不通 | 轴标题走对象模型 `origin_style` |
| `op.NLFit(name)` | **区分大小写**且无别名：`Gauss` 可用，`gauss1`/`Linear`/`Quadratic`/`FRADA` 直接抛 Invalid fitting function | `FIT_PRESETS` 只收逐个构造成功的名字 |
| `op.path('p')` | 返回工程**所在文件夹**，不是工程文件 | 自己用 `_project_file` 记录绑定 |
| `op.save(file)` | 语义是 Save-As；绑定后再存同名返回 False（不是失败） | 同路径改用无参 `op.save()`，并比对 mtime |
| COM 线程亲和 | 连接必须在**专用 COM 线程**上建立；在 stdio 读线程上连接会跨 apartment 复用对象 | `_resolve()` 只查句柄表不碰 COM；有"图工具作为首个调用"的回归项守着 |
| 已销毁对象的 `.name` | 留下 C 层待处理异常，之后一个不相干的 `len()` 才炸成 `SystemError` | 句柄登记时顺手存页面名，清理句柄表不再回摸 COM |
| 桥坏到一定程度 | `op.path()` 抛 "returned a result with an exception set"，此时连 `op.exit()` 都跑不动；自动 `detach()/attach()` 会把可诊断的报错变成**进程直接崩** | 不自愈，抛 `connection_error` 并写明要外部重启 Origin |
| 工作站 `.bin` 的数组布局 | 看着像两列（各 N/2），其实厂商 `.txt` 里那 600 行电流正好等于整个数组——只有测量通道 | `binio.py` 按单通道 + 方法参数重建轴，点数相等做门禁 |
| 矩阵图的色标标题 | 色条标题是模板占位文字；`lt_exec` 字符串赋值返回 False，而 originpro 1.1.15 里静态搜 `colorscale / colorbar / cbtitle` **一个 API 都没有**（2026-10-12 查过） | `origin_matrix` 只给模板选择（heat\_map / cmap / mesh / contline / contgray / 3d）与 `xy_range`；色标区间、配色、标题都没做，标题要改只能事后在 Origin 里手点——不假装能设 |
| 导出滤镜对文件名的改写 | 要 `100%_a#b[1].png` 实际写出 `100%_a1b[1].png`：**`#` 被当成编号 token 换成了数字**（`%`、`[]`、空格、中文都活着）。另外 Windows 会静默截掉路径组件尾部的空格/点，于是 `isfile()` 对我们请求的字面路径说 False，而字节确实在盘上 | `_norm_target` 先按 Win32 的规则规范化，`_written_or_mangled` 用 `#+ → \d+` 匹配实际落名的唯一候选、`os.replace` 改回用户要的名字并在 `warnings` 里说明；匹配有歧义就不猜 |
| 没有文件名的 path | `path=" "` 曾经 `abspath` 到进程 CWD，变成插件目录里一个叫 `.png` 的隐形文件（dsh 下就是 node\_modules 安装目录）；`path="."` 同理 | `_norm_target` 要求"第一个点之前有真字符"，否则 `bad_path`；目录、盘根、UNC、未映射盘一律 `bad_path`；已存在但只读/被占用返回 `target_locked`，不覆盖也不假装成功（`test_paths.py` 28 项离线锁住） |
| 编码候选顺序 | 仪器"另存为 Unicode 文本"导出的 UTF-16（有/无 BOM）此前直接报 `找不到数值数据行`；而 cp1252 写的 `µ`/`°` 会被 gb18030 **抢先**解成 `祍`/`癈`——它不报错，所以错误一路静默到列名 | `read_text` 先认 BOM，无 BOM 时按 NUL 占比猜 utf-16 并在 `encoding` 里写明是猜的；再用"高位字节成对=GBK、孤立单字节=cp1252"决定候选顺序（`high_byte_runs`） |
| 装饰过的数值格子 | `<0.001`、`2.5±0.1`、`25 mV`、`1,234.5`、`2.5*`、`(500)`、`1e400` 这些形态此前让整列被判"全为空"直接丢弃——用户看到的是少一列数据，不是报错 | `cell_number` 逐形态解开并回一条 `warnings` 说明代入了什么；溢出/Infinity 一律按缺值，绝不把 inf 送出桥 |
| 分隔符嗅探 | 欧陆 `Zeit;Signal / 0,0;1,5` 会被逗号拆开，而且错的拆法因为"列更多"在评分里胜出 | 评分改成：数值列数 → 行宽一致性 → 列数更少者优先；表头行不参与数值比 |
| `origin_open` 与句柄 | 开工程会替换所有页面，之前发出去的 `ws-/gr-` 句柄全部指向已销毁的对象；死对象上的 `save_fig` 会"成功"但什么都不写（1124 例跑里正是它让连续三个导出失败） | `open_project` 成功与失败都清空句柄表并在返回里给 `handles_reset: true`；旧句柄再用会得到 `worksheet_not_found`/`graph_not_found`，不会静默操作死页面 |
| LabTalk 黑名单 | 原来按子串匹配，2026-10-20 用 45 条高危语句实测 **37 条能过**：`win -cd` 销毁页面、`doc -n` 新建工程丢当前项目、`del/delete` 删数据集、`run -p`/`run.file` 执行外部程序、`exit`、以及能绕过句柄重置的 `doc -o` | 改成按语句（`;`/换行/`{}` 切分）+ 词边界规则：`doc -`、`win -c`、`del/delete`、`layer -d`、`page -d`、`quit/exit`、`run.`/`run -`、`system/shell`、`rmdir/mkdir`、`type -`、`close`、`saveas` 全拦；`test_labtalk_guard.py` 32 项锁住"该拦的拦、该放的放"。仍是劝告式护栏，不是沙箱 |
| dsh 新 profile 的补丁模板 | `cordis.patch.yml` 模板内容就是一行 `[]`（顶层数组占位）。往它后面**追加** `- insert:` 会得到两个 YAML 文档，dsh 直接 `failed to parse overlay` 起不来——`install_dsh.ps1` 2026-10-20 前正是这样，全新环境按 README 用脚本装会把配置写坏 | 安装器现在把"只有注释/只有一个 `[]`"的文件当空文件处理，插块成为数组本身；装完会用 PyYAML 校验一次，解析失败就提示从备份恢复 |
| `save_fig` 的 `width` | 传了也不生效：`save_fig(path)` 只有 path 时用页面尺寸，**必须同时给 type** 才认 width （实测 400 和 12000 导出都是 3216×2461、字节数相同）。我们的 `origin_export` 以前恰好只传 path | `save_fig(path, fmt, width=N)` + 缺文件时回退纯 path；PNG 导完比对 `dimensions.0` 与请求值，不一致就回 warning。题库宽度用例从"有 dimensions"改成**断言等于请求宽度** |
| 非标准 JSON | `json.dumps` 默认把 inf/nan 写成裸 `Infinity`/`NaN`，那不是合法 JSON——dsh 侧整条响应解析失败，看起来像"桥挂了"而不是"一个数不对" | `sanitize()` 把 ±inf/nan 归 None；`server.py` 写出走 `allow_nan=False`，兜不住就降级成带原 id 的 JSON-RPC 错误；`smoke_test` 客户端用 `strict_loads` 拒绝，测试不会替产品掩盖它（`test_json_safety.py` 29 项，含幻觉工具名与未知方法的协议层断言） |

## 已知边界

-   `originpro` 要求 Origin **2021+**。实测 OriginPro 2018C：能连、能存空工程，但导入返回 0 行、`add_plot` 直接打死连接器。
-   非线性拟合需要 **OriginPro** 授权；标准版调 `origin_fit(kind='gauss')` 会明确报 `fit_function_unavailable`。
-   `.bin` 只覆盖 `80 F2 1B 00` 头的工作站导出；**EIS 的频率表在另一套参数里，所以 `.bin` 的 EIS 只有测量列**。Gamry / Autolab / Pine 等其它厂商二进制没做，也不猜。
-   图例位置是"按数据坐标摆放"，对数轴上分数位置会偏；没有"避让数据点"的智能摆放。
-   交付路径：中文目录、中文文件名、含空格、200+ 字符长路径、自动建多级目录、`tiff→tif`/`jpeg→jpg` 改名——2026-10-12 在真机上逐条实测可用（PNG/TIF/PDF/SVG/EMF 和 `.opju` 都试过）。**不行的是网络路径**：UNC 与未映射盘返回 `bad_path`，不去猜认证。另有两条 Origin 自己的怪脾气已经兜住：文件名里的 `#` 会被导出滤镜换成编号（桥发现后会改名回你要的名字并在 `warnings` 里说明），路径组件尾部的空格/点会被 Windows 悄悄截掉（桥先截一遍再比较，不会报"文件不存在"）。
-   千分位和逗号分隔符同形的文件（不加引号的 `0,1,234.5`）没法机械还原：桥按最宽行补齐并报"行长度不一致"，不会假装列数对得上。欧陆小数点（`0,5`）只在分隔符是 `;` 或 tab 时才认。
-   Origin 以管理员运行而 Python 不是（或反之）时 COM 连不上，两侧权限要一致。
-   长会话攒太多页面会让 COM 桥报"无效指针"。`origin_close_pages` 能清，根治是 `origin_exit` 后重连。2026-10-20 实测到 300 页仍不崩，但每张图的耗时从 0.7 秒（前 100 页）涨到 2.8 秒（200–300 页）——所以 `origin_plot`/`origin_matrix` 每新建 50 个页面会在 `warnings` 里提醒一次（计数不花额外 COM 调用，`origin_close_pages`/`origin_exit`/`origin_open` 会清零）。
-   **Origin 实例不会跟着客户端退出**：originpro 需要时自己拉一个 Origin，Python 进程结束那个实例还在。攒够就会报"实例超过上限"。现在插件在进程退出时回收**自己起的、且没有窗口的**实例（`atexit`），历史残留用 `origin_reclaim` 清；有窗口的实例永远不动，因为那可能是你正开着的项目。
-   统计批处理（t 检验 / ANOVA / PCA / 生存分析）没有，originpro 本身也不提供。
-   **无并发保护**：两个会话同时驱动 Origin 会互相踩。
-   40 个图表预设只取元数据；预设自带的 `render_template()` 会先 `op.new()`——那会把用户正在编辑的工程整个丢掉，所以这里换成自己的渲染路径。

## 排障

| 现象 | 先查 |
| --- | --- |
| dsh 里看不到工具 | 配置是不是漏了 `- insert:`；`name:` 有没有写成 `@deepseek-ai/dsh-mcp-client`；有没有彻底退出重启 dsh；`python server.py --tools` 能否列出 27 个 |
| `connection_error` | Origin 是否 ≥2021；`pip show originpro`；任务管理器里 `Origin64.exe` 是否只有一个 |
| 报"实例超过上限" | `origin_instances` 看有几个、哪些没窗口；`origin_reclaim` 关残留的后台实例；有窗口的那些要用户自己关（可能有未保存的东西） |
| `import_empty` | 先 `origin_read_file` 看结构；空格/多行表头已由自研解析器处理，走到这个报错说明文件本身没有数值行 |
| `com_timeout` | Origin 窗口是否卡在对话框/许可证弹窗 |
| `无效指针` | 页面攒太多：`origin_close_pages`，或 `origin_exit` 让它重开 |
| 图片没出来 | `origin_inspect` 看 `plots` 是否为 0；中文/空格/长路径实测可用，不用刻意改 ASCII |
| 图是空的但没报错 | 看返回里的 `proof_level`；`plot_axis_degenerate` 会主动抛出 |
| `bad_path` | 路径本身不行：把目录当文件名、盘符没映射、UNC 主机不可达、或路径里没有真正的文件名。换可写的绝对路径，别原样重试 |
| `target_locked` | 目标文件已存在且不可写（只读属性或正被 Origin/图片查看器占用）。去掉只读或换名，桥不会覆盖它 |
| 导出报了 `export_failed` 但目录里有张图 | 看 `warnings`：文件名里的 `#` 会被 Origin 换成编号，桥认出后会改名回你要的名字；认出多个候选时不会猜，直接报错让你换名 |
| `ERR_PNPM_NO_MATCHING_VERSION`（装不上） | 镜像滞后，不是没发布：`npm config get registry` 看一眼，加 `--registry=https://registry.npmjs.org/` 或改用 `file:` 离线包 |

## 给维护者

```
dsh-origin-bridge/
├── server.py            # 同步 stdio JSON-RPC，27 个工具的分发与 schema
├── originlab.py         # 引擎：COM 专用线程、句柄表、每个操作及其读回验证
├── asciiio.py           # 健壮的 ASCII 读取（分隔符嗅探 / GBK / 多行表头）
├── binio.py             # 工作站 .bin：单测量通道 + 方法参数重建电势轴
├── templates_catalog.py # 40 个科研图表预设（纯元数据）
├── cordis.patch.yml     # dsh bundle 补丁（serverName: origin_bridge）
├── install_dsh.ps1      # 一键装入 dsh（备份 + 写 profile 层 + 自检）
├── icon.svg             # 卡片图标（scripts/make_icon.py 生成）
├── locale/*.json        # dsh 卡片显示名与描述
├── scripts/make_preview.py # 重新生成 docs/preview.png（关掉图例后才导出）
└── test_*.py / smoke_test.py / edge_checks.py   # 420 项离线与单元检查（随包发布）
```

开发侧另有 `cases/` + `run_cases.py` 的 1135 例全链路题库和 `render_realdata.py` 的真实数据逐文件出图脚本，两者都不随包发布（见上文）。

重新生成图标：`python scripts/make_icon.py`（只需要 numpy，PNG 由 stdlib 手写）。

## 商标声明与许可

本项目是独立的第三方工具，与 OriginLab Corporation **无关联、无合作、未获认可**。Origin、OriginPro 为 OriginLab Corporation 的商标；本工具通过 OriginLab 官方公开发布的 `originpro` Python 包以正常方式使用其产品，未分发其任何二进制、图标或文档。

MIT © Origin Bridge Contributors

## 常见问题

### dsh-origin-bridge 是什么？

在对话里驱动本机 Origin / OriginPro：导入 \.dat/\.csv/\.txt 与电化学工作站 \.bin，绘图、拟合、改样式，导出图片并保存可在 Origin 里继续编辑的 \.opju 工程。dsh 插件，27 个 MCP 工具。第三方工具，与 OriginLab Corporation 无关联、未获认可。

### 如何安装 dsh-origin-bridge？

在项目目录运行 npm install dsh-origin-bridge，或使用 pnpm add / yarn add / bun add。它还提供命令行工具 origin-bridge，可以用 npx origin-bridge 直接运行。

### dsh-origin-bridge 支持 TypeScript 吗？

该包的 package\.json 没有声明类型入口。需要类型提示时，可以在 npm 上查找社区维护的 @types/dsh-origin-bridge 是否存在。

### dsh-origin-bridge 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### dsh-origin-bridge 可以商用吗？

dsh-origin-bridge 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### dsh-origin-bridge 还在维护吗？

最新版本 0\.4\.10 发布于 2026/11/03（今天）；至今共发布 12 个版本，其中过去 12 个月发布了 12 个；首次发布于 2026/10/09。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

