mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 05:30:46 +08:00
Trim public tools to 14, expose only kept layout actions/recipes, merge Fabric queries, drop createTopologyView/listTopologyViews, and align skill/docs with the live catalog. Co-authored-by: Cursor <cursoragent@cursor.com>
54 lines
1.9 KiB
Python
54 lines
1.9 KiB
Python
"""Untangle must not fling nodes across the canvas."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
from netx_topology_mcp.layout_ops import build_state_from_nodes_edges
|
|
from netx_topology_mcp.layout_ops.untangle import _MAX_JUMP, _candidates, untangle_crossings
|
|
|
|
|
|
def test_candidates_stay_near_node() -> None:
|
|
pos = {
|
|
"a": (0.0, 0.0),
|
|
"b": (5000.0, 0.0), # far neighbor — old code used dx*3.5 → 17k jump
|
|
"c": (2500.0, 800.0),
|
|
}
|
|
adj = {"c": {"a", "b"}, "a": {"c"}, "b": {"c"}}
|
|
import random
|
|
|
|
cands = _candidates(pos, "c", adj, random.Random(1))
|
|
assert cands
|
|
x0, y0 = pos["c"]
|
|
for x, y in cands:
|
|
assert math.hypot(x - x0, y - y0) <= _MAX_JUMP + 1e-6
|
|
|
|
|
|
def test_untangle_preserves_bbox_order() -> None:
|
|
# Long chain with one intentional crossing-ish layout; moves stay local.
|
|
nodes = []
|
|
edges = []
|
|
for i in range(8):
|
|
nodes.append(
|
|
{
|
|
"fabric_node_id": f"n{i}",
|
|
"name": f"N{i}-EN-1",
|
|
"x": float(i * 200),
|
|
"y": 0.0 if i % 2 == 0 else 40.0,
|
|
}
|
|
)
|
|
if i:
|
|
edges.append({"a_node_id": f"n{i-1}", "b_node_id": f"n{i}"})
|
|
# add a long chord that crosses nothing badly but gives degree
|
|
edges.append({"a_node_id": "n0", "b_node_id": "n7"})
|
|
st = build_state_from_nodes_edges(nodes, edges)
|
|
before = dict(st.positions)
|
|
xs0 = [p[0] for p in before.values()]
|
|
ys0 = [p[1] for p in before.values()]
|
|
span0 = max(max(xs0) - min(xs0), max(ys0) - min(ys0))
|
|
out = untangle_crossings(st, max_rounds=40, max_degree=7, target_crossings=0)
|
|
xs1 = [p[0] for p in out.state.positions.values()]
|
|
ys1 = [p[1] for p in out.state.positions.values()]
|
|
span1 = max(max(xs1) - min(xs1), max(ys1) - min(ys1))
|
|
# Must not explode bbox by >2x
|
|
assert span1 <= span0 * 2.0 + 800.0
|