跳至正文
Tooling — Pascal Editor 源码分析:建筑 Scene Graph、几何系统与 MCP 业务流程

Pascal Editor 源码分析:建筑 Scene Graph、几何系统与 MCP 业务流程

项目源码:pascalorg/editor

AI 参与说明(Agent:Codex、Cursor):2026-09-15 Codex 阅读公开源码并运行选定测试,整理技术栈、核心算法与编辑业务流程。依据提交 eacc2a939bec53529809b06f68123df2459b7961(2026-09-14),不将在线产品状态与该提交等同。运行记录:模型 gpt-6-astra,reasoning effort medium,执行入口 Codex Desktop。验证范围为源码、core 构建、选定自动测试与房间识别示例。同日 Cursor Cloud Agent 另写架构长稿(未合并的 draft PR #13),覆盖 monorepo 分包、Zustand + Zundo、registry / Dirty Node systems、WebGPU / TSL 与 CLI 分发。运行记录:模型 Claude Opus 4.8,执行入口 Cursor Cloud Agent;reasoning effort 未取得运行记录。2026-09-16 由 Cursor Cloud Agent 将两稿收敛为本文:保留 Codex 的场景几何、MCP 流程与测试核验,并吸收 PR #13 中上述架构材料。运行记录:模型 grok-4.6(Cursor 路由标识 cursor-grok-4.6-high),提供方 xAI,执行入口 Cursor Cloud。reasoning effort 未取得独立运行记录。未完成整个 Pascal 应用的浏览器端验收。

Pascal Editor 是一个面向建筑场景的参数化三维编辑器。它最值得研究的地方,是把墙、门、楼层、楼板等对象保存为可计算的语义节点,再由渲染与几何系统生成画面;MCP 接入也直接操作这些节点。浏览器与 headless 运行时共用同一份 Scene Graph,而不是给 Agent 另写一套场景逻辑。

可以把项目理解为“建筑领域模型 + 三维编辑工作台 + 本地项目服务 + Agent 操作接口”。 阅读时抓住四个问题:一面墙如何从参数变成 Mesh,关闭房间后哪些数据需要一起变化,声明式 React 树如何与每帧几何重建配合,以及网页和 MCP 如何避免相互覆盖保存。

相关的 Web 3D 基础概念见 Web 3D 基础概念:从 Mesh 到 GLB,工具链协作见 Three.js 与 Blender 入门。

英文术语中文名称简要解释
Scene Graph场景图用节点与父子关系表达建筑文档
Node节点场景的最小数据单元,如 wall、slab、item
Mesh网格由顶点和面组成、供渲染器显示的几何对象
CSG构造实体几何通过并、交、差等运算组合实体,例如从墙体扣除门洞
Half-edge半边把一条边拆成两个方向,用来沿墙边寻找封闭区域
Dirty Node脏节点数据已变、几何尚未重建的节点标记
System系统每帧扫描 Dirty Node 并重建几何的组件
Registry注册表从节点类型或节点 id 到运行时对象的映射
Zundo——Zustand 的撤销中间件;本文场景 store 使用其 temporal wrapper
TSLThree.js Shading Language用 JavaScript 组合着色器节点、可编译到 WebGPU / WebGL 的 DSL
MCP模型上下文协议让外部 Agent 调用创建墙、放置门等结构化工具
SSE服务器发送事件服务器通过持续 HTTP 连接向浏览器推送场景变化
Optimistic Locking乐观锁保存时检查版本,拒绝用旧数据覆盖新版本
Local-first本地优先数据默认存在本地、可离线工作,云同步是可选增强

技术栈:Next.js 是宿主,建筑能力集中在包内

下表记录的是该提交的 manifest 声明或根目录 override,不代表各依赖当前最新版本;带 ^ 的范围也不等于锁文件的精确安装版本。

层次源码中的技术具体职责
工程Bun workspaces、Turborepo、TypeScript、Biome组织应用与库、依赖构建、检查和格式化;根目录指定 Bun 1.3.14
Web 宿主Next.js 16.3.0 override、React ^19.2.4页面、项目加载、场景 API 与编辑器装配
三维视图Three.js 0.186.0 override、React Three Fiber ^9.5.0、Drei ^10.7.7Canvas、相机、对象树和交互辅助
GPU 路径WebGPURenderer、TSL、WebGL 2 fallback优先 WebGPU,能力探测失败后尝试 WebGL 2
状态与约束Zustand 5、Zundo、Zod 4、mitt文档状态、撤销历史、节点校验、编辑事件
几何处理three-bvh-csg、three-mesh-bvh墙体开洞、空间加速等;打印导出另有 manifold-3d 依赖
UITailwind CSS 4、Radix UI面板、菜单、表单与样式
本地服务MCP SDK、SQLite、Node.js / Bun工具调用、项目持久化、事件同步和 CLI runtime

版本与依赖来源:根 package.json、Web 应用、editor 包、viewer 包。根工程声明 Node.js ≥20.9,但 CLI 要求 ≥22.13,不能用前者替代后者的运行要求。CLI manifest

这不是只包含一个前端应用的仓库。源码分层直接决定了后续功能应放在哪里。The viewer renders the scene; the editor extends it with interactive tools. viewer 可以作为只读运行时单独使用;交互式工具、选择、面板和拖拽叠加在 editor 之上。

flowchart TD
  app["apps/editor<br/>Next.js 宿主"]
  editor["packages/editor<br/>工具 · 面板 · 选择 · 保存"]
  nodes["packages/nodes<br/>内建节点插件"]
  viewer["packages/viewer<br/>Canvas · 几何 · 渲染"]
  core["packages/core<br/>schema · Scene Graph · store"]
  mcp["packages/mcp<br/>SceneBridge · 工具 · SQLite"]
  cli["packages/cli<br/>安装 · 启动 · 连接 MCP"]

  app --> editor
  app --> nodes
  editor --> viewer
  nodes --> viewer
  viewer --> core
  nodes --> core
  mcp --> core
  cli --> mcp
  cli -.-> app
目录主要责任阅读时关注的问题
packages/coreschema、Scene Graph、store、领域运算、Registry 契约什么是合法节点,修改会影响什么
packages/nodes内置节点插件及其 renderer、System、tool、inspector一个建筑对象如何把数据、显示和编辑连起来
packages/viewerCanvas、相机、几何 System、渲染系统节点怎样生成与更新画面
packages/editor工具、面板、选择、拖拽、保存回调、导出用户操作怎样提交到领域模型
apps/editorNext.js 页面与本地场景 API项目如何加载、保存、同步
packages/mcpSceneBridge、SceneOperations、工具与存储Agent 如何不依赖浏览器执行语义操作
packages/cli安装与启动 runtime、连接 MCP本地服务如何分发、管理和连接

core 和 viewer 等包有构建产物入口,但 editor 暴露源码,并带有 Next.js peer dependency。因此,“拆成 package”不等于“已经可以零成本嵌入任意前端框架”。editor 包入口

核心原理一:Scene Graph 保存语义,Mesh 是派生结果

The scene document stores semantic nodes; rendering derives geometry from those nodes. 例如墙保存起点、终点、厚度、高度、材质及与门窗的关系,渲染系统再计算顶点和面。保存项目时,无须把每次生成的墙体 Mesh 当作文档主体。

基础节点包含 id、type、parentId、可见性和 metadata 等字段。id 由带类型前缀的 nanoid 生成,例如 wall_3f9a…。文档主体使用 nodes 字典与 rootNodeIds,节点再通过子节点 ID 建立关系;完整导出还包含 collections、materials、installedPlugins。各类型在 schema/nodes/*.ts 里扩展 BaseNode,再收敛成 Zod discriminated union AnyNode。BaseNode、useScene、SceneBridge

BaseNode.metadata 使用 z.record(z.string(), z.unknown()),而不是递归 z.json()。源码注释给出的实测是:去掉递归 JSON schema 后 WallNode.parse 约快 1.5 倍,并让节点树能使用 Zod 4.5 的 z.compile()。When a schema field is both expensive and rarely constrained, keep it an open record and validate elsewhere.

典型建筑层级如下。这里只画主要节点,实际还支持 roof、stair、item 等更多类型。

flowchart TD
  A[Site] --> B[Building]
  B --> C[Level]
  C --> D[Wall]
  D --> E[Door / Window]
  C --> F[Slab / Ceiling]
  C --> G[Item / Zone]

平面坐标使用 XZ,Y 表示高度,尺寸以米表达。这里应区分楼层排序、楼层基准高度、层高和墙高:Level 的相关字段承担不同职责,不能看到 level 就把它理解成米制高度,也不能默认所有墙总等于层高。Level schema、Wall schema

状态还分成三个用途:useScene 管文档;useViewer 管视图;useEditor 管工具与界面。拖拽过程的临时覆盖值另行管理,避免把每一个鼠标位置都变成正式文档历史。

这套设计的工程收益是:修改墙长、撤销操作、Agent 创建墙和项目保存,都能围绕同一套可校验参数进行。代价是必须处理派生数据的一致性;只更新一面墙的 Mesh,而漏掉房间边界、楼板或门洞,仍会造成文档与画面不同步。

核心原理二:Zustand + Zundo 管文档历史,手势状态不进撤销

README 提到场景 persist 到 IndexedDB,但该提交的 useScene 只包了 Zundo 的 temporal 中间件,并没有 Zustand persist。IndexedDB 的 idb-keyval 主要出现在资产二进制存储中。Scene Graph 的持久化由宿主完成:独立 Web 应用走场景 API 与 SQLite,嵌入时才可能落到 localStorage。这类差异必须以源码为准。场景 store、asset-storage

temporal 的配置有几处工程边界:

  • 历史快照上限为 50。
  • partialize 只对 nodes / rootNodeIds / collections / materials / installedPlugins 建快照,并用语义比较避免无意义的撤销步。
  • 放置工具会给正在拖动的节点打 metadata.isNew = true;建快照前会剔除这类子树,直到首次 commit 才进入历史。

Preview state and document state are deliberately different stores. 拖拽时的 live overrides 也不写入 Zundo:System 通过 getEffectiveNode() 读取即时值更新 Mesh,指针抬起才提交为一次撤销步。Live gesture state bypasses undo; only committed state enters history.

写入后会给相关节点打 Dirty Node 标记。GuardedDirtySet 只允许“存在脏消费者、且所属插件已启用”的节点进入集合,避免为没有几何 System 的节点做无谓标记。updateNodesAction 还会用 requestAnimationFrame 批量收敛标记。撤销 / 重做后则通过快照 diff 重新标脏受影响的墙、item 与相邻拼角。编辑器 store

核心原理三:墙体建模包含接缝、开洞与增量重建

画墙并非简单地创建一个长方体。入口工具监听网格移动、点击与取消事件,在预览阶段处理吸附与对齐,确认后提交节点。墙体 System 再处理同楼层相邻墙的连接轮廓,以及门窗对子墙体的开洞影响。Wall tool、WallSystem

可按四步理解这条几何链路:

  1. 根据墙的起终点和厚度求平面轮廓,结合楼层内相邻墙计算转角接缝。
  2. 把轮廓沿高度方向生成实体。
  3. 收集门窗等对象的切割几何,使用 CSG subtraction 扣出洞口。
  4. 将结果更新到对应的 Three.js 对象,并参与后续显示与批处理。
flowchart TD
  A[工具提交 Wall 参数] --> B[标记相关节点 Dirty Node]
  B --> C[计算墙连接与轮廓]
  C --> D[生成墙体几何]
  D --> E[减去 Door / Window 切割体]
  E --> F[更新 Mesh 与渲染批次]
  G[拖拽时的临时覆盖值] --> C

WallSystem 引入 three-bvh-csg 的 Brush、Evaluator 与布尔操作,并缓存同楼层的墙连接信息。拖动门窗时会合并 live overrides,让洞口跟随预览位置,而不是每次都先永久写入文档。

重建也不是每帧遍历重算所有墙。系统消费 Dirty Node 集合,对较大的待处理队列设置每帧数量及时间预算,并让相邻墙更新分批进行。需要注意:时间检查发生在墙体任务之间,单面复杂墙的布尔计算仍可能超出预算;这不构成严格的帧耗时上限。

WebGPU 加速的是显示管线,不意味着所有建模运算都迁到了 GPU。 墙轮廓、关系整理和这里的 CSG 调用仍是 JavaScript 侧的几何工作。复杂建筑的性能分析必须分别观察建模计算、React 状态更新与 GPU 绘制。

核心原理四:封闭房间会触发一次领域级联

当最后一面墙闭合时,用户通常期待房间、楼板、天花板与墙的内外侧一起正确出现。源码通过 space-detection 处理这种跨节点关系,核心是从墙段构建带方向的边,排序连接点的出边,再沿边寻找封闭轮廓。space-detection

这与简单判断“首尾坐标是否相等”不同:连接点可能有多个方向,还需要区分不同区域和墙的朝向。识别结果包括 rooms、spaces 与 wallUpdates,并被后续逻辑用于协调自动生成的 Slab、Ceiling、Zone 等对象。

更关键的是提交边界。项目把本地墙变化引起的相关派生更新纳入同一次 scene commit,并在派生写入阶段控制历史记录,使“一次封闭房间”可以作为一个撤销步骤处理。远端应用图数据时则需要避免再次生成一套派生节点与本地历史。架构说明、对应测试

这说明房间识别并非仅为画一个标签服务,而是参与文档一致性。二次开发若绕过正规节点操作直接写字典,很容易遗漏关系协调、Dirty Node 标记和历史边界。

渲染管线:两张 Registry、Dirty Node Systems、WebGPU 优先

React Three Fiber 支持异步 Canvas gl 初始化;Pascal 使用这一能力等待 renderer 初始化,再交给场景系统使用。R3F Canvas 文档、Viewer 入口

声明式树 + 命令式几何更新

真正把“数据变化”变成“几何更新”的,是两张 Registry 加一套 Dirty Node 循环:

  • 节点定义 Registry(packages/core/src/registry/):类型 → schema、renderer、System、capabilities。插件通过它注册新节点类型。
  • 场景对象 Registry sceneRegistry:节点 id → 具体的 THREE.Object3D,外加 byType 索引。renderer 挂载时用 useRegistry 登记,卸载时注销。

NodeRenderer 按类型分发。NodeDefinition 的渲染行为可以概括成三选一:自定义 JSX renderer;纯函数 geometry(node, ctx) 交给 GeometrySystem 填充已注册的 group;或每帧 system 由 RegisteredSystems 按 priority 运行。NodeRenderer、registry

墙体是刻意的反例:它没有通用 geometry,而是由 WallRenderer 挂占位 Mesh、再由 priority 为 4 的 WallSystem 独占 CSG 重建。多个 System 用 useFrame 的 priority 排出确定顺序:楼板高程(1)→ 通用几何(2)→ 墙体(4)→ 楼层升降动画(5)→ 场景就绪探测(10)。这种“先地基后上层”的排序,避免同帧内相互依赖的几何读到半成品。

flowchart TD
  A[用户操作 / MCP 工具] --> B[useScene 写入文档]
  B --> C[标记 Dirty Node]
  B --> D[React 重渲染 NodeRenderer]
  D --> E[useRegistry 登记 Object3D]
  C --> F[System 在 useFrame 中重建几何]
  E --> F
  F --> G[clearDirty]

若 Object3D 尚未挂载,System 会保持 Dirty Node、下一帧再处理。组件间通信走基于 mitt 的类型化事件总线,事件名形如 wall:click,负载同时带世界坐标与建筑局部坐标。

WebGPU 初始化、帧循环与 TSL

源码会探测 WebGPU adapter 与 device;失败或超时后再探测 WebGL 2,并为 WebGPURenderer 选择相应参数。<Canvas gl={...}> 传入的是异步工厂,并以 canvas 为键缓存 renderer promise,避免 React StrictMode 双调用让两个异步 init 竞争、第二个停在默认 300×150 尺寸。两条路径都不可用时,源码更新 React state 卸载 Canvas、显示不支持界面,而不是让 promise reject 拖垮 R3F。不能依据 README 的 WebGPU 表述推断“不支持 WebGPU 的浏览器一定无法运行”。renderer-capability

Canvas 设置 frameloop="never",帧调度由 FrameLimiter 借助 requestAnimationFrame 和 R3F advance() 接管,默认上限 50 FPS,并处理暂停和 Canvas 被遮挡的情况。这是主动控制更新节奏,并非 R3F 默认连续帧循环。FrameLimiter

后处理使用 TSL,包含 SSGI、降噪、描边等路径;scene、zone、grid、overlay 等层分开处理,避免辅助对象污染场景的深度、法线和光照结果。手绘线稿观感来自屏幕空间 ink edges,不是 cel-shaded 材质。批处理降低绘制成本,但原对象仍需要保留拾取与交互用途。后处理、图层架构

WebGL 2 fallback 不应被解释成与 WebGPU 完全相同的视觉效果。源码包含直接渲染回退,并对后处理初始化错误作处理;其能力检测中也存在以 navigator.gpu 是否存在作为条件的分支。adapter 初始化失败后的具体画面仍应在真实浏览器验证,本次能力测试使用模拟环境,并未替代这种验收。

从用户操作到项目保存:实际走 API 与 SQLite

该提交的主链路需要以实现为准:独立 Web 应用将保存回调接到场景 API,由后端 SceneStore 写 SQLite。SceneLoader、存储工厂

编辑器包保留可替换的宿主契约:有 onSave 时调用宿主回调,没有时才使用 localStorage 保存路径。因此“浏览器独立嵌入”和“仓库中的完整 Next.js 应用”不能混成一种存储行为。use-auto-save

SQLite 驱动按运行时探测:优先 bun:sqlite,否则用 Node 22+ 内置的 node:sqlite(DatabaseSync)。库表包括 scenes、scene_revisions 与 scene_events。写入用事务范围内的版本检查,保存图 JSON、节点数量等元数据,并写 revision 记录;开启 journal_mode=WAL。加载侧使用较宽松的 Zod 校验,避免某个历史脏字段让整份场景打不开。Validate strictly on write, leniently on read. 图数据有默认大小限制,不能把保存函数里的普通 fetch 视为无限制传输。客户端和 API 还包含空图覆盖保护,用来避免加载尚未完成时把已有项目清空。SQLiteSceneStore、scene API

flowchart TD
  A[编辑节点与派生对象] --> B[约 1 秒 debounce]
  B --> C[宿主 onSave]
  C --> D[PUT scene API 携带 If-Match]
  D --> E{版本是否匹配}
  E -->|否| F[409 冲突提示]
  E -->|是| G[SQLite 保存图与 revision]
  G --> H[返回新版本]
  H --> I[客户端更新已知版本]

版本冲突出现后,当前客户端呈现冲突状态;源码并没有在这里实现 CRDT 式自动合并。网页收到 SSE 新版本后会应用服务器的图,并短时间抑制重复保存,避免把刚收到的远端变化立刻回写。这是一套版本化同步流程,不能直接称为完整多人协同编辑。SceneLoader

MCP 业务流程:外部 Agent 调工具,项目执行确定性操作

Pascal 的 MCP 层提供语义工具,例如创建墙、添加门窗、创建房间、放置物品和检查场景。自然语言规划由外部 Agent 完成;工具层负责参数校验和具体操作,不需要在每个工具里重新调用一个语言模型。server、工具目录

关键点是:SceneBridge 没有另写一套场景逻辑,而是直接驱动 @pascal-app/core 那个本为浏览器写的 Zustand store。为了让该 store 在 Node 里加载,MCP 侧只 polyfill 了 requestAnimationFrame,因为 updateNodesAction 与 temporal 订阅在模块加载期会用到它;没有 IndexedDB shim,持久化在 MCP 层替换成 SQLite,而不是在 core 里加运行时分支。SceneBridge

以 create_wall 为例:输入指定父 Level 和墙参数,工具校验节点类型及参数,构造 WallNode,经 SceneOperations / SceneBridge 调用核心 store,然后尝试发布当前场景快照。create-wall

完整同步链路是:

  1. Agent 通过 stdio 或 Streamable HTTP 调用 MCP 工具。
  2. 工具修改无浏览器 renderer 的 Scene Graph。
  3. 场景已绑定且存储支持事件时,按预期版本保存当前图。
  4. 服务追加 scene event,浏览器通过 SSE 接收。
  5. 浏览器更新 useScene,由自己的几何与渲染系统生成画面。

Agents edit the same Scene Graph the browser editor uses—no renderer, no React, no WebGPU required on the server. 工具修改内存成功与持久化成功是两个阶段。未绑定场景或存储不支持事件时,代码返回 persistence warning;版本冲突和保存失败则转换成 MCP error。不能仅看输出有一个 wallId 就认定项目一定已落盘,也不能推断保存失败会自动撤销此前的内存操作。live-sync

本地 CLI 负责服务 runtime 的安装、启动与连接;MCP 服务默认面向本地使用,HTTP 路径还包含认证及访问约束。SceneBridge 复用了核心 store,本地服务的 active scene 与多个传输会话之间并不能据此推导出独立的多租户隔离。CLI 源码、HTTP transport

CLI 分发:瘦包 + 校验过的 runtime 下载

npx @pascal-app/cli editor 背后是刻意做瘦的 npm 包:它只带 CLI 与打包好的 MCP service。Web 编辑器 runtime 在首次启动时按版本下载,并用 SHA-256 校验;下载的是 Next.js standalone server 包(apps/editor 以 output: 'standalone' 构建),再由 CLI 启动完整本地服务(含 scene API 与 SSE)。runtime-download

产物用确定性 tar 打包(路径排序、mtime 归零),因此钉在 CLI 里的摘要在不同构建间可核对。进程管理会选择无冲突的 loopback 端口,并用 /api/health 比对 instanceId 与版本,避免端口被其他进程占用后误判。MCP service 启动时生成本地 token 文件;连接器从磁盘读取后把 stdio 桥到本地 HTTP MCP。本文不展开 token 文件路径或权限细节。

插件化已经深入架构,但迁移尚未完全统一

节点 Registry 让一个节点类型描述 schema、默认值、能力、关系、renderer、geometry、System、tool 和 inspector。内建节点和第三方插件走同一套 Plugin API:id、apiVersion 与 nodes;tools / panels / systems 声明在各自的 NodeDefinition 内部,而不是 Plugin 顶层特权字段。宿主在挂载 Viewer 前 loadPlugin(builtinPlugin)。Wall 定义

不过,Wall 定义仍包装既有专用 renderer 与 System,schema 也保留从 core 导出的兼容路径。墙接缝需要楼层范围的信息,无法仅靠一个完全孤立的单节点 geometry 函数解决。当前项目是逐步统一中的插件架构,不能把所有节点都描述成同一套纯函数流水线。Wall schema

添加新节点时,除了 renderer,还需检查持久化、关系协调、选择拖拽、撤销、MCP 与导出。某个对象能显示出来,只证明其中一段链路已经接通。

导出与产品边界

能力该提交可核验的实现边界
Scene Graph JSON导出节点及相关文档数据用于保留参数与继续编辑,和最终 Mesh 交换不同
浏览器 GLBeditor 包调用 GLTFExporter,含导出对象整理依赖浏览器侧几何准备,不保证覆盖所有异步节点情形
MCP export_glb返回 status: not_implemented 且 isError: true注册了工具名不代表已经能生成 GLB
本地持久化SQLite SceneStore 与版本检查类型里出现其他 backend 名称不等于已有对应实现
同步场景版本、事件和 SSE本文未验证生产多人并发及远端服务部署

来源:SceneBridge、浏览器 GLB 导出、MCP GLB 工具、存储工厂。

仓库采用 MIT License。这里分析的是公开代码中的建模与本地运行链路;仓库存在远端服务或插件连接配置,不足以证明公开源码包含线上产品的所有账户、商业服务与资产后台。LICENSE

源码复现与实际验证

以下命令用于固定研究对象,不依赖之后的默认分支变化。工程指定 Bun 1.3.14;本次实际测试环境使用 Bun 1.3.2。安装使用 --ignore-scripts,仅用于这次源码测试,不代表完整应用安装流程已验收。

sh
git clone https://github.com/pascalorg/editor.git
cd editor
git checkout eacc2a939bec53529809b06f68123df2459b7961
bun install --frozen-lockfile --ignore-scripts
bun run --cwd packages/core build
bun test packages/core/src/lib/space-detection.test.ts packages/core/src/lib/space-detection-reconciliation.test.ts packages/core/src/store/history-control.test.ts packages/viewer/src/lib/renderer-capability.test.tsx packages/mcp/src/bridge/scene-bridge.test.ts packages/mcp/src/storage/sqlite-scene-store.test.ts packages/mcp/src/operations/scene-operations.test.ts

必须先构建 core:MCP 包的跨包导入指向其导出入口;直接运行测试而缺少 dist,会出现 Cannot find module '@pascal-app/core/schema'。本次首次运行遇到该问题,按工程构建入口补齐后重跑通过。

还可以在仓库根目录保存下面的 inspect-room.ts,执行 bun inspect-room.ts。它直接复用项目算法,检查一个 4 × 3 米矩形的封闭识别;不需要启动浏览器。

typescript
import { WallNode } from './packages/core/src/schema'
import { detectSpacesForLevel } from './packages/core/src/lib/space-detection'

const corners: [number, number][] = [[0, 0], [4, 0], [4, 3], [0, 3]]
const walls = corners.map((start, i) => WallNode.parse({
  start,
  end: corners[(i + 1) % 4],
  height: 2.5,
}))
const result = detectSpacesForLevel('level_demo', walls)
console.log(JSON.stringify({
  rooms: result.rooms.length,
  spaces: result.spaces.length,
  wallUpdates: result.wallUpdates.length,
}))

实际输出为 {"rooms":1,"spaces":1,"wallUpdates":4}。这个示例只验证闭合区域识别,不证明自动楼板、门洞外观或建筑尺寸精度;相关派生协调另由所选测试覆盖。

本次验证结果:core 构建通过;上述 7 个文件共 175 项测试通过,0 项失败(530 次断言);矩形识别示例输出符合预期。测试覆盖历史暂停控制、房间协调、renderer 能力回退、SceneBridge、SQLite 保存和 SceneOperations。未运行整个仓库全部测试,未启动 Pascal 完整应用,也未进行真实 GPU 性能、复杂模型及生产并发测试。

阅读源码时最有价值的切入顺序

先读 BaseNode、WallNode 与 useScene,明确文档真值;然后沿 Wall tool → WallSystem 观察一次参数变化如何产生 Mesh;再读 space-detection 理解派生对象与撤销边界;接着看 sceneRegistry 与 GeometrySystem / WallSystem 的 Dirty Node 循环;最后串起 SceneLoader → scene API → SceneStore 和 MCP 的 create-wall → live-sync。

这条顺序能把项目从“很多 Three.js 文件”还原为完整业务系统。它的可复用经验是:把领域参数和渲染结果分开,让人工与自动化共享操作语义,再把派生更新、历史和持久化边界明确下来。需要继续审视的地方,则是专用 System 与插件迁移的并存、复杂几何的单次计算成本,以及版本冲突后的用户恢复流程。

相关阅读:OpenGemCutting 架构调研。两者都把参数化文档与三维显示分离,但 OpenGemCutting 的主线是 CUT 工序,Pascal 的主线是建筑节点及其相互关系,不能直接套用同一套领域模型。

本文共 6619 字,创建于 Sep 15, 2026

相关标签:Frontend, 可视化, AI, ByAI

博客助手

正在打开博客助手…