← 返回项目
Industrial Web3D

3D 装配仿真平台

工业级 Web3D 装配仿真工程:Vue3+Babylon 实时装配、SimEngine 门面、同一份干涉算法前后端复用、性能治理与可观测。

Vue3Babylon.jsDraco/MeshoptBVH + OBB-SAT
12 → 3MBGLB 体积 -75%
<200ms200 零部件检测
99.9%核心可用性目标

3D 装配仿真平台

工业级 Web3D 装配仿真工程:浏览器实时装配 + 服务端离线预检,同一套算法驱动两端。覆盖 SimEngine 门面、干涉分析两阶段、模型轻量化、性能治理与工程化交付。

平台架构总览

产线 3D 装配仿真平台 · 工业级架构总览

接入层统一鉴权与限流路由 · 前端经 SimEngine 门面访问 Babylon(业务不直引引擎)· 后端无状态微服务多副本 · 数据层 MySQL 主从 + Redis + CDN · 安全/可观测/质量保障纵切。


技术选型:为什么 Babylon.js 而非 Three.js

  • 渲染:Vue3 + Babylon.js(非 Three.js)
  • 理由:① Vue3 + TS 强类型技术栈,Babylon 本身就是强类型 TS 引擎;② 装配仿真对碰撞检测和装配约束需求重,Babylon 内置碰撞系统、父级关系树、glTF 管线更全;③ 企业级复杂工程场景,官方 Inspector 场景调试器显著降排查成本

⚠️ 简历自洽口径:核心能力写「Vue3+Babylon.js 为主,熟悉 Three.js、原理通用」;别把两个引擎的 API 混着说(会暴露「只背了博客」)。


仓库分层:Monorepo 单向依赖

工程是 pnpm Monorepo,三种角色分目录,依赖方向强制单向

flowchart TD
    subgraph Apps["apps/ · 浏览器应用(Vue3 + TS)"]
        SP["sim-platform<br/>仿真工作台<br/>经 SimEngine 门面访问 Babylon"]
        SA["sim-admin<br/>管理后台(规划)"]
    end

    subgraph Services["services/ · 后端微服务<br/>无状态 · 端口隔离 · 可独立启动"]
        GW["gateway :80<br/>子进程编排 + 静态托管 + 反代"]
        AS["assembly-svc :7101<br/>产线 / BOM / 装配约束 / 工艺步骤"]
        IS["interference-svc :7102<br/>服务端离线整线批量预检"]
        MS["model-svc :7103<br/>glTF 元数据 / 资产版本 / CDN 签名"]
        TS2["takt-svc :7104<br/>节拍计算 / 瓶颈识别"]
        AU["auth-svc :7105<br/>OIDC / RBAC / 审计"]
    end

    subgraph Packages["packages/ · 唯一可被前后端同时 import 的层"]
        DOM["<b>@assemble/domain</b><br/>领域类型契约(全工程单一事实源)"]
        CC["<b>@assemble/clearance-core</b><br/>干涉分析核心算法(前后端复用)"]
        SU["sim-utils / storage / http / observability"]
    end

    SP -->|HTTP| GW
    SA -.->|HTTP| GW
    GW --> AS & IS & MS & TS2 & AU
    SP -->|"① 前端实时判定"| CC
    IS -->|"② 后端离线复用<br/>同一份算法"| CC
    CC --> SU
    CC --> DOM

    style CC fill:#ffe6cc,stroke:#d79b00
    style DOM fill:#d5e8d4,stroke:#82b366

依赖方向强制规则(写代码必须遵守)

  • 业务入口(apps/services)不得被其它入口依赖
  • apps 之间、services 之间互不 import:服务间通过网关 HTTP 解耦,跨服务数据一律用 @assemble/domain 描述
  • 共享算法/类型只放 packages/,保证浏览器与 Node 同源、判定一致

为什么用 Monorepo:算法与类型要在浏览器和 Node 之间共享,同一个仓库可以直接以源码引用(TS 路径映射),无需发包、无需版本对齐——这是「前后端复用同一份算法」这个需求的直接结果。


SimEngine 门面:前端核心设计决策

业务组件禁止直接 import '@babylonjs/core',一律经 SimEngine 门面:

