linanbuan/dsh-dwg-preview ↗★ 0

@local/dsh-dwg-preview

侧边栏 DWG/DXF 预览:宿主侧用 LibreDWG(WASM) 把 DWG 转成 DXF 并渲染为矢量 SVG,客户端在文档预览标签里显示。 A DSH sidebar preview for DWG/DXF drawings: the host half decodes DWG with LibreDWG (WASM) and renders vector SVG, the browser half shows it in the document preview tab. 利用WASM解码并在右侧标签页渲染SVG矢量图,适合需要直接查看工程图纸的用户。

パッケージ
@local/dsh-dwg-preview
互換性
未検証
Cordis ピア範囲
^4.0.1
バージョン
1.0.0
ライセンス
MIT
最終更新
2026/10/11

インストール

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:linanbuan/dsh-dwg-preview

ドキュメント

README 全文を読む ↗

DWG 预览 · dsh-dwg-preview

中文 | English

DeepSeek Harness(DSH)桌面端的文档预览插件:在工作区里点开 .dwg / .dxf 图纸, 右侧预览标签直接显示矢量图形 —— 可拖拽平移、Ctrl/⌘ + 滚轮缩放、一键适应宽度。

DSH 自带的预览只认识文本与图片,DWG 图纸点开只会是「无法预览」。这个插件补上那一步:宿主侧用 LibreDWG(编译成 WebAssembly)把 DWG 解成 DXF,再用本仓库里零依赖的渲染器 lib/dxf2svg.mjs 画成一张自包含的 SVG;浏览器侧只负责显示这张图,不解码、不加载任何第三方查看器库。DXF 是纯文本,不需要 LibreDWG,直接进渲染器。

示例图纸

上图由 test/sample.dxf 生成(node tools/make-example.mjs),覆盖下面列出的图元类型。

它做什么

  • 位置:工作区文件树里点开 .dwg 或 .dxf,右侧文档预览标签里的内容就是它 —— 槽位 sidebar.right.tab.document,body 键 @local/dsh-dwg-preview/dwg。
  • 行 id:dwg-preview(见 cordis.patch.yml)。插件不注册设置页,所以没有 order。
  • 接管方式:向官方 sidebar-documentpreview 插件提供的注册表登记一个渲染器,声明后缀 dwg 与 dxf、priority: 'extension'(按后缀认领)、loading: 'bytes-complete'(要整份文件,不是流)。其它后缀不受影响。
  • 工具栏:放大、缩小、适应宽度,以及当前图纸的图元总数;视图里可拖拽平移,Ctrl/⌘ + 滚轮缩放,普通滚轮仍然是滚动。
  • 宿主侧两条路由(挂在 connection 的共享 /api 通道上):
方法路径响应
GET/api/dwg-preview/ping200 {"ok":true,"plugin":"dsh-dwg-preview","cache":}
POST/api/dwg-preview/svg200 {"ok":true,"kind":"dwg"|"dxf","svg":…,"stats":…}
POST/api/dwg-preview/svg400 {"ok":false,"code":"EMPTY","message":"文件内容为空"}
POST/api/dwg-preview/svg413 {"ok":false,"code":"TOO_LARGE","message":"文件超过 64 MB,预览已跳过"}
POST/api/dwg-preview/svg500 {"ok":false,"code":"FAILED","message":…}
  • 依赖:浏览器半边只 require('react');DWG 解码需要 @mlightcad/libredwg-web(见文末许可)。

怎么工作

  1. 文档预览把文件的原始字节交给这个渲染器(content.kind === 'bytes')。
  2. 浏览器半边把字节 POST 到 /api/dwg-preview/svg,附带 x-dwg-name(百分号编码的文件名,只作旁注 —— 宿主的格式判定不看它)。
  3. 宿主按内容判定格式:前 8 字节以 AC10xx 开头是 DWG;前 8 KB 里同时出现 SECTION 与 HEADER/ENTITIES 是 DXF;都不是就报 FAILED。
  4. DWG 交给 LibreDWG(WASM)转成 DXF;DXF 直接进 dxfToSvg()。
  5. 渲染结果是一张自包含 SVG(viewBox + 背景 rect + 一个翻转 g),整份字符串返回给浏览器,浏览器用 Blob URL 显示。

