🚀 Auto-deploy: BrainWind atualizado em 13/07/2026 18:05:59
This commit is contained in:
+3
-3
@@ -14,7 +14,7 @@ import PiperackModule from './pages/PiperackModule';
|
||||
import {
|
||||
Home, Settings, Menu, Cylinder, Church, CircleDot,
|
||||
Square, Layers, BarChart3, Activity, Warehouse,
|
||||
Settings2, Sun, Moon,
|
||||
Settings2, Sun, Moon, Frame,
|
||||
} from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -84,7 +84,7 @@ function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
{ path: '/barras', icon: <BarChart3 className="w-5 h-5" />, labelKey: 'nav_bar' as const },
|
||||
{ path: '/pontes', icon: <BridgeIcon className="w-5 h-5" />, labelKey: 'nav_bridge' as const },
|
||||
{ path: '/torre', icon: <TowerIcon className="w-5 h-5" />, labelKey: 'nav_tower' as const },
|
||||
{ path: '/piperack', icon: <Layers className="w-5 h-5" />, labelKey: 'nav_piperack' as const },
|
||||
{ path: '/piperack', icon: <Frame className="w-5 h-5" />, labelKey: 'nav_piperack' as const },
|
||||
{ path: '/dinamica', icon: <Activity className="w-5 h-5" />, labelKey: 'nav_dynamics' as const },
|
||||
];
|
||||
|
||||
@@ -278,7 +278,7 @@ function HomeMock() {
|
||||
{ to: '/barras', icon: BarChart3, label: 'Barras Prismáticas', desc: 'Faces planas (Tab. 26) ou circulares (Tab. 27).' },
|
||||
{ to: '/pontes', icon: BridgeIcon, label: 'Pontes', desc: 'Pse, Cx/Cz do tabuleiro, flutter, galope (sec. 11).' },
|
||||
{ to: '/torre', icon: TowerIcon, label: 'Torres', desc: 'Torres treliçadas e mastros (sec. 8.4).' },
|
||||
{ to: '/piperack', icon: Layers, label: 'Pipe-Racks Industriais', desc: 'Estruturas reticuladas múltiplas, tubulações, fator η (Tab. 28).' },
|
||||
{ to: '/piperack', icon: Frame, label: 'Pipe-Racks Industriais', desc: 'Estruturas reticuladas múltiplas, tubulações, fator η (Tab. 28).' },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -23,6 +23,24 @@ const styles = StyleSheet.create({
|
||||
sceneCaption: { fontSize: 8, color: '#666', fontStyle: 'italic', textAlign: 'center', marginBottom: 8 },
|
||||
});
|
||||
|
||||
export function appendKgf(val: string | number): string {
|
||||
if (typeof val === 'number') return String(val);
|
||||
return val.replace(/(-?\d+(?:[.,]\d+)?)\s*(kN(?:\/m²|\/m2|\/m|·m|\.m|m)?)/g, (match, numStr, unit) => {
|
||||
const num = parseFloat(numStr.replace(',', '.'));
|
||||
if (isNaN(num)) return match;
|
||||
const kgf = num * 101.9716;
|
||||
|
||||
let kgfUnit = 'kgf';
|
||||
if (unit.includes('/m²') || unit.includes('/m2')) kgfUnit = 'kgf/m²';
|
||||
else if (unit.includes('/m')) kgfUnit = 'kgf/m';
|
||||
else if (unit.includes('·m') || unit.includes('.m') || unit === 'kNm') kgfUnit = 'kgf·m';
|
||||
|
||||
const kgfStr = kgf.toFixed(1);
|
||||
const kgfFormatted = numStr.includes(',') ? kgfStr.replace('.', ',') : kgfStr;
|
||||
return `${match} (${kgfFormatted} ${kgfUnit})`;
|
||||
});
|
||||
}
|
||||
|
||||
export interface GenericPDFSection {
|
||||
title: string;
|
||||
type: 'table' | 'text' | 'grid';
|
||||
@@ -80,7 +98,7 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener
|
||||
q = 0,613 × (Vₖ)²
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', color: '#4b5563' }}>
|
||||
q = 0,613 × ({wind.vk.toFixed(2)})² = {(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²
|
||||
{appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -102,14 +120,14 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener
|
||||
</Text>
|
||||
|
||||
{sec.type === 'text' && sec.content && (
|
||||
<Text style={styles.text}>{sec.content}</Text>
|
||||
<Text style={styles.text}>{appendKgf(sec.content)}</Text>
|
||||
)}
|
||||
|
||||
{sec.type === 'grid' && sec.gridItems && (
|
||||
sec.gridItems.map((item, i) => (
|
||||
<View style={styles.row} key={i}>
|
||||
<Text style={styles.label}>{item.label}:</Text>
|
||||
<Text style={styles.value}>{item.value}</Text>
|
||||
<Text style={styles.value}>{appendKgf(item.value)}</Text>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
@@ -127,7 +145,7 @@ const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: Gener
|
||||
<View style={styles.tableRow} key={rIdx}>
|
||||
{tr.map((tc, cIdx) => (
|
||||
<Text key={cIdx} style={cIdx === 0 ? styles.tableCellFirst : styles.tableCell}>
|
||||
{tc}
|
||||
{appendKgf(tc)}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
|
||||
+12
-11
@@ -9,6 +9,7 @@ import {
|
||||
getDragForce,
|
||||
} from './line-loads';
|
||||
import { getWallCpeOfficial, getRoofCpeOfficial } from './coefficients';
|
||||
import { appendKgf } from './export-generic-pdf';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
@@ -138,7 +139,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
q = 0,613 × (Vₖ)²
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', color: '#4b5563' }}>
|
||||
q = 0,613 × ({wind.vk.toFixed(2)})² = {(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²
|
||||
{appendKgf(`q = 0,613 × (${wind.vk.toFixed(2)})² = ${(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²`)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -201,14 +202,14 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
<Text style={styles.tableCellFirst}>Elemento / Região</Text>
|
||||
<Text style={styles.tableCell}>Cpe</Text>
|
||||
<Text style={styles.tableCell}>Cpi</Text>
|
||||
<Text style={styles.tableCell}>p [kN/m²]</Text>
|
||||
<Text style={styles.tableCell}>p [kN/m² (kgf/m²)]</Text>
|
||||
</View>
|
||||
{Object.entries(wCpe).map(([face, cpeVal]) => (
|
||||
<View style={styles.tableRow} key={`wall-${face}`}>
|
||||
<Text style={styles.tableCellFirst}>Parede — {face}</Text>
|
||||
<Text style={styles.tableCell}>{(cpeVal as number).toFixed(2)}</Text>
|
||||
<Text style={styles.tableCell}>{cpi}</Text>
|
||||
<Text style={styles.tableCell}>{pressure(cpeVal as number)}</Text>
|
||||
<Text style={styles.tableCell}>{pressure(cpeVal as number)} ({(parseFloat(pressure(cpeVal as number)) * 101.9716).toFixed(1)})</Text>
|
||||
</View>
|
||||
))}
|
||||
{Object.entries(rCpe).map(([face, cpeVal]) => (
|
||||
@@ -216,7 +217,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
<Text style={styles.tableCellFirst}>Telhado — {face}</Text>
|
||||
<Text style={styles.tableCell}>{(cpeVal as number).toFixed(2)}</Text>
|
||||
<Text style={styles.tableCell}>{cpi}</Text>
|
||||
<Text style={styles.tableCell}>{pressure(cpeVal as number)}</Text>
|
||||
<Text style={styles.tableCell}>{pressure(cpeVal as number)} ({(parseFloat(pressure(cpeVal as number)) * 101.9716).toFixed(1)})</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
@@ -224,7 +225,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
{secBase + 1}. Cargas Lineares (kN/m)
|
||||
{secBase + 1}. Cargas Lineares (kN/m (kgf/m))
|
||||
</Text>
|
||||
<Text style={{ fontSize: 9, marginBottom: 6 }}>
|
||||
Pórticos: {FRAME_SPACING} m | Terças: {PURLIN_SPACING} m
|
||||
@@ -234,7 +235,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
<View style={[styles.tableRow, styles.tableHeader]}>
|
||||
<Text style={styles.tableCellFirst}>Pilar</Text>
|
||||
<Text style={styles.tableCell}>Cpe</Text>
|
||||
<Text style={styles.tableCell}>w [kN/m]</Text>
|
||||
<Text style={styles.tableCell}>w [kN/m (kgf/m)]</Text>
|
||||
</View>
|
||||
{([
|
||||
['Barlavento', angle === 0 ? wCpe.C : wCpe.A, colLoads.windward],
|
||||
@@ -245,7 +246,7 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
<View style={styles.tableRow} key={`col-${label}`}>
|
||||
<Text style={styles.tableCellFirst}>{label}</Text>
|
||||
<Text style={styles.tableCell}>{cpeVal.toFixed(2)}</Text>
|
||||
<Text style={styles.tableCell}>{fmtSigned(w)}</Text>
|
||||
<Text style={styles.tableCell}>{fmtSigned(w)} ({fmtSigned(w * 101.9716, 1)})</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
@@ -254,20 +255,20 @@ const ReportDocument = ({ galpao, wind, sceneImage }: ReportProps) => {
|
||||
<View style={[styles.tableRow, styles.tableHeader]}>
|
||||
<Text style={styles.tableCellFirst}>Terça (Zona)</Text>
|
||||
<Text style={styles.tableCell}>Cpe</Text>
|
||||
<Text style={styles.tableCell}>w [kN/m]</Text>
|
||||
<Text style={styles.tableCell}>w [kN/m (kgf/m)]</Text>
|
||||
</View>
|
||||
{(['E', 'F', 'G', 'H', 'I', 'J'] as const).map((z) => (
|
||||
<View style={styles.tableRow} key={`roof-${z}`}>
|
||||
<Text style={styles.tableCellFirst}>{z}</Text>
|
||||
<Text style={styles.tableCell}>{rCpe[z].toFixed(2)}</Text>
|
||||
<Text style={styles.tableCell}>{fmtSigned(rLoads[z])}</Text>
|
||||
<Text style={styles.tableCell}>{fmtSigned(rLoads[z])} ({fmtSigned(rLoads[z] * 101.9716, 1)})</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View style={{ marginTop: 6, fontSize: 9 }}>
|
||||
<Text>Reação global na base: <Text style={{ fontWeight: 'bold' }}>{fmtSigned(rxns.total, 3)} kN</Text></Text>
|
||||
<Text>Força de arrasto global (Cₐ): <Text style={{ fontWeight: 'bold' }}>{dForce.forceKN.toFixed(3)} kN</Text></Text>
|
||||
<Text>Reação global na base: <Text style={{ fontWeight: 'bold' }}>{fmtSigned(rxns.total, 3)} kN ({fmtSigned(rxns.total * 101.9716, 1)} kgf)</Text></Text>
|
||||
<Text>Força de arrasto global (Cₐ): <Text style={{ fontWeight: 'bold' }}>{dForce.forceKN.toFixed(3)} kN ({(dForce.forceKN * 101.9716).toFixed(1)} kgf)</Text></Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -58,6 +58,7 @@ const translations: Dict = {
|
||||
nav_bar: { 'pt-BR': 'Barras', 'en-US': 'Bars' },
|
||||
nav_bridge: { 'pt-BR': 'Pontes', 'en-US': 'Bridges' },
|
||||
nav_tower: { 'pt-BR': 'Torres', 'en-US': 'Towers' },
|
||||
nav_piperack: { 'pt-BR': 'Pipe-rack', 'en-US': 'Pipe-rack' },
|
||||
nav_dynamics: { 'pt-BR': 'Dinâmica + Vórtices', 'en-US': 'Dynamics + Vortex' },
|
||||
nav_settings: { 'pt-BR': 'Preferências', 'en-US': 'Preferences' },
|
||||
nav_collapse: { 'pt-BR': 'Recolher sidebar', 'en-US': 'Collapse sidebar' },
|
||||
|
||||
@@ -15,12 +15,12 @@ import { Input } from '@/components/ui/input';
|
||||
|
||||
const PiperackModule: React.FC = () => {
|
||||
const { q } = useWindStore();
|
||||
const [width, setWidth] = useState(6);
|
||||
const [height, setHeight] = useState(2.5);
|
||||
const [elevation, setElevation] = useState(8);
|
||||
const [spacing, setSpacing] = useState(3);
|
||||
const [numFrames, setNumFrames] = useState(3);
|
||||
const [phiStruct, setPhiStruct] = useState(0.2);
|
||||
const [width, setWidth] = useState(17);
|
||||
const [height, setHeight] = useState(2);
|
||||
const [elevation, setElevation] = useState(6);
|
||||
const [spacing, setSpacing] = useState(2);
|
||||
const [numFrames, setNumFrames] = useState(2);
|
||||
const [phiStruct, setPhiStruct] = useState(0.5);
|
||||
const [pipes, setPipes] = useState<PipeDef[]>([]);
|
||||
|
||||
const addPipe = () => {
|
||||
@@ -59,7 +59,7 @@ const PiperackModule: React.FC = () => {
|
||||
{ label: 'Elevação média (z)', value: `${elevation} m` },
|
||||
{ label: 'Espaçamento (e)', value: `${spacing} m` },
|
||||
{ label: 'Número de pórticos (n)', value: String(numFrames) },
|
||||
{ label: 'φ (estrutural)', value: phiStruct.toFixed(2) },
|
||||
{ label: 'Solidez estrutural', value: phiStruct.toFixed(2) },
|
||||
{ label: 'Quantidade de Tubos', value: String(pipes.length) },
|
||||
],
|
||||
},
|
||||
@@ -67,9 +67,9 @@ const PiperackModule: React.FC = () => {
|
||||
title: 'Coeficientes Aerodinâmicos',
|
||||
type: 'grid',
|
||||
gridItems: [
|
||||
{ label: 'φ total (estrutura + tubos)', value: result.phiTotal.toFixed(2) },
|
||||
{ label: 'Solidez total (est. + tubos)', value: result.phiTotal.toFixed(2) },
|
||||
{ label: 'Ca frontal', value: result.caFrontal.toFixed(2) },
|
||||
{ label: 'Fator proteção η', value: result.eta.toFixed(2) },
|
||||
{ label: 'Fator de proteção', value: result.eta.toFixed(2) },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -80,6 +80,18 @@ const PiperackModule: React.FC = () => {
|
||||
{ label: 'Carga Linear Pórtico Traseiros (cada)', value: `${result.linearLoadBack.toFixed(2)} kN/m` },
|
||||
{ label: 'Força Global Total', value: `${result.globalForce.toFixed(2)} kN` },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Reações Simplificadas na Base (Binário de Tombamento)',
|
||||
type: 'grid',
|
||||
gridItems: [
|
||||
{ label: 'Pórtico Frontal - Momento (M)', value: `${(result.linearLoadFront * width * elevation).toFixed(2)} kN·m` },
|
||||
{ label: 'Pórtico Frontal - C1 (Tração) / C2 (Compressão)', value: `±${((result.linearLoadFront * width * elevation) / width).toFixed(2)} kN` },
|
||||
...(numFrames > 1 ? [
|
||||
{ label: 'Pórtico Traseiro - Momento (M)', value: `${(result.linearLoadBack * width * elevation).toFixed(2)} kN·m` },
|
||||
{ label: 'Pórtico Traseiro - C1 (Tração) / C2 (Compressão)', value: `±${((result.linearLoadBack * width * elevation) / width).toFixed(2)} kN` },
|
||||
] : [])
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
@@ -116,7 +128,6 @@ const PiperackModule: React.FC = () => {
|
||||
phiTotal={result.phiTotal}
|
||||
eta={result.eta}
|
||||
/>
|
||||
<SceneCapturePanel />
|
||||
</div>
|
||||
|
||||
{/* Painel Direito: Controles */}
|
||||
@@ -146,10 +157,64 @@ const PiperackModule: React.FC = () => {
|
||||
<span className="text-muted-foreground text-primary font-medium">Carga Pórtico 1</span>
|
||||
<span className="font-mono font-bold text-primary">{result.linearLoadFront.toFixed(2)} kN/m</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex justify-between items-center pb-4 border-b">
|
||||
<span className="text-muted-foreground text-primary font-medium">Carga Pórticos {numFrames > 1 ? `2 a ${numFrames}` : '-'}</span>
|
||||
<span className="font-mono font-bold text-primary">{result.linearLoadBack.toFixed(2)} kN/m</span>
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
<h4 className="font-semibold text-sm mb-3">Reações Simplificadas nas Bases</h4>
|
||||
<div className="bg-muted/30 p-3 rounded-md border text-xs space-y-4">
|
||||
|
||||
<svg width="100%" height="120" viewBox="0 0 300 120" className="bg-white rounded border">
|
||||
<line x1="20" y1="100" x2="280" y2="100" stroke="#cbd5e1" strokeWidth="2" />
|
||||
|
||||
<rect x="80" y="30" width="8" height="70" fill="#64748b" />
|
||||
<text x="84" y="115" textAnchor="middle" fontSize="10" fill="#475569">C1 (Barlavento)</text>
|
||||
<line x1="70" y1="100" x2="70" y2="70" stroke="#ef4444" strokeWidth="2" markerEnd="url(#arrowUp)" />
|
||||
<text x="65" y="85" textAnchor="end" fill="#ef4444" fontSize="10">Tração</text>
|
||||
|
||||
<rect x="212" y="30" width="8" height="70" fill="#64748b" />
|
||||
<text x="216" y="115" textAnchor="middle" fontSize="10" fill="#475569">C2 (Sotavento)</text>
|
||||
<line x1="225" y1="70" x2="225" y2="100" stroke="#3b82f6" strokeWidth="2" markerEnd="url(#arrowDown)" />
|
||||
<text x="230" y="85" textAnchor="start" fill="#3b82f6" fontSize="10">Comp.</text>
|
||||
|
||||
<rect x="76" y="30" width="148" height="10" fill="#64748b" />
|
||||
|
||||
<line x1="20" y1="35" x2="70" y2="35" stroke="#10b981" strokeWidth="2" markerEnd="url(#arrowRight)" />
|
||||
<text x="45" y="30" textAnchor="middle" fill="#10b981" fontSize="10">Vento (F)</text>
|
||||
|
||||
<defs>
|
||||
<marker id="arrowUp" markerWidth="6" markerHeight="6" refX="3" refY="0" orient="auto">
|
||||
<path d="M0,6 L3,0 L6,6" fill="none" stroke="#ef4444" strokeWidth="1.5" />
|
||||
</marker>
|
||||
<marker id="arrowDown" markerWidth="6" markerHeight="6" refX="3" refY="6" orient="auto">
|
||||
<path d="M0,0 L3,6 L6,0" fill="none" stroke="#3b82f6" strokeWidth="1.5" />
|
||||
</marker>
|
||||
<marker id="arrowRight" markerWidth="6" markerHeight="6" refX="6" refY="3" orient="auto">
|
||||
<path d="M0,0 L6,3 L0,6" fill="none" stroke="#10b981" strokeWidth="1.5" />
|
||||
</marker>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-1">
|
||||
<div className="font-bold text-primary">Pórtico Frontal</div>
|
||||
<div className="flex justify-between text-muted-foreground"><span>F (Transv.):</span> <span className="font-mono">{(result.linearLoadFront * width).toFixed(2)} kN</span></div>
|
||||
<div className="flex justify-between text-muted-foreground"><span>Momento:</span> <span className="font-mono">{(result.linearLoadFront * width * elevation).toFixed(2)} kN·m</span></div>
|
||||
<div className="flex justify-between text-muted-foreground"><span>Reação (C1/C2):</span> <span className="font-mono">±{((result.linearLoadFront * width * elevation) / width).toFixed(2)} kN</span></div>
|
||||
</div>
|
||||
{numFrames > 1 && (
|
||||
<div className="space-y-1">
|
||||
<div className="font-bold text-primary">Pórticos Traseiros</div>
|
||||
<div className="flex justify-between text-muted-foreground"><span>F (Transv.):</span> <span className="font-mono">{(result.linearLoadBack * width).toFixed(2)} kN</span></div>
|
||||
<div className="flex justify-between text-muted-foreground"><span>Momento:</span> <span className="font-mono">{(result.linearLoadBack * width * elevation).toFixed(2)} kN·m</span></div>
|
||||
<div className="flex justify-between text-muted-foreground"><span>Reação (C1/C2):</span> <span className="font-mono">±{((result.linearLoadBack * width * elevation) / width).toFixed(2)} kN</span></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -249,6 +314,8 @@ const PiperackModule: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SceneCapturePanel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user