Compare commits

...

27 Commits

Author SHA1 Message Date
sirlilpanda
469cb53119 auto commited 2026-02-18 17:38:29 +13:00
sirlilpanda
5e12d5b81f auto commited 2026-02-18 17:38:29 +13:00
sirlilpanda
38798ce8ea auto commited 2026-02-18 17:38:29 +13:00
sirlilpanda
3d3fffee0e auto commited 2026-02-18 17:38:29 +13:00
sirlilpanda
1e8af81591 auto commited 2026-02-18 17:37:58 +13:00
sirlilpanda
3d4c36bcbd auto commited 2026-02-18 17:37:58 +13:00
sirlilpanda
0826ef9df1 auto commited 2026-02-18 17:37:58 +13:00
sirlilpanda
058c1bc455 auto commited 2026-02-18 17:37:58 +13:00
sirlilpanda
aefd0879c3 updated kicad project to allow the parsing of raw args 2026-02-18 17:37:22 +13:00
8cf9b909e2 auto commited 2025-12-31 14:51:04 +13:00
ff063efde7 auto commited 2025-12-31 14:51:04 +13:00
577b9e11e0 auto commited 2025-12-31 14:51:04 +13:00
53a395af71 not needed 2025-12-31 14:50:55 +13:00
b1d4a80a2f fixed generation for the .rpt plain text format 2025-12-31 14:50:42 +13:00
9cd18828eb auto commited 2025-12-31 14:49:36 +13:00
4df8d34871 auto commited 2025-12-31 14:49:36 +13:00
3c7968bdcd auto commited 2025-12-31 14:49:36 +13:00
cd7fb288ad fixed report suffix 2025-12-31 14:35:54 +13:00
9163ed6d93 will now add the kicad .rpt output files to the docs dir 2025-12-31 14:34:28 +13:00
b79b1c2c0d not needed 2025-12-31 14:33:19 +13:00
4a0e878a73 auto commited 2025-12-31 14:32:31 +13:00
28dcbcabc9 added method to auto commit generated files 2025-12-31 14:32:21 +13:00
ef2f5452c1 updated readme with basic instructions 2025-12-31 14:32:00 +13:00
b9a73e4473 auto commited 2025-12-31 14:31:33 +13:00
5b69b23586 auto commited 2025-12-31 14:31:33 +13:00
df4813a650 now being ignored 2025-12-31 14:25:02 +13:00
ffc7d81dd2 ignoring the temp dir 2025-12-31 14:24:04 +13:00
12 changed files with 149 additions and 101 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
temp/

View File

