Skip to content

Commit 80f1f92

Browse files
committed
[Layer] Added KiPy support to all Layer members
1 parent c537f91 commit 80f1f92

5 files changed

Lines changed: 230 additions & 57 deletions

File tree

kibot/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ def get_layers_info():
480480
def detect_kicad():
481481
import_kicad_api()
482482
get_kicad_version()
483+
GS.set_version_pointers()
483484
get_layers_info()
484485

485486
# Setup details dependent on the API version

kibot/gs.py

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,33 +1242,90 @@ def module_position(m):
12421242
return f'({GS.to_mm(pos.x)}, {GS.to_mm(pos.y)}) mm'
12431243

12441244
@staticmethod
1245-
def layer_is_inner(id):
1246-
return pcbnew.IsInnerCopperLayer(id) if GS.ki9 else id > pcbnew.F_Cu and id < pcbnew.B_Cu
1245+
def set_version_pointers():
1246+
""" Used to setup function pointers according to the API and its version """
1247+
if GS.pn is not None:
1248+
if GS.ki9:
1249+
GS.layer_is_inner = GS.layer_is_inner_k9
1250+
GS.ordinal_to_copper_layer = GS.ordinal_to_copper_layer_k9
1251+
GS.copper_layer_to_ordinal = GS.copper_layer_to_ordinal_k9
1252+
else:
1253+
GS.layer_is_inner = GS.layer_is_inner_k5
1254+
GS.ordinal_to_copper_layer = GS.ordinal_to_copper_layer_k5
1255+
GS.copper_layer_to_ordinal = GS.copper_layer_to_ordinal_k5
1256+
elif GS.kp is not None:
1257+
GS.layer_is_inner = GS.layer_is_inner_kp
1258+
GS.ordinal_to_copper_layer = GS.ordinal_to_copper_layer_kp
1259+
GS.copper_layer_to_ordinal = GS.copper_layer_to_ordinal_kp
1260+
1261+
@staticmethod
1262+
def layer_is_inner_k5(id):
1263+
return id > GS.F_Cu and id < GS.B_Cu
1264+
1265+
@staticmethod
1266+
def layer_is_inner_k9(id):
1267+
return GS.pn.IsInnerCopperLayer(id)
1268+
1269+
@staticmethod
1270+
def layer_is_inner_kp(id):
1271+
return GS.kp.util.board_layer.is_copper_layer(id) and id != GS.F_Cu and id != GS.B_Cu
12471272

12481273
@staticmethod
12491274
def inner_layer_index(id):
12501275
return int(id/2-1) if GS.ki9 else id-pcbnew.F_Cu+1
12511276

12521277
@staticmethod
1253-
def copper_layer_to_ordinal(n):
1254-
if GS.ki9:
1255-
ordinal = pcbnew.CopperLayerToOrdinal(n)
1256-
# Adjust to the current PCB
1257-
if ordinal == pcbnew.CopperLayerToOrdinal(pcbnew.B_Cu):
1258-
ordinal = GS.board.GetCopperLayerCount()-1
1259-
return ordinal
1260-
# <= 8
1261-
return GS.board.GetCopperLayerCount()-1 if n == pcbnew.B_Cu else n
1262-
1263-
@staticmethod
1264-
def ordinal_to_copper_layer(n):
1265-
# Only for KiCad 9+
1266-
if n == GS.board.GetCopperLayerCount()-1:
1267-
return pcbnew.B_Cu
1268-
if n == 0:
1269-
return pcbnew.F_Cu
1278+
def copper_layer_to_ordinal_k5(n):
1279+
""" Converts a KiCad layer number to its position.
1280+
F.Cu will be 0 and B.Cu the last """
1281+
return GS.board.GetCopperLayerCount()-1 if n == GS.B_Cu else n
1282+
1283+
@staticmethod
1284+
def copper_layer_to_ordinal_k9(n):
1285+
""" Converts a KiCad layer number to its position.
1286+
F.Cu will be 0 and B.Cu the last """
1287+
ordinal = pcbnew.CopperLayerToOrdinal(n)
1288+
# Adjust to the current PCB
1289+
if ordinal == pcbnew.CopperLayerToOrdinal(pcbnew.B_Cu):
1290+
ordinal = GS.board.GetCopperLayerCount()-1
1291+
return ordinal
1292+
1293+
@staticmethod
1294+
def copper_layer_to_ordinal_kp(n):
1295+
""" Converts a KiCad layer number to its position.
1296+
F.Cu will be 0 and B.Cu the last """
1297+
return GS.board.get_copper_layer_count()-1 if n == GS.B_Cu else n
1298+
1299+
@staticmethod
1300+
def ordinal_to_copper_layer_k9(n):
1301+
""" From an ordinal layer position where F.Cu is 0 and B.Cu is the last
1302+
returns the KiCad layer number """
1303+
if n >= GS.board.GetCopperLayerCount()-1:
1304+
return GS.B_Cu
1305+
if n <= 0:
1306+
return GS.F_Cu
12701307
return (n+1)*2
12711308

1309+
@staticmethod
1310+
def ordinal_to_copper_layer_k5(n):
1311+
""" From an ordinal layer position where F.Cu is 0 and B.Cu is the last
1312+
returns the KiCad layer number """
1313+
if n >= GS.board.GetCopperLayerCount()-1:
1314+
return GS.B_Cu
1315+
if n <= 0:
1316+
return GS.F_Cu
1317+
return n
1318+
1319+
@staticmethod
1320+
def ordinal_to_copper_layer_kp(n):
1321+
""" From an ordinal layer position where F.Cu is 0 and B.Cu is the last
1322+
returns the KiCad layer number """
1323+
if n >= GS.board.get_copper_layer_count()-1:
1324+
return GS.B_Cu
1325+
if n <= 0:
1326+
return GS.F_Cu
1327+
return GS.kp.util.board_layer.layer_from_canonical_name(f'In{n}.Cu')
1328+
12721329
@staticmethod
12731330
def get_via_width(via):
12741331
if not GS.ki9:

kibot/layer.py

Lines changed: 108 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,14 @@ def create_print_priority(board):
3131
global LAYER_PRIORITY
3232
if len(LAYER_PRIORITY) > 0:
3333
return
34-
LAYER_PRIORITY = {board.GetLayerID(name): c for c, name in enumerate(LAYER_ORDER)}
34+
get_id = board.GetLayerID if GS.pn else GS.kp.util.board_layer.layer_from_canonical_name
35+
LAYER_PRIORITY = {get_id(name): c for c, name in enumerate(LAYER_ORDER)}
3536

3637

3738
def get_priority(id):
3839
return LAYER_PRIORITY.get(id, 1e6)
3940

4041

41-
def inner_id_in_range(id, cnt):
42-
if GS.ki9:
43-
return GS.layer_is_inner(id) and int(id/2) < cnt
44-
return id > 0 and id < cnt-1
45-
46-
4742
class Layer(Optionable):
4843
""" A layer description """
4944
# Protel extensions
@@ -122,9 +117,8 @@ def fix_protel_ext(self):
122117
@classmethod
123118
def solve(cls, values):
124119
board = GS.board
125-
layer_cnt = 2
126120
if board:
127-
layer_cnt = board.GetCopperLayerCount()
121+
board.GetCopperLayerCount() if GS.pn is not None else board.get_copper_layer_count()
128122
create_print_priority(board)
129123
# Get the list of used layers from the board
130124
# Used for 'all' but also to validate the layer names
@@ -146,9 +140,6 @@ def solve(cls, values):
146140
for layer in values:
147141
if isinstance(layer, Layer):
148142
layer._get_layer_id_from_name()
149-
# Check if the layer is in use
150-
if layer._is_inner and not inner_id_in_range(layer._id, layer_cnt):
151-
raise PlotError("Inner layer `{}` is not valid for this board".format(layer))
152143
layer.fix_protel_ext()
153144
new_vals.append(layer)
154145
elif isinstance(layer, int):
@@ -183,41 +174,76 @@ def solve(cls, values):
183174
raise AssertionError("Unimplemented layer type "+str(type(values)))
184175

185176
@staticmethod
186-
def _get_copper():
177+
def _get_copper_pn():
187178
return {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().CuStack()}
188179

189180
@staticmethod
190-
def _get_inners():
181+
def _get_copper_kp():
182+
return {GS.board.get_layer_name(id): id for id in GS.board.get_enabled_layers()
183+
if GS.kp.util.board_layer.is_copper_layer(id)}
184+
185+
@staticmethod
186+
def _get_inners_pn():
191187
return {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().CuStack()
192188
if id != GS.B_Cu and id != GS.F_Cu}
193189

194190
@staticmethod
195-
def _get_outers():
191+
def _get_inners_kp():
192+
return {GS.board.get_layer_name(id): id for id in GS.board.get_enabled_layers()
193+
if id != GS.B_Cu and id != GS.F_Cu and GS.kp.util.board_layer.is_copper_layer(id)}
194+
195+
@staticmethod
196+
def _get_outers_pn():
196197
return {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().CuStack()
197198
if id == GS.B_Cu or id == GS.F_Cu}
198199

