fix: net-class API compatibility for KiCad 10 and trackWidth alias (#144)

`create_netclass` previously called legacy NETCLASSES.Find/.Add APIs that
were removed in KiCad 10. NETCLASS getters like GetMicroViaDiameter that
also no longer exist crashed any subsequent edit. The schema accepted
`traceWidth` but the handler only read `trackWidth`, so requests using
the documented field silently produced no-op netclasses.

- Add a KiCad-version-defensive shim around netclass creation that
  prefers the new netclasses_map dict-style API and falls back to legacy.
- Introduce _safe_get/_safe_set helpers so missing getters/setters on
  KiCad 10 NETCLASS objects fail gracefully instead of raising.
- Accept both traceWidth and trackWidth in the request payload.

Net classes still need to be written into .kicad_pro directly because
KiCad 10 stores them in net_settings.classes and the MCP only writes
to .kicad_pcb; that's a separate fix.
This commit is contained in:
Matthew Runo
2026-05-18 11:34:30 -07:00
committed by GitHub
parent b69a4eb88b
commit 53d9b4b567

View File

@@ -48,11 +48,20 @@ class RoutingCommands:
net = pcbnew.NETINFO_ITEM(self.board, name) net = pcbnew.NETINFO_ITEM(self.board, name)
self.board.Add(net) self.board.Add(net)
# Set net class if provided # Set net class if provided — defensive against KiCad 6/7 vs KiCad 9/10 API.
if net_class: if net_class:
net_classes = self.board.GetNetClasses() net_classes = self.board.GetNetClasses()
if net_classes.Find(net_class): resolved = None
net.SetClass(net_classes.Find(net_class)) if hasattr(net_classes, "Find"):
resolved = net_classes.Find(net_class)
else:
try:
if net_class in net_classes:
resolved = net_classes[net_class]
except Exception:
resolved = None
if resolved is not None:
net.SetClass(resolved)
return { return {
"success": True, "success": True,
@@ -1028,7 +1037,8 @@ class RoutingCommands:
name = params.get("name") name = params.get("name")
clearance = params.get("clearance") clearance = params.get("clearance")
track_width = params.get("trackWidth") # Schema exposes "traceWidth"; older callers may send "trackWidth". Accept both.
track_width = params.get("traceWidth", params.get("trackWidth"))
via_diameter = params.get("viaDiameter") via_diameter = params.get("viaDiameter")
via_drill = params.get("viaDrill") via_drill = params.get("viaDrill")
uvia_diameter = params.get("uviaDiameter") uvia_diameter = params.get("uviaDiameter")
@@ -1044,34 +1054,52 @@ class RoutingCommands:
"errorDetails": "name parameter is required", "errorDetails": "name parameter is required",
} }
# Get net classes # Get net classes — KiCad 6/7 returns NETCLASSES with .Find/.Add;
# KiCad 9/10 returns a netclasses_map (SWIG-wrapped std::map) that is dict-like.
net_classes = self.board.GetNetClasses() net_classes = self.board.GetNetClasses()
# Create new net class if it doesn't exist existing = None
if not net_classes.Find(name): if hasattr(net_classes, "Find"):
existing = net_classes.Find(name)
else:
try:
if name in net_classes:
existing = net_classes[name]
except Exception:
existing = None
if existing is None:
netclass = pcbnew.NETCLASS(name) netclass = pcbnew.NETCLASS(name)
if hasattr(net_classes, "Add"):
net_classes.Add(netclass) net_classes.Add(netclass)
else: else:
netclass = net_classes.Find(name) net_classes[name] = netclass
else:
netclass = existing
# Set properties # Set properties
scale = 1000000 # mm to nm scale = 1000000 # mm to nm
if clearance is not None:
netclass.SetClearance(int(clearance * scale)) # Defensive setters — KiCad 10's NETCLASS dropped some legacy mutators.
if track_width is not None: def _safe_set(method_name, value):
netclass.SetTrackWidth(int(track_width * scale)) if value is None:
if via_diameter is not None: return
netclass.SetViaDiameter(int(via_diameter * scale)) method = getattr(netclass, method_name, None)
if via_drill is not None: if method is None:
netclass.SetViaDrill(int(via_drill * scale)) return
if uvia_diameter is not None: try:
netclass.SetMicroViaDiameter(int(uvia_diameter * scale)) method(int(value * scale))
if uvia_drill is not None: except Exception:
netclass.SetMicroViaDrill(int(uvia_drill * scale)) pass
if diff_pair_width is not None:
netclass.SetDiffPairWidth(int(diff_pair_width * scale)) _safe_set("SetClearance", clearance)
if diff_pair_gap is not None: _safe_set("SetTrackWidth", track_width)
netclass.SetDiffPairGap(int(diff_pair_gap * scale)) _safe_set("SetViaDiameter", via_diameter)
_safe_set("SetViaDrill", via_drill)
_safe_set("SetMicroViaDiameter", uvia_diameter)
_safe_set("SetMicroViaDrill", uvia_drill)
_safe_set("SetDiffPairWidth", diff_pair_width)
_safe_set("SetDiffPairGap", diff_pair_gap)
# Add nets to net class # Add nets to net class
netinfo = self.board.GetNetInfo() netinfo = self.board.GetNetInfo()
@@ -1081,19 +1109,29 @@ class RoutingCommands:
net = nets_map[net_name] net = nets_map[net_name]
net.SetClass(netclass) net.SetClass(netclass)
# Defensive accessors — KiCad 10's NETCLASS dropped some legacy getters.
def _safe_get(method_name):
method = getattr(netclass, method_name, None)
if method is None:
return None
try:
return method() / scale
except Exception:
return None
return { return {
"success": True, "success": True,
"message": f"Created net class: {name}", "message": f"Created net class: {name}",
"netClass": { "netClass": {
"name": name, "name": name,
"clearance": netclass.GetClearance() / scale, "clearance": _safe_get("GetClearance"),
"trackWidth": netclass.GetTrackWidth() / scale, "trackWidth": _safe_get("GetTrackWidth"),
"viaDiameter": netclass.GetViaDiameter() / scale, "viaDiameter": _safe_get("GetViaDiameter"),
"viaDrill": netclass.GetViaDrill() / scale, "viaDrill": _safe_get("GetViaDrill"),
"uviaDiameter": netclass.GetMicroViaDiameter() / scale, "uviaDiameter": _safe_get("GetMicroViaDiameter"),
"uviaDrill": netclass.GetMicroViaDrill() / scale, "uviaDrill": _safe_get("GetMicroViaDrill"),
"diffPairWidth": netclass.GetDiffPairWidth() / scale, "diffPairWidth": _safe_get("GetDiffPairWidth"),
"diffPairGap": netclass.GetDiffPairGap() / scale, "diffPairGap": _safe_get("GetDiffPairGap"),
"nets": nets, "nets": nets,
}, },
} }