diff --git a/app/src/App.tsx b/app/src/App.tsx index 7addc72..f173e07 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -14,7 +14,7 @@ import PiperackModule from './pages/PiperackModule'; import { Home, Settings, Menu, Cylinder, Church, CircleDot, Square, Layers, BarChart3, Activity, Warehouse, - Settings2, Sun, Moon, + Settings2, Sun, Moon, Frame, } from 'lucide-react'; import { useState } from 'react'; import { cn } from '@/lib/utils'; @@ -84,7 +84,7 @@ function AppLayout({ children }: { children: React.ReactNode }) { { path: '/barras', icon: , labelKey: 'nav_bar' as const }, { path: '/pontes', icon: , labelKey: 'nav_bridge' as const }, { path: '/torre', icon: , labelKey: 'nav_tower' as const }, - { path: '/piperack', icon: , labelKey: 'nav_piperack' as const }, + { path: '/piperack', icon: , labelKey: 'nav_piperack' as const }, { path: '/dinamica', icon: , labelKey: 'nav_dynamics' as const }, ]; @@ -278,7 +278,7 @@ function HomeMock() { { to: '/barras', icon: BarChart3, label: 'Barras Prismáticas', desc: 'Faces planas (Tab. 26) ou circulares (Tab. 27).' }, { to: '/pontes', icon: BridgeIcon, label: 'Pontes', desc: 'Pse, Cx/Cz do tabuleiro, flutter, galope (sec. 11).' }, { to: '/torre', icon: TowerIcon, label: 'Torres', desc: 'Torres treliçadas e mastros (sec. 8.4).' }, - { to: '/piperack', icon: Layers, label: 'Pipe-Racks Industriais', desc: 'Estruturas reticuladas múltiplas, tubulações, fator η (Tab. 28).' }, + { to: '/piperack', icon: Frame, label: 'Pipe-Racks Industriais', desc: 'Estruturas reticuladas múltiplas, tubulações, fator η (Tab. 28).' }, ]; return ( diff --git a/app/src/lib/export-generic-pdf.tsx b/app/src/lib/export-generic-pdf.tsx index 62a01d6..94003f8 100644 --- a/app/src/lib/export-generic-pdf.tsx +++ b/app/src/lib/export-generic-pdf.tsx @@ -23,6 +23,24 @@ const styles = StyleSheet.create({ sceneCaption: { fontSize: 8, color: '#666', fontStyle: 'italic', textAlign: 'center', marginBottom: 8 }, }); +export function appendKgf(val: string | number): string { + if (typeof val === 'number') return String(val); + return val.replace(/(-?\d+(?:[.,]\d+)?)\s*(kN(?:\/m²|\/m2|\/m|·m|\.m|m)?)/g, (match, numStr, unit) => { + const num = parseFloat(numStr.replace(',', '.')); + if (isNaN(num)) return match; + const kgf = num * 101.9716; + + let kgfUnit = 'kgf'; + if (unit.includes('/m²') || unit.includes('/m2')) kgfUnit = 'kgf/m²'; + else if (unit.includes('/m')) kgfUnit = 'kgf/m'; + else if (unit.includes('·m') || unit.includes('.m') || unit === 'kNm') kgfUnit = 'kgf·m'; + + const kgfStr = kgf.toFixed(1); + const kgfFormatted = numStr.includes(',') ? kgfStr.replace('.', ',') : kgfStr; + return `${match} (${kgfFormatted} ${kgfUnit})`; + }); +} + export interface GenericPDFSection { title: string; type: 'table' | 'text' | 'grid'; @@ -80,7 +98,7 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener q = 0,613 × (Vₖ)² - q = 0,613 × ({wind.vk.toFixed(2)})² = {(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m² + {appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)} @@ -102,14 +120,14 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener {sec.type === 'text' && sec.content && ( - {sec.content} + {appendKgf(sec.content)} )} {sec.type === 'grid' && sec.gridItems && ( sec.gridItems.map((item, i) => ( {item.label}: - {item.value} + {appendKgf(item.value)} )) )} @@ -127,7 +145,7 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener {tr.map((tc, cIdx) => ( - {tc} + {appendKgf(tc)} ))} diff --git a/app/src/lib/export-pdf.tsx b/app/src/lib/export-pdf.tsx index 833f023..cc34287 100644 --- a/app/src/lib/export-pdf.tsx +++ b/app/src/lib/export-pdf.tsx @@ -9,6 +9,7 @@ import { getDragForce, } from './line-loads'; import { getWallCpeOfficial, getRoofCpeOfficial } from './coefficients'; +import { appendKgf } from './export-generic-pdf'; const styles = StyleSheet.create({ page: { @@ -138,7 +139,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { q = 0,613 × (Vₖ)² - q = 0,613 × ({wind.vk.toFixed(2)})² = {(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m² + {appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)} @@ -201,14 +202,14 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { Elemento / Região Cpe Cpi - p [kN/m²] + p [kN/m² (kgf/m²)] {Object.entries(wCpe).map(([face, cpeVal]) => ( Parede — {face} {(cpeVal as number).toFixed(2)} {cpi} - {pressure(cpeVal as number)} + {pressure(cpeVal as number)} ({(parseFloat(pressure(cpeVal as number)) * 101.9716).toFixed(1)}) ))} {Object.entries(rCpe).map(([face, cpeVal]) => ( @@ -216,7 +217,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { Telhado — {face} {(cpeVal as number).toFixed(2)} {cpi} - {pressure(cpeVal as number)} + {pressure(cpeVal as number)} ({(parseFloat(pressure(cpeVal as number)) * 101.9716).toFixed(1)}) ))} @@ -224,7 +225,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { - {secBase + 1}. Cargas Lineares (kN/m) + {secBase + 1}. Cargas Lineares (kN/m (kgf/m)) Pórticos: {FRAME_SPACING} m | Terças: {PURLIN_SPACING} m @@ -234,7 +235,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { Pilar Cpe - w [kN/m] + w [kN/m (kgf/m)] {([ ['Barlavento', angle === 0 ? wCpe.C : wCpe.A, colLoads.windward], @@ -245,7 +246,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { {label} {cpeVal.toFixed(2)} - {fmtSigned(w)} + {fmtSigned(w)} ({fmtSigned(w * 101.9716, 1)}) ))} @@ -254,20 +255,20 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { Terça (Zona) Cpe - w [kN/m] + w [kN/m (kgf/m)] {(['E', 'F', 'G', 'H', 'I', 'J'] as const).map((z) => ( {z} {rCpe[z].toFixed(2)} - {fmtSigned(rLoads[z])} + {fmtSigned(rLoads[z])} ({fmtSigned(rLoads[z] * 101.9716, 1)}) ))} - Reação global na base: {fmtSigned(rxns.total, 3)} kN - Força de arrasto global (Cₐ): {dForce.forceKN.toFixed(3)} kN + Reação global na base: {fmtSigned(rxns.total, 3)} kN ({fmtSigned(rxns.total * 101.9716, 1)} kgf) + Força de arrasto global (Cₐ): {dForce.forceKN.toFixed(3)} kN ({(dForce.forceKN * 101.9716).toFixed(1)} kgf) diff --git a/app/src/lib/i18n.ts b/app/src/lib/i18n.ts index 70d32ed..b397e62 100644 --- a/app/src/lib/i18n.ts +++ b/app/src/lib/i18n.ts @@ -58,6 +58,7 @@ const translations: Dict = { nav_bar: { 'pt-BR': 'Barras', 'en-US': 'Bars' }, nav_bridge: { 'pt-BR': 'Pontes', 'en-US': 'Bridges' }, nav_tower: { 'pt-BR': 'Torres', 'en-US': 'Towers' }, + nav_piperack: { 'pt-BR': 'Pipe-rack', 'en-US': 'Pipe-rack' }, nav_dynamics: { 'pt-BR': 'Dinâmica + Vórtices', 'en-US': 'Dynamics + Vortex' }, nav_settings: { 'pt-BR': 'Preferências', 'en-US': 'Preferences' }, nav_collapse: { 'pt-BR': 'Recolher sidebar', 'en-US': 'Collapse sidebar' }, diff --git a/app/src/pages/PiperackModule.tsx b/app/src/pages/PiperackModule.tsx index bfc1fd3..62fe1e6 100644 --- a/app/src/pages/PiperackModule.tsx +++ b/app/src/pages/PiperackModule.tsx @@ -15,12 +15,12 @@ import { Input } from '@/components/ui/input'; const PiperackModule: React.FC = () => { const { q } = useWindStore(); - const [width, setWidth] = useState(6); - const [height, setHeight] = useState(2.5); - const [elevation, setElevation] = useState(8); - const [spacing, setSpacing] = useState(3); - const [numFrames, setNumFrames] = useState(3); - const [phiStruct, setPhiStruct] = useState(0.2); + const [width, setWidth] = useState(17); + const [height, setHeight] = useState(2); + const [elevation, setElevation] = useState(6); + const [spacing, setSpacing] = useState(2); + const [numFrames, setNumFrames] = useState(2); + const [phiStruct, setPhiStruct] = useState(0.5); const [pipes, setPipes] = useState([]); const addPipe = () => { @@ -59,7 +59,7 @@ const PiperackModule: React.FC = () => { { 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: 'φ (estrutural)', value: phiStruct.toFixed(2) }, + { label: 'Solidez estrutural', value: phiStruct.toFixed(2) }, { label: 'Quantidade de Tubos', value: String(pipes.length) }, ], }, @@ -67,9 +67,9 @@ const PiperackModule: React.FC = () => { title: 'Coeficientes Aerodinâmicos', type: 'grid', gridItems: [ - { label: 'φ total (estrutura + tubos)', value: result.phiTotal.toFixed(2) }, + { label: 'Solidez total (est. + tubos)', value: result.phiTotal.toFixed(2) }, { label: 'Ca frontal', value: result.caFrontal.toFixed(2) }, - { label: 'Fator proteção η', value: result.eta.toFixed(2) }, + { label: 'Fator de proteção', value: result.eta.toFixed(2) }, ], }, { @@ -80,6 +80,18 @@ const PiperackModule: React.FC = () => { { label: 'Carga Linear Pórtico Traseiros (cada)', value: `${result.linearLoadBack.toFixed(2)} kN/m` }, { label: 'Força Global Total', value: `${result.globalForce.toFixed(2)} kN` }, ], + }, + { + title: 'Reações Simplificadas na Base (Binário de Tombamento)', + type: 'grid', + gridItems: [ + { label: 'Pórtico Frontal - Momento (M)', value: `${(result.linearLoadFront * width * elevation).toFixed(2)} kN·m` }, + { label: 'Pórtico Frontal - C1 (Tração) / C2 (Compressão)', value: `±${((result.linearLoadFront * width * elevation) / width).toFixed(2)} kN` }, + ...(numFrames > 1 ? [ + { label: 'Pórtico Traseiro - Momento (M)', value: `${(result.linearLoadBack * width * elevation).toFixed(2)} kN·m` }, + { label: 'Pórtico Traseiro - C1 (Tração) / C2 (Compressão)', value: `±${((result.linearLoadBack * width * elevation) / width).toFixed(2)} kN` }, + ] : []) + ], } ]; @@ -116,7 +128,6 @@ const PiperackModule: React.FC = () => { phiTotal={result.phiTotal} eta={result.eta} /> - {/* Painel Direito: Controles */} @@ -146,10 +157,64 @@ const PiperackModule: React.FC = () => { Carga Pórtico 1 {result.linearLoadFront.toFixed(2)} kN/m -
+
Carga Pórticos {numFrames > 1 ? `2 a ${numFrames}` : '-'} {result.linearLoadBack.toFixed(2)} kN/m
+ +
+

Reações Simplificadas nas Bases

+
+ + + + + + C1 (Barlavento) + + Tração + + + C2 (Sotavento) + + Comp. + + + + + Vento (F) + + + + + + + + + + + + + + +
+
+
Pórtico Frontal
+
F (Transv.): {(result.linearLoadFront * width).toFixed(2)} kN
+
Momento: {(result.linearLoadFront * width * elevation).toFixed(2)} kN·m
+
Reação (C1/C2): ±{((result.linearLoadFront * width * elevation) / width).toFixed(2)} kN
+
+ {numFrames > 1 && ( +
+
Pórticos Traseiros
+
F (Transv.): {(result.linearLoadBack * width).toFixed(2)} kN
+
Momento: {(result.linearLoadBack * width * elevation).toFixed(2)} kN·m
+
Reação (C1/C2): ±{((result.linearLoadBack * width * elevation) / width).toFixed(2)} kN
+
+ )} +
+
+
@@ -249,6 +314,8 @@ const PiperackModule: React.FC = () => { )}
+ +