import React, { useMemo, useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { useHydrationStore } from '@/store/hydrationStore'; import { SaveModuleDialog } from '@/components/SaveModuleDialog'; import { Slider } from '@/components/ui/slider'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { useWindStore } from '@/store/appStore'; import { calculatePiperack, type PipeDef } from '@/lib/modules/piperack'; import { isResonantRisk } from '@/lib/dynamics'; import { Piperack3D } from '@/components/three/Piperack3D'; import SceneCapturePanel from '../components/SceneCapturePanel'; import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; import { WindParametersSummary } from '@/components/WindParametersSummary'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; import { Plus, Trash2, Info, AlertTriangle } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip'; import { Input } from '@/components/ui/input'; import { usePersistentState } from '@/hooks/usePersistentState'; const PiperackModule: React.FC = () => { const { q } = useWindStore(); const [width, setWidth] = usePersistentState('PiperackModule_width', 17); const [height, setHeight] = usePersistentState('PiperackModule_height', 2); const [elevation, setElevation] = usePersistentState('PiperackModule_elevation', 6); const [spacing, setSpacing] = usePersistentState('PiperackModule_spacing', 2); const [numFrames, setNumFrames] = usePersistentState('PiperackModule_numFrames', 2); const [phiStruct, setPhiStruct] = usePersistentState('PiperackModule_phiStruct', 0.5); const [f1, setF1] = usePersistentState('PiperackModule_f1', 1.5); // Frequência natural (Hz) const [pipes, setPipes] = usePersistentState('PiperackModule_pipes', []); const pendingLoad = useHydrationStore((s) => s.pendingLoad); const clearPendingLoad = useHydrationStore((s) => s.clearPendingLoad); React.useEffect(() => { if (pendingLoad && pendingLoad.module === 'piperack') { const p = pendingLoad.inputs as any; if (p.elevation !== undefined) setElevation(p.elevation); if (p.width !== undefined) setWidth(p.width); if (p.height !== undefined) setHeight(p.height); if (p.spacing !== undefined) setSpacing(p.spacing); if (p.numFrames !== undefined) setNumFrames(p.numFrames); if (p.pipes !== undefined) setPipes(p.pipes); if (p.phiStruct !== undefined) setPhiStruct(p.phiStruct); if (p.f1 !== undefined) setF1(p.f1); clearPendingLoad(); } }, [pendingLoad, clearPendingLoad]); const addPipe = () => { setPipes([...pipes, { id: Math.random().toString(36).substr(2, 9), diameter: 0.5, elevationOffset: height / 2 }]); }; const removePipe = (id: string) => { setPipes(pipes.filter(p => p.id !== id)); }; const updatePipe = (id: string, diam: number, yOffset: number) => { setPipes(pipes.map(p => p.id === id ? { ...p, diameter: diam, elevationOffset: yOffset } : p)); }; const result = useMemo(() => { return calculatePiperack({ width, height, elevation, spacing, numFrames, phiStruct, pipes, q, }); }, [width, height, elevation, spacing, numFrames, phiStruct, pipes, q]); const handleExportPDF = () => { const sections: GenericPDFSection[] = [ { title: 'Geometria do Pipe-Rack', type: 'grid', gridItems: [ { label: 'Largura do pórtico (b)', value: `${width} m` }, { label: 'Altura do pórtico (h)', value: `${height} m` }, { label: 'Elevação média (z)', value: `${elevation} m` }, { label: 'Espaçamento (e)', value: `${spacing} m` }, { label: 'Número de pórticos (n)', value: String(numFrames) }, { label: 'Solidez estrutural', value: phiStruct.toFixed(2) }, { label: 'Quantidade de Tubos', value: String(pipes.length) }, ], }, { title: 'Coeficientes Aerodinâmicos', type: 'grid', gridItems: [ { label: 'Solidez total (est. + tubos)', value: result.phiTotal.toFixed(2) }, { label: 'Ca frontal', value: result.caFrontal.toFixed(2) }, { label: 'Fator de proteção', value: result.eta.toFixed(2) }, ], }, { title: 'Forças e Momentos Globais', type: 'grid', gridItems: [ { label: 'Carga Linear Pórtico Frontal', value: `${result.linearLoadFront.toFixed(2)} kN/m` }, { label: 'Carga Linear Pórticos Traseiros (cada)', value: `${result.linearLoadBack.toFixed(2)} kN/m` }, { label: 'Força Global Total', value: `${result.globalForce.toFixed(2)} kN` }, { label: 'Centro de Pressão Efetivo (Z_eff)', value: `${result.effectiveElevation.toFixed(2)} m` }, { label: 'Momento Total de Tombamento', value: `${(result.globalForce * result.effectiveElevation).toFixed(2)} kN·m` }, ], }, { title: 'Demonstração dos Passos Matemáticos — Pipe-Racks Industriais', type: 'math-card', mathSteps: [ { title: '1. Solidez Combinada (φ_total) e Coeficiente Frontal (Ca)', formula: 'φ_total = φ_estrutura + φ_tubulações -> Ca_frontal = f(φ_total)', calculation: `φ_estrutural (${phiStruct.toFixed(2)}) + φ_tubos = ${result.phiTotal.toFixed(2)} -> Ca frontal = ${result.caFrontal.toFixed(2)}`, result: `Ca_frontal = ${result.caFrontal.toFixed(2)}`, note: 'Resistência aerodinâmica inicial considerando a obstrução conjunta das vigas, pilares e leito de tubulações.', }, { title: '2. Fator de Proteção Aerodinâmica (η) para Pórticos Sucessivos', formula: 'η = f(e / h , φ_total) (efeito de sombreamento entre quadros sucessivos)', calculation: `Espaçamento e/h = ${(spacing / height).toFixed(2)} e φ_total = ${result.phiTotal.toFixed(2)} -> Fator η = ${result.eta.toFixed(2)}`, result: `η = ${result.eta.toFixed(2)}`, note: 'Os pórticos posteriores ao frontal recebem vento atenuado pelo sombreamento do pórtico a barlavento.', }, { title: '3. Cargas Lineares nos Pórticos Frontal e Traseiros (q_linear)', formula: 'q_front = q · Ca · h | q_back = q_front · η [kN/m]', calculation: `${q.toFixed(2)} kN/m² · ${result.caFrontal.toFixed(2)} · (${height} m) = ${result.linearLoadFront.toFixed(2)} kN/m (Frontal) | Traseiro: ${result.linearLoadBack.toFixed(2)} kN/m`, result: `q_front = ${result.linearLoadFront.toFixed(2)} kN/m`, note: 'Carga linear distribuída horizontalmente no topo dos pórticos principais do pipe-rack.', }, { title: '4. Força Global Total e Momento de Tombamento na Base (M)', formula: 'F_total = F_front + (n − 1)·F_back | M_tombamento = F_total · Z_eff [kNm]', calculation: `${result.globalForce.toFixed(2)} kN · (${result.effectiveElevation.toFixed(2)} m) = ${(result.globalForce * result.effectiveElevation).toFixed(2)} kNm`, result: `M_total = ${(result.globalForce * result.effectiveElevation).toFixed(2)} kNm`, note: 'Momento fletor global transferido para os pilares e fundações industriais.', }, ], }, ]; exportGenericToPDF('Pipe-Racks Industriais', sections); }; return (
{/* Coluna Esquerda: Entradas */}
Pipe-Racks Industriais Vento transversal (Sec. 7 - NBR 6123)

Geometria da Estrutura

Largura (b) {width} m
setWidth(v[0])} />
Altura do pórtico (h) {height} m
setHeight(v[0])} />
Elevação (z) {elevation} m
setElevation(v[0])} />
Espaçamento (e) {spacing} m
setSpacing(v[0])} />
Número de Pórticos (n) {numFrames}
setNumFrames(v[0])} />
Índice de Solidez (φ str)

