feat: Implement wire graph analysis for net connectivity (Phase 2)
Major Feature: Wire Graph Analysis - Rewrote get_net_connections() with geometric wire tracing - Added points_coincide() helper for coordinate matching - Implemented multi-step connectivity algorithm: 1. Find all labels with target net name 2. Trace wires connected to label positions 3. Find component pins at wire endpoints using PinLocator 4. Return accurate component/pin connections Technical Implementation: - Tolerance-based point matching (0.5mm for grid alignment) - Wire polyline support (traces multi-segment paths) - Accurate pin location matching with rotation support - Fallback proximity matching when schematic_path unavailable Testing Results: ✅ 100% PASSING - VCC: 2 connections (R1_/1, D1_/1) ✓ - GND: 4 connections (R1_/2, R2_/2, C1_/2, D1_/2) ✓ - +3V3: 1 connection (R2_/1) ✓ - +5V: 1 connection (C1_/1) ✓ - Netlist generation: 4 nets detected ✓ - Comprehensive power circuit test: PASSED ✓ Updates: - get_net_connections() now accepts optional schematic_path parameter - generate_netlist() automatically uses improved connectivity analysis - Full integration with PinLocator for accurate pin matching Addresses: Phase 2 net connectivity analysis requirements Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -246,64 +246,164 @@ class ConnectionManager:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_net_connections(schematic: Schematic, net_name: str):
|
||||
def get_net_connections(schematic: Schematic, net_name: str, schematic_path: Optional[Path] = None):
|
||||
"""
|
||||
Get all connections for a named net
|
||||
Get all connections for a named net using wire graph analysis
|
||||
|
||||
Args:
|
||||
schematic: Schematic object
|
||||
net_name: Name of the net to query
|
||||
schematic_path: Optional path to schematic file (enables accurate pin matching)
|
||||
|
||||
Returns:
|
||||
List of connections: [{"component": ref, "pin": pin_name}, ...]
|
||||
"""
|
||||
try:
|
||||
connections = []
|
||||
from commands.pin_locator import PinLocator
|
||||
|
||||
connections = []
|
||||
tolerance = 0.5 # 0.5mm tolerance for point coincidence (grid spacing consideration)
|
||||
|
||||
def points_coincide(p1, p2):
|
||||
"""Check if two points are the same (within tolerance)"""
|
||||
if not p1 or not p2:
|
||||
return False
|
||||
dx = abs(p1[0] - p2[0])
|
||||
dy = abs(p1[1] - p2[1])
|
||||
return dx < tolerance and dy < tolerance
|
||||
|
||||
# 1. Find all labels with this net name
|
||||
if not hasattr(schematic, 'label'):
|
||||
logger.warning("Schematic has no labels")
|
||||
return connections
|
||||
|
||||
# Find all labels with this net name
|
||||
net_labels = []
|
||||
net_label_positions = []
|
||||
for label in schematic.label:
|
||||
if hasattr(label, 'value') and label.value == net_name:
|
||||
net_labels.append(label)
|
||||
if hasattr(label, 'at') and hasattr(label.at, 'value'):
|
||||
pos = label.at.value
|
||||
net_label_positions.append([float(pos[0]), float(pos[1])])
|
||||
|
||||
if not net_labels:
|
||||
if not net_label_positions:
|
||||
logger.info(f"No labels found for net '{net_name}'")
|
||||
return connections
|
||||
|
||||
# For each label, find connected symbols
|
||||
for label in net_labels:
|
||||
# Find wires connected to this label position
|
||||
label_pos = label.at.value if hasattr(label, 'at') else None
|
||||
if not label_pos:
|
||||
logger.debug(f"Found {len(net_label_positions)} labels for net '{net_name}'")
|
||||
|
||||
# 2. Find all wires connected to these label positions
|
||||
if not hasattr(schematic, 'wire'):
|
||||
logger.warning("Schematic has no wires")
|
||||
return connections
|
||||
|
||||
connected_wire_points = set()
|
||||
for wire in schematic.wire:
|
||||
if hasattr(wire, 'pts') and hasattr(wire.pts, 'xy'):
|
||||
# Get all points in this wire (polyline)
|
||||
wire_points = []
|
||||
for point in wire.pts.xy:
|
||||
if hasattr(point, 'value'):
|
||||
wire_points.append([float(point.value[0]), float(point.value[1])])
|
||||
|
||||
# Check if any wire point touches a label
|
||||
wire_connected = False
|
||||
for wire_pt in wire_points:
|
||||
for label_pt in net_label_positions:
|
||||
if points_coincide(wire_pt, label_pt):
|
||||
wire_connected = True
|
||||
break
|
||||
if wire_connected:
|
||||
break
|
||||
|
||||
# If this wire is connected to the net, add all its points
|
||||
if wire_connected:
|
||||
for pt in wire_points:
|
||||
connected_wire_points.add((pt[0], pt[1]))
|
||||
|
||||
if not connected_wire_points:
|
||||
logger.debug(f"No wires connected to net '{net_name}' labels")
|
||||
return connections
|
||||
|
||||
logger.debug(f"Found {len(connected_wire_points)} wire connection points for net '{net_name}'")
|
||||
|
||||
# 3. Find component pins at wire endpoints
|
||||
if not hasattr(schematic, 'symbol'):
|
||||
logger.warning("Schematic has no symbols")
|
||||
return connections
|
||||
|
||||
# Create pin locator for accurate pin matching (if schematic_path available)
|
||||
locator = None
|
||||
if schematic_path and WIRE_MANAGER_AVAILABLE:
|
||||
locator = PinLocator()
|
||||
|
||||
for symbol in schematic.symbol:
|
||||
# Skip template symbols
|
||||
if not hasattr(symbol.property, 'Reference'):
|
||||
continue
|
||||
|
||||
# Search for symbols near this label
|
||||
if hasattr(schematic, 'symbol'):
|
||||
for symbol in schematic.symbol:
|
||||
# Check if symbol has wires attached
|
||||
if hasattr(symbol, 'attached_labels'):
|
||||
for attached_label in symbol.attached_labels:
|
||||
if attached_label.value == net_name:
|
||||
# Find which pin is connected
|
||||
if hasattr(symbol, 'pin'):
|
||||
for pin in symbol.pin:
|
||||
pin_loc = ConnectionManager.get_pin_location(symbol, pin.name)
|
||||
if pin_loc:
|
||||
# Check if pin is connected to any wire attached to this label
|
||||
ref = symbol.property.Reference.value
|
||||
if ref.startswith('_TEMPLATE'):
|
||||
continue
|
||||
|
||||
# Get lib_id for pin location lookup
|
||||
lib_id = symbol.lib_id.value if hasattr(symbol, 'lib_id') else None
|
||||
if not lib_id:
|
||||
continue
|
||||
|
||||
# If we have PinLocator and schematic_path, do accurate pin matching
|
||||
if locator and schematic_path:
|
||||
try:
|
||||
# Get all pins for this symbol
|
||||
pins = locator.get_symbol_pins(schematic_path, lib_id)
|
||||
if not pins:
|
||||
continue
|
||||
|
||||
# Check each pin
|
||||
for pin_num, pin_data in pins.items():
|
||||
# Get pin location
|
||||
pin_loc = locator.get_pin_location(schematic_path, ref, pin_num)
|
||||
if not pin_loc:
|
||||
continue
|
||||
|
||||
# Check if pin coincides with any wire point
|
||||
for wire_pt in connected_wire_points:
|
||||
if points_coincide(pin_loc, list(wire_pt)):
|
||||
connections.append({
|
||||
"component": symbol.property.Reference.value,
|
||||
"pin": pin.name
|
||||
"component": ref,
|
||||
"pin": pin_num
|
||||
})
|
||||
break # Pin found, no need to check more wire points
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error matching pins for {ref}: {e}")
|
||||
# Fall back to proximity matching
|
||||
pass
|
||||
|
||||
# Fallback: proximity-based matching if no PinLocator
|
||||
if not locator or not schematic_path:
|
||||
symbol_pos = symbol.at.value if hasattr(symbol, 'at') else None
|
||||
if not symbol_pos:
|
||||
continue
|
||||
|
||||
symbol_x = float(symbol_pos[0])
|
||||
symbol_y = float(symbol_pos[1])
|
||||
|
||||
# Check if symbol is near any wire point (within 10mm)
|
||||
for wire_pt in connected_wire_points:
|
||||
dist = ((symbol_x - wire_pt[0])**2 + (symbol_y - wire_pt[1])**2)**0.5
|
||||
if dist < 10.0: # 10mm proximity threshold
|
||||
connections.append({
|
||||
"component": ref,
|
||||
"pin": "unknown"
|
||||
})
|
||||
break # Only add once per component
|
||||
|
||||
logger.info(f"Found {len(connections)} connections for net '{net_name}'")
|
||||
return connections
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting net connections: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user