import type { PileCapGeometry, MaterialProperties, PileReaction, FlexuralDesignResult, ReinforcementConfig } from '../types/pileCap'; export function performFlexuralDesign( geometry: PileCapGeometry, pileReactions: PileReaction[], materials: MaterialProperties, reinforcement: ReinforcementConfig ): { flexuralX: FlexuralDesignResult; flexuralY: FlexuralDesignResult } { const flexuralX = designFlexure(geometry, pileReactions, materials, reinforcement, 'X'); const flexuralY = designFlexure(geometry, pileReactions, materials, reinforcement, 'Y'); return { flexuralX, flexuralY }; } function designFlexure( geometry: PileCapGeometry, pileReactions: PileReaction[], materials: MaterialProperties, reinforcement: ReinforcementConfig, direction: 'X' | 'Y' ): FlexuralDesignResult { const { depth, coverBottom, columnLength, columnWidth, length, width } = geometry; // Determine bar properties based on direction and mat // Assuming bottom mat is primary for pile caps (positive moment) const isX = direction === 'X'; const barSizeStr = isX ? reinforcement.bottomXBarSize : reinforcement.bottomZBarSize; const barCount = isX ? reinforcement.bottomXBarCount : reinforcement.bottomZBarCount; const hasHooks = isX ? reinforcement.bottomXBarHooks : reinforcement.bottomZBarHooks; // Parse bar size (e.g., "#8" -> 8) const barSizeNum = parseInt(barSizeStr.replace('#', '')) || 8; const barDiameter = barSizeNum / 8; const barArea = Math.PI * Math.pow(barDiameter / 2, 2); // Effective depth d // Simplified: d = depth - cover - 1.0 (approx average) const d = depth - coverBottom - (isX ? barDiameter / 2 : barDiameter * 1.5); const columnCenterX = length / 2; const columnCenterY = width / 2; const columnDim = isX ? columnLength / 12 : columnWidth / 12; // ft const criticalLocation = columnDim / 2; // Face of column // Calculate Factored Moment at Face of Column let moment = 0; pileReactions.forEach(pile => { const dist = isX ? pile.x - columnCenterX : pile.y - columnCenterY; // Piles outside the critical section contribute to moment if (Math.abs(dist) > criticalLocation) { const leverArm = Math.abs(dist) - criticalLocation; // Only add positive moments (upward pile reaction causing tension in bottom) if (pile.reaction > 0) { moment += pile.reaction * leverArm; } } }); const Mu = moment * 12000; // lb-in const b = isX ? width * 12 : length * 12; // Width of section (in) // Minimum Steel (ACI 318) // As,min = max(3*sqrt(fc)/fy * b*d, 200/fy * b*d) const minRho1 = 3 * Math.sqrt(materials.fc) / materials.fy; const minRho2 = 200 / materials.fy; const minRho = Math.max(minRho1, minRho2); const minAs = minRho * b * d; // Required Steel for Strength // Rn = Mu / (phi * b * d^2) const Rn = Mu / (materials.phiFlexure * b * d * d); let requiredAs = 0; // Check if section is singly reinforced and under-reinforced limit // rho = 0.85*fc/fy * (1 - sqrt(1 - 2*Rn/(0.85*fc))) const term = 1 - 2 * Rn / (0.85 * materials.fc); if (term < 0) { // Section too small, concrete fails requiredAs = 999; // Fail flag } else { const rho = (0.85 * materials.fc / materials.fy) * (1 - Math.sqrt(term)); requiredAs = rho * b * d; } // Governing As required (Strength vs Min) // ACI allows As < As,min if As provided is at least 1/3 greater than analysis required. // For simplicity, we enforce As,min unless As_required * 1.33 < As_min if (requiredAs * 1.33 < minAs) { requiredAs = requiredAs * 1.33; } else { requiredAs = Math.max(requiredAs, minAs); } // Provided Steel const providedAs = barCount * barArea; const spacing = (b - 2 * coverBottom) / Math.max(1, barCount - 1); // Approx spacing // Development Length (ld) Calculation (ACI 318) const psi_t = 1.0; // Bottom bars const psi_e = 1.0; // Uncoated const psi_s = barSizeNum <= 6 ? 0.8 : 1.0; const lambda = 1.0; // Normal weight let ld_factor = 0; if (barSizeNum <= 6) { ld_factor = (materials.fy * psi_t * psi_e * psi_s * lambda) / (25 * Math.sqrt(materials.fc)); } else { ld_factor = (materials.fy * psi_t * psi_e * lambda) / (20 * Math.sqrt(materials.fc)); } let requiredLd = ld_factor * barDiameter; // Hook modification? if (hasHooks) { // Standard hook development length ldh const ldh = (0.24 * 1.0 * 0.7 * 1.0 * materials.fy / Math.sqrt(materials.fc)) * barDiameter; requiredLd = Math.max(ldh, 8 * barDiameter, 6); } // Available Length // Distance from critical section (column face) to edge of pile cap minus cover const distToEdge = (isX ? length : width) / 2 - criticalLocation; // ft // Available length is to the END of the bar (edge of cap - cover) const availableLd_actual = distToEdge * 12 - 3; // Assuming 3" side cover return { direction, moment, requiredAs, providedAs, minAs, barSize: barSizeStr, barCount, spacing, developmentLength: { required: requiredLd, available: availableLd_actual, pass: availableLd_actual >= requiredLd }, pass: providedAs >= requiredAs && availableLd_actual >= requiredLd }; }