techmannih/nema34-mounted-stepper-controller
A 48 V USB‑C PD 3.1 EPR NEMA‑34 stepper controller PCB combining STM32/TMC5160 control, external MOSFET motor bridges, protected power and regenerative braking, USB data, telemetry, diagnostics, status LEDs, and buzzer alarms.
- Version
- 0.3.92
- License
- unset
- Stars
- 1
scripts/make-power-only-dsn.py
#!/usr/bin/env python3
"""Create a power-only Specctra DSN for the first autorouting pass."""
from __future__ import annotations
import argparse
import re
from pathlib import Path
POWER_NETS = {
"PD_VBUS",
"EFUSE_IN",
"VMOTOR",
"MOTOR_A1",
"MOTOR_A2",
"MOTOR_B1",
"MOTOR_B2",
"SENSE_A",
"SENSE_B",
"BRAKE_RETURN",
}
def matching_paren(text: str, start: int) -> int:
depth = 0
quoted = False
escaped = False
for index in range(start, len(text)):
char = text[index]
if quoted:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == '"':
quoted = False
continue
if char == '"':
quoted = True
elif char == "(":
depth += 1
elif char == ")":
depth -= 1
if depth == 0:
return index + 1
raise ValueError(f"unbalanced expression at byte {start}")
def find_form(text: str, name: str) -> tuple[int, int]:
match = re.search(rf"\({re.escape(name)}(?:\s|\))", text)
if not match:
raise ValueError(f"missing ({name} ...) form")
return match.start(), matching_paren(text, match.start())
def indent_block(block: str, spaces: int) -> str:
prefix = " " * spaces
return "\n".join(prefix + line if line else line for line in block.splitlines())
def extract_named_net(network_body: str, net_name: str) -> str:
match = re.search(rf"\(net\s+(?:\"{re.escape(net_name)}\"|{re.escape(net_name)})(?:\s|\))", network_body)
if not match:
raise ValueError(f"power net {net_name} is absent from DSN")
return network_body[match.start() : matching_paren(network_body, match.start())]
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("source", type=Path)
parser.add_argument("destination", type=Path)
args = parser.parse_args()
source = args.source.read_text()
network_start, network_end = find_form(source, "network")
network = source[network_start:network_end]
net_forms = [extract_named_net(network, name) for name in sorted(POWER_NETS)]
power_class_start, power_class_end = find_form(network, "class POWER_2OZ")
power_class = network[power_class_start:power_class_end]
replacement = "(network\n"
replacement += "\n".join(indent_block(form, 4) for form in net_forms)
replacement += "\n" + indent_block(power_class, 4) + "\n )"
source = source[:network_start] + replacement + source[network_end:]
wiring_start, wiring_end = find_form(source, "wiring")
source = source[:wiring_start] + "(wiring\n )" + source[wiring_end:]
args.destination.parent.mkdir(parents=True, exist_ok=True)
args.destination.write_text(source)
print(f"Wrote {args.destination} with {len(net_forms)} power nets and no pre-routes")
if __name__ == "__main__":
main()