Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c5b59d520 | |||
| 2a5e4a9aca | |||
| 79da28d1bb | |||
| 545fc80d3b | |||
| 8ac70a9c18 | |||
| 4cb3006fa8 | |||
| 94bc00fa2b | |||
| 678032f041 | |||
| 006072f3b1 | |||
| a3081153e6 | |||
| 28ea32fedf | |||
| 05e10eba00 | |||
| 31690863e5 | |||
| da76ad54bc | |||
| e19c0e411b | |||
| 87df32ac65 | |||
| 702e7e6ee9 | |||
| f78ad8c141 | |||
| 8984740e74 | |||
| a43b555c90 | |||
| ab0b336ed9 | |||
| 88ae87bf48 | |||
| 1192d6cc11 | |||
| 266eb3bd31 | |||
| a3aea835b1 | |||
| c9e3ef16c0 | |||
| 0917e9f199 |
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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'
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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]
|
||||
Reference in New Issue
Block a user