@@ -11,7 +11,7 @@ from os import listdir
from os.path import isfile, join from os.path import isfile, join
KICAD_CLI_PATH = "kicad-cli" KICAD_CLI_PATH = "kicad-cli"
TEMP_FILE_PATH = "temp/" TEMP_FILE_PATH = "docs/"
PCB_IMAGE_OUTPUT_PATH = "res/" PCB_IMAGE_OUTPUT_PATH = "res/"
@@ -21,13 +21,27 @@ PCB_PDF_FILE_SUFFIX = "_pcb"
SCHEMATIC_OUTPUT_PATH = "docs/" SCHEMATIC_OUTPUT_PATH = "docs/"
SCHEMATIC_FILE_SUFFIX = "_schematic" SCHEMATIC_FILE_SUFFIX = "_schematic"
BOM_OUTPUT_PATH = "docs/"
BOM_REPORT_NAME = "_bom"
TEMP_DRC_REPORT_NAME = "_drc" TEMP_DRC_REPORT_NAME = "_drc"
TEMP_ERC_REPORT_NAME = "_erc" TEMP_ERC_REPORT_NAME = "_erc"
TEMP_BOM_REPORT_NAME = "_bom"
# quiet
KICAD_CLI_STDOUT=subprocess.DEVNULL
# verbose
# KICAD_CLI_STDOUT=subprocess.STDOUT
class OutputReportType(Enum): class OutputReportType(Enum):
JSON = 1 JSON = 1
RPT = 2 REPORT = 2
def get_file_extension(self) -> str:
match self:
case OutputReportType.JSON: return "json"
case OutputReportType.REPORT: return "rpt"
# dont trust it
case _: return "txt"
# this is a thin vale on the kicad cli tool # this is a thin vale on the kicad cli tool
class KicadProject: class KicadProject:
@@ -35,22 +49,32 @@ class KicadProject:
def __init__(self, path : Path) -> None: def __init__(self, path : Path) -> None:
self.project_path = path.parent self.project_path = path.parent
self.project_name = path.name.removesuffix(".kicad_pro") self.project_name = path.name.removesuffix(".kicad_pro")
self.created_files : list[Path] = []
print(f"{self.project_path=}") print(f"{self.project_path=}")
print(f"{self.project_name=}") print(f"{self.project_name=}")
def erc_check( def erc_check(
self, self,
report_format : OutputReportType = OutputReportType.JSON, report_format : OutputReportType = OutputReportType.JSON,
return_report : bool = False return_report : bool = False,
additional_args : str = ""
) -> None | dict | str: ) -> None | dict | str:
format_type = report_format.name.lower() format_type = report_format.name.lower()
pcb_file_path = self.project_path / f"{self.project_name}.kicad_sch" sch_file_path = self.project_path / f"{self.project_name}.kicad_sch"
erc_report_path = Path(TEMP_FILE_PATH) / f"{self.project_name}{TEMP_ERC_REPORT_NAME}.{format_type}" erc_report_path = Path(TEMP_FILE_PATH) / f"{self.project_name}{TEMP_ERC_REPORT_NAME}.{report_format.get_file_extension()}"
subprocess.call(
f'{KICAD_CLI_PATH} sch erc {pcb_file_path} --output {erc_report_path} --format {format_type}', retcode = subprocess.call(
f'{KICAD_CLI_PATH} sch erc {sch_file_path} --output {erc_report_path} --format {format_type} {additional_args}',
shell=True, shell=True,
stdout=KICAD_CLI_STDOUT
) )
if (retcode != 0):
print(f"erc check failed return code {retcode}")
exit(1)
self.created_files.append(erc_report_path)
if (return_report): if (return_report):
with open(erc_report_path, "r") as txt: with open(erc_report_path, "r") as txt:
if format_type == OutputReportType.JSON: if format_type == OutputReportType.JSON:
@@ -61,15 +85,24 @@ class KicadProject:
def drc_check( def drc_check(
self, self,
report_format : OutputReportType = OutputReportType.JSON, report_format : OutputReportType = OutputReportType.JSON,
return_report : bool = False return_report : bool = False,
additional_args : str = ""
) -> None | dict | str: ) -> None | dict | str:
format_type = report_format.name.lower() format_type = report_format.name.lower()
pcb_file_path = self.project_path / f"{self.project_name}.kicad_pcb" pcb_file_path = self.project_path / f"{self.project_name}.kicad_pcb"
drc_report_path = Path(TEMP_FILE_PATH) / f"{self.project_name}{TEMP_DRC_REPORT_NAME}.{format_type}" drc_report_path = Path(TEMP_FILE_PATH) / f"{self.project_name}{TEMP_DRC_REPORT_NAME}.{report_format.get_file_extension()}"
subprocess.call( print(f"{format_type=}, {drc_report_path=}")
f'{KICAD_CLI_PATH} pcb drc {pcb_file_path} --output {drc_report_path} --format {format_type}', retcode = subprocess.call(
f'{KICAD_CLI_PATH} pcb drc {pcb_file_path} --output {drc_report_path} --format {format_type} {additional_args}',
shell=True, shell=True,
stdout=KICAD_CLI_STDOUT
) )
print(f"{retcode=}")
if (retcode != 0):
print(f"drc check failed return code {retcode}")
exit(1)
self.created_files.append(drc_report_path)
if (return_report): if (return_report):
with open(drc_report_path, "r") as txt: with open(drc_report_path, "r") as txt:
@@ -78,13 +111,24 @@ class KicadProject:
if format_type == OutputReportType.RPT: if format_type == OutputReportType.RPT:
return txt.read() return txt.read()
def process_bom(self, return_csv : bool = False) -> None | list[list[str]]: def process_bom(
self,
return_csv : bool = False,
additional_args : str = ""
) -> None | list[list[str]]:
sch_file_path = self.project_path / f"{self.project_name}.kicad_sch" sch_file_path = self.project_path / f"{self.project_name}.kicad_sch"
bom_output_path = Path(TEMP_FILE_PATH) / f"{self.project_name}{TEMP_DRC_REPORT_NAME}.csv" bom_output_path = Path(BOM_OUTPUT_PATH) / f"{self.project_name}{BOM_REPORT_NAME}.csv"
subprocess.call( retcode = subprocess.call(
f'{KICAD_CLI_PATH} sch export bom {sch_file_path} --output {bom_output_path}', f'{KICAD_CLI_PATH} sch export bom {sch_file_path} --output {bom_output_path} {additional_args}',
shell=True, shell=True,
stdout=KICAD_CLI_STDOUT
) )
if (retcode != 0):
print(f"process_bom failed return code {retcode}")
exit(1)
self.created_files.append(bom_output_path)
if (return_csv): if (return_csv):
with open(bom_output_path, "r") as csvfile: with open(bom_output_path, "r") as csvfile:
bom_csv = csv.reader(csvfile, delimiter=',', quotechar='"') bom_csv = csv.reader(csvfile, delimiter=',', quotechar='"')
@@ -98,6 +142,7 @@ class KicadProject:
background : str = "default", background : str = "default",
preset : str = "follow_pcb_editor", preset : str = "follow_pcb_editor",
zoom : int = 2, zoom : int = 2,
additional_args : str = ""
) -> None: ) -> None:
""" """
image_typ = "png" | "jpg" image_typ = "png" | "jpg"
@@ -106,44 +151,71 @@ class KicadProject:
""" """
pcb_file_path = self.project_path / f"{self.project_name}.kicad_pcb" pcb_file_path = self.project_path / f"{self.project_name}.kicad_pcb"
render_output_path = Path(PCB_IMAGE_OUTPUT_PATH) / f"{self.project_name}_render.{image_type}" render_output_path = Path(PCB_IMAGE_OUTPUT_PATH) / f"{self.project_name}_render.{image_type}"
subprocess.call( retcode = subprocess.call(
f'{KICAD_CLI_PATH} pcb render {pcb_file_path} --output {render_output_path} --preset {preset} --zoom {zoom} ', f'{KICAD_CLI_PATH} pcb render {pcb_file_path} --output {render_output_path} --preset {preset} --zoom {zoom} {additional_args}',
shell=True, shell=True,
stdout=KICAD_CLI_STDOUT
) )
if (retcode != 0):
print(f"get_image failed return code {retcode}")
exit(1)
self.created_files.append(render_output_path)
# i am not giving you the pdf to output if you want to do that yourself go ahead # i am not giving you the pdf to output if you want to do that yourself go ahead
def create_schmatic_pdf(self) -> None: def create_schmatic_pdf(self, additional_args="") -> None:
sch_file_path = self.project_path / f"{self.project_name}.kicad_sch" sch_file_path = self.project_path / f"{self.project_name}.kicad_sch"
sch_report_path = Path(SCHEMATIC_OUTPUT_PATH) / f"{self.project_name}{SCHEMATIC_FILE_SUFFIX}.pdf" sch_report_path = Path(SCHEMATIC_OUTPUT_PATH) / f"{self.project_name}{SCHEMATIC_FILE_SUFFIX}.pdf"
subprocess.call( retcode = subprocess.call(
f'{KICAD_CLI_PATH} sch export pdf {sch_file_path} --output {sch_report_path}', f'{KICAD_CLI_PATH} sch export pdf {sch_file_path} --output {sch_report_path} {additional_args}',
shell=True, shell=True,
stdout=KICAD_CLI_STDOUT
) )
def create_pcb_pdf(self, layers : list[str] = ["F.Cu", "B.Cu"]) -> None: if (retcode != 0):
print(f"create_schmatic_pdf failed return code {retcode}")
exit(1)
self.created_files.append(sch_report_path)
def create_pcb_pdf(self, layers : list[str] = ["F.Cu", "B.Cu"], additional_args : str = "") -> None:
pcb_file_path = self.project_path / f"{self.project_name}.kicad_pcb" pcb_file_path = self.project_path / f"{self.project_name}.kicad_pcb"
pcb_report_path = Path(PCB_PDF_OUTPUT_PATH) / f"{self.project_name}{PCB_PDF_FILE_SUFFIX}.pdf" pcb_report_path = Path(PCB_PDF_OUTPUT_PATH) / f"{self.project_name}{PCB_PDF_FILE_SUFFIX}.pdf"
subprocess.call( retcode = subprocess.call(
f'{KICAD_CLI_PATH} pcb export pdf {pcb_file_path} --output {pcb_report_path} --layers {",".join(layers)}', f'{KICAD_CLI_PATH} pcb export pdf {pcb_file_path} --output {pcb_report_path} --layers {",".join(layers)} {additional_args}',
shell=True, shell=True,
stdout=KICAD_CLI_STDOUT
) )
def main() -> None: if (retcode != 0):
print(f"create_pcb_pdf failed return code {retcode}")
exit(1)
self.created_files.append(pcb_report_path)
def commit_files(files: list[Path], commit_message : str) -> None:
for file in files:
# add & commit, could use the return code however these should never fail
print(f"adding and commiting {file}")
ret_add = subprocess.call(f"git add {file}", shell=True)
ret_commit = subprocess.call(f"git commit -m \"{commit_message}\"", shell=True)
def main() -> None:
# find all kicad project files to operate on # find all kicad project files to operate on
for path in Path(".").rglob('*.kicad_pro'): for path in Path(".").rglob('*.kicad_pro'):
print(path.name)
print(path)
print(type(path.parent))
k = KicadProject(path) k = KicadProject(path)
k.drc_check() k.drc_check(report_format = OutputReportType.REPORT)
k.erc_check() k.erc_check(report_format = OutputReportType.REPORT)
k.process_bom() k.process_bom()
k.create_schmatic_pdf() k.create_schmatic_pdf()
k.create_pcb_pdf()
k.get_image() k.get_image()
commit_files(k.created_files, "auto commited")
print("hello world");
if __name__ == "__main__": if __name__ == "__main__":
try:
main() main()
exit(0)
except Exception as e:
exit(1)

