From 0755a5670294c67684e92f8dd81c92155095706a Mon Sep 17 00:00:00 2001 From: Marcos Date: Tue, 14 Jul 2026 18:03:29 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Auto-deploy:=20BrainWind=20atual?= =?UTF-8?q?izado=20em=2014/07/2026=2018:03:29?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/GlobalWindSettingsModal.tsx | 42 ++- app/src/components/SaveModuleDialog.tsx | 3 +- app/src/components/SceneCanvas.tsx | 2 +- app/src/components/WindParametersSummary.tsx | 44 +++ app/src/lib/export-generic-pdf.tsx | 187 +++++++++--- app/src/lib/export-pdf.tsx | 284 ++++++++++-------- app/src/lib/storage.ts | 2 +- app/src/pages/BarSelectorModule.tsx | 15 +- app/src/pages/BridgeModule.tsx | 22 +- app/src/pages/CylinderModule.tsx | 21 +- app/src/pages/DomeModule.tsx | 15 +- app/src/pages/DynamicsModule.tsx | 15 +- app/src/pages/GalpaoModule.tsx | 20 +- app/src/pages/IsolatedRoofModule.tsx | 15 +- app/src/pages/PiperackModule.tsx | 54 ++-- app/src/pages/SettingsModule.tsx | 54 ++++ app/src/pages/SignModule.tsx | 15 +- app/src/pages/TowerModule.tsx | 15 +- app/src/pages/VaultModule.tsx | 15 +- app/src/store/appStore.ts | 20 +- 20 files changed, 597 insertions(+), 263 deletions(-) create mode 100644 app/src/components/WindParametersSummary.tsx diff --git a/app/src/components/GlobalWindSettingsModal.tsx b/app/src/components/GlobalWindSettingsModal.tsx index 9b5663e..391b008 100644 --- a/app/src/components/GlobalWindSettingsModal.tsx +++ b/app/src/components/GlobalWindSettingsModal.tsx @@ -40,6 +40,8 @@ export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProp setS3, setS3Group, setTerrainCategory, + locality, + setLocality, } = useWindStore(); const [stationQuery, setStationQuery] = useState(''); @@ -74,7 +76,7 @@ export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProp NBR 6123 - Local + Localidade @@ -186,22 +188,30 @@ export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProp
- {filteredStations.map((s) => ( - - ))} + {filteredStations.map((s) => { + const isSelected = locality === s.nome; + return ( + + ); + })}
-

- Selecionar uma estação ajusta V₀. Você pode sobrescrever manualmente na aba NBR. +

+ Selecionar uma estação define a localidade e ajusta V₀. Você pode sobrescrever V₀ manualmente na aba NBR.

