Compare commits

..

27 Commits

Author SHA1 Message Date
Benny Megidish 5c5b59d520 chore: bump version 2024-10-14 22:25:48 +03:00
XiNGRZ 2a5e4a9aca Feat: Optionally use User.2 layer for alternative outline (#170)
* feat: optionally use User.2 layer for alternative outline #169

* chore: fix table of layer override fields
2024-10-14 22:02:02 +03:00
Lukas Lang 79da28d1bb Fix: Capitalize reference designators in generated files (#174) 2024-10-14 21:59:32 +03:00
Benny Megidish 545fc80d3b chore: readme 2024-07-07 21:15:43 +03:00
Benny Megidish 8ac70a9c18 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2024-07-07 21:12:14 +03:00
Benny Megidish 4cb3006fa8 feat: added options to readme #162 2024-07-07 21:12:09 +03:00
dzid26 94bc00fa2b feat: allow footprints with no pads (#166) 2024-07-03 21:43:07 +03:00
Lukas Lang 678032f041 chore: Update JLCPCB position alignment step size (#163) 2024-05-22 16:27:00 +03:00
Benny Megidish 006072f3b1 chore: added issue templates 2024-04-29 22:38:34 +03:00
Benny Megidish a3081153e6 chore: bump version 2024-04-25 12:21:20 +03:00
Benny Megidish 28ea32fedf chore: cleanup footprint position 2024-04-25 12:20:37 +03:00
Benny Megidish 05e10eba00 fix: dnp attribute invocation, closes #148 2024-04-25 12:08:28 +03:00
Benny Megidish 31690863e5 chore: reorder UI components and declerations 2024-04-21 12:11:55 +03:00
Benny Megidish da76ad54bc chore: bump version 2024-04-21 12:00:30 +03:00
Benny Megidish e19c0e411b feat: added extend edge cuts layer option #149 2024-04-21 00:31:03 +03:00
Benny Megidish 87df32ac65 chore: rename options vars 2024-04-21 00:19:10 +03:00
Benny Megidish 702e7e6ee9 feat: amended exclude DNP from BOM only option #159 #148 2024-04-20 21:26:14 +03:00
Benny Megidish f78ad8c141 feat: added auto fill option #151 2024-04-20 20:22:47 +03:00
Benny Megidish 8984740e74 chore: bump version 2024-04-16 17:16:46 +03:00
Alexander a43b555c90 feat: dynamic layers (#152)
Makes the additional layers text autocomplete and the gerber generator use the list of currently active layers

> * Add: utils to get dynamic layers
> 
> * Change: Use KiCad standard name for layers
> 
> * Add: description to new util funcs
> 
> * Fix: only include active layers in layer util funcs
> 
> * Fix: infinite loop
> 
> * Change: use util layer functions in processmanager
> 
> * Remove: hardcoded layers
> 
> * Change: use new util layer functions in dialog
> 
> * Change: include comma-separated hint in layer control
2024-04-09 19:53:27 +03:00
Arx ab0b336ed9 adjusting plot options and drill options to more closely match JLCPCB recommendations (#141)
Co-authored-by: Juraj Durajka <arx@macbook>
2024-03-14 12:58:12 +02:00
dzid26 88ae87bf48 fix: footprints without pads fix
* Skip components without pads (e.g. graphics)
- skip_footprint condition refactor.

* Unspecified footprint bounding box comments
2024-03-12 22:51:49 +02:00
Hannes Rüger 1192d6cc11 feat: remember settings (#136)
* fix #134: remember settings

* chore: move filename to variable in config file

* chore: rename config file

* feat: add new option

* fix: surround user options saving with try catch

* docs: add info about options to readme
2024-03-12 22:49:33 +02:00
Benny Megidish 266eb3bd31 feat: auto traslate as an option (#135) 2024-03-07 14:37:50 +02:00
Benny Megidish a3aea835b1 Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2024-03-02 21:46:53 +02:00
Benny Megidish c9e3ef16c0 chore: bump version 2024-03-02 21:46:46 +02:00
Benny Megidish 0917e9f199 Adds KiCad 8.99 compatibility patch (#132) 2024-03-01 10:38:01 +02:00
12 changed files with 308 additions and 118 deletions
+71
View File
@@ -0,0 +1,71 @@
name: Bug Report
description: Report an Fabrication Toolkit bug
title: "[Bug]: "
labels: "bug"
body:
- type: checkboxes
attributes:
label: Preflight Checklist
description: Please ensure you've completed all of the following.
options:
- label: I have searched the [issue tracker](https://www.github.com/bennymeg/Fabrication-Toolkit/issues) for a bug report that matches the one I want to file, without success.
required: true
- type: input
attributes:
label: Fabrication Toolkit Version
description: |
What version of Fabrication Toolkit are you using?
Note: Please only report issues for currently supported versions of Fabrication Toolkit.
placeholder: 4.0.0
validations:
required: true
- type: dropdown
attributes:
label: What operating system are you using?
options:
- Windows
- macOS
- Ubuntu
- Other Linux
- Other (specify below)
validations:
required: true
- type: input
attributes:
label: Operating System Version
description: What operating system version are you using? On Windows, click Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a.
placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04"
validations:
required: false
- type: dropdown
attributes:
label: What arch are you using?
options:
- x64
- ia32
- arm64 (including Apple Silicon)
- Other (specify below)
validations:
required: false
- type: input
attributes:
label: Last Known Working Fabrication Toolkit version
description: What is the last version of Fabrication Toolkit this worked in, if applicable?
placeholder: 4.0.0
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: A clear description of what actually happens.
validations:
required: true
- type: textarea
attributes:
label: Additional Information
description: If your problem needs further explanation, or if the issue you're seeing cannot be reproduced in a gist, please add more information here.
+4
View File
@@ -0,0 +1,4 @@
contact_links:
- name: Github Sponsor
url: https://github.com/sponsors/bennymeg
about: Help support Fabrication Toolkit by sponsoring us
@@ -0,0 +1,36 @@
name: Feature Request
description: Suggest an idea for Fabrication Toolkit
title: "[Feature Request]: "
labels: "enhancement"
body:
- type: checkboxes
attributes:
label: Preflight Checklist
description: Please ensure you've completed all of the following.
options:
- label: I have searched the [issue tracker](https://www.github.com/bennymeg/Fabrication-Toolkit/issues) for a feature request that matches the one I want to file, without success.
required: true
- type: textarea
attributes:
label: Problem Description
description: Please add a clear and concise description of the problem you are seeking to solve with this feature request.
validations:
required: true
- type: textarea
attributes:
label: Proposed Solution
description: Describe the solution you'd like in a clear and concise manner.
validations:
required: true
- type: textarea
attributes:
label: Alternatives Considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Additional Information
description: Add any other context about the problem here.
validations:
required: false
+17 -6
View File
@@ -31,10 +31,21 @@ Click on the Fabrication Toolkit <img src="https://github.com/bennymeg/JLC-Plugi
**⊛** Ensure your board is syncronized before invoking this addon [**F8**].
**⊛** The `User_1` layer in internally defined as a **V-Cuts** layer, please avoid using it for anything else. <span style="text-color: light-grey !important;">_(since v3.0.0)_.</span>
**⊛** The `User_1` layer in internally defined as a **V-Cuts** layer, please avoid using it for anything else (unless disabled). <span style="text-color: light-grey !important;">_(since v3.0.0)_.</span>
## Options
Options can be set in the dialog that appears when the plugin is invoked. They are saved in a file called `fabrication-toolkit-options.json` in the project directory so that they are remembered between invocations of the plugin.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/options.png?raw=true" height=275>
__Additional layers__: Comma-separated list of additional layers to include in the gerber archive.</br>
__Set User.1 as V-Cut layer__: Merge User.1 layer with the Edge-Cut layer in production.</br>
__Use User.2 for an alternative Edge-Cut layer__: Use the User.2 instead of the Edge-Cut layer for the board outline in production. This is useful if you need process edges or panelization during production but still want to keep the individual outline for prototyping, 3D model exports, or similar purposes.</br>
__Apply automatic translations__: Apply known translation fixes for common components.</br>
__Apply automatic fill for all zones__: Refill all zones before generation production files.</br>
__Exclude DNP components from BOM__: Exclude components the had been set a DNP from th BOM.</br>
### ① Include Component Part Number in Production Files
Add an 'LCSC Part #'* field with the LCSC component part number to the symbol's fields property.
@@ -104,7 +115,7 @@ Use following table to quickly find out to which coordinate enter the correction
|-90deg, Front or Back | down arrow | right arrow |
For custom angles it's best to place also a temporary straight symbol to perform alignment.
Single arrow press in JLC is about 0.07mm shift (8 clicks is about 0.5mm).
Single arrow press in JLC is 0.0635mm (= 1/400in) shift.
<img src="https://github.com/bennymeg/JLC-Plugin-for-KiCad/blob/master/assets/position.png?raw=true" height=420>
@@ -128,14 +139,14 @@ Some footprints may have their components defined on the opposite layer to there
Values can be `top`, `bottom`, `t` or `b`.
#### Primary Fields*:
| 'JLCPCB Layer Override' |
| --- |
| 'JLCPCB Layer Override' |
| --- |
_The fields will be queried in the order denoted above._
#### Fallback Fields*:
| 'JlcLayerOverride' | 'JLCLayerOverride' |
| --- | --- |
| 'JlcLayerOverride' | 'JLCLayerOverride' |
| --- | --- |
_The fields will be queried in the order denoted above._
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+6 -5
View File
@@ -2,24 +2,25 @@
"$schema": "https://go.kicad.org/pcm/schemas/v1",
"name": "Fabrication Toolkit",
"description": "JLC PCB fabrication toolkit",
"description_full": "Toolkit for automating PCB fabrication process with KiCad and JLC PCB. \n\n OPTIONS: \n\u2022 Include Component Part Number in Production Files \n Add an 'MPN' field with the LCSC component part number to the footprint component options. \n\n\u2022 Ignore Footprint in Production Files \n Select 'Exclude from position files' or 'Exclude from BOM' in the footprint properties fabrication attributes in order to ignore the footprint from the relevant file. \n\n\u2022 Rotate components \n Add an 'JLCPCB Rotation Offset' field with the additional counter-clockwise rotation in degrees to correct a mismatch between KiCad and JLC footprint orientation.",
"description_full": "Toolkit for automating PCB fabrication process with KiCad and JLC PCB. \n\n Features: \n\u2022 Generates gerber files in correct format for production. \n\u2022 Generates BOM file in correct format for production. \n\u2022 Generates Pick and Place file in correct format for assembly. \n\u2022 Automatic and manual component translations. \n\u2022 Many more unique additional features. \n\u2665 https://github.com/sponsors/bennymeg",
"identifier": "com.github.bennymeg.JLC-Plugin-for-KiCad",
"type": "plugin",
"author": {
"name": "Benny Megidish",
"contact": {
"web": "https://github.com/bennymeg/JLC-Plugin-for-KiCad"
"web": "https://github.com/bennymeg/Fabrication-Toolkit"
}
},
"maintainer": {
"name": "Benny Megidish",
"contact": {
"web": "https://github.com/bennymeg/JLC-Plugin-for-KiCad"
"web": "https://github.com/bennymeg/Fabrication-Toolkit"
}
},
"license": "Apache-2.0",
"resources": {
"homepage": "https://github.com/bennymeg/JLC-Plugin-for-KiCad"
"homepage": "https://github.com/bennymeg/Fabrication-Toolkit",
"Sponsor": "https://github.com/sponsors/bennymeg"
},
"tags": [
"jlc",
@@ -29,7 +30,7 @@
],
"versions": [
{
"version": "4.1.0",
"version": "4.5.0",
"status": "stable",
"kicad_version": "6.00"
}
+1 -44
View File
@@ -10,47 +10,4 @@ gerberArchiveName = 'gerbers.zip'
outputFolder = 'production'
bomRowLimit = 200
plotPlan = [
("F.Cu", pcbnew.F_Cu, "Top Layer"),
("B.Cu", pcbnew.B_Cu, "Bottom Layer"),
("In1.Cu", pcbnew.In1_Cu, "Internal plane 1"),
("In2.Cu", pcbnew.In2_Cu, "Internal plane 2"),
("In3.Cu", pcbnew.In3_Cu, "Internal plane 3"),
("In4.Cu", pcbnew.In4_Cu, "Internal plane 4"),
("In5.Cu", pcbnew.In5_Cu, "Internal plane 5"),
("In6.Cu", pcbnew.In6_Cu, "Internal plane 6"),
("In7.Cu", pcbnew.In7_Cu, "Internal plane 7"),
("In8.Cu", pcbnew.In8_Cu, "Internal plane 8"),
("In9.Cu", pcbnew.In9_Cu, "Internal plane 9"),
("In10.Cu", pcbnew.In10_Cu, "Internal plane 10"),
("In11.Cu", pcbnew.In11_Cu, "Internal plane 11"),
("In12.Cu", pcbnew.In12_Cu, "Internal plane 12"),
("In13.Cu", pcbnew.In13_Cu, "Internal plane 13"),
("In14.Cu", pcbnew.In14_Cu, "Internal plane 14"),
("In15.Cu", pcbnew.In15_Cu, "Internal plane 15"),
("In16.Cu", pcbnew.In16_Cu, "Internal plane 16"),
("In17.Cu", pcbnew.In17_Cu, "Internal plane 17"),
("In18.Cu", pcbnew.In18_Cu, "Internal plane 18"),
("In19.Cu", pcbnew.In19_Cu, "Internal plane 19"),
("In20.Cu", pcbnew.In20_Cu, "Internal plane 20"),
("In21.Cu", pcbnew.In21_Cu, "Internal plane 21"),
("In22.Cu", pcbnew.In22_Cu, "Internal plane 22"),
("In23.Cu", pcbnew.In23_Cu, "Internal plane 23"),
("In24.Cu", pcbnew.In24_Cu, "Internal plane 24"),
("In25.Cu", pcbnew.In25_Cu, "Internal plane 25"),
("In26.Cu", pcbnew.In26_Cu, "Internal plane 26"),
("In27.Cu", pcbnew.In27_Cu, "Internal plane 27"),
("In28.Cu", pcbnew.In28_Cu, "Internal plane 28"),
("In29.Cu", pcbnew.In29_Cu, "Internal plane 29"),
("In30.Cu", pcbnew.In30_Cu, "Internal plane 30"),
("F.SilkS", pcbnew.F_SilkS, "Top Silkscreen"),
("B.SilkS", pcbnew.B_SilkS, "Bottom Silkscreen"),
("F.Mask", pcbnew.F_Mask, "Top Soldermask"),
("B.Mask", pcbnew.B_Mask, "Bottom Soldermask"),
("F.Paste", pcbnew.F_Paste, "Top Paste (Stencil)"),
("B.Paste", pcbnew.B_Paste, "Bottom Paste (Stencil)"),
("Edge.Cuts", pcbnew.Edge_Cuts, "Board Outline"),
("User.Comments", pcbnew.Cmts_User, "User Comments")
]
layers = [layer[0] for layer in plotPlan]
optionsFileName = 'fabrication-toolkit-options.json'
+4
View File
@@ -1,3 +1,7 @@
AUTO_TRANSLATE_OPT = "AUTO TRANSLATE"
AUTO_FILL_OPT = "AUTO FILL"
EXCLUDE_DNP_OPT = "EXCLUDE DNP"
OUTPUT_NAME_OPT = "OUTPUT NAME"
EXTEND_EDGE_CUT_OPT = "EXTEND_EDGE_CUT"
ALTERNATIVE_EDGE_CUT_OPT = "ALTERNATIVE_EDGE_CUT"
EXTRA_LAYERS = "EXTRA_LAYERS"
+42 -9
View File
@@ -4,8 +4,8 @@ import pcbnew # type: ignore
from .thread import ProcessThread
from .events import StatusEvent
from .options import EXCLUDE_DNP_OPT, EXTRA_LAYERS
from .config import layers
from .options import AUTO_FILL_OPT, AUTO_TRANSLATE_OPT, EXCLUDE_DNP_OPT, EXTEND_EDGE_CUT_OPT, ALTERNATIVE_EDGE_CUT_OPT, EXTRA_LAYERS
from .utils import load_user_options, save_user_options, get_layer_names
# WX GUI form that show the plugin progress
@@ -27,23 +27,42 @@ class KiCadToJLCForm(wx.Frame):
self.SetSizeHints(wx.Size(600, 100), wx.DefaultSize)
userOptions = load_user_options({
EXTRA_LAYERS: "",
EXTEND_EDGE_CUT_OPT: False,
ALTERNATIVE_EDGE_CUT_OPT: False,
AUTO_TRANSLATE_OPT: True,
AUTO_FILL_OPT: True,
EXCLUDE_DNP_OPT: False
})
self.mOptionsLabel = wx.StaticText(self, label='Options:')
# self.mOptionsSeparator = wx.StaticLine(self)
self.mExcludeDnpCheckbox = wx.CheckBox(self, label='Exclude DNP components')
self.mExcludeDnpCheckbox.SetValue(False)
layers = get_layer_names(pcbnew.GetBoard())
self.mAdditionalLayersControl = wx.TextCtrl(self, size=wx.Size(600, 50))
self.mAdditionalLayersControl.Hint = "Additional layers"
self.mAdditionalLayersControl.Hint = "Additional layers (comma-separated)"
self.mAdditionalLayersControl.AutoComplete(layers)
self.mAdditionalLayersControl.Enable()
self.mAdditionalLayersControl.SetValue(userOptions[EXTRA_LAYERS])
self.mGenerateButton = wx.Button(self, label='Generate', size=wx.Size(600, 60))
self.mExtendEdgeCutsCheckbox = wx.CheckBox(self, label='Set User.1 as V-Cut layer')
self.mExtendEdgeCutsCheckbox.SetValue(userOptions[EXTEND_EDGE_CUT_OPT])
self.mAlternativeEdgeCutsCheckbox = wx.CheckBox(self, label='Use User.2 for alternative Edge-Cut layer')
self.mAlternativeEdgeCutsCheckbox.SetValue(userOptions[ALTERNATIVE_EDGE_CUT_OPT])
self.mAutomaticTranslationCheckbox = wx.CheckBox(self, label='Apply automatic translations')
self.mAutomaticTranslationCheckbox.SetValue(userOptions[AUTO_TRANSLATE_OPT])
self.mAutomaticFillCheckbox = wx.CheckBox(self, label='Apply automatic fill for all zones')
self.mAutomaticFillCheckbox.SetValue(userOptions[AUTO_FILL_OPT])
self.mExcludeDnpCheckbox = wx.CheckBox(self, label='Exclude DNP components from BOM')
self.mExcludeDnpCheckbox.SetValue(userOptions[EXCLUDE_DNP_OPT])
self.mGaugeStatus = wx.Gauge(
self, wx.ID_ANY, 100, wx.DefaultPosition, wx.Size(600, 20), wx.GA_HORIZONTAL)
self.mGaugeStatus.SetValue(0)
self.mGaugeStatus.Hide()
self.mGenerateButton = wx.Button(self, label='Generate', size=wx.Size(600, 60))
self.mGenerateButton.Bind(wx.EVT_BUTTON, self.onGenerateButtonClick)
boxSizer = wx.BoxSizer(wx.VERTICAL)
@@ -51,6 +70,10 @@ class KiCadToJLCForm(wx.Frame):
boxSizer.Add(self.mOptionsLabel, 0, wx.ALL, 5)
# boxSizer.Add(self.mOptionsSeparator, 0, wx.ALL, 5)
boxSizer.Add(self.mAdditionalLayersControl, 0, wx.ALL, 5)
boxSizer.Add(self.mExtendEdgeCutsCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mAlternativeEdgeCutsCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mAutomaticTranslationCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mAutomaticFillCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mExcludeDnpCheckbox, 0, wx.ALL, 5)
boxSizer.Add(self.mGaugeStatus, 0, wx.ALL, 5)
boxSizer.Add(self.mGenerateButton, 0, wx.ALL, 5)
@@ -64,12 +87,22 @@ class KiCadToJLCForm(wx.Frame):
def onGenerateButtonClick(self, event):
options = dict()
options[EXCLUDE_DNP_OPT] = self.mExcludeDnpCheckbox.GetValue()
options[EXTRA_LAYERS] = self.mAdditionalLayersControl.GetValue()
options[EXTEND_EDGE_CUT_OPT] = self.mExtendEdgeCutsCheckbox.GetValue()
options[ALTERNATIVE_EDGE_CUT_OPT] = self.mAlternativeEdgeCutsCheckbox.GetValue()
options[AUTO_TRANSLATE_OPT] = self.mAutomaticTranslationCheckbox.GetValue()
options[AUTO_FILL_OPT] = self.mAutomaticFillCheckbox.GetValue()
options[EXCLUDE_DNP_OPT] = self.mExcludeDnpCheckbox.GetValue()
save_user_options(options)
self.mAdditionalLayersControl.Hide()
self.mExcludeDnpCheckbox.Hide()
self.mOptionsLabel.Hide()
self.mAdditionalLayersControl.Hide()
self.mExtendEdgeCutsCheckbox.Hide()
self.mAlternativeEdgeCutsCheckbox.Hide()
self.mAutomaticTranslationCheckbox.Hide()
self.mAutomaticFillCheckbox.Hide()
self.mExcludeDnpCheckbox.Hide()
self.mGenerateButton.Hide()
self.mGaugeStatus.Show()
+64 -42
View File
@@ -12,7 +12,7 @@ from typing import Tuple
# Interaction with KiCad.
import pcbnew # type: ignore
from .utils import footprint_has_field, footprint_get_field
from .utils import footprint_has_field, footprint_get_field, get_plot_plan
# Application definitions.
from .config import *
@@ -42,7 +42,7 @@ class ProcessManager:
# Finally rebuild the connectivity db
self.board.BuildConnectivity()
def generate_gerber(self, temp_dir, extra_layers):
def generate_gerber(self, temp_dir, extra_layers, extend_edge_cuts, alternative_edge_cuts):
'''Generate the Gerber files.'''
settings = self.board.GetDesignSettings()
settings.m_SolderMaskMargin = 50000
@@ -59,9 +59,10 @@ class ProcessManager:
plot_options.SetScale(1)
plot_options.SetMirror(False)
plot_options.SetUseGerberAttributes(True)
plot_options.SetUseGerberProtelExtensions(False)
plot_options.SetUseGerberProtelExtensions(True)
plot_options.SetUseAuxOrigin(True)
plot_options.SetSubtractMaskFromSilk(True)
plot_options.SetUseGerberX2format(False)
plot_options.SetDrillMarksType(0) # NO_DRILL_SHAPE
if hasattr(plot_options, "SetExcludeEdgeLayer"):
@@ -72,17 +73,23 @@ class ProcessManager:
else:
extra_layers = []
for layer_info in plotPlan:
for layer_info in get_plot_plan(self.board):
if self.board.IsLayerEnabled(layer_info[1]) or layer_info[0] in extra_layers:
plot_controller.SetLayer(layer_info[1])
plot_controller.OpenPlotfile(layer_info[0], pcbnew.PLOT_FORMAT_GERBER, layer_info[2])
if layer_info[1] == pcbnew.Edge_Cuts and hasattr(plot_controller, 'PlotLayers'):
if layer_info[1] == pcbnew.Edge_Cuts and hasattr(plot_controller, 'PlotLayers') and (extend_edge_cuts or alternative_edge_cuts):
seq = pcbnew.LSEQ()
# uses User_2 layer for alternative Edge_Cuts layer
if alternative_edge_cuts:
seq.push_back(pcbnew.User_2)
else:
seq.push_back(layer_info[1])
# includes User_1 layer with Edge_Cuts layer to allow V Cuts to be defined as User_1 layer
# available for KiCad 7.0.1+
seq = pcbnew.LSEQ()
seq.push_back(layer_info[1])
seq.push_back(pcbnew.User_1)
if extend_edge_cuts:
seq.push_back(layer_info[1])
seq.push_back(pcbnew.User_1)
plot_controller.PlotLayers(seq)
else:
plot_controller.PlotLayer()
@@ -95,11 +102,12 @@ class ProcessManager:
drill_writer.SetOptions(
False,
True,
False,
self.board.GetDesignSettings().GetAuxOrigin(),
False)
drill_writer.SetFormat(False)
drill_writer.CreateDrillandMapFilesSet(temp_dir, True, False)
drill_writer.SetFormat(True)
drill_writer.SetMapFileFormat(pcbnew.PLOT_FORMAT_GERBER)
drill_writer.CreateDrillandMapFilesSet(temp_dir, True, True)
def generate_netlist(self, temp_dir):
'''Generate the connection netlist.'''
@@ -108,18 +116,26 @@ class ProcessManager:
def _get_footprint_position(self, footprint):
"""Calculate position based on center of bounding box."""
if footprint.GetAttributes() & pcbnew.FP_SMD:
return footprint.GetPosition()
if footprint.GetAttributes() & pcbnew.FP_THROUGH_HOLE:
return footprint.GetBoundingBox(False, False).GetCenter()
pads = footprint.Pads()
#get bounding box based on pads only to ignore non-copper layers, e.g. silkscreen
bbox = pads[0].GetBoundingBox()
for pad in pads:
bbox.Merge(pad.GetBoundingBox())
return bbox.GetCenter()
position = footprint.GetPosition()
attributes = footprint.GetAttributes()
def generate_tables(self, temp_dir, exclude_dnp):
if attributes & pcbnew.FP_SMD:
position = footprint.GetPosition()
elif attributes & pcbnew.FP_THROUGH_HOLE:
position = footprint.GetBoundingBox(False, False).GetCenter()
else: # handle Unspecified footprint type
pads = footprint.Pads()
if len(pads) > 0:
# get bounding box based on pads only to ignore non-copper layers, e.g. silkscreen
bbox = pads[0].GetBoundingBox() # start with small bounding box
for pad in pads:
bbox.Merge(pad.GetBoundingBox()) # expand bounding box
position = bbox.GetCenter()
return position
def generate_tables(self, temp_dir, auto_translate, exclude_dnp):
'''Generate the data tables.'''
if hasattr(self.board, 'GetModules'):
footprints = list(self.board.GetModules())
@@ -127,13 +143,13 @@ class ProcessManager:
footprints = list(self.board.GetFootprints())
# sort footprint after designator
footprints.sort(key=lambda x: x.GetReference())
footprints.sort(key=lambda x: x.GetReference().upper())
# unique designator dictionary
footprint_designators = defaultdict(int)
for i, footprint in enumerate(footprints):
# count unique designators
footprint_designators[footprint.GetReference()] += 1
footprint_designators[footprint.GetReference().upper()] += 1
bom_designators = footprint_designators.copy()
if len(footprint_designators.items()) > 0:
@@ -155,20 +171,22 @@ class ProcessManager:
# 2: 'unspecified'
# }.get(footprint.GetAttributes())
skip_footprint = exclude_dnp and (footprint_has_field(footprint, 'dnp')
or footprint.GetValue().upper() == 'DNP'
or getattr(footprint, 'IsDNP', bool)())
is_dnp = (footprint_has_field(footprint, 'dnp')
or (footprint.GetValue().upper() == 'DNP')
or getattr(footprint, 'IsDNP', bool)())
skip_dnp = exclude_dnp and is_dnp
if not (footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_POS_FILES) and not skip_footprint:
if not (footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_POS_FILES) and not is_dnp:
# append unique ID if duplicate footprint designator
unique_id = ""
if footprint_designators[footprint.GetReference()] > 1:
unique_id = str(footprint_designators[footprint.GetReference()])
footprint_designators[footprint.GetReference()] -= 1
if footprint_designators[footprint.GetReference().upper()] > 1:
unique_id = str(footprint_designators[footprint.GetReference().upper()])
footprint_designators[footprint.GetReference().upper()] -= 1
designator = "{}{}{}".format(footprint.GetReference(), "" if unique_id == "" else "_", unique_id)
mid_x = (self._get_footprint_position(footprint)[0] - self.board.GetDesignSettings().GetAuxOrigin()[0]) / 1000000.0
mid_y = (self._get_footprint_position(footprint)[1] - self.board.GetDesignSettings().GetAuxOrigin()[1]) * -1.0 / 1000000.0
designator = "{}{}{}".format(footprint.GetReference().upper(), "" if unique_id == "" else "_", unique_id)
position = self._get_footprint_position(footprint)
mid_x = (position[0] - self.board.GetDesignSettings().GetAuxOrigin()[0]) / 1000000.0
mid_y = (position[1] - self.board.GetDesignSettings().GetAuxOrigin()[1]) * -1.0 / 1000000.0
rotation = footprint.GetOrientation().AsDegrees() if hasattr(footprint.GetOrientation(), 'AsDegrees') else footprint.GetOrientation() / 10.0
rotation_offset_db = self._get_rotation_from_db(footprint_name) # internal database offset
rotation_offset_manual = self._get_rotation_offset_from_footprint(footprint) # explicated offset by the designer
@@ -187,7 +205,11 @@ class ProcessManager:
# JLC expect 'Rotation' to be 'as viewed from above component', so bottom needs inverting, and ends up 180 degrees out as well
if layer == 'bottom':
rotation = (180.0 - rotation)
rotation = (rotation + rotation_offset_db + rotation_offset_manual) % 360.0
if auto_translate:
rotation += rotation_offset_db
rotation = (rotation + rotation_offset_manual) % 360.0
self.components.append({
'Designator': designator,
@@ -197,12 +219,12 @@ class ProcessManager:
'Layer': layer,
})
if not (footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM) and not skip_footprint:
if not (footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM) and not skip_dnp:
# append unique ID if we are dealing with duplicate bom designator
unique_id = ""
if bom_designators[footprint.GetReference()] > 1:
unique_id = str(bom_designators[footprint.GetReference()])
bom_designators[footprint.GetReference()] -= 1
if bom_designators[footprint.GetReference().upper()] > 1:
unique_id = str(bom_designators[footprint.GetReference().upper()])
bom_designators[footprint.GetReference().upper()] -= 1
# merge similar parts into single entry
insert = True
@@ -213,7 +235,7 @@ class ProcessManager:
under_limit = component['Quantity'] < bomRowLimit
if same_footprint and same_value and same_lcsc and under_limit:
component['Designator'] += ", " + "{}{}{}".format(footprint.GetReference(), "" if unique_id == "" else "_", unique_id)
component['Designator'] += ", " + "{}{}{}".format(footprint.GetReference().upper(), "" if unique_id == "" else "_", unique_id)
component['Quantity'] += 1
insert = False
break
@@ -221,7 +243,7 @@ class ProcessManager:
# add component to BOM
if insert:
self.bom.append({
'Designator': "{}{}{}".format(footprint.GetReference(), "" if unique_id == "" else "_", unique_id),
'Designator': "{}{}{}".format(footprint.GetReference().upper(), "" if unique_id == "" else "_", unique_id),
'Footprint': self._normalize_footprint_name(footprint_name),
'Quantity': 1,
'Value': footprint.GetValue(),
+4 -3
View File
@@ -35,11 +35,12 @@ class ProcessThread(Thread):
try:
# Verify all zones are up-to-date
self.progress(10)
self.process_manager.update_zone_fills()
if (self.options[AUTO_FILL_OPT]):
self.process_manager.update_zone_fills()
# generate gerber
self.progress(20)
self.process_manager.generate_gerber(temp_dir_gerber, self.options[EXTRA_LAYERS])
self.process_manager.generate_gerber(temp_dir_gerber, self.options[EXTRA_LAYERS], self.options[EXTEND_EDGE_CUT_OPT], self.options[ALTERNATIVE_EDGE_CUT_OPT])
# generate drill file
self.progress(30)
@@ -51,7 +52,7 @@ class ProcessThread(Thread):
# generate data tables
self.progress(50)
self.process_manager.generate_tables(temp_dir, self.options[EXCLUDE_DNP_OPT])
self.process_manager.generate_tables(temp_dir, self.options[AUTO_TRANSLATE_OPT], self.options[EXCLUDE_DNP_OPT])
# generate pick and place file
self.progress(60)
+59 -9
View File
@@ -1,28 +1,78 @@
import pcbnew # type: ignore
import os
import json
from .config import optionsFileName
import wx
def get_version():
return float('.'.join(pcbnew.GetBuildVersion().split(".")[0:2])) #e.g GetBuildVersion(): e.g. '7.99.0-3969-gc5ac2337e4'
return float('.'.join(pcbnew.GetBuildVersion().split(".")[0:2])) # e.g GetBuildVersion(): e.g. '7.99.0-3969-gc5ac2337e4'
def is_v8():
version = get_version()
def is_v9(version = get_version()):
return version >= 8.99 and version < 9.99
def is_v8(version = get_version()):
return version >= 7.99 and version < 8.99
def is_v7():
version = get_version()
def is_v7(version = get_version()):
return version >= 6.99 and version < 7.99
def is_v6():
version = get_version()
def is_v6(version = get_version()):
return version >= 5.99 and version < 6.99
def footprint_has_field(footprint, field_name):
if is_v8():
version = get_version()
if is_v8(version) or is_v9(version):
return footprint.HasFieldByName(field_name)
else:
return footprint.HasProperty(field_name)
def footprint_get_field(footprint, field_name):
if is_v8():
version = get_version()
if is_v8(version) or is_v9(version):
return footprint.GetFieldByName(field_name).GetText()
else:
return footprint.GetProperty(field_name)
def get_user_options_file_path():
boardFilePath = pcbnew.GetBoard().GetFileName()
return os.path.join(os.path.dirname(boardFilePath), optionsFileName)
def load_user_options(default_options):
try:
with open(get_user_options_file_path(), 'r') as f:
user_options = json.load(f)
except:
user_options = default_options
# merge the user options with the default options
options = default_options.copy()
options.update(user_options)
return options
def save_user_options(options):
try:
with open(get_user_options_file_path(), 'w') as f:
json.dump(options, f)
except:
wx.MessageBox("Error saving user options", "Error", wx.OK | wx.ICON_ERROR)
def get_plot_plan(board, active_only=True):
"""Returns `(KiCad standard name, layer id, custom user name)` of all (active) layers of the given board."""
layers = []
i = pcbnew.PCBNEW_LAYER_ID_START - 1
while i < pcbnew.PCBNEW_LAYER_ID_START + pcbnew.PCB_LAYER_ID_COUNT - 1:
i += 1
if active_only and not board.IsLayerEnabled(i):
continue
layer_std_name = pcbnew.BOARD.GetStandardLayerName(i)
layer_name = pcbnew.BOARD.GetLayerName(board, i)
layers.append((layer_std_name, i, layer_name))
return layers
def get_layer_names(board, active_only=True):
"""Returns a list of (active) layer names of the current board"""
plotPlan = get_plot_plan(board, active_only)
return [layer_info[0] for layer_info in plotPlan]