🚀 Auto-deploy: BrainWind atualizado em 27/07/2026 23:01:47
This commit is contained in:
+198
-21
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user