Fix PNP wire-through-body routing and inductor coil fill

The is_inverted flag incorrectly reversed stub wire directions for
PNP/PFET devices, causing wires to route through the transistor body
instead of away from it.  SchemDraw's BjtPnp already orients the
emitter (supply) terminal at the top, so stubs should always go UP
for supply and DOWN for ground regardless of polarity.

- Remove is_inverted direction flipping from _path_style and stubs
- Use output_term (always collector/drain) for output path detection
  instead of supply_term which varies by polarity
- Add .fill(_GRID_BG) to Inductor2 elements so the coil body has a
  solid background fill, visually breaking the through-wire
This commit is contained in:
Ryan Malloy 2026-02-24 13:28:59 -07:00
parent faebe1cee4
commit 8c66b10448

View File

@ -531,8 +531,7 @@ def _path_style(
term_name: str, term_name: str,
path: TerminalPath, path: TerminalPath,
has_signal: bool, has_signal: bool,
is_inverted: bool, output_term: str,
supply_term: str,
input_term: str, input_term: str,
) -> str: ) -> str:
"""Classify a path's drawing style: up, down, input, input_up, or output. """Classify a path's drawing style: up, down, input, input_up, or output.
@ -540,18 +539,25 @@ def _path_style(
``input_up`` routes input-terminal bias paths (base/gate VCC) left then ``input_up`` routes input-terminal bias paths (base/gate VCC) left then
up with a local Vdd symbol, keeping them on the input side of the up with a local Vdd symbol, keeping them on the input side of the
schematic and avoiding wire crossings with collector/drain vertical paths. schematic and avoiding wire crossings with collector/drain vertical paths.
Direction classification is polarity-independent: supply up, ground
down. SchemDraw's BJT/FET symbols already orient supply terminals at the
top and ground terminals at the bottom for both N- and P-type devices.
``output_term`` is always collector (BJT) or drain (FET), regardless of
polarity the output coupling path originates from this terminal.
""" """
if path.end_type == "supply": if path.end_type == "supply":
if term_name == input_term: if term_name == input_term:
return "input_up" return "input_up"
return "up" if not is_inverted else "down" return "up"
if term_name == input_term and has_signal and len(path.components) > 1: if term_name == input_term and has_signal and len(path.components) > 1:
return "input" return "input"
if term_name == supply_term and path.end_type == "ground" and len(path.components) > 1: if term_name == output_term and path.end_type == "ground" and len(path.components) > 1:
return "output" return "output"
if path.end_type == "ground": if path.end_type == "ground":
return "down" if not is_inverted else "up" return "down"
return "down" if not is_inverted else "up" return "down"
# ── Value Formatting ─────────────────────────────────────────── # ── Value Formatting ───────────────────────────────────────────
@ -606,7 +612,7 @@ def _get_element(comp: SpiceComponent, models: dict[str, str]):
elif prefix == "C": elif prefix == "C":
return elm.Capacitor() return elm.Capacitor()
elif prefix == "L": elif prefix == "L":
return elm.Inductor2() return elm.Inductor2().fill(_GRID_BG)
elif prefix == "V": elif prefix == "V":
return elm.SourceV() return elm.SourceV()
elif prefix == "I": elif prefix == "I":
@ -1296,6 +1302,7 @@ def _render_connected(parsed: ParsedNetlist, layout: ActiveLayout) -> str:
"emitter": q.emitter, "emitter": q.emitter,
} }
supply_term = "emitter" if is_inverted else "collector" supply_term = "emitter" if is_inverted else "collector"
output_term = "collector"
input_term = "base" input_term = "base"
else: else:
dev_elem = elm.PFet() if is_inverted else elm.NFet() dev_elem = elm.PFet() if is_inverted else elm.NFet()
@ -1309,6 +1316,7 @@ def _render_connected(parsed: ParsedNetlist, layout: ActiveLayout) -> str:
"source": q.source, "source": q.source,
} }
supply_term = "source" if is_inverted else "drain" supply_term = "source" if is_inverted else "drain"
output_term = "drain"
input_term = "gate" input_term = "gate"
for term_name, term_paths in layout.paths.items(): for term_name, term_paths in layout.paths.items():
@ -1326,7 +1334,7 @@ def _render_connected(parsed: ParsedNetlist, layout: ActiveLayout) -> str:
for p in term_paths: for p in term_paths:
has_sig = any(c.name in signal_names for c in p.components) has_sig = any(c.name in signal_names for c in p.components)
style = _path_style( style = _path_style(
term_name, p, has_sig, is_inverted, supply_term, input_term term_name, p, has_sig, output_term, input_term
) )
if style == "up": if style == "up":
up_paths.append(p) up_paths.append(p)
@ -1356,21 +1364,15 @@ def _render_connected(parsed: ParsedNetlist, layout: ActiveLayout) -> str:
junc = d.add(elm.Line().at(anchor).left(_INPUT_JUNCTION_LENGTH)) junc = d.add(elm.Line().at(anchor).left(_INPUT_JUNCTION_LENGTH))
draw_from = junc.end draw_from = junc.end
elif term_name == supply_term and (up_paths or down_paths or output_paths): elif term_name == supply_term and (up_paths or down_paths or output_paths):
# Supply terminal stub: direction matches polarity. # Supply terminal stub: always route UP (away from body toward VCC).
# NPN/NFET collector→up; PNP/PFET emitter→down. # SchemDraw's BjtPnp/PFet already orient the supply terminal at
# Longer when output paths also branch so labels clear the wire. # the top of the symbol, so UP moves away from the body for both
# NPN and PNP. Longer when output paths also branch.
stub = _LEAD_STUB_LENGTH * (1.5 if output_paths else 1.0) stub = _LEAD_STUB_LENGTH * (1.5 if output_paths else 1.0)
if is_inverted:
lead = d.add(elm.Line().at(anchor).down().length(stub))
else:
lead = d.add(elm.Line().at(anchor).up().length(stub)) lead = d.add(elm.Line().at(anchor).up().length(stub))
draw_from = lead.end draw_from = lead.end
elif term_name not in (supply_term, input_term) and (up_paths or down_paths): elif term_name not in (supply_term, input_term) and (up_paths or down_paths):
# Ground terminal stub: opposite direction from supply. # Ground terminal stub: always route DOWN (away from body toward GND).
# NPN/NFET emitter→down; PNP/PFET collector→up.
if is_inverted:
lead = d.add(elm.Line().at(anchor).up().length(_LEAD_STUB_LENGTH))
else:
lead = d.add(elm.Line().at(anchor).down().length(_LEAD_STUB_LENGTH)) lead = d.add(elm.Line().at(anchor).down().length(_LEAD_STUB_LENGTH))
draw_from = lead.end draw_from = lead.end
else: else: