🚀 Auto-deploy: BrainWind atualizado em 14/07/2026 18:03:29
This commit is contained in:
@@ -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 (
|
||||
<Document>
|
||||
<Page size="A4" style={styles.page}>
|
||||
{/* Cabeçalho com Logotipo customizado ou placeholder */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>BrainWind v1.0 — Memória de Cálculo</Text>
|
||||
<Text style={styles.subtitle}>Cargas de Vento: {moduleName} — NBR 6123:2023</Text>
|
||||
{wind.companyLogo ? (
|
||||
<PdfImage src={wind.companyLogo} style={{ width: 110, height: 40, objectFit: 'contain' }} />
|
||||
) : (
|
||||
<View style={{ width: 110, height: 40, border: '0.75pt dashed #cbd5e1', borderRadius: 2, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 7, color: '#94a3b8' }}>Logotipo Empresa</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={{ flex: 1, marginLeft: 15 }}>
|
||||
<Text style={styles.title}>BrainWind v1.0 — Memória de Cálculo</Text>
|
||||
<Text style={styles.subtitle}>Módulo: {moduleName} — ABNT NBR 6123:2023</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 1. Parâmetros Globais do Vento */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>1. Parâmetros Globais do Vento e Pressão Dinâmica</Text>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Localidade de Projeto:</Text>
|
||||
<Text style={styles.value}>{wind.locality || 'V₀ generalizado'}</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Velocidade Básica (V₀):</Text>
|
||||
<Text style={styles.value}>{wind.v0} m/s (conforme Figura 1 e Anexo C da NBR 6123)</Text>
|
||||
@@ -85,19 +165,20 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener
|
||||
<Text style={styles.value}>{wind.s3.toFixed(2)} (Grupo {wind.s3Group}, conforme Tabela 4)</Text>
|
||||
</View>
|
||||
|
||||
<View style={{ marginTop: 10, padding: 8, backgroundColor: '#f9fafb', borderLeft: '3pt solid #6b21a8' }}>
|
||||
<Text style={{ fontSize: 11, fontWeight: 'bold', marginBottom: 4 }}>Memória de Cálculo (Sec 4.2 e 4.3):</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 4 }}>
|
||||
<View style={styles.mathCard}>
|
||||
<Text style={{ fontSize: 10, fontWeight: 'bold', marginBottom: 3, color: '#1e1b4b' }}>
|
||||
Formulações de Velocidade e Pressão (Sec. 4.2 e 4.3):
|
||||
</Text>
|
||||
<Text style={{ fontSize: 9, fontFamily: 'Courier', marginBottom: 2 }}>
|
||||
Vₖ = V₀ × S₁ × S₂ × S₃
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 8, color: '#4b5563' }}>
|
||||
<Text style={{ fontSize: 9, fontFamily: 'Courier', marginBottom: 6, color: '#475569' }}>
|
||||
Vₖ = {wind.v0} × {wind.s1} × {wind.s2.toFixed(3)} × {wind.s3.toFixed(2)} = {wind.vk.toFixed(2)} m/s
|
||||
</Text>
|
||||
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 4 }}>
|
||||
<Text style={{ fontSize: 9, fontFamily: 'Courier', marginBottom: 2 }}>
|
||||
q = 0,613 × (Vₖ)²
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', color: '#4b5563' }}>
|
||||
<Text style={{ fontSize: 9, fontFamily: 'Courier', color: '#475569' }}>
|
||||
{appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -108,7 +189,7 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener
|
||||
<Text style={styles.sectionTitle}>2. Modelo 3D (Captura de Cena)</Text>
|
||||
<PdfImage src={sceneImage} style={styles.sceneImage} />
|
||||
<Text style={styles.sceneCaption}>
|
||||
Vista isométrica capturada em tempo real pelo usuário.
|
||||
Vista isométrica em tempo real capturada pelo usuário na interface.
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -124,12 +205,14 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener
|
||||
)}
|
||||
|
||||
{sec.type === 'grid' && sec.gridItems && (
|
||||
sec.gridItems.map((item, i) => (
|
||||
<View style={styles.row} key={i}>
|
||||
<Text style={styles.label}>{item.label}:</Text>
|
||||
<Text style={styles.value}>{appendKgf(item.value)}</Text>
|
||||
</View>
|
||||
))
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
|
||||
{sec.gridItems.map((item, i) => (
|
||||
<View style={{ width: '50%', marginBottom: 4 }} key={i}>
|
||||
<Text style={{ fontWeight: 'bold', color: '#475569' }}>{item.label}:</Text>
|
||||
<Text style={{ color: '#1e293b' }}>{appendKgf(item.value)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{sec.type === 'table' && sec.tableHeaders && sec.tableRows && (
|
||||
@@ -155,9 +238,39 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener
|
||||
</View>
|
||||
))}
|
||||
|
||||
<Text style={styles.footer}>
|
||||
Gerado por BrainWind v1.0 — Ferramenta de Auxílio ao Cálculo Estrutural (NBR 6123:2023)
|
||||
</Text>
|
||||
{/* 6. Parecer Técnico e Considerações */}
|
||||
<View style={styles.section} wrap={false}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
{sceneImage ? sections.length + 3 : sections.length + 2}. Parecer Técnico e Observações
|
||||
</Text>
|
||||
<View style={styles.recCard}>
|
||||
{recommendations.map((rec, rIdx) => (
|
||||
<Text style={{ fontSize: 9, marginBottom: 4, lineHeight: 1.3, color: '#3b0764' }} key={rIdx}>
|
||||
• {rec}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Rodapé fixo */}
|
||||
<View style={styles.footer} fixed>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 6.5, color: '#94a3b8', flex: 1, textAlign: 'left' }}>
|
||||
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
|
||||
</Text>
|
||||
<Text style={{ fontSize: 7, color: '#64748b', textAlign: 'center', marginHorizontal: 10 }} render={({ pageNumber, totalPages }) => (
|
||||
`Pág. ${pageNumber} de ${totalPages}`
|
||||
)} />
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', flex: 0.3 }}>
|
||||
<Svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="#6b21a8" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{ marginRight: 2 }}>
|
||||
<Path d="M12.8 19.6A2 2 0 1 0 14 16H2" />
|
||||
<Path d="M17.5 8a2.5 2.5 0 1 1 2 4H2" />
|
||||
<Path d="M9.8 4.4A2 2 0 1 1 11 8H2" />
|
||||
</Svg>
|
||||
<Text style={{ fontSize: 7, fontWeight: 'bold', color: '#6b21a8' }}>BrainWind</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
</Document>
|
||||
);
|
||||
|
||||
+154
-130
@@ -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 (
|
||||
<Document>
|
||||
<Page size="A4" style={styles.page}>
|
||||
{/* Cabeçalho com Logotipo customizado ou placeholder */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>BrainWind v1.0 — Memória de Cálculo</Text>
|
||||
<Text style={styles.subtitle}>Relatório de Ação do Vento em Galpão (Conforme NBR 6123:2023)</Text>
|
||||
<Text style={{ fontSize: 9, color: '#666', marginTop: 8 }}>
|
||||
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.
|
||||
</Text>
|
||||
{wind.companyLogo ? (
|
||||
<PdfImage src={wind.companyLogo} style={{ width: 110, height: 40, objectFit: 'contain' }} />
|
||||
) : (
|
||||
<View style={{ width: 110, height: 40, border: '0.75pt dashed #cbd5e1', borderRadius: 2, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 7, color: '#94a3b8' }}>Logotipo Empresa</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={{ flex: 1, marginLeft: 15 }}>
|
||||
<Text style={styles.title}>BrainWind v1.0 — Memória de Cálculo</Text>
|
||||
<Text style={styles.subtitle}>Galpão Retangular — ABNT NBR 6123:2023</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 1. Parâmetros Globais do Vento */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>1. Parâmetros Globais do Vento e Pressão Dinâmica</Text>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Localidade de Projeto:</Text>
|
||||
<Text style={styles.value}>{wind.locality || 'V₀ generalizado'}</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Velocidade Básica (V₀):</Text>
|
||||
<Text style={styles.value}>{wind.v0} m/s (conforme Figura 1 e Anexo C da NBR 6123)</Text>
|
||||
@@ -126,48 +126,53 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
<Text style={styles.value}>{wind.s3.toFixed(2)} (Grupo {wind.s3Group}, conforme Tabela 4)</Text>
|
||||
</View>
|
||||
|
||||
<View style={{ marginTop: 10, padding: 8, backgroundColor: '#f9fafb', borderLeft: '3pt solid #4f46e5' }}>
|
||||
<Text style={{ fontSize: 11, fontWeight: 'bold', marginBottom: 4 }}>Memória de Cálculo (Sec 4.2 e 4.3):</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 4 }}>
|
||||
<View style={styles.mathCard}>
|
||||
<Text style={{ fontSize: 10, fontWeight: 'bold', marginBottom: 3, color: '#1e1b4b' }}>
|
||||
Formulações de Velocidade e Pressão (Sec. 4.2 e 4.3):
|
||||
</Text>
|
||||
<Text style={{ fontSize: 9, fontFamily: 'Courier', marginBottom: 2 }}>
|
||||
Vₖ = V₀ × S₁ × S₂ × S₃
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 8, color: '#4b5563' }}>
|
||||
<Text style={{ fontSize: 9, fontFamily: 'Courier', marginBottom: 6, color: '#475569' }}>
|
||||
Vₖ = {wind.v0} × {wind.s1} × {wind.s2.toFixed(3)} × {wind.s3.toFixed(2)} = {wind.vk.toFixed(2)} m/s
|
||||
</Text>
|
||||
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 4 }}>
|
||||
<Text style={{ fontSize: 9, fontFamily: 'Courier', marginBottom: 2 }}>
|
||||
q = 0,613 × (Vₖ)²
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', color: '#4b5563' }}>
|
||||
<Text style={{ fontSize: 9, fontFamily: 'Courier', color: '#475569' }}>
|
||||
{appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 2. Geometria do Galpão */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>2. Geometria do Galpão (Tabelas 4 e 5)</Text>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Largura (b):</Text>
|
||||
<Text style={styles.value}>{galpao.width} m</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Comprimento (a):</Text>
|
||||
<Text style={styles.value}>{galpao.length} m</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Altura do Pé-direito (h):</Text>
|
||||
<Text style={styles.value}>{galpao.height} m</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Inclinação do Telhado (θ):</Text>
|
||||
<Text style={styles.value}>{galpao.roofPitch}°</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Direção do Vento Analisada:</Text>
|
||||
<Text style={styles.value}>{wind.windAngle}°</Text>
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
|
||||
<View style={{ width: '50%', marginBottom: 4 }}>
|
||||
<Text style={{ fontWeight: 'bold', color: '#475569' }}>Largura (b):</Text>
|
||||
<Text style={{ color: '#1e293b' }}>{galpao.width} m</Text>
|
||||
</View>
|
||||
<View style={{ width: '50%', marginBottom: 4 }}>
|
||||
<Text style={{ fontWeight: 'bold', color: '#475569' }}>Comprimento (a):</Text>
|
||||
<Text style={{ color: '#1e293b' }}>{galpao.length} m</Text>
|
||||
</View>
|
||||
<View style={{ width: '50%', marginBottom: 4 }}>
|
||||
<Text style={{ fontWeight: 'bold', color: '#475569' }}>Altura do Pé-direito (h):</Text>
|
||||
<Text style={{ color: '#1e293b' }}>{galpao.height} m</Text>
|
||||
</View>
|
||||
<View style={{ width: '50%', marginBottom: 4 }}>
|
||||
<Text style={{ fontWeight: 'bold', color: '#475569' }}>Inclinação do Telhado (θ):</Text>
|
||||
<Text style={{ color: '#1e293b' }}>{galpao.roofPitch}°</Text>
|
||||
</View>
|
||||
<View style={{ width: '50%', marginBottom: 4 }}>
|
||||
<Text style={{ fontWeight: 'bold', color: '#475569' }}>Direção do Vento Selecionada:</Text>
|
||||
<Text style={{ color: '#1e293b' }}>{wind.windAngle}°</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 3. Pressão Interna */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>3. Pressão Interna (Sec. 6.3, Figura 2)</Text>
|
||||
<View style={styles.row}>
|
||||
@@ -180,6 +185,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 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 (
|
||||
<View wrap={false} key={`angle-${angle}`}>
|
||||
<Text style={{ fontSize: 16, fontWeight: 'bold', color: '#1e1b4b', marginTop: 20, marginBottom: 10, borderBottom: '1pt solid #ddd', paddingBottom: 5 }}>
|
||||
<View wrap={false} key={`angle-${angle}`} style={{ marginTop: 15 }}>
|
||||
<Text style={{ fontSize: 13, fontWeight: 'bold', color: '#1e1b4b', marginTop: 10, marginBottom: 6, borderBottom: '1pt solid #cbd5e1', paddingBottom: 3 }}>
|
||||
Cenário: Vento a {angle}°
|
||||
</Text>
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>{secBase}. Coeficientes e Pressões (q × (Cpe − Cpi))</Text>
|
||||
<Text style={styles.sectionTitle}>{secBase}. Coeficientes e Pressões Localizadas (p = q × (Cpe − Cpi))</Text>
|
||||
<View style={styles.table}>
|
||||
<View style={[styles.tableRow, styles.tableHeader]}>
|
||||
<Text style={styles.tableCellFirst}>Elemento / Região</Text>
|
||||
<Text style={styles.tableCell}>Cpe</Text>
|
||||
<Text style={styles.tableCell}>Cpi</Text>
|
||||
<Text style={styles.tableCell}>p [kN/m² (kgf/m²)]</Text>
|
||||
<Text style={styles.tableCell}>p [kN/m²]</Text>
|
||||
</View>
|
||||
{Object.entries(wCpe).map(([face, cpeVal]) => (
|
||||
<View style={styles.tableRow} key={`wall-${face}`}>
|
||||
<Text style={styles.tableCellFirst}>Parede — {face}</Text>
|
||||
<Text style={styles.tableCell}>{(cpeVal as number).toFixed(2)}</Text>
|
||||
<Text style={styles.tableCell}>{cpi}</Text>
|
||||
<Text style={styles.tableCell}>{pressure(cpeVal as number)} ({(parseFloat(pressure(cpeVal as number)) * 101.9716).toFixed(1)})</Text>
|
||||
<Text style={styles.tableCell}>{appendKgf(`${pressure(cpeVal as number)} kN/m²`)}</Text>
|
||||
</View>
|
||||
))}
|
||||
{Object.entries(rCpe).map(([face, cpeVal]) => (
|
||||
@@ -217,7 +223,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
<Text style={styles.tableCellFirst}>Telhado — {face}</Text>
|
||||
<Text style={styles.tableCell}>{(cpeVal as number).toFixed(2)}</Text>
|
||||
<Text style={styles.tableCell}>{cpi}</Text>
|
||||
<Text style={styles.tableCell}>{pressure(cpeVal as number)} ({(parseFloat(pressure(cpeVal as number)) * 101.9716).toFixed(1)})</Text>
|
||||
<Text style={styles.tableCell}>{appendKgf(`${pressure(cpeVal as number)} kN/m²`)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
@@ -225,17 +231,17 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
{secBase + 1}. Cargas Lineares (kN/m (kgf/m))
|
||||
{secBase + 1}. Cargas Lineares por Pórtico e Terças
|
||||
</Text>
|
||||
<Text style={{ fontSize: 9, marginBottom: 6 }}>
|
||||
Pórticos: {FRAME_SPACING} m | Terças: {PURLIN_SPACING} m
|
||||
<Text style={{ fontSize: 8, color: '#64748b', marginBottom: 4 }}>
|
||||
Espaçamento dos Pórticos: {FRAME_SPACING} m | Terças: {PURLIN_SPACING} m
|
||||
</Text>
|
||||
|
||||
<View style={styles.table}>
|
||||
<View style={[styles.tableRow, styles.tableHeader]}>
|
||||
<Text style={styles.tableCellFirst}>Pilar</Text>
|
||||
<Text style={styles.tableCellFirst}>Pilar principal do pórtico</Text>
|
||||
<Text style={styles.tableCell}>Cpe</Text>
|
||||
<Text style={styles.tableCell}>w [kN/m (kgf/m)]</Text>
|
||||
<Text style={styles.tableCell}>Carga w [kN/m]</Text>
|
||||
</View>
|
||||
{([
|
||||
['Barlavento', angle === 0 ? wCpe.C : wCpe.A, colLoads.windward],
|
||||
@@ -246,29 +252,29 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
<View style={styles.tableRow} key={`col-${label}`}>
|
||||
<Text style={styles.tableCellFirst}>{label}</Text>
|
||||
<Text style={styles.tableCell}>{cpeVal.toFixed(2)}</Text>
|
||||
<Text style={styles.tableCell}>{fmtSigned(w)} ({fmtSigned(w * 101.9716, 1)})</Text>
|
||||
<Text style={styles.tableCell}>{appendKgf(`${fmtSigned(w)} kN/m`)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View style={[styles.table, { marginTop: 10 }]}>
|
||||
<View style={[styles.table, { marginTop: 8 }]}>
|
||||
<View style={[styles.tableRow, styles.tableHeader]}>
|
||||
<Text style={styles.tableCellFirst}>Terça (Zona)</Text>
|
||||
<Text style={styles.tableCellFirst}>Terça de Cobertura (Zonas A-J)</Text>
|
||||
<Text style={styles.tableCell}>Cpe</Text>
|
||||
<Text style={styles.tableCell}>w [kN/m (kgf/m)]</Text>
|
||||
<Text style={styles.tableCell}>Carga w [kN/m]</Text>
|
||||
</View>
|
||||
{(['E', 'F', 'G', 'H', 'I', 'J'] as const).map((z) => (
|
||||
<View style={styles.tableRow} key={`roof-${z}`}>
|
||||
<Text style={styles.tableCellFirst}>{z}</Text>
|
||||
<Text style={styles.tableCell}>{rCpe[z].toFixed(2)}</Text>
|
||||
<Text style={styles.tableCell}>{fmtSigned(rLoads[z])} ({fmtSigned(rLoads[z] * 101.9716, 1)})</Text>
|
||||
<Text style={styles.tableCell}>{appendKgf(`${fmtSigned(rLoads[z])} kN/m`)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View style={{ marginTop: 6, fontSize: 9 }}>
|
||||
<Text>Reação global na base: <Text style={{ fontWeight: 'bold' }}>{fmtSigned(rxns.total, 3)} kN ({fmtSigned(rxns.total * 101.9716, 1)} kgf)</Text></Text>
|
||||
<Text>Força de arrasto global (Cₐ): <Text style={{ fontWeight: 'bold' }}>{dForce.forceKN.toFixed(3)} kN ({(dForce.forceKN * 101.9716).toFixed(1)} kgf)</Text></Text>
|
||||
<View style={{ marginTop: 6, fontSize: 8.5, color: '#1e293b' }}>
|
||||
<Text>• Reação total na base do pilar: <Text style={{ fontWeight: 'bold' }}>{appendKgf(`${fmtSigned(rxns.total, 3)} kN`)}</Text></Text>
|
||||
<Text>• Força de arrasto global do galpão: <Text style={{ fontWeight: 'bold' }}>{appendKgf(`${dForce.forceKN.toFixed(3)} kN`)}</Text></Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -280,24 +286,42 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
<Text style={styles.sectionTitle}>8. Modelo 3D (Captura de Cena)</Text>
|
||||
<PdfImage src={sceneImage} style={styles.sceneImage} />
|
||||
<Text style={styles.sceneCaption}>
|
||||
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.
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={{ marginTop: 20, padding: 10, backgroundColor: '#f3f4f6', borderRadius: 4 }}>
|
||||
<Text style={{ fontSize: 10, fontWeight: 'bold', marginBottom: 4, color: '#111' }}>Declaração de Conformidade:</Text>
|
||||
<Text style={{ fontSize: 9, color: '#444' }}>
|
||||
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.
|
||||
</Text>
|
||||
{/* Parecer Técnico */}
|
||||
<View style={styles.section} wrap={false}>
|
||||
<Text style={styles.sectionTitle}>9. Parecer Técnico e Recomendações</Text>
|
||||
<View style={styles.recCard}>
|
||||
{recommendations.map((rec, rIdx) => (
|
||||
<Text style={{ fontSize: 9, marginBottom: 4, lineHeight: 1.3, color: '#1e3a8a' }} key={rIdx}>
|
||||
• {rec}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.footer}>
|
||||
Gerado por BrainWind v1.0 — Tecnologia Auditada em Engenharia Estrutural (NBR 6123:2023)
|
||||
</Text>
|
||||
{/* Rodapé fixo */}
|
||||
<View style={styles.footer} fixed>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 6.5, color: '#94a3b8', flex: 1, textAlign: 'left' }}>
|
||||
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
|
||||
</Text>
|
||||
<Text style={{ fontSize: 7, color: '#64748b', textAlign: 'center', marginHorizontal: 10 }} render={({ pageNumber, totalPages }) => (
|
||||
`Pág. ${pageNumber} de ${totalPages}`
|
||||
)} />
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', flex: 0.3 }}>
|
||||
<Svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="#6b21a8" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{ marginRight: 2 }}>
|
||||
<Path d="M12.8 19.6A2 2 0 1 0 14 16H2" />
|
||||
<Path d="M17.5 8a2.5 2.5 0 1 1 2 4H2" />
|
||||
<Path d="M9.8 4.4A2 2 0 1 1 11 8H2" />
|
||||
</Svg>
|
||||
<Text style={{ fontSize: 7, fontWeight: 'bold', color: '#6b21a8' }}>BrainWind</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
</Document>
|
||||
);
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
windData?: Record<string, unknown>;
|
||||
createdAt: number;
|
||||
|
||||
Reference in New Issue
Block a user