为什么要绕这一圈:LibreDWG 的 wasm 约 9.5 MB,而 DWG 解码是 CPU 密集的同步调用。放进宿主进程,页面侧只处理一张图片;放进页面,每个预览标签都要为它付一份代价。DXF 路径是纯函数,所以能完全离线测试。

渲染器覆盖的图元:LINE、LWPOLYLINE(含 bulge 圆弧)、POLYLINE + VERTEX、CIRCLE、ARC、ELLIPSE、TEXT、MTEXT、ATTRIB、INSERT(嵌套块、行列阵列、旋转与缩放)、HATCH(多段线边界与边边界:直线、圆弧、椭圆弧、拟合点)、SOLID / 3DFACE、POINT、DIMENSION(走它的匿名块)。列表之外的实体被忽略,不报错也不画。

刻意的取舍

  • 范围:优先用图纸自己的 $EXTMIN / $EXTMAX;缺失或退化时用模型空间坐标的原始跨度。块内几何不参与范围计算 —— 有些块带着远在图纸之外的垃圾几何,否则会把视图撑坏。最后加 2% 内边距。
  • 垃圾几何过滤:块内实体(嵌套深度 > 0)的世界坐标足迹若超过图纸对角线的 3 倍,或超过 0.8 倍且中心落在「图纸 ± 0.6 倍跨度」的裁剪带之外,则整条丢弃;INSERT 阵列落在这条带外的肢体也不画。丢弃数量记在 stats.__stray(图元级)与 stats.__skippedOutside(总数,含被丢掉的阵列肢体),界面不显示。
  • 图层颜色取绝对值:libredwg 写出的 DXF 把每个图层颜色都写成负数(DXF 里表示「图层关闭」),所以这里一律忽略符号、只看颜色号。代价是不实现「按颜色关闭图层」,只有 70 位 1(冻结)的图层会被跳过。
  • 曲线:bulge 段转成 SVG 的 A 命令;ARC 用一段 A;HATCH 的圆弧边界按每 10° 一段折线近似(单条最多 64 段),椭圆弧按每 15° 一段。
  • 文字:TEXT / ATTRIB / MTEXT 都放在翻转组里再用 scale(1 -1) 翻回来,所以字是正的。MTEXT 只做有限清洗(\P 换行、\A…; 一类格式码、{}、\~),格式与堆叠分数会丢;不嵌入字体,字形靠系统字体(SimSun, 'Microsoft YaHei', sans-serif),换机器字形会变;不做折行与分栏,也不做 SHX 大字体。
  • 不画线型与线宽:全部是实线,靠 vector-effect="non-scaling-stroke" 在任何缩放下保持 1 像素。
  • 只读:没有实体拾取、没有图层开关、没有标注编辑、没有打印导出。

顺带说清楚计数口径:预览页上的「N 个图元」是渲染器各类型计数之和;ATTRIB 在图元遍历与图元渲染两处各计一次,所以含属性块的图纸会多算一个。它只是显示口径,不影响绘制。

安装

本插件是一个标准 DSH bundle:package.json 里声明了 dsh.bundle.patch 与 dsh.client,装进任何 profile 都走同一条路。先装好 DSH 桌面端(deepseek.com/download),然后下面任选一种。

方式一 · 在 DSH 桌面端里装(推荐)

  1. 侧栏点 插件 → 添加插件(Add plugin);
  2. 在输入框里填下面任意一种 spec:
    • 本仓库地址:https://github.com/linanbuan/dsh-dwg-preview
    • 本仓库的压缩包链接(GitHub 页面的 Source code (zip))
    • 已经克隆到本机的目录绝对路径
    • 包名(若将来发布到 npm)
  3. 点 安装:Harness 先读出这个 spec 指向什么(包名、简介、版本),确认后开始装;
  4. 装完点 立即启用。

