Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 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
a6acaa3
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 8, 2026
e0fec5b
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 10, 2026
7fa9276
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 13, 2026
c3ff9d6
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 14, 2026
00d84d5
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 19, 2026
2c2dabc
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 22, 2026
29f914f
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 22, 2026
1cbf910
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 23, 2026
8f59da7
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 24, 2026
650abd6
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 27, 2026
1f8dc8b
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 28, 2026
7c4b791
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Jul 31, 2026
ef7184f
Merge branch 'main' of github.com:sudhir9297/editor; branch 'main' of…
sudhir9297 Aug 10, 2026
c92a376
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Aug 12, 2026
da49781
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Aug 19, 2026
0f66e8f
Merge branch 'main' of github.com:sudhir9297/editor; branch 'main' of…
sudhir9297 Aug 21, 2026
0928a63
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Aug 24, 2026
f749d5d
feat(cabinets): improve modular sizing and ceiling finishes
sudhir9297 Aug 24, 2026
6366fe4
fix(cabinet): improve constrained run reflow
sudhir9297 Aug 25, 2026
51c52a7
fix(nodes): constrain modular cabinet reflow
sudhir9297 Aug 25, 2026
85476a4
chore(cabinet): format top finish changes
sudhir9297 Aug 25, 2026
33fe8b5
fix modular cabinet appliance and corner behavior
sudhir9297 Aug 26, 2026
c7ab1d9
fix cabinet schema version
sudhir9297 Aug 26, 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
58 changes: 57 additions & 1 deletion apps/editor/components/build-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { nodeRegistry } from '@pascal-app/core'
import {
CATALOG_ITEMS,
type FloorplanMode,
getFloorplanNodeExtension,
isFloorplanToolAvailableInMode,
Expand All @@ -12,6 +13,7 @@ import {
useFloorplanMode,
} from '@pascal-app/editor'
import { useLiquidLineToolOptions } from '@pascal-app/nodes'
import { useViewer } from '@pascal-app/viewer'
import Image from 'next/image'
import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react'
import {
Expand All @@ -38,7 +40,7 @@ type MepToolKind =
| 'pipe-trap'

type BuildType = {
/** Selection id — equals `kind` for tool types, `'painting'` for paint mode, `'mep'` for the MEP group. */
/** Selection id — equals `kind` for tool types, with dedicated ids for modes and groups. */
id: string
label: string
/** Raster asset tile (legacy Build sidebar artwork). */
Expand Down Expand Up @@ -72,6 +74,7 @@ const BASE_BUILD_TYPES: BuildType[] = [
{ id: 'column', label: 'Column', iconSrc: '/icons/column.webp', kind: 'column' },
{ id: 'shelf', label: 'Shelf', iconSrc: '/icons/shelf.webp', kind: 'shelf' },
{ id: 'spawn', label: 'Spawn Point', iconSrc: '/icons/spawn-point.webp', kind: 'spawn' },
{ id: 'kitchen', label: 'Kitchen', iconSrc: '/icons/kitchen.webp' },
// Group tile — no tool of its own; opens the MEP sub-grid below (like Roof).
{ id: 'mep', label: 'MEP', iconSrc: '/icons/HVAC.webp' },
{ id: 'painting', label: 'Painting', iconSrc: '/icons/paint.webp', mode: 'material-paint' },
Expand Down Expand Up @@ -129,6 +132,9 @@ const MEP_ITEMS: MepItem[] = [
{ id: 'pipe-segment', label: 'DWV Pipe', iconSrc: '/icons/dwv-pipes.webp', kind: 'pipe-segment' },
]

const MODULAR_CABINET_CATALOG_ITEM = CATALOG_ITEMS.find((item) => item.id === 'cabinet')
const MODULAR_CABINET_ICON = MODULAR_CABINET_CATALOG_ITEM?.thumbnail ?? '/icons/item.webp'

/**
* Activate a raw structure draw/cursor tool. Mirrors the editor's own
* structure-tool activation (`setPhase`/`setStructureLayer`/`setMode`/`setTool`).
Expand All @@ -153,6 +159,17 @@ function activateBuildTool(kind: string): void {
ed.setTool(kind)
}

function activateModularCabinetTool(): void {
const ed = useEditor.getState()
useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
if (MODULAR_CABINET_CATALOG_ITEM) ed.setSelectedItem(MODULAR_CABINET_CATALOG_ITEM)
ed.setPhase('structure')
ed.setStructureLayer('elements')
ed.setCatalogCategory(null)
ed.setMode('build')
ed.setTool('cabinet')
}

/** Enter material-paint mode — the Build tab's "Painting" category. */
function activatePaintMode(): void {
const ed = useEditor.getState()
Expand Down Expand Up @@ -275,10 +292,12 @@ export function BuildTab() {
const isRoofFeatureActive =
mode === 'build' && !!activeTool && roofFeatures.some((f) => f.kind === activeTool)
const isMepActive = mode === 'build' && !!activeTool && MEP_TOOL_KINDS.has(activeTool)
const isKitchenActive = mode === 'build' && activeTool === 'cabinet'

const isTypeActive = (type: BuildType) => {
if (type.mode) return mode === type.mode
if (type.id === 'mep') return isMepActive
if (type.id === 'kitchen') return isKitchenActive
if (type.id === 'roof')
return mode === 'build' && (activeTool === 'roof' || isRoofFeatureActive)
return mode === 'build' && activeTool === type.kind
Expand All @@ -293,6 +312,8 @@ export function BuildTab() {
// MEP is a group tile: arm its first tool so a usable tool is active
// (and we leave any prior paint mode), then reveal the MEP sub-grid.
activateBuildTool('duct-segment')
} else if (type.id === 'kitchen') {
activateModularCabinetTool()
} else if (type.kind) {
activateBuildTool(type.kind)
}
Expand Down Expand Up @@ -413,6 +434,41 @@ export function BuildTab() {
</div>
</TooltipProvider>
</div>
) : isKitchenActive ? (
<div className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto">
<div className="px-0.5 pt-1 font-medium text-muted-foreground text-xs">Kitchen</div>
<TooltipProvider delayDuration={0} disableHoverableContent>
<div
className="grid gap-1.5 px-0.5"
style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(56px, 1fr))' }}
>
<Tooltip>
<TooltipTrigger asChild>
<button
className="group relative flex aspect-square items-center justify-center rounded-xl bg-primary/10 p-1 ring-1 ring-primary/50 transition-all duration-200"
onClick={() => {
triggerSFX('sfx:menu-click')
activateModularCabinetTool()
}}
onMouseEnter={() => triggerSFX('sfx:menu-hover')}
type="button"
>
<Image
alt="Modular Cabinet"
className="size-full object-contain transition-transform duration-200 group-hover:scale-110"
height={48}
src={MODULAR_CABINET_ICON}
width={48}
/>
</button>
</TooltipTrigger>
<TooltipContent className="pointer-events-none" side="top">
Modular Cabinet
</TooltipContent>
</Tooltip>
</div>
</TooltipProvider>
</div>
) : isMepActive ? (
<div className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto">
<div className="px-0.5 pt-1 font-medium text-muted-foreground text-xs">MEP</div>
Expand Down
23 changes: 23 additions & 0 deletions apps/editor/lib/graph-schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, test } from 'bun:test'
import { CabinetModuleNode, CabinetNode } from '@pascal-app/core/schema'
import { apiGraphSchema } from './graph-schema'

function buildGraph(nodes: Record<string, unknown>, rootNodeIds: string[] = []) {
Expand Down Expand Up @@ -39,6 +40,28 @@ test('accepts a builtin container whose children include a plugin node id', () =
expect(apiGraphSchema.safeParse(graph).success).toBe(true)
})

test('accepts a cabinet run containing a derived L-corner run', () => {
const source = CabinetNode.parse({
id: 'cabinet_graph-source',
children: ['cabinet_graph-derived'],
})
const derived = CabinetNode.parse({
id: 'cabinet_graph-derived',
parentId: source.id,
children: ['cabinet-module_graph-derived'],
})
const module = CabinetModuleNode.parse({
id: 'cabinet-module_graph-derived',
parentId: derived.id,
})

expect(
apiGraphSchema.safeParse(
buildGraph({ [source.id]: source, [derived.id]: derived, [module.id]: module }, [source.id]),
).success,
).toBe(true)
})

test('keeps plugin child ids in the parsed graph', () => {
const graph = buildGraph({ [LEVEL_ID]: level([TREE_ID]), [TREE_ID]: pluginTree() }, [LEVEL_ID])

Expand Down
Loading
Loading