Files
stalendeuren/lib/door-models.ts
Ubuntu 3d788740cb feat: Latest production version with interior scene and glass
Includes room interior with floor, walls, glass you can see through,
and all uncommitted production changes that were running live.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 14:50:31 +00:00

319 lines
8.5 KiB
TypeScript

/**
* Door Manufacturing Specifications
* Based on "Metalworks" market analysis
* All dimensions in millimeters (mm)
*/
// ============================================
// MANUFACTURING CONSTANTS
// ============================================
export const PROFILE_WIDTH = 40; // mm - Face width
export const PROFILE_DEPTH = 40; // mm - Tube depth
export const PROFILE_CORNER_RADIUS = 2; // mm - Rounded corners for welding
export const STILE_WIDTH = 40; // mm - Vertical profiles
export const RAIL_WIDTH = 20; // mm - Horizontal slim-line profiles
export const GLASS_THICKNESS = 7; // mm - Standard 33.1 Safety Glass
export const GLASS_OFFSET = 15; // mm - Center glass in 40mm profile
export const RAIL_HEIGHT_SLIM = 20; // mm - Slim horizontal rails
export const RAIL_HEIGHT_ROBUST = 40; // mm - Standard robust rails
export const TAATS_PIVOT_OFFSET = 60; // mm
export const STELRUIMTE = 10; // mm
export const HANGNAAD = 3; // mm
export const WALL_THICKNESS = 150; // mm
export function calculateMountingDimensions(sparingsmaatWidth: number, sparingsmaatHeight: number) {
const frameOuterWidth = sparingsmaatWidth - STELRUIMTE;
const frameOuterHeight = sparingsmaatHeight - STELRUIMTE / 2;
const doorLeafWidth = frameOuterWidth - (2 * HANGNAAD);
const doorLeafHeight = frameOuterHeight - (2 * HANGNAAD);
return {
sparingsmaatWidth,
sparingsmaatHeight,
frameOuterWidth,
frameOuterHeight,
doorLeafWidth,
doorLeafHeight,
stelruimtePerSide: STELRUIMTE / 2,
hangnaadPerSide: HANGNAAD,
};
}
// ============================================
// PHYSICAL PART TYPES
// ============================================
export type PartType = 'stile' | 'rail' | 'glass' | 'divider';
export type DoorModel = 'taats' | 'scharnier' | 'paneel';
export type GridLayout =
| 'geen'
| '2-vlak'
| '3-vlak'
| '4-vlak'
| '6-vlak'
| '8-vlak'
| 'kruis'
| 'ongelijk-3'
| 'boerderij'
| 'herenhuis';
export interface PhysicalPart {
type: PartType;
x: number;
y: number;
z: number;
width: number;
height: number;
depth: number;
label?: string;
isGlass?: boolean;
}
export interface DoorAssembly {
modelId: DoorModel;
gridLayout: GridLayout;
doorWidth: number;
doorHeight: number;
parts: PhysicalPart[];
}
// ============================================
// GRID PATTERN DEFINITIONS (DATA-DRIVEN)
// ============================================
/**
* Grid pattern definition as data.
* horizontalPositions: fractional Y positions (0 = bottom, 1 = top) for horizontal dividers
* verticalPositions: fractional X positions (0 = left, 1 = right) for vertical dividers
*/
interface GridPatternDef {
horizontalPositions: number[];
verticalPositions: number[];
}
const GRID_PATTERNS: Record<GridLayout, GridPatternDef> = {
'geen': { horizontalPositions: [], verticalPositions: [] },
'2-vlak': { horizontalPositions: [0.5], verticalPositions: [] },
'3-vlak': { horizontalPositions: [1 / 3, 2 / 3], verticalPositions: [] },
'4-vlak': { horizontalPositions: [0.25, 0.5, 0.75], verticalPositions: [] },
'6-vlak': { horizontalPositions: [1 / 3, 2 / 3], verticalPositions: [0.5] },
'8-vlak': { horizontalPositions: [0.25, 0.5, 0.75], verticalPositions: [0.5] },
'kruis': { horizontalPositions: [0.5], verticalPositions: [0.5] },
'ongelijk-3': { horizontalPositions: [0.35, 0.65], verticalPositions: [] },
'boerderij': { horizontalPositions: [0.7], verticalPositions: [0.5] },
'herenhuis': { horizontalPositions: [0.3, 0.7], verticalPositions: [] },
};
// ============================================
// LAYOUT GENERATION
// ============================================
export function generateDoorAssembly(
modelId: DoorModel,
gridLayout: GridLayout,
doorWidth: number,
doorHeight: number
): DoorAssembly {
const parts: PhysicalPart[] = [];
const pattern = GRID_PATTERNS[gridLayout] || GRID_PATTERNS['geen'];
// ============================================
// PERIMETER FRAME
// ============================================
// LEFT STILE
parts.push({
type: 'stile',
x: -doorWidth / 2 + PROFILE_WIDTH / 2,
y: 0,
z: 0,
width: PROFILE_WIDTH,
height: doorHeight,
depth: PROFILE_DEPTH,
label: 'Left Stile',
});
// RIGHT STILE
parts.push({
type: 'stile',
x: doorWidth / 2 - PROFILE_WIDTH / 2,
y: 0,
z: 0,
width: PROFILE_WIDTH,
height: doorHeight,
depth: PROFILE_DEPTH,
label: 'Right Stile',
});
// TOP RAIL
const topRailWidth = doorWidth - PROFILE_WIDTH * 2;
parts.push({
type: 'rail',
x: 0,
y: doorHeight / 2 - RAIL_HEIGHT_ROBUST / 2,
z: 0,
width: topRailWidth,
height: RAIL_HEIGHT_ROBUST,
depth: PROFILE_DEPTH,
label: 'Top Rail',
});
// BOTTOM RAIL
parts.push({
type: 'rail',
x: 0,
y: -doorHeight / 2 + RAIL_HEIGHT_ROBUST / 2,
z: 0,
width: topRailWidth,
height: RAIL_HEIGHT_ROBUST,
depth: PROFILE_DEPTH,
label: 'Bottom Rail',
});
// ============================================
// HORIZONTAL DIVIDERS (from pattern data)
// ============================================
const innerHeight = doorHeight - RAIL_HEIGHT_ROBUST * 2;
const innerBottom = -doorHeight / 2 + RAIL_HEIGHT_ROBUST;
for (const fraction of pattern.horizontalPositions) {
const dividerY = innerBottom + innerHeight * fraction;
// Determine width: if there are vertical dividers, horizontal dividers span full width
// (vertical dividers will be handled separately)
parts.push({
type: 'divider',
x: 0,
y: dividerY,
z: 0,
width: topRailWidth,
height: RAIL_HEIGHT_SLIM,
depth: PROFILE_DEPTH,
label: `H-Divider ${Math.round(fraction * 100)}%`,
});
}
// ============================================
// VERTICAL DIVIDERS (from pattern data)
// ============================================
const innerWidth = doorWidth - PROFILE_WIDTH * 2;
const innerLeft = -doorWidth / 2 + PROFILE_WIDTH;
for (const fraction of pattern.verticalPositions) {
const dividerX = innerLeft + innerWidth * fraction;
const verticalDividerHeight = innerHeight;
parts.push({
type: 'divider',
x: dividerX,
y: 0,
z: 0,
width: PROFILE_WIDTH,
height: verticalDividerHeight,
depth: PROFILE_DEPTH,
label: `V-Divider ${Math.round(fraction * 100)}%`,
});
}
// ============================================
// PANEEL TYPE CENTER VERTICAL DIVIDER
// ============================================
if (modelId === 'paneel' && !pattern.verticalPositions.includes(0.5)) {
const verticalDividerHeight = doorHeight - RAIL_HEIGHT_ROBUST * 2;
parts.push({
type: 'divider',
x: 0,
y: 0,
z: 0,
width: PROFILE_WIDTH,
height: verticalDividerHeight,
depth: PROFILE_DEPTH,
label: 'Center Vertical Divider',
});
}
// ============================================
// GLASS PANELS
// ============================================
const glassWidth = doorWidth - PROFILE_WIDTH * 2 - GLASS_OFFSET * 2;
const glassHeight = doorHeight - RAIL_HEIGHT_ROBUST * 2 - GLASS_OFFSET * 2;
parts.push({
type: 'glass',
x: 0,
y: 0,
z: 0,
width: glassWidth,
height: glassHeight,
depth: GLASS_THICKNESS,
label: 'Main Glass Panel',
isGlass: true,
});
return {
modelId,
gridLayout,
doorWidth,
doorHeight,
parts,
};
}
export function mmToMeters(mm: number): number {
return mm / 1000;
}
export function getDividerPositions(
gridLayout: GridLayout,
doorHeight: number
): number[] {
const pattern = GRID_PATTERNS[gridLayout];
if (!pattern) return [];
const doorHeightMeters = mmToMeters(doorHeight);
const innerHeight = doorHeightMeters - mmToMeters(RAIL_HEIGHT_ROBUST * 2);
const innerBottom = -doorHeightMeters / 2 + mmToMeters(RAIL_HEIGHT_ROBUST);
return pattern.horizontalPositions.map(
(fraction) => innerBottom + innerHeight * fraction
);
}
export function validateDoorDimensions(
doorWidth: number,
doorHeight: number
): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (doorWidth < PROFILE_WIDTH * 3) {
errors.push(`Door width too small (min: ${PROFILE_WIDTH * 3}mm)`);
}
if (doorHeight < RAIL_HEIGHT_ROBUST * 3) {
errors.push(`Door height too small (min: ${RAIL_HEIGHT_ROBUST * 3}mm)`);
}
if (doorWidth > 1200) {
errors.push('Door width exceeds maximum (1200mm) - structural integrity');
}
if (doorHeight > 3000) {
errors.push('Door height exceeds maximum (3000mm) - structural integrity');
}
return {
valid: errors.length === 0,
errors,
};
}