View File

@@ -0,0 +1,23 @@
# How to use this template
this template will auto:
- run erc checks
- run drc checks
- create schematic pdfs
- create BOM for each project
- create images of the current pcb for your readme like you can see below
![rendered pcb](res/test_render.png)
the code for creating all of this lives in `.hooks/`
## setup
dependencies:
- python3.9+ (used for crossplatfrom scripting)
- kicad-cli
to set up the hooks just run
```
python setup.py
```
This script will add a line in the `.git/hooks/pre-push` to auto run `.hooks/kicad_cli_tools.py`

13
docs/test_drc.rpt Normal file
View File

@@ -0,0 +1,13 @@
** Drc report for test.kicad_pcb **
** Created on 2026-02-18T17:38:29+1300 **
** Found 1 DRC violations **
[invalid_outline]: Board has malformed outline (no edges found on Edge.Cuts layer)
Local override; error
@(0.0000 mm, 0.0000 mm): PCB
** Found 0 unconnected pads **
** Found 0 Footprint errors **
** End of Report **

5
docs/test_erc.rpt Normal file
View File

@@ -0,0 +1,5 @@
ERC report (2026-02-18T17:38:29+1300, Encoding UTF8)
***** Sheet /
** ERC messages: 0 Errors 0 Warnings 0

