Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3731eb3
Add roof surface placement support for items
sudhir9297 May 18, 2026
ed53bc2
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 20, 2026
fd8e02c
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 20, 2026
7c1e383
fixed conflict
sudhir9297 May 20, 2026
b3377da
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 20, 2026
f177a65
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 22, 2026
9af7491
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 22, 2026
fd27524
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 27, 2026
b516298
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 May 28, 2026
ebfc8ce
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 3, 2026
b7b313b
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 4, 2026
b2ad645
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 4, 2026
bffdb4a
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 8, 2026
ee7b10c
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 9, 2026
7d4b474
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 10, 2026
3a3318c
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 13, 2026
26df69f
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 17, 2026
5376e07
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 22, 2026
d2204aa
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 23, 2026
f2a5186
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jun 29, 2026
5841052
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 1, 2026
3b2e774
Add modular cabinet node
sudhir9297 Jul 1, 2026
92f2c25
Add modular cabinet run and tall cabinet workflows
sudhir9297 Jul 1, 2026
2b5964e
Add cabinet paint slots and material groups
sudhir9297 Jul 1, 2026
b1a7764
Improve modular cabinet presets and wall snapping
sudhir9297 Jul 2, 2026
0c64b77
Improve cabinet wall snapping
sudhir9297 Jul 2, 2026
21e6195
Fix cabinet wall snapping and countertop joins
sudhir9297 Jul 2, 2026
c6f3271
Improve cabinet appliance compartments
sudhir9297 Jul 2, 2026
b01df0e
Improve fridge visuals and smooth cabinet animations
sudhir9297 Jul 2, 2026
9608792
Improve gas hob flames and cooktop knob controls
sudhir9297 Jul 3, 2026
9ccfaad
Fix cabinet UVs and registry actions
sudhir9297 Jul 3, 2026
5691212
Merge remote-tracking branch 'upstream/main' into fix/wed-jul-1
sudhir9297 Jul 3, 2026
28eb570
Fix registry move snap typecheck
sudhir9297 Jul 3, 2026
eb171f9
Fix cabinet knob animation concurrency
sudhir9297 Jul 3, 2026
a2ab25d
Fix cabinet material disposal, undo flood, and 2D move parity
sudhir9297 Jul 4, 2026
89a284c
Restructure cabinet schema and centralize run math/mutations
sudhir9297 Jul 4, 2026
7b50f09
Add cabinet snap tests and split the cabinet panel
sudhir9297 Jul 4, 2026
306a87d
Split cabinet geometry.ts into per-appliance modules
sudhir9297 Jul 4, 2026
113c94d
Add undermount sink compartment with CSG-cut countertops
sudhir9297 Jul 5, 2026
4b9ba64
Update ifc-converter next-env reference to build-mode route types
sudhir9297 Jul 5, 2026
45cee0e
Draw cabinet floor plans with kitchen drafting symbols
sudhir9297 Jul 6, 2026
d528ec1
Play cabinet open/close animation on the E interaction key
sudhir9297 Jul 6, 2026
36f84d3
Add kitchen island and bar counter support to cabinet runs
sudhir9297 Jul 6, 2026
fff3cf5
Merge quick-action arrow and plus into one directional add glyph
sudhir9297 Jul 6, 2026
a2d63cb
Extend plugin discovery instead of replacing the host source
sudhir9297 Jul 6, 2026
ccaeae1
Add L-corner runs to modular cabinets with run-scoped grouping
sudhir9297 Jul 6, 2026
845cb01
Delete emptied cabinet run groups in the same delete gesture
sudhir9297 Jul 6, 2026
ea97e3a
Remove filler front gap treatment
sudhir9297 Jul 7, 2026
67d67e2
Apply architecture review fixes across cabinet, plugin-trees, and fra…
sudhir9297 Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions apps/editor/lib/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {
type AnyNodeDefinition,
discoverPlugins,
extendPluginDiscovery,
loadPlugin,
nodeRegistry,
registerNode,
setPluginDiscovery,
} from '@pascal-app/core'
import { builtinPlugin } from '@pascal-app/nodes'
import { treesPlugin } from '@pascal-app/plugin-trees'
Expand Down Expand Up @@ -80,9 +80,8 @@ export async function loadExternalPlugins(): Promise<void> {
}

// Register the first-party example plugin (trees node + presets rail panel)
// through the same discovery hook a third-party pack would use. Must be set
// before `loadExternalPlugins()` reads it below.
setPluginDiscovery(async () => [treesPlugin])
// alongside any host-provided discovery source instead of replacing it.
extendPluginDiscovery(async () => [treesPlugin])

