112 lines
3.4 KiB
TypeScript

import type { AnyNode, AnyNodeId, ParametricDescriptor, PipeSegmentNode } from '@pascal-app/core'
import { getPipeFittingPorts } from './ports'
import type { PipeFittingNode } from './schema'
/** A pipe endpoint sitting this close to a fitting hub counts as mated. */
const MATE_TOL_M = 0.05
type Point = [number, number, number]
type PipeMate = { pipe: PipeSegmentNode; endIndex: number }
function matedPipes(
fitting: PipeFittingNode,
nodes: Record<AnyNodeId, AnyNode>,
): Map<string, PipeMate> {
const mates = new Map<string, PipeMate>()
const ports = getPipeFittingPorts(fitting)
for (const node of Object.values(nodes)) {
if (node.type !== 'pipe-segment') continue
const pipe = node as PipeSegmentNode
for (const endIndex of [0, pipe.path.length - 1]) {
const p = pipe.path[endIndex]
if (!p) continue
for (const port of ports) {
if (mates.has(port.id)) continue
const dx = p[0] - port.position[0]
const dy = p[1] - port.position[1]
const dz = p[2] - port.position[2]
if (dx * dx + dy * dy + dz * dz <= MATE_TOL_M * MATE_TOL_M) {
mates.set(port.id, { pipe, endIndex })
}
}
}
}
return mates
}
export const pipeFittingParametrics: ParametricDescriptor<PipeFittingNode> = {
// Deleting an auto-inserted DWV bend restores the corner it replaced.
// The connected pipe endpoints were pulled back onto the bend collars;
// send those endpoints back to the junction so the L-shape regains its
// original length.
onDelete: (fitting, nodes) => {
if (fitting.fittingType !== 'elbow') return []
const updates: Array<{ id: AnyNodeId; data: Partial<AnyNode> }> = []
for (const mate of matedPipes(fitting, nodes).values()) {
const end = mate.pipe.path[mate.endIndex]
if (!end) continue
const target = fitting.position
const dx = end[0] - target[0]
const dy = end[1] - target[1]
const dz = end[2] - target[2]
if (dx * dx + dy * dy + dz * dz < 1e-12) continue
const path = mate.pipe.path.map((p) => [...p] as Point)
path[mate.endIndex] = [...target]
updates.push({ id: mate.pipe.id, data: { path } as Partial<PipeSegmentNode> })
}
return updates
},
groups: [
{
label: 'Fitting',
fields: [
{
key: 'fittingType',
kind: 'enum',
options: ['elbow', 'wye', 'sanitary-tee', 'cross'],
display: 'segmented',
},
{
key: 'angle',
kind: 'number',
unit: '°',
min: 0,
max: 90,
step: 7.5,
visibleIf: (n) => n.fittingType === 'elbow',
},
{
key: 'system',
kind: 'enum',
options: ['waste', 'vent'],
display: 'segmented',
},
],
},
{
label: 'Connections',
fields: [
{ key: 'diameter', kind: 'number', unit: 'in', min: 1.25, max: 6, step: 0.25 },
{
key: 'diameter2',
kind: 'number',
unit: 'in',
min: 1.25,
max: 6,
step: 0.25,
visibleIf: (n) => n.fittingType !== 'elbow',
},
{ key: 'pipeMaterial', kind: 'enum', options: ['pvc', 'abs', 'cast-iron'] },
],
},
{
label: 'Placement',
fields: [
{ key: 'position', kind: 'vec3' },
{ key: 'rotation', kind: 'vec3' },
],
},
],
}