O índice de solidez (φ) representa a relação entre a área efetivamente bloqueada pelos perfis do reticulado e a área total contornada pelo pórtico. Quanto mais "fechado" for o pórtico estrutural, maior o arrasto aerodinâmico (ref. Tabela 28 da NBR 6123).

{phiStruct.toFixed(2)}
setPhiStruct(v[0])} />
Freq. Natural (f₁)

Frequência fundamental no sentido do vento (Hz). Valores abaixo de 1 Hz indicam risco dinâmico elevado.

{f1.toFixed(1)} Hz
setF1(v[0])} /> {isResonantRisk(f1) && (

Estrutura flexível (f₁ < 1 Hz). O vento produzirá amplificação dinâmica (C.A.D) considerável. É exigida análise pela NBR 6123 Capítulo 9.

)}

Tubulações (Bloqueio)

{pipes.length === 0 ? (
Nenhuma tubulação adicionada.
) : (
{pipes.map((pipe, index) => (
updatePipe(pipe.id, Number(e.target.value), pipe.elevationOffset)} step={0.1} min={0.1} className="h-8 text-xs font-mono" />
updatePipe(pipe.id, pipe.diameter, Number(e.target.value))} step={0.1} min={0} max={height} className="h-8 text-xs font-mono" />
))}
)}
{/* Coluna Central: 3D */}
{/* Coluna Direita: Resultados */}
Forças Calculadas {result.globalForce.toFixed(1)} kN (Global)
φ efetivo {result.phiTotal.toFixed(2)}
Ca frontal (Tab. 26 adaptada) {result.caFrontal.toFixed(2)}
Fator η (Tab. 28) {result.eta.toFixed(2)}
Carga Pórtico 1 {result.linearLoadFront.toFixed(2)} kN/m
Carga Pórticos {numFrames > 1 ? `2 a ${numFrames}` : '-'} {result.linearLoadBack.toFixed(2)} kN/m

