Controller board: ESP32 + 2x StepStick carrier (v0.4), FluidNC config
- KiCad project generated by pcb/tools (design, route, schematic, fab outputs). - v0.4 fixes the v0.3 review findings: low-dropout 3.3 V regulator, OR'd 5 V supply, polyfuse, USB ESD protection, input clamps, ceramic decoupling at the drivers, A4988/DRV8825 jumper, ground stitching. DRC/ERC clean. - v0.3 (as ordered) kept in pcb/archive/v0.3. - FluidNC config.yaml for the board; rho hard limit on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c498882bbb
commit
1400cc8afd
46 files changed
+226470
No files matched your search
@@ -0,0 +1,100 @@
|
||||
"""Tiny S-expression reader/writer for KiCad files.
|
||||
|
||||
Lists are Python lists; atoms are str. Quoted strings are kept as QStr so they
|
||||
round-trip with quotes. Numbers stay as plain str atoms (KiCad cares about text).
|
||||
"""
|
||||
|
||||
|
||||
class QStr(str):
|
||||
"""A string that was quoted in the source (and must be written quoted)."""
|
||||
|
||||
|
||||
def parse(text):
|
||||
pos = 0
|
||||
n = len(text)
|
||||
stack = [[]]
|
||||
while pos < n:
|
||||
c = text[pos]
|
||||
if c in ' \t\r\n':
|
||||
pos += 1
|
||||
elif c == '(':
|
||||
stack.append([])
|
||||
pos += 1
|
||||
elif c == ')':
|
||||
done = stack.pop()
|
||||
stack[-1].append(done)
|
||||
pos += 1
|
||||
elif c == '"':
|
||||
pos += 1
|
||||
buf = []
|
||||
while True:
|
||||
ch = text[pos]
|
||||
if ch == '\\':
|
||||
buf.append(text[pos:pos + 2])
|
||||
pos += 2
|
||||
continue
|
||||
if ch == '"':
|
||||
pos += 1
|
||||
break
|
||||
buf.append(ch)
|
||||
pos += 1
|
||||
raw = ''.join(buf)
|
||||
# keep escapes as-is (we re-emit them unchanged)
|
||||
stack[-1].append(QStr(raw))
|
||||
else:
|
||||
start = pos
|
||||
while pos < n and text[pos] not in ' \t\r\n()"':
|
||||
pos += 1
|
||||
stack[-1].append(text[start:pos])
|
||||
assert len(stack) == 1
|
||||
return stack[0][0] if len(stack[0]) == 1 else stack[0]
|
||||
|
||||
|
||||
def dumps(node, indent=0):
|
||||
"""Serialize back to KiCad-style text (one sub-list per line when nested)."""
|
||||
if isinstance(node, QStr):
|
||||
return '"' + str(node) + '"'
|
||||
if isinstance(node, str):
|
||||
return node
|
||||
if isinstance(node, (int, float)):
|
||||
return fmt(node)
|
||||
# list
|
||||
simple = all(not isinstance(x, list) for x in node)
|
||||
if simple:
|
||||
return '(' + ' '.join(dumps(x) for x in node) + ')'
|
||||
pad = ' ' * (indent + 1)
|
||||
parts = []
|
||||
head = []
|
||||
i = 0
|
||||
while i < len(node) and not isinstance(node[i], list):
|
||||
head.append(dumps(node[i]))
|
||||
i += 1
|
||||
out = '(' + ' '.join(head)
|
||||
for x in node[i:]:
|
||||
if isinstance(x, list):
|
||||
out += '\n' + pad + dumps(x, indent + 1)
|
||||
else:
|
||||
out += ' ' + dumps(x)
|
||||
out += '\n' + ' ' * indent + ')'
|
||||
return out
|
||||
|
||||
|
||||
def fmt(v):
|
||||
if isinstance(v, str):
|
||||
return v
|
||||
s = f'{v:.4f}'.rstrip('0').rstrip('.')
|
||||
if s in ('-0', ''):
|
||||
s = '0'
|
||||
return s
|
||||
|
||||
|
||||
def find(node, key):
|
||||
"""First child list whose head is key."""
|
||||
for x in node:
|
||||
if isinstance(x, list) and x and x[0] == key:
|
||||
return x
|
||||
return None
|
||||
|
||||
|
||||
def findall(node, key):
|
||||
return [x for x in node if isinstance(x, list) and x and x[0] == key]
|
||||
Reference in new issue
Block a user