ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Pascal Editor 架构与实战指南:本地优先的 3D 建筑编辑器与 AI Agent 工作流

Pascal Editor 架构与实战指南:本地优先的 3D 建筑编辑器与 AI Agent 工作流 Pascal Editor 架构与实战指南本地优先的 3D 建筑编辑器与 AI Agent 工作流【免费下载链接】editorOpen-source 3D architectural editor with a local CLI, MCP tools, and practical workflows for humans and AI agents.项目地址: https://gitcode.com/GitHub_Trending/editor93/editorPascal Editor 是一个开源、本地优先local-first的 3D 建筑编辑项目基于 React Three Fiber 与 WebGPU 构建支持在浏览器或 CLI 中运行并能通过 MCPModel Context Protocol连接 AI Agent。本文以仓库根目录 README.md 为主体脉络结合 packages/core、packages/viewer、packages/cli、packages/mcp 的源码实现系统讲解从本地部署、Agent 技能安装、MCP 接入到节点Nodes、场景状态、渲染系统、事件总线、空间网格等核心机制的完整链路读完可掌握一套可运行、可扩展、可被 AI 调用的 3D 建筑编辑方案。项目定位与设计理念Pascal Editor 的核心定位可以概括为三个关键词开源MIT 协议见 LICENSE完整代码托管在本文档所描述的仓库中本地优先数据默认存储在本地~/.pascal/data/pascal.db不上传项目不依赖云端账号即可运行Agent 友好内置 MCP 服务AI 可以通过标准协议读写场景、执行构建操作。技术底座是 React 19 Next.js 16、Three.jsWebGPU 渲染器、React Three Fiber Drei。值得注意的是虽然渲染走 WebGPU但整个编辑器的场景数据模型与渲染解耦所有建筑对象被抽象为节点Node由 Zustand 管理状态、Zod 校验 schema、mitt 驱动事件、three-bvh-csg 完成布尔几何运算最终由 Turborepo 组织成一个可拆分的 monorepo。快速开始本地运行编辑器一条命令启动不需要克隆本仓库只要 Node.js 版本在22.13 或更新即可创建持久的本地安装npx pascal-app/cli editor这条命令背后的pascal-app/cli源码见 packages/cli/src/index.ts承担了三件事启动编辑器本体激活一个版本化的独立编辑器运行时后台拉起一个已认证的 MCP 服务为 AI Agent 提供工具接口自动选择无冲突的回环loopback端口避免与本地其他服务抢占端口。项目数据保存在~/.pascal/data/pascal.db属于持久化本地数据不会因为关闭终端而丢失。从 packages/cli/src/index.ts 的导出可以看到 CLI 提供了完整的生命周期管理能力startEditor/stopEditor/restartEditor、installBundledRuntime安装捆绑运行时、runDoctor诊断检查、resolvePascalPaths路径解析等这也是它能作为持久安装而非一次性脚本的根本原因。让 Agent 连接 MCP编辑器启动后配置 Agent 执行pascal mcp connect即可接入 MCP 服务。整个 MCP 连接是本地的不需要 Pascal 账号、不需要 API key、不会自动上传项目。MCP 服务器的实现位于 packages/mcp/src/server.ts其中createPascalMcpServer依次注册了四类能力registerTools场景操作工具registerVisionTools视觉相关工具registerResources场景资源registerPrompts提示词模板。这种工具 资源 提示词的三位一体结构正是 MCP 协议允许 Agent 既执行操作又理解上下文的关键。单服务并发约束README 强调了一个容易被忽略的约束每个本地 CLI 服务只允许一个活跃的 Agent 客户端。因为独立本地 HTTP 运行时会在客户端之间共享同一份活跃场景状态如果需要并行独立工作应当使用不同的PASCAL_HOME目录并分别启动服务进程。这与多用户共享场景的产品逻辑不同属于典型的本地单机约束。Agent 技能让 AI 干建筑活通过 skills.sh 安装Pascal 将面向 Agent 的工作流封装成技能Skill从本仓库用 skills.sh 一键安装npx skills add pascalorg/editor \ --skill pascal-3d \ --skill furniture-fit两个技能各有明确边界技能正文见 skills/pascal-3d/SKILL.md 与 skills/furniture-fit/SKILL.md技能定位pascal-3d安全的本地或托管 MCP 配置以及经过验证的场景工作流furniture-fit产出有界、有证据支撑的家具占地面积评估不臆断未经验证的层高、摆动或交付能力README 特别提醒这些工作流依赖已连接的 Pascal MCP 服务器才能执行工具类动作。如果能力在当前仓库存在、但旧版或托管的发布版本中缺失Agent 应当如实报告更窄的支持结果而不是假设源码输入一定可用——这与furniture-fit技能证据边界见 skills/furniture-fit/references/evidence-boundaries.md的理念一脉相承。Claude Code 插件安装Claude Code 用户可以通过插件市场安装同一份技能源/plugin marketplace add pascalorg/editor /plugin install pascal-agent-skillspascalClaude 插件同时提供本地的pascal mcp connect服务器。安装前需要先装好 Pascal CLI并确保pascal位于启动 Claude Code 的PATH中。该本地连接器不需要账号、不需要 API key、不会自动上传项目。一个常见的坑Claude Code 2.1.258 会同时加载pascal mcp setup claude创建的用户级pascal服务器和插件提供的服务器导致两个连接并存违反单服务单客户端约束。此时应删除手动条目让插件独占连接生命周期claude mcp remove --scope user pascal也可以用/mcp面板移除或禁用任何项目级、本地级 Pascal 连接。如果目标项目托管在 Pascal 账号或组织中则应通过/mcp禁用插件自带的本地服务器改从技能设置指南配置托管端点。Codex 与 OpenClawCodex 用户从仓库市场安装同一插件codex plugin marketplace add pascalorg/editor codex plugin add pascal-agent-skillspascalOpenClaw 的安装则在技能发布到 Pascal 的 ClawHub 发布者之后可用具体的所有者限定安装与验证命令见 skills/README.md。另外若向 OpenAI 目录提交必须使用With MCP方式并随技能一起提交生产环境的托管 MCP 端点。MCP Registry 清单server.json 是 Pascal 在官方 MCP Registry 中的清单文件。它的版本号独立于 npm 包版本单独跟踪托管 MCP 实现的演进。针对该文件的拉取请求会校验清单本身和生产端点只有 Pascal 组织所有者才能从main分支通过官方发布器发布已批准的版本。对于想把自己的场景能力接入 MCP 生态的开发者这份清单是理解MCP 服务如何被注册、被校验、被发布的最佳入口。使用已发布的 npm 包安装与挂载查看器运行时与内置节点定义是独立包。完整的内置查看器安装方式如下npm install pascal-app/core pascal-app/viewer pascal-app/editor pascal-app/nodes npm install pascal-app/capture-protocol pascal-app/capture-viewer其中capture-*两个包是可选的捕获会话扩展传输中立transport-neutral即不绑定具体的流式传输实现。挂载Viewer之前必须先加载内置插件import { loadPlugin } from pascal-app/core import { builtinPlugin } from pascal-app/nodes await loadPlugin(builtinPlugin)loadPlugin的实现位于 packages/core/src/registry/registry.ts内置插件 ID 为pascal:core所有插件通过同一套注册机制把节点定义NodeDefinition写入全局注册表。注册表还维护了一个单调递增的registryVersion每当有新 kind 注册插件异步加载完成就会通过onRegistryChange通知订阅者配合useRegistryVersionhook 触发 React 重渲染——这正是插件异步加载后依赖注册表快照的组件不会失效的关键设计。pascal-app/viewer的 React 使用示例见 packages/viewer/README.md。仓库架构Turborepo monorepo顶层结构这是一个 Turborepo monorepo由可复用的编辑器包、独立应用和分发它的 CLI 组成editor/ ├── apps/ │ └── editor/ # Next.js application ├── packages/ │ ├── core/ # Schemas, scene state, and registry contracts │ ├── viewer/ # 3D rendering runtime and shared systems │ ├── capture-protocol/ # Static/live capture-session contracts │ ├── capture-viewer/ # Capture source runtime and reference renderers │ ├── editor/ # Editing tools and UI components │ ├── nodes/ # Built-in node definitions, renderers, and systems │ ├── cli/ # Persistent local editor installer and process manager │ ├── mcp/ # Model Context Protocol server and scene storage │ └── ui/ # Shared UI components职责分离PackageResponsibilitypascal-app/coreNode schemas, scene state (Zustand), registry contracts, spatial queries, and event buspascal-app/viewer3D rendering via React Three Fiber, shared render systems, default camera/controls, and post-processingpascal-app/capture-protocolVersioned capture manifests, normalized streams, and transport-neutral static/live sourcespascal-app/capture-viewerViewer child runtime and reference model, device-motion, and point-cloud layerspascal-app/editorEditing tools, panels, selection, and direct-manipulation UIpascal-app/nodesBuilt-in registry plugin with node definitions, renderers, geometry, and systemspascal-app/cliInstalls and manages a versioned standalone editor runtime and persistent local datapascal-app/mcpExposes scene tools, resources, prompts, and local storage to MCP-compatible AI hostsapps/editorStandalone Next.js host for the editor packages层次关系很清晰viewer 用合理默认值渲染场景editor 在 viewer 之上扩展出交互工具、选择管理和编辑能力。这正是查看器与编辑器解耦的架构意图——纯展示场景只需要 viewer编辑才需要 editor。Stores每个包一个 Zustand storeStorePackageResponsibilityuseScenepascal-app/coreScene data: nodes, root IDs, dirty nodes, CRUD operations. Persisted to IndexedDB with undo/redo via Zundo.useViewerpascal-app/viewerViewer state: current selection (building/level/zone IDs), level display mode (stacked/exploded/solo), camera mode.useEditorapps/editorEditor state: active tool, structure layer visibility, panel states, editor-specific preferences.访问模式分为两种React 组件内用 hook 订阅React 之外回调、系统用getState()直接读取// Subscribe to state changes (React component) const nodes useScene((state) state.nodes) const levelId useViewer((state) state.selection.levelId) const activeTool useEditor((state) state.tool) // Access state outside React (callbacks, systems) const node useScene.getState().nodes[id] useViewer.getState().setSelection({ levelId: level_123 })在真实实现中packages/core/src/store/use-scene.ts 远不止三个字段它承担了旧版本场景的逐类型迁移职责。例如把退役的内联material*字段迁移到统一的node.slots材质模型migrateWallSurfaceMaterials、migrateStairSurfaceSlots等、把旧屋顶迁移为屋顶 分段结构、把custom-mesh重命名为block、为电梯节点重新挂到 Building 下等。这些迁移逻辑在每次场景加载时通过migrateNodes统一执行确保旧数据能干净地进入新 schema。核心概念Nodes场景的数据原语所有节点都扩展自BaseNode。README 给出了概念形态BaseNode { id: string // Auto-generated with type prefix (e.g., wall_abc123) type: string // Discriminator for type-safe handling parentId: string | null // Parent node reference visible: boolean camera?: Camera // Optional saved camera position metadata?: JSON // Arbitrary metadata (e.g., { isTransient: true }) }实际的 Zod schema 定义在 packages/core/src/schema/base.tsexport const BaseNode z.object({ object: z.literal(node).default(node), id: z.string(), type: nodeType(node), name: z.string().optional(), parentId: z.string().nullable().default(null), visible: z.boolean().optional().default(true), camera: CameraSchema.optional(), metadata: z.record(z.string(), z.unknown()).optional().default({}), })几个值得注意的实现细节id 生成generateId使用 nanoid 的customAlphabet16 位小写字母数字并按类型加前缀如wall_abc123metadata 不用z.json()源码注释说明递归 JSON schema 是嵌入它的每个节点最昂贵的成员实测WallNode.parse去掉它后快约 1.5 倍且用编译解析器z.compile()时从 1.0x 提升到 2.2x 加速。metadata 本质上是每个节点附带的扁平额外数据袋所以开放对象就足够type 是可辨识联合的判别字段让整个系统能做类型安全的 switch 分发。节点层级场景遵循一个固定的建筑学层级Site └── Building └── Level ├── Wall → Item (doors, windows) ├── Slab ├── Ceiling → Item (lights) ├── Roof ├── Zone ├── Scan (3D reference) └── Guide (2D reference)关键实现决策节点存储在扁平字典Recordid, Node中而非嵌套树父子关系通过parentId与children数组表达。扁平化存储让按 id 随机访问任意节点变成 O(1)也让脏节点标记、注册表查找等高频操作避免深递归。场景状态Zustand Store场景由pascal-app/core中的 Zustand store 管理useScene.getState() { nodes: Recordid, AnyNode, // All nodes rootNodeIds: string[], // Top-level nodes (sites) dirtyNodes: Setstring, // Nodes pending system updates createNode(node, parentId), updateNode(id, updates), deleteNode(id), }中间件Persist持久化到 IndexedDB保存时排除瞬态节点transient nodesTemporalZundo撤销/重做默认50 步历史。场景注册表Scene Registry注册表把节点 ID 映射到其 Three.js 对象实现快速查找避免每次遍历场景图sceneRegistry { nodes: Mapid, Object3D, // ID → 3D object byType: { wall: Setid, item: Setid, zone: Setid, // ... } }渲染器用useRegistryhook 注册自己的 refconst ref useRefMesh(null!) useRegistry(node.id, wall, ref)这样系统systems可以直接拿到 3D 对象更新几何无需遍历场景图。注册表相关代码位于 packages/core/src/hooks/scene-registry。节点渲染器Node Renderers渲染器是为每种节点类型创建 Three.js 对象的 React 组件SceneRenderer └── NodeRenderer (dispatches by type) ├── BuildingRenderer ├── LevelRenderer ├── WallRenderer ├── SlabRenderer ├── ZoneRenderer ├── ItemRenderer └── ...模式三步走渲染器先创建一个占位的 mesh/group通过useRegistry注册它系统systems根据节点数据更新几何。示例简化const WallRenderer ({ node }) { const ref useRefMesh(null!) useRegistry(node.id, wall, ref) return ( mesh ref{ref} boxGeometry args{[0, 0, 0]} / {/* Replaced by WallSystem */} meshStandardMaterial / {node.children.map(id NodeRenderer key{id} nodeId{id} /)} /mesh ) }占位 mesh 系统改写几何的设计把React 的声明式组件树与命令式的几何生成巧妙地接合起来React 负责生命周期与结构几何计算交给按帧执行的系统。Systems按帧驱动的几何生成系统是在渲染循环useFrame中运行的 React 组件负责更新几何与变换处理对象是 store 标记的脏节点。Core Systems在pascal-app/coreSystemResponsibilityWallSystemGenerates wall geometry with mitering and CSG cutouts for doors/windowsSlabSystemGenerates floor geometry from polygonsCeilingSystemGenerates ceiling geometryRoofSystemGenerates roof geometryItemSystemPositions items on walls, ceilings, or floors (slab elevation)Viewer Systems在pascal-app/viewerSystemResponsibilityLevelSystemHandles level visibility and vertical positioning (stacked/exploded/solo modes)ScanSystemControls 3D scan visibilityGuideSystemControls guide image visibility处理模式useFrame(() { for (const id of dirtyNodes) { const obj sceneRegistry.nodes.get(id) const node useScene.getState().nodes[id] // Update geometry, transforms, etc. updateGeometry(obj, node) dirtyNodes.delete(id) } })对应的目录为 packages/core/src/systems几何生成与 packages/viewer/src/systems层级可见性、扫描、引导图等查看器级系统。脏节点Dirty Nodes节点变化时会被标记进useScene.getState().dirtyNodes。系统每帧检查这个集合只为脏节点重算几何——这是性能的关键不是每帧重算全场景而是只重算这一帧有变化的节点。// Automatic: createNode, updateNode, deleteNode mark nodes dirty useScene.getState().updateNode(wallId, { thickness: 0.2 }) // → wallId added to dirtyNodes // → WallSystem regenerates geometry next frame // → wallId removed from dirtyNodes手动标记useScene.getState().dirtyNodes.add(wallId)事件总线Event Bus组件间通信使用基于 mitt 的类型化事件发射器实现见 packages/core/src/events/bus.ts// Node events emitter.on(wall:click, (event) { ... }) emitter.on(item:enter, (event) { ... }) emitter.on(zone:context-menu, (event) { ... }) // Grid events (background) emitter.on(grid:click, (event) { ... }) // Event payload NodeEvent { node: AnyNode position: [x, y, z] localPosition: [x, y, z] normal?: [x, y, z] stopPropagation: () void }源码中EditorEvents是一个巨大的类型交集每种节点类型 × 八种事件后缀click、move、enter、leave、pointerdown、pointerup、context-menu、double-click再加上相机控制、工具、门/窗动画、缩略图、快照、AI 聊天附件、房间预设、选择等专项事件。所有事件在编译期就被约束emitter.on(wall:click, ...)的回调参数类型是推导出来的这比裸字符串事件总线安全得多。空间网格管理器Spatial Grid Manager负责碰撞检测与放置校验spatialGridManager.canPlaceOnFloor(levelId, position, dimensions, rotation) spatialGridManager.canPlaceOnWall(wallId, t, height, dimensions) spatialGridManager.getSlabElevationAt(levelId, x, z)实现位于 packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts。canPlaceOnFloor底层调用canPlaceOnFloorFootprints做足迹级碰撞检测canPlaceOnWall则委托给墙网格getWallGrid(levelId).canPlaceOnWall——这正是家具放置工具furniture-fit技能背后依赖的能力校验位置、计算楼板标高的基础。配套目录中还有floor-placed-elevation.ts楼层放置标高、wall-spatial-grid.ts墙体空间网格等模块以及成体系的测试文件floor-placed-elevation.test.ts、wall-slab-overlap.test.ts等。编辑器架构编辑器在 viewer 之上扩展了以下能力。工具Tools工具通过工具栏激活处理特定操作的用户输入SelectTool选择与操控WallTool绘制墙体ZoneTool创建区域ItemTool放置家具/固定设施SlabTool创建楼板。选择管理器Selection Manager编辑器使用自定义选择管理器支持层级导航Site → Building → Level → Zone → Items每一层都有自己的悬停/点击选择策略。编辑器专属系统ZoneSystem根据层级显示模式控制区域可见性带节点聚焦功能的自定义相机控制。数据流从用户操作到几何更新的完整链路User Action (click, drag) ↓ Tool Handler ↓ useScene.createNode() / updateNode() ↓ Node added/updated in store Node marked dirty ↓ React re-renders NodeRenderer useRegistry() registers 3D object ↓ System detects dirty node (useFrame) Updates geometry via sceneRegistry Clears dirty flag这条链路完美呼应了前面各节工具写 store → store 标记脏 → React 挂载渲染器并注册 3D 对象 → 系统在帧循环中消费脏集合改几何。整个架构没有一处命令全局重绘的耦合点而是靠三个松散部件store、registry、systems协作完成。构建插件编辑器是可扩展的插件通过与内置节点完全相同的Pluginmanifest交付节点类型schema、3D/2D 渲染、放置工具、检查器参数化和左侧面板left-rail panels——没有独立的内部 API。README 特别指出新增节点类型与侧边栏面板应以插件形式交付而不是修改内置实现。从 packages/core/src/registry/registry.ts 可以看到插件注册的底层契约每个节点定义必须有非空kind与正整数schemaVersion重复 kind 在生产环境直接抛错插件作者契约保证两个插件都声明kind: couch是启动期错误而非静默覆盖在开发环境HMR则降级为警告并替换避免热重载时崩溃或遗留过期描述符。loadPlugin支持异步发现插件应用启动时通过动态导入 bootstrap注册表版本号机制确保挂载后加载的插件能被消费方感知。完整的手把手教程Plugin形态、面板贡献、发现机制、生命周期、v1 边界见 wiki/architecture/plugin-authoring.md示例插件pascalorg/plugin-trees提供了带程序化树木、花草与预设面板的独立实现可作为起步模板克隆。技术栈一览React 19Next.js 16Three.jsWebGPU rendererReact Three FiberDreiZustandstate managementZodschema validationZundoundo/redothree-bvh-csgBoolean geometry operationsTurborepomonorepo managementBunpackage manager开发与构建开发模式务必从根目录运行开发服务器以启用所有包的 hot reload# Install dependencies bun install # Run development server (builds packages starts editor with watch mode) bun dev # This will: # 1. Build pascal-app/core and pascal-app/viewer # 2. Start watching both packages for changes # 3. Start the Next.js editor dev server # Open http://localhost:3002关键提醒在根目录运行bun dev才能保证包级 watcher 处于运行状态编辑packages/core/src/或packages/viewer/src/时才能触发热重载。生产构建# Build all packages turbo build # Build specific package turbo build --filterpascal-app/core发布包# Build packages turbo build --filterpascal-app/core --filterpascal-app/viewer # Publish to npm npm publish --workspacepascal-app/core --access public npm publish --workspacepascal-app/viewer --access public关键文件索引PathDescriptionpackages/core/src/schema/base.tsBaseNode的 Zod schema 定义与 id 生成packages/core/src/schema各节点类型的 Zod schemawall、door、window、roof-segment 等packages/core/src/store/use-scene.ts场景状态 store、旧场景数据迁移逻辑packages/core/src/registry/registry.ts节点注册表、插件加载、注册表版本通知packages/core/src/hooks/scene-registry3D 对象注册表useRegistrypackages/core/src/hooks/spatial-grid/spatial-grid-manager.ts空间网格碰撞检测与放置校验packages/core/src/events/bus.ts类型化事件总线mittpackages/core/src/systems几何生成系统Wall/Slab/Ceiling/Roof/Itempackages/viewer/src/systems查看器级系统Level/Scan/Guide 等packages/mcp/src/server.tsMCP 服务器组装tools/resources/prompts/visionpackages/cli/src/index.tsCLI 公共 API进程管理、运行时安装、诊断packages/editor/src编辑工具、面板与选择管理组件apps/editor独立 Next.js 宿主应用参与贡献Bug 修复、功能、文档与想法均欢迎。先阅读 CONTRIBUTING.md 了解环境搭建、代码风格与 PR 流程新节点类型与侧边栏面板以插件形式交付示例见上文插件章节而非修改内置实现问题与想法走 Discussions可复现的 bug 走 Issues参与行为受 CODE_OF_CONDUCT.md 约束安全问题请走 SECURITY.md不要公开提交 issue。小结从部署链路看Pascal Editor 做到了一条命令起本地、一个协议连 AInpx pascal-app/cli editor同时交付编辑器与 MCP 服务技能包则把pascal-3d、furniture-fit等经过验证的建筑工作流带给 Claude Code、Codex 等 Agent。从架构链路看它用扁平节点字典 Zustand 注册表 脏节点系统 类型化事件总线构成了一个数据与渲染解耦、增量更新、类型安全的可扩展 3D 编辑内核而插件机制让第三方节点与面板无需改动内核即可接入。对于希望自建 3D 编辑产品或让 AI 直接操作建筑场景的开发者这个仓库的源码尤其是 packages/core 与 packages/mcp是极具参考价值的落地范本。【免费下载链接】editorOpen-source 3D architectural editor with a local CLI, MCP tools, and practical workflows for humans and AI agents.项目地址: https://gitcode.com/GitHub_Trending/editor93/editor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表