From fbc9fefeeac6ca652296542cab9b0f674edbaeb4 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 14 Feb 2026 01:19:54 +0000 Subject: [PATCH] fix: Physically mounted handles with proper standoffs and powder-coat material - All 6 handle types now have cylindrical mount standoffs (pootjes) connecting grip to door face: r=6mm, length=40mm - Z-positioning: grip sits at PROFILE_DEPTH/2 + MOUNT_LENGTH (60mm from center), no more floating handles inside the door - Material: replaced chrome HandleMaterial (metalness=0.95) with PowderCoatMaterial matching door frame texture (roughness=0.7, metalness=0.6) - UGreep fully redesigned: proper U-shape with 2 standoffs + vertical bar - All handles cast shadows onto the door frame for depth Co-Authored-By: Claude Opus 4.6 --- components/configurator/handles-3d.tsx | 478 ++++++++++++++++--------- 1 file changed, 311 insertions(+), 167 deletions(-) diff --git a/components/configurator/handles-3d.tsx b/components/configurator/handles-3d.tsx index 784ee65..11d650a 100644 --- a/components/configurator/handles-3d.tsx +++ b/components/configurator/handles-3d.tsx @@ -1,9 +1,25 @@ "use client"; -import { RoundedBox } from "@react-three/drei"; +import { Suspense } from "react"; +import { RoundedBox, useTexture } from "@react-three/drei"; import * as THREE from "three"; -interface HandleProps { +// ============================================ +// PHYSICAL CONSTANTS (mm converted to meters) +// ============================================ + +const PROFILE_DEPTH_M = 0.04; // 40mm profile depth +const DOOR_FACE_Z = PROFILE_DEPTH_M / 2; // 20mm - front face of door + +const MOUNT_RADIUS = 0.006; // 6mm radius standoff cylinders +const MOUNT_LENGTH = 0.04; // 40mm standoff length +const MOUNT_CENTER_Z = DOOR_FACE_Z + MOUNT_LENGTH / 2; // Center of mount +const GRIP_CENTER_Z = DOOR_FACE_Z + MOUNT_LENGTH; // Front face of mount = grip center + +const GRIP_RADIUS = 0.01; // 10mm radius for round grips +const GRIP_BAR_SIZE = 0.02; // 20mm for square grip cross-section + +export interface HandleProps { finish: string; doorWidth: number; doorHeight: number; @@ -11,66 +27,190 @@ interface HandleProps { stileWidth: number; } -// Steel material for handles -const HandleMaterial = ({ color }: { color: string }) => ( - -); +// ============================================ +// MATERIALS +// ============================================ /** - * Beugelgreep - Vertical bar handle with mounting blocks - * Classic industrial style, common on steel pivot doors + * Powder-coated steel material matching door frame finish. + * Loaded with texture for visual continuity with the frame. */ -export function Beugelgreep({ finish, doorHeight, railDepth }: HandleProps) { - const color = ({ - zwart: "#1a1a1a", - brons: "#8B6F47", - grijs: "#525252", - }[finish] || "#1a1a1a") as string; +function HandleMaterialTextured({ color, finish }: { color: string; finish: string }) { + try { + const texturePath = { + zwart: "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-zwart.jpg", + brons: "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-brons.jpg", + grijs: "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-antraciet.jpg", + }[finish] || "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-zwart.jpg"; - const handleLength = Math.min(doorHeight * 0.35, 0.8); // Max 80cm - const barDiameter = 0.025; // 25mm diameter bar - const mountBlockSize: [number, number, number] = [0.04, 0.06, 0.03]; // Mount block dimensions + const texture = useTexture(texturePath); + texture.wrapS = texture.wrapT = THREE.RepeatWrapping; + texture.repeat.set(0.2, 1); + texture.colorSpace = THREE.SRGBColorSpace; + + return ( + + ); + } catch { + return ; + } +} + +function HandleMaterialFallback({ color }: { color: string }) { + return ( + + ); +} + +/** Wrap textured material in Suspense */ +function PowderCoatMaterial({ color, finish }: { color: string; finish: string }) { + return ( + }> + + + ); +} + +function getColor(finish: string): string { + return { zwart: "#1a1a1a", brons: "#8B6F47", grijs: "#525252" }[finish] || "#1a1a1a"; +} + +// ============================================ +// SHARED MOUNT COMPONENT +// ============================================ + +/** + * A single cylindrical standoff (pootje) connecting handle to door face. + * Rotated 90° on X to point outward from the door surface. + */ +function MountStandoff({ + position, + color, + finish, +}: { + position: [number, number, number]; + color: string; + finish: string; +}) { + return ( + + + + + ); +} + +// ============================================ +// HANDLE COMPONENTS +// ============================================ + +/** + * U-Greep: Proper U-shaped bar handle with two standoff mounts. + * The grip sits 40mm off the door face, connected by two cylindrical pootjes. + */ +export function UGreep({ finish, doorHeight }: HandleProps) { + const color = getColor(finish); + const gripLength = Math.min(doorHeight * 0.25, 0.6); // Max 60cm, proportional + const mountSpacing = gripLength - GRIP_BAR_SIZE; // Distance between mount centers return ( - - {/* Main vertical bar */} - - - - + + {/* Top mount standoff */} + - {/* Top mounting block */} + {/* Bottom mount standoff */} + + + {/* Vertical grip bar */} + + + + + ); +} + +/** + * Beugelgreep: Vertical bar handle (round) with mounting blocks. + * Two rectangular mounting blocks press against the door face, + * with a round bar connecting them. + */ +export function Beugelgreep({ finish, doorHeight }: HandleProps) { + const color = getColor(finish); + const gripLength = Math.min(doorHeight * 0.35, 0.8); // Max 80cm + const barDiameter = 0.025; // 25mm + const mountBlockSize: [number, number, number] = [0.04, 0.05, MOUNT_LENGTH]; + const mountSpacing = gripLength * 0.85; + + return ( + + {/* Top mounting block (sits on door face, extends outward) */} - + {/* Bottom mounting block */} - + - {/* Mounting screws (detail) */} - {[ - [0, handleLength / 2 + mountBlockSize[1] / 2, 0.005], - [0, -handleLength / 2 - mountBlockSize[1] / 2, 0.005], - ].map((pos, i) => ( - - + {/* Main vertical round bar */} + + + + + + {/* Mounting screw details */} + {[mountSpacing / 2, -mountSpacing / 2].map((y, i) => ( + + ))} @@ -79,32 +219,44 @@ export function Beugelgreep({ finish, doorHeight, railDepth }: HandleProps) { } /** - * Hoekgreep - L-shaped corner handle - * Minimalist flush-mount design + * Hoekgreep: L-shaped corner handle with standoff mounts. + * Horizontal bar + vertical bar meeting at a rounded corner. */ -export function Hoekgreep({ finish, doorWidth, railDepth, stileWidth }: HandleProps) { - const color = ({ - zwart: "#1a1a1a", - brons: "#8B6F47", - grijs: "#525252", - }[finish] || "#1a1a1a") as string; +export function Hoekgreep({ finish, doorWidth, stileWidth }: HandleProps) { + const color = getColor(finish); + const horizontalLength = 0.15; + const verticalLength = 0.12; + const barThickness = 0.02; + const barWidth = 0.03; - const horizontalLength = 0.15; // 15cm horizontal - const verticalLength = 0.12; // 12cm vertical - const barThickness = 0.02; // 20mm thick - const barWidth = 0.03; // 30mm wide + // Position near right stile + const xPos = doorWidth / 2 - stileWidth - 0.12; return ( - + + {/* Top mount standoff */} + + + {/* Bottom mount standoff */} + + {/* Horizontal bar */} - + {/* Vertical bar */} @@ -112,157 +264,173 @@ export function Hoekgreep({ finish, doorWidth, railDepth, stileWidth }: HandlePr args={[barWidth, verticalLength, barThickness]} radius={0.003} smoothness={4} - position={[0, 0, 0]} + position={[0, 0, GRIP_CENTER_Z]} castShadow > - + - {/* Corner radius (decorative) */} - - - + {/* Corner radius */} + + + ); } /** - * Maangreep - Crescent/moon shaped recessed handle - * Elegant curved design for flush doors + * Maangreep: Crescent/moon shaped handle with standoff mounts. + * Curved torus section mounted on two pootjes. */ -export function Maangreep({ finish, doorWidth, railDepth, stileWidth }: HandleProps) { - const color = ({ - zwart: "#1a1a1a", - brons: "#8B6F47", - grijs: "#525252", - }[finish] || "#1a1a1a") as string; - - const curveRadius = 0.08; // 8cm radius - const handleDepth = 0.025; // 25mm deep recess +export function Maangreep({ finish, doorWidth, stileWidth }: HandleProps) { + const color = getColor(finish); + const curveRadius = 0.08; + const xPos = doorWidth / 2 - stileWidth - 0.12; return ( - + + {/* Left mount standoff */} + + + {/* Right mount standoff */} + + {/* Main curved handle body */} - + - + {/* Left end cap */} - + - + {/* Right end cap */} - + - + - - {/* Recessed mounting plate */} - - - ); } /** - * Ovaalgreep - Oval/elliptical pull handle - * Modern minimalist design + * Ovaalgreep: Oval/elliptical pull handle with standoff mounts. + * Extruded ellipse shape mounted on two pootjes. */ -export function Ovaalgreep({ finish, doorWidth, railDepth, stileWidth }: HandleProps) { - const color = ({ - zwart: "#1a1a1a", - brons: "#8B6F47", - grijs: "#525252", - }[finish] || "#1a1a1a") as string; +export function Ovaalgreep({ finish, doorWidth, stileWidth }: HandleProps) { + const color = getColor(finish); + const xPos = doorWidth / 2 - stileWidth - 0.12; - // Create oval shape using THREE.Shape const shape = new THREE.Shape(); - const rx = 0.06; // 6cm horizontal radius - const ry = 0.03; // 3cm vertical radius - - // Draw ellipse + const rx = 0.06; + const ry = 0.03; for (let i = 0; i <= 64; i++) { const angle = (i / 64) * Math.PI * 2; const x = Math.cos(angle) * rx; const y = Math.sin(angle) * ry; - if (i === 0) { - shape.moveTo(x, y); - } else { - shape.lineTo(x, y); - } + if (i === 0) shape.moveTo(x, y); + else shape.lineTo(x, y); } - const extrudeSettings = { - depth: 0.02, - bevelEnabled: true, - bevelThickness: 0.003, - bevelSize: 0.003, - bevelSegments: 8, - }; - return ( - - {/* Oval handle ring */} - - - + + {/* Top mount standoff */} + + + {/* Bottom mount standoff */} + + + {/* Oval handle */} + + + ); } /** - * Klink - Traditional door handle with lever - * Classic hinged door handle + * Klink: Traditional lever handle with rosette, standoff-mounted. + * Rosette plate against door, lever extending horizontally. */ -export function Klink({ finish, doorWidth, railDepth, stileWidth }: HandleProps) { - const color = ({ - zwart: "#1a1a1a", - brons: "#8B6F47", - grijs: "#525252", - }[finish] || "#1a1a1a") as string; - - const leverLength = 0.12; // 12cm lever - const leverThickness = 0.015; // 15mm thick +export function Klink({ finish, doorWidth, stileWidth }: HandleProps) { + const color = getColor(finish); + const leverLength = 0.12; + const xPos = doorWidth / 2 - stileWidth - 0.1; return ( - - {/* Mounting rosette (round plate) */} - + + {/* Mounting rosette (flat against door face) */} + - + + + + {/* Square spindle standoff (connects rosette to lever) */} + + + {/* Lever handle */} - + - {/* Lever end (ergonomic grip) */} - + {/* Lever end grip */} + - + - {/* Lock cylinder (detail) */} - + {/* Lock cylinder below */} + ); } - -/** - * U-Greep - U-shaped bar handle (current simple version) - * Straight vertical bar for pivot doors - */ -export function UGreep({ finish, railDepth }: HandleProps) { - const color = ({ - zwart: "#1a1a1a", - brons: "#8B6F47", - grijs: "#525252", - }[finish] || "#1a1a1a") as string; - - return ( - - - - ); -}