Files
editor/packages/nodes/src/fence/inspector-editors.tsx
T
Wassim SAMADandClaude Opus 4.7 282cb22585 parametrics: add custom field kind + restore fence Length / Curve
User noticed the auto-derived fence inspector was missing the legacy
panel's Length + Curve sliders, and the section labelling was wrong
(Posts → Structure). Both Length and Curve are awkward for the
parametrics field model:

- **Length** doesn't map to a single node key — it's derived from
  `start`/`end`, and editing it moves `end` along the existing
  direction.
- **Curve** maps to `curveOffset` but the slider's min/max are
  bounded per-node by the chord length, plus updates need
  `normalizeWallCurveOffset`.

Adds a `kind: 'custom'` field with a kind-supplied
`component: ComponentType<{ node, onUpdate }>`. The inspector mounts
it and lets the kind own rendering + update logic. `key` becomes a
free-form React key/label since it no longer needs to map to a node
property.

Fence parametrics now mirrors the legacy layout 1:1:
- Style (segmented controls + showInfill toggle).
- Dimensions (Length, Curve, Height, Thickness).
- Structure (Base Height, Top Rail, Post Spacing, Post Size, Ground
  Clear, Edge Inset).

Length + Curve live in fence/inspector-editors.tsx.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 10:27:40 -04:00

87 lines
2.3 KiB
TypeScript

'use client'
import {
type FenceNode,
getClampedWallCurveOffset,
getMaxWallCurveOffset,
getWallCurveLength,
normalizeWallCurveOffset,
} from '@pascal-app/core'
import { SliderControl } from '@pascal-app/editor'
/**
* Custom inspector editors for fence fields that don't map to a single
* node property in the canonical way:
*
* - **Length** is derived from `start`/`end`. Adjusting the slider
* moves `end` along the existing direction so the fence resizes from
* the start point. Matches the legacy `FencePanel`'s "Length" slider.
* - **Curve** is a slider on `curveOffset` with min/max bounded by the
* chord length (per-node), normalized via `normalizeWallCurveOffset`.
* Can't use a plain `number` field because the bounds change with
* the fence's shape.
*
* Both are wired through `parametrics.fields[].kind: 'custom'`.
*/
export function FenceLengthEditor({
node,
onUpdate,
}: {
node: FenceNode
onUpdate: (patch: Partial<FenceNode>) => void
}) {
const length = getWallCurveLength(node)
const handleChange = (newLength: number) => {
if (newLength <= 0) return
const dx = node.end[0] - node.start[0]
const dz = node.end[1] - node.start[1]
const currentLength = Math.sqrt(dx * dx + dz * dz)
if (currentLength === 0) return
const dirX = dx / currentLength
const dirZ = dz / currentLength
const newEnd: [number, number] = [
node.start[0] + dirX * newLength,
node.start[1] + dirZ * newLength,
]
onUpdate({ end: newEnd })
}
return (
<SliderControl
label="Length"
max={50}
min={0.1}
onChange={handleChange}
precision={2}
step={0.01}
unit="m"
value={length}
/>
)
}
export function FenceCurveEditor({
node,
onUpdate,
}: {
node: FenceNode
onUpdate: (patch: Partial<FenceNode>) => void
}) {
const curveOffset = getClampedWallCurveOffset(node)
const maxCurveOffset = getMaxWallCurveOffset(node)
return (
<SliderControl
label="Curve"
max={Math.max(0.01, maxCurveOffset)}
min={-Math.max(0.01, maxCurveOffset)}
onChange={(value) => onUpdate({ curveOffset: normalizeWallCurveOffset(node, value) })}
precision={2}
step={0.1}
unit="m"
value={Math.round(curveOffset * 100) / 100}
/>
)
}