-
-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
227 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Fabrication: Neoden YY1 | ||
|
||
The basic usage of this exporter is: | ||
``` | ||
kikit fab neodenyy1 [OPTIONS] BOARD OUTPUTDIR | ||
``` | ||
|
||
When you run this command, you will find file `top_pos.csv` and `bottom_pos.csv` | ||
in `OUTPUTDIR`. This file can be used in Neoden YY1. KiKit automatically detects | ||
the number of layers. | ||
|
||
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. | ||
|
||
## Assembly | ||
|
||
For Neoden YY1 you must specify `--assembly` option and provide the board | ||
`--schematic <schematics_file>`. KiKit will generate files: `top_pos.csv` | ||
(top layer component placement) and `bottom_pos.csv` (bottom layer component | ||
placement). Use these two files to assembly PCB on machine. | ||
|
||
On Neoden YY1, the position origin must use the bottom left corner of the board | ||
edge. | ||
|
||
## Correction of the Footprint Position | ||
|
||
It is possible that orientation footprints in your SMD does not match the | ||
orientation of the components in the SMD assembly service. There are two | ||
solutions: | ||
|
||
- correct the orientation in the library or | ||
- apply KiKit's orientation corrections. | ||
|
||
The first option is not always feasible - e.g., when you use KiCAD's built-in | ||
libraries or you are preparing a board for multiple fabrication houses and each | ||
of them uses a different orientation. | ||
|
||
KiKit allows you to specify the origin and orientation correction of the | ||
position. The correction is specified by `YY1_CORRECTION` field. The field | ||
value is a semicolon separated tuple: `<X>; <Y>; <Rotation>` with values in | ||
millimeters and degrees. You can read the XY corrections by hovering cursor over | ||
the intended origin in footprint editor and mark the coordinates. Note that | ||
first the rotation correction is applied, then the translation. Usually, you | ||
will need only the rotation correction. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import click | ||
from pcbnewTransition import pcbnew | ||
import csv | ||
import os | ||
import sys | ||
import shutil | ||
import re | ||
from pathlib import Path | ||
from kikit.fab.common import * | ||
from kikit.common import * | ||
from kikit.units import mm | ||
|
||
FOOTPRIINTREGEX = { | ||
re.compile(r'Capacitor_SMD:C_(\d+)_.*'): 'C_{}', | ||
re.compile(r'Diode_SMD:D_(\d+)_.*'): 'D_{}', | ||
re.compile(r'Inductor_SMD:L_(\d+)_.*'): 'L_{}', | ||
re.compile(r'Resistor_SMD:R_(\d+)_.*'): 'R_{}', | ||
re.compile(r'Crystal:Crystal_SMD_(.*?)_.*'): 'CRYSTAL_{}' | ||
} | ||
|
||
def collectBom(components, 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 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 | ||
cType = ( | ||
getField(c, "Value"), | ||
getField(c, "Footprint") | ||
) | ||
bom[cType] = bom.get(cType, []) + [reference] | ||
return bom | ||
|
||
def transcodeFootprint(footprint): | ||
for pattern, replacement in FOOTPRIINTREGEX.items(): | ||
matchedFootprint = pattern.match(footprint) | ||
if matchedFootprint != None: | ||
return replacement.format(matchedFootprint.groups()[0]) | ||
matchedFootprint = footprint.split(':') | ||
if len(matchedFootprint) > 1: | ||
return matchedFootprint[1].split('_')[0] | ||
else: | ||
return footprint | ||
|
||
def posDataProcess(posData, pcbSize, bom): | ||
topLayer = [] | ||
bottomLayer = [] | ||
ref = {} | ||
for cType, references in bom.items(): | ||
sortedReferences = sorted(references, key=naturalComponentKey) | ||
for refComponent in sortedReferences: | ||
ref[refComponent] = cType | ||
for line in posData: | ||
if line[0] in ref: | ||
value, footprint = ref[line[0]] | ||
if line[3] == 'T': | ||
topLayer.append(line + (value, footprint, line[1], line[2],)) | ||
elif line[3] == 'B': | ||
# Neoden YY1 need the position on the bottom layer need position origin from bottom right corner, but KiCad only support one origin on all layer, so calculate it by using PCB BoundingBox Width | ||
bottomLayer.append(line + (value, footprint, pcbSize[1] - line[1], line[2], )) | ||
else: | ||
value = None | ||
footprint = None | ||
if line[3] == 'T': | ||
topLayer.append(line + (value, footprint, line[1], line[2],)) | ||
elif line[3] == 'B': | ||
# Neoden YY1 need the position on the bottom layer need position origin from bottom right corner, but KiCad only support one origin on all layer, so calculate it by using PCB BoundingBox Width | ||
bottomLayer.append(line + (value, footprint, pcbSize[1] - line[1], line[2],)) | ||
return (topLayer, bottomLayer) | ||
|
||
""" | ||
Export pos file for Neoden YY1 | ||
Neoden YY1 file is in csv format. | ||
""" | ||
def posDataToCSV(layerData, prepend, filename): | ||
basename = os.path.basename(filename) | ||
basename = prepend + '_' + basename | ||
dirname = os.path.dirname(filename) | ||
with open(os.path.join(dirname, basename), "w", newline="", encoding="utf-8") as csvfile: | ||
writer = csv.writer(csvfile) | ||
# First line is fixed with `NEODEN,YY1,P&P FILE,,,,,,,,,,,` | ||
writer.writerow(["NEODEN","YY1","P&P FILE","","","","","","","","","","",""]) | ||
writer.writerow(["","","","","","","","","","","","","",""]) | ||
# This line is for Panelized, make it deafult to not panelized, if anyone need panelized assembly, just change it on the machine. | ||
writer.writerow(["PanelizedPCB","UnitLength","0","UnitWidth","0","Rows","1","Columns","1",""]) | ||
writer.writerow(["","","","","","","","","","","","","",""]) | ||
# Neoden YY1 only support one Fiducial on the board, make Fiducial as 0 to disable Fiducial correction method, if anyone need it just set it on the machine. | ||
# OverallOffset is the global offset. This depends on the real task, just ignore it and set it on the machine when you need. | ||
writer.writerow(["Fiducial","1-X","0","1-Y","0","OverallOffsetX","0.00","OverallOffsetY","0.00",""]) | ||
writer.writerow(["","","","","","","","","","","","","",""]) | ||
# Automatic Nozzle Changer, Neoden YY1 only support 4 Nozzle Change task in one project. Nozzle Setting and Nozzle Station Setting is different for every user, so disable it by default, edit it by user when needed. | ||
# ["NozzleChange","(Enable Nozzle change task? ON/OFF)","BeforeComponent","1","Head1","Drop","Station2","PickUp","Station1",""] | ||
writer.writerow(["NozzleChange","OFF","BeforeComponent","1","Head1","Drop","Station2","PickUp","Station1",""]) | ||
writer.writerow(["NozzleChange","OFF","BeforeComponent","2","Head2","Drop","Station3","PickUp","Station2",""]) | ||
writer.writerow(["NozzleChange","OFF","BeforeComponent","1","Head1","Drop","Station1","PickUp","Station1",""]) | ||
writer.writerow(["NozzleChange","OFF","BeforeComponent","1","Head1","Drop","Station1","PickUp","Station1",""]) | ||
writer.writerow(["","","","","","","","","","","","","",""]) | ||
# Neoden YY1 using Comment and Footprint for batch feeder selection when in edit mode. Neoden YY1 only support 2 decimal digits. | ||
# "Head" is for Picker, it has two picker, 0 for all picker, 1 for picker 1, 2 for picker 2. | ||
# "FeederNo" to define which feeder should be use, every user have different feeder setting, so just make it to use feeder 1 and left for user to edit. | ||
# "Mode" is how to confirm the component is picked, 0 - disable, 1 - camera, 2 - vacuum, 3 - camera and vacuum, 4 - camera for big IC | ||
# "Skip" should this line skipped by machine? | ||
writer.writerow(["Designator","Comment","Footprint","Mid X(mm)","Mid Y(mm)","Rotation","Head","FeederNo","Mount Speed(%)","Pick Height(mm)","Place Height(mm)","Mode","Skip"]) | ||
for line in sorted(layerData, key=lambda x: naturalComponentKey(x[0])): | ||
line = list(line) | ||
skip = "0" | ||
if line[5] == None or line[6] == None: | ||
skip = "1" | ||
line[5] = "Unknown" | ||
line[6] = "Unknown" | ||
line = [line[0], line[5], transcodeFootprint(line[6]), line[7], line[8], line[4], "0", "1", "100", "0", "0", "1", skip] | ||
for i in [3, 4, 5]: | ||
line[i] = f"{line[i]:.2f}" # Most Fab houses expect only 2 decimal digits | ||
writer.writerow(line) | ||
|
||
def posDataToFile(posData, pcbSize, bom, filename): | ||
topLayer, bottomLayer = posDataProcess(posData=posData,pcbSize = pcbSize, bom=bom) | ||
posDataToCSV(topLayer, 'top', filename) | ||
posDataToCSV(bottomLayer, 'bottom', filename) | ||
|
||
def exportNeodenYY1(board, outputdir, schematic, ignore, | ||
corrections, correctionpatterns, nametemplate, drc): | ||
if schematic is None: | ||
raise RuntimeError("When outputing assembly data, schematic is required") | ||
|
||
ensureValidBoard(board) | ||
loadedBoard = pcbnew.LoadBoard(board) | ||
|
||
if drc: | ||
ensurePassingDrc(loadedBoard) | ||
|
||
refsToIgnore = parseReferences(ignore) | ||
removeComponents(loadedBoard, refsToIgnore) | ||
Path(outputdir).mkdir(parents=True, exist_ok=True) | ||
|
||
ensureValidSch(schematic) | ||
|
||
correctionFields = [x.strip() for x in corrections.split(",")] | ||
components = extractComponents(schematic) | ||
bom = collectBom(components, refsToIgnore) | ||
|
||
posData = collectPosData(loadedBoard, correctionFields, | ||
bom=components, posFilter=noFilter, correctionFile=correctionpatterns) | ||
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} | ||
|
||
boundingBox = loadedBoard.GetBoardEdgesBoundingBox() | ||
pcbSize = (boundingBox.GetHeight() / mm, boundingBox.GetWidth() / mm, ) | ||
posDataToFile(posData, pcbSize, bom, os.path.join(outputdir, expandNameTemplate(nametemplate, "pos", loadedBoard) + ".csv")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63301c2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kikit
63301c2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?