Files
Leantime/dev/开发文档.md

11 KiB
Raw Permalink Blame History

2D / 3D 在线查看器 · 开发文档

更新2026-08-21。本文覆盖 dev/ 目录下的自研查看器(不含 2D/、3D/ 里的新迪官方案例)。

一、项目结构

dev/
├── index.html              ← 统一入口:落地页 + 2D/3D 数模库 + 嵌入预览弹窗
├── library.php             ← 数模库元数据接口GET 读 / POST 保存)
├── library.json            ← 数模库记录(上传自动收录 + 手动增删改)
├── DWGViewer/              ← 2D 图纸查看器(.dwg / .dxf
│   ├── index.html / app.js / style.css
│   ├── js/                 ← source.js(解析) → flatten.js(展平) → render.js(Canvas2D) → tools.js
│   ├── libs/dxf-parser(MIT)  libs/libredwg-web(GPL-3.0, 仅打开 .dwg 时动态 import)
│   ├── samples/            ← XQD601.dwg、patent.dwg、pulley.dxf、cushion.dxf
│   └── api/                ← token.php / model.php / upload.php防下载三件套
└── STEPViewer/             ← 3D 模型查看器(.step / .stp浏览器内 WASM 直读,无云服务)
    ├── index.html / app.js / style.css
    ├── libs/three.js + occt-import-js0.0.23
    ├── samples/            ← as1-oc-214.stp 等
    └── api/                ← 同款三件套SECRET 与 DWGViewer 不同)

二、本地开发

  • 服务器python http.server 脚本(%TEMP%\nds_server_step.py 等),root 指向 dev/ 端口 8920;日志 %TEMP%\req_log_*.txt
  • 打开 http://127.0.0.1:8920/(入口)或直接开 DWGViewer/index.html?file=samples/XQD601.dwg
  • 本地没有 PHP:查看器会自动退回直连(判断依据是接口响应的 content-type 是否为 application/json——python 会把 token.php 源码当静态文件 200 发回来,只看状态码会误判); 数模库只读(增删改走 library.php 会 501 并给出明确提示)
  • 改完 JS 用 node --check app.js 做语法检查

三、架构要点

2D 数据流source.js(解析+归一化libredwg 全给弧度dxf-parser 混用——统一成弧度) → flatten.js展平成世界坐标显示列表INSERT 递归烘焙变换,只做一次;缩放平移只重算 屏幕坐标 → 2.9 万实体 6ms/帧)→ render.jsCanvas2D支持旋转/三种背景/线宽)。

3D 数据流:拖入/?file= 取到 STEP 字节 → occt-import-js WASM 转换 → three.js 场景 MeshPhongMaterial + Ambient/2×Directional30° 法线平滑)。零件树按 mesh 名分组, S.meshEntryMap 做 mesh→树行精确映射视口点选用它不要按树顺序 first-match

入口分发dev/index.html

  • 按扩展名路由dwg/dxf → DWGViewerstep/stp → STEPVieweriframe 加载
  • 本地文件postMessageviewer-ready 握手后发 viewer-file只传 ArrayBuffer 不落地)
  • 路径换算优先「查看器目录内」HEAD 探测),入口目录兜底;选中候选后要换算成 相对查看器的路径,否则双目录 404
  • 参数透传:embed=1bg= 必须显式转发给查看器(入口 URL 不会自动带过去)

嵌入模式?file=路径&embed=1 隐藏顶栏3D 另强制高精度);bg=grey|beige|black 定背景。 嵌入地址规则:私有目录文件用裸文件名?file=xxx.encweb 目录内文件写相对路径 samples/xxx.stp../ 逃出查看器目录的路径会被令牌通道拒绝(防目录穿越)。

四、防下载令牌三件套PHP 部署后自动生效)

查看器 fetch → api/token.php同源校验 + HMAC 5 分钟令牌)
            → api/model.php验令牌 → .enc XOR 解密 / 原文件原样 → gzip 传输)
上传POST api/upload.phpX-Filename 头;客户端 gzip → 服务端再 gzip → XOR 加密落盘)
  • SECRET:同一查看器内 token/model/upload 三处一致DWG 与 STEP 的 SECRET 不同
  • 私有目录$PRIVATE_DIR = '/volume1/stepviewer_private'DWG 为 dwgviewer_private—— 模型存 web 目录外web 里根本没有文件可下载model.php 按 basename($f) 在私有目录找, 找不到时退回 web 目录(留在 web 目录里的样例仍可按原路径用,但可直链下载)
  • 落盘名STEP = 原名+8 位随机 hex+.encDWG = 纯随机 hex.enc原文件名藏加密体内 model.php 用 X-Filename 头带回)
  • 客户端 apiRequestSTEP.php 必须拼在 ? 之前(曾经拼成 api/token?f=xxx.stp.php 导致 PHP 部署下令牌流程整体失效——血泪教训)
  • 接口未部署自动退回直连;接口在跑但拒绝时明确报错,不悄悄回退
  • 网页端无法绝对防抓包,此方案挡的是直接下载、批量爬取、链接外传猜名

五、数模库(首页库表)

  • 记录结构:{path, name, type(2d|3d), note, time}library.json(入口目录)
  • path=链接核心)只在新增时可填、之后锁定 🔒name/type/note 可编辑;删除只删记录
  • library.phpGET 返回全量(静态 JSON 也能读,无 PHP 只读可用POST 整体保存 清洗path 拒绝 ..、限制字符长度LOCK_EX 写文件Sec-Fetch-Site 弱校验
  • 自动收录:两个查看器 upload.php 成功后调 add_to_library()../../library.json 同 path 更新、否则插最前),并 postMessage library-updated 让入口刷新
  • 库表功能:打开(按 type 进对应查看器、复制链接embed=1 完整地址)、预览 (弹窗内 iframe 以 embed=1 直载查看器 + 复制 iframe 代码)、编辑、删除、新增、 搜索(名称/路径/备注)、类型筛选

