diff --git a/docs/fabrication/gatema.md b/docs/fabrication/gatema.md new file mode 100644 index 00000000..fc22ffa0 --- /dev/null +++ b/docs/fabrication/gatema.md @@ -0,0 +1,16 @@ +# Fabrication: Gatema PCB + +The basic usage of this exporter is: +``` +kikit fab gatema [OPTIONS] BOARD OUTPUTDIR +``` + +When you run this command, you will find file `gerbers.zip` in `OUTPUTDIR`. This +file can be directly uploaded to Gatema PCB site. KiKit automatically detects the +number of layers. If you would like to include the project name in the archive +name, you can supply `--autoname` + +If you want to name your files differently, you can specify `--nametemplate`. +This option takes a string that should contain `{}`. This string will be +replaced by `gerber`, `pos` or `bom` in the out file names. The extension is +appended automatically. diff --git a/docs/fabrication/intro.md b/docs/fabrication/intro.md index 32a1351d..09b939f0 100644 --- a/docs/fabrication/intro.md +++ b/docs/fabrication/intro.md @@ -35,6 +35,7 @@ See documentation for the individual manufacturer below: Note: click on the name of the manufacturer to see corresponding documentation: - [JLC PCB](jlcpcb.md): board manufacturing, SMD assembly. [https://jlcpcb.com/](https://jlcpcb.com/) +- [Gatema](gatema.md): board manufacturing. [https://gatemapcb.cz/](https://gatemapcb.cz/) - [PCBWay](pcbway.md): board manufacturing, assembly. [https://www.pcbway.com/](https://www.pcbway.com/) - [OSH Park](oshpark.md): board manufacturing. [https://oshpark.com/](https://oshpark.com/) - [Neoden YY1](neodenyy1.md): desktop PCB assembly. [https://neodenusa.com/neoden-yy1-pick-place-machine](https://neodenusa.com/neoden-yy1-pick-place-machine) diff --git a/kikit/common.py b/kikit/common.py index 5cb0f384..7f693811 100644 --- a/kikit/common.py +++ b/kikit/common.py @@ -3,7 +3,7 @@ import traceback from typing import List, Optional, Tuple, Union, Callable from kikit.defs import Layer -from kikit.typing import Box +from kikit.kityping import Box from pcbnewTransition import pcbnew, kicad_major from kikit.intervals import AxialLine from pcbnewTransition.pcbnew import BOX2I, VECTOR2I, EDA_ANGLE diff --git a/kikit/export.py b/kikit/export.py index ade2c4cd..143618ab 100644 --- a/kikit/export.py +++ b/kikit/export.py @@ -32,6 +32,17 @@ "SubstractMaskFromSilk": True } +exportSettingsGatema = { + "UseGerberProtelExtensions": True, + "UseAuxOrigin": True, + "ExcludeEdgeLayer": True, + "MinimalHeader": False, + "NoSuffix": False, + "MergeNPTH": False, + "MapFileFormat": PLOT_FORMAT_GERBER, + "ZerosFormat": GENDRILL_WRITER_BASE.DECIMAL_FORMAT, +} + exportSettingsPcbway = { "UseGerberProtelExtensions": True, "UseAuxOrigin": False, diff --git a/kikit/fab/gatema.py b/kikit/fab/gatema.py new file mode 100644 index 00000000..10d2e932 --- /dev/null +++ b/kikit/fab/gatema.py @@ -0,0 +1,132 @@ +import click +from pcbnewTransition import pcbnew +import csv +import os +import sys +import shutil +from pathlib import Path +from kikit.fab.common import * +from kikit.common import * +from kikit.export import gerberImpl +from kikit.export import exportSettingsGatema + +#def collectBom(components, lscsFields, ignore): +# bom = {} +# for c in components: +# if getUnit(c) != 1: +# continue +# reference = getReference(c) +# if reference.startswith("#PWR") or reference.startswith("#FL"): +# continue +# if reference in ignore: +# continue +# if getField(c, "JLCPCB_IGNORE") is not None and getField(c, "JLCPCB_IGNORE") != "": +# continue +# if hasattr(c, "in_bom") and not c.in_bom: +# continue +# if hasattr(c, "on_board") and not c.on_board: +# continue +# if hasattr(c, "dnp") and c.dnp: +# continue +# orderCode = None +# for fieldName in lscsFields: +# orderCode = getField(c, fieldName) +# if orderCode is not None and orderCode.strip() != "": +# break +# cType = ( +# getField(c, "Value"), +# getField(c, "Footprint"), +# orderCode +# ) +# bom[cType] = bom.get(cType, []) + [reference] +# return bom + +#def bomToCsv(bomData, filename): +# with open(filename, "w", newline="", encoding="utf-8") as csvfile: +# writer = csv.writer(csvfile) +# writer.writerow(["Comment", "Designator", "Footprint", "LCSC"]) +# for cType, references in bomData.items(): +# # JLCPCB allows at most 200 components per line so we have to split +# # the BOM into multiple lines. Let's make the chunks by 100 just to +# # be sure. +# CHUNK_SIZE = 100 +# sortedReferences = sorted(references, key=naturalComponentKey) +# for i in range(0, len(references), CHUNK_SIZE): +# refChunk = sortedReferences[i:i+CHUNK_SIZE] +# value, footprint, lcsc = cType +# writer.writerow([value, ",".join(refChunk), footprint, lcsc]) + +def exportGatema(board, outputdir, ignore, nametemplate, drc, + autoname): + """ + Prepare fabrication files for Gatema PCB + """ + ensureValidBoard(board) + loadedBoard = pcbnew.LoadBoard(board) + + if drc: + ensurePassingDrc(loadedBoard) + + refsToIgnore = parseReferences(ignore) + removeComponents(loadedBoard, refsToIgnore) + Path(outputdir).mkdir(parents=True, exist_ok=True) + + gerberdir = os.path.join(outputdir, "gerber") + shutil.rmtree(gerberdir, ignore_errors=True) + gerberImpl(board, gerberdir, settings=exportSettingsGatema) + ext_list = [("-CuTop.gtl", ".top"), ("-CuBottom.gbl", ".bot"), ("-MaskTop.gts", ".smt"), ("-MaskBottom.gbs", ".smb"), ("-NPTH.drl", ".mill"), ("-PTH.drl", ".pth"), ("-SilkTop.gto", ".plt"), ("-SilkBottom.gbo", ".plb"), ("-EdgeCuts.gm1", ".dim"), ("-PasteTop.gtp", ".pastetop"), ("-PasteBottom.gbp", ".pastebot")] + for f in os.listdir(gerberdir): + unneded = True + # mayby drrilling should be using the map - not tested + for old, new in ext_list: + if f.endswith(old): + unneded = False + newname = f.replace(old, new) + os.rename(os.path.join(gerberdir, f), os.path.join(gerberdir, newname)) + break + if unneded: + os.remove(os.path.join(gerberdir, f)) # remove unneeded files + + + if autoname: + boardName = os.path.basename(board.replace(".kicad_pcb", "")) + archiveName = expandNameTemplate(nametemplate, boardName + "-gerbers", loadedBoard) + else: + archiveName = expandNameTemplate(nametemplate, "gerbers", loadedBoard) + shutil.make_archive(os.path.join(outputdir, archiveName), "zip", outputdir, "gerber") + +# if not assembly: +# return +# if schematic is None: +# raise RuntimeError("When outputing assembly data, schematic is required") +# +# ensureValidSch(schematic) +# +# correctionFields = [x.strip() for x in corrections.split(",")] +# components = extractComponents(schematic) +# ordercodeFields = [x.strip() for x in field.split(",")] +# bom = collectBom(components, ordercodeFields, refsToIgnore) +# +# bom_refs = set(x for xs in bom.values() for x in xs) +# bom_components = [c for c in components if getReference(c) in bom_refs] +# +# posData = collectPosData(loadedBoard, correctionFields, +# bom=bom_components, posFilter=noFilter, correctionFile=correctionpatterns, +# orientationHandling=FootprintOrientationHandling.MirrorBottom) +# boardReferences = set([x[0] for x in posData]) +# bom = {key: [v for v in val if v in boardReferences] for key, val in bom.items()} +# bom = {key: val for key, val in bom.items() if len(val) > 0} +# +# +# missingFields = False +# for type, references in bom.items(): +# _, _, lcsc = type +# if not lcsc: +# missingFields = True +# for r in references: +# print(f"WARNING: Component {r} is missing ordercode") +# if missingFields and missingerror: +# sys.exit("There are components with missing ordercode, aborting") +# +# posDataToFile(posData, os.path.join(outputdir, expandNameTemplate(nametemplate, "pos", loadedBoard) + ".csv")) +# bomToCsv(bom, os.path.join(outputdir, expandNameTemplate(nametemplate, "bom", loadedBoard) + ".csv")) diff --git a/kikit/fab_ui.py b/kikit/fab_ui.py index af529094..857f2687 100644 --- a/kikit/fab_ui.py +++ b/kikit/fab_ui.py @@ -41,6 +41,20 @@ def jlcpcb(**kwargs): app = fakeKiCADGui() return execute_with_debug(jlcpcb.exportJlcpcb, kwargs) +@click.command() +@fabCommand +@click.option("--ignore", type=str, default="", help="Comma separated list of designators to exclude from SMT assembly") +@click.option("--autoname/--no-autoname", is_flag=True, help="Automatically name the output files based on the board name") +def gatema(**kwargs): + """ + Prepare fabrication files for Gatema PCB + """ + from kikit.fab import gatema + from kikit.common import fakeKiCADGui + app = fakeKiCADGui() + return execute_with_debug(gatema.exportGatema, kwargs) + + @click.command() @fabCommand @click.option("--assembly/--no-assembly", help="Generate files for SMT assembly (schematics is required)") @@ -120,6 +134,7 @@ def fab(): pass fab.add_command(jlcpcb) +fab.add_command(gatema) fab.add_command(pcbway) fab.add_command(oshpark) fab.add_command(neodenyy1) diff --git a/kikit/intervals.py b/kikit/intervals.py index 1e2cbffa..0a917bd9 100644 --- a/kikit/intervals.py +++ b/kikit/intervals.py @@ -1,7 +1,7 @@ from __future__ import annotations import typing from typing import Any, Dict, List, Optional, Union, Tuple, Callable, Iterable -from kikit.typing import Box, T, ComparableT +from kikit.kityping import Box, T, ComparableT from itertools import islice, chain from math import isclose from copy import copy diff --git a/kikit/typing.py b/kikit/kityping.py similarity index 100% rename from kikit/typing.py rename to kikit/kityping.py diff --git a/mkdocs.yml b/mkdocs.yml index 0e617060..f5f64b48 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,6 +63,7 @@ nav: - Introduction: fabrication/intro.md - Fab houses: - JLC PCB: fabrication/jlcpcb.md + - Gatema: fabrication/gatema.md - Pcb Way: fabrication/pcbway.md - OSH Park: fabrication/oshpark.md - Multiboard workflow: multiboard.md