199200
@staticmethod
200-
def _get_technical():
201-
if GS.ki9:
202-
return {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().AllTechMask().Seq()}
201+
def _get_outers_kp():
202+
return {GS.board.get_layer_name(id): id for id in GS.board.get_enabled_layers() if id == GS.B_Cu or id == GS.F_Cu}
203+
204+
@staticmethod
205+
def _get_technical_pn_k6():
203206
return {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().Technicals()}
204207

205208
@staticmethod
206-
def _get_user():
209+
def _get_technical_pn_k9():
210+
return {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().AllTechMask().Seq()}
211+
212+
@staticmethod
213+
def _get_technical_kp():
214+
# All but copper and user
215+
return {GS.board.get_layer_name(id): id for id in GS.board.get_enabled_layers()
216+
if not GS.kp.util.board_layer.is_copper_layer(id) and
217+
not GS.kp.util.board_layer.canonical_name(id).startswith('User.')}
218+
219+
@staticmethod
220+
def _get_user_pn_k6():
221+
enabled = GS.board.GetEnabledLayers()
222+
return {GS.board.GetLayerName(id): id for id in enabled.Users()}
223+
224+
@staticmethod
225+
def _get_user_pn_k9():
207226
b = GS.board
208227
enabled = b.GetEnabledLayers()
209-
if GS.ki9:
210-
layers = {b.GetLayerName(id): id for id in enabled.UserMask().Seq()}
211-
# Applying UserDefinedLayersMask() doesn't work as expected it returns all possible user layers
212-
# This is why we need the "if id ..." and this why we need to get the list in 2 steps
213-
layers.update({b.GetLayerName(id): id for id in enabled.UserDefinedLayersMask().Seq() if id in enabled.Seq()})
214-
return layers
215-
return {GS.board.GetLayerName(id): id for id in enabled.Users()}
228+
layers = {b.GetLayerName(id): id for id in enabled.UserMask().Seq()}
229+
# Applying UserDefinedLayersMask() doesn't work as expected it returns all possible user layers
230+
# This is why we need the "if id ..." and this why we need to get the list in 2 steps
231+
layers.update({b.GetLayerName(id): id for id in enabled.UserDefinedLayersMask().Seq() if id in enabled.Seq()})
232+
return layers
233+
234+
@staticmethod
235+
def _get_user_kp():
236+
return {GS.board.get_layer_name(id): id for id in GS.board.get_enabled_layers()
237+
if GS.kp.util.board_layer.canonical_name(id).startswith('User.')}
216238

217239
@staticmethod
218-
def _set_pcb_layers():
240+
def _set_pcb_layers_pn():
219241
Layer._pcb_layers = {GS.board.GetLayerName(id): id for id in GS.board.GetEnabledLayers().Seq()}
220242

243+
@staticmethod
244+
def _set_pcb_layers_kp():
245+
Layer._pcb_layers = {GS.board.get_layer_name(id): id for id in GS.board.get_enabled_layers()}
246+
221247
def get_default_suffix(self):
222248
if GS.global_layer_defaults:
223249
layer = next(filter(lambda x: x.layer == self.layer, GS.global_layer_defaults), None)
@@ -241,7 +267,7 @@ def create_layer(cls, name):
241267
else:
242268
layer._id = name
243269
layer._is_inner = GS.layer_is_inner(name)
244-
name = GS.board.GetLayerName(name)
270+
name = GS.board.GetLayerName(name) if GS.pn is not None else GS.board.get_layer_name(name)
245271
layer.layer = name
246272
layer.suffix = layer.get_default_suffix()
247273
layer.description = layer.get_default_description()
@@ -257,13 +283,19 @@ def _get_layers(cls, d_layers):
257283
return layers
258284

259285
@staticmethod
260-
def _set_plot_layers():
286+
def _set_plot_layers_pn():
261287
board = GS.board
262288
enabled = board.GetEnabledLayers().Seq()
263289
for id in board.GetPlotOptions().GetLayerSelection().Seq():
264290
if id in enabled:
265291
Layer._plot_layers[board.GetLayerName(id)] = id
266292

