Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 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
d9f8be9
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Aug 31, 2026
f944cfe
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Sep 1, 2026
d14666e
fix: pass nodes to lazy inspector panels
sudhir9297 Sep 1, 2026
f02b3b0
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Sep 2, 2026
4b07173
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Sep 4, 2026
c8eb97a
feat: add immersive WebXR editor support
sudhir9297 Sep 7, 2026
0bc6996
Merge remote-tracking branch 'upstream/main'
sudhir9297 Sep 9, 2026
922f5cd
chore: remove WebXR integration
sudhir9297 Sep 9, 2026
f984371
chore: remove WebXR support
sudhir9297 Sep 9, 2026
24d73fb
Merge branch 'main' of github.com:pascalorg/editor
sudhir9297 Sep 11, 2026
8f57607
feat(nodes): improve duct and pipe wall drawing
sudhir9297 Sep 11, 2026
a53e527
Merge remote-tracking branch 'upstream/main'
sudhir9297 Sep 13, 2026
3ccc7d6
Merge branch 'main' into t3code/improve-duct-piping-wall-drawing
sudhir9297 Sep 13, 2026
ab2d983
fix(editor): improve parametric inspector controls
sudhir9297 Sep 13, 2026
e9e2820
build: pin pool plugin to github commit
sudhir9297 Sep 15, 2026
bde47fe
fix: pin pool plugin typecheck fix
sudhir9297 Sep 15, 2026
1424ed9
fix(nodes): keep distribution runs on intended wall plane
sudhir9297 Sep 15, 2026
c02bca2
fix: update pool plugin to latest commit
sudhir9297 Sep 15, 2026
cd436b2
fix: pin pool plugin to type-safe latest commit
sudhir9297 Sep 15, 2026
a721d13
fix(editor): restore portable switch styling
sudhir9297 Sep 15, 2026
af4904d
Fix distribution run surface placement
sudhir9297 Sep 16, 2026
98a1d28
Fix plugin integration and duct run interactions
sudhir9297 Sep 16, 2026
cdf2cbc
Fix Pool Plugin resolution in CI
sudhir9297 Sep 16, 2026
2021f85
fix(editor): vendor updated pool plugin
sudhir9297 Sep 16, 2026
0a1014e
fix(editor): consume pool plugin from GitHub
sudhir9297 Sep 16, 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
2 changes: 1 addition & 1 deletion apps/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@pascal-app/nodes": "*",
"@pascal-app/plugin-bones": "github:pascalorg/plugin-bones#5679260261ee1c733656ff6dfb99e30bb24b58a7",
"@pascal-app/plugin-environment": "github:AxiomeCG/environment#40baf63ddd06a657aaa0f60e1559fd9ad561f295",
"@pascal-app/plugin-pool": "file:./vendor/pascal-app-plugin-pool-0.1.0-connection-status.tgz",
"@pascal-app/plugin-pool": "github:sudhir9297/pool-pascal-plugin#483a080558293cd344170e66318230e2ac9b6ae8",
"@pascal-app/plugin-streetscape": "github:sudhir9297/streetscape-pascal-plugin#1c04ec9ccb3fa8124ec56dfc1026567cbbc51aef",
"@pascal-app/plugin-trees": "github:pascalorg/plugin-trees#56d978cd9b409b716207b3f3d269455d3cd6f067",
"@pascal-app/viewer": "*",
Expand Down
Binary file not shown.
5 changes: 3 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/core/src/registry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2455,6 +2455,8 @@ export type ParamAction<N> = {
export type ParamGroup<N> = {
label: string
fields: ParamField<N>[]
/** Whether this inspector group is open when it is first rendered. */
defaultExpanded?: boolean
}

export type ParamField<N> =
Expand Down
7 changes: 3 additions & 4 deletions packages/editor/src/components/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1347,10 +1347,9 @@ function EditorContent({

// Load on mount, project switches, and explicit retry attempts.
useEffect(() => {
void sceneLoadAttempt
let cancelled = false

async function load() {
async function load(attempt: number) {
isLoadingSceneRef.current = true
setSceneLoadError(null)
setHasLoadedInitialScene(false)
Expand All @@ -1364,7 +1363,7 @@ function EditorContent({
let failed = false
try {
const sceneGraph = onLoad ? await onLoad() : loadSceneFromLocalStorage()
if (!cancelled) {
if (!cancelled && attempt === sceneLoadAttempt) {
applySceneGraphToEditor(sceneGraph)
setIsViewerSceneReady(false)
setSceneReadyKey((key) => key + 1)
Expand All @@ -1390,7 +1389,7 @@ function EditorContent({
}
}

load()
load(sceneLoadAttempt)

return () => {
cancelled = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ export function MultiParametricInspector({ footer }: { footer?: React.ReactNode
)}
{parametrics.groups.map((group, gi) => (
<MultiGroupFields
defaultExpanded={group.defaultExpanded}
fields={group.fields as ParamField<AnyNode>[]}
key={`group-${gi}`}
key={`${nodeType}-${group.label}-${gi}`}
nodeIds={nodeIds}
nodeType={nodeType}
parametrics={parametrics}
Expand All @@ -88,12 +89,14 @@ export function MultiParametricInspector({ footer }: { footer?: React.ReactNode
}

function MultiGroupFields({
defaultExpanded,
title,
fields,
nodeIds,
nodeType,
parametrics,
}: {
defaultExpanded?: boolean
title: string
fields: ParamField<AnyNode>[]
nodeIds: AnyNodeId[]
Expand All @@ -108,7 +111,7 @@ function MultiGroupFields({
)
if (genericFields.length === 0 || !anyVisible) return null
return (
<PanelSection title={title}>
<PanelSection defaultExpanded={defaultExpanded} title={title}>
{genericFields.map((field, fi) => {
if (
String(field.key) === 'height' &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ export function ParametricInspector({
width={320}
>
{parametrics.groups.map((group, gi) => (
<PanelSection key={`group-${gi}`} title={group.label}>
<PanelSection
defaultExpanded={group.defaultExpanded}
key={`${nodeType}-${group.label}-${gi}`}
title={group.label}
>
{group.fields.map((field, fi) => (
<FieldRenderer
key={`field-${gi}-${fi}-${String(field.key)}`}
Expand All @@ -175,7 +179,7 @@ export function ParametricInspector({
)}
{(canMove || canDelete || (parametrics.actions && parametrics.actions.length > 0)) && (
<PanelSection title="Actions">
<ActionGroup className={isZone ? 'flex-col' : undefined}>
<ActionGroup className={isZone ? 'flex-col' : parametrics.actions?.length ? 'grid grid-cols-2 gap-2' : undefined}>
{canMove && (
<ActionButton icon={<Move className="h-4 w-4" />} label="Move" onClick={handleMove} />
)}
Expand Down
1 change: 1 addition & 0 deletions packages/nodes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@pascal-app/editor": "^1.0.0",
"@pascal-app/viewer": "^1.0.0",
"@pascal/typescript-config": "*",
"@react-three/test-renderer": "^9.1.0",
"@types/bun": "^1.3.0",
"@types/node": "^22.19.12",
"@types/react": "^19.2.2",
Expand Down
22 changes: 21 additions & 1 deletion packages/nodes/src/duct-segment/draw-plan.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import { expect, test } from 'bun:test'
import { DuctSegmentNode, useScene } from '@pascal-app/core'
import { planDuctDraw } from './tool'
import { ductSurfaceClearanceM, planDuctDraw } from './tool'

const profile = { shape: 'round' as const, diameter: 6, width: 12, height: 8 }

test('keeps rectangular and oval ducts outside wall faces using their largest dimension', () => {
expect(ductSurfaceClearanceM({ shape: 'round', diameter: 6, width: 12, height: 8 })).toBeCloseTo(
0.0762,
)
expect(
ductSurfaceClearanceM({ shape: 'rect', diameter: 6, width: 14, height: 8 }, true),
).toBeCloseTo(0.1878)
expect(
ductSurfaceClearanceM({ shape: 'oval', diameter: 6, width: 14, height: 8 }, true),
).toBeCloseTo(0.1878)
})

test('floor and ceiling clearance uses the height of rectangular and oval ducts', () => {
for (const shape of ['rect', 'oval'] as const) {
expect(ductSurfaceClearanceM({ shape, diameter: 6, width: 14, height: 8 })).toBeCloseTo(0.1016)
expect(ductSurfaceClearanceM({ shape, diameter: 6, width: 20, height: 8 })).toBeCloseTo(0.1016)
}
})

test('a short existing run cannot silently lose its required elbow', () => {
const node = DuctSegmentNode.parse({
path: [
Expand Down
7 changes: 5 additions & 2 deletions packages/nodes/src/duct-segment/parametrics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type DuctFittingNode, type ParametricDescriptor, useScene } from '@pascal-app/core'
import { Vector3 } from 'three'
import { getDuctFittingPorts } from '../duct-fitting/ports'
import { findAutomaticRunEndCapIds } from '../shared/automatic-run-end-cap'
import { fittingDeletionPlansForRun } from '../shared/fitting-deletion-cleanup'
import { rollToContinueAcrossElbow } from './geometry'
import type { DuctSegmentNode } from './schema'
Expand Down Expand Up @@ -105,10 +106,12 @@ export const ductSegmentParametrics: ParametricDescriptor<DuctSegmentNode> = {
fittingDeletionPlansForRun(duct, nodes, requestedDeleteIds, true).flatMap(
(plan) => plan.updates,
),
onDeleteCascade: (duct, nodes, _pendingDeleteIds, requestedDeleteIds) =>
fittingDeletionPlansForRun(duct, nodes, requestedDeleteIds, false).flatMap((plan) =>
onDeleteCascade: (duct, nodes, _pendingDeleteIds, requestedDeleteIds) => [
...findAutomaticRunEndCapIds(duct.id, nodes, 'duct-fitting'),
...fittingDeletionPlansForRun(duct, nodes, requestedDeleteIds, false).flatMap((plan) =>
plan.deleteFitting ? [plan.fittingId, ...plan.cascadeDeleteIds] : [],
),
],
trailingSection: () => import('../shared/run-hanger-inspector'),
groups: [
{
Expand Down
25 changes: 15 additions & 10 deletions packages/nodes/src/duct-segment/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { rectSectionAxes, rollToContinueAcrossElbow } from './geometry'
*/
const DUCT_DIAMETERS_IN = [4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20] as const
const BODY_SNAP_RADIUS_M = 0.35
const DUCT_WALL_STANDOFF_M = 0.01
/** Angle step (radians) for the XZ angle lock — 45°. */

/**
Expand Down Expand Up @@ -188,6 +189,18 @@ type DraftProfile = {
height: number
}

export function ductSurfaceClearanceM(profile: DraftProfile, wall = false): number {
return (
runSectionHalfSizeM(
profile.shape === 'round'
? profile.diameter
: wall
? Math.max(profile.width, profile.height)
: profile.height,
) + (wall && profile.shape !== 'round' ? DUCT_WALL_STANDOFF_M : 0)
)
}

/**
* Profile to inherit when the segment start snaps onto `port` — joining
* means continuing that thing: a rect trunk end keeps its W×H, a round
Expand Down Expand Up @@ -553,13 +566,7 @@ const DuctSegmentTool = () => {
findBody: (point) =>
findNearestRunBody3D(point, BODY_SNAP_RADIUS_M, { levelId: activeLevelId ?? undefined }),
surfaceClearance: (surface) =>
surface
? runSectionHalfSizeM(
profileRef.current.shape === 'round'
? profileRef.current.diameter
: profileRef.current.height,
)
: 0,
surface ? ductSurfaceClearanceM(profileRef.current, surface.kind === 'wall') : 0,
minimumSegmentLength: 0.08,
inheritFromConnection: ({ port }) => {
if (!port) return
Expand Down Expand Up @@ -593,9 +600,7 @@ const DuctSegmentTool = () => {
node.path[0]!,
node.path.at(-1)!,
surfaceTarget,
profileRef.current.shape === 'round'
? runSectionHalfSizeM(profileRef.current.diameter)
: runSectionHalfSizeM(profileRef.current.height),
ductSurfaceClearanceM(profileRef.current, true),
)
: undefined
return { ...node, wallAttachment }
Expand Down
7 changes: 5 additions & 2 deletions packages/nodes/src/pipe-segment/parametrics.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ParametricDescriptor } from '@pascal-app/core'
import { findAutomaticRunEndCapIds } from '../shared/automatic-run-end-cap'
import { fittingDeletionPlansForRun } from '../shared/fitting-deletion-cleanup'
import type { PipeSegmentNode } from './schema'

Expand All @@ -15,10 +16,12 @@ export const pipeSegmentParametrics: ParametricDescriptor<PipeSegmentNode> = {
fittingDeletionPlansForRun(pipe, nodes, requestedDeleteIds, true).flatMap(
(plan) => plan.updates,
),
onDeleteCascade: (pipe, nodes, _pendingDeleteIds, requestedDeleteIds) =>
fittingDeletionPlansForRun(pipe, nodes, requestedDeleteIds, false).flatMap((plan) =>
onDeleteCascade: (pipe, nodes, _pendingDeleteIds, requestedDeleteIds) => [
...findAutomaticRunEndCapIds(pipe.id, nodes, 'pipe-fitting'),
...fittingDeletionPlansForRun(pipe, nodes, requestedDeleteIds, false).flatMap((plan) =>
plan.deleteFitting ? [plan.fittingId, ...plan.cascadeDeleteIds] : [],
),
],
trailingSection: () => import('../shared/run-hanger-inspector'),
groups: [
{
Expand Down
23 changes: 23 additions & 0 deletions packages/nodes/src/shared/automatic-run-end-cap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DuctSegmentNode,
loadPlugin,
nodeRegistry,
PipeFittingNode,
PipeSegmentNode,
} from '@pascal-app/core'
import { getDuctFittingPorts } from '../duct-fitting/ports'
Expand All @@ -12,6 +13,7 @@ import { getPipeFittingPorts } from '../pipe-fitting/ports'
import {
createDuctRunEndCap,
createPipeRunEndCap,
findAutomaticRunEndCapIds,
findMatedRunEndCapIds,
isRunEndCapPort,
planRunEndCapFollowUpdates,
Expand Down Expand Up @@ -154,6 +156,27 @@ describe('automatic run end caps', () => {
).toEqual([firstCap.id])
})

test('finds automatic caps by run owner without matching manual caps', () => {
const pipe = PipeSegmentNode.parse({
path: [
[0, 1, 0],
[3, 1, 0],
],
})
const automatic = createPipeRunEndCap(pipe)!
const manual = PipeFittingNode.parse({
fittingType: 'end-cap',
metadata: {},
})
const nodes = {
[pipe.id]: pipe,
[automatic.id]: automatic,
[manual.id]: manual,
} as Record<string, AnyNode>

expect(findAutomaticRunEndCapIds(pipe.id, nodes, 'pipe-fitting')).toEqual([automatic.id])
})

test.each([
[
'duct',
Expand Down
17 changes: 17 additions & 0 deletions packages/nodes/src/shared/automatic-run-end-cap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,23 @@ export function findMatedRunEndCapIds(
return ids
}

export function findAutomaticRunEndCapIds(
runId: AnyNodeId,
nodes: Readonly<Record<string, AnyNode>>,
fittingKind: 'duct-fitting' | 'pipe-fitting',
): AnyNodeId[] {
return Object.values(nodes).flatMap((node) => {
if (
!node ||
node.type !== fittingKind ||
node.fittingType !== 'end-cap' ||
node.metadata[END_CAP_OWNER_ID_KEY] !== runId
)
return []
return [node.id]
})
}

export function planRunEndCapFollowUpdates(
originalRun: DuctSegmentNode | PipeSegmentNode,
nextRun: DuctSegmentNode | PipeSegmentNode,
Expand Down
Loading
Loading