整个安装由 Harness 自己的插件管理器完成:把这个包写进当前 profile 的 dsh.profile.bundles,并挂上宿主半边。桌面端装插件不需要命令行,也不需要手改 profile 文件。

方式二 · 让 Agent 自己装

把仓库地址或本机目录直接交给任意会话里的 Agent,它会调用 plugin_manager 工具(action: install_bundle)走同一条路。这是官方给第三方插件推荐的安装方式。

方式三 · 命令行

装了 dsh CLI 的机器上:

dsh plugin --profile 
 add 

`` 与方式一相同(包名、Git 地址、压缩包链接或本地路径);参数以 dsh plugin --help 的实际输出为准。

方式四 · 手工装进某个 profile(排障用,不推荐)

profile 目录默认在 ~/.dsh/profiles/ 。把包放进去,并在 profile 自己的 package.json 里登记两处:

"dependencies": { "@local/dsh-dwg-preview": "link:" },
"dsh": { "profile": { "bundles": [ "…", "@local/dsh-dwg-preview" ] } }

手改 profile 文件要过权限审批,写坏会让 Harness 起不来,所以正常安装请走前面三种。手工安装时依赖不会自动装:宿主半边需要 @mlightcad/libredwg-web,你要自己把它装进 profile(方式一至方式三由 Harness 按 dependencies 装)。

装上之后

  • 浏览器半边:注册 documentPreviews 渲染器与 sidebar.right.tab.document 的 body,随启动图加载;由 HMR 更新时不需要刷新页面。
  • 宿主半边:注册两条 /api/dwg-preview/* 路由。安装时若提示需要重启,重启 DeepSeek Harness 应用(不是刷新页面)—— 桌面 profile 的 base 组合包以 root: [] 启用 HMR(只监听显式注册的配置),而 HMR 的忽略规则默认含 **/node_modules,装在 node_modules 下的插件代码不在监听范围内。
  • 注册表要先在:渲染器登记到官方 @deepseek-ai/dsh-client-ui-sidebar-documentpreview 插件提供的注册表上(dsh.client.inject 声明了这份加载顺序)。注册表还没就绪时,浏览器半边会在 6 秒内每 150 毫秒重试一次(最多 40 次),不会静默丢掉渲染器。
  • 第一次看 DWG 会慢一点:宿主懒加载 LibreDWG 的 wasm(约 9.5 MB)并只建一次实例,之后复用。
  • 确认它在跑:侧栏 插件 → 找到这个包,看组件状态是否为「运行中」;也可以在会话里请求 GET /api/dwg-preview/ping。

停用 / 卸载

临时关掉(在 profile 自己的 cordis.patch.yml 里,或直接用插件页那一行的开关):

- id: dwg-preview
  disabled: true

彻底移除:插件页 → 这个包 → 卸载,它会把包从 profile 的依赖与 dsh.profile.bundles 里去掉。

