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>
319 lines
8.5 KiB
TypeScript
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,
|
|
};
|
|
}
|