loadBuiltinsSync()
void loadExternalPlugins()
2 changes: 1 addition & 1 deletion apps/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "module",
"private": true,
"scripts": {
"dev": "dotenv -e ../../.env.local -- next dev --port 3002",
"dev": "dotenv -e ../../.env.local -- next dev --port ${PORT:-3002}",
"build": "dotenv -e ../../.env.local -- next build",
"start": "next start",
"lint": "biome lint",
Expand Down
2 changes: 1 addition & 1 deletion apps/ifc-converter/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
import "./.next/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
18 changes: 17 additions & 1 deletion packages/core/src/events/bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { Object3D } from 'three'
import type {
BoxVentNode,
BuildingNode,
CabinetModuleNode,
CabinetNode,
CeilingNode,
ChimneyNode,
ColumnNode,
Expand Down Expand Up @@ -89,6 +91,8 @@ export type FenceEvent = NodeEvent<FenceNode>
export type ItemEvent = NodeEvent<ItemNode>
export type SiteEvent = NodeEvent<SiteNode>
export type BuildingEvent = NodeEvent<BuildingNode>
export type CabinetEvent = NodeEvent<CabinetNode>
export type CabinetModuleEvent = NodeEvent<CabinetModuleNode>
export type LevelEvent = NodeEvent<LevelNode>
export type ZoneEvent = NodeEvent<ZoneNode>
export type ShelfEvent = NodeEvent<ShelfNode>
Expand Down Expand Up @@ -246,9 +250,20 @@ type RoomPresetEvents = {
'room-preset:create': RoomPresetCreateEvent
}

type SelectionEvents = {
/**
* "Reveal this node" intent — the editor's node action menu emits it with the
* selected node; whoever owns the node's catalog/panel (host browser, a
* plugin's presets panel) listens and reveals it.
*/
'selection:find-node': AnyNode
}

type EditorEvents = GridEvents &
NodeEvents<'wall', WallEvent> &
NodeEvents<'fence', FenceEvent> &
NodeEvents<'cabinet', CabinetEvent> &
NodeEvents<'cabinet-module', CabinetModuleEvent> &
NodeEvents<'item', ItemEvent> &
NodeEvents<'site', SiteEvent> &
NodeEvents<'building', BuildingEvent> &
Expand Down Expand Up @@ -296,6 +311,7 @@ type EditorEvents = GridEvents &
ThumbnailEvents &
SnapshotEvents &
AIChatEvents &
RoomPresetEvents
RoomPresetEvents &
SelectionEvents

export const emitter = mitt<EditorEvents>()
4 changes: 4 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export type {
BoxVentEvent,
BuildingEvent,
CabinetEvent,
CabinetModuleEvent,
CameraControlEvent,
CameraControlFitSceneEvent,
CeilingEvent,
Expand Down Expand Up @@ -73,6 +75,7 @@ export {
segmentsIntersect,
} from './lib/polygon-relations'
export { getRenderableSlabPolygon } from './lib/slab-polygon'
export { resolveSelectionProxyId, selectionProxyIdFromMetadata } from './lib/selection-proxy'
export {
deriveSlotId,
isSlotMaterialName,
Expand Down Expand Up @@ -103,6 +106,7 @@ export {
type WallSegment,
type WallSegmentClosest,
} from './lib/wall-distance'
export { isMovable, movePlanToward, moveToward, resolveMovable } from './services/movement'
export {
getCatalogMaterialById,
getLibraryMaterialIdFromRef,
Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/lib/selection-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { AnyNode, AnyNodeId } from '../schema/types'

export function selectionProxyIdFromMetadata(metadata: unknown): AnyNodeId | null {
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) return null
const value = (metadata as Record<string, unknown>).nodeSelectionProxyId
return typeof value === 'string' ? (value as AnyNodeId) : null
}

export function resolveSelectionProxyId(
node: AnyNode,
nodes: Readonly<Record<string, AnyNode | undefined>>,
): AnyNodeId {
const proxyId = selectionProxyIdFromMetadata((node as { metadata?: unknown }).metadata)
if (!proxyId || !nodes[proxyId]) return node.id as AnyNodeId
return proxyId
}
14 changes: 13 additions & 1 deletion packages/core/src/registry/handles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,11 @@ export type Cursor = 'ew-resize' | 'ns-resize' | 'move' | 'grab' | 'grabbing'
export type HandleDecoration<N> = {
kind: 'ring'
/** Node-local radius of the ring (XZ plane). */
radius: (node: N) => number
radius: (node: N, sceneApi: SceneApi) => number
/** Node-local Y of the ring. Defaults to 0. */
y?: (node: N) => number
/** Node-local center of the ring. Defaults to the node origin. */
center?: (node: N, sceneApi: SceneApi) => readonly [number, number, number]
}

/**
Expand All @@ -127,6 +129,16 @@ export type LinearResizeHandle<N> = {
anchor: HandleAnchor
currentValue: (node: N) => number
apply: (node: N, newValue: number, sceneApi: SceneApi) => Partial<N>
/** Optional live-scene visibility gate for context-dependent arrows. */
visible?: (node: N, sceneApi: SceneApi) => boolean
/**
* Optional committed-write hook. The generic handle renderer previews the
* selected node with the `apply` patch during drag; on release it normally
* writes that patch back to the same node. Composite nodes can override the
* final write here to fan the resize out to siblings / parents while keeping
* the handle UI generic.
*/
commit?: (node: N, patch: Partial<N>, sceneApi: SceneApi) => void
/**
* Optional per-tick hook fired while this handle is being dragged, with the
* live (in-progress, override-merged) node. A pure side-channel for transient
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type {
export {
bakePolicyOf,
discoverPlugins,
extendPluginDiscovery,
getHostRefFields,
getSelectableKinds,
hasRegistry3DMoveTool,
Expand Down Expand Up @@ -88,12 +89,18 @@ export type {
KeyboardAction,
KeyboardActions,
LazyComponent,
LiveTransformLike,
McpOverrides,
Modifiers,
MovableConfig,
MovableParentFrame,
NodeCategory,
NodeDefinition,
NodePort,
NodeQuickAction,
NodeQuickActionIcon,
NodeQuickActionProvider,
NodeQuickActionResult,
NodeRegistry,
PaintCapability,
PaintEffectiveMaterialArgs,
Expand All @@ -113,6 +120,7 @@ export type {
RoofAccessoryConfig,
RotatableConfig,
ScalableConfig,
SceneActionCapability,
SceneApi,
SelectableConfig,
SlotDeclaration,
Expand Down
39 changes: 30 additions & 9 deletions packages/core/src/registry/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,15 @@ export function registerNode(def: AnyNodeDefinition): void {
}

/**
* Registry of left-rail panels contributed by plugins. Same add-only,
* duplicate-id-throws semantics as the node registry, with one difference: it
* is *observable*. Plugin discovery runs asynchronously after the first React
* render (see `loadExternalPlugins`), so the sidebar subscribes via
* {@link PanelRegistryImpl.subscribe} / {@link PanelRegistryImpl.getSnapshot}
* (the `useSyncExternalStore` contract) and re-renders when a panel lands.
* `getSnapshot` returns a stable array reference between registrations so the
* subscribing component doesn't re-render in a loop.
* Registry of UI panels contributed by plugins; the host decides where they
* surface. Same add-only, duplicate-id-throws semantics as the node registry,
* with one difference: it is *observable*. Plugin discovery runs
* asynchronously after the first React render (see `loadExternalPlugins`), so
* the host UI subscribes via {@link PanelRegistryImpl.subscribe} /
* {@link PanelRegistryImpl.getSnapshot} (the `useSyncExternalStore` contract)
* and re-renders when a panel lands. `getSnapshot` returns a stable array
* reference between registrations so the subscribing component doesn't
* re-render in a loop.
*/
class PanelRegistryImpl {
private readonly panels = new Map<string, PluginPanel>()
Expand Down Expand Up @@ -346,7 +347,9 @@ export async function loadPlugin(plugin: Plugin): Promise<void> {
*/
export type PluginDiscovery = () => Promise<Plugin[]>

let pluginDiscovery: PluginDiscovery = async () => []
const defaultPluginDiscovery: PluginDiscovery = async () => []

let pluginDiscovery: PluginDiscovery = defaultPluginDiscovery

/**
* Replace the plugin discovery implementation. Call once at app startup
Expand All @@ -359,9 +362,27 @@ let pluginDiscovery: PluginDiscovery = async () => []
* gate + duplicate-kind protection applies.
*/
export function setPluginDiscovery(fn: PluginDiscovery): void {
if (isDevMode() && pluginDiscovery !== defaultPluginDiscovery) {
console.warn(
'[registry] setPluginDiscovery replaced an existing discovery chain — plugins registered earlier (e.g. via extendPluginDiscovery) are dropped. Use extendPluginDiscovery to compose instead.',
)
}
pluginDiscovery = fn
}

/**
* Extend the current plugin discovery instead of replacing it. Useful for app-
* bundled example or first-party plugins that should load alongside any host-
* provided discovery source, not clobber it.
*/
export function extendPluginDiscovery(fn: PluginDiscovery): void {
const previous = pluginDiscovery
pluginDiscovery = async () => {
const [base, extra] = await Promise.all([previous(), fn()])
return [...base, ...extra]
}
}

/**
* Run the active plugin discovery and return the discovered plugins.
* Bootstrap code is expected to call this after `loadPlugin(builtinPlugin)`
Expand Down
Loading
Loading