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>
87 lines
2.3 KiB
TypeScript
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}
|
|
/>
|
|
)
|
|
}
|