editor README

The project's README from github.com/pascalorg/editor. As one markdown file: README.md.

Pascal Editor

An open-source, local-first 3D building editor built with React Three Fiber and WebGPU. Run it in the browser or from the CLI, and connect AI agents through MCP.

MIT License npm @pascal-app/core npm @pascal-app/viewer npm @pascal-app/cli Discord X (Twitter)

https://github.com/user-attachments/assets/8b50e7cf-cebe-4579-9cf3-8786b35f7b6b

Run the Editor Locally

Node.js 22.13 or newer can create a persistent local Pascal installation without cloning this repository:

npx @pascal-app/cli editor

The CLI starts the editor and an authenticated MCP service in the background, selects collision-free loopback ports, and keeps projects in ~/.pascal/data/pascal.db. Configure an agent to launch pascal mcp connect. See Run Pascal locally for pnpm/Bun commands, project management, MCP setup, updates, storage paths, and troubleshooting. The npm release is the older runtime described below; use the verified GitHub preview when a task needs the new read-only furniture candidate check.

Verified CLI preview

The npm beta tag currently resolves to @pascal-app/[email protected], which predates the read-only furniture candidate input and hosted agent claim/status commands in this repository. To use those capabilities before the next npm release, install the verified GitHub prerelease built from commit 5dabbc3b56109c9f79dc8a378443a4c520d9ee0a:

PASCAL_PREVIEW_VERSION='1.0.0-beta.2.status.0'
PASCAL_PREVIEW_PREFIX="${XDG_DATA_HOME:-$HOME/.local/share}/pascal-preview"
PASCAL_PREVIEW_DOWNLOAD="$(mktemp -d)"
cd "$PASCAL_PREVIEW_DOWNLOAD"

curl --fail --location --remote-name \
  "https://github.com/pascalorg/editor/releases/download/cli-v1.0.0-beta.2-status.0/pascal-app-cli-${PASCAL_PREVIEW_VERSION}.tgz"
curl --fail --location --remote-name \
  "https://github.com/pascalorg/editor/releases/download/cli-v1.0.0-beta.2-status.0/SHA256SUMS.txt"

# macOS
shasum -a 256 -c SHA256SUMS.txt
# Linux: use `sha256sum -c SHA256SUMS.txt` instead.

npm install --global --prefix "$PASCAL_PREVIEW_PREFIX" --ignore-scripts \
  "./pascal-app-cli-${PASCAL_PREVIEW_VERSION}.tgz"
export PATH="$PASCAL_PREVIEW_PREFIX/bin:$PATH"
pascal --version
pascal update --version "$PASCAL_PREVIEW_VERSION"
pascal editor --no-open
# For an existing hosted autonomous-agent key:
PASCAL_API_KEY='sk_live_...' pascal agent claim
PASCAL_API_KEY='sk_live_...' pascal agent status --json

The expected archive SHA-256 is 15628baeeb174fb7786a1643db08f0554bf6d18afaaa3979f01922c5cd40019a. The same-version update command installs and activates this CLI's bundled runtime, restarting an older running service when necessary. Keep an existing PASCAL_HOME unchanged so stored projects remain in the same data directory; pascal editor alone reuses any healthy service, including an older one. Keep the preview prefix on the agent host's PATH before using the Claude plugin-provided connector, running pascal mcp setup claude or pascal mcp setup codex for another installation path, or configuring pascal mcp connect manually. pascal agent claim opens a prefilled 15-minute human handoff; pascal agent status verifies the key and reports the bounded claim state. Neither command stores or prints the hosted key. This GitHub prerelease is not an npm version.

Use one active agent client per local CLI service. The standalone local HTTP runtime shares active scene state between clients; use separate PASCAL_HOME directories and service processes when independent concurrent work is required.

Agent skills

Install Pascal's public agent workflows from this repository with skills.sh:

npx skills add pascalorg/editor \
  --skill pascal-3d \
  --skill furniture-fit

Claude Code users can install the same canonical skill source as a plugin:

/plugin marketplace add pascalorg/editor
/plugin install pascal-agent-skills@pascal