注意事项

  1. 本插件不是官方插件:与 DeepSeek AI 无任何关联,不提供服务承诺。它跟随 Harness 的插件接缝工作,Harness 升级后可能失效;失效时预览以错误码或「预览失败」的形式退化,不会影响 Harness 本身启动。
  2. 安装后不会自动更新:官方插件管理器目前不支持自动更新,升级=先卸载再装新版。手动替换已安装的包之后,宿主半边要重启应用才会加载新代码(Node 的 ESM 缓存按文件 URL 命中,停用再启用只会重跑旧模块的 apply());浏览器半边在开发时由 HMR 更新。
  3. 平台:只要 DSH 能跑(Windows / macOS / Linux)即可用。DXF 路径是纯 Node 代码;DWG 路径依赖 @mlightcad/libredwg-web 的 wasm,同样跨平台。本插件在 Windows 桌面端 + DSH 0.1.7-rc.2 上实测过。
  4. 用到的内部接缝不是稳定 API:connection.fetch.register、slots.register、locale.register 与 documentPreviews 注册表都是 Harness 的内部形态,官方没有兼容承诺,版本升级后可能改名或改形。
  5. 权限:只读预览。除了在插件目录追加一行 .mount.log 挂载标记(已 gitignore,只为排障),它不写任何文件;不读你的会话、不读凭据、不联网、无遥测。
  6. 数据在哪:文件字节只在本机宿主进程里处理 —— 读进内存、解码、渲染,随请求结束释放;渲染结果按 sha1 缓存在内存里(最多 6 张,进程退出即消失)。浏览器半边拿到的只是 SVG 字符串。任何内容都不会上传,也不会写到磁盘。
  7. 一起分发要付 GPL-3.0 的代价:DWG 解码依赖 @mlightcad/libredwg-web(GPL-3.0),它由 Harness profile 按 dependencies 单独安装,本仓库不分发它。若你把本插件与它打进同一份 bundle 再分发,这份组合受 GPL-3.0 约束;本仓库自己的代码是 MIT。
  8. 失败是退化,不是崩溃:空文件 400 EMPTY、超过 64 MB 413 TOO_LARGE、格式认不出或解码失败 500 FAILED,界面显示「预览失败」与原因;路由把异常都收在响应里,不会把宿主进程带崩。
  9. 没有破坏性操作:它不删除、不覆盖、不改名任何文件,也不修改 profile 配置;唯一的写入是那一行挂载标记。卸载就是移除包本身。
  10. 64 MB 是请求体上限:超过就不解码,直接 TOO_LARGE。上限以内的大图纸仍会占用宿主进程的内存,并让它同步忙上一会儿(渲染是同步的)。
  11. 不支持编辑类功能:没有实体拾取、图层开关、标注编辑、打印导出。渲染器认不出的实体会被静默忽略,所以「画出来了但少东西」时先对照上面那份图元清单。
  12. 隐私:本仓库不含任何密钥、账号标识或本机路径;示例图纸 test/sample.dxf 是为这个仓库手写的,不含任何真实图纸数据。

自检

node test/selfcheck.mjs      # 或 pnpm test
node tools/make-example.mjs  # 重新生成 docs/example.svg(或 pnpm run example)

自检不需要网络、不需要 Harness、不需要 React、不需要安装任何依赖,纯 Node 跑完 31 项,全部通过时打印 31/31 checks passed 并以 0 退出,任何一项失败就以非 0 退出(可直接接 CI)。注意 pnpm test 会走 pnpm 自己的流程:它第一次会按 dependencies 真的装一遍依赖(于是写 node_modules/ 与 pnpm-lock.yaml,两者都在 .gitignore 里、不随仓库分发),装上 @mlightcad/libredwg-web 之后,DWG 那一项检查会自动跳过并打印原因 —— 它只能在装好依赖的 profile 里验证真实解码。它做的事:

  • 用真实渲染器渲染 test/sample.dxf,逐类型钉住 stats(LINE 11、CIRCLE 5、ARC 1……)与 SVG 结构:viewBox 的具体数值、唯一的根 g、translate(0 80) scale(1 -1) 翻转、non-scaling-stroke、标签成对闭合;
  • TEXT / MTEXT / ATTRIB 各一个 ``(共 4 个)、都在翻转组里翻正、XML 特殊字符被转义;
  • 用合成图纸验证垃圾几何过滤(stats.__stray)、图层颜色取绝对值、冻结图层被跳过、空图纸抛错;
  • 驱动 client.js:后缀(dwg/dxf)、priority: 'extension'、keyed body(sidebar.right.tab.document + @local/dsh-dwg-preview/dwg)、zh/en 字典键一致、样式只装一次,并断言 require 只被 'react' 调用过(一旦它去 require 任何官方客户端包,自检当场失败);
  • 用桩 fetch 真跑一遍视图:字节以 POST 发到 /api/dwg-preview/svg、x-dwg-name 是百分号编码、返回的 SVG 变成 Blob URL、图元总数显示为 31 个图元;放大 / 缩小 / 适应宽度、Ctrl + 滚轮、拖拽平移各自的结果都断言到百分比与滚动偏移;
  • 用假 ctx 驱动 index.js 的 apply() 并真调用两条路由:ping、DXF 往返(同一份文件第二次走缓存)、空体 400 EMPTY、垃圾内容 500 FAILED、超过 64 MB 413 TOO_LARGE(用零填充的 SharedArrayBuffer 视图把请求体做到 64 MB + 1 字节,不复制、不占 64 MB 内存)、以 AC10xx 开头的体走 DWG 分支;
  • 断言打包约束:package.json 的 license / repository / keywords / files 覆盖树里每个文件、engines.node 且没有 engines.dsh;.gitignore 覆盖 node_modules/、*.log、.mount.log;两份 README 的章节、代码围栏与表格行数一致;全树扫描没有密钥、没有个人绝对路径、版权人姓名只出现在 LICENSE / package.json / 两份 README 里。