六、手机适配≤768px

  • 2D 图层 / 3D 模型树:桌面默认显示(按钮 toggle 隐藏);手机变抽屉 (默认隐藏,点「图层」/「结构树」弹出,抽屉内 ✕ 或点空白处收起)
  • 顶栏收起 logo/文件名;底部工具栏横向滚动、图标缩小;浮动面板限宽可滚动
  • 首页:库表外层 overflow-x:auto 横向滚动、预览弹窗全屏
  • 实现要点:
    • 抽屉 = body.tree-open 类 + transform: translateX(-100%→0) + transition
    • 「点外部收起」的 document click 监听会把打开按钮的那次点击也当外部点击—— 按钮 handler 必须 stopPropagation()
    • 媒体查询必须写在被覆盖规则之后(同特异性后写生效;放前面会被基础规则吃掉)
    • STEP 的 #viewport left 是内联样式(会压过 CSS手机态要 style.left=''

七、测试

playwright-core + 系统 Edgeheadless脚本在 %TEMP%\nds3dtest\

const { chromium } = require('playwright-core')
const browser = await chromium.launch({
  executablePath: 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
  headless: true, args: ['--enable-unsafe-swiftshader', '--disable-gpu', '--ignore-certificate-errors'],
})

关键脚本:embed_matrix3.js(嵌入矩阵)、dwg_php_sim.js / route 拦截模拟 PHP 令牌流程、 prod_final_all.js(生产回归)、mobile_test2.js(手机行为)、diag_*.js(诊断)。

测试坑:

  • 加载断点别用 #fileName 默认文本(非空,会提前 break 假失败)——等含「实体」/「加载完成」 或 errBox 出现
  • 嵌入模式下顶栏隐藏,#btnEmbed 点不到——测嵌入面板用非嵌入模式
  • playwright 点击偶报「tree 拦截指针事件」而 elementFromPoint 实际命中按钮——加载完 等 1.5s 再点 + fallback 坐标点击
  • headless 全新加载测不出缓存问题——线上用户复现的白屏往往是缓存混版(见下)

八、⚠️ 缓存版本号(每次改文件必做)

两个查看器的 index.html 用固定 ?v= 引用 app.js/style.css。任何一轮修改都必须升级 版本号当前DWG app.js?v=2/style.css?v=2STEP app.js?v=5/style.css?v=5 否则用户浏览器缓存新旧混合(旧 index.html + 新 app.js 引用新 DOM 元素 → 模块崩溃白屏)。 跨文件 DOM 引用(如 #btnTreeClose)一律加 null 容错。

九、已踩过的坑(速查)

2D

  • 大半径圆弧包围盒按实际弧段不能按整圆R=49000 的过渡弧会把图纸撑大 20 倍)
  • HATCH 的 offset(DXF 45/46) 是已旋转到世界系的位移向量ANSI31 等预定义图案要内置图案表现场展开
  • 文档内单位一律弧度;$INSUNITS 国内图纸常年默认值,只认显式公制,默认 mm
  • dxf-parser 的 dist 是无扩展名相对 import + 依赖 loglevelimportmap 指到本地 shim
  • libredwg 的块表在 db.tables.BLOCK_RECORD.entries[],模型空间取 *Model_Space
  • 该 wasm 构建禁用了 DXF 读写(错误码 2048alpha:false canvas 要先画一次白底

3D

  • occt 0.0.23 的 attributes.position/index{array} 包装(转 TypedArray树节点字段是 meshesbrep_faces{first,last,color}[]
  • 顶点颜色必须 Color.setRGB(..., THREE.SRGBColorSpace),否则模型洗白
  • ⚠️ 曾试 PBR(MeshStandard+RoomEnvironment+ACES) 被用户否掉(上色被洗淡),不要再加 色调映射/环境反射;过暗 CAD 面色(<0.3)自动提亮到 0.35 保色相
  • 剖切平面必须按模型包围盒中心重算(固定原点切不到)

PHP/接口

  • PHP 没执行时 token.php 返回源码 200——判部署看 content-type 是否 JSON
  • 群晖 Web Station 的 PHP 按「网站门户」逐个启用,新域名不会自动继承
  • 同源校验Sec-Fetch-Site 优先(不受父页面 Referrer-Policy 影响、跨站 iframe 嵌入可用), Referer 兜底;直链/curl 两者都过不了

许可DWGViewer 的 libredwg-web 是 GPL-3.0(前端分发有传染性,商用需评估);换服务端 dwg2dxf / ODA SDK 只需替换 source.jsloadDwg()

十、部署(群晖 NAS Web Station生产 3d.ruiguo.wang

  1. dev 内容放站点根目录3d.ruiguo.wang 根即入口);本地 FTP 目录与生产双向同步
  2. Web Station → 网站门户 → 该站点 → 编辑 → PHP 选 PHP 8.x(不选=纯静态返回源码)
  3. File Station 在 web 目录外建 /volume1/stepviewer_private/volume1/dwgviewer_privatehttp 用户组读写权限
  4. 两个查看器 api/ 的 $SECRET 已设为随机值(同查看器三处一致)
  5. nginx 下 .htaccess 不生效——防下载靠私有目录方案,不需要 nginx 规则
  6. 验证清单token.php 返回 JSON上传返回裸文件名直开 .stp/.dwg 已不在 web 目录; 嵌入链接能打开;库表增删改持久化

另外 ftp.ruiguo.wang/上海新迪3D/dev/ 也是同一套文件PHP 未启用), 上传的加密模型在那里打不开属预期(只读回退),正式使用请走 3d.ruiguo.wang。