The Claude plugin also supplies the local pascal mcp connect server. Install and start the Pascal CLI first, and keep pascal on the PATH used to launch Claude Code. This local connector needs no Pascal account or API key and does not upload projects automatically.

Claude Code 2.1.258 loads both the user-scoped pascal server created by pascal mcp setup claude and the plugin-provided server. Remove the manual entry before reloading or restarting Claude Code so only the plugin owns the connection lifecycle:

claude mcp remove --scope user pascal

Use /mcp to remove or disable any project- or local-scoped Pascal connection too. Leaving both connections active violates the one-active-agent-client-per-local-service requirement. When the intended project is hosted in a Pascal account or organization, disable the plugin-provided local server in /mcp and configure the hosted endpoint from the skill setup guide instead.

Codex users can install the same plugin from the repository marketplace:

codex plugin marketplace add pascalorg/editor
codex plugin add pascal-agent-skills@pascal

OpenClaw installation becomes available after the skills are published under Pascal's ClawHub publisher. See skills/README.md for the owner-qualified install and verification commands.

pascal-3d covers safe local or hosted MCP setup and verified scene work. furniture-fit produces a bounded, evidence-based footprint assessment without claiming unsupported height, swing, or delivery checks. See skills/README.md for package details and validation.

The skills inspect the connected MCP tool schemas before using optional fields. A capability present in this repository may be absent from an older installed or hosted release; the agent should report the narrower supported result instead of assuming source-only inputs are available.

These workflows require a connected Pascal MCP server for their tool-backed actions. An OpenAI directory submission must therefore use With MCP and submit the production hosted MCP endpoint together with the skills. The repository package does not prove that the endpoint, OAuth flow, reviewer credentials, domain verification, or portal scan is ready for review.

MCP Registry

server.json is Pascal's manifest for the official MCP Registry. Its version tracks the hosted MCP implementation independently of the npm package version. Pull requests validate the manifest and production endpoint. A Pascal organization owner publishes an approved version from main with the official registry publisher.

Using Published Packages

The viewer runtime and built-in node definitions are separate packages. Install the full built-in viewer set, then load the built-in plugin once before mounting <Viewer>. Capture sessions are an optional transport-neutral extension:

npm install @pascal-app/core @pascal-app/viewer @pascal-app/editor @pascal-app/nodes
npm install @pascal-app/capture-protocol @pascal-app/capture-viewer
import { loadPlugin } from '@pascal-app/core'
import { builtinPlugin } from '@pascal-app/nodes'

await loadPlugin(builtinPlugin)

See the @pascal-app/viewer quick start for a React example.

Repository Architecture

This is a Turborepo monorepo with the reusable editor packages, the standalone app, and the CLI that distributes it:

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

Separation of Concerns

Package Responsibility
@pascal-app/core Node schemas, scene state (Zustand), registry contracts, spatial queries, and event bus
@pascal-app/viewer 3D rendering via React Three Fiber, shared render systems, default camera/controls, and post-processing
@pascal-app/capture-protocol Versioned capture manifests, normalized streams, and transport-neutral static/live sources
@pascal-app/capture-viewer Viewer child runtime and reference model, device-motion, and point-cloud layers
@pascal-app/editor Editing tools, panels, selection, and direct-manipulation UI
@pascal-app/nodes Built-in registry plugin with node definitions, renderers, geometry, and systems
@pascal-app/cli Installs and manages a versioned standalone editor runtime and persistent local data
@pascal-app/mcp Exposes scene tools, resources, prompts, and local storage to MCP-compatible AI hosts
apps/editor Standalone Next.js host for the editor packages

The viewer renders the scene with sensible defaults. The editor extends it with interactive tools, selection management, and editing capabilities.

Stores

Each package has its own Zustand store for managing state:

Store Package Responsibility
useScene @pascal-app/core Scene data: nodes, root IDs, dirty nodes, CRUD operations. Persisted to IndexedDB with undo/redo via Zundo.
useViewer @pascal-app/viewer Viewer state: current selection (building/level/zone IDs), level display mode (stacked/exploded/solo), camera mode.
useEditor apps/editor Editor state: active tool, structure layer visibility, panel states, editor-specific preferences.

Access patterns:

// 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' })

Core Concepts

Nodes

Nodes are the data primitives that describe the 3D scene. All nodes extend BaseNode:

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 })
}

Node Hierarchy:

Site
└── Building
    └── Level
        ├── Wall → Item (doors, windows)
        ├── Slab
        ├── Ceiling → Item (lights)
        ├── Roof
        ├── Zone
        ├── Scan (3D reference)
        └── Guide (2D reference)

Nodes are stored in a flat dictionary (Record<id, Node>), not a nested tree. Parent-child relationships are defined via parentId and children arrays.


Scene State (Zustand Store)

The scene is managed by a Zustand store in @pascal-app/core:

useScene.getState() = {
  nodes: Record<id, AnyNode>,  // All nodes
  rootNodeIds: string[],       // Top-level nodes (sites)
  dirtyNodes: Set<string>,     // Nodes pending system updates

  createNode(node, parentId),
  updateNode(id, updates),
  deleteNode(id),
}

Middleware:


Scene Registry

The registry maps node IDs to their Three.js objects for fast lookup:

sceneRegistry = {
  nodes: Map<id, Object3D>,    // ID → 3D object
  byType: {
    wall: Set<id>,
    item: Set<id>,
    zone: Set<id>,
    // ...
  }
}

Renderers register their refs using the useRegistry hook:

const ref = useRef<Mesh>(null!)
useRegistry(node.id, 'wall', ref)

This allows systems to access 3D objects directly without traversing the scene graph.


Node Renderers

Renderers are React components that create Three.js objects for each node type:

SceneRenderer
└── NodeRenderer (dispatches by type)
    ├── BuildingRenderer
    ├── LevelRenderer
    ├── WallRenderer
    ├── SlabRenderer
    ├── ZoneRenderer
    ├── ItemRenderer
    └── ...

Pattern:

  1. Renderer creates a placeholder mesh/group
  2. Registers it with useRegistry
  3. Systems update geometry based on node data

Example (simplified):

const WallRenderer = ({ node }) => {
  const ref = useRef<Mesh>(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>
  )
}

Systems

Systems are React components that run in the render loop (useFrame) to update geometry and transforms. They process dirty nodes marked by the store.

Core Systems (in @pascal-app/core):

System Responsibility
WallSystem Generates wall geometry with mitering and CSG cutouts for doors/windows
SlabSystem Generates floor geometry from polygons
CeilingSystem Generates ceiling geometry
RoofSystem Generates roof geometry
ItemSystem Positions items on walls, ceilings, or floors (slab elevation)

Viewer Systems (in @pascal-app/viewer):

System Responsibility
LevelSystem Handles level visibility and vertical positioning (stacked/exploded/solo modes)
ScanSystem Controls 3D scan visibility
GuideSystem Controls guide image visibility

Processing Pattern:

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)
  }
})

Dirty Nodes

When a node changes, it's marked as dirty in useScene.getState().dirtyNodes. Systems check this set each frame and only recompute geometry for dirty nodes.

// 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

Manual marking:

useScene.getState().dirtyNodes.add(wallId)

Event Bus

Inter-component communication uses a typed event emitter (mitt):

// 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
}

Spatial Grid Manager

Handles collision detection and placement validation:

spatialGridManager.canPlaceOnFloor(levelId, position, dimensions, rotation)
spatialGridManager.canPlaceOnWall(wallId, t, height, dimensions)
spatialGridManager.getSlabElevationAt(levelId, x, z)

Used by item placement tools to validate positions and calculate slab elevations.


Editor Architecture

The editor extends the viewer with:

Tools

Tools are activated via the toolbar and handle user input for specific operations:

Selection Manager

The editor uses a custom selection manager with hierarchical navigation:

Site → Building → Level → Zone → Items

Each depth level has its own selection strategy for hover/click behavior.

Editor-Specific Systems


Data Flow

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

Building a Plugin

