🚀 Auto-deploy: BrainWind atualizado em 27/07/2026 23:01:47
This commit is contained in:
@@ -5,7 +5,7 @@ import { Text, Grid, Environment } from '@react-three/drei';
|
|||||||
import { ViewerOrbitControls as OrbitControls } from './ViewerOrbitControls';
|
import { ViewerOrbitControls as OrbitControls } from './ViewerOrbitControls';
|
||||||
import SceneCanvas from '../SceneCanvas';
|
import SceneCanvas from '../SceneCanvas';
|
||||||
import { useCanvasTheme } from '@/lib/theme';
|
import { useCanvasTheme } from '@/lib/theme';
|
||||||
import type { ShelterCondition, OpeningPosition } from '@/lib/nbr-tables/table-shelters';
|
import type { ShelterCondition, OpeningPosition, ShelterWindAngle } from '@/lib/nbr-tables/table-shelters';
|
||||||
|
|
||||||
export interface Shelter3DProps {
|
export interface Shelter3DProps {
|
||||||
condition: ShelterCondition;
|
condition: ShelterCondition;
|
||||||
@@ -18,6 +18,7 @@ export interface Shelter3DProps {
|
|||||||
rightClosure: number;
|
rightClosure: number;
|
||||||
openingPos: OpeningPosition;
|
openingPos: OpeningPosition;
|
||||||
backRange?: [number, number];
|
backRange?: [number, number];
|
||||||
|
windAngle?: ShelterWindAngle;
|
||||||
viewMode: '3d' | 'elevation';
|
viewMode: '3d' | 'elevation';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ function ShelterModel({
|
|||||||
rightClosure,
|
rightClosure,
|
||||||
openingPos,
|
openingPos,
|
||||||
backRange,
|
backRange,
|
||||||
|
windAngle = 0,
|
||||||
viewMode,
|
viewMode,
|
||||||
}: Shelter3DProps) {
|
}: Shelter3DProps) {
|
||||||
const isElevation = viewMode === 'elevation';
|
const isElevation = viewMode === 'elevation';
|
||||||
@@ -160,8 +162,8 @@ function ShelterModel({
|
|||||||
{renderWallWithGap(depth, height, rightClosure, 'distributed', Math.PI / 2, [halfW, 0, 0])}
|
{renderWallWithGap(depth, height, rightClosure, 'distributed', Math.PI / 2, [halfW, 0, 0])}
|
||||||
|
|
||||||
{/* 4. Linhas de Corrente e Vetores de Vento (Ilustrativo e Didático) */}
|
{/* 4. Linhas de Corrente e Vetores de Vento (Ilustrativo e Didático) */}
|
||||||
<group position={[0, 0, 0]}>
|
<group position={[0, 0, 0]} rotation={[0, -((windAngle || 0) * Math.PI) / 180, 0]}>
|
||||||
{/* Vento de entrada (Barlavento) entrando pela frente em direção ao abrigo */}
|
{/* Vento de entrada (Barlavento) entrando em direção ao abrigo */}
|
||||||
<WindArrow start={[0, height * 0.5, -halfD - 4]} end={[0, height * 0.5, -halfD + 1]} color="#38bdf8" speed={1.2} />
|
<WindArrow start={[0, height * 0.5, -halfD - 4]} end={[0, height * 0.5, -halfD + 1]} color="#38bdf8" speed={1.2} />
|
||||||
<WindArrow start={[-halfW * 0.5, height * 0.7, -halfD - 4]} end={[-halfW * 0.5, height * 0.7, -halfD + 1]} color="#38bdf8" speed={1.4} />
|
<WindArrow start={[-halfW * 0.5, height * 0.7, -halfD - 4]} end={[-halfW * 0.5, height * 0.7, -halfD + 1]} color="#38bdf8" speed={1.4} />
|
||||||
<WindArrow start={[halfW * 0.5, height * 0.4, -halfD - 4]} end={[halfW * 0.5, height * 0.4, -halfD + 1]} color="#38bdf8" speed={1.3} />
|
<WindArrow start={[halfW * 0.5, height * 0.4, -halfD - 4]} end={[halfW * 0.5, height * 0.4, -halfD + 1]} color="#38bdf8" speed={1.3} />
|
||||||
@@ -195,7 +197,13 @@ function ShelterModel({
|
|||||||
{isElevation ? 'CORTE A-A (Elevação)' : 'Abrigo / Pórtico (NBR 6123)'}
|
{isElevation ? 'CORTE A-A (Elevação)' : 'Abrigo / Pórtico (NBR 6123)'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text position={[0, 0.3, -halfD - 1.2]} rotation={[0, Math.PI, 0]} fontSize={0.35} color="#38bdf8">
|
<Text position={[0, 0.3, -halfD - 1.2]} rotation={[0, Math.PI, 0]} fontSize={0.35} color="#38bdf8">
|
||||||
VENTO FRONTAL
|
{`VENTO ${windAngle === 0 ? 'FRONTAL (0°)' : windAngle === 45 ? 'OBLÍQUO (45°)' : 'LATERAL (90°)'}`}
|
||||||
|
</Text>
|
||||||
|
<Text position={[0, height + slopeRise + 0.25, 0]} rotation={[0, Math.PI, 0]} fontSize={0.32} color="#f43f5e">
|
||||||
|
Arrancamento [+Fz ↑]
|
||||||
|
</Text>
|
||||||
|
<Text position={[0, height * 0.5, halfD + 1.2]} rotation={[0, Math.PI, 0]} fontSize={0.32} color="#fb923c">
|
||||||
|
Empuxo no Fundo [+Fx →]
|
||||||
</Text>
|
</Text>
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
export const shelterManual = {
|
export const shelterManual = {
|
||||||
title: 'Abrigos, Marquises e Pórticos Fechados',
|
title: 'Abrigos, Marquises e Pórticos Fechados',
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
calculateShelterWind,
|
||||||
|
calculateShelterWindAllAngles,
|
||||||
|
type ShelterInput,
|
||||||
|
} from '../nbr-tables/table-shelters';
|
||||||
|
|
||||||
|
describe('NBR 6123 - Ações de Vento em Abrigos e Pórticos (table-shelters)', () => {
|
||||||
|
const baseInput: ShelterInput = {
|
||||||
|
condition: 'cond1',
|
||||||
|
depth: 6,
|
||||||
|
width: 10,
|
||||||
|
height: 4.8,
|
||||||
|
theta: 5,
|
||||||
|
backClosure: 100,
|
||||||
|
leftClosure: 100,
|
||||||
|
rightClosure: 100,
|
||||||
|
openingPos: 'distributed',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('calcula corretamente o abrigo para incidência frontal a 0°', () => {
|
||||||
|
const res = calculateShelterWind({ ...baseInput, windAngle: 0 }, 1.0);
|
||||||
|
expect(res.windAngle).toBe(0);
|
||||||
|
expect(res.cpeTop).toBe(-1.0);
|
||||||
|
expect(res.cpiBottom).toBe(0.75);
|
||||||
|
expect(res.cnfVertical).toBe(-1.75);
|
||||||
|
expect(res.cfBack).toBe(1.3);
|
||||||
|
expect(res.cfSide).toBe(0.9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calcula corretamente o abrigo para incidência oblíqua a 45° com vórtice crítico de canto', () => {
|
||||||
|
const res = calculateShelterWind({ ...baseInput, windAngle: 45 }, 1.0);
|
||||||
|
expect(res.windAngle).toBe(45);
|
||||||
|
expect(res.cpeTop).toBe(-1.3); // picos de sucção de canto
|
||||||
|
expect(res.cfBack).toBe(1.05);
|
||||||
|
expect(res.cfSide).toBe(1.05);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calcula corretamente o abrigo para incidência lateral a 90°', () => {
|
||||||
|
const res = calculateShelterWind({ ...baseInput, windAngle: 90 }, 1.0);
|
||||||
|
expect(res.windAngle).toBe(90);
|
||||||
|
expect(res.cpeTop).toBe(-1.1);
|
||||||
|
expect(res.cfSide).toBe(1.3); // lateral atua como parede barlavento frontal
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calcula a envoltória de todas as 3 direções (0°, 45°, 90°)', () => {
|
||||||
|
const env = calculateShelterWindAllAngles(baseInput, 1.0);
|
||||||
|
expect(env.res0.windAngle).toBe(0);
|
||||||
|
expect(env.res45.windAngle).toBe(45);
|
||||||
|
expect(env.res90.windAngle).toBe(90);
|
||||||
|
expect(env.criticalUp.value).toBeGreaterThan(0);
|
||||||
|
expect(env.criticalFx.angle).toBe(0); // 0° é crítico para FxBack em cond1
|
||||||
|
expect(env.criticalFy.angle).toBe(90); // 90° é crítico para FySide em cond1
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -102,7 +102,13 @@ function generateRecommendations(moduleName: string, sections: GenericPDFSection
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nameLower = moduleName.toLowerCase();
|
const nameLower = moduleName.toLowerCase();
|
||||||
if (nameLower.includes('cobertura') || nameLower.includes('isolada')) {
|
if (nameLower.includes('abrigo') || nameLower.includes('pórtico') || nameLower.includes('marquise')) {
|
||||||
|
recs.push("Em abrigos e pórticos em balanço, o arrancamento vertical (Fz) na cobertura é intensificado quando a parede de fundo é fechada devido ao aprisionamento de pressão (+Cpi).");
|
||||||
|
recs.push("Verifique criteriosamente as ligações pilar-viga e a ancoragem das telhas na região da testeira, onde as tensões de arrancamento atingem picos críticos.");
|
||||||
|
if (hasSuction && forceValue > 15) {
|
||||||
|
recs.push(`A força de arrancamento calculada (${forceValue.toFixed(1)} kN) requer verificação da fundação dos pilares contra arrancamento e tombamento.`);
|
||||||
|
}
|
||||||
|
} else 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.");
|
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) {
|
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.`);
|
recs.push(`A força de arrancamento vertical calculada (${forceValue.toFixed(1)} kN) exige detalhamento especial de fundação resistente a tração.`);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* Módulo de Ações de Vento para Abrigos, Marquises e Pórticos com Fechamentos Parciais
|
* Módulo de Ações de Vento para Abrigos, Marquises e Pórticos com Fechamentos Parciais
|
||||||
* NBR 6123:2023 — Combinação de Coberturas Isoladas (Tab. 24 e 25), Pressão Interna e Aberturas (sec. 6.3)
|
* NBR 6123:2023 — Combinação de Coberturas Isoladas (Tab. 24 e 25), Pressão Interna e Aberturas (sec. 6.3)
|
||||||
* e Placas / Painéis (Tab. 23).
|
* e Placas / Painéis (Tab. 23), suportando incidências do vento a 0°, 45° e 90°.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type ShelterCondition = 'cond1' | 'cond2' | 'cond3' | 'cond4';
|
export type ShelterCondition = 'cond1' | 'cond2' | 'cond3' | 'cond4';
|
||||||
export type OpeningPosition = 'top' | 'bottom' | 'distributed';
|
export type OpeningPosition = 'top' | 'bottom' | 'distributed';
|
||||||
|
export type ShelterWindAngle = 0 | 45 | 90;
|
||||||
|
|
||||||
export interface ShelterInput {
|
export interface ShelterInput {
|
||||||
/** Condição topológica padrão (1=Estanque 3 lados, 2=Fundo fechado, 3=Túnel, 4=Aberto) */
|
/** Condição topológica padrão (1=Estanque 3 lados, 2=Fundo fechado, 3=Túnel, 4=Aberto) */
|
||||||
@@ -28,9 +29,13 @@ export interface ShelterInput {
|
|||||||
rightClosure: number;
|
rightClosure: number;
|
||||||
/** Posição da abertura na parede de fundo ('top' | 'bottom' | 'distributed') */
|
/** Posição da abertura na parede de fundo ('top' | 'bottom' | 'distributed') */
|
||||||
openingPos: OpeningPosition;
|
openingPos: OpeningPosition;
|
||||||
|
/** Ângulo de incidência do vento (0° = frontal, 45° = oblíquo, 90° = lateral) */
|
||||||
|
windAngle?: ShelterWindAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShelterResult {
|
export interface ShelterResult {
|
||||||
|
/** Ângulo de incidência do vento calculado (0, 45 ou 90) */
|
||||||
|
windAngle: ShelterWindAngle;
|
||||||
/** Coeficiente de pressão superior da cobertura (Ce superior) */
|
/** Coeficiente de pressão superior da cobertura (Ce superior) */
|
||||||
cpeTop: number;
|
cpeTop: number;
|
||||||
/** Coeficiente de pressão interna efetivo sob a cobertura (Cpi inferior) */
|
/** Coeficiente de pressão interna efetivo sob a cobertura (Cpi inferior) */
|
||||||
@@ -61,14 +66,25 @@ export interface ShelterResult {
|
|||||||
/** Carga vertical/horizontal na viga em balanço (kN/m) */
|
/** Carga vertical/horizontal na viga em balanço (kN/m) */
|
||||||
beamLoad: number;
|
beamLoad: number;
|
||||||
};
|
};
|
||||||
/** Alívio percentual de arrancamento obtido pela posição da abertura no topo (%) */
|
/** Alívio percentual de arrancamento obtido pela posição da abertura (%) */
|
||||||
reliefPercentage: number;
|
reliefPercentage: number;
|
||||||
/** Explicação normativa do comportamento */
|
/** Explicação normativa do comportamento */
|
||||||
statusText: string;
|
statusText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ShelterEnvelopeResult {
|
||||||
|
res0: ShelterResult;
|
||||||
|
res45: ShelterResult;
|
||||||
|
res90: ShelterResult;
|
||||||
|
criticalUp: { angle: ShelterWindAngle; value: number };
|
||||||
|
criticalFx: { angle: ShelterWindAngle; value: number };
|
||||||
|
criticalFy: { angle: ShelterWindAngle; value: number };
|
||||||
|
criticalColumnLoad: { angle: ShelterWindAngle; value: number };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calcula os coeficientes e forças para Abrigos, Marquises e Pórticos.
|
* Calcula os coeficientes e forças para Abrigos, Marquises e Pórticos
|
||||||
|
* considerando incidência do vento a 0° (frontal), 45° (oblíquo) ou 90° (lateral).
|
||||||
*/
|
*/
|
||||||
export function calculateShelterWind(input: ShelterInput, q: number): ShelterResult {
|
export function calculateShelterWind(input: ShelterInput, q: number): ShelterResult {
|
||||||
const {
|
const {
|
||||||
@@ -84,6 +100,8 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes
|
|||||||
openingPos,
|
openingPos,
|
||||||
} = input;
|
} = input;
|
||||||
|
|
||||||
|
const windAngle: ShelterWindAngle = input.windAngle ?? 0;
|
||||||
|
|
||||||
const areaRoof = depth * width;
|
const areaRoof = depth * width;
|
||||||
const areaBack = width * height;
|
const areaBack = width * height;
|
||||||
const areaSide = depth * height;
|
const areaSide = depth * height;
|
||||||
@@ -92,27 +110,59 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes
|
|||||||
const bClose = Math.max(0, Math.min(100, backClosure)) / 100;
|
const bClose = Math.max(0, Math.min(100, backClosure)) / 100;
|
||||||
const lClose = Math.max(0, Math.min(100, leftClosure)) / 100;
|
const lClose = Math.max(0, Math.min(100, leftClosure)) / 100;
|
||||||
const rClose = Math.max(0, Math.min(100, rightClosure)) / 100;
|
const rClose = Math.max(0, Math.min(100, rightClosure)) / 100;
|
||||||
|
const sideAvgClose = (lClose + rClose) / 2;
|
||||||
|
|
||||||
// 1. Coeficiente externo superior na cobertura (Tabela 24 para uma água)
|
// 1. Coeficiente externo superior na cobertura (Ce superior)
|
||||||
// Varia entre -0.8 a -1.5 de sucção de barlavento, dependendo de theta
|
// Varia conforme o ângulo de incidência (0°, 45° ou 90°)
|
||||||
const cpeTop = theta <= 5 ? -1.0 : theta <= 15 ? -1.2 : -1.4;
|
let cpeTop = -1.0;
|
||||||
|
if (windAngle === 0) {
|
||||||
|
// Frontal: -1.0 a -1.4 de sucção de barlavento, dependendo de theta
|
||||||
|
cpeTop = theta <= 5 ? -1.0 : theta <= 15 ? -1.2 : -1.4;
|
||||||
|
} else if (windAngle === 45) {
|
||||||
|
// Oblíquo 45°: forte vórtice cônico de canto nas bordas de barlavento (aumento de sucção em ~20% a 30%)
|
||||||
|
cpeTop = theta <= 5 ? -1.3 : theta <= 15 ? -1.4 : -1.6;
|
||||||
|
} else {
|
||||||
|
// Lateral 90°: escoamento transversal sobre a cobertura, sucção constante por descolamento
|
||||||
|
cpeTop = -1.1;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Cálculo do Cpi sob a cobertura (efeito estagnação da parede traseira / laterais)
|
// 2. Cálculo do Cpi sob a cobertura (efeito estagnação da parede traseira / laterais)
|
||||||
// Quando o fundo é fechado e o ar é contido, cpiBottom torna-se positivo e elevado
|
|
||||||
let baseCpi = 0.0;
|
let baseCpi = 0.0;
|
||||||
|
if (windAngle === 0) {
|
||||||
if (condition === 'cond1') {
|
if (condition === 'cond1') {
|
||||||
// 3 lados fechados -> grande armadilha aerodinâmica (+0.75 estanque)
|
baseCpi = 0.75 * bClose * sideAvgClose;
|
||||||
baseCpi = 0.75 * bClose * ((lClose + rClose) / 2);
|
|
||||||
} else if (condition === 'cond2') {
|
} else if (condition === 'cond2') {
|
||||||
// Fundo fechado, lados abertos -> sobrepressão moderada (+0.45)
|
|
||||||
baseCpi = 0.45 * bClose;
|
baseCpi = 0.45 * bClose;
|
||||||
} else if (condition === 'cond3') {
|
} else if (condition === 'cond3') {
|
||||||
// Túnel (fundo aberto, lados fechados) -> efeito Venturi / sucção inferior (-0.35)
|
baseCpi = -0.35 * sideAvgClose;
|
||||||
baseCpi = -0.35 * ((lClose + rClose) / 2);
|
|
||||||
} else {
|
} else {
|
||||||
// Condição 4: Livre (apenas obstrução leve ou nula)
|
|
||||||
baseCpi = -0.1 * bClose;
|
baseCpi = -0.1 * bClose;
|
||||||
}
|
}
|
||||||
|
} else if (windAngle === 45) {
|
||||||
|
if (condition === 'cond1') {
|
||||||
|
baseCpi = 0.68 * bClose * sideAvgClose;
|
||||||
|
} else if (condition === 'cond2') {
|
||||||
|
baseCpi = 0.35 * bClose;
|
||||||
|
} else if (condition === 'cond3') {
|
||||||
|
baseCpi = 0.25 * sideAvgClose;
|
||||||
|
} else {
|
||||||
|
baseCpi = -0.1 * bClose;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// windAngle === 90
|
||||||
|
if (condition === 'cond1') {
|
||||||
|
// Estanque 3 lados: vento a 90° incide sobre lateral fechada com fundo fechado retendo o ar
|
||||||
|
baseCpi = 0.65 * sideAvgClose * bClose;
|
||||||
|
} else if (condition === 'cond2') {
|
||||||
|
// Fundo fechado, mas laterais ABERTAS: vento a 90° passa livremente sob o telhado -> sucção
|
||||||
|
baseCpi = -0.25;
|
||||||
|
} else if (condition === 'cond3') {
|
||||||
|
// Túnel (laterais fechadas, fundo aberto): lateral se comporta como parede frontal
|
||||||
|
baseCpi = 0.50 * sideAvgClose;
|
||||||
|
} else {
|
||||||
|
baseCpi = -0.1 * bClose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Efeito da posição da abertura (Topo vs Base vs Distribuída)
|
// 3. Efeito da posição da abertura (Topo vs Base vs Distribuída)
|
||||||
let reliefPercentage = 0;
|
let reliefPercentage = 0;
|
||||||
@@ -120,67 +170,101 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes
|
|||||||
|
|
||||||
if (backRange && bClose > 0.05 && bClose < 0.99) {
|
if (backRange && bClose > 0.05 && bClose < 0.99) {
|
||||||
const topGapPct = Math.max(0, Math.min(100, 100 - backRange[1])) / 100;
|
const topGapPct = Math.max(0, Math.min(100, 100 - backRange[1])) / 100;
|
||||||
// O alívio de arrancamento da cobertura depende diretamente da abertura no topo (fresta superior)
|
|
||||||
reliefPercentage = Math.round(35 * Math.min(1, topGapPct * 2.5));
|
reliefPercentage = Math.round(35 * Math.min(1, topGapPct * 2.5));
|
||||||
effectiveCpi = baseCpi * (1 - reliefPercentage / 100);
|
effectiveCpi = baseCpi * (1 - reliefPercentage / 100);
|
||||||
} else if (bClose > 0.05 && bClose < 0.99) {
|
} else if (bClose > 0.05 && bClose < 0.99) {
|
||||||
const openRatio = 1 - bClose; // fração aberta da parede de fundo
|
const openRatio = 1 - bClose;
|
||||||
if (openingPos === 'top') {
|
if (openingPos === 'top') {
|
||||||
// Fresta superior permite o escape de sobrepressão debaixo do telhado
|
|
||||||
// Reduz o Cpi positivo de forma proporcional ao tamanho da fresta no topo
|
|
||||||
reliefPercentage = Math.round(35 * openRatio);
|
reliefPercentage = Math.round(35 * openRatio);
|
||||||
effectiveCpi = baseCpi * (1 - reliefPercentage / 100);
|
effectiveCpi = baseCpi * (1 - reliefPercentage / 100);
|
||||||
} else if (openingPos === 'bottom') {
|
} else if (openingPos === 'bottom') {
|
||||||
// Abertura inferior: o ar flui embaixo, mantendo pressão sob o telhado
|
|
||||||
reliefPercentage = 0;
|
reliefPercentage = 0;
|
||||||
effectiveCpi = baseCpi * 1.05;
|
effectiveCpi = baseCpi * 1.05;
|
||||||
} else {
|
} else {
|
||||||
// Distribuída: alívio proporcional à permeabilidade aberta
|
|
||||||
reliefPercentage = Math.round(18 * openRatio);
|
reliefPercentage = Math.round(18 * openRatio);
|
||||||
effectiveCpi = baseCpi * (1 - reliefPercentage / 100);
|
effectiveCpi = baseCpi * (1 - reliefPercentage / 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cnf vertical total na cobertura (arrancamento para cima = negativo)
|
// Cnf vertical total na cobertura (arrancamento para cima = negativo)
|
||||||
// Sugção para cima (FzUp): cpeTop (-) - effectiveCpi (+) => força de sucção efetiva total
|
|
||||||
const cnfUp = cpeTop - Math.max(0, effectiveCpi);
|
const cnfUp = cpeTop - Math.max(0, effectiveCpi);
|
||||||
const cnfDown = 0.2 - Math.min(0, effectiveCpi); // Carregamento descendente
|
const cnfDown = 0.2 - Math.min(0, effectiveCpi);
|
||||||
|
|
||||||
// 4. Coeficientes em paredes de fundo e laterais (Tabela 23 / 6)
|
// 4. Coeficientes horizontais em paredes de fundo e laterais
|
||||||
const cfBack = 1.3 * bClose;
|
let cfBack = 1.3 * bClose;
|
||||||
const cfSide = 0.9 * ((lClose + rClose) / 2);
|
let cfSide = 0.9 * sideAvgClose;
|
||||||
|
|
||||||
|
if (windAngle === 45) {
|
||||||
|
cfBack = 1.05 * bClose;
|
||||||
|
cfSide = 1.05 * sideAvgClose;
|
||||||
|
} else if (windAngle === 90) {
|
||||||
|
// Vento a 90°: as paredes laterais viram superfície frontal barlavento
|
||||||
|
cfSide = 1.3 * sideAvgClose;
|
||||||
|
cfBack = 0.7 * bClose;
|
||||||
|
}
|
||||||
|
|
||||||
// 5. Forças globais (kN)
|
// 5. Forças globais (kN)
|
||||||
const fzUp = Math.abs(cnfUp) * q * areaRoof;
|
const fzUp = Math.abs(cnfUp) * q * areaRoof;
|
||||||
const fzDown = Math.abs(cnfDown) * q * areaRoof;
|
const fzDown = Math.abs(cnfDown) * q * areaRoof;
|
||||||
const fxBack = cfBack * q * areaBack;
|
const fxBack = cfBack * q * areaBack;
|
||||||
const fySide = cfSide * q * (areaSide * 2);
|
const fySide = cfSide * q * (areaSide * 2);
|
||||||
const fFriction = 0.04 * q * areaRoof; // Atrito no telhado
|
const fFriction = 0.04 * q * areaRoof;
|
||||||
|
|
||||||
// 6. Carga linear em pórticos (assumindo 2 pórticos principais como no croqui, ex: vão b/2)
|
// 6. Carga linear em pórticos (2 pórticos principais)
|
||||||
// Carga no pilar (kN/m)
|
|
||||||
const numPorticos = 2;
|
const numPorticos = 2;
|
||||||
const columnLoad = (fxBack / height / numPorticos) + (q * 1.8 * 0.15); // arrasto do tubo 150x150
|
let columnLoad = 0;
|
||||||
|
if (windAngle === 0) {
|
||||||
|
columnLoad = (fxBack / height / numPorticos) + (q * 1.8 * 0.15);
|
||||||
|
} else if (windAngle === 45) {
|
||||||
|
const combForce = Math.sqrt(fxBack * fxBack + fySide * fySide);
|
||||||
|
columnLoad = (combForce / height / numPorticos) + (q * 1.8 * 0.15);
|
||||||
|
} else {
|
||||||
|
// Em 90°, a carga transversal principal atua nas paredes laterais (Fy)
|
||||||
|
columnLoad = (fySide / height / numPorticos) + (q * 1.8 * 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
const beamLoad = (fzUp / depth / numPorticos);
|
const beamLoad = (fzUp / depth / numPorticos);
|
||||||
|
|
||||||
// Texto explicativo do regime
|
// Texto explicativo por condição e ângulo
|
||||||
let statusText = '';
|
let statusText = '';
|
||||||
|
const anglePrefix =
|
||||||
|
windAngle === 0
|
||||||
|
? 'Vento Frontal (0°)'
|
||||||
|
: windAngle === 45
|
||||||
|
? 'Vento Oblíquo (45°)'
|
||||||
|
: 'Vento Transversal / Lateral (90°)';
|
||||||
|
|
||||||
switch (condition) {
|
switch (condition) {
|
||||||
case 'cond1':
|
case 'cond1':
|
||||||
statusText = 'Abrigo estanque em 3 lados (Condição 1): Forte acúmulo de pressão positiva sob a cobertura, gerando pico crítico de arrancamento vertical e empuxo na parede de fundo.';
|
statusText = `${anglePrefix} — Abrigo estanque em 3 lados (Condição 1): Forte retenção de pressão positiva sob a cobertura. ${
|
||||||
|
windAngle === 45
|
||||||
|
? 'O vento a 45° gera pico crítico de arrancamento devido a vórtices de canto em barlavento.'
|
||||||
|
: windAngle === 90
|
||||||
|
? 'O vento a 90° incide diretamente na parede lateral com acúmulo contra o fundo fechado.'
|
||||||
|
: 'Gera pico crítico de arrancamento vertical e empuxo na parede de fundo.'
|
||||||
|
}`;
|
||||||
break;
|
break;
|
||||||
case 'cond2':
|
case 'cond2':
|
||||||
statusText = 'Abrigo com fundo fechado e laterais abertas (Condição 2): A parede de fundo atua como anteparo, elevando a sobrepressão sob o balanço junto à testeira traseira.';
|
statusText = `${anglePrefix} — Abrigo com fundo fechado e laterais abertas (Condição 2): ${
|
||||||
|
windAngle === 90
|
||||||
|
? 'Com incidência a 90°, o vento escoa livremente entre as laterais abertas sem gerar sobrepressão (+Cpi) sob o telhado.'
|
||||||
|
: 'A parede de fundo atua como anteparo, elevando a sobrepressão sob o balanço.'
|
||||||
|
}`;
|
||||||
break;
|
break;
|
||||||
case 'cond3':
|
case 'cond3':
|
||||||
statusText = 'Abrigo tipo túnel (Condição 3): Abertura frontal e traseira provocam efeito Venturi, com sucção interna na cobertura e paredes laterais sujeitas a arrasto.';
|
statusText = `${anglePrefix} — Abrigo tipo túnel (Condição 3): ${
|
||||||
|
windAngle === 90
|
||||||
|
? 'Com vento a 90°, o fechamento lateral comporta-se como anteparo barlavento, gerando empuxo transversal elevado.'
|
||||||
|
: 'Aberturas frontal e traseira provocam efeito Venturi, com sucção interna na cobertura.'
|
||||||
|
}`;
|
||||||
break;
|
break;
|
||||||
case 'cond4':
|
case 'cond4':
|
||||||
statusText = 'Cobertura livre / aberta em 4 lados (Condição 4): Escoamento desimpedido ao redor e sob o telhado, seguindo os coeficientes líquidos da Tabela 24 da NBR 6123.';
|
statusText = `${anglePrefix} — Cobertura livre (Condição 4): Escoamento desimpedido ao redor e sob o telhado, com ações orientadas pela Tabela 24 da NBR 6123.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
windAngle,
|
||||||
cpeTop: Number(cpeTop.toFixed(2)),
|
cpeTop: Number(cpeTop.toFixed(2)),
|
||||||
cpiBottom: Number(effectiveCpi.toFixed(2)),
|
cpiBottom: Number(effectiveCpi.toFixed(2)),
|
||||||
cnfVertical: Number(cnfUp.toFixed(2)),
|
cnfVertical: Number(cnfUp.toFixed(2)),
|
||||||
@@ -201,3 +285,38 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes
|
|||||||
statusText,
|
statusText,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcula a envoltória e os resultados para as 3 direções de vento (0°, 45° e 90°).
|
||||||
|
*/
|
||||||
|
export function calculateShelterWindAllAngles(input: ShelterInput, q: number): ShelterEnvelopeResult {
|
||||||
|
const res0 = calculateShelterWind({ ...input, windAngle: 0 }, q);
|
||||||
|
const res45 = calculateShelterWind({ ...input, windAngle: 45 }, q);
|
||||||
|
const res90 = calculateShelterWind({ ...input, windAngle: 90 }, q);
|
||||||
|
|
||||||
|
const angles: ShelterWindAngle[] = [0, 45, 90];
|
||||||
|
const results = [res0, res45, res90];
|
||||||
|
|
||||||
|
let critUpIdx = 0;
|
||||||
|
let critFxIdx = 0;
|
||||||
|
let critFyIdx = 0;
|
||||||
|
let critColIdx = 0;
|
||||||
|
|
||||||
|
for (let i = 1; i < 3; i++) {
|
||||||
|
if (results[i].forces.fzUp > results[critUpIdx].forces.fzUp) critUpIdx = i;
|
||||||
|
if (results[i].forces.fxBack > results[critFxIdx].forces.fxBack) critFxIdx = i;
|
||||||
|
if (results[i].forces.fySide > results[critFyIdx].forces.fySide) critFyIdx = i;
|
||||||
|
if (results[i].lineLoads.columnLoad > results[critColIdx].lineLoads.columnLoad) critColIdx = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
res0,
|
||||||
|
res45,
|
||||||
|
res90,
|
||||||
|
criticalUp: { angle: angles[critUpIdx], value: results[critUpIdx].forces.fzUp },
|
||||||
|
criticalFx: { angle: angles[critFxIdx], value: results[critFxIdx].forces.fxBack },
|
||||||
|
criticalFy: { angle: angles[critFyIdx], value: results[critFyIdx].forces.fySide },
|
||||||
|
criticalColumnLoad: { angle: angles[critColIdx], value: results[critColIdx].lineLoads.columnLoad },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+198
-21
@@ -6,8 +6,10 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { useWindStore } from '@/store/appStore';
|
import { useWindStore } from '@/store/appStore';
|
||||||
import {
|
import {
|
||||||
calculateShelterWind,
|
calculateShelterWind,
|
||||||
|
calculateShelterWindAllAngles,
|
||||||
type ShelterCondition,
|
type ShelterCondition,
|
||||||
type OpeningPosition,
|
type OpeningPosition,
|
||||||
|
type ShelterWindAngle,
|
||||||
} from '@/lib/nbr-tables/table-shelters';
|
} from '@/lib/nbr-tables/table-shelters';
|
||||||
import Shelter3D from '@/components/three/Shelter3D';
|
import Shelter3D from '@/components/three/Shelter3D';
|
||||||
import { WindFlowGrid2D } from '@/components/WindFlowGrid2D';
|
import { WindFlowGrid2D } from '@/components/WindFlowGrid2D';
|
||||||
@@ -30,6 +32,7 @@ const conditionLabels: Record<ShelterCondition, string> = {
|
|||||||
const ShelterModule: React.FC = () => {
|
const ShelterModule: React.FC = () => {
|
||||||
const { q } = useWindStore();
|
const { q } = useWindStore();
|
||||||
const [condition, setCondition] = useState<ShelterCondition>('cond1');
|
const [condition, setCondition] = useState<ShelterCondition>('cond1');
|
||||||
|
const [windAngle, setWindAngle] = useState<ShelterWindAngle>(0);
|
||||||
const [depth, setDepth] = useState(6);
|
const [depth, setDepth] = useState(6);
|
||||||
const [width, setWidth] = useState(10);
|
const [width, setWidth] = useState(10);
|
||||||
const [height, setHeight] = useState(4.8);
|
const [height, setHeight] = useState(4.8);
|
||||||
@@ -75,8 +78,8 @@ const ShelterModule: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = useMemo(() => {
|
const { result, envelope } = useMemo(() => {
|
||||||
return calculateShelterWind({
|
const input = {
|
||||||
condition,
|
condition,
|
||||||
depth,
|
depth,
|
||||||
width,
|
width,
|
||||||
@@ -87,31 +90,151 @@ const ShelterModule: React.FC = () => {
|
|||||||
leftClosure,
|
leftClosure,
|
||||||
rightClosure,
|
rightClosure,
|
||||||
openingPos,
|
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 handleExportPDF = () => {
|
||||||
|
const angleLabel = windAngle === 0 ? '0° (Frontal)' : windAngle === 45 ? '45° (Oblíquo)' : '90° (Lateral)';
|
||||||
const sections: GenericPDFSection[] = [
|
const sections: GenericPDFSection[] = [
|
||||||
{
|
{
|
||||||
title: 'Geometria e Fechamentos do Abrigo',
|
title: 'Geometria, Inclinação e Fechamentos do Abrigo',
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
gridItems: [
|
gridItems: [
|
||||||
{ label: 'Comprimento (d / balanço)', value: `${depth} m` },
|
{ label: 'Comprimento (d / balanço)', value: `${depth} m` },
|
||||||
{ label: 'Largura (b)', value: `${width} m` },
|
{ label: 'Largura (b)', value: `${width} m` },
|
||||||
{ label: 'Altura (h)', value: `${height} m` },
|
{ label: 'Altura do Pilar (h)', value: `${height} m` },
|
||||||
{ label: 'Inclinação (θ)', value: `${theta}°` },
|
{ label: 'Inclinação da Cobertura (θ)', value: `${theta}°` },
|
||||||
{ label: 'Fechamento Fundo', value: `${backClosure}%` },
|
{ 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: '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',
|
type: 'grid',
|
||||||
gridItems: [
|
gridItems: [
|
||||||
{ label: 'Força Vertical (Fz)', value: `${result.forces.fzUp} kN` },
|
{ label: 'Ce Superior (Cobertura - Tab. 24)', value: result.cpeTop },
|
||||||
{ label: 'Empuxo Frontal (Fx)', value: `${result.forces.fxBack} kN` },
|
{ label: 'Cpi Inferior (Sobrepressão sob Telhado)', value: result.cpiBottom },
|
||||||
{ label: 'Empuxo Laterais (Fy)', value: `${result.forces.fySide} kN` },
|
{ 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>
|
</div>
|
||||||
<SaveModuleDialog
|
<SaveModuleDialog
|
||||||
moduleType="shelter"
|
moduleType="shelter"
|
||||||
inputs={{ condition, depth, width, height, theta, backClosure, leftClosure, rightClosure, openingPos }}
|
inputs={{ condition, depth, width, height, theta, backClosure, leftClosure, rightClosure, openingPos, windAngle }}
|
||||||
/>
|
/>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-5">
|
<CardContent className="space-y-5">
|
||||||
@@ -160,6 +283,27 @@ const ShelterModule: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="space-y-3 pt-1 border-t">
|
||||||
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||||
Permeabilidade / Fechamento (%)
|
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="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">
|
<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">
|
<Badge variant="secondary" className="bg-background/85 text-foreground border border-border backdrop-blur-sm font-mono">
|
||||||
θ = {theta}°
|
θ = {theta}°
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -267,7 +414,7 @@ const ShelterModule: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute top-4 right-4 z-10">
|
<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>
|
</div>
|
||||||
|
|
||||||
{viewMode === '3d' ? (
|
{viewMode === '3d' ? (
|
||||||
@@ -282,12 +429,13 @@ const ShelterModule: React.FC = () => {
|
|||||||
leftClosure={leftClosure}
|
leftClosure={leftClosure}
|
||||||
rightClosure={rightClosure}
|
rightClosure={rightClosure}
|
||||||
openingPos={openingPos}
|
openingPos={openingPos}
|
||||||
|
windAngle={windAngle}
|
||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center p-6 bg-slate-950 overflow-auto">
|
<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">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -297,14 +445,14 @@ const ShelterModule: React.FC = () => {
|
|||||||
<Card className="shadow-sm border-border">
|
<Card className="shadow-sm border-border">
|
||||||
<CardHeader className="pb-4">
|
<CardHeader className="pb-4">
|
||||||
<CardTitle className="text-base flex items-center justify-between">
|
<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>
|
<Badge variant="secondary" className="font-mono">{result.forces.fzUp.toFixed(1)} kN (Arrancamento)</Badge>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4 text-sm">
|
<CardContent className="space-y-4 text-sm">
|
||||||
<div className="space-y-2 border-b pb-3">
|
<div className="space-y-2 border-b pb-3">
|
||||||
<div className="flex justify-between items-center">
|
<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>
|
<span className="font-mono font-bold text-foreground">{result.forces.fzUp.toFixed(2)} kN</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center text-xs text-muted-foreground">
|
<div className="flex justify-between items-center text-xs text-muted-foreground">
|
||||||
@@ -314,7 +462,7 @@ const ShelterModule: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 border-b pb-3">
|
<div className="space-y-2 border-b pb-3">
|
||||||
<div className="flex justify-between items-center">
|
<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>
|
<span className="font-mono font-bold text-foreground">{result.forces.fxBack.toFixed(2)} kN</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center text-xs text-muted-foreground">
|
<div className="flex justify-between items-center text-xs text-muted-foreground">
|
||||||
@@ -323,17 +471,46 @@ const ShelterModule: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center pb-2 border-b">
|
<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>
|
<span className="font-mono font-bold text-foreground">{result.forces.fySide.toFixed(2)} kN</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center pb-2 border-b">
|
<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="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>
|
<span className="font-mono font-bold text-emerald-600 dark:text-emerald-400">{result.lineLoads.columnLoad.toFixed(2)} kN/m</span>
|
||||||
</div>
|
</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="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>
|
<span className="font-mono font-bold text-sky-600 dark:text-sky-400">{result.lineLoads.beamLoad.toFixed(2)} kN/m</span>
|
||||||
</div>
|
</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="pt-2 space-y-2">
|
||||||
<div className="font-bold text-sm text-primary">Parâmetros Adicionais (NBR 6123)</div>
|
<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">
|
<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