import { useState, useMemo } from 'react'; import { usePileCapStore } from '../../store/pileCapStore'; import { calculatePileReactions } from '../../calculations/pileReactions'; import { calculateLoadCombinations, calculateServiceCombinations } from '../../calculations/loadCombinations'; import { DraggableLabel } from './DraggableLabel'; export const PunchingShearDiagram = () => { const geometry = usePileCapStore(state => state.geometry); const loads = usePileCapStore(state => state.loads); const design = usePileCapStore(state => state.design); // Global Diagram State const isLocked = usePileCapStore(state => state.diagramState.isLocked); const labelPositions = usePileCapStore(state => state.diagramState.labelPositions); const setDiagramLocked = usePileCapStore(state => state.setDiagramLocked); const updateLabelPosition = usePileCapStore(state => state.updateLabelPosition); const getLabelPos = (id: string, defaultX: number, defaultY: number) => { return labelPositions[id] || { x: defaultX, y: defaultY }; }; const [combType, setCombType] = useState<'service' | 'factored'>('factored'); // Default to factored for punching checks usually const [selectedCombName, setSelectedCombName] = useState(''); // Calculate combinations based on type const combinations = useMemo(() => { return combType === 'service' ? calculateServiceCombinations(loads.cases) : calculateLoadCombinations(loads.cases); }, [loads.cases, combType]); // Select default combination useMemo(() => { if (combinations.length > 0) { const exists = combinations.find(c => c.combinationName === selectedCombName); if (!exists) { setSelectedCombName(combinations[0].combinationName); } } }, [combinations, selectedCombName]); const activeCombination = combinations.find(c => c.combinationName === selectedCombName) || combinations[0]; // Calculate reactions for active combination const pileReactions = useMemo(() => { if (!activeCombination) return []; return calculatePileReactions(geometry, activeCombination, design); }, [geometry, activeCombination, design]); // --- Calculations for Punching Shear --- const d = geometry.depth - geometry.coverBottom; // inches const d_ft = d / 12; // 1. Critical Section at d/2 const c1 = geometry.columnLength / 12; // ft const c2 = geometry.columnWidth / 12; // ft const critDimX_d2 = c1 + d_ft; // ft const critDimY_d2 = c2 + d_ft; // ft const b0_d2 = 2 * (critDimX_d2 + critDimY_d2) * 12; // inches // Identify piles inside d/2 perimeter const pilesInside_d2 = pileReactions.filter(p => { const dx = Math.abs(p.x - geometry.length / 2); const dy = Math.abs(p.y - geometry.width / 2); return dx <= critDimX_d2 / 2 && dy <= critDimY_d2 / 2; }); const P_u = activeCombination ? activeCombination.axialLoad : 0; const reactionInside_d2 = pilesInside_d2.reduce((sum, p) => sum + p.reaction, 0); const V_u_d2 = P_u - reactionInside_d2; // kips const stress_d2 = (V_u_d2 * 1000) / (b0_d2 * d); // psi // 2. Critical Section at Column Face const critDimX_face = c1; // ft const critDimY_face = c2; // ft const b0_face = 2 * (critDimX_face + critDimY_face) * 12; // inches // Identify piles inside face perimeter (unlikely, but possible for large columns) const pilesInside_face = pileReactions.filter(p => { const dx = Math.abs(p.x - geometry.length / 2); const dy = Math.abs(p.y - geometry.width / 2); return dx <= critDimX_face / 2 && dy <= critDimY_face / 2; }); const reactionInside_face = pilesInside_face.reduce((sum, p) => sum + p.reaction, 0); const V_u_face = P_u - reactionInside_face; // kips const stress_face = (V_u_face * 1000) / (b0_face * d); // psi // --- Drawing Logic --- const scale = 30; const padding = 40; const planWidth = geometry.length * scale; const planHeight = geometry.width * scale; // We render two diagrams side-by-side or stacked. Stacked matches user image better. const svgWidth = planWidth + 2 * padding; const svgHeight = (planHeight + padding + 40) * 2 + padding; // Two diagrams const originX = padding; const originY1 = padding + 20; // Top diagram const originY2 = originY1 + planHeight + padding + 60; // Bottom diagram const renderDiagram = (yOffset: number, title: string, critDimX: number, critDimY: number, stress: number, pilesInside: typeof pileReactions) => { return ( {/* Title */} {/* Cap Outline */} {/* Centerlines */} {/* Piles */} {pileReactions.map((p, i) => { const px = p.x * scale; const py = (geometry.width - p.y) * scale; const pileRadius = (geometry.pileSize / 12 / 2) * scale; // Check if this pile is inside the critical perimeter // We already calculated pilesInside for calculation, but need to match by ID or position // Simple check again for visual highlighting const isInside = pilesInside.some(pi => pi.pileNumber === p.pileNumber); return ( ); })} {/* Critical Perimeter (Dashed Area) */} {/* Centered rect */} {/* Column */} {/* Dimensions for Critical Section */} {/* Width */} {/* Height */} {/* Stress Callout */} {/* Draw a line from the critical perimeter to a label */} ); }; return (
{/* Controls */}
{/* Diagram */}
{renderDiagram(originY1, "Punching at d/2", critDimX_d2, critDimY_d2, stress_d2, pilesInside_d2)} {renderDiagram(originY2, "Punching at Column Face", critDimX_face, critDimY_face, stress_face, pilesInside_face)}
); };