Compare commits

..

23 Commits

Author SHA1 Message Date
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
Benny Megidish bce98f72c3 chore: bump version 2024-02-29 15:42:26 +02:00
Benny Megidish 84e933b566 fix: extra layers filter 2024-02-28 07:53:29 +02:00
Benny Megidish 3714af0afb Merge branch 'master' of https://github.com/bennymeg/JLC-Plugin-for-KiCad 2024-02-27 22:42:03 +02:00
Benny Megidish b80a14dfbb feat: extra layers support (#113) 2024-02-27 22:41:54 +02:00
Maksim Salau bfa87db797 ifix: Use IsDNP() to get the DNP attribute in KiCAD v8 (#128)
`footprint_has_field(footprint, 'dnp')` returns `False` for DNP
components in KiCAD v8.

In KiCAD v8 the DNP flag is available via either the IsDNP() getter or
the FP_DNP attribute.

The added `getattr()` call turns into `footprint.IsDNP()` in KiCAD v8 or
`bool()` (which evaluates into `False`) in KiCAD v7 and earlier.
2024-02-27 22:16:20 +02:00
8 changed files with 167 additions and 91 deletions
+2
View File
@@ -35,6 +35,8 @@ Click on the Fabrication Toolkit <img src="https://github.com/bennymeg/JLC-Plugi
## 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.
### ① Include Component Part Number in Production Files
Add an 'LCSC Part #'* field with the LCSC component part number to the symbol's fields property.
+4 -4
View File
@@ -8,18 +8,18 @@
"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"
},
"tags": [
"jlc",
@@ -29,7 +29,7 @@
],
"versions": [
{
"version": "4.0.0",
"version": "4.4.1",
"status": "stable",
"kicad_version": "6.00"
}
+1 -42
View File
@@ -10,45 +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")
]
optionsFileName = 'fabrication-toolkit-options.json'
+5 -1
View File
@@ -1,2 +1,6 @@
AUTO_TRANSLATE_OPT = "AUTO TRANSLATE"
AUTO_FILL_OPT = "AUTO FILL"
EXCLUDE_DNP_OPT = "EXCLUDE DNP"
OUTPUT_NAME_OPT = "OUTPUT NAME"
OUTPUT_NAME_OPT = "OUTPUT NAME"
EXTEND_EDGE_CUT_OPT = "EXTEND_EDGE_CUT"
EXTRA_LAYERS = "EXTRA_LAYERS"
+42 -6
View File
@@ -1,10 +1,11 @@
import os
import wx
import pcbnew # type: ignore
import pcbnew # type: ignore
from .thread import ProcessThread
from .events import StatusEvent
from .options import EXCLUDE_DNP_OPT
from .options import AUTO_FILL_OPT, AUTO_TRANSLATE_OPT, EXCLUDE_DNP_OPT, EXTEND_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
@@ -26,24 +27,49 @@ class KiCadToJLCForm(wx.Frame):
self.SetSizeHints(wx.Size(600, 100), wx.DefaultSize)
userOptions = load_user_options({
EXTRA_LAYERS: "",
EXTEND_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)
self.mGenerateButton = wx.Button(self, label='Generate', size=wx.Size(600, 60))
layers = get_layer_names(pcbnew.GetBoard())
self.mAdditionalLayersControl = wx.TextCtrl(self, size=wx.Size(600, 50))
self.mAdditionalLayersControl.Hint = "Additional layers (comma-separated)"
self.mAdditionalLayersControl.AutoComplete(layers)
self.mAdditionalLayersControl.Enable()
self.mAdditionalLayersControl.SetValue(userOptions[EXTRA_LAYERS])
self.mExtendEdgeCutsCheckbox = wx.CheckBox(self, label='Set User.1 as V-Cut layer')
self.mExtendEdgeCutsCheckbox.SetValue(userOptions[EXTEND_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)
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.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)
@@ -57,10 +83,20 @@ class KiCadToJLCForm(wx.Frame):
def onGenerateButtonClick(self, event):
options = dict()
options[EXTRA_LAYERS] = self.mAdditionalLayersControl.GetValue()
options[EXTEND_EDGE_CUT_OPT] = self.mExtendEdgeCutsCheckbox.GetValue()
options[AUTO_TRANSLATE_OPT] = self.mAutomaticTranslationCheckbox.GetValue()
options[AUTO_FILL_OPT] = self.mAutomaticFillCheckbox.GetValue()
options[EXCLUDE_DNP_OPT] = self.mExcludeDnpCheckbox.GetValue()
self.mExcludeDnpCheckbox.Hide()
save_user_options(options)
self.mOptionsLabel.Hide()
self.mAdditionalLayersControl.Hide()
self.mExtendEdgeCutsCheckbox.Hide()
self.mAutomaticTranslationCheckbox.Hide()
self.mAutomaticFillCheckbox.Hide()
self.mExcludeDnpCheckbox.Hide()
self.mGenerateButton.Hide()
self.mGaugeStatus.Show()
+50 -26
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):
def generate_gerber(self, temp_dir, extra_layers, extend_edge_cuts):
'''Generate the Gerber files.'''
settings = self.board.GetDesignSettings()
settings.m_SolderMaskMargin = 50000
@@ -59,20 +59,26 @@ 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"):
plot_options.SetExcludeEdgeLayer(True)
for layer_info in plotPlan:
if self.board.IsLayerEnabled(layer_info[1]):
if extra_layers is not None:
extra_layers = [element.strip() for element in extra_layers.strip().split(',') if element.strip()]
else:
extra_layers = []
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:
# 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()
@@ -90,11 +96,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.'''
@@ -103,18 +110,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())
@@ -150,9 +165,13 @@ class ProcessManager:
# 2: 'unspecified'
# }.get(footprint.GetAttributes())
skip_footprint = exclude_dnp and (footprint_has_field(footprint, 'dnp') or footprint.GetValue().upper() == 'DNP')
is_dnp = (footprint_has_field(footprint, 'dnp')
or (footprint.GetValue().upper() == 'DNP')
or getattr(footprint, 'IsDNP', bool)())
skip_dnp = exclude_dnp and is_dnp
skip_footprint = footprint.GetPadCount() == 0
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 skip_footprint and not is_dnp:
# append unique ID if duplicate footprint designator
unique_id = ""
if footprint_designators[footprint.GetReference()] > 1:
@@ -160,8 +179,9 @@ class ProcessManager:
footprint_designators[footprint.GetReference()] -= 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
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
@@ -180,7 +200,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,
@@ -190,7 +214,7 @@ 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_footprint and not skip_dnp:
# append unique ID if we are dealing with duplicate bom designator
unique_id = ""
if bom_designators[footprint.GetReference()] > 1:
+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.process_manager.generate_gerber(temp_dir_gerber, self.options[EXTRA_LAYERS], self.options[EXTEND_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]