🚀 Auto-deploy: BrainWind atualizado em 27/07/2026 23:01:47

This commit is contained in:
2026-07-27 23:01:47 +00:00
parent 812a180bd8
commit 3e289b5dde
6 changed files with 429 additions and 65 deletions
+198 -21
View File
@@ -6,8 +6,10 @@ import { Button } from '@/components/ui/button';
import { useWindStore } from '@/store/appStore';
import {
calculateShelterWind,
calculateShelterWindAllAngles,
type ShelterCondition,
type OpeningPosition,
type ShelterWindAngle,
} from '@/lib/nbr-tables/table-shelters';
import Shelter3D from '@/components/three/Shelter3D';
import { WindFlowGrid2D } from '@/components/WindFlowGrid2D';
@@ -30,6 +32,7 @@ const conditionLabels: Record<ShelterCondition, string> = {
const ShelterModule: React.FC = () => {
const { q } = useWindStore();
const [condition, setCondition] = useState<ShelterCondition>('cond1');
const [windAngle, setWindAngle] = useState<ShelterWindAngle>(0);
const [depth, setDepth] = useState(6);
const [width, setWidth] = useState(10);
const [height, setHeight] = useState(4.8);
@@ -75,8 +78,8 @@ const ShelterModule: React.FC = () => {
}
};
const result = useMemo(() => {
return calculateShelterWind({
const { result, envelope } = useMemo(() => {
const input = {
condition,
depth,
width,
@@ -87,31 +90,151 @@ const ShelterModule: React.FC = () => {
leftClosure,
rightClosure,
openingPos,
}, q);
}, [condition, q, depth, width, height, theta, backClosure, backRange, leftClosure, rightClosure, openingPos]);
};
return {
result: calculateShelterWind({ ...input, windAngle }, q),
envelope: calculateShelterWindAllAngles(input, q),
};
}, [condition, q, depth, width, height, theta, backClosure, backRange, leftClosure, rightClosure, openingPos, windAngle]);
const handleExportPDF = () => {
const angleLabel = windAngle === 0 ? '0° (Frontal)' : windAngle === 45 ? '45° (Oblíquo)' : '90° (Lateral)';
const sections: GenericPDFSection[] = [
{
title: 'Geometria e Fechamentos do Abrigo',
title: 'Geometria, Inclinação e Fechamentos do Abrigo',
type: 'grid',
gridItems: [
{ label: 'Comprimento (d / balanço)', value: `${depth} m` },
{ label: 'Largura (b)', value: `${width} m` },
{ label: 'Altura (h)', value: `${height} m` },
{ label: 'Inclinação (θ)', value: `${theta}°` },
{ label: 'Fechamento Fundo', value: `${backClosure}%` },
{ label: 'Altura do Pilar (h)', value: `${height} m` },
{ label: 'Inclinação da Cobertura (θ)', value: `${theta}°` },
{ label: 'Incidência do Vento (Direção)', value: angleLabel },
{ label: 'Fechamento da Parede Traseira', value: `${backClosure}%` },
{ label: 'Faixa de Parede [Base, Topo]', value: backRange ? `${backRange[0]}% até ${backRange[1]}%` : '0% até 100%' },
{ label: 'Fechamento Lateral Esq / Dir', value: `${leftClosure}% / ${rightClosure}%` },
{ label: 'Posição da Abertura', value: openingPos },
],
},
{
title: 'Forças Resultantes (kN)',
title: `Coeficientes Aerodinâmicos e Pressões Líquidas - Direção ${angleLabel}`,
type: 'grid',
gridItems: [
{ label: 'Força Vertical (Fz)', value: `${result.forces.fzUp} kN` },
{ label: 'Empuxo Frontal (Fx)', value: `${result.forces.fxBack} kN` },
{ label: 'Empuxo Laterais (Fy)', value: `${result.forces.fySide} kN` },
{ label: 'Ce Superior (Cobertura - Tab. 24)', value: result.cpeTop },
{ label: 'Cpi Inferior (Sobrepressão sob Telhado)', value: result.cpiBottom },
{ label: 'Cnf Resultante (Ce - Cpi)', value: result.cnfVertical },
{ label: 'Cf Parede de Fundo (Tab. 23 / 6)', value: result.cfBack },
{ label: 'Cf Paredes Laterais', value: result.cfSide },
{ label: 'Alívio por Fresta Superior', value: `${result.reliefPercentage}%` },
],
},
{
title: 'Envoltória e Comparativo Normativo — As 3 Incidências de Vento (0° • 45° • 90°)',
type: 'table',
tableHeaders: ['Parâmetro / Força', '0° (Frontal)', '45° (Oblíquo)', '90° (Lateral)', 'Envoltória / Crítico'],
tableRows: [
[
'Ce Superior (Cobertura)',
String(envelope.res0.cpeTop),
String(envelope.res45.cpeTop),
String(envelope.res90.cpeTop),
`Máx: ${Math.min(envelope.res0.cpeTop, envelope.res45.cpeTop, envelope.res90.cpeTop)} (Sucção)`
],
[
'Cpi Inferior (Sob Telhado)',
String(envelope.res0.cpiBottom),
String(envelope.res45.cpiBottom),
String(envelope.res90.cpiBottom),
`Máx: +${Math.max(envelope.res0.cpiBottom, envelope.res45.cpiBottom, envelope.res90.cpiBottom).toFixed(2)}`
],
[
'Cnf Vertical Líquido',
String(envelope.res0.cnfVertical),
String(envelope.res45.cnfVertical),
String(envelope.res90.cnfVertical),
`Crítico: ${Math.min(envelope.res0.cnfVertical, envelope.res45.cnfVertical, envelope.res90.cnfVertical)}`
],
[
'Força Vertical Telhado [Fz ↑]',
`${envelope.res0.forces.fzUp} kN`,
`${envelope.res45.forces.fzUp} kN`,
`${envelope.res90.forces.fzUp} kN`,
`${envelope.criticalUp.value} kN (${envelope.criticalUp.angle}°)`
],
[
'Empuxo Parede Traseira [Fx →]',
`${envelope.res0.forces.fxBack} kN`,
`${envelope.res45.forces.fxBack} kN`,
`${envelope.res90.forces.fxBack} kN`,
`${envelope.criticalFx.value} kN (${envelope.criticalFx.angle}°)`
],
[
'Empuxo Paredes Laterais [Fy ↗]',
`${envelope.res0.forces.fySide} kN`,
`${envelope.res45.forces.fySide} kN`,
`${envelope.res90.forces.fySide} kN`,
`${envelope.criticalFy.value} kN (${envelope.criticalFy.angle}°)`
],
[
'Carga Horiz. no Pilar',
`${envelope.res0.lineLoads.columnLoad} kN/m`,
`${envelope.res45.lineLoads.columnLoad} kN/m`,
`${envelope.res90.lineLoads.columnLoad} kN/m`,
`${envelope.criticalColumnLoad.value} kN/m (${envelope.criticalColumnLoad.angle}°)`
],
[
'Carga Vert. na Viga',
`${envelope.res0.lineLoads.beamLoad} kN/m`,
`${envelope.res45.lineLoads.beamLoad} kN/m`,
`${envelope.res90.lineLoads.beamLoad} kN/m`,
`${Math.max(envelope.res0.lineLoads.beamLoad, envelope.res45.lineLoads.beamLoad, envelope.res90.lineLoads.beamLoad).toFixed(2)} kN/m`
],
],
},
{
title: `Forças Resultantes no Pórtico (${angleLabel}) (kN)`,
type: 'grid',
gridItems: [
{ label: 'Força Vertical no Telhado [Fz ↑]', value: `${result.forces.fzUp} kN` },
{ label: 'Empuxo na Parede Traseira [Fx →]', value: `${result.forces.fxBack} kN` },
{ label: 'Empuxo nas Paredes Laterais [Fy ↗]', value: `${result.forces.fySide} kN` },
{ label: 'Força de Atrito no Telhado [F_atrito]', value: `${result.forces.fFriction} kN` },
],
},
{
title: `Cargas Lineares para Dimensionamento (${angleLabel}) (kN/m)`,
type: 'grid',
gridItems: [
{ label: 'Carga Horizontal no Pilar Traseiro', value: `${result.lineLoads.columnLoad} kN/m` },
{ label: 'Carga Vertical na Viga em Balanço', value: `${result.lineLoads.beamLoad} kN/m` },
],
},
{
title: 'Parecer Técnico do Regime Topológico e Aerodinâmico',
type: 'text',
content: `${result.statusText} Permeabilidade líquida da parede de fundo: ${100 - backClosure}%. Alívio de arrancamento obtido pela fresta superior: ${result.reliefPercentage}%. O relatório acima apresenta a tabela comparativa das 3 incidências de vento (0°, 45° e 90°) para garantir a verificação da envoltória mais desfavorável conforme a NBR 6123:2023. As cargas lineares calculadas (kN/m) podem ser aplicadas diretamente em softwares de pórtico plano como Ftool, SAP2000 ou CYPE 3D.`,
},
{
title: 'Guia de Aplicação dos Eixos e Resultantes no Croqui',
type: 'table',
tableHeaders: ['Eixo / Componente', 'Símbolo', 'Sentido e Aplicação no Croqui', 'Carga Distribuída'],
tableRows: [
[
'Eixo Z (Vertical)',
'Fz (↑)',
'Perpendicular ao telhado. Valor (+) indica sucção para cima (arrancamento); (-) indica carga descendente.',
`${result.lineLoads.beamLoad} kN/m na viga`
],
[
'Eixo X (Longitudinal)',
'Fx (→)',
'Na direção do vento (frente -> fundo). Atua como empuxo horizontal contra a parede traseira.',
`${result.lineLoads.columnLoad} kN/m no pilar`
],
[
'Eixo Y (Transversal)',
'Fy (↗)',
'Perpendicular ao vento. Atua horizontalmente contra o fechamento das paredes laterais.',
'—'
],
],
},
];
@@ -133,7 +256,7 @@ const ShelterModule: React.FC = () => {
</div>
<SaveModuleDialog
moduleType="shelter"
inputs={{ condition, depth, width, height, theta, backClosure, leftClosure, rightClosure, openingPos }}
inputs={{ condition, depth, width, height, theta, backClosure, leftClosure, rightClosure, openingPos, windAngle }}
/>
</CardHeader>
<CardContent className="space-y-5">
@@ -160,6 +283,27 @@ const ShelterModule: React.FC = () => {
</div>
</div>
<div className="space-y-2 pt-1 border-t">
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
Incidência do Vento (Direção)
</label>
<div className="grid grid-cols-3 gap-1.5">
{([0, 45, 90] as ShelterWindAngle[]).map((ang) => (
<Button
key={ang}
type="button"
variant={windAngle === ang ? 'default' : 'outline'}
size="sm"
className="h-9 px-1.5 text-xs font-medium flex flex-col items-center justify-center leading-none"
onClick={() => setWindAngle(ang)}
>
<span className="font-bold">{ang}°</span>
<span className="text-[10px] opacity-80 mt-0.5">{ang === 0 ? 'Frontal' : ang === 45 ? 'Oblíquo' : 'Lateral'}</span>
</Button>
))}
</div>
</div>
<div className="space-y-3 pt-1 border-t">
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
Permeabilidade / Fechamento (%)
@@ -244,6 +388,9 @@ const ShelterModule: React.FC = () => {
<div className="flex-1 flex flex-col min-h-[500px] lg:min-h-0 bg-background rounded-xl border border-border shadow-sm overflow-hidden relative">
<div className="absolute top-4 left-4 z-10 flex flex-wrap items-center gap-2">
<Badge variant="secondary" className="bg-primary/10 text-primary border border-primary/20 backdrop-blur-sm font-mono font-bold">
Vento: {windAngle}° ({windAngle === 0 ? 'Frontal' : windAngle === 45 ? 'Oblíquo' : 'Lateral'})
</Badge>
<Badge variant="secondary" className="bg-background/85 text-foreground border border-border backdrop-blur-sm font-mono">
θ = {theta}°
</Badge>
@@ -267,7 +414,7 @@ const ShelterModule: React.FC = () => {
</div>
<div className="absolute top-4 right-4 z-10">
<EducationalManual type="shelter" params={{ condition, backClosure, openingPos, theta }} />
<EducationalManual type="shelter" params={{ condition, backClosure, openingPos, theta, windAngle }} />
</div>
{viewMode === '3d' ? (
@@ -282,12 +429,13 @@ const ShelterModule: React.FC = () => {
leftClosure={leftClosure}
rightClosure={rightClosure}
openingPos={openingPos}
windAngle={windAngle}
viewMode={viewMode}
/>
) : (
<div className="w-full h-full flex items-center justify-center p-6 bg-slate-950 overflow-auto">
<div className="max-w-4xl w-full">
<WindFlowGrid2D type="shelter" params={{ condition, backClosure, openingPos, theta }} />
<WindFlowGrid2D type="shelter" params={{ condition, backClosure, openingPos, theta, windAngle }} />
</div>
</div>
)}
@@ -297,14 +445,14 @@ const ShelterModule: React.FC = () => {
<Card className="shadow-sm border-border">
<CardHeader className="pb-4">
<CardTitle className="text-base flex items-center justify-between">
Forças Calculadas
Forças ({windAngle}°)
<Badge variant="secondary" className="font-mono">{result.forces.fzUp.toFixed(1)} kN (Arrancamento)</Badge>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<div className="space-y-2 border-b pb-3">
<div className="flex justify-between items-center">
<span className="text-muted-foreground">F. Vertical (Fz - Cobertura)</span>
<span className="text-muted-foreground">F. Vertical [Fz - Cobertura]</span>
<span className="font-mono font-bold text-foreground">{result.forces.fzUp.toFixed(2)} kN</span>
</div>
<div className="flex justify-between items-center text-xs text-muted-foreground">
@@ -314,7 +462,7 @@ const ShelterModule: React.FC = () => {
</div>
<div className="space-y-2 border-b pb-3">
<div className="flex justify-between items-center">
<span className="text-muted-foreground">Empuxo no Fundo (Fx)</span>
<span className="text-muted-foreground">Empuxo Traseiro [Fx - Parede]</span>
<span className="font-mono font-bold text-foreground">{result.forces.fxBack.toFixed(2)} kN</span>
</div>
<div className="flex justify-between items-center text-xs text-muted-foreground">
@@ -323,17 +471,46 @@ const ShelterModule: React.FC = () => {
</div>
</div>
<div className="flex justify-between items-center pb-2 border-b">
<span className="text-muted-foreground">Empuxo Lateral (Fy)</span>
<span className="text-muted-foreground">Empuxo Lateral [Fy - Paredes]</span>
<span className="font-mono font-bold text-foreground">{result.forces.fySide.toFixed(2)} kN</span>
</div>
<div className="flex justify-between items-center pb-2 border-b">
<span className="text-primary font-medium">Carga no Pilar (Pórtico)</span>
<span className="font-mono font-bold text-emerald-600 dark:text-emerald-400">{result.lineLoads.columnLoad.toFixed(2)} kN/m</span>
</div>
<div className="flex justify-between items-center pb-4 border-b">
<div className="flex justify-between items-center pb-3 border-b">
<span className="text-primary font-medium">Carga na Viga (Balanço)</span>
<span className="font-mono font-bold text-sky-600 dark:text-sky-400">{result.lineLoads.beamLoad.toFixed(2)} kN/m</span>
</div>
<div className="pt-1 space-y-2">
<div className="font-bold text-xs text-primary flex items-center justify-between">
<span>Envoltória 3 Incidências (0° 45° 90°)</span>
<Badge variant="outline" className="text-[10px] font-mono">Crítico: {envelope.criticalUp.angle}°</Badge>
</div>
<div className="grid grid-cols-4 gap-1 text-[11px] bg-muted/40 p-2.5 rounded-md border">
<div className="font-semibold text-muted-foreground">Direção</div>
<div className="font-semibold text-right">Fz ()</div>
<div className="font-semibold text-right">Fx ()</div>
<div className="font-semibold text-right">Fy ()</div>
<div className={cn("font-mono", windAngle === 0 && "font-bold text-primary")}>0° Frontal</div>
<div className="font-mono text-right">{envelope.res0.forces.fzUp.toFixed(1)}</div>
<div className="font-mono text-right">{envelope.res0.forces.fxBack.toFixed(1)}</div>
<div className="font-mono text-right">{envelope.res0.forces.fySide.toFixed(1)}</div>
<div className={cn("font-mono", windAngle === 45 && "font-bold text-primary")}>45° Oblíquo</div>
<div className="font-mono text-right">{envelope.res45.forces.fzUp.toFixed(1)}</div>
<div className="font-mono text-right">{envelope.res45.forces.fxBack.toFixed(1)}</div>
<div className="font-mono text-right">{envelope.res45.forces.fySide.toFixed(1)}</div>
<div className={cn("font-mono", windAngle === 90 && "font-bold text-primary")}>90° Lateral</div>
<div className="font-mono text-right">{envelope.res90.forces.fzUp.toFixed(1)}</div>
<div className="font-mono text-right">{envelope.res90.forces.fxBack.toFixed(1)}</div>
<div className="font-mono text-right">{envelope.res90.forces.fySide.toFixed(1)}</div>
</div>
</div>
<div className="pt-2 space-y-2">
<div className="font-bold text-sm text-primary">Parâmetros Adicionais (NBR 6123)</div>
<div className="grid grid-cols-1 gap-2 bg-muted/30 p-3 rounded-md border text-xs">