Vue3 业务组件层(views/stores/components)   ← 只依赖 SimEngine 公开接口
        │
   SimEngine Facade  (apps/sim-platform/src/engine)
        │  抽象:SceneManager / AssetManager / InteractionManager
        │        / AssemblyController / ClearanceController
        ▼
   @babylonjs/core(可替换引擎层,业务不 touch)

架构红线

  1. 业务组件禁止直接 import '@babylonjs/core';一律经 SimEngine 门面
  2. SimEngine 的接口稳定(对未来切 Three.js/升级引擎不敏感),实现细节隔离在门面内部
  3. 门面需可被 mock —— 单测装配控制器时注入假门面,不启动真实 WebGL

对外能力

能力职责关联 domain
实时装配自动/手动/回放三模式,约束贴合用 slerp 平滑过渡assembly.ts
实时干涉拖拽装配时的实时碰撞检测,命中即高亮/拦截clearance-core
产线运行态GLB 设备运动节点、物料沿 BOM 工位路径流转rhythm.ts + runtime

干涉分析:同一套算法前后端复用

同一套 @assemble/clearance-core 被两端复用,保证判定一致——这是本工程最具含金量的设计。

@assemble/clearance-core
  ├─ bvh.ts        # Broad Phase:BVH 空间索引(构建 + selfIntersect + queryAgainstSingle)
  ├─ obbSat.ts     # Narrow Phase:OBB-SAT,15 分离轴短路判定
  ├─ detector.ts   # ClearanceDetector 门面:交互式(增量)与离线(批量)两条路径
  └─ aabb.ts / obb.ts   # AABB 运算、AABB→OBB 派生
flowchart LR
    subgraph Core["⭐ @assemble/clearance-core<br/>同一份 TS 算法"]
        direction TB
        BVH["bvh.ts · Broad Phase<br/>BVH 空间索引"]
        SAT["obbSat.ts · Narrow Phase<br/>OBB-SAT 15 分离轴"]
        BVH --> SAT
    end

    subgraph FE["🖥 前端 sim-platform"]
        direction TB
        A1["交互拖拽 / 单步装配"]
        A2["queryInteractive<br/>BVH 树 vs 单件"]
        A3["实时红高亮<br/>+ 阻断非法放置"]
        A1 --> A2 --> A3
    end

    subgraph BE["🖧 服务端 interference-svc"]
        direction TB
        B1["整线全量预检<br/>/ 出干涉报告"]
        B2["loadAll + runFull<br/>BVH 自碰撞 selfIntersect"]
        B3["干涉清单 → 3D 定位<br/>→ 调整工位 → 复检归零"]
        B1 --> B2 --> B3
    end

    A2 -.->|"复用同一算法"| Core
    B2 -.->|"复用同一算法"| Core

两阶段判定(Broad + Narrow)

graph TD
  A["200 零件两两组合<br/>19,900 对候选"] --> B["Broad Phase<br/>BVH 粗筛"]
  B -->|包围盒相交| C["Narrow Phase<br/>OBB-SAT 精判"]
  B -->|不相交| E["排除,跳过"]
  C -->|"15 条分离轴短路"| D["判定干涉/无干涉"]
场景执行端路径为什么
交互拖拽、单步装配(实时)前端queryInteractive(BVH 树 vs 单件)需 <200ms 满足拖拽手感,本地零往返
整线全量预检、出干涉报告服务端loadAll + runFull(BVH 自碰撞)数千零件组合校验太重,放离线队列

OBB-SAT 15 条分离轴 = A 的 3 个面法线 + B 的 3 个面法线 + 9 条边对叉积(3+3+9=15)。

  • SAT 短路特性:15 条轴只要找到一条投影不重叠就立即返回「不相交」,不用测完——这是它快的原因,面试一定要说「短路」这个词
  • 为什么 OBB 不用 AABB:AABB 轴对齐,旋转件(机械臂/传送带)包不紧、误报多;OBB 跟着零件旋转仍贴合

