Compare commits
27 Commits
2ec99a48d9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
469cb53119 | ||
|
|
5e12d5b81f | ||
|
|
38798ce8ea | ||
|
|
3d3fffee0e | ||
|
|
1e8af81591 | ||
|
|
3d4c36bcbd | ||
|
|
0826ef9df1 | ||
|
|
058c1bc455 | ||
|
|
aefd0879c3 | ||
| 8cf9b909e2 | |||
| ff063efde7 | |||
| 577b9e11e0 | |||
| 53a395af71 | |||
| b1d4a80a2f | |||
| 9cd18828eb | |||
| 4df8d34871 | |||
| 3c7968bdcd | |||
| cd7fb288ad | |||
| 9163ed6d93 | |||
| b79b1c2c0d | |||
| 4a0e878a73 | |||
| 28dcbcabc9 | |||
| ef2f5452c1 | |||
| b9a73e4473 | |||
| 5b69b23586 | |||
| df4813a650 | |||
| ffc7d81dd2 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
temp/
|
||||
@@ -11,7 +11,7 @@ from os import listdir
|
||||
from os.path import isfile, join
|
||||
|
||||
KICAD_CLI_PATH = "kicad-cli"
|
||||
TEMP_FILE_PATH = "temp/"
|
||||
TEMP_FILE_PATH = "docs/"
|
||||
|
||||
PCB_IMAGE_OUTPUT_PATH = "res/"
|
||||
|
||||
@@ -21,13 +21,27 @@ PCB_PDF_FILE_SUFFIX = "_pcb"
|
||||
SCHEMATIC_OUTPUT_PATH = "docs/"
|
||||
SCHEMATIC_FILE_SUFFIX = "_schematic"
|
||||
|
||||
BOM_OUTPUT_PATH = "docs/"
|
||||
BOM_REPORT_NAME = "_bom"
|
||||
|
||||
TEMP_DRC_REPORT_NAME = "_drc"
|
||||
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):
|
||||
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
|
||||
class KicadProject:
|
||||
@@ -35,22 +49,32 @@ class KicadProject:
|
||||
def __init__(self, path : Path) -> None:
|
||||
self.project_path = path.parent
|
||||
self.project_name = path.name.removesuffix(".kicad_pro")
|
||||
self.created_files : list[Path] = []
|
||||
print(f"{self.project_path=}")
|
||||
print(f"{self.project_name=}")
|
||||
|
||||
def erc_check(
|
||||
self,
|
||||
report_format : OutputReportType = OutputReportType.JSON,
|
||||
return_report : bool = False
|
||||
return_report : bool = False,
|
||||
additional_args : str = ""
|
||||
) -> None | dict | str:
|
||||
format_type = report_format.name.lower()
|
||||
pcb_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}"
|
||||
subprocess.call(
|
||||
f'{KICAD_CLI_PATH} sch erc {pcb_file_path} --output {erc_report_path} --format {format_type}',
|
||||
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}.{report_format.get_file_extension()}"
|
||||
|
||||
retcode = subprocess.call(
|
||||
f'{KICAD_CLI_PATH} sch erc {sch_file_path} --output {erc_report_path} --format {format_type} {additional_args}',
|
||||
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):
|
||||
with open(erc_report_path, "r") as txt:
|
||||
if format_type == OutputReportType.JSON:
|
||||
@@ -61,15 +85,24 @@ class KicadProject:
|
||||
def drc_check(
|
||||
self,
|
||||
report_format : OutputReportType = OutputReportType.JSON,
|
||||
return_report : bool = False
|
||||
return_report : bool = False,
|
||||
additional_args : str = ""
|
||||
) -> None | dict | str:
|
||||
format_type = report_format.name.lower()
|
||||
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}"
|
||||
subprocess.call(
|
||||
f'{KICAD_CLI_PATH} pcb drc {pcb_file_path} --output {drc_report_path} --format {format_type}',
|
||||
drc_report_path = Path(TEMP_FILE_PATH) / f"{self.project_name}{TEMP_DRC_REPORT_NAME}.{report_format.get_file_extension()}"
|
||||
print(f"{format_type=}, {drc_report_path=}")
|
||||
retcode = subprocess.call(
|
||||
f'{KICAD_CLI_PATH} pcb drc {pcb_file_path} --output {drc_report_path} --format {format_type} {additional_args}',
|
||||
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):
|
||||
with open(drc_report_path, "r") as txt:
|
||||
@@ -78,13 +111,24 @@ class KicadProject:
|
||||
if format_type == OutputReportType.RPT:
|
||||
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"
|
||||
bom_output_path = Path(TEMP_FILE_PATH) / f"{self.project_name}{TEMP_DRC_REPORT_NAME}.csv"
|
||||
subprocess.call(
|
||||
f'{KICAD_CLI_PATH} sch export bom {sch_file_path} --output {bom_output_path}',
|
||||
bom_output_path = Path(BOM_OUTPUT_PATH) / f"{self.project_name}{BOM_REPORT_NAME}.csv"
|
||||
retcode = subprocess.call(
|
||||
f'{KICAD_CLI_PATH} sch export bom {sch_file_path} --output {bom_output_path} {additional_args}',
|
||||
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):
|
||||
with open(bom_output_path, "r") as csvfile:
|
||||
bom_csv = csv.reader(csvfile, delimiter=',', quotechar='"')
|
||||
@@ -98,6 +142,7 @@ class KicadProject:
|
||||
background : str = "default",
|
||||
preset : str = "follow_pcb_editor",
|
||||
zoom : int = 2,
|
||||
additional_args : str = ""
|
||||
) -> None:
|
||||
"""
|
||||
image_typ = "png" | "jpg"
|
||||
@@ -106,44 +151,71 @@ class KicadProject:
|
||||
"""
|
||||
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}"
|
||||
subprocess.call(
|
||||
f'{KICAD_CLI_PATH} pcb render {pcb_file_path} --output {render_output_path} --preset {preset} --zoom {zoom} ',
|
||||
retcode = subprocess.call(
|
||||
f'{KICAD_CLI_PATH} pcb render {pcb_file_path} --output {render_output_path} --preset {preset} --zoom {zoom} {additional_args}',
|
||||
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
|
||||
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_report_path = Path(SCHEMATIC_OUTPUT_PATH) / f"{self.project_name}{SCHEMATIC_FILE_SUFFIX}.pdf"
|
||||
subprocess.call(
|
||||
f'{KICAD_CLI_PATH} sch export pdf {sch_file_path} --output {sch_report_path}',
|
||||
retcode = subprocess.call(
|
||||
f'{KICAD_CLI_PATH} sch export pdf {sch_file_path} --output {sch_report_path} {additional_args}',
|
||||
shell=True,
|
||||
stdout=KICAD_CLI_STDOUT
|
||||
)
|
||||
|
||||
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"]) -> None:
|
||||
|
||||
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_report_path = Path(PCB_PDF_OUTPUT_PATH) / f"{self.project_name}{PCB_PDF_FILE_SUFFIX}.pdf"
|
||||
subprocess.call(
|
||||
f'{KICAD_CLI_PATH} pcb export pdf {pcb_file_path} --output {pcb_report_path} --layers {",".join(layers)}',
|
||||
retcode = subprocess.call(
|
||||
f'{KICAD_CLI_PATH} pcb export pdf {pcb_file_path} --output {pcb_report_path} --layers {",".join(layers)} {additional_args}',
|
||||
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
|
||||
for path in Path(".").rglob('*.kicad_pro'):
|
||||
print(path.name)
|
||||
print(path)
|
||||
print(type(path.parent))
|
||||
k = KicadProject(path)
|
||||
k.drc_check()
|
||||
k.erc_check()
|
||||
k.drc_check(report_format = OutputReportType.REPORT)
|
||||
k.erc_check(report_format = OutputReportType.REPORT)
|
||||
k.process_bom()
|
||||
k.create_schmatic_pdf()
|
||||
k.create_pcb_pdf()
|
||||
k.get_image()
|
||||
|
||||
print("hello world");
|
||||
commit_files(k.created_files, "auto commited")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
try:
|
||||
main()
|
||||
exit(0)
|
||||
except Exception as e:
|
||||
exit(1)
|
||||
|
||||
23
README.md
23
README.md
@@ -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
|
||||
|
||||

|
||||
|
||||
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
13
docs/test_drc.rpt
Normal 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
5
docs/test_erc.rpt
Normal 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.
26
drc.json
26
drc.json
@@ -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 |
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user