🚀 Auto-deploy: BrainWind atualizado em 26/08/2026 11:43:19

This commit is contained in:
2026-08-26 11:43:19 +00:00
parent b842d6009f
commit f787e8c002
6 changed files with 258 additions and 69 deletions
-2
View File
@@ -15,7 +15,6 @@ const TowerModule = lazy(() => import('./pages/TowerModule'));
const PiperackModule = lazy(() => import('./pages/PiperackModule'));
const ShelterModule = lazy(() => import('./pages/ShelterModule'));
const CertificatePage = lazy(() => import('./pages/CertificatePage'));
const CompareModule = lazy(() => import('./pages/Compare'));
import {
Home, Settings, Menu, Cylinder, Church, CircleDot,
Square, Layers, BarChart3, Activity, Warehouse,
@@ -377,7 +376,6 @@ function App() {
<Route path="/calc-ca" element={<CalcCaModule />} />
<Route path="/settings" element={<SettingsModule />} />
<Route path="/certificado" element={<CertificatePage />} />
<Route path="/compare" element={<CompareModule />} />
</Routes>
</Suspense>
</AppLayout>
+1 -1
View File
@@ -85,7 +85,7 @@ export function DragCoefficientChart({ l1, l2, h, caLow, caHigh }: DragCoefficie
{/* Fake Curves */}
{curves.map((d, idx) => (
<path key={idx} d={d} fill="none" stroke="currentColor" strokeOpacity={0.2} strokeWidth={1.5} className="text-red-500" />
<path key={idx} d={d} fill="none" strokeWidth={1.5} className="stroke-amber-500/40 dark:stroke-amber-400/60" />
))}
{/* Axes */}
+28 -26
View File
@@ -67,33 +67,34 @@ const Heatmap2D: React.FC<Heatmap2DProps> = ({ overrideAngle, overrideWallCpe, o
return (
<div className="w-full h-full flex flex-col items-center justify-center p-4">
<div className="bg-background rounded-xl p-4 shadow-sm border border-border w-full max-w-2xl">
<h3 className="text-center font-semibold mb-2">Planta Baixa - Telhado e Paredes</h3>
<p className="text-xs text-center text-muted-foreground mb-4">Vento incidindo a {windAngle}°</p>
<div className="bg-background rounded-xl p-4 shadow-sm border border-border w-full max-w-2xl flex flex-col max-h-full">
<h3 className="text-center font-semibold mb-2 shrink-0">Planta Baixa - Telhado e Paredes</h3>
<p className="text-xs text-center text-muted-foreground mb-4 shrink-0">Vento incidindo a {windAngle}°</p>
<svg width="100%" height="100%" viewBox={`0 0 ${svgW} ${svgH}`} className="max-w-[500px] mx-auto overflow-visible">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor" className="text-foreground" />
</marker>
</defs>
<div className="flex-1 min-h-0 relative flex items-center justify-center w-full">
<svg width="100%" height="100%" viewBox={`0 0 ${svgW} ${svgH}`} className="max-w-[400px] max-h-[40vh] mx-auto overflow-visible" style={{ maxHeight: 'min(400px, 40vh)' }}>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor" className="text-foreground" />
</marker>
</defs>
{/* Vento (Setas Dinâmicas) */}
{windAngle === 90 && (
<g className="text-blue-500" stroke="currentColor" strokeWidth="3" markerEnd="url(#arrow)">
<line x1={x0 - 60} y1={cy} x2={x0 - 20} y2={cy} />
<line x1={x0 - 60} y1={cy - 40} x2={x0 - 20} y2={cy - 40} />
<line x1={x0 - 60} y1={cy + 40} x2={x0 - 20} y2={cy + 40} />
<text x={x0 - 70} y={cy} textAnchor="end" className="text-sm font-bold fill-blue-500" stroke="none">Vento (90°)</text>
<line x1={x0 - 100} y1={cy} x2={x0 - 60} y2={cy} />
<line x1={x0 - 100} y1={cy - 40} x2={x0 - 60} y2={cy - 40} />
<line x1={x0 - 100} y1={cy + 40} x2={x0 - 60} y2={cy + 40} />
<text x={x0 - 110} y={cy} textAnchor="end" dominantBaseline="middle" className="text-2xl font-bold fill-blue-500" stroke="none">Vento (90°)</text>
</g>
)}
{windAngle === 0 && (
<g className="text-blue-500" stroke="currentColor" strokeWidth="3" markerEnd="url(#arrow)">
<line x1={cx} y1={y0 - 60} x2={cx} y2={y0 - 20} />
<line x1={cx - 40} y1={y0 - 60} x2={cx - 40} y2={y0 - 20} />
<line x1={cx + 40} y1={y0 - 60} x2={cx + 40} y2={y0 - 20} />
<text x={cx} y={y0 - 70} textAnchor="middle" className="text-sm font-bold fill-blue-500" stroke="none">Vento (0°)</text>
<line x1={cx} y1={y0 - 100} x2={cx} y2={y0 - 60} />
<line x1={cx - 40} y1={y0 - 100} x2={cx - 40} y2={y0 - 60} />
<line x1={cx + 40} y1={y0 - 100} x2={cx + 40} y2={y0 - 60} />
<text x={cx} y={y0 - 115} textAnchor="middle" className="text-2xl font-bold fill-blue-500" stroke="none">Vento (0°)</text>
</g>
)}
@@ -162,23 +163,24 @@ const Heatmap2D: React.FC<Heatmap2DProps> = ({ overrideAngle, overrideWallCpe, o
{/* Indicadores de Paredes */}
{windAngle === 0 ? (
<>
<text x={cx} y={y0 - 25} textAnchor="middle" className="text-xs font-bold fill-foreground">A</text>
<text x={cx} y={y0 + drawL + 25} textAnchor="middle" dominantBaseline="hanging" className="text-xs font-bold fill-foreground">B</text>
<text x={x0 - 25} y={cy} textAnchor="end" dominantBaseline="middle" className="text-xs font-bold fill-foreground">D</text>
<text x={x0 + drawW + 25} y={cy} textAnchor="start" dominantBaseline="middle" className="text-xs font-bold fill-foreground">C</text>
<text x={cx} y={y0 - 25} textAnchor="middle" className="text-2xl font-bold fill-foreground">A</text>
<text x={cx} y={y0 + drawL + 25} textAnchor="middle" dominantBaseline="hanging" className="text-2xl font-bold fill-foreground">B</text>
<text x={x0 - 25} y={cy} textAnchor="end" dominantBaseline="middle" className="text-2xl font-bold fill-foreground">D</text>
<text x={x0 + drawW + 25} y={cy} textAnchor="start" dominantBaseline="middle" className="text-2xl font-bold fill-foreground">C</text>
</>
) : (
<>
<text x={x0 - 25} y={cy} textAnchor="end" dominantBaseline="middle" className="text-xs font-bold fill-foreground">A</text>
<text x={x0 + drawW + 25} y={cy} textAnchor="start" dominantBaseline="middle" className="text-xs font-bold fill-foreground">B</text>
<text x={cx} y={y0 + drawL + 25} textAnchor="middle" dominantBaseline="hanging" className="text-xs font-bold fill-foreground">C</text>
<text x={cx} y={y0 - 25} textAnchor="middle" className="text-xs font-bold fill-foreground">D</text>
<text x={x0 - 25} y={cy} textAnchor="end" dominantBaseline="middle" className="text-2xl font-bold fill-foreground">A</text>
<text x={x0 + drawW + 25} y={cy} textAnchor="start" dominantBaseline="middle" className="text-2xl font-bold fill-foreground">B</text>
<text x={cx} y={y0 + drawL + 25} textAnchor="middle" dominantBaseline="hanging" className="text-2xl font-bold fill-foreground">C</text>
<text x={cx} y={y0 - 25} textAnchor="middle" className="text-2xl font-bold fill-foreground">D</text>
</>
)}
</svg>
</div>
<div className="mt-6 flex flex-wrap justify-center gap-4 text-sm">
<div className="mt-4 shrink-0 flex flex-wrap justify-center gap-4 text-xs sm:text-sm">
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded" style={{ backgroundColor: getPressureColor(-1.0, cpi) }}></div>
<span>Sucção Máxima</span>
+210
View File
@@ -17,6 +17,13 @@ import { evaluateComfort } from '../comfort';
import { classifyBridge, type BridgeClassificationInput } from '../modules/bridge';
import { calculateTower } from '../modules/tower';
import { calculatePiperack, type PiperackInput } from '../modules/piperack';
import { calculateVault, type VaultInput } from '../modules/vault';
import { calculateDome, type DomeInput } from '../modules/dome';
import { calculateIsolatedGableRoof, calculateIsolatedShedRoof } from '../nbr-tables/table-24-25';
import { calculateFlatBarForce, type FlatBarForceInput } from '../nbr-tables/table-26';
import { applyRoughnessChange } from '../wind-direction';
import { TABLE_1 } from '../nbr-tables/table-1';
import { getStrouhalNumber, criticalVelocity, scrutonNumber } from '../nbr-tables/table-33';
function calcWind(
v0: number, s1: number, s3: number,
@@ -267,6 +274,209 @@ export function generateAllScenarios(): AuditScenario[] {
));
}
// --- NOVOS CENÁRIOS BIBLIOGRÁFICOS EXPANDIDOS (LITERATURA & NBR 6123) ---
// 1.1 Abóbada Cilíndrica (Blessmann / Tabela 15 NBR 6123)
{
const vaultIn: VaultInput = { f: 4, l: 20, b: 40, vk: 35, regime: 'laminar-rough', cpi: 0 };
const vaultRes = calculateVault(vaultIn);
scenarios.push(makeScenario(
'blessmann-abobada-f4-l20', 'vault', 'Blessmann: Abóbada Cilíndrica f=4m l=20m b=40m',
{ ...vaultIn },
{ q: vaultRes.q, fOverL: 4 / 20 },
{ ...vaultRes.windPerpendicular, q: vaultRes.q },
{ q: { min: 0.5, max: 1.5 } },
'vault',
{ f: 4, l: 20, b: 40, cpeValues: vaultRes.windPerpendicular },
'Sec. 6.2.3 / Tabela 15',
true,
'Blessmann, J. Ação do Vento em Coberturas Curvas e Abóbadas, Exemplo 3.2 / NBR 6123:2023 Tabela 15',
'Abóbada cilíndrica com vão de 20m, flecha de 4m (f/l = 0,20) e comprimento de 40m sob vento perpendicular de 35 m/s. Avaliação da sucção no topo e pressões no bordo de ataque.'
));
}
// 1.2 Cúpula Semiesférica apoiada no solo (Rebello / Tabela 21 NBR 6123)
{
const domeIn: DomeInput = { d: 30, f: 6, vk: 40, type: 'on-ground', cpi: 0 };
const domeRes = calculateDome(domeIn);
scenarios.push(makeScenario(
'rebello-cupula-d30-f6', 'dome', 'Rebello: Cúpula no Solo d=30m f=6m',
{ ...domeIn },
{ q: domeRes.q, fOverD: domeRes.fOverD },
{ cpeBarlavento: domeRes.cpeBarlavento, cpeTopo: domeRes.cpeTopo, liftCoefficient: domeRes.liftCoefficient, liftForceKN: domeRes.liftForceKN },
{ q: { min: 0.5, max: 1.8 }, liftForceKN: { min: 50, max: 1000 } },
'dome',
{ d: 30, f: 6, cpeTopo: domeRes.cpeTopo, cpeBarlavento: domeRes.cpeBarlavento },
'Sec. 6.2.4 / Tabela 21',
true,
'Rebello, Y. A Concepção Estrutural e a Arquitetura / NBR 6123 Tabela 21 (Cúpulas sobre o solo)',
'Cúpula esférica implantada diretamente no terreno com diâmetro d=30m e flecha f=6m (f/d = 0,20). Determinação da força global de sustentação aerodinâmica (empuxo ascendente) para velocidade característica de 40 m/s.'
));
}
// 1.3 Cobertura Isolada a Duas Águas - Hangar Aberto (Pfeil / Tabela 25 NBR 6123)
{
const isoIn = { theta: 10, height: 5, depth: 15 };
const isoRes = calculateIsolatedGableRoof(isoIn);
scenarios.push(makeScenario(
'pfeil-cobertura-isolada-2aguas', 'isolated-roof', 'Pfeil: Cobertura Isolada 2 Águas θ=10°',
{ ...isoIn, q: 0.85 },
{ cpb1: isoRes.cpb.cpb1, cpa1: isoRes.cpa.cpa1 },
{ cpb1: isoRes.cpb.cpb1, cpb2: isoRes.cpb.cpb2, cpa1: isoRes.cpa.cpa1, cpa2: isoRes.cpa.cpa2, applies: isoRes.applies ? 1 : 0 },
{ cpb1: { min: 0.5, max: 2.0 }, cpa1: { min: -2.0, max: -0.5 } },
'isolatedRoof',
{ theta: 10, height: 5, depth: 15, cpa: isoRes.cpa, cpb: isoRes.cpb },
'Sec. 7.2 / Tabela 25',
true,
'Pfeil & Pfeil, Estruturas de Aço, 8ª ed., Cap. 5 (Galpões Abertos e Hangares Isolados) / NBR 6123 Tabela 25',
'Cobertura isolada simétrica de duas águas com inclinação de 10° e pé-direito livre h=5m (b=15m). Análise dos coeficientes de pressão líquida nos carregamentos extremos de sobrepressão e sucção.'
));
}
// 1.4 Cobertura Isolada a Uma Água - Marquise / Abrigo (NBR 6123 Tabela 24)
{
const shedIn = { theta: 5, height: 3.5, depth: 10 };
const shedRes = calculateIsolatedShedRoof(shedIn);
scenarios.push(makeScenario(
'nbr-marquise-isolada-1agua', 'isolated-roof', 'NBR 6123: Marquise Isolada a Uma Água θ=5°',
{ ...shedIn, q: 0.95 },
{ cph1High: shedRes.cph1.high, cph1Low: shedRes.cph1.low },
{ cph1High: shedRes.cph1.high, cph1Low: shedRes.cph1.low, applies: shedRes.applies ? 1 : 0 },
{ cph1High: { min: -1.5, max: 0.0 }, cph1Low: { min: -2.5, max: -0.5 } },
'isolatedRoof',
{ theta: 5, height: 3.5, depth: 10 },
'Sec. 7.2.1 / Tabela 24',
true,
'NBR 6123:2023, Tabela 24 — Coberturas isoladas a uma água plana / Exemplo Didático CBCA',
'Marquise/alpendre isolado a 1 água com inclinação suave de 5° e altura de 3.5m. Verificação dos coeficientes médios e limites de sucção no bordo de ataque.'
));
}
// 1.5 Torre Treliçada Triangular de Telecomunicações (NBR 6123 Tabela 29/30 & Blessmann)
{
const towerIn = { section: 'triangular' as const, barType: 'flat' as const, phi: 0.25, aFace: 90, alphaWind: 0 as const, q: 1.15 };
const towerRes = calculateTower(towerIn);
scenarios.push(makeScenario(
'blessmann-torre-triangular-phi025', 'tower', 'Blessmann: Torre Triangular φ=0.25',
{ ...towerIn },
{ caEff: towerRes.caEff },
{ caEff: towerRes.caEff, forceKN: towerRes.forceKN },
{ caEff: { min: 1.5, max: 3.5 }, forceKN: { min: 100, max: 500 } },
'tower',
{ section: 'triangular', baseWidth: 3, height: 30, panels: 6, phi: 0.25, alphaWind: 0, forceKN: towerRes.forceKN },
'Sec. 8.5 / Tabelas 29-30',
true,
'Blessmann, J. Ação do Vento em Torres e Estruturas Treliçadas, Cap. 7 / NBR 6123:2023 Sec. 8.5',
'Torre treliçada de seção triangular equilátera com índice de plenitude φ=0,25 e área projetada da face de 90 m² submetida a vento frontal (0°). Cálculo do coeficiente de arrasto efetivo e da força global na torre.'
));
}
// 1.6 Barra Isolada Perfil L / Cantoneira de Torre (Pfeil & NBR 6123 Tabela 26)
{
const barIn: FlatBarForceInput = { section: 'l', alpha: 45, width: 0.15, length: 3.0, q: 1.05 };
const barRes = calculateFlatBarForce(barIn);
scenarios.push(makeScenario(
'pfeil-cantoneira-l-alpha45', 'bar', 'Pfeil: Cantoneira L150x150 sob Vento a 45°',
{ ...barIn },
{ cx: barRes.cx, cy: barRes.cy, kFactor: barRes.kFactor },
{ fxKN: barRes.fxKN, fyKN: barRes.fyKN, cx: barRes.cx, cy: barRes.cy },
{ cx: { min: 1.0, max: 2.2 }, cy: { min: 1.0, max: 2.2 }, fxKN: { min: 0.5, max: 2.0 } },
'bar',
{ section: 'l', width: 0.15, length: 3.0, alpha: 45, fxKN: barRes.fxKN, fyKN: barRes.fyKN },
'Sec. 8.1 / Tabelas 26 e 28',
true,
'Pfeil, M. & Pfeil, W. Estruturas de Aço, Cap. Ações em Barras Isoladas / NBR 6123 Tabela 26',
'Barra prismática isolada de seção cantoneira de abas iguais (L 150x150 mm) com 3 metros de comprimento livre, sob incidência oblíqua do vento a 45°. Determinação das componentes de força Cx, Cy e redução por comprimento finito (Fator K).'
));
}
// 1.7 Galpão Extenso — Força de Atrito Superficial (Blessmann Cap. 5 / NBR 6123 Sec. 6.1.5)
{
const fricIn = { roughness: 'rounded-ribs' as const, length: 80, width: 15, height: 6, roofPitch: 8, q: 1.0, windAngle: 0 as const };
const fricRes = calculateFriction(fricIn);
scenarios.push(makeScenario(
'blessmann-galpao-extenso-atrito', 'galpao', 'Blessmann: Galpão Extenso 80x15m — Atrito Superficial',
{ ...fricIn },
{ applies: fricRes.applies ? 1 : 0, ratioLh: 80 / 6 },
{ applies: fricRes.applies ? 1 : 0, forceKN: fricRes.forceKN, roofArea: fricRes.roofArea, cf: fricRes.cf },
{ forceKN: { min: 10, max: 50 }, roofArea: { min: 1000, max: 1500 } },
'warehouse',
{ width: 15, length: 80, height: 6, roofPitch: 8, windAngle: 0, cpi: 0 },
'Sec. 6.1.5',
true,
'Blessmann, J. O Vento na Engenharia Estrutural, Cap. 5 (Forças de Atrito em Edificações Longas) / NBR 6123 Sec. 6.1.5',
'Galpão industrial longo de 80 metros de comprimento por 15m de largura e 6m de altura (l/h = 13,3 > 4). Telhas com nervuras onduladas/arredondadas (Cf = 0,02). Cálculo da força de arrasto por atrito superficial na cobertura.'
));
}
// 1.8 Transição de Rugosidade de Terreno — Litoral para Centro Urbano (NBR 6123 Sec. 5.5)
{
const transIn = { near: 'IV' as TerrainCategory, far: 'I' as TerrainCategory, distance: 500, height: 30 };
const params = new Map<TerrainCategory, { b: number; p: number; fr: number }>();
(['I', 'II', 'III', 'IV', 'V'] as TerrainCategory[]).forEach(cat => {
params.set(cat, TABLE_1[cat].B);
});
const transRes = applyRoughnessChange(transIn, params);
scenarios.push(makeScenario(
'nbr-transicao-rugosidade-catI-IV', 'galpao', 'NBR 6123: Transição de Rugosidade Cat I -> Cat IV (x=500m, z=30m)',
{ ...transIn },
{ z1: transRes.z1, z2: transRes.z2, s2: transRes.s2 },
{ s2: transRes.s2, z1: transRes.z1 },
{ s2: { min: 0.8, max: 1.3 } },
'warehouse',
{ width: 20, length: 40, height: 30, roofPitch: 0, windAngle: 0, cpi: 0 },
'Sec. 5.5',
true,
'NBR 6123:2023, Seção 5.5 (Mudança de Rugosidade do Terreno) / Notas de Aula Prof. Blessmann',
'Edificação de 30m de altura situada a 500 metros da linha de transição costeira (saída de Terreno Aberto Cat. I para Zona Urbana Densa Cat. IV). Determinação da altura da camada limite interna (z1) e do fator S2 interpolado.'
));
}
// 1.9 Desprendimento de Vórtices em Chaminé Esbelta (Strouhal & Scruton — NBR 6123 Sec. 10)
{
const st = getStrouhalNumber('circle', 0); // 0.20
const fn = 1.2; // Hz
const d = 1.5; // m
const vcr = criticalVelocity(fn, d, st); // 1.2 * 1.5 / 0.20 = 9.0 m/s
const sc = scrutonNumber(0.02, 600, 1.225, 1.5); // 0.02 * 600 / (1.225 * 1.5) = 6.53
scenarios.push(makeScenario(
'nbr-vortices-chamine-strouhal', 'dynamics', 'NBR 6123: Vórtices de Von Kármán em Chaminé Cilíndrica',
{ shape: 'circle', diameter: d, naturalFreq: fn, massPerLength: 600, dampingRatio: 0.02 },
{ st, vcr, sc },
{ st, vcr, sc, isSusceptible: sc < 20 ? 1 : 0 },
{ vcr: { min: 5, max: 20 }, sc: { min: 2, max: 20 } },
'dynamics',
{ height: 45, freq: fn, windSpeed: vcr, scruton: sc, sectionShape: 'circle', sectionSize: d },
'Sec. 10 / Tabela 33',
true,
'NBR 6123:2023, Seção 10 (Efeitos Dinâmicos de Desprendimento de Vórtices) & Simiu & Scanlan',
'Chaminé metálica esbelta circular com d=1.5m, frequência natural fundamental fn=1.2 Hz, massa linear de 600 kg/m e amortecimento estrutural ξ=0,02. Cálculo do Número de Strouhal (St=0,20), velocidade crítica de ressonância (Vcr) e Número de Scruton (Sc).'
));
}
// 1.10 Pipe-Rack Industrial com Múltiplos Planos (NBR 6123 Sec. 8.4 / Blessmann)
{
const pipeIn: PiperackInput = {
width: 12, height: 5, elevation: 8, spacing: 6, numFrames: 4, phiStruct: 0.20,
pipes: [{ id: 'p1', diameter: 0.6, elevationOffset: 5 }, { id: 'p2', diameter: 0.6, elevationOffset: 5 }],
q: 1.10
};
const pipeRes = calculatePiperack(pipeIn);
scenarios.push(makeScenario(
'blessmann-piperack-4quadros', 'piperack', 'Blessmann: Pipe-Rack Industrial 4 Quadros com Tubulações',
{ ...pipeIn },
{ phiTotal: pipeRes.phiTotal, effectiveElevation: pipeRes.effectiveElevation },
{ caFrontal: pipeRes.caFrontal, eta: pipeRes.eta, globalForce: pipeRes.globalForce },
{ caFrontal: { min: 1.5, max: 3.0 }, eta: { min: 0.4, max: 1.0 }, globalForce: { min: 10, max: 300 } },
'warehouse',
{ width: 12, height: 5, length: 24 },
'Sec. 8.4',
true,
'Blessmann, J. Ação do Vento em Estruturas Industriais e Reticulados / NBR 6123:2023 Sec. 8.4',
'Pipe-rack industrial contendo 4 pórticos transversais espaçados de 6m com bandejas de cabos e 2 linhas de tubulação de 600mm de diâmetro. Cálculo do índice de plenitude total (φ), fator de sombreamento (η) e carga de arrasto global.'
));
}
// 2. GALPÃO VARREDURA
const galpaoGeos = [
{ w: 10, l: 20, h: 4 }, { w: 15, l: 30, h: 6 }, { w: 20, l: 50, h: 8 },
-28
View File
@@ -1,28 +0,0 @@
import React from 'react';
import { useGalpaoStore } from '../store/galpaoStore';
import { getWallCpeOfficial as getWallCpe, getRoofCpeOfficial as getRoofCpe } from '../lib/coefficients';
import Heatmap2D from '../components/Heatmap2D';
const Compare: React.FC = () => {
const { length, width, height, roofPitch } = useGalpaoStore();
const wallCpe0 = getWallCpe(length, width, height, 0);
const roofCpe0 = getRoofCpe(length, width, height, roofPitch, 0);
const wallCpe90 = getWallCpe(length, width, height, 90);
const roofCpe90 = getRoofCpe(length, width, height, roofPitch, 90);
return (
<div className="flex flex-col h-full w-full p-4 lg:p-6 bg-muted/30 overflow-auto">
<h1 className="text-2xl font-bold mb-2">Comparação A/B (0° vs 90°)</h1>
<p className="text-muted-foreground mb-6">Compare visualmente a distribuição de pressões (Cpe - Cpi) nas duas direções normativas simultaneamente.</p>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 flex-1 min-h-[600px]">
<Heatmap2D overrideAngle={0} overrideWallCpe={wallCpe0} overrideRoofCpe={roofCpe0} />
<Heatmap2D overrideAngle={90} overrideWallCpe={wallCpe90} overrideRoofCpe={roofCpe90} />
</div>
</div>
);
};
export default Compare;
+19 -12
View File
@@ -1,5 +1,4 @@
import React from 'react';
import { Link } from 'react-router-dom';
import Warehouse3DViewer from '../components/Warehouse3D';
import Heatmap2D from '../components/Heatmap2D';
import LinearLoadsTable from '../components/LinearLoadsTable';
@@ -8,6 +7,7 @@ import FtoolExportCard from '../components/FtoolExportCard';
import { CpiSettingsCard } from '../components/CpiSettingsCard';
import { useGalpaoStore } from '../store/galpaoStore';
import { useWindStore } from '../store/appStore';
import { getWallCpeOfficial as getWallCpe, getRoofCpeOfficial as getRoofCpe } from '../lib/coefficients';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Slider } from '@/components/ui/slider';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
@@ -16,7 +16,7 @@ import { Separator } from '@/components/ui/separator';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button';
import { AlertCircle, Lightbulb, Box, Wind, Columns } from 'lucide-react';
import { AlertCircle, Lightbulb, Box, Wind } from 'lucide-react';
import ExportMenu from '../components/ExportMenu';
import { EducationalManual } from '@/components/EducationalManual';
import { PressureLegend } from '@/components/PressureLegend';
@@ -280,25 +280,17 @@ const GalpaoModule: React.FC = () => {
<TabsList className="h-7 px-1">
<TabsTrigger value="3d" className="text-xs h-6 px-3">Modelo 3D</TabsTrigger>
<TabsTrigger value="heatmap" className="text-xs h-6 px-3">Heatmap 2D</TabsTrigger>
<TabsTrigger value="compare" className="text-xs h-6 px-3">Modo A/B</TabsTrigger>
</TabsList>
</div>
<div className="flex pointer-events-auto ml-1">
<Button asChild size="sm" variant="outline" className="h-7 px-2 text-xs bg-background/80 backdrop-blur-sm border-border shadow-sm">
<Link to="/compare">
<Columns className="w-3.5 h-3.5 mr-1.5" />
Modo A/B
</Link>
</Button>
</div>
<div className="flex flex-wrap gap-2 pointer-events-auto justify-start sm:justify-end ml-auto">
<EducationalManual type="warehouse" params={{ windAngle, cpi }} />
</div>
</div>
<TabsContent value="3d" className="flex-1 m-0 h-full w-full relative">
<div className="absolute top-4 left-4 z-20 flex bg-background/80 backdrop-blur-sm rounded-md p-1 shadow-sm border border-border pointer-events-auto">
<div className="absolute bottom-20 left-4 z-20 flex bg-background/80 backdrop-blur-sm rounded-md p-1 shadow-sm border border-border pointer-events-auto">
<Button
variant={viewMode === 'solid' ? 'secondary' : 'ghost'}
size="sm"
@@ -328,6 +320,21 @@ const GalpaoModule: React.FC = () => {
<TabsContent value="heatmap" className="flex-1 m-0 h-full w-full bg-muted/10 overflow-auto flex items-center justify-center">
<Heatmap2D />
</TabsContent>
<TabsContent value="compare" className="flex-1 m-0 h-full w-full bg-muted/10 overflow-auto p-4 flex flex-col">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 flex-1">
<Heatmap2D
overrideAngle={0}
overrideWallCpe={getWallCpe(length, width, height, 0)}
overrideRoofCpe={getRoofCpe(length, width, height, roofPitch, 0)}
/>
<Heatmap2D
overrideAngle={90}
overrideWallCpe={getWallCpe(length, width, height, 90)}
overrideRoofCpe={getRoofCpe(length, width, height, roofPitch, 90)}
/>
</div>
</TabsContent>
</Tabs>
</div>