+ <>
+
+
+
+ >
);
};
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 (
+
+ );
+}
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 @@
+
+