为什么这个设计是加分项:前端要的是低延迟(拖拽手感,本地零往返),后端要的是吞吐与完整性(几千零件组合、可长时间跑)。把算法抽成 packages/clearance-core 后,两端调用的是同一份代码——判定逻辑、边界处理、数值精度完全一致,从根本上消除了「前后端结论不一致」这类最难排查的 bug。这是「工程正确性优先于实现便利」的典型取舍。

性能门禁:200 零部件 runFull < 200ms(不可退让),已固化为 CI 性能门禁。


模型轻量化:两道压缩互补

压缩器管什么优点代价场景
Draco压几何(顶点/索引/法线量化)体积降最多CPU 解压耗时体积敏感的大静态模型
Meshopt优化缓冲布局 + 顺带压缩又小又快、GPU 友好压缩率略低动态/反复加载的模型

为什么两个一起用(-75% 的关键):互补——Draco 把体积砍到底,Meshopt 保证砍完体积后 GPU 还能读得快。

首屏秒开四招:按需/懒加载(只加载当前视角必需)、资源池化 + InstancedMesh 复用、LOD 渐进加载、Web Worker 解压(Draco 解码不阻主线程)。

// Babylon.js 正确写法(别写 THREE.GLTFLoader/DRACOLoader)
import { DracoCompression } from '@babylonjs/core/Meshes/Compression/dracoCompression'
import { MeshoptCompression } from '@babylonjs/core/Meshes/Compression/meshoptCompression'
const { meshes } = await SceneLoader.ImportMeshAsync('', '/assets/', 'line.glb', scene)

装配约束与三模式动画

零件装配本质是约束求解——把子件贴合到父件的指定约束位:

  • 四种约束:对齐 Coincident、共面 Coplanar、同轴 Concentric、距离 Distance
  • 实现:维护父子层级树 + 约束参数表,用矩阵变换(平移 + 四元数 slerp)贴合
  • 三种模式
    • 自动装配:按 BOM/约束表自动贴合
    • 手动装配:拖拽 + 磁吸吸附,命中即高亮拦截
    • 步骤回放:关键帧倒放,时间轴可 scrub 拖动
  • 关键帧动画:每个零件一条轨道编进 AnimationGroup 统一播放;位置/缩放用 lerp,旋转用 四元数 slerp(避免万向锁)
  • 一键拆解(爆炸图):零件沿轴心分离、与装配动画互逆,共用同一套关键帧数据

性能治理体系

1. 性能预算分层

flowchart TD
    SLI["用户可感知目标<br/>仿真页面 3s 内可交互 · 拖拽跟手不卡顿"] --> B1["网络与加载预算"]
    SLI --> B2["渲染帧预算"]
    SLI --> B3["交互响应预算"]
    SLI --> B4["服务端接口预算"]

    B1 --> B1a["首屏 JS + CSS gzip < 800KB"]
    B1 --> B1b["模型资产首屏 < 3MB<br/>(12MB → 3MB,-75%)"]
    B2 --> B2a["稳定 60fps,单帧预算 16.7ms"]
    B2 --> B2b["掉帧帧占比 < 5%"]
    B3 --> B3a["拖拽干涉判定本地零往返<br/>单次 < 16ms"]
    B4 --> B4a["普通接口 P95 < 200ms"]
    B4 --> B4b["整线离线预检 < 200ms / 200 件"]

预算的三条铁律

  1. 预算是「总额」不是「单点」:模型资产省下来的额度不能被首屏 JS 吃掉——分层预算、分层守住
  2. 预算必须可自动判定:不能进 CI 的预算只是愿望。runFull < 200ms 之所以可信,是因为它在 merge 门禁里跑
  3. 预算超了要有取舍流程:不是「下次再优化」,而是当场决定——降 LOD?砍后处理?走分级渲染(Tier A/B/C)

2. 并发形态:三种混合负载分开打

负载类型特征主要瓶颈应对
静态与模型资产大文件、可缓存、读多写零带宽CDN + 强缓存 + 版本指纹(绕开源站)
交互式 API高频、轻量、低延迟事件循环与连接数无状态多实例 + 连接复用
CPU 密集批量任务整线预检/节拍仿真,秒级、吃满单核CPU(单进程单核)独立进程 + 队列 + 限并发,绝不同进程抢主线程

第一原则interference-svc 的离线预检是 CPU 密集,Node 单进程单事件循环意味着一个预检任务就能拖慢同进程的所有请求。所以它必须被隔离——这是本项目并发设计的第一原则。

3. 六层保护机制

flowchart LR
    U["浏览器<br/>SimEngine"] --> CDN["CDN<br/>静态资源 / GLB 资产"]
    U --> GW["gateway :80<br/>子进程编排 + 反代"]
    GW --> L1{"① 入口限流<br/>令牌桶·按 IP/租户"}
    L1 -->|超限| R429["429 + Retry-After"]
    L1 -->|通过| SVC["上游服务<br/>assembly/model/takt"]
    SVC --> POOL["② 连接池<br/>MySQL / Redis"]
    SVC --> Q["③ 任务队列<br/>整线预检 / 节拍仿真"]
    Q --> W["独立 Worker 进程<br/>限并发 = CPU 核数"]
    W --> BUD["④ 任务级预算<br/>超时 + 件数上限"]
    SVC -.->|连续失败| CB["⑤ 熔断<br/>快速失败 · 半开探测"]
    SVC -.->|DB 不可用| DG["⑥ 降级读<br/>内存/文件仓储"]

4. 瓶颈定位决策树

flowchart TD
    P["现象:卡 / 慢"] --> Q1{"范围?"}
    Q1 -->|"只有 3D 卡"| F1["前端渲染路径<br/>Performance / Spector.js / Stats.js"]
    Q1 -->|"接口慢"| F2["服务端路径<br/>分段耗时 + /metrics"]
    Q1 -->|"切换产线后越来越卡"| F3["内存泄漏路径<br/>堆快照对比"]

    F1 --> D1{"帧时间尖刺在哪?"}
    D1 -->|JS 执行长| D1a["长任务 → 拆帧/Worker"]
    D1 -->|draw call 高| D1b["实例化 / 合批 / LOD"]
    D1 -->|GPU 时间高| D1c["纹理 / 后处理 / 抗锯齿降级"]

    F2 --> D2{"哪一段最慢?"}
    D2 -->|"事件循环延迟高"| D2a["同步阻塞主线程<br/>CPU 密集混进交互进程"]
    D2 -->|"连接池等待"| D2b["池太小 / 慢查询占连接"]
    D2 -->|"下游服务慢"| D2c["看该服务 /metrics 与日志"]
    D2 -->|"网关自身慢"| D2d["反代/序列化/日志开销"]

    F3 --> D3{"堆快照增量对象"}
    D3 --> D3a["Babylon 资源未 dispose<br/>场景/材质/纹理/回调"]

四类高频陷阱

  1. 「服务端慢」其实是 CPU 密集任务混进了交互进程——本项目最可能的性能事故:预检跑在 gateway 子进程里,事件循环被 OBB-SAT 计算占满。修法是进程隔离,不是「优化算法」
  2. 「越来越卡」其实是资源没释放——切换产线只 dispose() 了 Mesh,漏了材质/纹理/AnimationGroup/事件回调/ResizeObserver
  3. 「接口慢」其实是日志与序列化开销——大对象 JSON.stringify、同步写日志、请求体全量打点
  4. 「缓存没生效」其实是 key 不稳定或缓存了不该缓存的——GLB 走强缓存必须带版本指纹,否则改了模型拿不到新版

工程治理覆盖

平台沉淀了完整的工程治理文档族:

文档内容
ARCHITECTURE分层架构、依赖方向红线、SimEngine 门面、干涉算法前后端复用、数据契约
PERFORMANCE性能预算分层、三种并发形态、六层保护、瓶颈定位决策树、性能回归门禁
SECURITYHS256 JWT、AES-256-GCM、RBAC 矩阵、审计哈希链、传输/静态加密
TESTINGVitest 单测 + 服务层集成 + 200 件性能门禁 + 趋势报告
OBSERVABILITYCounter/Histogram 指标库 + Prometheus 文本 + 全链路 X-Request-Id
HA多副本 + 故障转移 + 健康检查
DISASTER_RECOVERY备份策略与恢复 RTO/RPO
ALERTING滑动窗口告警 + 阈值可配 + webhook 推送
VERSIONINGSemVer + 里程碑(M0~M5) + 版本路线
DEPLOYMENTCloudBase 单容器聚合镜像 + Git 自动构建发布
FRESHCUT_ASSET_SPEC净菜设备 GLB 资产接入规范
HARDCODE_AUDIT硬编码审计清单

