/* global React */ /* =========================================================== VOLTLAB · ATERRAMENTO v2.1 (bugfix) Resistência de Aterramento · Método das Resistências Mútuas =========================================================== */ const { useState, useRef, useEffect, useCallback } = React; // ─── Cálculo ───────────────────────────────────────────────────────────────── function resistenciaPropria(rho, L, d) { return (rho / (2 * Math.PI * L)) * Math.log(4 * L / d); } function resistenciaMutua(rho, Lm, b) { if (b <= Lm) return 0; return 0.183 * (rho / Lm) * Math.log10((b + Lm) / (b - Lm)); } function calcularSistema(hastes, rho) { const keys = Object.keys(hastes); const n = keys.length; if (n === 0) return null; const matriz = Array.from({ length: n }, () => Array(n).fill(0)); for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { const hi = hastes[keys[i]], hj = hastes[keys[j]]; if (i === j) { matriz[i][j] = resistenciaPropria(rho, hi.L, hi.D); } else { const e = Math.sqrt((hi.X - hj.X) ** 2 + (hi.Y - hj.Y) ** 2); const b = Math.sqrt(hj.L ** 2 + e ** 2); matriz[i][j] = resistenciaMutua(rho, hj.L, b); } } } const resPorHaste = matriz.map(row => row.reduce((a, v) => a + v, 0)); const somaRecip = resPorHaste.reduce((a, v) => (v > 0 ? a + 1 / v : a), 0); const resTotal = somaRecip > 0 ? 1 / somaRecip : 0; return { matriz, resPorHaste, resTotal, keys }; } // ─── CSV parser ─────────────────────────────────────────────────────────────── function parseCSV(text) { const lines = text.replace(/\r/g, '').split('\n').filter(Boolean); const header = lines[0].split(',').map(h => h.trim().replace(/^\uFEFF/, '')); const rows = lines.slice(1).map(line => { const vals = line.split(','); return Object.fromEntries(header.map((h, i) => [h, (vals[i] || '').trim()])); }); const round = v => parseFloat(v.toFixed(4)); const hastes = {}, linhas = {}, mapaXY = {}; let cH = 1, cL = 1; for (const row of rows) { if (row['Name'] === 'Circle') { const nome = 'haste_' + cH++; const X = round(parseFloat(row['Center X'])); const Y = round(parseFloat(row['Center Y'])); hastes[nome] = { X, Y }; mapaXY[X + ',' + Y] = nome; } if (row['Name'] === 'Line') { const nome = 'cabo_' + cL++; const sx = round(parseFloat(row['Start X'])), sy = round(parseFloat(row['Start Y'])); const ex = round(parseFloat(row['End X'])), ey = round(parseFloat(row['End Y'])); linhas[nome] = { P_INICIAL: [sx, sy], P_FINAL: [ex, ey], COMPRIMENTO: parseFloat(row['Length']) }; } } for (const cabo of Object.values(linhas)) { cabo.HASTE_DE = mapaXY[cabo.P_INICIAL[0] + ',' + cabo.P_INICIAL[1]] || null; cabo.HASTE_PARA = mapaXY[cabo.P_FINAL[0] + ',' + cabo.P_FINAL[1]] || null; } return { hastes, linhas }; } // ─── Classify (FORA do componente) ─────────────────────────────────────────── function classify(r) { if (r <= 1) return { label: 'Excelente', pill: 'ok', col: 'var(--ok)' }; if (r <= 5) return { label: 'Bom', pill: 'ok', col: 'var(--ok)' }; if (r <= 10) return { label: 'Aceitável', pill: 'warn', col: 'var(--warn)' }; return { label: 'Verificar', pill: 'warn', col: 'var(--danger)' }; } // ─── Canvas (FORA do componente) ───────────────────────────────────────────── function GradeCanvas(props) { var hastes = props.hastes, linhas = props.linhas, resPorHaste = props.resPorHaste, keys = props.keys; var canvasRef = useRef(null); useEffect(function() { var canvas = canvasRef.current; if (!canvas) return; var ctx = canvas.getContext('2d'); var W = canvas.width, H = canvas.height; ctx.clearRect(0, 0, W, H); var hasteList = Object.values(hastes); if (!hasteList.length) return; var xs = hasteList.map(function(h) { return h.X; }); var ys = hasteList.map(function(h) { return h.Y; }); var minX = Math.min.apply(null, xs), maxX = Math.max.apply(null, xs); var minY = Math.min.apply(null, ys), maxY = Math.max.apply(null, ys); var pad = 52, rangeX = maxX - minX || 1, rangeY = maxY - minY || 1; var scale = Math.min((W - pad * 2) / rangeX, (H - pad * 2) / rangeY); function toC(x, y) { return [pad + (x - minX) * scale, H - pad - (y - minY) * scale]; } // Grid ctx.strokeStyle = 'rgba(61,139,255,0.07)'; ctx.lineWidth = 1; for (var i = 0; i <= 10; i++) { var cx = pad + (i / 10) * (W - pad * 2); ctx.beginPath(); ctx.moveTo(cx, 0); ctx.lineTo(cx, H); ctx.stroke(); var cy = pad + (i / 10) * (H - pad * 2); ctx.beginPath(); ctx.moveTo(0, cy); ctx.lineTo(W, cy); ctx.stroke(); } // Cabos var linhaVals = Object.values(linhas); for (var li = 0; li < linhaVals.length; li++) { var cabo = linhaVals[li]; var p1 = toC(cabo.P_INICIAL[0], cabo.P_INICIAL[1]); var p2 = toC(cabo.P_FINAL[0], cabo.P_FINAL[1]); ctx.beginPath(); ctx.moveTo(p1[0], p1[1]); ctx.lineTo(p2[0], p2[1]); ctx.strokeStyle = '#f59e0b'; ctx.lineWidth = 2; ctx.setLineDash([6, 3]); ctx.stroke(); ctx.setLineDash([]); } // Hastes var hasteKeys = Object.keys(hastes); var maxRes = resPorHaste ? Math.max.apply(null, resPorHaste) : 1; if (maxRes === 0) maxRes = 1; hasteKeys.forEach(function(nome, idx) { var h = hastes[nome]; var pt = toC(h.X, h.Y); var ccx = pt[0], ccy = pt[1]; var res = resPorHaste ? resPorHaste[idx] : null; if (res !== null && isFinite(res)) { var g = ctx.createRadialGradient(ccx, ccy, 4, ccx, ccy, 22); g.addColorStop(0, 'rgba(61,139,255,' + (0.3 * res / maxRes) + ')'); g.addColorStop(1, 'rgba(61,139,255,0)'); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(ccx, ccy, 22, 0, Math.PI * 2); ctx.fill(); } ctx.fillStyle = '#3d8bff'; ctx.strokeStyle = '#0a0e1a'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(ccx, ccy, 7, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.fillStyle = '#93c5fd'; ctx.font = '600 10px "JetBrains Mono",monospace'; ctx.textAlign = 'center'; ctx.fillText(nome.replace('haste_', '#'), ccx, ccy - 12); if (res !== null && isFinite(res)) { ctx.fillStyle = '#fbbf24'; ctx.font = '9px "JetBrains Mono",monospace'; ctx.fillText(res.toFixed(1) + 'Ω', ccx, ccy + 20); } }); }); return React.createElement('canvas', { ref: canvasRef, width: 580, height: 360, style: { width: '100%', borderRadius: 8, background: 'var(--bg-0)', border: '1px solid var(--border)', display: 'block' } }); } // ─── Helpers UI (FORA do componente) ───────────────────────────────────────── function AtField(props) { return React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } }, React.createElement('label', { style: { fontFamily: 'var(--ff-mono)', fontSize: 10, letterSpacing: '0.12em', color: 'var(--fg-mute)', textTransform: 'uppercase' } }, props.label), props.children ); } function AtInput(props) { var inputStyle = { width: '100%', padding: '8px 12px', background: 'var(--bg-0)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--fg)', fontFamily: 'var(--ff-mono)', fontSize: 13, outline: 'none' }; return React.createElement('input', Object.assign({}, props, { style: inputStyle, onFocus: function(e) { e.target.style.borderColor = 'var(--accent)'; }, onBlur: function(e) { e.target.style.borderColor = 'var(--border)'; }, })); } // ModeBtn FORA do componente — recebe modoManual e setModoManual como props function ModeBtn(props) { var active = (props.mode === 'manual') === props.modoManual; return React.createElement('button', { onClick: function() { props.setModoManual(props.mode === 'manual'); }, style: { padding: '6px 14px', borderRadius: 6, fontFamily: 'var(--ff-mono)', fontSize: 11, letterSpacing: '0.06em', cursor: 'pointer', transition: 'all .15s', border: active ? '1px solid rgba(61,139,255,0.3)' : '1px solid transparent', background: active ? 'var(--accent-soft)' : 'transparent', color: active ? 'var(--accent)' : 'var(--fg-mute)', } }, props.children); } // ─── Componente principal ───────────────────────────────────────────────────── function ModuloAterramento() { var rhoState = useState(100); var rho = rhoState[0], setRho = rhoState[1]; var LState = useState(2.4); var L = LState[0], setL = LState[1]; var DState = useState(0.016); var D = DState[0], setD = DState[1]; var csvState = useState(null); var csvData = csvState[0], setCsvData = csvState[1]; var resultState = useState(null); var resultado = resultState[0], setResultado = resultState[1]; var erroState = useState(''); var erro = erroState[0], setErro = erroState[1]; var arqState = useState(''); var nomeArq = arqState[0], setNomeArq = arqState[1]; var modoState = useState(false); var modoManual = modoState[0], setModoManual = modoState[1]; var hastesState = useState([ { X: 0, Y: 0 }, { X: 2, Y: 0 }, { X: 0, Y: 2 }, { X: 2, Y: 2 } ]); var hastesManual = hastesState[0], setHastesManual = hastesState[1]; var fileRef = useRef(null); var onCSV = useCallback(function(e) { var file = e.target.files[0]; if (!file) return; setNomeArq(file.name); var reader = new FileReader(); reader.onload = function(ev) { try { var p = parseCSV(ev.target.result); if (!Object.keys(p.hastes).length) { setErro('Nenhuma haste (Circle) no CSV.'); setCsvData(null); } else { setErro(''); setCsvData(p); } } catch (ex) { setErro('Erro CSV: ' + ex.message); setCsvData(null); } }; reader.readAsText(file, 'utf-8'); }, []); var calcular = useCallback(function() { setErro(''); try { var hastes = {}, linhas = {}; if (modoManual) { hastesManual.forEach(function(h, i) { hastes['haste_' + (i + 1)] = { X: +h.X || 0, Y: +h.Y || 0, L: +L || 2.4, D: +D || 0.016 }; }); } else { if (!csvData) { setErro('Importe um CSV primeiro.'); return; } Object.entries(csvData.hastes).forEach(function(entry) { hastes[entry[0]] = Object.assign({}, entry[1], { L: +L || 2.4, D: +D || 0.016 }); }); linhas = csvData.linhas; } if (Object.keys(hastes).length < 2) { setErro('Mínimo 2 hastes para o cálculo.'); return; } var res = calcularSistema(hastes, +rho || 100); if (!res) { setErro('Erro interno no cálculo.'); return; } setResultado(Object.assign({}, res, { hastes: hastes, linhas: linhas })); } catch (err) { setErro('Erro no cálculo: ' + err.message); } }, [modoManual, csvData, hastesManual, rho, L, D]); var addHaste = function() { setHastesManual(function(h) { return h.concat([{ X: 0, Y: 0 }]); }); }; var removeHaste = function(i) { setHastesManual(function(h) { return h.filter(function(_, idx) { return idx !== i; }); }); }; var updateHaste = function(i, f, v) { setHastesManual(function(h) { return h.map(function(it, idx) { return idx === i ? Object.assign({}, it, { [f]: v }) : it; }); }); }; var cls = resultado ? classify(resultado.resTotal) : null; // ── Seção de resultado (segura) ────────────────────────────────────────── var resultSection = null; if (resultado && cls) { var n = resultado.keys ? resultado.keys.length : 0; resultSection = (
RESISTÊNCIA TOTAL DO SISTEMA
{isFinite(resultado.resTotal) ? resultado.resTotal.toFixed(3) : '—'} Ω
{cls.label}
Hastes
{n}
ρ Solo
{rho}Ω·m
L haste
{L}m
NBR 5419-3 — Referência R ≤ 1 Ω — SPDA / Proteção contra descargas
R ≤ 10 Ω — Sistemas de distribuição BT
R ≤ 100 Ω — Edificações residenciais
); } return (
{/* ── Parâmetros ──────────────────────────────────────── */}
CONFIGURAÇÃO · MÉTODO DAS RESISTÊNCIAS MÚTUAS

Parâmetros das Hastes

{/* ── Grid entrada + resultado ────────────────────────── */}
{/* Entrada */}
ENTRADA DE DADOS

Posicionamento das Hastes

📂 CSV AutoCAD ✏️ Manual
{!modoManual && (
ARQUIVO CSV — AUTOCAD DATA EXTRACTION
{nomeArq && {nomeArq}} {csvData && ( ✓ {Object.keys(csvData.hastes).length} hastes · {Object.keys(csvData.linhas).length} cabos )}

AutoCAD: Express Tools → Data Extraction · selecionar Circle e Line, campos Center X/Y e Start/End X/Y.

)} {modoManual && (
X (m) Y (m)
{hastesManual.map(function(h, i) { return (
); })}
)} {erro && (
⚠ {erro}
)}
{/* Resultado */}
RESULTADO DO SISTEMA

Resistência total

{resultSection || (

Aguardando cálculo

Configure os parâmetros e clique em Calcular Sistema.

)}
{/* ── Canvas topologia ─────────────────────────────────── */} {resultado && (
VISUALIZAÇÃO · TOPOLOGIA

Disposição das hastes e cabos

Hastes Cabos
)} {/* ── Tabela por haste ─────────────────────────────────── */} {resultado && resultado.keys && resultado.keys.length > 0 && (
ANÁLISE DETALHADA

Resistências por haste

{['HASTE','X (m)','Y (m)','R PRÓPRIA (Ω)','SOMA MÚTUA (Ω)','Σ (Ω)'].map(function(h) { return ( ); })} {resultado.keys.map(function(k, i) { var h = resultado.hastes ? resultado.hastes[k] : null; if (!h) return null; var rP = resultado.matriz && resultado.matriz[i] ? resultado.matriz[i][i] : 0; var tot = resultado.resPorHaste ? resultado.resPorHaste[i] : 0; var isMax = tot === Math.max.apply(null, resultado.resPorHaste); return ( ); })}
{h}
{k} {(h.X || 0).toFixed(4)} {(h.Y || 0).toFixed(4)} {isFinite(rP) ? rP.toFixed(4) : '—'} {isFinite(tot - rP) ? (tot - rP).toFixed(4) : '—'} {isFinite(tot) ? tot.toFixed(4) : '—'}
)} {/* ── Matriz ───────────────────────────────────────────── */} {resultado && resultado.matriz && resultado.keys && (
MATRIZ DE ACOPLAMENTO

Resistências mútuas (Ω)

); })} {resultado.matriz.map(function(row, i) { return ( {row.map(function(val, j) { return ( ); })} ); })}
{resultado.keys.map(function(k) { return ( {k.replace('haste_', '#')}
{resultado.keys[i] ? resultado.keys[i].replace('haste_', '#') : i} {isFinite(val) ? val.toFixed(3) : '—'}
)}
); } window.ModuloAterramento = ModuloAterramento;