diff --git a/app/src/components/three/Shelter3D.tsx b/app/src/components/three/Shelter3D.tsx index 92e68bd..98ff018 100644 --- a/app/src/components/three/Shelter3D.tsx +++ b/app/src/components/three/Shelter3D.tsx @@ -17,6 +17,7 @@ export interface Shelter3DProps { rightClosure: number; openingPos: OpeningPosition; backRange?: [number, number]; + numColumns?: number; windAngle?: ShelterWindAngle; viewMode: '3d' | 'elevation' | 'airflow'; } @@ -32,6 +33,7 @@ function ShelterModel({ rightClosure, openingPos, backRange, + numColumns = 2, windAngle: _windAngle = 0, viewMode, }: Shelter3DProps) { @@ -91,10 +93,17 @@ function ShelterModel({ ); }; + const overhang = 0.6; + const spanWidth = Math.max(0.1, width - 2 * overhang); + const spacing = spanWidth / Math.max(1, numColumns - 1); + const columnPositions = Array.from({ length: numColumns }).map( + (_, i) => -halfW + overhang + i * spacing + ); + return ( {/* 1. Estrutura Metálica Principal (Pilares e Vigas em Balanço - Tubos Quadrados) */} - {[-halfW + 0.6, halfW - 0.6].map((xPos, idx) => ( + {columnPositions.map((xPos, idx) => ( {/* Pilar traseiro (lado direito na elevação) */} diff --git a/app/src/lib/nbr-tables/table-shelters.ts b/app/src/lib/nbr-tables/table-shelters.ts index e6265cb..1f9d81f 100644 --- a/app/src/lib/nbr-tables/table-shelters.ts +++ b/app/src/lib/nbr-tables/table-shelters.ts @@ -29,6 +29,8 @@ export interface ShelterInput { rightClosure: number; /** Posição da abertura na parede de fundo ('top' | 'bottom' | 'distributed') */ openingPos: OpeningPosition; + /** Número de pórticos principais/pilares contínuos (padrão: 2) */ + numColumns?: number; /** Ângulo de incidência do vento (0° = frontal, 45° = oblíquo, 90° = lateral) */ windAngle?: ShelterWindAngle; } @@ -38,10 +40,14 @@ export interface ShelterResult { windAngle: ShelterWindAngle; /** Coeficiente de pressão superior da cobertura (Ce superior) */ cpeTop: number; + /** Coeficiente de pressão superior EXTREMO na zona de borda livre (Ce borda) */ + cpeEdgeTop: number; /** Coeficiente de pressão interna efetivo sob a cobertura (Cpi inferior) */ cpiBottom: number; - /** Coeficiente de força líquida vertical na cobertura (Cnf = cpeTop - cpiBottom) */ + /** Coeficiente de força líquida vertical média na cobertura (Cnf = cpeTop - cpiBottom) */ cnfVertical: number; + /** Coeficiente de força líquida vertical de PICO na zona de borda livre */ + cnfEdgeVertical: number; /** Coeficiente de força horizontal na parede de fundo */ cfBack: number; /** Coeficiente de força horizontal nas paredes laterais */ @@ -61,10 +67,18 @@ export interface ShelterResult { }; /** Carga linear estimada nos pórticos metálicos principais (kN/m) (ex: Tubo 150x150) */ lineLoads: { - /** Carga horizontal em cada pilar (kN/m) */ + /** Carga horizontal no pilar do PÓRTICO CENTRAL (mais carregado) (kN/m) */ columnLoad: number; - /** Carga vertical/horizontal na viga em balanço (kN/m) */ + /** Carga horizontal no pilar do PÓRTICO DE EXTREMIDADE (kN/m) */ + columnLoadEdge: number; + /** Carga vertical/horizontal média equivalente na viga do PÓRTICO CENTRAL (kN/m) */ beamLoad: number; + /** Carga vertical/horizontal média equivalente na viga do PÓRTICO DE EXTREMIDADE (kN/m) */ + beamLoadEdge: number; + /** Momento fletor na raiz/engaste do balanço CENTRAL (kN.m) considerando o Efeito Asa de Avião */ + beamMoment: number; + /** Momento fletor na raiz/engaste do balanço na EXTREMIDADE (kN.m) */ + beamMomentEdge: number; }; /** Alívio percentual de arrancamento obtido pela posição da abertura (%) */ reliefPercentage: number; @@ -98,6 +112,7 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes leftClosure, rightClosure, openingPos, + numColumns = 2, } = input; const windAngle: ShelterWindAngle = input.windAngle ?? 0; @@ -115,15 +130,19 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes // 1. Coeficiente externo superior na cobertura (Ce superior) // Varia conforme o ângulo de incidência (0°, 45° ou 90°) let cpeTop = -1.0; + let cpeEdgeTop = -2.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; + cpeEdgeTop = theta <= 5 ? -1.8 : theta <= 15 ? -2.0 : -2.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; + cpeEdgeTop = theta <= 5 ? -2.2 : theta <= 15 ? -2.5 : -2.8; } else { // Lateral 90°: escoamento transversal sobre a cobertura, sucção constante por descolamento cpeTop = -1.1; + cpeEdgeTop = -1.6; } // 2. Cálculo do Cpi sob a cobertura (efeito estagnação da parede traseira / laterais) @@ -189,6 +208,7 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes // Cnf vertical total na cobertura (arrancamento para cima = negativo) const cnfUp = cpeTop - Math.max(0, effectiveCpi); const cnfDown = 0.2 - Math.min(0, effectiveCpi); + const cnfEdgeUp = cpeEdgeTop - Math.max(0, effectiveCpi); // 4. Coeficientes horizontais em paredes de fundo e laterais let cfBack = 1.3 * bClose; @@ -210,20 +230,45 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes const fySide = cfSide * q * (areaSide * 2); const fFriction = 0.04 * q * areaRoof; - // 6. Carga linear em pórticos (2 pórticos principais) - const numPorticos = 2; - let columnLoad = 0; + // 6. Carga linear em pórticos (usando Áreas Tributárias) + const numPorticos = Math.max(2, numColumns); + const numBays = numPorticos - 1; + const overhang = 0.6; // beiral genérico considerado no 3D + // Largura total efetiva entre o primeiro e o último pilar: + const spanWidth = Math.max(0.1, width - 2 * overhang); + const spacing = spanWidth / numBays; + + // A área tributária de um pórtico central é o espaçamento 's' + // A área tributária de um pórtico de extremidade é 's/2' + overhang + const tributaryCentral = spacing; + const tributaryEdge = spacing / 2 + overhang; + + // Fração da carga total que vai para o pórtico central vs extremidade + const fractionCentral = tributaryCentral / width; + const fractionEdge = tributaryEdge / width; + + let columnLoadTotal = 0; if (windAngle === 0) { - columnLoad = (fxBack / height / numPorticos) + (q * 1.8 * 0.15); + columnLoadTotal = fxBack; } else if (windAngle === 45) { - const combForce = Math.sqrt(fxBack * fxBack + fySide * fySide); - columnLoad = (combForce / height / numPorticos) + (q * 1.8 * 0.15); + columnLoadTotal = Math.sqrt(fxBack * fxBack + fySide * fySide); } else { - // Em 90°, a carga transversal principal atua nas paredes laterais (Fy) - columnLoad = (fySide / height / numPorticos) + (q * 1.8 * 0.15); + columnLoadTotal = fySide; } - const beamLoad = (fzUp / depth / numPorticos); + // Cargas Distribuídas Lineares no Pilar + const windBaseLoadColumn = q * 1.8 * 0.15; // arrasto base no próprio perfil do pilar + const columnLoad = (columnLoadTotal * fractionCentral / height) + windBaseLoadColumn; + const columnLoadEdge = (columnLoadTotal * fractionEdge / height) + windBaseLoadColumn; + + // Cargas Distribuídas Lineares na Viga (Balanço) + const beamLoad = (fzUp * fractionCentral / depth); + const beamLoadEdge = (fzUp * fractionEdge / depth); + + // Cálculo do Momento Fletor na raiz da viga em balanço considerando o centro de pressão deslocado (L/3) + const cpRootDist = (2 / 3) * depth; + const beamMoment = (fzUp * fractionCentral) * cpRootDist; + const beamMomentEdge = (fzUp * fractionEdge) * cpRootDist; // Texto explicativo por condição e ângulo let statusText = ''; @@ -266,8 +311,10 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes return { windAngle, cpeTop: Number(cpeTop.toFixed(2)), + cpeEdgeTop: Number(cpeEdgeTop.toFixed(2)), cpiBottom: Number(effectiveCpi.toFixed(2)), cnfVertical: Number(cnfUp.toFixed(2)), + cnfEdgeVertical: Number(cnfEdgeUp.toFixed(2)), cfBack: Number(cfBack.toFixed(2)), cfSide: Number(cfSide.toFixed(2)), forces: { @@ -279,7 +326,11 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes }, lineLoads: { columnLoad: Number(columnLoad.toFixed(2)), + columnLoadEdge: Number(columnLoadEdge.toFixed(2)), beamLoad: Number(beamLoad.toFixed(2)), + beamLoadEdge: Number(beamLoadEdge.toFixed(2)), + beamMoment: Number(beamMoment.toFixed(2)), + beamMomentEdge: Number(beamMomentEdge.toFixed(2)), }, reliefPercentage, statusText, diff --git a/app/src/pages/ShelterModule.tsx b/app/src/pages/ShelterModule.tsx index fdb5850..9effeaa 100644 --- a/app/src/pages/ShelterModule.tsx +++ b/app/src/pages/ShelterModule.tsx @@ -37,6 +37,7 @@ const ShelterModule: React.FC = () => { const [width, setWidth] = useState(10); const [height, setHeight] = useState(4.8); const [theta, setTheta] = useState(5); + const [numColumns, setNumColumns] = useState(2); const [backClosure, setBackClosure] = useState(75); const [backRange, setBackRange] = useState<[number, number]>([0, 75]); const [leftClosure, setLeftClosure] = useState(100); @@ -91,12 +92,13 @@ const ShelterModule: React.FC = () => { leftClosure, rightClosure, openingPos, + numColumns, }; return { result: calculateShelterWind({ ...input, windAngle }, q), envelope: calculateShelterWindAllAngles(input, q), }; - }, [condition, q, depth, width, height, theta, backClosure, backRange, leftClosure, rightClosure, openingPos, windAngle]); + }, [condition, q, depth, width, height, theta, backClosure, backRange, leftClosure, rightClosure, openingPos, windAngle, numColumns]); const effAreaRoof = depth * width; const effAreaBack = (width * height) * (backClosure / 100); @@ -126,12 +128,13 @@ const ShelterModule: React.FC = () => { title: `Coeficientes Aerodinâmicos e Pressões Líquidas - Direção ${angleLabel}`, type: 'grid', gridItems: [ - { label: 'Ce Superior (Cobertura - Tab. 24)', value: result.cpeTop }, + { label: 'Ce Médio (Cobertura)', value: result.cpeTop }, + { label: 'Ce Borda (Pico Aerodinâmico)', value: result.cpeEdgeTop }, { label: 'Cpi Inferior (Sobrepressão sob Telhado)', value: result.cpiBottom }, - { label: 'Cnf Resultante (Ce - Cpi)', value: result.cnfVertical }, + { label: 'Cnf Médio Resultante', value: result.cnfVertical }, + { label: 'Cnf Borda Resultante', value: result.cnfEdgeVertical }, { 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}%` }, ], }, { @@ -224,11 +227,11 @@ const ShelterModule: React.FC = () => { `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)}` + 'Cnf Vertical Médio | Borda', + `${envelope.res0.cnfVertical} | ${envelope.res0.cnfEdgeVertical}`, + `${envelope.res45.cnfVertical} | ${envelope.res45.cnfEdgeVertical}`, + `${envelope.res90.cnfVertical} | ${envelope.res90.cnfEdgeVertical}`, + `Crítico Borda: ${Math.min(envelope.res0.cnfEdgeVertical, envelope.res45.cnfEdgeVertical, envelope.res90.cnfEdgeVertical)}` ], [ 'Força Vertical Telhado [Fz ↑]', @@ -286,6 +289,13 @@ const ShelterModule: React.FC = () => { `${envelope.res90.lineLoads.beamLoad} kN/m`, `${Math.max(envelope.res0.lineLoads.beamLoad, envelope.res45.lineLoads.beamLoad, envelope.res90.lineLoads.beamLoad).toFixed(2)} kN/m` ], + [ + 'Momento na Raiz da Viga', + `${envelope.res0.lineLoads.beamMoment} kN.m`, + `${envelope.res45.lineLoads.beamMoment} kN.m`, + `${envelope.res90.lineLoads.beamMoment} kN.m`, + `${Math.max(envelope.res0.lineLoads.beamMoment, envelope.res45.lineLoads.beamMoment, envelope.res90.lineLoads.beamMoment).toFixed(2)} kN.m` + ], ], }, { @@ -302,14 +312,19 @@ const ShelterModule: React.FC = () => { 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` }, + { label: 'Carga Horiz. Pórtico Central', value: `${result.lineLoads.columnLoad} kN/m` }, + { label: 'Carga Horiz. Extremidade', value: `${result.lineLoads.columnLoadEdge} kN/m` }, + { label: 'Carga Vert. Média Viga Central', value: `${result.lineLoads.beamLoad} kN/m` }, + { label: 'Carga Vert. Média Extremidade', value: `${result.lineLoads.beamLoadEdge} kN/m` }, + { label: 'Momento Engaste Viga Central', value: `${result.lineLoads.beamMoment} kN.m` }, + { label: 'Momento Engaste Extremidade', value: `${result.lineLoads.beamMomentEdge} kN.m` }, ], }, { - title: 'Parecer Técnico do Regime Topológico e Aerodinâmico', + title: 'Parecer Técnico e Alertas Aerodinâmicos (Zonas de Borda)', 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.`, + content: `${result.statusText} Permeabilidade líquida da parede de fundo: ${100 - backClosure}%. Alívio de arrancamento obtido pela fresta superior: ${result.reliefPercentage}%. +⚠️ ATENÇÃO ESTRUTURAL (Efeito Asa de Avião): O coeficiente global médio subestima a força de arrancamento na ponta do balanço. Os cálculos acima consideram as zonas de borda com coeficientes extremos (Cnf Borda = ${result.cnfEdgeVertical}). O Momento Fletor na raiz da viga foi calculado considerando que o centro de pressão está deslocado para próximo à extremidade livre (L/3), o que resulta num momento de engaste substancialmente maior que o de uma carga uniformemente distribuída simples.`, }, { title: 'Guia de Aplicação dos Eixos e Resultantes no Croqui', @@ -355,7 +370,7 @@ const ShelterModule: React.FC = () => { @@ -480,6 +495,13 @@ const ShelterModule: React.FC = () => { setTheta(v[0])} /> +
+
+ Número de Pórticos (Pilares): + {numColumns} pórticos +
+ setNumColumns(v[0])} /> +
@@ -547,6 +569,7 @@ const ShelterModule: React.FC = () => { rightClosure={rightClosure} openingPos={openingPos} windAngle={windAngle} + numColumns={numColumns} viewMode={viewMode} /> ) : ( @@ -573,8 +596,8 @@ const ShelterModule: React.FC = () => { {result.forces.fzUp.toFixed(2)} kN
- Coeficiente Cnf - {result.cnfVertical} + Cnf Médio | Cnf Borda + {result.cnfVertical} | {result.cnfEdgeVertical}
Pressão (/m²) @@ -614,13 +637,17 @@ const ShelterModule: React.FC = () => {
- Carga no Pilar (Pórtico) + Carga no Pilar (Central) {result.lineLoads.columnLoad.toFixed(2)} kN/m
-
- Carga na Viga (Balanço) +
+ Carga Vertical Viga (Central) {result.lineLoads.beamLoad.toFixed(2)} kN/m
+
+ Momento Engaste (Asa) + {result.lineLoads.beamMoment.toFixed(2)} kN.m +