Distribuição do Vento (Vista Lateral)

{/* Linha do chão */} {/* Renderização Dinâmica baseada no numFrames */} {(() => { const startX = 60; const endX = 240; const step = numFrames > 1 ? (endX - startX) / (numFrames - 1) : 0; return ( <> {/* Vigas Longitudinais */} {numFrames > 1 && ( <> )} {/* Colunas */} {Array.from({ length: numFrames }).map((_, i) => { const colX = numFrames === 1 ? 150 : startX + i * step; return ( 3 ? "8" : "10"} fill="#475569"> {`C${i + 1}`} ); })} {/* Linha de cota para o espaçamento (e) entre C1 e C2 */} {numFrames > 1 && ( <> e = {spacing}m )} {/* Suportes para tubulação quando n=1 */} {numFrames === 1 && pipes.length > 0 && ( )} {/* Vento batendo na primeira coluna */} Vento (F) ); })()} {/* Tubulações transversais cortadas (Círculos azuis) */} {(() => { if (pipes.length === 0) return null; const startX = numFrames === 1 ? 100 : 60; const endX = numFrames === 1 ? 200 : 240; const gap = pipes.length > 1 ? (endX - startX) / (pipes.length - 1) : 0; return pipes.map((p, idx) => { const cx = pipes.length === 1 ? (startX + endX)/2 : startX + idx * gap; const r = Math.max(4, Math.min(15, p.diameter * 8)); // escala visual // p.elevationOffset varia de 0 (base) a height (topo) // no SVG: Y cresce para baixo. y=110 (chão). Vigas y=40, y=65 // Para não complicar a matemática com proporções da viga, // Mapeamos a elevação local para a região do pórtico no SVG (y=110 chão, base=110 - (elevation - height/2) - isso é dinâmico) // Vamos fazer simples: The drawn structural portico is roughly Y=35 (top) to Y=110 (bottom). // If n=1, structure top is Y=35. // Actually the pipe is attached to the column at a relative height. const localRatio = Math.max(0, Math.min(1, p.elevationOffset / height)); // No desenho, a altura reticulada é desenhada num "y" aproximado entre Y=35 (topo) e Y=110 (base da coluna inteira). // Mas o pórtico real desenhado ali tem topo em 35 e altura 75. // Então base do reticulado = 35 + 75 = 110. (Não, isso é pro solo). // Supondo que a altura h desenhada seja o topo da coluna Y=35 até uns 2/3 (Y=60), // Y invertido no SVG: base da seção = 65, topo = 35 (altura de 30 px). const svgTop = 35; const svgBottom = 65; const cy = svgBottom - localRatio * (svgBottom - svgTop) - r; return ( ); }); })()} {/* Eixo indicativo de elevação Z_eff */} Z_eff
Forças e Momentos Aerodinâmicos
F. Frontal: {(result.linearLoadFront * width).toFixed(2)} kN
{numFrames > 1 &&
F. Traseiros (Total): {((numFrames - 1) * result.linearLoadBack * width).toFixed(2)} kN
}
Z efetivo (Z_eff): {result.effectiveElevation.toFixed(2)} m
Momento Global de Tombamento:
{(result.globalForce * result.effectiveElevation).toFixed(2)} kN·m

* O Momento Global representa a ação aerodinâmica pura. As reações reais (esforços axiais e fletores) devem ser obtidas no modelo estrutural.

); }; export default PiperackModule;