它不能证明什么,说清楚:

  • DWG 解码本身:@mlightcad/libredwg-web 是本仓库之外的运行时依赖(见许可),这里只验证「以 AC10xx 开头的内容会走 DWG 分支,且依赖缺失时如实报 FAILED」。真正的 DWG 解码只能在装好依赖的 profile 里试;自检检测到该依赖存在时会跳过那一项并打印原因。
  • 真实浏览器:浏览器半边跑在桩 __ModuleLoader__ 与一个最小 hook 运行时上,不是 DOM,也没有官方注册表,因此字体、paint 与 Blob 的真实语义都没被覆盖。
  • 官方接缝的行为:documentPreviews、slots、locale、connection.fetch.register 在自检里都是替身;HMR 与重启规则也无法在这里验证。
  • 字形与线型:SVG 依赖系统字体,自检只检查属性,不检查字形。

结构

package.json           bundle 清单(dsh.bundle.patch + dsh.client)
cordis.patch.yml       一行 insert:装载宿主半边
index.js               宿主半边:两条 /api/dwg-preview/* 路由、内容判定、sha1 缓存
client.js              浏览器半边:documentPreviews 渲染器 + sidebar.right.tab.document 视图(手写 lazy CJS,只用 react)
lib/dxf2svg.mjs        零依赖的 DXF → SVG 渲染器(dxfToSvg(text, options))
icon.svg               插件图标
docs/example.svg       由 test/sample.dxf 生成的演示图(本文顶上那张)
test/sample.dxf        样例图纸:覆盖上面列出的 14 类图元
test/selfcheck.mjs     自检(31 项)
tools/make-example.mjs 重新生成 docs/example.svg

致谢与第三方许可

  • @mlightcad/libredwg-web(GPL-3.0):DWG 解码依赖,即 LibreDWG 的 WebAssembly 封装(上游 LibreDWG 同样是 GPL-3.0)。它是 Harness profile 按 dependencies 单独安装的 npm 包,本仓库不分发、不内置、也不把它打进任何产物。把本插件与它打包在一起分发,这份组合就受 GPL-3.0 约束;本仓库自己的代码仍是 MIT(见 LICENSE)。
  • 没有复制的第三方插件代码:index.js、client.js、lib/dxf2svg.mjs 与 test/sample.dxf 都是为这个插件写的;ACI 颜色表的取值来自 DXF 规范本身。
  • 用到的 Harness 接缝:connection、slots、locale 服务,以及官方 sidebar-documentpreview 插件提供的 documentPreviews 注册表。浏览器半边自己不 require 任何官方客户端包,也不加载任何第三方查看器库(没有 three.js、没有 dxf-parser、页面里没有 wasm);dsh.client.inject 只是声明加载顺序,不是代码引用。

License

MIT © 2026 linanbuan