Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion bun.lock

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

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
],
"patchedDependencies": {
"iwer@2.3.0": "patches/iwer@2.3.0.patch",
"three@0.186.0": "patches/three@0.186.0.patch",
"@pascal-app/plugin-pool@github:sudhir9297/pool-pascal-plugin#483a080": "patches/plugin-pool@483a080.patch"
"three@0.186.0": "patches/three@0.186.0.patch"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
type RunCursorRay,
type RunPoint,
runDistanceSquared,
runSectionHalfSizeM,
snapRunLength,
snapRunPointToSurface,
snapRunValue,
Expand Down Expand Up @@ -217,11 +216,6 @@ describe('distribution run drafting helpers', () => {
expect(runDistanceSquared([1, 2, 3], [4, 6, 3])).toBe(25)
})

test('places a run centerline half its section above the support plane', () => {
expect(runSectionHalfSizeM(2)).toBeCloseTo(0.0254)
expect(runSectionHalfSizeM(8)).toBeCloseTo(0.1016)
})

test('projects and snaps a run in a vertical wall plane', () => {
const wall = createRunSurfaceFrame([4, 2, 6], [0, 0, 1])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,6 @@ const ALT_PIXELS_PER_METER = 100
export const RUN_PREVIEW_OPACITY = 0.55
export const RUN_SNAP_CURSOR_COLOR = '#22c55e'

export function runSectionHalfSizeM(nominalInches: number): number {
return (nominalInches * 0.0254) / 2
}

export function snapRunValue(value: number, step: number): number {
if (step <= 0) return value
return Math.round(value / step) * step
Expand Down
29 changes: 11 additions & 18 deletions packages/editor/src/components/tools/distribution/ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ import type { RunSurfaceTarget } from './distribution-run-contract'
/** A port plus the scene node that owns it. */
export type ScenePort = NodePort & { nodeId: AnyNodeId }

/** Air-loop port systems — what duct runs and fittings snap to. */
export const DUCT_PORT_SYSTEMS = ['supply', 'return'] as const
/** DWV port systems — what drain / waste / vent pipe runs snap to. */
export const DWV_PORT_SYSTEMS = ['waste', 'vent'] as const
/** Refrigerant-loop port system — what linesets snap to. */
export const REFRIGERANT_PORT_SYSTEMS = ['refrigerant'] as const

/**
* Filter narrowing which ports a tool will snap to.
* - `excludeNodeId` skips the node currently being drawn/placed so a
Expand Down Expand Up @@ -119,7 +112,7 @@ export function findNearestPort3D(

// ─── Run-body hits ───────────────────────────────────────────────────

/** Closest-point hit on a duct run's centerline (not its end ports). */
/** Closest-point hit on a run's centerline (not its end ports). */
export type RunBodyHit = {
nodeId: AnyNodeId
/** Polyline segment hit — between `path[segmentIndex]` and `path[segmentIndex + 1]`. */
Expand All @@ -129,20 +122,20 @@ export type RunBodyHit = {
}

/**
* Nearest point on any duct-segment CENTERLINE within `radius` of `point`
* Nearest point on any run CENTERLINE of the given kinds within `radius` of `point`
* on the XZ plane — how a branch taps the side of a trunk. Same XZ-only
* distance convention as `findNearestPortXZ` (grid events ride the floor,
* runs hang at duct height); the hit adopts the centerline's full 3D
* runs hang above it); the hit adopts the centerline's full 3D
* position. Vertical risers project to a point in XZ and are skipped —
* tapping those isn't meaningful.
*/
export function findNearestRunBodyXZ(
point: readonly [number, number, number],
radius: number,
filter: { excludeNodeId?: AnyNodeId; kinds?: readonly string[]; levelId?: AnyNodeId } = {},
filter: { excludeNodeId?: AnyNodeId; kinds: readonly string[]; levelId?: AnyNodeId },
sceneNodes?: Readonly<Record<AnyNodeId, AnyNode>>,
): RunBodyHit | null {
const kinds = filter.kinds ?? ['duct-segment']
const { kinds } = filter
const nodes = sceneNodes ?? useScene.getState().nodes
let best: RunBodyHit | null = null
let bestDistSq = radius * radius
Expand Down Expand Up @@ -184,11 +177,11 @@ export function findNearestRunBodyXZ(
export function findNearestRunBody3D(
point: readonly [number, number, number],
radius: number,
filter: { excludeNodeId?: AnyNodeId; kinds?: readonly string[]; levelId?: AnyNodeId } = {},
filter: { excludeNodeId?: AnyNodeId; kinds: readonly string[]; levelId?: AnyNodeId },
surface?: RunSurfaceTarget | null,
sceneNodes?: Readonly<Record<AnyNodeId, AnyNode>>,
): RunBodyHit | null {
const kinds = filter.kinds ?? ['duct-segment']
const { kinds } = filter
const nodes = sceneNodes ?? useScene.getState().nodes
let best: RunBodyHit | null = null
let bestDistSq = radius * radius
Expand Down Expand Up @@ -255,9 +248,9 @@ export function findRunBodyCrossingXZ(
start: readonly [number, number, number],
end: readonly [number, number, number],
endMargin: number,
filter: { excludeNodeId?: AnyNodeId; kinds?: readonly string[] } = {},
filter: { excludeNodeId?: AnyNodeId; kinds: readonly string[] },
): RunBodyHit | null {
const kinds = filter.kinds ?? ['duct-segment']
const { kinds } = filter
const { nodes } = useScene.getState()
const dx = end[0] - start[0]
const dz = end[2] - start[2]
Expand Down Expand Up @@ -313,7 +306,7 @@ export function findRunBodyCrossingSurface(
end: readonly [number, number, number],
endMargin: number,
surface: RunSurfaceTarget,
filter: { excludeNodeId?: AnyNodeId; kinds?: readonly string[] } = {},
filter: { excludeNodeId?: AnyNodeId; kinds: readonly string[] },
): RunBodyHit | null {
const project = (p: readonly [number, number, number]): [number, number, number] => {
const d: [number, number, number] = [
Expand All @@ -340,7 +333,7 @@ export function findRunBodyCrossingSurface(
const drawnLen = Math.hypot(dx, dy)
if (drawnLen < 1e-8) return null
const drawnPad = Math.min(0.45, endMargin / drawnLen)
const kinds = filter.kinds ?? ['duct-segment']
const { kinds } = filter
const { nodes } = useScene.getState()
let best: RunBodyHit | null = null
let bestScore = Number.POSITIVE_INFINITY
Expand Down
2 changes: 1 addition & 1 deletion packages/nodes/src/duct-fitting/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
alignDrawPoint,
clearDrawAlignment,
collectScenePorts,
DUCT_PORT_SYSTEMS,
type ScenePort,
} from '@pascal-app/editor/distribution'
import { useViewer } from '@pascal-app/viewer'
Expand All @@ -42,6 +41,7 @@ import {
} from '../shared/fitting-rotation'
import { createFittingSurfaceSupport } from '../shared/fitting-surface-support'
import { LevelOffsetGroup } from '../shared/level-offset-group'
import { DUCT_PORT_SYSTEMS } from '../shared/port-systems'
import { ductFittingDefinition } from './definition'
import { buildDuctFittingGeometry } from './geometry'
import { localFittingPorts } from './ports'
Expand Down
3 changes: 2 additions & 1 deletion packages/nodes/src/duct-segment/move-tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
useAlignmentGuides,
useEditor,
} from '@pascal-app/editor'
import { collectScenePorts, DUCT_PORT_SYSTEMS } from '@pascal-app/editor/distribution'
import { collectScenePorts } from '@pascal-app/editor/distribution'
import { useViewer } from '@pascal-app/viewer'
import { useEffect, useRef, useState } from 'react'
import { Matrix4, Vector3 } from 'three'
Expand All @@ -35,6 +35,7 @@ import {
resolveGhostAlignment,
} from '../shared/ghost-alignment'
import { DuctSegmentGhost, FittingGhost } from '../shared/mep-ghost'
import { DUCT_PORT_SYSTEMS } from '../shared/port-systems'
import { type RunMoveConnectivity, startRunMoveConnectivity } from '../shared/run-move-connectivity'
import {
planRunTranslationOffsets,
Expand Down
7 changes: 2 additions & 5 deletions packages/nodes/src/duct-segment/selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ import {
triggerSFX,
useEditor,
} from '@pascal-app/editor'
import {
collectScenePorts,
DUCT_PORT_SYSTEMS,
findNearestPortXZ,
} from '@pascal-app/editor/distribution'
import { collectScenePorts, findNearestPortXZ } from '@pascal-app/editor/distribution'
import { useViewer } from '@pascal-app/viewer'
import { Html } from '@react-three/drei'
import { createPortal, type ThreeEvent, useFrame, useThree } from '@react-three/fiber'
Expand Down Expand Up @@ -60,6 +56,7 @@ import {
planFittingEndpointReaim,
} from '../shared/fitting-endpoint-reaim'
import { DuctSegmentGhost, FittingGhost } from '../shared/mep-ghost'
import { DUCT_PORT_SYSTEMS } from '../shared/port-systems'
import { planRunTranslationOffsets } from '../shared/run-translation-offset'
import { ContinuePlusHandle, HandleCube, MoveChevron, RotateArc } from '../shared/selection-handles'
import { planVerticalOffsets, type VerticalOffsetResult } from '../shared/vertical-offset'
Expand Down
12 changes: 8 additions & 4 deletions packages/nodes/src/duct-segment/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ import {
collectScenePorts,
createRunWallAttachment,
DistributionRunCursor,
DUCT_PORT_SYSTEMS,
runDistanceSquared as dist2,
findNearestRunBody3D,
findRunBodyCrossingSurface,
type RunBodyHit,
type RunSurfaceTarget,
runSectionHalfSizeM,
type ScenePort,
stepNominalRunSize,
useDistributionRunTool,
Expand All @@ -42,6 +40,7 @@ import { ductProfilesMatch, planDuctAdapter } from '../shared/duct-adapter'
import { FITTING_CLEARANCE_MESSAGE, hasFittingClearance } from '../shared/fitting-clearance'
import { LevelOffsetGroup } from '../shared/level-offset-group'
import { DuctSegmentGhost, FittingGhost } from '../shared/mep-ghost'
import { DUCT_PORT_SYSTEMS, runSectionHalfSizeM } from '../shared/port-systems'
import { RunHangerPreview } from '../shared/run-hanger-controls'
import { useRunHangerMode } from '../shared/run-hanger-mode'
import { currentDuctContinuationSeed, ductEndpointPort } from './continuation'
Expand Down Expand Up @@ -415,7 +414,9 @@ export function planDuctDraw(
const realigns = [startRealign, endRealign].filter((p) => p !== null)

const crossHit = surface
? findRunBodyCrossingSurface(start, end, BODY_SNAP_RADIUS_M, surface)
? findRunBodyCrossingSurface(start, end, BODY_SNAP_RADIUS_M, surface, {
kinds: ['duct-segment'],
})
: null
const crossOwner = crossHit ? nodes[crossHit.nodeId] : null
const crossTappedElsewhere =
Expand Down Expand Up @@ -566,7 +567,10 @@ const DuctSegmentTool = () => {
: null,
getPorts: () => getConnectionPorts(activeLevelId, sceneApi.nodes()),
findBody: (point) =>
findNearestRunBody3D(point, BODY_SNAP_RADIUS_M, { levelId: activeLevelId ?? undefined }),
findNearestRunBody3D(point, BODY_SNAP_RADIUS_M, {
kinds: ['duct-segment'],
levelId: activeLevelId ?? undefined,
}),
surfaceClearance: (surface) =>
surface ? ductSurfaceClearanceM(profileRef.current, surface.kind === 'wall') : 0,
minimumSegmentLength: 0.08,
Expand Down
2 changes: 1 addition & 1 deletion packages/nodes/src/duct-terminal/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
alignDrawPoint,
clearDrawAlignment,
collectScenePorts,
DUCT_PORT_SYSTEMS,
findNearestPort3D,
} from '@pascal-app/editor/distribution'
import { useViewer } from '@pascal-app/viewer'
Expand All @@ -40,6 +39,7 @@ import { Euler, Matrix3, Matrix4, Plane, Quaternion, Raycaster, Vector2, Vector3
import { subscribeAccessorySnapping } from '../shared/accessory-snapping'
import { ConnectionFeedback } from '../shared/connection-feedback'
import { LevelOffsetGroup } from '../shared/level-offset-group'
import { DUCT_PORT_SYSTEMS } from '../shared/port-systems'
import { ductTerminalDefinition } from './definition'
import { buildDuctTerminalGeometry } from './geometry'
import { COLLAR_LENGTH, getDuctTerminalPorts, mountQuaternion } from './ports'
Expand Down
2 changes: 1 addition & 1 deletion packages/nodes/src/lineset/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import {
clearDrawAlignment,
collectScenePorts,
findNearestPortXZ,
REFRIGERANT_PORT_SYSTEMS,
} from '@pascal-app/editor/distribution'
import { useViewer } from '@pascal-app/viewer'
import { Html } from '@react-three/drei'
import { useEffect, useRef, useState } from 'react'
import { type Group, Vector3 } from 'three'
import { LevelOffsetGroup } from '../shared/level-offset-group'
import { REFRIGERANT_PORT_SYSTEMS } from '../shared/port-systems'
import { linesetDefinition } from './definition'

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/nodes/src/liquid-line/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import {
clearDrawAlignment,
collectScenePorts,
findNearestPortXZ,
REFRIGERANT_PORT_SYSTEMS,
} from '@pascal-app/editor/distribution'
import { useViewer } from '@pascal-app/viewer'
import { Html } from '@react-three/drei'
import { useEffect, useRef, useState } from 'react'
import { type Group, Vector3 } from 'three'
import { LevelOffsetGroup } from '../shared/level-offset-group'
import { offsetPathHorizontal } from '../shared/path-offset'
import { REFRIGERANT_PORT_SYSTEMS } from '../shared/port-systems'
import { liquidLineDefinition } from './definition'
import { useLiquidLineToolOptions } from './options'

Expand Down
2 changes: 1 addition & 1 deletion packages/nodes/src/pipe-fitting/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
alignDrawPoint,
clearDrawAlignment,
collectScenePorts,
DWV_PORT_SYSTEMS,
findNearestRunBody3D,
findNearestRunBodyXZ,
type ScenePort,
Expand All @@ -47,6 +46,7 @@ import {
} from '../shared/fitting-rotation'
import { createFittingSurfaceSupport } from '../shared/fitting-surface-support'
import { LevelOffsetGroup } from '../shared/level-offset-group'
import { DWV_PORT_SYSTEMS } from '../shared/port-systems'
import { pipeFittingDefinition } from './definition'
import { buildPipeFittingGeometry } from './geometry'
import {
Expand Down
7 changes: 2 additions & 5 deletions packages/nodes/src/pipe-segment/selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@ import {
triggerSFX,
useEditor,
} from '@pascal-app/editor'
import {
collectScenePorts,
DWV_PORT_SYSTEMS,
findNearestPortXZ,
} from '@pascal-app/editor/distribution'
import { collectScenePorts, findNearestPortXZ } from '@pascal-app/editor/distribution'
import { useViewer } from '@pascal-app/viewer'
import { Html } from '@react-three/drei'
import { createPortal, type ThreeEvent, useFrame, useThree } from '@react-three/fiber'
Expand All @@ -42,6 +38,7 @@ import {
import { PipeFittingGhost, PipeSegmentGhost } from '../shared/mep-ghost'
import { planPipeRunTranslationOffsets } from '../shared/pipe-run-translation-offset'
import { planVerticalOffsets, type VerticalOffsetResult } from '../shared/pipe-vertical-offset'
import { DWV_PORT_SYSTEMS } from '../shared/port-systems'
import { ContinuePlusHandle, HandleCube, MoveChevron } from '../shared/selection-handles'
import { refreshWallRunAttachment } from '../shared/wall-run-move'
import {
Expand Down
3 changes: 1 addition & 2 deletions packages/nodes/src/pipe-segment/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ import {
collectScenePorts,
createRunWallAttachment,
DistributionRunCursor,
DWV_PORT_SYSTEMS,
findNearestRunBody3D,
findRunBodyCrossingSurface,
RUN_PREVIEW_OPACITY,
type RunConnection,
type RunPoint,
type RunSurfaceTarget,
runDistanceSquared,
runSectionHalfSizeM,
type ScenePort,
stepNominalRunSize,
useDistributionRunTool,
Expand All @@ -41,6 +39,7 @@ import { ConnectionFeedback } from '../shared/connection-feedback'
import { FITTING_CLEARANCE_MESSAGE, hasFittingClearance } from '../shared/fitting-clearance'
import { LevelOffsetGroup } from '../shared/level-offset-group'
import { PipeFittingGhost } from '../shared/mep-ghost'
import { DWV_PORT_SYSTEMS, runSectionHalfSizeM } from '../shared/port-systems'
import { RunHangerPreview } from '../shared/run-hanger-controls'
import { useRunHangerMode } from '../shared/run-hanger-mode'
import { currentPipeContinuationSeed, pipeEndpointPort } from './continuation'
Expand Down
7 changes: 7 additions & 0 deletions packages/nodes/src/shared/port-systems.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { expect, test } from 'bun:test'
import { runSectionHalfSizeM } from './port-systems'

test('places a run centerline half its section above the support plane', () => {
expect(runSectionHalfSizeM(2)).toBeCloseTo(0.0254)
expect(runSectionHalfSizeM(8)).toBeCloseTo(0.1016)
})
11 changes: 11 additions & 0 deletions packages/nodes/src/shared/port-systems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** Air-loop port systems — what duct runs and fittings snap to. */
export const DUCT_PORT_SYSTEMS = ['supply', 'return'] as const
/** DWV port systems — what drain / waste / vent pipe runs snap to. */
export const DWV_PORT_SYSTEMS = ['waste', 'vent'] as const
/** Refrigerant-loop port system — what linesets snap to. */
export const REFRIGERANT_PORT_SYSTEMS = ['refrigerant'] as const

/** Half a run's section, in metres, from its nominal size in inches: how far its centerline sits off a support. */
export function runSectionHalfSizeM(nominalInches: number): number {
return (nominalInches * 0.0254) / 2
}
7 changes: 2 additions & 5 deletions packages/nodes/src/shared/refrigerant-line-selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,13 @@ import {
useScene,
} from '@pascal-app/core'
import { DimensionPill, swallowNextClick, triggerSFX, useEditor } from '@pascal-app/editor'
import {
collectScenePorts,
findNearestPortXZ,
REFRIGERANT_PORT_SYSTEMS,
} from '@pascal-app/editor/distribution'
import { collectScenePorts, findNearestPortXZ } from '@pascal-app/editor/distribution'
import { useViewer } from '@pascal-app/viewer'
import { Html } from '@react-three/drei'
import { createPortal, type ThreeEvent, useFrame, useThree } from '@react-three/fiber'
import { useEffect, useMemo, useRef, useState } from 'react'
import { type Group, type Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three'
import { REFRIGERANT_PORT_SYSTEMS } from './port-systems'
import { HandleCube, MoveChevron } from './selection-handles'

type RefrigerantLineKind = 'lineset' | 'liquid-line'
Expand Down
Loading
Loading