Template
release/: JLCPCB order files when the repository makes them
A repository with .gitea/scripts/jlcpcb-files.py (harNES) gets <board>-<tag>-jlcpcb-bom.csv and -jlcpcb-cpl.csv (rotations corrected per LCSC part) in release/: the script runs with --force on a copy of hardware/ without the committed jlcpcb/ folders, so nothing stale is taken. When it refuses a board (DRC, missing correction), release/README.md and a workflow warning say why; the generic -cpl.csv then is the only placement file.
This commit is contained in:
1 parent
52a40c1c3e
commit
59f3df278b
2 files changed
+61
-5
No files matched your search
@@ -13,6 +13,8 @@ Writes into --out (default release/; existing content is replaced):
|
|||||||
<board>-<version>-pcb.pdf one page per copper, silkscreen and fab layer
|
<board>-<version>-pcb.pdf one page per copper, silkscreen and fab layer
|
||||||
<board>-<version>-top.png 3D render, top
|
<board>-<version>-top.png 3D render, top
|
||||||
<board>-<version>-bottom.png 3D render, bottom
|
<board>-<version>-bottom.png 3D render, bottom
|
||||||
|
<board>-<version>-jlcpcb-bom.csv, -jlcpcb-cpl.csv JLCPCB order files with rotations
|
||||||
|
corrected, when the repository has .gitea/scripts/jlcpcb-files.py (harNES)
|
||||||
docs/<name>-<version>.pdf docs/*.md (pandoc, pdflatex) and docs/tex/*.tex (pdflatex)
|
docs/<name>-<version>.pdf docs/*.md (pandoc, pdflatex) and docs/tex/*.tex (pdflatex)
|
||||||
|
|
||||||
Boards are the [[boards]] of proplan.toml, or hardware/*/*.kicad_pro when it names none. DRC and ERC
|
Boards are the [[boards]] of proplan.toml, or hardware/*/*.kicad_pro when it names none. DRC and ERC
|
||||||
@@ -190,7 +192,42 @@ def cpl(pcb, dest):
|
|||||||
return f"pick and place: {max(len(rows) - 1, 0)} parts, both sides, mm, DNP left out"
|
return f"pick and place: {max(len(rows) - 1, 0)} parts, both sides, mm, DNP left out"
|
||||||
|
|
||||||
|
|
||||||
def board(board_dir, pro, version, out, tmp):
|
def jlcpcb_files(tmp):
|
||||||
|
"""The repository's own JLCPCB order files, if it has .gitea/scripts/jlcpcb-files.py (harNES's pattern:
|
||||||
|
JLCPCB BOM, CPL with rotations corrected per LCSC part, refused unless DRC is clean). It runs with
|
||||||
|
--force on a copy of hardware/ without the committed jlcpcb/ folders, so nothing stale is taken.
|
||||||
|
Returns {board folder relative to the root: (jlcpcb folder or None, why not)}."""
|
||||||
|
script = ROOT / ".gitea" / "scripts" / "jlcpcb-files.py"
|
||||||
|
if not script.exists():
|
||||||
|
return {}
|
||||||
|
work = tmp / "jlcpcb-work"
|
||||||
|
shutil.copytree(ROOT / "hardware", work / "hardware", ignore=shutil.ignore_patterns("jlcpcb", "render", "*-backups"))
|
||||||
|
(work / ".gitea" / "scripts").mkdir(parents=True)
|
||||||
|
shutil.copyfile(script, work / ".gitea" / "scripts" / script.name)
|
||||||
|
r = run([sys.executable, work / ".gitea" / "scripts" / script.name, "--force"], cwd=work, check=False)
|
||||||
|
print(" " + "\n ".join((r.stdout + r.stderr).strip().splitlines()))
|
||||||
|
blocks = {b.split("\n", 1)[0].split(":")[0].strip(): b for b in re.split(r"^== ", r.stdout, flags=re.M) if b.strip()}
|
||||||
|
result = {}
|
||||||
|
for pro in sorted(work.glob("hardware/*/*.kicad_pro")):
|
||||||
|
folder = pro.parent / "jlcpcb"
|
||||||
|
rel = pro.parent.relative_to(work).as_posix()
|
||||||
|
if (folder / f"{pro.stem}-bom.csv").exists() and (folder / f"{pro.stem}-cpl.csv").exists():
|
||||||
|
result[rel] = (folder, "")
|
||||||
|
else:
|
||||||
|
lines = [l.strip() for l in blocks.get(pro.stem, "").splitlines()[1:] if l.strip()]
|
||||||
|
i = next((k for k, l in enumerate(lines) if l.startswith("NOT written")), None)
|
||||||
|
if i is None: # no verdict: the script crashed (its last line says why)
|
||||||
|
why = (r.stderr.strip().splitlines() or ["jlcpcb-files.py failed"])[-1]
|
||||||
|
else: # "NOT written: DRC is not clean" or "NOT written:", then one line per problem
|
||||||
|
head, rest = lines[i].partition(":")[2].strip(), lines[i + 1:]
|
||||||
|
why = "; ".join(x for x in (head, rest[0] if rest else "") if x) or "NOT written"
|
||||||
|
if len(rest) > 1:
|
||||||
|
why += f" (+{len(rest) - 1} more)"
|
||||||
|
result[rel] = (None, why[:300])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def board(board_dir, pro, version, out, tmp, jlc):
|
||||||
"""Write one board's files; returns (name, [(file, what)], checks)."""
|
"""Write one board's files; returns (name, [(file, what)], checks)."""
|
||||||
name = pro.stem
|
name = pro.stem
|
||||||
sch, pcb = board_dir / f"{name}.kicad_sch", board_dir / f"{name}.kicad_pcb"
|
sch, pcb = board_dir / f"{name}.kicad_sch", board_dir / f"{name}.kicad_pcb"
|
||||||
@@ -210,6 +247,17 @@ def board(board_dir, pro, version, out, tmp):
|
|||||||
f"{d['schematic_parity']} schematic parity issues")
|
f"{d['schematic_parity']} schematic parity issues")
|
||||||
files.insert(0, (f"{base}-gerbers.zip", gerbers_zip(pcb, dest / f"{base}-gerbers.zip", tmp)))
|
files.insert(0, (f"{base}-gerbers.zip", gerbers_zip(pcb, dest / f"{base}-gerbers.zip", tmp)))
|
||||||
files.append((f"{base}-cpl.csv", cpl(pcb, dest / f"{base}-cpl.csv")))
|
files.append((f"{base}-cpl.csv", cpl(pcb, dest / f"{base}-cpl.csv")))
|
||||||
|
rel = board_dir.relative_to(ROOT).as_posix()
|
||||||
|
if rel in jlc:
|
||||||
|
folder, why = jlc[rel]
|
||||||
|
if folder:
|
||||||
|
for kind, what in (("bom", "JLCPCB BOM: one line per LCSC part number"),
|
||||||
|
("cpl", "JLCPCB CPL: rotations and centres corrected for JLCPCB's footprints")):
|
||||||
|
shutil.copyfile(folder / f"{name}-{kind}.csv", dest / f"{base}-jlcpcb-{kind}.csv")
|
||||||
|
files.append((f"{base}-jlcpcb-{kind}.csv", what))
|
||||||
|
files[-3] = (files[-3][0], files[-3][1] + "; KiCad rotations, for JLCPCB use the -jlcpcb-cpl.csv")
|
||||||
|
else:
|
||||||
|
checks.append(f"JLCPCB files not made ({why})")
|
||||||
copper = ",".join(copper_layers(pcb))
|
copper = ",".join(copper_layers(pcb))
|
||||||
cli("pcb", "export", "pdf", "--mode-multipage", "--include-border-title",
|
cli("pcb", "export", "pdf", "--mode-multipage", "--include-border-title",
|
||||||
"--layers", f"{copper},F.Silkscreen,B.Silkscreen,F.Fab,B.Fab", "--common-layers", "Edge.Cuts",
|
"--layers", f"{copper},F.Silkscreen,B.Silkscreen,F.Fab,B.Fab", "--common-layers", "Edge.Cuts",
|
||||||
@@ -358,6 +406,11 @@ def main():
|
|||||||
kicad = cli("version").strip()
|
kicad = cli("version").strip()
|
||||||
clear(out)
|
clear(out)
|
||||||
boards, skipped, ok = [], [], True
|
boards, skipped, ok = [], [], True
|
||||||
|
jlc_tmp = tempfile.TemporaryDirectory()
|
||||||
|
jlc = {}
|
||||||
|
if (ROOT / ".gitea" / "scripts" / "jlcpcb-files.py").exists():
|
||||||
|
print("== JLCPCB order files (.gitea/scripts/jlcpcb-files.py)")
|
||||||
|
jlc = jlcpcb_files(Path(jlc_tmp.name))
|
||||||
for board_dir, pro in projects(m):
|
for board_dir, pro in projects(m):
|
||||||
rel = board_dir.relative_to(ROOT).as_posix()
|
rel = board_dir.relative_to(ROOT).as_posix()
|
||||||
if pro is None:
|
if pro is None:
|
||||||
@@ -367,7 +420,7 @@ def main():
|
|||||||
print(f"== {pro.stem}")
|
print(f"== {pro.stem}")
|
||||||
with tempfile.TemporaryDirectory() as t:
|
with tempfile.TemporaryDirectory() as t:
|
||||||
try:
|
try:
|
||||||
name, files, checks = board(board_dir, pro, a.version, out, Path(t))
|
name, files, checks = board(board_dir, pro, a.version, out, Path(t), jlc)
|
||||||
except Failed as e:
|
except Failed as e:
|
||||||
print(f"::error::{pro.stem}: {e}")
|
print(f"::error::{pro.stem}: {e}")
|
||||||
ok = False
|
ok = False
|
||||||
@@ -375,10 +428,11 @@ def main():
|
|||||||
boards.append((name, files, checks))
|
boards.append((name, files, checks))
|
||||||
for c in checks:
|
for c in checks:
|
||||||
print(f" {c}")
|
print(f" {c}")
|
||||||
if re.search(r"[1-9]\d* (errors|unconnected|schematic)", c):
|
if re.search(r"[1-9]\d* (errors|unconnected|schematic)", c) or c.startswith("JLCPCB"):
|
||||||
print(f"::warning::{name} {c}")
|
print(f"::warning::{name} {c}")
|
||||||
for f, _ in files:
|
for f, _ in files:
|
||||||
print(f" hardware/{name}/{f}")
|
print(f" hardware/{name}/{f}")
|
||||||
|
jlc_tmp.cleanup()
|
||||||
print("== documentation")
|
print("== documentation")
|
||||||
foot = tex_escape(f"{product} {a.version}, {commit}, {when[:10]}")
|
foot = tex_escape(f"{product} {a.version}, {commit}, {when[:10]}")
|
||||||
docs_made, docs_failed = documents(a.version, foot, out)
|
docs_made, docs_failed = documents(a.version, foot, out)
|
||||||
|
|||||||
+4
-2
@@ -121,6 +121,7 @@ stage tag is pushed; nobody edits it.
|
|||||||
| `release/hardware/<board>/<board>-<tag>-cpl.csv` | pick and place (KiCad position file, mm, both sides, DNP left out) |
|
| `release/hardware/<board>/<board>-<tag>-cpl.csv` | pick and place (KiCad position file, mm, both sides, DNP left out) |
|
||||||
| `release/hardware/<board>/<board>-<tag>-schematic.pdf`, `-pcb.pdf` | the schematic; the PCB with one page per copper, silkscreen and fab layer |
|
| `release/hardware/<board>/<board>-<tag>-schematic.pdf`, `-pcb.pdf` | the schematic; the PCB with one page per copper, silkscreen and fab layer |
|
||||||
| `release/hardware/<board>/<board>-<tag>-top.png`, `-bottom.png` | 3D renders |
|
| `release/hardware/<board>/<board>-<tag>-top.png`, `-bottom.png` | 3D renders |
|
||||||
|
| `release/hardware/<board>/<board>-<tag>-jlcpcb-bom.csv`, `-jlcpcb-cpl.csv` | only in a repository with `.gitea/scripts/jlcpcb-files.py` (harNES): JLCPCB's BOM and a CPL with rotations corrected per LCSC part, made only from a clean DRC; use these, not `-cpl.csv`, for JLCPCB assembly |
|
||||||
| `release/docs/<name>-<tag>.pdf` | every `docs/*.md` except `README.md` and `input.md` (pandoc), and every `docs/tex/*.tex` with a `\documentclass` (pdflatex); the footer carries product, tag, commit and date |
|
| `release/docs/<name>-<tag>.pdf` | every `docs/*.md` except `README.md` and `input.md` (pandoc), and every `docs/tex/*.tex` with a `\documentclass` (pdflatex); the footer carries product, tag, commit and date |
|
||||||
|
|
||||||
- Boards are the `[[boards]]` of `proplan.toml` (or `hardware/*/*.kicad_pro`
|
- Boards are the `[[boards]]` of `proplan.toml` (or `hardware/*/*.kicad_pro`
|
||||||
@@ -146,8 +147,9 @@ stage tag is pushed; nobody edits it.
|
|||||||
writes the same files to `build/` (ignored by git).
|
writes the same files to `build/` (ignored by git).
|
||||||
|
|
||||||
Not in it (yet): firmware binaries (firmware has its own tags), STEP models
|
Not in it (yet): firmware binaries (firmware has its own tags), STEP models
|
||||||
(`mech/`), JLCPCB-specific BOM/CPL with rotation corrections (harNES's
|
(`mech/`). JLCPCB order files need `jlcpcb-files.py` and a
|
||||||
`jlcpcb-files.py` does that per push).
|
`jlcpcb-corrections.csv` per board (harNES has both); without them the generic
|
||||||
|
`-cpl.csv` carries KiCad's rotations, which JLCPCB's footprints may not share.
|
||||||
|
|
||||||
## proplan.toml
|
## proplan.toml
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user