The editor is extensible: a plugin ships node kinds (schema, 3D/2D rendering, placement tools, inspector parametrics) and left-rail panels through the same Plugin manifest the built-ins use — there is no separate internal API.


Technology Stack


Getting Started

Development

Run the development server from the root directory to enable hot reload for all packages:

# 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

Important: Always run bun dev from the root directory to ensure the package watchers are running. This enables hot reload when you edit files in packages/core/src/ or packages/viewer/src/.

Building for Production

# Build all packages
turbo build

# Build specific package
turbo build --filter=@pascal-app/core

Publishing Packages

# Build packages
turbo build --filter=@pascal-app/core --filter=@pascal-app/viewer

# Publish to npm
npm publish --workspace=@pascal-app/core --access public
npm publish --workspace=@pascal-app/viewer --access public

Key Files

Path Description
packages/core/src/schema/ Node type definitions (Zod schemas)
packages/core/src/store/use-scene.ts Scene state store
packages/core/src/hooks/scene-registry/ 3D object regi

editor (pascalorg/editor)

Searching Database…

24.9K ·GitHub
这是干什么的?Pascal Editor 是一个开源、本地优先的 3D 建筑编辑器:你可以在浏览器渲染的场景里画墙、楼板、分区和家具,AI 智能体也能通过 MCP 驱动同一个场景。
它是我访问的网站,还是需要安装的软件?两者都是。编辑器界面在浏览器里运行,同时本地的 CLI 服务会在后台启动编辑器和一个经过身份验证的 MCP 服务,并把你的工作保存在磁盘上。
怎么在我自己的电脑上把它跑起来?从 npm 安装 @pascal-app/cli——需要 Node.js 22.13 或更新版本,不需要克隆代码仓库。CLI 会完成一次持久的本地安装,运行 'pascal editor' 即可启动编辑器和它的后台 MCP 服务。
哪些 AI 智能体能连上它,怎么连?任何兼容 MCP 的宿主都行。Claude Code 和 Codex 支持一条命令完成配置('pascal mcp setup claude' 或 'pascal mcp setup codex'),还有应用市场插件;pascal-3d 和 furniture-fit 这两个智能体技能可以通过 skills.sh 安装。
它稳定到可以放心依赖吗?它目前还是 beta:npm 的 beta 标签(1.0.0-beta.1)落后于最新功能——只读的家具候选检查,以及托管的智能体 claim/status 命令——这些功能请使用经过验证的 GitHub 预发布版。另外,每个本地 CLI 服务同时只能运行一个活跃的智能体客户端。
会有任何数据离开我的电脑吗?不需要账号或 API 密钥,也不会自动上传任何内容:项目保存在本地数据库 ~/.pascal/data/pascal.db 中,场景持久化到 IndexedDB,支持 50 步撤销/重做。
我到底能用它建模什么?建筑:可以生成带自动门洞和窗洞的墙体(CSG 布尔几何)、楼板、天花板、屋顶、分区,以及摆放在墙面、天花板或地面上的家具。楼层可以叠放显示、展开显示或单独显示。
我能添加自己的对象类型和工具吗?可以——插件可以通过与内置工具相同的 Plugin manifest 提供节点类型、摆放工具、检查器参数和左侧栏面板,不需要单独的内部 API。pascalorg/plugin-trees 仓库就是一个现成的示例,可以克隆下来作为起点。
pascal-3d 和 furniture-fit 这两个技能是做什么的?它们是配合 MCP 服务器一起安装的智能体工作流。pascal-3d 涵盖安全的本地或托管场景操作;furniture-fit 会对某件物品是否放得下给出有边界、有依据的占地评估,不会声称做它并不支持的高度、开合或配送检查。
它是用什么构建的?React 19 + Next.js 16,通过 React Three Fiber 使用 Three.js 及其 WebGPU 渲染器,用 Zustand 存储配合 Zundo 实现撤销/重做,Zod 模式校验,three-bvh-csg 做布尔几何——整体组织为由 Bun 管理的 Turborepo monorepo。
Creator
pascalorg
Size
163.9 MB
Stars
24.9K
Age
1y
Commits · 90 Days