From 53393b7def5c7abdc8cb19b3a5c2309f1083e204 Mon Sep 17 00:00:00 2001 From: sudhir Date: Fri, 8 May 2026 12:54:59 +0530 Subject: [PATCH] Add procedural support column variants --- packages/core/src/schema/index.ts | 1 + packages/core/src/schema/nodes/column.ts | 582 +++++ packages/core/src/schema/nodes/fence.ts | 2 + .../src/components/editor/floorplan-panel.tsx | 58 +- .../src/components/ui/panels/column-panel.tsx | 611 ++++-- .../src/components/ui/panels/fence-panel.tsx | 7 + .../renderers/column/column-renderer.tsx | 1907 ++++++++++++----- .../viewer/src/systems/fence/fence-system.tsx | 23 +- 8 files changed, 2461 insertions(+), 730 deletions(-) diff --git a/packages/core/src/schema/index.ts b/packages/core/src/schema/index.ts index 0a16d951..b088079a 100644 --- a/packages/core/src/schema/index.ts +++ b/packages/core/src/schema/index.ts @@ -39,6 +39,7 @@ export { ColumnShaftDetail, ColumnShaftProfile, ColumnStyle, + ColumnSupportStyle, } from './nodes/column' export { DoorNode, DoorSegment } from './nodes/door' export { FenceBaseStyle, FenceNode, FenceStyle } from './nodes/fence' diff --git a/packages/core/src/schema/nodes/column.ts b/packages/core/src/schema/nodes/column.ts index a7fa83a8..0e19a8d6 100644 --- a/packages/core/src/schema/nodes/column.ts +++ b/packages/core/src/schema/nodes/column.ts @@ -56,6 +56,20 @@ export const ColumnRingPlacement = z.enum(['ends', 'even', 'top', 'bottom']) export const ColumnCarvingPlacement = z.enum(['shaft', 'base', 'capital', 'all']) +export const ColumnSupportStyle = z.enum([ + 'vertical', + 'a-frame', + 'y-frame', + 'v-frame', + 'x-brace', + 'k-brace', + 'single-strut', + 'tripod', + 'trestle', + 'portal-frame', + 'box-frame', +]) + export type ColumnStyle = z.infer export type ColumnCrossSection = z.infer export type ColumnShaftProfile = z.infer @@ -65,6 +79,7 @@ export type ColumnBaseStyle = z.infer export type ColumnCapitalStyle = z.infer export type ColumnRingPlacement = z.infer export type ColumnCarvingPlacement = z.infer +export type ColumnSupportStyle = z.infer export const ColumnNode = BaseNode.extend({ id: objectId('column'), @@ -136,6 +151,12 @@ export const ColumnNode = BaseNode.extend({ lowerBandCarvingLevel: z.number().int().min(0).max(4).default(0), dentilCount: z.number().int().min(0).max(48).default(0), beadCount: z.number().int().min(0).max(64).default(0), + supportStyle: ColumnSupportStyle.default('vertical'), + braceWidth: z.number().positive().default(0.16), + braceDepth: z.number().positive().default(0.16), + braceBottomSpread: z.number().min(0.2).default(1.2), + braceTopSpread: z.number().min(0).default(0.12), + bracePlateEnabled: z.boolean().default(true), material: MaterialSchema.optional(), materialPreset: z.string().optional(), }).describe(dedent` @@ -150,6 +171,7 @@ export const ColumnNode = BaseNode.extend({ - baseStyle/capitalStyle: procedural base and top treatment with tier/detail controls - baseHeight/capitalHeight: bottom and top block proportions - ring/flute/spiral/panel/lathe/carving fields: procedural detail controls + - supportStyle/brace fields: vertical column or procedural support assembly `) export const COLUMN_PRESETS = { @@ -403,6 +425,566 @@ export const COLUMN_PRESETS = { dentilCount: 0, beadCount: 0, }, + aFrameSupport: { + label: 'A-Frame Support', + supportStyle: 'a-frame', + style: 'faceted', + crossSection: 'rectangular', + height: 2.6, + radius: 0.08, + width: 0.16, + depth: 0.16, + edgeSoftness: 0.012, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.012, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.16, + braceDepth: 0.16, + braceBottomSpread: 1.2, + braceTopSpread: 0.06, + bracePlateEnabled: false, + }, + yFrameSupport: { + label: 'Y Support', + supportStyle: 'y-frame', + style: 'faceted', + crossSection: 'rectangular', + height: 2.7, + radius: 0.08, + width: 0.16, + depth: 0.16, + edgeSoftness: 0.012, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.012, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.16, + braceDepth: 0.16, + braceBottomSpread: 0.2, + braceTopSpread: 1, + bracePlateEnabled: false, + }, + vFrameSupport: { + label: 'V Support', + supportStyle: 'v-frame', + style: 'faceted', + crossSection: 'rectangular', + height: 2.6, + radius: 0.08, + width: 0.16, + depth: 0.16, + edgeSoftness: 0.012, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.012, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.16, + braceDepth: 0.16, + braceBottomSpread: 0.2, + braceTopSpread: 1, + bracePlateEnabled: false, + }, + xBraceSupport: { + label: 'X Brace', + supportStyle: 'x-brace', + style: 'faceted', + crossSection: 'rectangular', + height: 2.6, + radius: 0.07, + width: 0.14, + depth: 0.14, + edgeSoftness: 0.01, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.01, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.14, + braceDepth: 0.14, + braceBottomSpread: 1, + braceTopSpread: 1, + bracePlateEnabled: false, + }, + kBraceSupport: { + label: 'K Brace', + supportStyle: 'k-brace', + style: 'faceted', + crossSection: 'rectangular', + height: 2.6, + radius: 0.07, + width: 0.14, + depth: 0.14, + edgeSoftness: 0.01, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.01, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.14, + braceDepth: 0.14, + braceBottomSpread: 1, + braceTopSpread: 1, + bracePlateEnabled: false, + }, + singleStrutSupport: { + label: 'Single Strut', + supportStyle: 'single-strut', + style: 'faceted', + crossSection: 'rectangular', + height: 2.6, + radius: 0.07, + width: 0.14, + depth: 0.14, + edgeSoftness: 0.01, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.01, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.14, + braceDepth: 0.14, + braceBottomSpread: 1, + braceTopSpread: 1, + bracePlateEnabled: false, + }, + tripodSupport: { + label: 'Tripod Support', + supportStyle: 'tripod', + style: 'faceted', + crossSection: 'rectangular', + height: 2.6, + radius: 0.07, + width: 0.14, + depth: 0.14, + edgeSoftness: 0.01, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.01, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.14, + braceDepth: 0.14, + braceBottomSpread: 1.1, + braceTopSpread: 1.1, + bracePlateEnabled: false, + }, + trestleSupport: { + label: 'Trestle Frame', + supportStyle: 'trestle', + style: 'faceted', + crossSection: 'rectangular', + height: 2.6, + radius: 0.07, + width: 0.14, + depth: 0.14, + edgeSoftness: 0.01, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.01, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.14, + braceDepth: 0.14, + braceBottomSpread: 1.2, + braceTopSpread: 1, + bracePlateEnabled: false, + }, + portalFrameSupport: { + label: 'Portal Frame', + supportStyle: 'portal-frame', + style: 'faceted', + crossSection: 'rectangular', + height: 2.6, + radius: 0.08, + width: 0.16, + depth: 0.16, + edgeSoftness: 0.012, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.012, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.16, + braceDepth: 0.16, + braceBottomSpread: 1.4, + braceTopSpread: 0.2, + bracePlateEnabled: false, + }, + boxFrameSupport: { + label: 'Box Frame', + supportStyle: 'box-frame', + style: 'faceted', + crossSection: 'rectangular', + height: 2.6, + radius: 0.07, + width: 0.14, + depth: 0.14, + edgeSoftness: 0.01, + baseHeight: 0, + capitalHeight: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0.01, + shaftDetail: 'none', + baseStyle: 'none', + baseWidthScale: 1, + baseDepthScale: 1, + baseTierCount: 1, + baseStepSpread: 0.34, + basePlinthHeightRatio: 0.44, + baseRoundBandScale: 0.92, + baseNeckScale: 0.72, + baseRoundBandCount: 0, + baseRibCount: 0, + baseCarvingLevel: 0, + capitalStyle: 'none', + capitalWidthScale: 1, + capitalDepthScale: 1, + capitalTierCount: 1, + capitalStepSpread: 0.34, + capitalBandCount: 0, + capitalCarvingLevel: 0, + ringCount: 0, + ringSpread: 0.16, + fluteCount: 0, + spiralTwist: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + braceWidth: 0.14, + braceDepth: 0.14, + braceBottomSpread: 1.4, + braceTopSpread: 1, + bracePlateEnabled: false, + }, } as const satisfies Record>> export type ColumnPresetId = keyof typeof COLUMN_PRESETS diff --git a/packages/core/src/schema/nodes/fence.ts b/packages/core/src/schema/nodes/fence.ts index 234da407..fe84c885 100644 --- a/packages/core/src/schema/nodes/fence.ts +++ b/packages/core/src/schema/nodes/fence.ts @@ -23,6 +23,7 @@ export const FenceNode = BaseNode.extend({ groundClearance: z.number().default(0), edgeInset: z.number().default(0.015), baseStyle: FenceBaseStyle.default('grounded'), + showInfill: z.boolean().default(true), color: z.string().default('#ffffff'), style: FenceStyle.default('slat'), }).describe( @@ -33,6 +34,7 @@ export const FenceNode = BaseNode.extend({ - height/thickness: overall fence dimensions in meters - baseHeight/postSpacing/postSize/topRailHeight: exact geometric controls from the plan3D fence model - groundClearance/edgeInset/baseStyle: fence support and inset configuration + - showInfill: whether to draw intermediate posts/slats between end posts - color/style: visual appearance options `, ) diff --git a/packages/editor/src/components/editor/floorplan-panel.tsx b/packages/editor/src/components/editor/floorplan-panel.tsx index 7c91df60..b74e4515 100644 --- a/packages/editor/src/components/editor/floorplan-panel.tsx +++ b/packages/editor/src/components/editor/floorplan-panel.tsx @@ -1768,6 +1768,56 @@ function getRotatedRectanglePolygon( function getColumnPlanFootprint(column: ColumnNode): Point2D[] { const center = { x: column.position[0], y: column.position[2] } + + if ( + column.supportStyle === 'a-frame' || + column.supportStyle === 'y-frame' || + column.supportStyle === 'v-frame' || + column.supportStyle === 'x-brace' || + column.supportStyle === 'k-brace' || + column.supportStyle === 'single-strut' || + column.supportStyle === 'tripod' || + column.supportStyle === 'trestle' || + column.supportStyle === 'portal-frame' || + column.supportStyle === 'box-frame' + ) { + const width = Math.max( + column.supportStyle === 'a-frame' || + column.supportStyle === 'x-brace' || + column.supportStyle === 'k-brace' || + column.supportStyle === 'single-strut' || + column.supportStyle === 'tripod' || + column.supportStyle === 'trestle' || + column.supportStyle === 'portal-frame' || + column.supportStyle === 'box-frame' + ? (column.braceBottomSpread ?? 1.2) + : 0, + column.braceTopSpread ?? + (column.supportStyle === 'y-frame' || + column.supportStyle === 'v-frame' || + column.supportStyle === 'x-brace' || + column.supportStyle === 'k-brace' || + column.supportStyle === 'single-strut' || + column.supportStyle === 'tripod' || + column.supportStyle === 'trestle' || + column.supportStyle === 'portal-frame' || + column.supportStyle === 'box-frame' + ? 1 + : 0), + (column.braceWidth ?? column.width) * 2, + ) + const depth = Math.max( + column.supportStyle === 'tripod' || + column.supportStyle === 'trestle' || + column.supportStyle === 'box-frame' + ? (column.braceTopSpread ?? 1) + : 0, + column.braceDepth ?? column.depth, + 0.08, + ) + return getRotatedRectanglePolygon(center, width, depth, column.rotation) + } + const shaftWidth = column.crossSection === 'round' || column.crossSection === 'octagonal' || @@ -5742,6 +5792,12 @@ const FloorplanFenceLayer = memo(function FloorplanFenceLayer({ const fenceGlowOpacity = isDeleteHovered ? 0.18 : isActive ? 0.22 : isHovered ? 0.14 : 0 const fenceUnderlayWidth = isActive ? '6.5' : isHovered ? '6' : '5.2' const fenceStrokeWidth = isActive ? '2.6' : isHovered ? '2.35' : '2.05' + const showFenceInfill = fence.showInfill ?? true + const visibleMarkerFrames = showFenceInfill + ? markerFrames + : markerFrames.filter( + (_, markerIndex) => markerIndex === 0 || markerIndex === markerFrames.length - 1, + ) const privacyMarkerWidth = clamp(fence.postSize * 0.58, 0.038, 0.068) const privacyMarkerHeight = clamp( Math.max(fence.baseHeight * 0.5, fence.postSize * 1.4), @@ -5792,7 +5848,7 @@ const FloorplanFenceLayer = memo(function FloorplanFenceLayer({ strokeWidth={fenceStrokeWidth} vectorEffect="non-scaling-stroke" /> - {markerFrames.map(({ angleDeg, point }, markerIndex) => { + {visibleMarkerFrames.map(({ angleDeg, point }, markerIndex) => { const svgPoint = toSvgPoint(point) if (fence.style === 'privacy') { diff --git a/packages/editor/src/components/ui/panels/column-panel.tsx b/packages/editor/src/components/ui/panels/column-panel.tsx index 8b7e97de..283dc2da 100644 --- a/packages/editor/src/components/ui/panels/column-panel.tsx +++ b/packages/editor/src/components/ui/panels/column-panel.tsx @@ -15,6 +15,7 @@ import useEditor from '../../../store/use-editor' import { ActionButton, ActionGroup } from '../controls/action-button' import { PanelSection } from '../controls/panel-section' import { SliderControl } from '../controls/slider-control' +import { ToggleControl } from '../controls/toggle-control' import { PanelWrapper } from './panel-wrapper' const SELECT_CLASS = @@ -83,6 +84,7 @@ function presetUpdates(presetId: ColumnPresetId): Partial { const { label, ...preset } = COLUMN_PRESETS[presetId] return { name: label, + supportStyle: 'supportStyle' in preset ? preset.supportStyle : 'vertical', ...preset, } } @@ -199,6 +201,18 @@ export function ColumnPanel() { if (!(node && node.type === 'column' && selectedId && selectedCount === 1)) return null const shaftProfile = node.shaftProfile ?? 'straight' + const supportStyle = node.supportStyle ?? 'vertical' + const isBraceSupport = + supportStyle === 'a-frame' || + supportStyle === 'y-frame' || + supportStyle === 'v-frame' || + supportStyle === 'x-brace' || + supportStyle === 'k-brace' || + supportStyle === 'single-strut' || + supportStyle === 'tripod' || + supportStyle === 'trestle' || + supportStyle === 'portal-frame' || + supportStyle === 'box-frame' return ( @@ -223,53 +237,115 @@ export function ColumnPanel() { - handleUpdate({ edgeSoftness: value })} - precision={3} - step={0.005} - unit="m" - value={node.edgeSoftness ?? 0.025} - /> - {(node.crossSection === 'square' || node.crossSection === 'rectangular') && ( - handleUpdate({ shaftCornerRadius: value })} - precision={3} - step={0.005} - unit="m" - value={node.shaftCornerRadius ?? 0.035} - /> + {isBraceSupport ? ( + <> + handleUpdate({ braceWidth: value, width: value })} + precision={2} + step={0.01} + unit="m" + value={node.braceWidth ?? node.width} + /> + handleUpdate({ braceDepth: value, depth: value })} + precision={2} + step={0.01} + unit="m" + value={node.braceDepth ?? node.depth} + /> + + ) : ( + <> + + handleUpdate({ edgeSoftness: value })} + precision={3} + step={0.005} + unit="m" + value={node.edgeSoftness ?? 0.025} + /> + {(node.crossSection === 'square' || node.crossSection === 'rectangular') && ( + handleUpdate({ shaftCornerRadius: value })} + precision={3} + step={0.005} + unit="m" + value={node.shaftCornerRadius ?? 0.035} + /> + )} + )} - + {!isBraceSupport && ( + + )} - - handleUpdate({ - width: value, - radius: value / 2, - ...(node.crossSection === 'rectangular' ? {} : { depth: value }), - }) - } - precision={2} - step={0.02} - unit="m" - value={node.width} - /> - {node.crossSection === 'rectangular' && ( - handleUpdate({ depth: value })} - precision={2} - step={0.02} - unit="m" - value={node.depth} - /> + {isBraceSupport ? ( + <> + {(supportStyle === 'a-frame' || + supportStyle === 'x-brace' || + supportStyle === 'k-brace' || + supportStyle === 'single-strut' || + supportStyle === 'tripod' || + supportStyle === 'trestle' || + supportStyle === 'portal-frame' || + supportStyle === 'box-frame') && ( + + handleUpdate({ + braceBottomSpread: value, + braceTopSpread: + supportStyle === 'a-frame' + ? Math.min(node.braceTopSpread ?? 0.12, value) + : (node.braceTopSpread ?? 1), + }) + } + precision={2} + step={0.05} + unit="m" + value={node.braceBottomSpread ?? 1.2} + /> + )} + handleUpdate({ braceTopSpread: value })} + precision={2} + step={0.02} + unit="m" + value={ + node.braceTopSpread ?? + (supportStyle === 'y-frame' || + supportStyle === 'v-frame' || + supportStyle === 'x-brace' || + supportStyle === 'k-brace' || + supportStyle === 'single-strut' || + supportStyle === 'tripod' || + supportStyle === 'trestle' || + supportStyle === 'portal-frame' || + supportStyle === 'box-frame' + ? 1 + : 0.12) + } + /> + handleUpdate({ bracePlateEnabled: checked })} + /> + + ) : ( + <> + + handleUpdate({ + width: value, + radius: value / 2, + ...(node.crossSection === 'rectangular' ? {} : { depth: value }), + }) + } + precision={2} + step={0.02} + unit="m" + value={node.width} + /> + {node.crossSection === 'rectangular' && ( + handleUpdate({ depth: value })} + precision={2} + step={0.02} + unit="m" + value={node.depth} + /> + )} + )} - - - {shaftProfile === 'straight' && ( + {!isBraceSupport && ( + + + {shaftProfile === 'straight' && ( + handleUpdate({ shaftStartScale: value, shaftEndScale: value })} + precision={2} + step={0.02} + value={node.shaftStartScale ?? 0.72} + /> + )} + {shaftProfile === 'tapered' && ( + <> + handleUpdate({ shaftStartScale: value })} + precision={2} + step={0.02} + value={node.shaftStartScale ?? 0.82} + /> + handleUpdate({ shaftEndScale: value })} + precision={2} + step={0.02} + value={node.shaftEndScale ?? 0.72} + /> + handleUpdate({ shaftTaper: value })} + precision={2} + step={0.01} + value={node.shaftTaper ?? 0.14} + /> + + )} + {shaftProfile === 'bulged' && ( + <> + + handleUpdate({ shaftStartScale: value, shaftEndScale: value }) + } + precision={2} + step={0.02} + value={node.shaftStartScale ?? 0.68} + /> + handleUpdate({ shaftBulge: value })} + precision={2} + step={0.01} + value={node.shaftBulge ?? 0.12} + /> + + )} + {shaftProfile === 'hourglass' && ( + <> + + handleUpdate({ shaftStartScale: value, shaftEndScale: value }) + } + precision={2} + step={0.02} + value={node.shaftStartScale ?? 0.84} + /> + handleUpdate({ shaftBulge: value })} + precision={2} + step={0.01} + value={node.shaftBulge ?? 0.12} + /> + + )} handleUpdate({ shaftStartScale: value, shaftEndScale: value })} - precision={2} - step={0.02} - value={node.shaftStartScale ?? 0.72} + label="Segment Twist" + max={90} + min={-90} + onChange={(value) => + handleUpdate({ + shaftTwistStep: value, + ...(Math.abs(value) > 0.001 && (node.shaftSegmentCount ?? 1) < 8 + ? { shaftSegmentCount: 12 } + : {}), + }) + } + precision={0} + step={5} + unit="°" + value={node.shaftTwistStep ?? 0} /> - )} - {shaftProfile === 'tapered' && ( - <> + {Math.abs(node.shaftTwistStep ?? 0) > 0.001 && ( handleUpdate({ shaftStartScale: value })} - precision={2} - step={0.02} - value={node.shaftStartScale ?? 0.82} + label="Twist Segments" + max={48} + min={4} + onChange={(value) => handleUpdate({ shaftSegmentCount: Math.round(value) })} + precision={0} + step={1} + value={node.shaftSegmentCount ?? 12} /> - handleUpdate({ shaftEndScale: value })} - precision={2} - step={0.02} - value={node.shaftEndScale ?? 0.72} - /> - handleUpdate({ shaftTaper: value })} - precision={2} - step={0.01} - value={node.shaftTaper ?? 0.14} - /> - - )} - {shaftProfile === 'bulged' && ( - <> - handleUpdate({ shaftStartScale: value, shaftEndScale: value })} - precision={2} - step={0.02} - value={node.shaftStartScale ?? 0.68} - /> - handleUpdate({ shaftBulge: value })} - precision={2} - step={0.01} - value={node.shaftBulge ?? 0.12} - /> - - )} - {shaftProfile === 'hourglass' && ( - <> - handleUpdate({ shaftStartScale: value, shaftEndScale: value })} - precision={2} - step={0.02} - value={node.shaftStartScale ?? 0.84} - /> - handleUpdate({ shaftBulge: value })} - precision={2} - step={0.01} - value={node.shaftBulge ?? 0.12} - /> - - )} - - handleUpdate({ - shaftTwistStep: value, - ...(Math.abs(value) > 0.001 && (node.shaftSegmentCount ?? 1) < 8 - ? { shaftSegmentCount: 12 } - : {}), - }) - } - precision={0} - step={5} - unit="°" - value={node.shaftTwistStep ?? 0} - /> - {Math.abs(node.shaftTwistStep ?? 0) > 0.001 && ( + )} handleUpdate({ shaftSegmentCount: Math.round(value) })} + label="Ring Pairs" + max={4} + min={0} + onChange={(value) => + handleUpdate({ + ringCount: Math.round(value) * 2, + ringPlacement: 'ends', + ringSpread: node.ringSpread ?? 0.16, + ringThickness: node.ringThickness ?? 0.055, + }) + } precision={0} step={1} - value={node.shaftSegmentCount ?? 12} + value={Math.ceil((node.ringCount ?? 0) / 2)} /> - )} - - handleUpdate({ - ringCount: Math.round(value) * 2, - ringPlacement: 'ends', - ringSpread: node.ringSpread ?? 0.16, - ringThickness: node.ringThickness ?? 0.055, - }) - } - precision={0} - step={1} - value={Math.ceil((node.ringCount ?? 0) / 2)} - /> - {(node.ringCount ?? 0) > 0 && ( - handleUpdate({ ringThickness: value })} - precision={3} - step={0.005} - unit="m" - value={node.ringThickness ?? 0.055} - /> - )} - {(node.ringCount ?? 0) > 0 && ( - handleUpdate({ ringSpread: value, ringPlacement: 'ends' })} - precision={2} - step={0.01} - value={node.ringSpread ?? 0.16} - /> - )} - + {(node.ringCount ?? 0) > 0 && ( + handleUpdate({ ringThickness: value })} + precision={3} + step={0.005} + unit="m" + value={node.ringThickness ?? 0.055} + /> + )} + {(node.ringCount ?? 0) > 0 && ( + handleUpdate({ ringSpread: value, ringPlacement: 'ends' })} + precision={2} + step={0.01} + value={node.ringSpread ?? 0.16} + /> + )} + + )} + {!isBraceSupport && (