diff --git a/app/src/components/SaveModuleDialog.tsx b/app/src/components/SaveModuleDialog.tsx index 9bfbc88..17339c4 100644 --- a/app/src/components/SaveModuleDialog.tsx +++ b/app/src/components/SaveModuleDialog.tsx @@ -15,7 +15,7 @@ import { useProjects } from '../lib/hooks/useProjects'; import { useWindStore } from '../store/appStore'; interface SaveModuleDialogProps { - moduleType: 'galpao' | 'cilindro' | 'vault' | 'dome' | 'sign' | 'isolated-roof' | 'bar' | 'bridge' | 'dynamics' | 'piperack'; + moduleType: 'galpao' | 'cilindro' | 'vault' | 'dome' | 'sign' | 'isolated-roof' | 'bar' | 'bridge' | 'dynamics' | 'piperack' | 'tower'; inputs: Record; triggerButton?: React.ReactNode; } @@ -48,6 +48,7 @@ export const SaveModuleDialog: React.FC = ({ moduleType, terrainCategory: windStore.terrainCategory, largestDimension: windStore.largestDimension, heightZ: windStore.heightZ, + locality: windStore.locality, } : undefined; await save({ diff --git a/app/src/components/SceneCanvas.tsx b/app/src/components/SceneCanvas.tsx index fca7dc9..8a97821 100644 --- a/app/src/components/SceneCanvas.tsx +++ b/app/src/components/SceneCanvas.tsx @@ -97,7 +97,7 @@ export default function SceneCanvas({ fallback, style, className, moduleId, ...r }`} aria-label={isLocked ? 'Destravar visualização 3D' : 'Travar visualização 3D'} > - {isLocked ? : } + {isLocked ? : } diff --git a/app/src/components/WindParametersSummary.tsx b/app/src/components/WindParametersSummary.tsx new file mode 100644 index 0000000..0ad1f28 --- /dev/null +++ b/app/src/components/WindParametersSummary.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { useWindStore } from '@/store/appStore'; +import { MapPin, Wind } from 'lucide-react'; +import { GlobalWindSettingsModal } from './GlobalWindSettingsModal'; + +export const WindParametersSummary: React.FC = () => { + const { v0, s1, s2, s3, vk, q, locality } = useWindStore(); + + return ( +
+
+

+ + Vento Base +

+ Editar} /> +
+ +
+ Localidade: + {locality || 'Não selecionada'} +
+ +
+
+ V₀: + {v0} m/s +
+
+ Vₖ: + {vk.toFixed(2)} m/s +
+
+ S₁×S₂×S₃: + {(s1 * s2 * s3).toFixed(3)} +
+
+ q: + {q.toFixed(3)} kN/m² +
+
+
+ ); +}; diff --git a/app/src/lib/export-generic-pdf.tsx b/app/src/lib/export-generic-pdf.tsx index 94003f8..bdf4232 100644 --- a/app/src/lib/export-generic-pdf.tsx +++ b/app/src/lib/export-generic-pdf.tsx @@ -1,26 +1,38 @@ -import { Document, Page, Text, View, StyleSheet, Image as PdfImage, pdf } from '@react-pdf/renderer'; +import { Document, Page, Text, View, StyleSheet, Image as PdfImage, pdf, Font, Svg, Path } from '@react-pdf/renderer'; import { useWindStore } from '../store/appStore'; import { useCaptureStore } from '../store/captureStore'; +// Registrar fonte Roboto com suporte Unicode completo (subscripts, grego, etc.) +Font.register({ + family: 'Roboto', + fonts: [ + { src: 'https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.ttf' }, + { src: 'https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1Mu4mxMyIFYW8YR8w.ttf', fontWeight: 'bold' }, + { src: 'https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1Mu51xIIzc.ttf', fontStyle: 'italic' }, + ], +}); + const styles = StyleSheet.create({ - page: { flexDirection: 'column', padding: 40, fontSize: 10, fontFamily: 'Helvetica', color: '#333' }, - header: { marginBottom: 20, borderBottom: '2pt solid #6b21a8', paddingBottom: 10 }, - title: { fontSize: 20, fontWeight: 'bold', color: '#6b21a8' }, - subtitle: { fontSize: 10, color: '#666', marginTop: 4 }, - section: { marginTop: 15, marginBottom: 10 }, - sectionTitle: { fontSize: 14, fontWeight: 'bold', marginBottom: 8, color: '#111' }, - row: { flexDirection: 'row', marginBottom: 4 }, - label: { width: 200, fontWeight: 'bold' }, - value: { flex: 1 }, - text: { fontSize: 10, marginBottom: 4, lineHeight: 1.4 }, - table: { display: 'flex', flexDirection: 'column', marginTop: 10, borderTop: '1pt solid #ccc', borderLeft: '1pt solid #ccc' }, + page: { flexDirection: 'column', padding: 40, fontSize: 9, fontFamily: 'Roboto', color: '#334155' }, + header: { marginBottom: 15, borderBottom: '1.5pt solid #6b21a8', paddingBottom: 8, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + title: { fontSize: 16, fontWeight: 'bold', color: '#6b21a8', textAlign: 'right' }, + subtitle: { fontSize: 9, color: '#64748b', marginTop: 3, textAlign: 'right' }, + section: { marginTop: 12, marginBottom: 8 }, + sectionTitle: { fontSize: 11, fontWeight: 'bold', marginBottom: 6, color: '#4c1d95', borderBottom: '0.5pt solid #e2e8f0', paddingBottom: 2 }, + row: { flexDirection: 'row', marginBottom: 3 }, + label: { width: 180, fontWeight: 'bold', color: '#475569' }, + value: { flex: 1, color: '#1e293b' }, + text: { fontSize: 9, marginBottom: 3, lineHeight: 1.3, color: '#334155' }, + table: { display: 'flex', flexDirection: 'column', marginTop: 8, borderTop: '0.5pt solid #cbd5e1', borderLeft: '0.5pt solid #cbd5e1' }, tableRow: { flexDirection: 'row' }, - tableHeader: { backgroundColor: '#f3f4f6', fontWeight: 'bold' }, - tableCell: { flex: 1, padding: 5, borderRight: '1pt solid #ccc', borderBottom: '1pt solid #ccc', textAlign: 'center' }, - tableCellFirst: { flex: 1, padding: 5, borderRight: '1pt solid #ccc', borderBottom: '1pt solid #ccc', textAlign: 'left' }, - footer: { position: 'absolute', bottom: 30, left: 40, right: 40, textAlign: 'center', color: '#999', fontSize: 8, borderTop: '1pt solid #eaeaea', paddingTop: 10 }, - sceneImage: { width: 480, height: 270, objectFit: 'contain', marginVertical: 8, border: '1pt solid #ddd' }, - sceneCaption: { fontSize: 8, color: '#666', fontStyle: 'italic', textAlign: 'center', marginBottom: 8 }, + tableHeader: { backgroundColor: '#f8fafc', fontWeight: 'bold' }, + tableCell: { flex: 1, padding: 4, borderRight: '0.5pt solid #cbd5e1', borderBottom: '0.5pt solid #cbd5e1', textAlign: 'center', fontSize: 8 }, + tableCellFirst: { flex: 1, padding: 4, borderRight: '0.5pt solid #cbd5e1', borderBottom: '0.5pt solid #cbd5e1', textAlign: 'left', fontSize: 8 }, + footer: { position: 'absolute', bottom: 30, left: 40, right: 40, borderTop: '0.5pt solid #cbd5e1', paddingTop: 6 }, + sceneImage: { width: 480, height: 250, objectFit: 'contain', marginVertical: 6, border: '0.5pt solid #e2e8f0', borderRadius: 4 }, + sceneCaption: { fontSize: 7, color: '#64748b', fontStyle: 'italic', textAlign: 'center', marginBottom: 6 }, + mathCard: { marginTop: 8, padding: 8, backgroundColor: '#f8fafc', borderLeft: '2.5pt solid #6b21a8', borderRadius: 2 }, + recCard: { marginTop: 8, padding: 8, backgroundColor: '#faf5ff', borderLeft: '2.5pt solid #a855f7', borderRadius: 2 }, }); export function appendKgf(val: string | number): string { @@ -57,17 +69,85 @@ export interface GenericPDFProps { sceneImage?: string | null; } +function generateRecommendations(moduleName: string, sections: GenericPDFSection[], wind: any): string[] { + const recs: string[] = []; + let hasSuction = false; + let forceValue = 0; + + for (const sec of sections) { + if (sec.gridItems) { + for (const item of sec.gridItems) { + const labelLower = item.label.toLowerCase(); + if (labelLower.includes('vertical') || labelLower.includes('fy') || labelLower.includes('força vertical')) { + const valStr = String(item.value); + const valNum = parseFloat(valStr.replace(/[^\d.-]/g, '')); + if (!isNaN(valNum)) { + if (valNum > 0 || labelLower.includes('arrancamento') || labelLower.includes('sucção')) { + hasSuction = true; + forceValue = Math.max(forceValue, Math.abs(valNum)); + } + } + } + } + } + } + + const nameLower = moduleName.toLowerCase(); + if (nameLower.includes('cobertura') || nameLower.includes('isolada')) { + recs.push("Para coberturas isoladas, o vento gera sucções severas de arrancamento nas bordas. Verifique criteriosamente o dimensionamento e ancoragem das terças."); + if (hasSuction && forceValue > 20) { + recs.push(`A força de arrancamento vertical calculada (${forceValue.toFixed(1)} kN) exige detalhamento especial de fundação resistente a tração.`); + } + } else if (nameLower.includes('torre') || nameLower.includes('reticulada')) { + recs.push("Torres reticuladas sofrem grandes forças de arrasto. Certifique-se de que a esbeltez local atende aos limites regulamentares e verifique a fadiga nas conexões."); + } else if (nameLower.includes('muro') || nameLower.includes('placa') || nameLower.includes('sign')) { + recs.push("Em painéis e muros, o momento de tombamento na fundação é crítico. Recomenda-se adotar coeficiente de segurança mínimo de 1,5 contra tombamento."); + } else if (nameLower.includes('ponte') || nameLower.includes('tabuleiro')) { + recs.push("Pontes de grandes vãos são susceptíveis a oscilações aeroelásticas (Flutter/Galloping). Caso a velocidade crítica de flutter seja próxima à de projeto, execute ensaios aerodinâmicos complementares."); + } else if (nameLower.includes('dinâ') || nameLower.includes('dinamica') || nameLower.includes('vórtice')) { + recs.push("Estruturas esbeltas com frequência natural inferior a 1,0 Hz devem obrigatoriamente ser analisadas sob efeito de oscilação dinâmica."); + } else if (nameLower.includes('cilindro') || nameLower.includes('chaminé')) { + recs.push("Cilindros e reservatórios estão expostos a picos locais de pressão e flambagem da chapa. Anéis de rigidez superiores são altamente sugeridos."); + } else if (nameLower.includes('abóbada') || nameLower.includes('cúpula') || nameLower.includes('vault') || nameLower.includes('dome')) { + recs.push("Estruturas curvas apresentam zonas de transição com forte gradiente de pressões. Cuidado redobrado no cálculo das pressões tangenciais locais."); + } + + if (wind.vk > 45) { + recs.push(`A velocidade característica encontrada (${wind.vk.toFixed(1)} m/s) é elevada. É sugerido rígido controle de deslocamentos laterais na estrutura.`); + } + + recs.push("O memorial serve como suporte ao projetista estrutural, cabendo ao Engenheiro Responsável Técnico (RT) a aprovação final dos esforços de dimensionamento."); + return recs; +} + const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: GenericPDFProps) => { + const recommendations = generateRecommendations(moduleName, sections, wind); + return ( + {/* Cabeçalho com Logotipo customizado ou placeholder */} - BrainWind v1.0 — Memória de Cálculo - Cargas de Vento: {moduleName} — NBR 6123:2023 + {wind.companyLogo ? ( + + ) : ( + + Logotipo Empresa + + )} + + BrainWind v1.0 — Memória de Cálculo + Módulo: {moduleName} — ABNT NBR 6123:2023 + + {/* 1. Parâmetros Globais do Vento */} 1. Parâmetros Globais do Vento e Pressão Dinâmica + + Localidade de Projeto: + {wind.locality || 'V₀ generalizado'} + Velocidade Básica (V₀): {wind.v0} m/s (conforme Figura 1 e Anexo C da NBR 6123) @@ -85,19 +165,20 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener {wind.s3.toFixed(2)} (Grupo {wind.s3Group}, conforme Tabela 4) - - Memória de Cálculo (Sec 4.2 e 4.3): - + + + Formulações de Velocidade e Pressão (Sec. 4.2 e 4.3): + + Vₖ = V₀ × S₁ × S₂ × S₃ - + Vₖ = {wind.v0} × {wind.s1} × {wind.s2.toFixed(3)} × {wind.s3.toFixed(2)} = {wind.vk.toFixed(2)} m/s - - + q = 0,613 × (Vₖ)² - + {appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)} @@ -108,7 +189,7 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener 2. Modelo 3D (Captura de Cena) - Vista isométrica capturada em tempo real pelo usuário. + Vista isométrica em tempo real capturada pelo usuário na interface. )} @@ -124,12 +205,14 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener )} {sec.type === 'grid' && sec.gridItems && ( - sec.gridItems.map((item, i) => ( - - {item.label}: - {appendKgf(item.value)} - - )) + + {sec.gridItems.map((item, i) => ( + + {item.label}: + {appendKgf(item.value)} + + ))} + )} {sec.type === 'table' && sec.tableHeaders && sec.tableRows && ( @@ -155,9 +238,39 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener ))} - - Gerado por BrainWind v1.0 — Ferramenta de Auxílio ao Cálculo Estrutural (NBR 6123:2023) - + {/* 6. Parecer Técnico e Considerações */} + + + {sceneImage ? sections.length + 3 : sections.length + 2}. Parecer Técnico e Observações + + + {recommendations.map((rec, rIdx) => ( + + • {rec} + + ))} + + + + {/* Rodapé fixo */} + + + + Legendas: V₀: Vel. básica | S₁, S₂, S₃: Fatores de vento | Vₖ: Vel. de projeto | q: Pressão dinâmica | Cpe: Pressão externa | Cpi: Pressão interna + + ( + `Pág. ${pageNumber} de ${totalPages}` + )} /> + + + + + + + BrainWind + + + ); diff --git a/app/src/lib/export-pdf.tsx b/app/src/lib/export-pdf.tsx index cc34287..e48ed8a 100644 --- a/app/src/lib/export-pdf.tsx +++ b/app/src/lib/export-pdf.tsx @@ -1,4 +1,4 @@ -import { Document, Page, Text, View, StyleSheet, Image as PdfImage, pdf } from '@react-pdf/renderer'; +import { Document, Page, Text, View, StyleSheet, Image as PdfImage, pdf, Font, Svg, Path } from '@react-pdf/renderer'; import { useGalpaoStore } from '../store/galpaoStore'; import { useWindStore } from '../store/appStore'; import { useCaptureStore } from '../store/captureStore'; @@ -11,74 +11,37 @@ import { import { getWallCpeOfficial, getRoofCpeOfficial } from './coefficients'; import { appendKgf } from './export-generic-pdf'; +// Registrar fonte Roboto com suporte Unicode completo (subscripts, grego, etc.) +Font.register({ + family: 'Roboto', + fonts: [ + { src: 'https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.ttf' }, + { src: 'https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1Mu4mxMyIFYW8YR8w.ttf', fontWeight: 'bold' }, + { src: 'https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1Mu51xIIzc.ttf', fontStyle: 'italic' }, + ], +}); + const styles = StyleSheet.create({ - page: { - flexDirection: 'column', - padding: 40, - fontSize: 10, - fontFamily: 'Helvetica', - color: '#333', - }, - header: { - marginBottom: 15, - borderBottom: '2pt solid #4f46e5', - paddingBottom: 10, - }, - title: { fontSize: 22, fontWeight: 'extrabold', color: '#1e1b4b' }, - subtitle: { fontSize: 11, color: '#4338ca', marginTop: 4, fontWeight: 'bold' }, - section: { marginTop: 15, marginBottom: 10 }, - sectionTitle: { fontSize: 14, fontWeight: 'bold', marginBottom: 8, color: '#111' }, - row: { flexDirection: 'row', marginBottom: 4 }, - label: { width: 200, fontWeight: 'bold' }, - value: { flex: 1 }, - table: { - display: 'flex', - flexDirection: 'column', - marginTop: 10, - borderTop: '1pt solid #ccc', - borderLeft: '1pt solid #ccc', - }, + page: { flexDirection: 'column', padding: 40, fontSize: 9, fontFamily: 'Roboto', color: '#334155' }, + header: { marginBottom: 15, borderBottom: '1.5pt solid #4f46e5', paddingBottom: 8, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + title: { fontSize: 16, fontWeight: 'bold', color: '#1e1b4b', textAlign: 'right' }, + subtitle: { fontSize: 9, color: '#4338ca', marginTop: 3, textAlign: 'right' }, + section: { marginTop: 12, marginBottom: 8 }, + sectionTitle: { fontSize: 11, fontWeight: 'bold', marginBottom: 6, color: '#111', borderBottom: '0.5pt solid #cbd5e1', paddingBottom: 2 }, + row: { flexDirection: 'row', marginBottom: 3 }, + label: { width: 180, fontWeight: 'bold', color: '#475569' }, + value: { flex: 1, color: '#1e293b' }, + text: { fontSize: 9, marginBottom: 3, lineHeight: 1.3, color: '#334155' }, + table: { display: 'flex', flexDirection: 'column', marginTop: 8, borderTop: '0.5pt solid #cbd5e1', borderLeft: '0.5pt solid #cbd5e1' }, tableRow: { flexDirection: 'row' }, - tableHeader: { backgroundColor: '#f3f4f6', fontWeight: 'bold' }, - tableCell: { - flex: 1, - padding: 5, - borderRight: '1pt solid #ccc', - borderBottom: '1pt solid #ccc', - textAlign: 'center', - }, - tableCellFirst: { - flex: 1, - padding: 5, - borderRight: '1pt solid #ccc', - borderBottom: '1pt solid #ccc', - textAlign: 'left', - }, - footer: { - position: 'absolute', - bottom: 30, - left: 40, - right: 40, - textAlign: 'center', - color: '#999', - fontSize: 8, - borderTop: '1pt solid #eaeaea', - paddingTop: 10, - }, - sceneImage: { - width: 480, - height: 270, - objectFit: 'contain', - marginVertical: 8, - border: '1pt solid #ddd', - }, - sceneCaption: { - fontSize: 8, - color: '#666', - fontStyle: 'italic', - textAlign: 'center', - marginBottom: 8, - }, + tableHeader: { backgroundColor: '#f8fafc', fontWeight: 'bold' }, + tableCell: { flex: 1, padding: 4, borderRight: '0.5pt solid #cbd5e1', borderBottom: '0.5pt solid #cbd5e1', textAlign: 'center', fontSize: 8 }, + tableCellFirst: { flex: 1, padding: 4, borderRight: '0.5pt solid #cbd5e1', borderBottom: '0.5pt solid #cbd5e1', textAlign: 'left', fontSize: 8 }, + footer: { position: 'absolute', bottom: 30, left: 40, right: 40, borderTop: '0.5pt solid #cbd5e1', paddingTop: 6 }, + sceneImage: { width: 480, height: 250, objectFit: 'contain', marginVertical: 6, border: '0.5pt solid #e2e8f0', borderRadius: 4 }, + sceneCaption: { fontSize: 7, color: '#64748b', fontStyle: 'italic', textAlign: 'center', marginBottom: 6 }, + mathCard: { marginTop: 8, padding: 8, backgroundColor: '#f8fafc', borderLeft: '2.5pt solid #4f46e5', borderRadius: 2 }, + recCard: { marginTop: 8, padding: 8, backgroundColor: '#eff6ff', borderLeft: '2.5pt solid #3b82f6', borderRadius: 2 }, }); interface ReportProps { @@ -87,6 +50,30 @@ interface ReportProps { sceneImage?: string | null; } +function generateGalpaoRecommendations(galpao: any, wind: any): string[] { + const recs: string[] = []; + + // Analisar sucção no telhado + // Cpi normalmente gera sucção extra se for positivo + if (wind.cpi > 0) { + recs.push(`Coeficiente de pressão interna positivo (Cpi = +${wind.cpi.toFixed(2)}) indica pressurização interna. Isso amplifica os esforços de arrancamento (sucção) na cobertura.`); + } else if (wind.cpi < 0) { + recs.push(`Coeficiente de pressão interna negativo (Cpi = ${wind.cpi.toFixed(2)}) indica sucção interna, o que diminui a sucção no telhado mas amplifica as pressões nas paredes barlavento.`); + } + + // Verificar esbeltez h/b + const ratio = galpao.height / Math.min(galpao.width, galpao.length); + if (ratio > 1.5) { + recs.push("A relação pé-direito / largura é alta (estrutura esbelta). Verifique atentamente os deslocamentos no topo dos pórticos e garanta contraventamentos eficientes nas paredes."); + } + + // Declaração geral + recs.push("Verifique as ligações das terças de cobertura (presilhas de fixação) contra arrancamento devido à ação local do vento nas zonas de borda (zonas F, G, J)."); + recs.push("O memorial serve como suporte ao projetista estrutural, cabendo ao Engenheiro Responsável Técnico (RT) a aprovação final dos esforços de dimensionamento."); + + return recs; +} + const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { const pressure = (cpe: number) => (wind.q * (cpe - wind.cpi)).toFixed(3); const cpi = wind.cpi.toFixed(2); @@ -95,20 +82,33 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { const FRAME_SPACING = 6.0; const PURLIN_SPACING = 1.5; + const recommendations = generateGalpaoRecommendations(galpao, wind); + return ( + {/* Cabeçalho com Logotipo customizado ou placeholder */} - BrainWind v1.0 — Memória de Cálculo - Relatório de Ação do Vento em Galpão (Conforme NBR 6123:2023) - - Este documento apresenta o memorial de cálculo detalhado passo a passo, comprovando a exatidão - dos parâmetros adotados e a aderência integral às diretrizes da norma brasileira. - + {wind.companyLogo ? ( + + ) : ( + + Logotipo Empresa + + )} + + BrainWind v1.0 — Memória de Cálculo + Galpão Retangular — ABNT NBR 6123:2023 + + {/* 1. Parâmetros Globais do Vento */} 1. Parâmetros Globais do Vento e Pressão Dinâmica + + Localidade de Projeto: + {wind.locality || 'V₀ generalizado'} + Velocidade Básica (V₀): {wind.v0} m/s (conforme Figura 1 e Anexo C da NBR 6123) @@ -126,48 +126,53 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { {wind.s3.toFixed(2)} (Grupo {wind.s3Group}, conforme Tabela 4) - - Memória de Cálculo (Sec 4.2 e 4.3): - + + + Formulações de Velocidade e Pressão (Sec. 4.2 e 4.3): + + Vₖ = V₀ × S₁ × S₂ × S₃ - + Vₖ = {wind.v0} × {wind.s1} × {wind.s2.toFixed(3)} × {wind.s3.toFixed(2)} = {wind.vk.toFixed(2)} m/s - - + q = 0,613 × (Vₖ)² - + {appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)} + {/* 2. Geometria do Galpão */} 2. Geometria do Galpão (Tabelas 4 e 5) - - Largura (b): - {galpao.width} m - - - Comprimento (a): - {galpao.length} m - - - Altura do Pé-direito (h): - {galpao.height} m - - - Inclinação do Telhado (θ): - {galpao.roofPitch}° - - - Direção do Vento Analisada: - {wind.windAngle}° + + + Largura (b): + {galpao.width} m + + + Comprimento (a): + {galpao.length} m + + + Altura do Pé-direito (h): + {galpao.height} m + + + Inclinação do Telhado (θ): + {galpao.roofPitch}° + + + Direção do Vento Selecionada: + {wind.windAngle}° + + {/* 3. Pressão Interna */} 3. Pressão Interna (Sec. 6.3, Figura 2) @@ -180,6 +185,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { + {/* 4. Cenários de Vento */} {[0, 90].map((angle, index) => { const wCpe = getWallCpeOfficial(galpao.length, galpao.width, galpao.height, angle as 0 | 90); const rCpe = getRoofCpeOfficial(galpao.length, galpao.width, galpao.height, galpao.roofPitch, angle as 0 | 90); @@ -190,26 +196,26 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { const secBase = index === 0 ? 4 : 6; return ( - - + + Cenário: Vento a {angle}° - {secBase}. Coeficientes e Pressões (q × (Cpe − Cpi)) + {secBase}. Coeficientes e Pressões Localizadas (p = q × (Cpe − Cpi)) Elemento / Região Cpe Cpi - p [kN/m² (kgf/m²)] + p [kN/m²] {Object.entries(wCpe).map(([face, cpeVal]) => ( Parede — {face} {(cpeVal as number).toFixed(2)} {cpi} - {pressure(cpeVal as number)} ({(parseFloat(pressure(cpeVal as number)) * 101.9716).toFixed(1)}) + {appendKgf(`${pressure(cpeVal as number)} kN/m²`)} ))} {Object.entries(rCpe).map(([face, cpeVal]) => ( @@ -217,7 +223,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { Telhado — {face} {(cpeVal as number).toFixed(2)} {cpi} - {pressure(cpeVal as number)} ({(parseFloat(pressure(cpeVal as number)) * 101.9716).toFixed(1)}) + {appendKgf(`${pressure(cpeVal as number)} kN/m²`)} ))} @@ -225,17 +231,17 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { - {secBase + 1}. Cargas Lineares (kN/m (kgf/m)) + {secBase + 1}. Cargas Lineares por Pórtico e Terças - - Pórticos: {FRAME_SPACING} m | Terças: {PURLIN_SPACING} m + + Espaçamento dos Pórticos: {FRAME_SPACING} m | Terças: {PURLIN_SPACING} m - Pilar + Pilar principal do pórtico Cpe - w [kN/m (kgf/m)] + Carga w [kN/m] {([ ['Barlavento', angle === 0 ? wCpe.C : wCpe.A, colLoads.windward], @@ -246,29 +252,29 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { {label} {cpeVal.toFixed(2)} - {fmtSigned(w)} ({fmtSigned(w * 101.9716, 1)}) + {appendKgf(`${fmtSigned(w)} kN/m`)} ))} - + - Terça (Zona) + Terça de Cobertura (Zonas A-J) Cpe - w [kN/m (kgf/m)] + Carga w [kN/m] {(['E', 'F', 'G', 'H', 'I', 'J'] as const).map((z) => ( {z} {rCpe[z].toFixed(2)} - {fmtSigned(rLoads[z])} ({fmtSigned(rLoads[z] * 101.9716, 1)}) + {appendKgf(`${fmtSigned(rLoads[z])} kN/m`)} ))} - - 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) + + • Reação total na base do pilar: {appendKgf(`${fmtSigned(rxns.total, 3)} kN`)} + • Força de arrasto global do galpão: {appendKgf(`${dForce.forceKN.toFixed(3)} kN`)} @@ -280,24 +286,42 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { 8. Modelo 3D (Captura de Cena) - Vista isométrica capturada em tempo real pelo projetista na interface web. Cores indicam intensidade de pressão (azul: - empuxo, vermelho: sucção). + Vista isométrica 3D do galpão renderizado com distribuição tridimensional de pressões. )} - - Declaração de Conformidade: - - Todos os coeficientes de forma (Cpe), coeficientes de pressão interna (Cpi) e pressões dinâmicas calculados neste relatório - estão em conformidade estrita com os procedimentos, tabelas e ábacos definidos pela norma ABNT NBR 6123:2023. - Este memorial serve como prova matemática e pode ser anexado aos documentos do projeto estrutural. - + {/* Parecer Técnico */} + + 9. Parecer Técnico e Recomendações + + {recommendations.map((rec, rIdx) => ( + + • {rec} + + ))} + - - Gerado por BrainWind v1.0 — Tecnologia Auditada em Engenharia Estrutural (NBR 6123:2023) - + {/* Rodapé fixo */} + + + + Legendas: V₀: Vel. básica | S₁, S₂, S₃: Fatores de vento | Vₖ: Vel. de projeto | q: Pressão dinâmica | Cpe: Pressão externa | Cpi: Pressão interna + + ( + `Pág. ${pageNumber} de ${totalPages}` + )} /> + + + + + + + BrainWind + + + ); diff --git a/app/src/lib/storage.ts b/app/src/lib/storage.ts index 4c32114..8d484af 100644 --- a/app/src/lib/storage.ts +++ b/app/src/lib/storage.ts @@ -9,7 +9,7 @@ export interface SavedProject { id?: number; name: string; type: 'projeto' | 'template'; - module: 'galpao' | 'cilindro' | 'vault' | 'dome' | 'sign' | 'isolated-roof' | 'bar' | 'bridge' | 'dynamics' | 'piperack'; + module: 'galpao' | 'cilindro' | 'vault' | 'dome' | 'sign' | 'isolated-roof' | 'bar' | 'bridge' | 'dynamics' | 'piperack' | 'tower'; inputs: Record; windData?: Record; createdAt: number; diff --git a/app/src/pages/BarSelectorModule.tsx b/app/src/pages/BarSelectorModule.tsx index 111f433..847ac24 100644 --- a/app/src/pages/BarSelectorModule.tsx +++ b/app/src/pages/BarSelectorModule.tsx @@ -10,7 +10,9 @@ import { calculateCircleBarForce, reynoldsBar } from '@/lib/nbr-tables/table-27' import Bar3DViewer from '@/components/three/Bar3D'; import SceneCapturePanel from '../components/SceneCapturePanel'; import ExportMenu from '../components/ExportMenu'; +import { WindParametersSummary } from '@/components/WindParametersSummary'; import { EducationalManual } from '@/components/EducationalManual'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; const BarSelectorModule: React.FC = () => { @@ -88,11 +90,18 @@ const BarSelectorModule: React.FC = () => { {/* Coluna Esquerda: Controles */}
- - Barra Prismática - Sec. 8.1 — faces planas (Tab. 26) ou circulares (Tab. 27). + +
+ Barras Individuais + Sec. 8.1 — faces planas (Tab. 26) ou circulares (Tab. 27). +
+
+
setVf(Number(e.target.value))} />
-
-
Categoria:{terrainCategory}
-
V₀:{v0} m/s
-
@@ -271,10 +275,6 @@ const BridgeModule: React.FC = () => {
-
diff --git a/app/src/pages/CylinderModule.tsx b/app/src/pages/CylinderModule.tsx index 34d7d72..cc6ab88 100644 --- a/app/src/pages/CylinderModule.tsx +++ b/app/src/pages/CylinderModule.tsx @@ -12,10 +12,12 @@ import SceneCapturePanel from '../components/SceneCapturePanel'; import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; import { PressureLegend } from '@/components/PressureLegend'; +import { WindParametersSummary } from '@/components/WindParametersSummary'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; const CylinderModule: React.FC = () => { - const { v0, terrainCategory, structureClass, s2, vk, q, cpi: globalCpi } = useWindStore(); + const { vk, cpi: globalCpi } = useWindStore(); const [diameter, setDiameter] = React.useState(8); const [height, setHeight] = React.useState(20); @@ -78,11 +80,18 @@ const CylinderModule: React.FC = () => { {/* Coluna Esquerda: Controles */}
- - Cilindro Vertical - Sec. 6.2.1 — silos, reservatórios, chaminés. + +
+ Cilindro Vertical + Sec. 6.2.1 — silos, reservatórios, chaminés. +
+
+
@@ -121,10 +130,6 @@ const CylinderModule: React.FC = () => {
-
V₀:{v0} m/s
-
S₂ (cat. {terrainCategory}, cl. {structureClass}):{s2.toFixed(3)}
-
Vₖ:{vk.toFixed(2)} m/s
-
q:{q.toFixed(4)} kN/m²
Reynolds Re:{result.re.toLocaleString('pt-BR')}
h/d:{result.hOverD.toFixed(2)}
Regime:{result.supercritical ? 'supercrítico' : 'subcrítico'}
diff --git a/app/src/pages/DomeModule.tsx b/app/src/pages/DomeModule.tsx index 5d78b61..56e5904 100644 --- a/app/src/pages/DomeModule.tsx +++ b/app/src/pages/DomeModule.tsx @@ -12,6 +12,8 @@ import SceneCapturePanel from '../components/SceneCapturePanel'; import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; import { PressureLegend } from '@/components/PressureLegend'; +import { WindParametersSummary } from '@/components/WindParametersSummary'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; const DomeModule: React.FC = () => { @@ -71,11 +73,18 @@ const DomeModule: React.FC = () => { {/* Coluna Esquerda: Controles */}
- - Cúpula - Sec. 6.2.4 — silos, reservatórios, ginásios. + +
+ Cúpulas + Sec. 6.2.5 — coberturas esféricas. +
+
+
setStructureType(v as StructureDynamicType)}> diff --git a/app/src/pages/GalpaoModule.tsx b/app/src/pages/GalpaoModule.tsx index 348a63e..20862ed 100644 --- a/app/src/pages/GalpaoModule.tsx +++ b/app/src/pages/GalpaoModule.tsx @@ -16,6 +16,7 @@ import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; import { PressureLegend } from '@/components/PressureLegend'; import { SaveModuleDialog } from '@/components/SaveModuleDialog'; +import { WindParametersSummary } from '@/components/WindParametersSummary'; import { useHydrationStore } from '@/store/hydrationStore'; const GalpaoModule: React.FC = () => { @@ -55,11 +56,18 @@ const GalpaoModule: React.FC = () => { {/* Coluna Esquerda: Controles */}
- - Geometria - Ajuste as dimensões do galpão. - + +
+ Geometria + Tabelas 4 e 5 — NBR 6123. +
+ +
+
@@ -180,10 +188,6 @@ const GalpaoModule: React.FC = () => {
-
diff --git a/app/src/pages/IsolatedRoofModule.tsx b/app/src/pages/IsolatedRoofModule.tsx index 497c4b8..0a4fc9e 100644 --- a/app/src/pages/IsolatedRoofModule.tsx +++ b/app/src/pages/IsolatedRoofModule.tsx @@ -11,6 +11,8 @@ import SceneCapturePanel from '../components/SceneCapturePanel'; import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; import { PressureLegend } from '@/components/PressureLegend'; +import { WindParametersSummary } from '@/components/WindParametersSummary'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; const IsolatedRoofModule: React.FC = () => { @@ -116,11 +118,18 @@ const IsolatedRoofModule: React.FC = () => { {/* Coluna Esquerda: Controles */}
- - Cobertura Isolada - Sec. 7.2 — sobre suportes de reduzidas dimensões. + +
+ Coberturas Isoladas + Sec. 6.2.2 — coberturas de uma ou duas águas. +
+
+
{ + const file = e.target.files?.[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = (event) => { + if (typeof event.target?.result === 'string') { + setCompanyLogo(event.target.result); + } + }; + reader.readAsDataURL(file); + }} + className="hidden" + accept="image/*" + /> + + + {t('settings_about')} diff --git a/app/src/pages/SignModule.tsx b/app/src/pages/SignModule.tsx index 2f2c62f..8f1a5bb 100644 --- a/app/src/pages/SignModule.tsx +++ b/app/src/pages/SignModule.tsx @@ -11,6 +11,8 @@ import SceneCapturePanel from '../components/SceneCapturePanel'; import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; import { PressureLegend } from '@/components/PressureLegend'; +import { WindParametersSummary } from '@/components/WindParametersSummary'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; const SignModule: React.FC = () => { @@ -68,11 +70,18 @@ const SignModule: React.FC = () => { {/* Coluna Esquerda: Controles */}
- - Muros e Placas - Sec. 7.1 — vento perpendicular à face. + +
+ Muros e Placas + Sec. 6.3 — retangulares ao nível do solo. +
+
+
{length} m
setLength(v[0])} /> diff --git a/app/src/pages/TowerModule.tsx b/app/src/pages/TowerModule.tsx index a7226a0..428762c 100644 --- a/app/src/pages/TowerModule.tsx +++ b/app/src/pages/TowerModule.tsx @@ -9,6 +9,8 @@ import Tower3DViewer from '@/components/three/Tower3D'; import SceneCapturePanel from '../components/SceneCapturePanel'; import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; +import { WindParametersSummary } from '@/components/WindParametersSummary'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; const TowerModule: React.FC = () => { @@ -77,11 +79,18 @@ const TowerModule: React.FC = () => { {/* Coluna Esquerda: Controles */}
- - Torre Reticulada - Sec. 8.5 — faces planas ou circulares. + +
+ Torre Reticulada + Sec. 8.5 — faces planas ou circulares. +
+
+