293+
@staticmethod
294+
def _set_plot_layers_kp():
295+
# TODO: kipy NOT IMPLEMENTED!!!
296+
# Here we just get all of them, not good
297+
Layer._plot_layers = {GS.board.get_layer_name(id): id for id in GS.board.get_enabled_layers()}
298+
267299
def _get_layer_id_from_name(self):
268300
""" Get the pcbnew layer from the string provided in the config """
269301
# Priority
@@ -281,17 +313,20 @@ def _get_layer_id_from_name(self):
281313
# 3) Inner.N names
282314
m = match(r"^Inner\.([0-9]+)$", self.layer)
283315
if not m:
284-
raise KiPlotConfigurationError("Malformed inner layer name: `{}`, use Inner.N".format(self.layer))
316+
raise KiPlotConfigurationError(f"Malformed inner layer name: `{self.layer}`, use Inner.N")
285317
id = int(m.group(1))
286-
self._id = (id+1)*2 if GS.ki9 else id
318+
self._id = GS.ordinal_to_copper_layer(id)
319+
if self._id == GS.B_Cu or self._id == GS.F_Cu:
320+
raise PlotError(f"Inner layer `{self.layer}` is not valid for this board")
287321
self._is_inner = True
288322
else:
289323
raise KiPlotConfigurationError("Unknown layer name: `{}`".format(self.layer))
290324
return self._id
291325

292-
def is_copper(self):
293-
if GS.pn is not None:
294-
return self._id >= GS.F_Cu and self._id <= GS.B_Cu
326+
def is_copper_pn(self):
327+
return self._id >= GS.F_Cu and self._id <= GS.B_Cu
328+
329+
def is_copper_kp(self):
295330
return GS.kp.util.board_layer.is_copper_layer(self._id)
296331

297332
def is_top(self):
@@ -306,7 +341,42 @@ def __str__(self):
306341
return "{} ('{}' {})".format(self.layer, self.description, self.suffix)
307342

308343
@staticmethod
309-
def id2def_name(id):
310-
if GS.ki5:
311-
return GS.ID_2_DEFAULT_NAME[id]
312-
return GS.pn.LayerName(id) if GS.pn is not None else GS.kp.canonical_name(id)
344+
def id2def_name_k5(id):
345+
return GS.ID_2_DEFAULT_NAME[id]
346+
347+
@staticmethod
348+
def id2def_name_k6(id):
349+
return GS.pn.LayerName(id)
350+
351+
@staticmethod
352+
def id2def_name_kp(id):
353+
return GS.kp.canonical_name(id)
354+
355+
356+
if GS.pn is not None:
357+
Layer._set_pcb_layers = Layer._set_pcb_layers_pn
358+
Layer._get_copper = Layer._get_copper_pn
359+
Layer._get_inners = Layer._get_inners_pn
360+
Layer._get_outers = Layer._get_outers_pn
361+
Layer._set_plot_layers = Layer._set_plot_layers_pn
362+
Layer.is_copper = Layer.is_copper_pn
363+
if GS.ki9:
364+
Layer._get_technical = Layer._get_technical_pn_k9
365+
Layer._get_user = Layer._get_user_pn_k9
366+
else:
367+
Layer._get_technical = Layer._get_technical_pn_k6
368+
Layer._get_user = Layer._get_user_pn_k6
369+
if GS.ki5:
370+
Layer.id2def_name = Layer.id2def_name_k5
371+
else:
372+
Layer.id2def_name = Layer.id2def_name_k6
373+
else:
374+
Layer._set_pcb_layers = Layer._set_pcb_layers_kp
375+
Layer._get_copper = Layer._get_copper_kp
376+
Layer._get_inners = Layer._get_inners_kp
377+
Layer._get_outers = Layer._get_outers_kp
378+
Layer._get_technical = Layer._get_technical_kp
379+
Layer._get_user = Layer._get_user_kp
380+
Layer._set_plot_layers = Layer._set_plot_layers_kp
381+
Layer.is_copper = Layer.is_copper_kp
382+
Layer.id2def_name = Layer.id2def_name_kp

tests/test_plot/test_gerber.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ def test_gerber_inner_wrong(test_dir):
118118
ctx.clean_up()
119119

120120

121+
def test_gerber_inner_wrong_2(test_dir):
122+
prj = 'good-project'
123+
ctx = context.TestContext(test_dir, prj, 'gerber_inner_wrong_2')
124+
ctx.run(PLOT_ERROR)
125+
assert ctx.search_err('is not valid for this board')
126+
ctx.clean_up()
127+
128+
121129
def compose_fname(dir, prefix, layer, suffix, ext='gbr'):
122130
return os.path.join(dir, prefix+'-'+layer+suffix+'.'+ext)
123131

0 commit comments

Comments
 (0)