diff --git a/frontend/app.js b/frontend/app.js index 482f238..6d62d02 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -1633,7 +1633,7 @@ async function compileModel(model, opts = {}) { const m = result.layout_metrics; setStatus( - `Compiled (${result.errors.length}E, ${result.warnings.length}W | ${m.crossings} crossings, ${m.overlap_edges} overlaps, ${m.total_bends ?? 0} bends, ${(m.detour_ratio ?? 1).toFixed(2)}x detour)` + `Compiled (${result.errors.length}E, ${result.warnings.length}W | ${m.crossings} crossings, ${m.overlap_edges} overlaps, ${m.total_bends ?? 0} bends, ${m.label_tie_routes ?? 0} tie-nets, ${(m.detour_ratio ?? 1).toFixed(2)}x detour)` ); } catch (err) { setStatus(`Compile failed: ${err.message}`, false); @@ -2120,7 +2120,7 @@ async function runLayoutAction(path) { fitView(out.compile.layout); saveSnapshot(); setStatus( - `Compiled (${out.compile.errors.length}E, ${out.compile.warnings.length}W | ${out.compile.layout_metrics.crossings} crossings, ${out.compile.layout_metrics.overlap_edges} overlaps, ${out.compile.layout_metrics.total_bends ?? 0} bends)` + `Compiled (${out.compile.errors.length}E, ${out.compile.warnings.length}W | ${out.compile.layout_metrics.crossings} crossings, ${out.compile.layout_metrics.overlap_edges} overlaps, ${out.compile.layout_metrics.total_bends ?? 0} bends, ${out.compile.layout_metrics.label_tie_routes ?? 0} tie-nets)` ); } catch (err) { setStatus(`Layout action failed: ${err.message}`, false); diff --git a/src/compile.js b/src/compile.js index 140cf0a..a43597a 100644 --- a/src/compile.js +++ b/src/compile.js @@ -260,7 +260,8 @@ export function compile(payload, options = {}) { total_length: 0, direct_length: 0, detour_ratio: 1, - label_tie_fallbacks: 0 + label_tie_fallbacks: 0, + label_tie_routes: 0 }, bus_groups: [], focus_map: {}, diff --git a/src/layout.js b/src/layout.js index 57eef95..649a4b6 100644 --- a/src/layout.js +++ b/src/layout.js @@ -1366,6 +1366,15 @@ function detectBusGroups(nets) { } function shouldUseLabelTie(net, pinNodes, context) { + if (!pinNodes.length) { + return false; + } + const minX = Math.min(...pinNodes.map((p) => p.exit.x), 0); + const maxX = Math.max(...pinNodes.map((p) => p.exit.x), 0); + const minY = Math.min(...pinNodes.map((p) => p.exit.y), 0); + const maxY = Math.max(...pinNodes.map((p) => p.exit.y), 0); + const span = Math.abs(maxX - minX) + Math.abs(maxY - minY); + if (context.renderMode === "explicit") { return LABEL_TIE_CLASSES.has(net.class) && pinNodes.length > 2; } @@ -1378,6 +1387,16 @@ function shouldUseLabelTie(net, pinNodes, context) { return true; } + // Prefer readable schematic stubs for dense multi-node nets. + if (pinNodes.length >= 5) { + return true; + } + + // Long-distributed analog/signal nets become noisy when fully routed. + if ((net.class === "analog" || net.class === "signal") && pinNodes.length >= 3 && span > GRID * 56) { + return true; + } + return false; } @@ -1577,6 +1596,7 @@ function computeLayoutMetrics(routed, busGroups) { (total, rn) => total + (rn.route_stats?.used_label_tie && rn.route_stats?.fallback_reason ? 1 : 0), 0 ); + const labelTieRoutes = routed.reduce((total, rn) => total + (rn.route_stats?.used_label_tie ? 1 : 0), 0); return { segment_count: segmentCount, @@ -1589,7 +1609,8 @@ function computeLayoutMetrics(routed, busGroups) { total_length: totalLength, direct_length: totalDirect, detour_ratio: totalDirect > 0 ? totalLength / totalDirect : 1, - label_tie_fallbacks: labelTieFallbacks + label_tie_fallbacks: labelTieFallbacks, + label_tie_routes: labelTieRoutes }; } diff --git a/tests/compile.test.js b/tests/compile.test.js index 10e73d1..e393292 100644 --- a/tests/compile.test.js +++ b/tests/compile.test.js @@ -15,6 +15,7 @@ test("compile returns svg and topology for valid model", () => { assert.equal(typeof result.layout_metrics.total_bends, "number"); assert.equal(typeof result.layout_metrics.detour_ratio, "number"); assert.equal(typeof result.layout_metrics.label_tie_fallbacks, "number"); + assert.equal(typeof result.layout_metrics.label_tie_routes, "number"); assert.ok(Array.isArray(result.bus_groups)); assert.ok(result.render_mode_used); }); @@ -26,6 +27,7 @@ test("compile fails on invalid model", () => { assert.ok(result.errors.length > 0); assert.equal(result.layout_metrics.total_bends, 0); assert.equal(result.layout_metrics.detour_ratio, 1); + assert.equal(result.layout_metrics.label_tie_routes, 0); }); test("compile accepts render mode options", () => {