Binary file not shown.

Binary file not shown.

View File

@@ -1,26 +0,0 @@
{
"$schema": "https://schemas.kicad.org/drc.v1.json",
"coordinate_units": "mm",
"date": "2025-12-31T01:11:53+1300",
"kicad_version": "9.0.6",
"schematic_parity": [],
"source": "test.kicad_pcb",
"unconnected_items": [],
"violations": [
{
"description": "Board has malformed outline (no edges found on Edge.Cuts layer)",
"items": [
{
"description": "PCB",
"pos": {
"x": 0.0,
"y": 0.0
},
"uuid": "7690956f-7cf1-4850-b24e-2c34f81a5616"
}
],
"severity": "error",
"type": "invalid_outline"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -1,26 +0,0 @@
{
"$schema": "https://schemas.kicad.org/drc.v1.json",
"coordinate_units": "mm",
"date": "2025-12-31T02:13:27+1300",
"kicad_version": "9.0.6",
"schematic_parity": [],
"source": "test.kicad_pcb",
"unconnected_items": [],
"violations": [
{
"description": "Board has malformed outline (no edges found on Edge.Cuts layer)",
"items": [
{
"description": "PCB",
"pos": {
"x": 0.0,
"y": 0.0
},
"uuid": "11ba8380-b107-4944-a66d-39eb5d745a00"
}
],
"severity": "error",
"type": "invalid_outline"
}
]
}

View File

@@ -1,14 +0,0 @@
{
"$schema": "https://schemas.kicad.org/erc.v1.json",
"coordinate_units": "mm",
"date": "2025-12-31T02:13:27+1300",
"kicad_version": "9.0.6",
"sheets": [
{
"path": "/",
"uuid_path": "/f507f841-113f-488d-81ff-25ffa9ae803f",
"violations": []
}
],
"source": "test.kicad_sch"
}