运维与交付治理要点

平台把「高可用 / 可观测 / 告警 / 容灾」拆成代码层前置件 + 部署层 checklist 两层推进(本机无 docker,容器编排留部署层):

① 高可用(HA)—— 代码层前置件已全部落地

能力实现面试话术
优雅停机SIGTERM/SIGINT → drain 3s → 关连接 → 退出滚动发布不切断在途请求
双探针分离/healthz liveness 恒 200,/readyz readiness 依赖不健康 503编排器标准 liveness/readiness 双探针
上游健康池gateway 内 UpstreamHealthPool(多副本轮询 + 故障摘流 + 5s 周期恢复)单容器内即表达「多副本 + failover」
进程自愈子进程异常退出指数退避重启(500ms→15s,上限 10 次),不再整体 process.exit(1)单机自愈,整体崩溃留给容器重启
副本配置化UPSTREAM_TARGETS env 逗号分隔 host:port不写死 127.0.0.1:7101–7105

② 可观测性(OBSERVABILITY)—— 轻量自研三件套

  • 自研 /metrics(Prometheus 文本格式,不引 Prometheus/OTel)—— Counter/Histogram 纯 TS 指标库,curl 直看,未来接 Prometheus 无缝迁移
  • X-Request-Id 透传跨服务调用,日志串成一条链路
  • 前端 SimMonitor SDK:Web Vitals(LCP/INP/CLS)+ FPS + 内存采样,节流批量上报 gateway /telemetry

③ 告警(ALERTING)—— 规则引擎 + 状态机 + webhook

flowchart LR
    A["健康池快照<br/>telemetry 直方图<br/>上游 /metrics"] --> B["规则引擎<br/>静态阈值 / 持续增长 / 健康状态"]
    B --> C["告警状态机<br/>P0/P1/P2 · 去重 · 冷却 · 恢复"]
    C --> D["webhook 推送<br/>企微 / 钉钉群机器人"]
  • 评估位置选 gateway 单点(单容器入口,天然聚合上游指标 + telemetry + 健康快照,零新增服务)
  • 防告警疲劳:去重 + 冷却 + 恢复通知(最小集,不做完整告警平台)
  • 未配置 ALERT_WEBHOOK_URL → 只记结构化日志,静默降级不报错

④ 容灾(DR)—— 部署层 checklist

维度口径
数据备份MySQL 主从 + 半同步,每日全量 + binlog 增量,PITR 时间点恢复
RPO / RTORPO ≤ 15min、RTO ≤ 30min
对象存储glTF 资产多区冗余 + CDN 多源容灾
恢复演练每季度 Restore Drill(不演练 = 没备份)

面试金句:HA 与容灾是两个互补方向——HA 管「单机自愈 + 摘流 failover」,容灾管「跨机/跨区数据不丢」。代码层前置件(优雅停机/双探针/健康池/退避重启)本机可闭环验证,容器编排(k8s/compose)属部署层交付物。


量化结果

  • GLB 体积 12MB → 3MB(-75%)(Draco + Meshopt 双压缩)
  • 200 零部件检测耗时 < 200ms(BVH 粗筛 + OBB-SAT 短路,已固化 CI 门禁)
  • 稳定 60fps / 单帧 16.7ms(Tier A/B/C 分级渲染适配低端集显办公机)
  • 新产线建模到上线 ↓50%,复用 3 类产线(净菜清洗 / 切配 / 果蔬)
  • 拖拽装配本地零往返(同一份 clearance-core 复用,避免前后端结论不一致)

一句话收尾:「这工程的核心不是算法本身,而是三件事——SimEngine 门面把引擎封装掉、同一份 clearance-core 被前后端复用、性能靠『预算 + 度量 + 门禁』守出来。」面试时把每一件事的具体证据(架构图、门禁值、门禁 CI 位置)准备好,比背任何 API 都管用。