diff --git a/app/public/logo_brainwind.png b/app/public/logo_brainwind.png new file mode 100644 index 0000000..a5207fb Binary files /dev/null and b/app/public/logo_brainwind.png differ diff --git a/app/public/logo_brainwind.svg b/app/public/logo_brainwind.svg new file mode 100644 index 0000000..ddc9511 --- /dev/null +++ b/app/public/logo_brainwind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/public/logo_brainwind_dark.svg b/app/public/logo_brainwind_dark.svg new file mode 100644 index 0000000..176db87 --- /dev/null +++ b/app/public/logo_brainwind_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/App.tsx b/app/src/App.tsx index 79e6098..aa6fe76 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -23,6 +23,7 @@ import { ThemeProvider, useTheme } from '@/lib/theme'; import { useI18n } from './store/i18nStore'; import { GlobalWindSettingsModal } from './components/GlobalWindSettingsModal'; import { TooltipProvider } from '@/components/ui/tooltip'; +import { TermsOfUseDialog } from './components/TermsOfUseDialog'; // Ícones Customizados de Engenharia Premium const BridgeIcon = (props: React.SVGProps) => (
- {!isCollapsed && BrainWind} + {!isCollapsed && BrainWind}
- BrainWind + BrainWind
)}
+ + + + + Gerar Relatório PDF + +
+
+ + setLocalMeta({ ...localMeta, projectName: e.target.value })} + className="col-span-3" + /> +
+
+ + setLocalMeta({ ...localMeta, clientName: e.target.value })} + className="col-span-3" + /> +
+
+ + setLocalMeta({ ...localMeta, authorName: e.target.value })} + className="col-span-3" + /> +
+
+ + setLocalMeta({ ...localMeta, projectRev: e.target.value })} + className="col-span-3" + /> +
+
+ + + +
+
+ ); }; diff --git a/app/src/components/TermsOfUseDialog.tsx b/app/src/components/TermsOfUseDialog.tsx new file mode 100644 index 0000000..255b4e9 --- /dev/null +++ b/app/src/components/TermsOfUseDialog.tsx @@ -0,0 +1,120 @@ +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Info, AlertTriangle } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { useState } from 'react'; + +interface TermsOfUseDialogProps { + isCollapsed?: boolean; +} + +export function TermsOfUseDialog({ isCollapsed = false }: TermsOfUseDialogProps) { + const [open, setOpen] = useState(false); + + return ( + + + + + + + + + + Termos de Uso e Responsabilidade + + + +
+
+ +
+

Norma de Referência

+

+ Os cálculos estruturais e parâmetros de vento gerados pelo BrainWind seguem as + diretrizes estabelecidas pela ABNT NBR 6123:2023 — + Forças devidas ao vento em edificações. +

+
+ +

+ O BrainWind é uma ferramenta computacional projetada para auxiliar projetistas + no cálculo e na análise dos efeitos do vento em edificações. Por ser um aplicativo + de suporte técnico, sua utilização está estritamente sujeita às seguintes condições: +

+ +
+
+ + Aviso de Responsabilidade Técnica +
+

+ A checagem, validação e verificação de todos os resultados, relatórios, dados de + entrada e parâmetros gerados pelo BrainWind são de responsabilidade exclusiva + e integral do usuário (Engenheiro Responsável). +

+ O aplicativo é estritamente uma ferramenta de auxílio e não substitui, sob + nenhuma circunstância, o julgamento técnico, a análise detalhada das normas e o + dimensionamento final da estrutura por parte de um profissional de engenharia + legalmente habilitado (com registro ativo no CREA/CAU). +

+
+ +
    +
  • + Licenciamento e Uso Autorizado: O uso do + BrainWind é restrito aos detentores de licença válida e ativa, emitida diretamente + pelos desenvolvedores ou por seus distribuidores autorizados. É terminantemente + proibida a distribuição, reprodução, engenharia reversa, compartilhamento de credenciais + ou qualquer forma de redistribuição não autorizada do software ou de suas licenças. +
  • + +
  • + Privacidade de Dados (LGPD): Os dados pessoais, + credenciais de acesso e projetos informados ao BrainWind são armazenados em servidores + seguros e tratados em rigorosa conformidade com a Lei Geral de Proteção de Dados + (Lei nº 13.709/2018). Detalhes adicionais sobre coleta, retenção e direitos do titular + estão disponíveis em nossa Política de Privacidade integral. +
  • + +
  • + Isenção de Responsabilidade e Danos: Os + desenvolvedores, distribuidores e afiliados do BrainWind não se responsabilizam por + quaisquer danos materiais, morais, perdas financeiras, interrupção de negócios ou + falhas estruturais decorrentes de decisões de projeto tomadas com base nas + informações ou cálculos fornecidos pelo aplicativo. +
  • + +
  • + Suporte e Aprimoramento Contínuo: Como o + sistema passa por atualizações e revisões constantes para melhor alinhamento + normativo, solicitamos que dúvidas, sugestões de melhoria e eventuais reportes + de inconsistências algorítmicas sejam enviados diretamente para nossa equipe + técnica através do canal: suporte@brainwind.com.br. +
  • +
+ +
+

Última atualização: julho de 2026. Esta versão substitui todas as anteriores.

+
+
+
+ +
+ +
+
+
+ ); +} diff --git a/app/src/lib/export-csv.ts b/app/src/lib/export-csv.ts index 8959fef..49457fd 100644 --- a/app/src/lib/export-csv.ts +++ b/app/src/lib/export-csv.ts @@ -33,7 +33,14 @@ export function exportGalpaoToCSV() { ['Direção do Vento (graus)', wind.windAngle.toString()], [], ['--- Pressão Interna ---'], - ['Caso de Permeabilidade', wind.permeabilityCase], + ['Caso de Permeabilidade', { + 'two-opposite-permeable': 'Duas faces opostas permeáveis', + 'four-equally-permeable': 'Quatro faces igualmente permeáveis', + 'dominant-windward': 'Abertura dominante — barlavento', + 'dominant-leeward': 'Abertura dominante — sotavento', + 'dominant-lateral': 'Abertura dominante — lateral', + 'airtight': 'Edificação estanque' + }[wind.permeabilityCase] || wind.permeabilityCase], ['Coeficiente Cpi', cpi.toFixed(2)], [], ['--- Coeficientes de Pressão (Cpe), Cpi e Pressão Líquida (kN/m2) ---'], diff --git a/app/src/lib/export-generic-pdf.tsx b/app/src/lib/export-generic-pdf.tsx index bdf4232..185f7b0 100644 --- a/app/src/lib/export-generic-pdf.tsx +++ b/app/src/lib/export-generic-pdf.tsx @@ -1,4 +1,4 @@ -import { Document, Page, Text, View, StyleSheet, Image as PdfImage, pdf, Font, Svg, Path } from '@react-pdf/renderer'; +import { Document, Page, Text, View, StyleSheet, Image as PdfImage, pdf, Font } from '@react-pdf/renderer'; import { useWindStore } from '../store/appStore'; import { useCaptureStore } from '../store/captureStore'; @@ -6,9 +6,9 @@ import { useCaptureStore } from '../store/captureStore'; 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' }, + { src: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/fonts/Roboto/Roboto-Regular.ttf' }, + { src: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/fonts/Roboto/Roboto-Medium.ttf', fontWeight: 'bold' }, + { src: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/fonts/Roboto/Roboto-Italic.ttf', fontStyle: 'italic' }, ], }); @@ -33,8 +33,17 @@ const styles = StyleSheet.create({ 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 }, + signatureBlock: { marginTop: 40, flexDirection: 'row', justifyContent: 'space-around', paddingTop: 30 }, + signatureLine: { width: 200, borderTop: '0.75pt solid #334155', paddingTop: 4, alignItems: 'center' }, + signatureText: { fontSize: 9, color: '#334155' }, }); +export function getEffortColor(val: string | number): string { + const num = typeof val === 'number' ? val : parseFloat(val.toString().replace(',', '.').replace(/[^\d.-]/g, '')); + if (isNaN(num) || num === 0) return '#1e293b'; + return num < 0 ? '#ea580c' : '#2563eb'; // orange for negative (suction), blue for positive (compression) +} + 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) => { @@ -136,32 +145,53 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener )} - BrainWind v1.0 — Memória de Cálculo + {wind.projectName || 'BrainWind v1.0 — Memória de Cálculo'} Módulo: {moduleName} — ABNT NBR 6123:2023 + + + Cliente + {wind.clientName || '-'} + + + Autor + {wind.authorName || '-'} + + + Revisão + {wind.projectRev || '-'} + + + Data + {new Date().toLocaleDateString('pt-BR')} + + + {/* 1. Parâmetros Globais do Vento */} 1. Parâmetros Globais do Vento e Pressão Dinâmica Localidade de Projeto: - {wind.locality || 'V₀ generalizado'} + {wind.locality || 'V0 generalizado'} - Velocidade Básica (V₀): + Velocidade Básica (V0): {wind.v0} m/s (conforme Figura 1 e Anexo C da NBR 6123) - Fator Topográfico (S₁): + Fator Topográfico (S1): {wind.s1} (conforme Seção 5.2) - Fator de Rugosidade (S₂): - {wind.s2.toFixed(3)} (Categoria {wind.terrainCategory}, Classe {wind.structureClass}, conforme Tabela 2) + Fator de Rugosidade (S2): + + {wind.s2.toFixed(3)} (Categoria {wind.terrainCategory}, Classe {wind.structureClass}, conforme Tabela 2) + - Fator Estatístico (S₃): + Fator Estatístico (S3): {wind.s3.toFixed(2)} (Grupo {wind.s3Group}, conforme Tabela 4) @@ -169,17 +199,17 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener Formulações de Velocidade e Pressão (Sec. 4.2 e 4.3): - - Vₖ = V₀ × S₁ × S₂ × S₃ + + Fórmula Base: Vk = V0 × S1 × S2 × S3 - - Vₖ = {wind.v0} × {wind.s1} × {wind.s2.toFixed(3)} × {wind.s3.toFixed(2)} = {wind.vk.toFixed(2)} m/s + + Vk = {wind.v0} × {wind.s1} × {wind.s2.toFixed(3)} × {wind.s3.toFixed(2)} = {wind.vk.toFixed(2)} m/s - - q = 0,613 × (Vₖ)² + + Pressão Dinâmica: q = 0,613 × (Vk)² - - {appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)} + + q = 0,613 × ({wind.vk.toFixed(2)})² = {(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m² @@ -208,7 +238,9 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener {sec.gridItems.map((item, i) => ( - {item.label}: + + {item.label.replace('⊥', 'perpendicular à').replace('∥', 'paralelo à')}: + {appendKgf(item.value)} ))} @@ -227,7 +259,7 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener {sec.tableRows.map((tr, rIdx) => ( {tr.map((tc, cIdx) => ( - + 0 ? { color: getEffortColor(tc) } : {}]}> {appendKgf(tc)} ))} @@ -238,7 +270,6 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener ))} - {/* 6. Parecer Técnico e Considerações */} {sceneImage ? sections.length + 3 : sections.length + 2}. Parecer Técnico e Observações @@ -250,28 +281,38 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener ))} + + + + {wind.authorName || 'Engenheiro Responsável'} + CREA / CAU: + + + + + + Isenção de Responsabilidade do Software: Os resultados e relatórios gerados pelo BrainWind são de responsabilidade exclusiva e integral do usuário. O aplicativo atua como ferramenta de auxílio e não substitui o julgamento técnico, a análise detalhada das normas e o dimensionamento final por profissional habilitado. + + - {/* 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 - - ( + + + Legendas: V0: Vel. básica | S1, S2, S3: Fatores de vento | Vk: 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 e48ed8a..324a53d 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, Font, Svg, Path } from '@react-pdf/renderer'; +import { Document, Page, Text, View, StyleSheet, Image as PdfImage, pdf, Font } from '@react-pdf/renderer'; import { useGalpaoStore } from '../store/galpaoStore'; import { useWindStore } from '../store/appStore'; import { useCaptureStore } from '../store/captureStore'; @@ -9,15 +9,15 @@ import { getDragForce, } from './line-loads'; import { getWallCpeOfficial, getRoofCpeOfficial } from './coefficients'; -import { appendKgf } from './export-generic-pdf'; +import { appendKgf, getEffortColor } 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' }, + { src: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/fonts/Roboto/Roboto-Regular.ttf' }, + { src: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/fonts/Roboto/Roboto-Medium.ttf', fontWeight: 'bold' }, + { src: 'https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/fonts/Roboto/Roboto-Italic.ttf', fontStyle: 'italic' }, ], }); @@ -42,6 +42,9 @@ const styles = StyleSheet.create({ 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 }, + signatureBlock: { marginTop: 40, flexDirection: 'row', justifyContent: 'space-around', paddingTop: 30 }, + signatureLine: { width: 200, borderTop: '0.75pt solid #334155', paddingTop: 4, alignItems: 'center' }, + signatureText: { fontSize: 9, color: '#334155' }, }); interface ReportProps { @@ -53,21 +56,48 @@ interface ReportProps { function generateGalpaoRecommendations(galpao: any, wind: any): string[] { const recs: string[] = []; - // Analisar sucção no telhado - // Cpi normalmente gera sucção extra se for positivo + let maxSuction = 0; + let maxSuctionZone = ''; + let maxCompression = 0; + let maxCompressionZone = ''; + + const pressure = (cpe: number) => (wind.q * (cpe - wind.cpi)); + + [0, 90].forEach(angle => { + 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); + + Object.entries(wCpe).forEach(([zone, cpe]) => { + const p = pressure(cpe as number); + if (p < maxSuction) { maxSuction = p; maxSuctionZone = `Parede ${zone} (${angle}°)`; } + if (p > maxCompression) { maxCompression = p; maxCompressionZone = `Parede ${zone} (${angle}°)`; } + }); + + Object.entries(rCpe).forEach(([zone, cpe]) => { + const p = pressure(cpe as number); + if (p < maxSuction) { maxSuction = p; maxSuctionZone = `Telhado ${zone} (${angle}°)`; } + if (p > maxCompression) { maxCompression = p; maxCompressionZone = `Telhado ${zone} (${angle}°)`; } + }); + }); + 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 + if (maxSuction < 0) { + recs.push(`Para este projeto, a pressão crítica de sucção (arrancamento) ocorre na zona ${maxSuctionZone} com valor de ${maxSuction.toFixed(3)} kN/m². Atenção especial à ancoragem e fixação das telhas nesta região.`); + } + if (maxCompression > 0) { + recs.push(`A pressão máxima de compressão ocorre na zona ${maxCompressionZone} com valor de ${maxCompression.toFixed(3)} kN/m².`); + } + 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."); @@ -97,32 +127,50 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { )} - BrainWind v1.0 — Memória de Cálculo + {wind.projectName || 'BrainWind v1.0 — Memória de Cálculo'} Galpão Retangular — ABNT NBR 6123:2023 - {/* 1. Parâmetros Globais do Vento */} + + + Cliente + {wind.clientName || '-'} + + + Autor + {wind.authorName || '-'} + + + Revisão + {wind.projectRev || '-'} + + + Data + {new Date().toLocaleDateString('pt-BR')} + + + 1. Parâmetros Globais do Vento e Pressão Dinâmica Localidade de Projeto: - {wind.locality || 'V₀ generalizado'} + {wind.locality || 'V0 generalizado'} - Velocidade Básica (V₀): + Velocidade Básica (V0): {wind.v0} m/s (conforme Figura 1 e Anexo C da NBR 6123) - Fator Topográfico (S₁): + Fator Topográfico (S1): {wind.s1} (conforme Seção 5.2) - Fator de Rugosidade (S₂): + Fator de Rugosidade (S2): {wind.s2.toFixed(3)} (Categoria {wind.terrainCategory}, Classe {wind.structureClass}, conforme Tabela 2) - Fator Estatístico (S₃): + Fator Estatístico (S3): {wind.s3.toFixed(2)} (Grupo {wind.s3Group}, conforme Tabela 4) @@ -130,22 +178,21 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { Formulações de Velocidade e Pressão (Sec. 4.2 e 4.3): - - Vₖ = V₀ × S₁ × S₂ × S₃ + + Fórmula Base: Vk = V0 × S1 × S2 × S3 - - Vₖ = {wind.v0} × {wind.s1} × {wind.s2.toFixed(3)} × {wind.s3.toFixed(2)} = {wind.vk.toFixed(2)} m/s + + Vk = {wind.v0} × {wind.s1} × {wind.s2.toFixed(3)} × {wind.s3.toFixed(2)} = {wind.vk.toFixed(2)} m/s - - q = 0,613 × (Vₖ)² + + Pressão Dinâmica: q = 0,613 × (Vk)² - - {appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)} + + q = 0,613 × ({wind.vk.toFixed(2)})² = {wind.q.toFixed(3)} kN/m² - {/* 2. Geometria do Galpão */} 2. Geometria do Galpão (Tabelas 4 e 5) @@ -172,12 +219,20 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { - {/* 3. Pressão Interna */} 3. Pressão Interna (Sec. 6.3, Figura 2) Caso de Permeabilidade: - {wind.permeabilityCase} + + {{ + 'two-opposite-permeable': 'Duas faces opostas permeáveis', + 'four-equally-permeable': 'Quatro faces igualmente permeáveis', + 'dominant-windward': 'Abertura dominante — barlavento', + 'dominant-leeward': 'Abertura dominante — sotavento', + 'dominant-lateral': 'Abertura dominante — lateral', + 'airtight': 'Edificação estanque' + }[wind.permeabilityCase] || wind.permeabilityCase} + Coeficiente Cpi: @@ -185,7 +240,6 @@ 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); @@ -202,10 +256,21 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { - {secBase}. Coeficientes e Pressões Localizadas (p = q × (Cpe − Cpi)) + {secBase}. Coeficientes e Pressões Localizadas + + + + Fórmula de Pressão Efetiva: p = q × (Cpe − Cpi) + + + Valores de Cpe extraídos da Tabela 4 (Paredes) e Tabela 5 (Telhado Duas Águas) da NBR 6123:2023. + + + Elemento / Região + Cpe Cpi p [kN/m²] @@ -215,7 +280,9 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { Parede — {face} {(cpeVal as number).toFixed(2)} {cpi} - {appendKgf(`${pressure(cpeVal as number)} kN/m²`)} + + {appendKgf(`${pressure(cpeVal as number)} kN/m²`)} + ))} {Object.entries(rCpe).map(([face, cpeVal]) => ( @@ -223,19 +290,30 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { Telhado — {face} {(cpeVal as number).toFixed(2)} {cpi} - {appendKgf(`${pressure(cpeVal as number)} kN/m²`)} + + {appendKgf(`${pressure(cpeVal as number)} kN/m²`)} + ))} + + Nota: Valores avermelhados indicam sucção (pressão apontando para fora da superfície). Valores azuis indicam compressão (pressão empurrando a superfície). + {secBase + 1}. Cargas Lineares por Pórtico e Terças - - Espaçamento dos Pórticos: {FRAME_SPACING} m | Terças: {PURLIN_SPACING} m - + + + + Fórmula de Carga Linear: w = p × espaçamento + + + Espaçamento dos Pórticos: {FRAME_SPACING} m | Terças: {PURLIN_SPACING} m + + @@ -252,7 +330,9 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { {label} {cpeVal.toFixed(2)} - {appendKgf(`${fmtSigned(w)} kN/m`)} + + {appendKgf(`${fmtSigned(w)} kN/m`)} + ))} @@ -267,7 +347,9 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { {z} {rCpe[z].toFixed(2)} - {appendKgf(`${fmtSigned(rLoads[z])} kN/m`)} + + {appendKgf(`${fmtSigned(rLoads[z])} kN/m`)} + ))} @@ -291,7 +373,6 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { )} - {/* Parecer Técnico */} 9. Parecer Técnico e Recomendações @@ -301,24 +382,33 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => { ))} + + + + {wind.authorName || 'Engenheiro Responsável'} + CREA / CAU: + + + + + + Isenção de Responsabilidade do Software: Os resultados e relatórios gerados pelo BrainWind são de responsabilidade exclusiva e integral do usuário. O aplicativo atua como ferramenta de auxílio e não substitui o julgamento técnico, a análise detalhada das normas e o dimensionamento final por profissional habilitado. + + - {/* 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 - - ( + + + Legendas: V0: Vel. básica | S1, S2, S3: Fatores de vento | Vk: 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/store/appStore.ts b/app/src/store/appStore.ts index ceb243e..5ad3266 100644 --- a/app/src/store/appStore.ts +++ b/app/src/store/appStore.ts @@ -44,6 +44,13 @@ export interface GlobalWindState { setCpiManual: (c: number) => void; companyLogo: string | null; setCompanyLogo: (logo: string | null) => void; + + // Metadados do Projeto + projectName: string; + clientName: string; + authorName: string; + projectRev: string; + setProjectMetadata: (data: Partial<{projectName: string, clientName: string, authorName: string, projectRev: string}>) => void; } function calcCpi(permeabilityCase: PermeabilityCase, ratio: number, windAngle: 0 | 90): number { @@ -105,6 +112,12 @@ export const useWindStore = create((set, get) => { } }, + projectName: 'Projeto BrainWind', + clientName: 'Cliente Padrão', + authorName: 'Eng. Responsável', + projectRev: 'R0', + setProjectMetadata: (data) => set((state) => ({ ...state, ...data })), + updateCalculations: () => set((state) => recalc(state)), setV0: (val) => { diff --git a/app/vite.config.ts b/app/vite.config.ts index 5011c9d..b9e2a44 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -2,6 +2,25 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' import path from 'path' +import fs from 'fs' + +try { + const svgPath = path.resolve(__dirname, '../logotipo_brainwind color original3.svg') + if (fs.existsSync(svgPath)) { + let text = fs.readFileSync(svgPath, 'utf8') + text = text.replace(/([0-9]+\.[0-9]+)/g, (m) => parseFloat(m).toFixed(1).replace(/\.0$/, '')) + text = text.replace(/\s+/g, ' ') + fs.mkdirSync(path.resolve(__dirname, 'public'), { recursive: true }) + fs.writeFileSync(path.resolve(__dirname, 'public/logo_brainwind.svg'), text) + fs.writeFileSync(path.resolve(__dirname, 'public/logo_brainwind_dark.svg'), text.replace(/#06315A/gi, '#E2E8F0')) + } + + const pngPath = path.resolve(__dirname, '../logotipo_brainwind color original.png') + if (fs.existsSync(pngPath)) { + fs.mkdirSync(path.resolve(__dirname, 'public'), { recursive: true }) + fs.copyFileSync(pngPath, path.resolve(__dirname, 'public/logo_brainwind.png')) + } +} catch(e) {} // https://vite.dev/config/ export default defineConfig({ diff --git a/logotipo_brainwind color original.png b/logotipo_brainwind color original.png new file mode 100644 index 0000000..a5207fb Binary files /dev/null and b/logotipo_brainwind color original.png differ diff --git a/logotipo_brainwind color original3.svg b/logotipo_brainwind color original3.svg new file mode 100644 index 0000000..0a480a6 --- /dev/null +++ b/logotipo_brainwind color original3.svg @@ -0,0 +1,1408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +