From fee593c0916e013938bc52ee3ba324c3a4843bb4 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Sun, 30 Aug 2020 18:26:27 -0300 Subject: [PATCH 01/33] Added members to create the cache.lib This is equivalent to the cache.lib, not the real one. We can generate two versions, one normal and the other with crossed components. Now we need a schematic to use them. --- kibot/kicad/v5_sch.py | 191 ++++++++++++++++++++++++++++++++++++++++-- kibot/kiplot.py | 3 + 2 files changed, 186 insertions(+), 8 deletions(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 47bf6b46..51703782 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -140,6 +140,19 @@ class LibComponentField(object): field.name = ['Reference', 'Value', 'Footprint', 'Datasheet'][field.number] return field + def write(self, f): + s = 'F'+str(self.number) + s += ' "{}" {} {} {} '.format(self.value, self.x, self.y, self.size) + s += 'H' if self.horizontal else 'V' + s += ' ' + s += 'V' if self.visible else 'I' + s += ' '+self.hjustify+' '+self.vjustify + s += 'I' if self.italic else 'N' + s += 'B' if self.bold else 'N' + if self.number > 3: + s += ' "'+self.name+'"' + f.write(s+'\n') + class DrawPoligon(object): pol_re = re.compile(r'P\s+(\d+)\s+' # 0 Number of points @@ -168,9 +181,30 @@ class DrawPoligon(object): coords = _split_space(g[4]) if len(coords) != 2*pol.points: logger.warning('Expected {} coordinates and got {} in poligon'.format(2*pol.points, len(coords))) - pol.coords = coords + pol.points = int(len(coords)/2) + pol.coords = [int(c) for c in coords] return pol + def write(self, f): + f.write('P {} {} {} {}'.format(self.points, self.sub_part, self.convert, self.thickness)) + for p in self.coords: + f.write(' '+str(p)) + f.write(' '+self.fill+'\n') + + def get_rect(self): + if not self.points: + return 0, 0, 0, 0, False + xm = xM = self.coords[0] + ym = yM = self.coords[1] + for i in range(1, self.points): + x = self.coords[i*2] + y = self.coords[i*2+1] + xm = min(x, xm) + xM = max(x, xM) + ym = min(y, ym) + yM = max(y, yM) + return xm, ym, xM, yM, True + class DrawRectangle(object): rec_re = re.compile(r'S\s+' @@ -204,6 +238,17 @@ class DrawRectangle(object): rec.fill = g[7] return rec + def write(self, f): + f.write('S {} {} {} {}'.format(self.start_x, self.start_y, self.end_x, self.end_y)) + f.write(' {} {} {} {}\n'.format(self.sub_part, self.convert, self.thickness, self.fill)) + + def get_rect(self): + xm = min(self.start_x, self.end_x) + ym = min(self.start_y, self.end_y) + xM = max(self.start_x, self.end_x) + yM = max(self.start_y, self.end_y) + return xm, ym, xM, yM, True + class DrawCircle(object): cir_re = re.compile(r'C\s+' @@ -235,6 +280,17 @@ class DrawCircle(object): cir.fill = g[6] return cir + def write(self, f): + f.write('C {} {} {}'.format(self.pos_x, self.pos_y, self.radius)) + f.write(' {} {} {} {}\n'.format(self.sub_part, self.convert, self.thickness, self.fill)) + + def get_rect(self): + xm = self.pos_x-self.radius + ym = self.pos_y-self.radius + xM = self.pos_x+self.radius + yM = self.pos_y+self.radius + return xm, ym, xM, yM, True + class DrawArc(object): arc_re = re.compile(r'A\s+' @@ -278,6 +334,19 @@ class DrawArc(object): arc.end_y = int(g[12]) return arc + def write(self, f): + f.write('A {} {} {}'.format(self.pos_x, self.pos_y, self.radius)) + f.write(' {} {}'.format(self.start, self.end)) + f.write(' {} {} {} {}'.format(self.sub_part, self.convert, self.thickness, self.fill)) + f.write(' {} {} {} {}\n'.format(self.start_x, self.start_y, self.end_x, self.end_y)) + + def get_rect(self): + xm = self.pos_x-self.radius + ym = self.pos_y-self.radius + xM = self.pos_x+self.radius + yM = self.pos_y+self.radius + return xm, ym, xM, yM, True + class DrawText(object): txt_re = re.compile(r'T\s+' @@ -305,7 +374,7 @@ class DrawText(object): return None txt = DrawText() g = m.groups() - txt.vertical = g[0] != '0' + txt.orientation = int(g[0]) txt.pos_x = int(g[1]) txt.pos_y = int(g[2]) txt.size = int(g[3]) @@ -319,6 +388,14 @@ class DrawText(object): txt.vjustify = g[11] return txt + def write(self, f): + f.write('T {} {} {} {}'.format(self.orientation, self.pos_x, self.pos_y, self.radius)) + f.write(' {} {} {} "{}"'.format(self.type, self.sub_part, self.convert, self.text)) + f.write(' {} {} {} {}\n" '.format(['Normal', 'Italic'][self.italic], int(self.bold), self.hjustify, self.vjustify)) + + def get_rect(self): + return 0, 0, 0, 0, False + class Pin(object): pin_re = re.compile(r'X\s+' @@ -360,6 +437,25 @@ class Pin(object): pin.gtype = g[11] return pin + def write(self, f): + f.write('X {} {} {} {}'.format(self.name, self.number, self.pos_x, self.pos_y)) + f.write(' {} {} {} {}'.format(self.len, self.dir, self.size_name, self.size_num)) + f.write(' {} {} {}'.format(self.sub_part, self.convert, self.type)) + if self.gtype: + f.write(' '+self.gtype) + f.write('\n') + + def get_rect(self): + if self.dir == 'U': + return self.pos_x, self.pos_y, self.pos_x, self.pos_y+self.len, True + if self.dir == 'D': + return self.pos_x, self.pos_y-self.len, self.pos_x, self.pos_y, True + if self.dir == 'L': + return self.pos_x, self.pos_y, self.pos_x+self.len, self.pos_y, True + if self.dir == 'R': + return self.pos_x-self.len, self.pos_y, self.pos_x, self.pos_y, True + return 0, 0, 0, 0, False + class LibComponent(object): def_re = re.compile(r'DEF\s+' @@ -393,7 +489,7 @@ class LibComponent(object): self.vname = True else: self.vname = False - if GS.debug_level > 1: + if GS.debug_level > 2: logger.debug('- Loading component {} from {}'.format(self.name, lib_name)) else: logger.warning('Failed to load component definition: `{}`'.format(line)) @@ -438,6 +534,53 @@ class LibComponent(object): line = f.get_line() line = f.get_line() + def write(self, f, id, cross=False): + id = id.replace(':', '_') + if self.vname: + id = '~'+id + f.write('#\n# '+id+'\n#\n') + f.write('DEF {} {} {} {} {} {} {} {} {}\n'. + format(id, self.ref_prefix, self.unused, self.text_offset, ['N', 'Y'][self.draw_pinnumber], + ['N', 'Y'][self.draw_pinname], self.unit_count, ['F', 'L'][self.units_locked], + ['N', 'P'][self.is_power])) + for field in self.fields: + field.write(f) + f.write('$FPLIST\n') + for fp in self.fp_list: + f.write(' '+fp+'\n') + f.write('$ENDFPLIST\n') + if self.alias: + f.write('ALIAS '+' '.join(self.alias)+'\n') + f.write('DRAW\n') + xmt = ymt = 1e6 + xMt = yMt = -1e6 + ok_t = False + for dr in self.draw: + dr.write(f) + if cross: + xm, ym, xM, yM, ok = dr.get_rect() + logger.debug([dr, xm, ym, xM, yM, ok]) + if ok: + ok_t = True + xmt = min(xm, xmt) + ymt = min(ym, ymt) + xMt = max(xM, xMt) + yMt = max(yM, yMt) + if ok_t: + # Cross this component using 2 lines + o = DrawPoligon() + o.points = 2 + o.sub_part = o.convert = 0 + o.thickness = 30 + o.fill = 'N' + o.coords = [xmt, ymt, xMt, yMt] + o.write(f) + o.coords = [xmt, yMt, xMt, ymt] + o.write(f) + f.write('ENDDRAW\n') + f.write('ENDDEF\n') + + # def __repr__(self): # s = 'Component('+self.name # if self.desc: @@ -453,7 +596,20 @@ class SymLib(object): self.comps = OrderedDict() self.alias = {} - def load(self, file): + @staticmethod + def _check_add(o, id, lib, needed): + name = lib+':'+id + if name in needed: + needed[name] = o + return True + else: + name = 'None:'+id + if name in needed: + needed[name] = o + return True + return False + + def load(self, file, lib_alias, needed): """ Populates the class, file must exist """ logger.debug('Loading library `{}`'.format(file)) with open(file, 'rt') as fh: @@ -466,10 +622,13 @@ class SymLib(object): if line.startswith('DEF'): o = LibComponent(line, f, file) if o.name: - self.comps[o.name] = o + # Only add components we need + if self._check_add(o, o.name, lib_alias, needed): + self.comps[o.name] = o if o.alias: for a in o.alias: - self.alias[a] = o + if self._check_add(o, a, lib_alias, needed): + self.alias[a] = o else: raise SchLibError('Unknown library entry', line, f) line = f.get_line() @@ -1183,13 +1342,17 @@ class Schematic(object): logger.debug('Using `{}` for library alias `{}`'.format(alias.uri, k)) else: logger.warning('Missing library `{}`'.format(k)) + # Create a hash with all the used components + self.comps_data = {'{}:{}'.format(c.lib, c.name): None for c in self.components} + if GS.debug_level > 1: + logger.debug("Components before loading: "+str(self.comps_data)) # Load the libraries and descriptions for k, v in self.libs.items(): if v: # Load library if os.path.isfile(v): o = SymLib() - o.load(v) + o.load(v, k, self.comps_data) else: logger.warning('Missing library `{}` ({})'.format(v, k)) o = None @@ -1206,6 +1369,8 @@ class Schematic(object): # Mark as None if we don't know the file self.lib_comps[k] = None self.dcms[k] = None + if GS.debug_level > 1: + logger.debug("Components after loading: "+str(self.comps_data)) # Join the descriptions with the components for k in self.libs.keys(): lib = self.lib_comps[k] @@ -1213,7 +1378,17 @@ class Schematic(object): if lib and dcm: for name, comp in lib.comps.items(): comp.dcm = dcm.comps.get(name) - if not comp.dcm: + if not comp.dcm and k+':'+name in self.comps_data: logger.warning('Missing doc-lib entry for {}:{}'.format(k, name)) # Transfer the descriptions to the instances of the components self.walk_components(self.apply_dcm, self) + + def gen_lib(self, name, cross=False): + """ Dumps all the used components to one library. + This is like the KiCad cache. """ + with open(name, 'wt') as f: + f.write('EESchema-LIBRARY Version 2.4\n') + f.write('#encoding utf-8\n') + for k, v in self.comps_data.items(): + v.write(f, k, cross=cross) + f.write('#\n#End Library\n') diff --git a/kibot/kiplot.py b/kibot/kiplot.py index 693f9f07..b530a675 100644 --- a/kibot/kiplot.py +++ b/kibot/kiplot.py @@ -157,6 +157,9 @@ def load_sch(): GS.sch.load_libs(GS.sch_file) if GS.debug_level > 1: logger.debug('Schematic dependencies: '+str(GS.sch.get_files())) + # Testing 1, 2, 3 ... + GS.sch.gen_lib('test.lib') + GS.sch.gen_lib('test_cross.lib', cross=True) except SchFileError as e: trace_dump() logger.error('At line {} of `{}`: {}'.format(e.line, e.file, e.msg)) From 6620779e094b6ce73e44c6bf7d730a9b1f1ae435 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Sun, 30 Aug 2020 18:49:01 -0300 Subject: [PATCH 02/33] Handled missing components. --- kibot/kicad/v5_sch.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 51703782..c29b0ca6 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -559,7 +559,7 @@ class LibComponent(object): dr.write(f) if cross: xm, ym, xM, yM, ok = dr.get_rect() - logger.debug([dr, xm, ym, xM, yM, ok]) + # logger.debug([dr, xm, ym, xM, yM, ok]) if ok: ok_t = True xmt = min(xm, xmt) @@ -1390,5 +1390,8 @@ class Schematic(object): f.write('EESchema-LIBRARY Version 2.4\n') f.write('#encoding utf-8\n') for k, v in self.comps_data.items(): - v.write(f, k, cross=cross) + if v: + v.write(f, k, cross=cross) + else: + logger.warning('Missing component `{}`'.format(k)) f.write('#\n#End Library\n') From 8b0247bf80b578fd62e97e2eee692f65fac5b08b Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:41:02 -0300 Subject: [PATCH 03/33] Fixed rectangle computation for pins. Left and Right exchanged. --- kibot/kicad/v5_sch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index c29b0ca6..2fb4052f 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -450,9 +450,9 @@ class Pin(object): return self.pos_x, self.pos_y, self.pos_x, self.pos_y+self.len, True if self.dir == 'D': return self.pos_x, self.pos_y-self.len, self.pos_x, self.pos_y, True - if self.dir == 'L': - return self.pos_x, self.pos_y, self.pos_x+self.len, self.pos_y, True if self.dir == 'R': + return self.pos_x, self.pos_y, self.pos_x+self.len, self.pos_y, True + if self.dir == 'L': return self.pos_x-self.len, self.pos_y, self.pos_x, self.pos_y, True return 0, 0, 0, 0, False From a0db94d67c674c4927d69b6da303532971efc246 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:42:06 -0300 Subject: [PATCH 04/33] Added support for cross to multi-part components. --- kibot/kicad/v5_sch.py | 55 +++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 2fb4052f..aea5bb1f 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -535,6 +535,7 @@ class LibComponent(object): line = f.get_line() def write(self, f, id, cross=False): + """ cross is used to cross the component (DNF) """ id = id.replace(':', '_') if self.vname: id = '~'+id @@ -552,31 +553,39 @@ class LibComponent(object): if self.alias: f.write('ALIAS '+' '.join(self.alias)+'\n') f.write('DRAW\n') - xmt = ymt = 1e6 - xMt = yMt = -1e6 - ok_t = False for dr in self.draw: dr.write(f) - if cross: - xm, ym, xM, yM, ok = dr.get_rect() - # logger.debug([dr, xm, ym, xM, yM, ok]) - if ok: - ok_t = True - xmt = min(xm, xmt) - ymt = min(ym, ymt) - xMt = max(xM, xMt) - yMt = max(yM, yMt) - if ok_t: - # Cross this component using 2 lines - o = DrawPoligon() - o.points = 2 - o.sub_part = o.convert = 0 - o.thickness = 30 - o.fill = 'N' - o.coords = [xmt, ymt, xMt, yMt] - o.write(f) - o.coords = [xmt, yMt, xMt, ymt] - o.write(f) + if cross: + # Generated the crossed stuff + # logger.debug('Computing size for {}:'.format(id)) + for unit in range(self.unit_count): + xmt = ymt = 1e6 + xMt = yMt = -1e6 + ok_t = False + logger.debug("Unit "+str(unit+1)) + for dr in self.draw: + if dr.sub_part != unit + 1 and dr.sub_part != 0: + continue + xm, ym, xM, yM, ok = dr.get_rect() + # logger.debug([dr, xm, ym, xM, yM, ok]) + if ok: + ok_t = True + xmt = min(xm, xmt) + ymt = min(ym, ymt) + xMt = max(xM, xMt) + yMt = max(yM, yMt) + if ok_t: + # Cross this component using 2 lines + o = DrawPoligon() + o.points = 2 + o.sub_part = unit+1 + o.convert = 0 + o.thickness = 30 + o.fill = 'N' + o.coords = [xmt, ymt, xMt, yMt] + o.write(f) + o.coords = [xmt, yMt, xMt, ymt] + o.write(f) f.write('ENDDRAW\n') f.write('ENDDEF\n') From 094cf41e7bf66fb2be97c070d604b8b5e36f4869 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:43:52 -0300 Subject: [PATCH 05/33] Added support for Italic and Bold in Text* elements. --- kibot/kicad/v5_sch.py | 49 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index aea5bb1f..1f01c0a5 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -986,29 +986,58 @@ class SchematicConnection(object): c.y = int(m.group(2)) return c + def write(self, f): + f.write('{} ~ {} {}\n'.format(['NoConn', 'Connection'][self.connect], self.x, self.y)) + class SchematicText(object): label_re = re.compile(r'Text\s+(Notes|HLabel|GLabel|Label)\s+(-?\d+)\s+(-?\d+)\s+(\d)\s+(\d+)\s+(\S+)') + TYPES = ['Notes', 'HLabel', 'GLabel', 'Label'] def __init__(self): super().__init__() @staticmethod def load(f, line): - m = SchematicText.label_re.match(line) - if not m: - raise SchFileError('Malformed text', line, f) + gs = _split_space(line) + c = len(gs) + if c < 6 or gs[0] != 'Text' or gs[1] not in SchematicText.TYPES: + raise SchFileError('Malformed `Text`', line, f) text = SchematicText() - gs = m.groups() - text.type = gs[0] - text.x = int(gs[1]) - text.y = int(gs[2]) - text.orient = int(gs[3]) - text.size = int(gs[4]) - text.shape = gs[5] + text.type = gs[1] + try: + text.x = int(gs[2]) + text.y = int(gs[3]) + text.orient = int(gs[4]) + text.size = int(gs[5]) + offset = 6 + text.shape = None + if gs[1][0] in 'GH': + if c < 7: + raise SchFileError('Missing `Text` shape', line, f) + text.shape = gs[6] + offset += 1 + # New versions adds Italics and Bold, in a different way of course + text.italic = False + if c > offset: + text.italic = gs[offset] == 'Italic' + offset += 1 + text.thickness = 0 + if c > offset: + text.thickness = int(gs[offset]) + offset += 1 + except ValueError: + raise SchFileError('Not a number in `Text`', line, f) text.text = f.get_line() return text + def write(self, f): + f.write('Text {} {} {} {} {}'.format(self.type, self.x, self.y, self.orient, self.size)) + if self.type[0] in 'GH': + f.write(' '+self.shape) + f.write(' {} {}\n'.format(['~', 'Italic'][self.italic], self.thickness)) + f.write(self.text+'\n') + class SchematicWire(object): WIRE = 0 From b0c014abe9ac315ae85d6a86f7f6999e0b77cd43 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:44:34 -0300 Subject: [PATCH 06/33] Added support to write schematics. --- kibot/kicad/v5_sch.py | 116 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 4 deletions(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 1f01c0a5..26d0f12a 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -730,6 +730,14 @@ class SchematicField(object): field.name = ['Reference', 'Value', 'Footprint', 'Datasheet'][field.number] return field + def write(self, f): + f.write('F {} "{}" {}'.format(self.number, self.value, ['V', 'H'][self.horizontal])) + f.write(' {} {} {} {}'.format(self.x, self.y, self.size, self.flags)) + f.write(' {} {}{}{}'.format(self.hjustify, self.vjustify, ['N', 'I'][self.italic], ['N', 'B'][self.bold])) + if self.number > 3: + f.write(' "{}"'.format(self.name)) + f.write('\n') + class SchematicAltRef(): def __init__(self): @@ -757,6 +765,16 @@ class SchematicAltRef(): logger.warning('Alternative Reference without reference `{}`'.format(line)) return ar + def write(self, f): + f.write('AR') + if self.path: + f.write(' Path="{}"'.format(self.path)) + if self.ref: + f.write(' Ref="{}"'.format(self.ref)) + if self.part: + f.write(' Part="{}"'.format(self.part)) + f.write('\n') + class SchematicComponent(object): """ Class for a component in the schematic. @@ -781,6 +799,10 @@ class SchematicComponent(object): self.footprint = '' self.datasheet = '' self.desc = '' + # Will be computed + self.fitted = True + self.in_bom = True + self.fixed = False def get_field_value(self, field): field = field.lower() @@ -968,6 +990,24 @@ class SchematicComponent(object): comp._validate() return comp + def write(self, f): + # Fake lib to reflect fitted status + lib = 'y' if self.fitted else 'n' + # Fake name using cache style + name = '{}:{}_{}'.format(lib, self.lib, self.name) + f.write('$Comp\n') + f.write('L {} {}\n'.format(name, self.f_ref)) + f.write('U {} {} {}\n'.format(self.unit, self.unit2, self.id)) + f.write('P {} {}\n'.format(self.x, self.y)) + for ar in self.ar: + ar.write(f) + for field in self.fields: + if field.number >= 0: + field.write(f) + f.write('\t1 {} {}\n'.format(self.x, self.y)) + f.write('\t{} {} {} {}\n'.format(self.matrix[0], self.matrix[1], self.matrix[2], self.matrix[3])) + f.write('$EndComp\n') + class SchematicConnection(object): conn_re = re.compile(r'\s*~\s+(-?\d+)\s+(-?\d+)') @@ -1047,6 +1087,7 @@ class SchematicWire(object): ENTRY_WIRE = 3 ENTRY_BUS = 4 ENTRIES = {'Wire': ENTRY_WIRE, 'Bus': ENTRY_BUS} + NAMES = ['Wire Wire Line', 'Wire Bus Line', 'Wire Notes Line', 'Entry Wire Line', 'Entry Bus Bus'] def __init__(self): super().__init__() @@ -1082,6 +1123,10 @@ class SchematicWire(object): wire.ey = int(res[3]) return wire + def write(self, f): + f.write(SchematicWire.NAMES[self.type]) + f.write('\n\t{} {} {} {}\n'.format(self.x, self.y, self.ex, self.ey)) + class SchematicBitmap(object): def __init__(self): @@ -1126,6 +1171,18 @@ class SchematicBitmap(object): raise SchFileError('Missing end of bitmap', line, f) return bmp + def write(self, f): + f.write('$Bitmap\n') + f.write('Pos {} {}\n'.format(self.x, self.y)) + f.write('Scale {}\n'.format(self.scale)) + f.write('Data') + for c, b in enumerate(self.data): + if (c % 32) == 0: + f.write('\n') + f.write('%02X ' % b) + f.write('\nEndData\n') + f.write('$EndBitmap\n') + class SchematicPort(object): port_re = re.compile(r'(\d+)\s+"(.*?)"\s+([IOBTU])\s+([RLTB])\s+(-?\d+)\s+(-?\d+)\s+(\d+)$') @@ -1149,6 +1206,9 @@ class SchematicPort(object): port.size = int(res[6]) return port + def write(s, f): + f.write('F{} "{}" {} {} {} {} {}\n'.format(s.number, s.name, s.form, s.side, s.x, s.y, s.size)) + class SchematicSheet(object): name_re = re.compile(r'"(.*?)"\s+(\d+)$') @@ -1216,6 +1276,18 @@ class SchematicSheet(object): raise SchFileError('Missing sub-sheet file name', sch.name, f) return sch + def write(self, f): + # Fake file name + file = self.file.replace('/', '_') + f.write('$Sheet\n') + f.write('S {} {} {} {}\n'.format(self.x, self.y, self.w, self.h)) + f.write('U {}\n'.format(self.id)) + f.write('F0 "{}" {}\n'.format(self.name, self.name_size)) + f.write('F1 "{}" {}\n'.format(file, self.file_size)) + for label in self.labels: + label.write(f) + f.write('$EndSheet\n') + class Schematic(object): def __init__(self): @@ -1232,8 +1304,8 @@ class Schematic(object): self.page_width = m.group(2) self.page_height = m.group(3) self.sheet = 1 - self.sheets = 1 - self.title_block = {} + self.nsheets = 1 + self.title_block = OrderedDict() while True: line = f.get_line() if line.startswith('$EndDescr'): @@ -1246,7 +1318,7 @@ class Schematic(object): if len(res) != 2: raise SchFileError('Wrong sheet number', line, f) self.sheet = int(res[0]) - self.sheets = int(res[1]) + self.nsheets = int(res[1]) else: m = re.match(r'(\S+)\s+"(.*)"', line) if not m: @@ -1381,7 +1453,7 @@ class Schematic(object): else: logger.warning('Missing library `{}`'.format(k)) # Create a hash with all the used components - self.comps_data = {'{}:{}'.format(c.lib, c.name): None for c in self.components} + self.comps_data = {'{}:{}'.format(c.lib, c.name): None for c in self.get_components(exclude_power=False)} if GS.debug_level > 1: logger.debug("Components before loading: "+str(self.comps_data)) # Load the libraries and descriptions @@ -1433,3 +1505,39 @@ class Schematic(object): else: logger.warning('Missing component `{}`'.format(k)) f.write('#\n#End Library\n') + + def save(self, fname, dest_dir): + fname = os.path.join(dest_dir, fname) + with open(fname, 'wt') as f: + f.write('EESchema Schematic File Version {}\n'.format(self.version)) + f.write('EELAYER {} {}\n'.format(self.eelayer_n, self.eelayer_m)) + f.write('EELAYER END\n') + f.write('$Descr {} {} {}\n'.format(self.page_type, self.page_width, self.page_height)) + f.write('encoding utf-8\n') + f.write('Sheet {} {}\n'.format(self.sheet, self.nsheets)) + for k, v in self.title_block.items(): + f.write('{} "{}"\n'.format(k, v)) + f.write('$EndDescr\n') + for e in self.all: + e.write(f) + f.write('$EndSCHEMATC\n') + # Save sub-sheets + for c, sch in enumerate(self.sheets): + # Fake file name + file = sch.file.replace('/', '_') + self.sub_sheets[c].save(file, dest_dir) + + def save_variant(self, dest_dir): + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + lib_yes = os.path.join(dest_dir, 'y.lib') + lib_no = os.path.join(dest_dir, 'n.lib') + self.gen_lib(lib_yes) + self.gen_lib(lib_no, cross=True) + self.save(os.path.basename(self.fname), dest_dir) + # SymLibTable to use y/n + with open(os.path.join(dest_dir, 'sym-lib-table'), 'wt') as f: + f.write('(sym_lib_table\n') + f.write(' (lib (name y)(type Legacy)(uri ${KIPRJMOD}/y.lib)(options "")(descr ""))\n') + f.write(' (lib (name n)(type Legacy)(uri ${KIPRJMOD}/n.lib)(options "")(descr ""))\n') + f.write(')\n') From f12c7fc1ce5c555b009821f5b84ab8acc7096cce Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:45:08 -0300 Subject: [PATCH 07/33] Removed code for testing. --- kibot/kiplot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/kibot/kiplot.py b/kibot/kiplot.py index b530a675..693f9f07 100644 --- a/kibot/kiplot.py +++ b/kibot/kiplot.py @@ -157,9 +157,6 @@ def load_sch(): GS.sch.load_libs(GS.sch_file) if GS.debug_level > 1: logger.debug('Schematic dependencies: '+str(GS.sch.get_files())) - # Testing 1, 2, 3 ... - GS.sch.gen_lib('test.lib') - GS.sch.gen_lib('test_cross.lib', cross=True) except SchFileError as e: trace_dump() logger.error('At line {} of `{}`: {}'.format(e.line, e.file, e.msg)) From d5fe46ab8e4932cad487a9243a3a0559fd136424 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:48:11 -0300 Subject: [PATCH 08/33] Updated error text in the test. --- tests/test_plot/test_sch_errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_plot/test_sch_errors.py b/tests/test_plot/test_sch_errors.py index 8f046688..19de8f69 100644 --- a/tests/test_plot/test_sch_errors.py +++ b/tests/test_plot/test_sch_errors.py @@ -151,7 +151,7 @@ def test_sch_errors_bad_conn(): def test_sch_errors_bad_text(): - setup_ctx('bad_text', 'Malformed text') + setup_ctx('bad_text', 'Malformed .?Text.?') def test_sch_errors_bad_wire(): From 7882cb0f4f03021541112d3d92f64a09fba9a9d3 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:48:56 -0300 Subject: [PATCH 09/33] Moved internal filters to the base class. So they can be used not only for internal BoM purposes. --- kibot/fil_base.py | 107 +++++++++++++++++++++++++++++++++++----------- kibot/misc.py | 3 ++ kibot/out_bom.py | 51 +++------------------- 3 files changed, 90 insertions(+), 71 deletions(-) diff --git a/kibot/fil_base.py b/kibot/fil_base.py index 39ccdc6c..4a2b93cf 100644 --- a/kibot/fil_base.py +++ b/kibot/fil_base.py @@ -4,8 +4,23 @@ # License: GPL-3.0 # Project: KiBot (formerly KiPlot) from .registrable import RegFilter, Registrable, RegOutput +from .misc import IFILL_MECHANICAL from .error import KiPlotConfigurationError +from .bom.columnlist import ColumnList from .macros import macros, document # noqa: F401 +from . import log + +logger = log.get_logger(__name__) +DEFAULT_EXCLUDE = [{'column': ColumnList.COL_REFERENCE, 'regex': '^TP[0-9]*'}, + {'column': ColumnList.COL_REFERENCE, 'regex': '^FID'}, + {'column': ColumnList.COL_PART, 'regex': 'mount.*hole'}, + {'column': ColumnList.COL_PART, 'regex': 'solder.*bridge'}, + {'column': ColumnList.COL_PART, 'regex': 'solder.*jump'}, + {'column': ColumnList.COL_PART, 'regex': 'test.*point'}, + {'column': ColumnList.COL_FP, 'regex': 'test.*point'}, + {'column': ColumnList.COL_FP, 'regex': 'mount.*hole'}, + {'column': ColumnList.COL_FP, 'regex': 'fiducial'}, + ] class DummyFilter(Registrable): @@ -54,6 +69,7 @@ class BaseFilter(RegFilter): def __init__(self): super().__init__() self._unkown_is_error = True + self._internal = False with document: self.name = '' """ Used to identify this particular filter definition """ @@ -62,8 +78,56 @@ class BaseFilter(RegFilter): self.comment = '' """ A comment for documentation purposes """ + def config(self): + super().config() + if self.name[0] == '_' and not self._internal: + raise KiPlotConfigurationError('Filter names starting with `_` are reserved ({})'.format(self.name)) + @staticmethod - def solve_filter(names, def_key, def_real, creator, target_name): + def _create_mechanical(name): + o_tree = {'name': name} + o_tree['type'] = 'generic' + o_tree['comment'] = 'Internal default mechanical filter' + o_tree['exclude_any'] = DEFAULT_EXCLUDE + logger.debug('Creating internal filter: '+str(o_tree)) + return o_tree + + @staticmethod + def _create_kibom_dnx(name): + type = name[7:10] + if len(name) > 11: + subtype = name[11:] + else: + subtype = 'config' + o_tree = {'name': name} + o_tree['type'] = 'generic' + o_tree['comment'] = 'Internal KiBoM '+type.upper()+' filter ('+subtype+')' + o_tree['config_field'] = subtype + o_tree['exclude_value'] = True + o_tree['exclude_config'] = True + o_tree['keys'] = type+'_list' + if type[-1] == 'c': + o_tree['invert'] = True + logger.debug('Creating internal filter: '+str(o_tree)) + return o_tree + + @staticmethod + def _create_internal_filter(name): + if name == IFILL_MECHANICAL: + tree = BaseFilter._create_mechanical(name) + elif name.startswith('_kibom_dn') and len(name) >= 10: + tree = BaseFilter._create_kibom_dnx(name) + else: + return None + filter = RegFilter.get_class_for(tree['type'])() + filter._internal = True + filter.set_tree(tree) + filter.config() + RegOutput.add_filter(filter) + return filter + + @staticmethod + def solve_filter(names, target_name, default=None): """ Name can be: - A class, meaning we have to use a default. - A string, the name of a filter. @@ -72,42 +136,33 @@ class BaseFilter(RegFilter): If def_real is not None we pass this name to creator. """ if isinstance(names, type): # Nothing specified, use the default - names = [def_key] + if default is None: + return None + names = [default] elif isinstance(names, str): # User provided, but only one, make a list names = [names] # Here we should have a list of strings filters = [] for name in names: - if name and name[0] == '!': + if not name: + continue + if name[0] == '!': invert = True name = name[1:] else: invert = False - filter = None - if name == def_key: - # Matched the default name, translate it to the real name - if def_real: - name = def_real - # Is already defined? - if RegOutput.is_filter(name): - filter = RegOutput.get_filter(name) - else: # Nope, create it - tree = creator(name) - filter = RegFilter.get_class_for(tree['type'])() - filter.set_tree(tree) - filter.config() - RegOutput.add_filter(filter) - elif name: - # A filter that is supposed to exist - if not RegOutput.is_filter(name): - raise KiPlotConfigurationError("Unknown filter `{}` used for `{}`".format(name, target_name)) + # Is already defined? + if RegOutput.is_filter(name): filter = RegOutput.get_filter(name) - if filter: - if invert: - filters.append(NotFilter(filter)) - else: - filters.append(filter) + else: # Nope, can be created? + filter = BaseFilter._create_internal_filter(name) + if filter is None: + raise KiPlotConfigurationError("Unknown filter `{}` used for `{}`".format(name, target_name)) + if invert: + filters.append(NotFilter(filter)) + else: + filters.append(filter) # Finished collecting filters if not filters: return DummyFilter() diff --git a/kibot/misc.py b/kibot/misc.py index 8947524e..571770a2 100644 --- a/kibot/misc.py +++ b/kibot/misc.py @@ -46,6 +46,9 @@ URL_PCBDRAW = 'https://github.com/INTI-CMNB/pcbdraw' EXAMPLE_CFG = 'example.kibot.yaml' AUTO_SCALE = 0 +# Internal filter names +IFILL_MECHANICAL = '_mechanical' + # Supported values for "do not fit" DNF = { "dnf": 1, diff --git a/kibot/out_bom.py b/kibot/out_bom.py index 5b080bde..43a05483 100644 --- a/kibot/out_bom.py +++ b/kibot/out_bom.py @@ -12,6 +12,7 @@ from .gs import GS from .optionable import Optionable, BaseOptions from .registrable import RegOutput from .error import KiPlotConfigurationError +from .misc import IFILL_MECHANICAL from .macros import macros, document, output_class # noqa: F401 from .bom.columnlist import ColumnList, BoMError from .bom.bom import do_bom @@ -174,17 +175,6 @@ class GroupFields(Optionable): class BoMOptions(BaseOptions): - DEFAULT_EXCLUDE = [{'column': ColumnList.COL_REFERENCE, 'regex': '^TP[0-9]*'}, - {'column': ColumnList.COL_REFERENCE, 'regex': '^FID'}, - {'column': ColumnList.COL_PART, 'regex': 'mount.*hole'}, - {'column': ColumnList.COL_PART, 'regex': 'solder.*bridge'}, - {'column': ColumnList.COL_PART, 'regex': 'solder.*jump'}, - {'column': ColumnList.COL_PART, 'regex': 'test.*point'}, - {'column': ColumnList.COL_FP, 'regex': 'test.*point'}, - {'column': ColumnList.COL_FP, 'regex': 'mount.*hole'}, - {'column': ColumnList.COL_FP, 'regex': 'fiducial'}, - ] - def __init__(self): with document: self.number = 1 @@ -282,31 +272,7 @@ class BoMOptions(BaseOptions): self.variant.config_field = self.fit_field self.variant.variant = [] self.variant.name = 'default' - - @staticmethod - def _create_mechanical(name): - o_tree = {'name': name} - o_tree['type'] = 'generic' - o_tree['comment'] = 'Internal default mechanical filter' - o_tree['exclude_any'] = BoMOptions.DEFAULT_EXCLUDE - logger.debug('Creating internal filter: '+str(o_tree)) - return o_tree - - @staticmethod - def _create_kibom_dnx(name): - type = name[7:10] - subtype = name[11:] - o_tree = {'name': name} - o_tree['type'] = 'generic' - o_tree['comment'] = 'Internal KiBoM '+type.upper()+' filter ('+subtype+')' - o_tree['config_field'] = subtype - o_tree['exclude_value'] = True - o_tree['exclude_config'] = True - o_tree['keys'] = type+'_list' - if type[-1] == 'c': - o_tree['invert'] = True - logger.debug('Creating internal filter: '+str(o_tree)) - return o_tree + self.variant.config() # Fill or adjust any detail def config(self): super().config() @@ -335,15 +301,10 @@ class BoMOptions(BaseOptions): # component_aliases if isinstance(self.component_aliases, type): self.component_aliases = DEFAULT_ALIASES - # exclude_filter - self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, '_mechanical', None, - BoMOptions._create_mechanical, 'exclude_filter') - # dnf_filter - self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, '_kibom_dnf', '_kibom_dnf_'+self.fit_field, - BoMOptions._create_kibom_dnx, 'dnf_filter') - # dnc_filter - self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, '_kibom_dnc', '_kibom_dnc_'+self.fit_field, - BoMOptions._create_kibom_dnx, 'dnc_filter') + # Filters + self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, 'exclude_filter', IFILL_MECHANICAL) + self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter', '_kibom_dnf_'+self.fit_field) + self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, 'dnc_filter', '_kibom_dnc_'+self.fit_field) # Variants, make it an object self._normalize_variant() # Field names are handled in lowercase From 4cc8a0916fed721c5bacdca55c69ba10472f60db Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:49:41 -0300 Subject: [PATCH 10/33] Added filters to the variants. So they have the same functionality than internal BoM. I keep the (redundant) filters in internal BoM so users doesn't need to create a variant just to apply a filter. --- kibot/var_base.py | 33 +++++++++++++++++++++++++++++++++ kibot/var_ibom.py | 1 + kibot/var_kibom.py | 1 + 3 files changed, 35 insertions(+) diff --git a/kibot/var_base.py b/kibot/var_base.py index 976789fd..cab5ee23 100644 --- a/kibot/var_base.py +++ b/kibot/var_base.py @@ -4,6 +4,8 @@ # License: GPL-3.0 # Project: KiBot (formerly KiPlot) from .registrable import RegVariant +from .optionable import Optionable +from .fil_base import BaseFilter from .macros import macros, document # noqa: F401 @@ -20,3 +22,34 @@ class BaseVariant(RegVariant): """ A comment for documentation purposes """ self.file_id = '' """ Text to use as the """ + # * Filters + self.exclude_filter = Optionable + """ [string|list(string)=''] Name of the filter to exclude components from BoM processing. + Use '_mechanical' for the default KiBoM behavior """ + self.dnf_filter = Optionable + """ [string|list(string)=''] Name of the filter to mark components as 'Do Not Fit'. + Use '_kibom_dnf' for the default KiBoM behavior """ + self.dnc_filter = Optionable + """ [string|list(string)=''] Name of the filter to mark components as 'Do Not Change'. + Use '_kibom_dnc' for the default KiBoM behavior """ + + def config(self): + super().config() + # exclude_filter + self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, 'exclude_filter') + # dnf_filter + self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter') + # dnc_filter + self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, 'dnc_filter') + + def filter(self, comps): + # Apply all the filters + if self.exclude_filter: + for c in comps: + c.in_bom = self.exclude_filter.filter(c) + if self.dnf_filter: + for c in comps: + c.fitted = self.dnf_filter.filter(c) + if self.dnc_filter: + for c in comps: + c.fixed = self.dnc_filter.filter(c) diff --git a/kibot/var_ibom.py b/kibot/var_ibom.py index 4c0e31f1..5c3e5a07 100644 --- a/kibot/var_ibom.py +++ b/kibot/var_ibom.py @@ -62,6 +62,7 @@ class IBoM(BaseVariant): # noqa: F821 return False def filter(self, comps): + super().filter(comps) logger.debug("Applying IBoM style variants `{}`".format(self.name)) # Make black/white lists case insensitive self.variants_whitelist = [v.lower() for v in self.variants_whitelist] diff --git a/kibot/var_kibom.py b/kibot/var_kibom.py index 069c88e7..44aba572 100644 --- a/kibot/var_kibom.py +++ b/kibot/var_kibom.py @@ -64,6 +64,7 @@ class KiBoM(BaseVariant): # noqa: F821 return not exclusive def filter(self, comps): + super().filter(comps) logger.debug("Applying KiBoM style variants `{}`".format(self.name)) for c in comps: if not (c.fitted and c.in_bom): From 744aa3b9c5067964fa946fd38e0dd5be4726ff5a Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:51:20 -0300 Subject: [PATCH 11/33] Added a simple output to generate filtered schematics. They can display DNF components crossed! --- kibot/__main__.py | 1 + kibot/out_sch_variant.py | 53 ++++++++++++++++ tests/yaml_samples/sch_variant_t1.kibot.yaml | 63 ++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 kibot/out_sch_variant.py create mode 100644 tests/yaml_samples/sch_variant_t1.kibot.yaml diff --git a/kibot/__main__.py b/kibot/__main__.py index 7ff7b4c3..75728672 100644 --- a/kibot/__main__.py +++ b/kibot/__main__.py @@ -94,6 +94,7 @@ has_macro = [ 'out_pdf_sch_print', 'out_position', 'out_ps', + 'out_sch_variant', 'out_step', 'out_svg', 'out_svg_sch_print', diff --git a/kibot/out_sch_variant.py b/kibot/out_sch_variant.py new file mode 100644 index 00000000..0063d03e --- /dev/null +++ b/kibot/out_sch_variant.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 Salvador E. Tropea +# Copyright (c) 2020 Instituto Nacional de TecnologĂ­a Industrial +# License: GPL-3.0 +# Project: KiBot (formerly KiPlot) +from .error import KiPlotConfigurationError +from .gs import GS +from .optionable import BaseOptions +from .registrable import RegOutput +from .macros import macros, document, output_class # noqa: F401 +from . import log + +logger = log.get_logger(__name__) + + +class Sch_Variant_Options(BaseOptions): + def __init__(self): + with document: + self.variant = '' + """ Board variant(s) to apply """ + super().__init__() + + def config(self): + super().config() + if self.variant: + if not RegOutput.is_variant(self.variant): + raise KiPlotConfigurationError("Unknown variant name `{}`".format(self.variant)) + self.variant = RegOutput.get_variant(self.variant) + else: + self.variant = None + + def run(self, output_dir, board): + if self.variant: + # Get the components list from the schematic + comps = GS.sch.get_components() + # Apply the variant + self.variant.filter(comps) + # Create the schematic + GS.sch.save_variant(output_dir) + + +@output_class +class Sch_Variant(BaseOutput): # noqa: F821 + """ Schematic with variant generator + Creates a copy of the schematic with all the filters and variants applied. + This copy isn't intended for development. + Is just a tweaked version of the original where you can look at the results. """ + def __init__(self): + super().__init__() + with document: + self.options = Sch_Variant_Options + """ [dict] Options for the `sch_variant` output """ + self._sch_related = True diff --git a/tests/yaml_samples/sch_variant_t1.kibot.yaml b/tests/yaml_samples/sch_variant_t1.kibot.yaml new file mode 100644 index 00000000..71e0db6f --- /dev/null +++ b/tests/yaml_samples/sch_variant_t1.kibot.yaml @@ -0,0 +1,63 @@ +# Example KiBot config file +kibot: + version: 1 + +variants: + - name: 't1_v1' + comment: 'Test 1 Variant V1' + type: kibom + file_id: '_(V1)' + variant: V1 + dnf_filter: '_kibom_dnf' + + - name: 't1_v2' + comment: 'Test 1 Variant V2' + type: kibom + file_id: '_(V2)' + variant: V2 + + - name: 't1_v3' + comment: 'Test 1 Variant V3' + type: kibom + file_id: '_V3' + variant: V3 + + - name: 'bla bla' + comment: 'Test 1 Variant V1+V3' + type: kibom + file_id: '_bla_bla' + variant: ['V1', 'V3'] + +outputs: + - name: 'dummy' + comment: "Copy the Schematic" + type: sch_variant + dir: Copy + + - name: 't1_v1' + comment: "V1 applied" + type: sch_variant + dir: V1 + options: + variant: t1_v1 + + - name: 't1_v2' + comment: "V2 applied" + type: sch_variant + dir: V2 + options: + variant: t1_v2 + + - name: 't1_v3' + comment: "V3 applied" + type: sch_variant + dir: V3 + options: + variant: t1_v3 + + - name: 't1_v1v3' + comment: "V1+V3 applied" + type: sch_variant + dir: V1V3 + options: + variant: 'bla bla' From 4b007938c19339587aa690bf8fdf6e37b8f5ec54 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Mon, 31 Aug 2020 20:52:48 -0300 Subject: [PATCH 12/33] Updated README and generic example. - New output format (Schematic variant) - Support for global output names in gerber. --- README.md | 15 ++++++++++++++- docs/samples/generic_plot.kibot.yaml | 15 +++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6740c4ea..83d89a18 100644 --- a/README.md +++ b/README.md @@ -522,7 +522,7 @@ Next time you need this list just use an alias, like this: - `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer. - `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen. - `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible. - - `gerber_job_file`: [string='%f-%i.%x'] name for the gerber job file (%i='job', %x='gbrjob'). + - `gerber_job_file`: [string='%f-%i%v.%x'] name for the gerber job file (%i='job', %x='gbrjob'). Affected by global options. - `gerber_precision`: [number=4.6] this the gerber coordinate format, can be 4.5 or 4.6. - `line_width`: [number=0.1] [0.02,2] line_width for objects without width [mm]. - `output`: [string='%f-%i%v.%x'] output file name, the default KiCad name if empty. Affected by global options. @@ -866,6 +866,19 @@ Next time you need this list just use an alias, like this: - `width_adjust`: [number=0] this width factor is intended to compensate PS printers/plotters that do not strictly obey line width settings. Only used to plot pads and tracks. +* Schematic with variant generator + * Type: `sch_variant` + * Description: Creates a copy of the schematic with all the filters and variants applied. + This copy isn't intended for development. + Is just a tweaked version of the original where you can look at the results. + * Valid keys: + - `comment`: [string=''] A comment for documentation purposes. + - `dir`: [string='.'] Output directory for the generated files. + - `name`: [string=''] Used to identify this particular output definition. + - `options`: [dict] Options for the `sch_variant` output. + * Valid keys: + - `variant`: [string=''] Board variant(s) to apply. + * STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure) * Type: `step` * Description: Exports the PCB as a 3D model. diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index 936aee86..f564ed59 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -258,8 +258,8 @@ outputs: exclude_pads_from_silkscreen: false # [boolean=false] include references and values even when they are marked as invisible force_plot_invisible_refs_vals: false - # [string='%f-%i.%x'] name for the gerber job file (%i='job', %x='gbrjob') - gerber_job_file: '%f-%i.%x' + # [string='%f-%i%v.%x'] name for the gerber job file (%i='job', %x='gbrjob'). Affected by global options + gerber_job_file: '%f-%i%v.%x' # [number=4.6] this the gerber coordinate format, can be 4.5 or 4.6 gerber_precision: 4.6 # [number=0.1] [0.02,2] line_width for objects without width [mm] @@ -686,6 +686,17 @@ outputs: width_adjust: 0 layers: all + # Schematic with variant generator: + # This copy isn't intended for development. + # Is just a tweaked version of the original where you can look at the results. + - name: 'sch_variant_example' + comment: 'Creates a copy of the schematic with all the filters and variants applied.' + type: 'sch_variant' + dir: 'Example/sch_variant_dir' + options: + # [string=''] Board variant(s) to apply + variant: '' + # STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure): # This is the most common 3D format for exchange purposes. # This output is what you get from the 'File/Export/STEP' menu in pcbnew. From 7cd0ccafa130a845c4c1636215af785577bb74f5 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 16:48:43 -0300 Subject: [PATCH 13/33] Fixed component save method. The first value of the redundant position is the unit. Not always 1. --- kibot/kicad/v5_sch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 26d0f12a..053e6502 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -1004,7 +1004,7 @@ class SchematicComponent(object): for field in self.fields: if field.number >= 0: field.write(f) - f.write('\t1 {} {}\n'.format(self.x, self.y)) + f.write('\t{} {} {}\n'.format(self.unit, self.x, self.y)) f.write('\t{} {} {} {}\n'.format(self.matrix[0], self.matrix[1], self.matrix[2], self.matrix[3])) f.write('$EndComp\n') From 16aefbffb62cd10b0a6da489b53288b731d88149 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 16:49:51 -0300 Subject: [PATCH 14/33] Removed debug print. --- kibot/kicad/v5_sch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 053e6502..170254f8 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -562,7 +562,7 @@ class LibComponent(object): xmt = ymt = 1e6 xMt = yMt = -1e6 ok_t = False - logger.debug("Unit "+str(unit+1)) + # logger.debug("Unit "+str(unit+1)) for dr in self.draw: if dr.sub_part != unit + 1 and dr.sub_part != 0: continue From 05f14e2049852620e1066c7e68896a5d70b3214a Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 16:51:11 -0300 Subject: [PATCH 15/33] Removed the plural in variant. Is just one. It can be complex, but one name. --- kibot/out_bom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kibot/out_bom.py b/kibot/out_bom.py index 43a05483..23e1d7ac 100644 --- a/kibot/out_bom.py +++ b/kibot/out_bom.py @@ -180,7 +180,7 @@ class BoMOptions(BaseOptions): self.number = 1 """ Number of boards to build (components multiplier) """ self.variant = '' - """ Board variant(s), used to determine which components + """ Board variant, used to determine which components are output to the BoM. """ self.output = GS.def_global_output """ filename for the output (%i=bom)""" From 0216fc93c7fd5af4c72c14ef635e8d1d0028b798 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 16:54:51 -0300 Subject: [PATCH 16/33] Added support for filters and variants to pdf_print_sch --- kibot/kicad/v5_sch.py | 4 +++- kibot/out_pdf_sch_print.py | 39 ++++++++++++++++++++++++++++++++++++-- kibot/registrable.py | 9 +++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 170254f8..6d90d0cd 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -1534,10 +1534,12 @@ class Schematic(object): lib_no = os.path.join(dest_dir, 'n.lib') self.gen_lib(lib_yes) self.gen_lib(lib_no, cross=True) - self.save(os.path.basename(self.fname), dest_dir) + fname = os.path.basename(self.fname) + self.save(fname, dest_dir) # SymLibTable to use y/n with open(os.path.join(dest_dir, 'sym-lib-table'), 'wt') as f: f.write('(sym_lib_table\n') f.write(' (lib (name y)(type Legacy)(uri ${KIPRJMOD}/y.lib)(options "")(descr ""))\n') f.write(' (lib (name n)(type Legacy)(uri ${KIPRJMOD}/n.lib)(options "")(descr ""))\n') f.write(')\n') + return fname diff --git a/kibot/out_pdf_sch_print.py b/kibot/out_pdf_sch_print.py index 6c1ae014..8557c842 100644 --- a/kibot/out_pdf_sch_print.py +++ b/kibot/out_pdf_sch_print.py @@ -4,11 +4,15 @@ # License: GPL-3.0 # Project: KiBot (formerly KiPlot) import os +from tempfile import mkdtemp +from shutil import rmtree from .gs import (GS) from .kiplot import check_eeschema_do, exec_with_retry from .misc import (CMD_EESCHEMA_DO, PDF_SCH_PRINT) -from .optionable import BaseOptions +from .optionable import BaseOptions, Optionable +from .registrable import RegOutput from .macros import macros, document, output_class # noqa: F401 +from .fil_base import BaseFilter from . import log logger = log.get_logger(__name__) @@ -19,11 +23,38 @@ class PDF_Sch_PrintOptions(BaseOptions): with document: self.output = GS.def_global_output """ filename for the output PDF (%i=schematic %x=pdf) """ + self.variant = '' + """ Board variant(s), used to determine which components are crossed. """ + self.dnf_filter = Optionable + """ [string|list(string)=''] Name of the filter to mark components as not fitted. + A short-cut to use for simple cases where a variant is an overkill """ super().__init__() + def config(self): + super().config() + self.variant = RegOutput.check_variant(self.variant) + self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter') + def run(self, output_dir, board): check_eeschema_do() - cmd = [CMD_EESCHEMA_DO, 'export', '--all_pages', '--file_format', 'pdf', GS.sch_file, output_dir] + if self.variant or self.dnf_filter: + # Get the components list from the schematic + comps = GS.sch.get_components() + # Apply the filter + if self.dnf_filter: + for c in comps: + c.fitted = self.dnf_filter.filter(c) + # Apply the variant + if self.variant: + self.variant.filter(comps) + # Save it to a temporal dir + sch_dir = mkdtemp(prefix='tmp-kibot-pdf_sch_print-') + fname = GS.sch.save_variant(sch_dir) + sch_file = os.path.join(sch_dir, fname) + else: + sch_dir = None + sch_file = GS.sch_file + cmd = [CMD_EESCHEMA_DO, 'export', '--all_pages', '--file_format', 'pdf', sch_file, output_dir] if GS.debug_enabled: cmd.insert(1, '-vv') cmd.insert(1, '-r') @@ -38,6 +69,10 @@ class PDF_Sch_PrintOptions(BaseOptions): new = self.expand_filename_sch(output_dir, self.output, id, ext) logger.debug('Moving '+cur+' -> '+new) os.rename(cur, new) + # Remove the temporal dir if needed + if sch_dir: + logger.debug('Removing temporal variant dir `{}`'.format(sch_dir)) + rmtree(sch_dir) @output_class diff --git a/kibot/registrable.py b/kibot/registrable.py index f44c8834..d1f84368 100644 --- a/kibot/registrable.py +++ b/kibot/registrable.py @@ -4,6 +4,7 @@ # License: GPL-3.0 # Project: KiBot (formerly KiPlot) from .optionable import Optionable +from .error import KiPlotConfigurationError class Registrable(object): @@ -72,6 +73,14 @@ class RegOutput(Optionable, Registrable): def add_filter(obj): RegOutput._def_filters[obj.name] = obj + @staticmethod + def check_variant(variant): + if variant: + if not RegOutput.is_variant(variant): + raise KiPlotConfigurationError("Unknown variant name `{}`".format(variant)) + return RegOutput.get_variant(variant) + return None + class RegVariant(Optionable, Registrable): """ An optionable that is also registrable. From c26481790a88bf432a1ae085bcb4b7bc02488ac3 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 16:55:36 -0300 Subject: [PATCH 17/33] Added support for filters to sch_variant --- kibot/out_sch_variant.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/kibot/out_sch_variant.py b/kibot/out_sch_variant.py index 0063d03e..82a124e4 100644 --- a/kibot/out_sch_variant.py +++ b/kibot/out_sch_variant.py @@ -3,11 +3,11 @@ # Copyright (c) 2020 Instituto Nacional de TecnologĂ­a Industrial # License: GPL-3.0 # Project: KiBot (formerly KiPlot) -from .error import KiPlotConfigurationError from .gs import GS -from .optionable import BaseOptions +from .optionable import BaseOptions, Optionable from .registrable import RegOutput from .macros import macros, document, output_class # noqa: F401 +from .fil_base import BaseFilter from . import log logger = log.get_logger(__name__) @@ -18,23 +18,28 @@ class Sch_Variant_Options(BaseOptions): with document: self.variant = '' """ Board variant(s) to apply """ + self.dnf_filter = Optionable + """ [string|list(string)=''] Name of the filter to mark components as not fitted. + A short-cut to use for simple cases where a variant is an overkill """ super().__init__() def config(self): super().config() - if self.variant: - if not RegOutput.is_variant(self.variant): - raise KiPlotConfigurationError("Unknown variant name `{}`".format(self.variant)) - self.variant = RegOutput.get_variant(self.variant) - else: - self.variant = None + self.variant = RegOutput.check_variant(self.variant) + self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter') def run(self, output_dir, board): - if self.variant: + if self.dnf_filter or self.variant: # Get the components list from the schematic comps = GS.sch.get_components() + # Apply the filter + if self.dnf_filter: + for c in comps: + c.fitted = self.dnf_filter.filter(c) # Apply the variant - self.variant.filter(comps) + if self.variant: + # Apply the variant + self.variant.filter(comps) # Create the schematic GS.sch.save_variant(output_dir) From 7f6144e32e3f1e2cfeede0fbd9511b8befea20b5 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 16:56:31 -0300 Subject: [PATCH 18/33] Added tests for sch_variant and pdf_print_sch For the filter and variant stuff. --- Makefile | 3 + tests/reference/test_v5-schematic_(no_L).pdf | Bin 0 -> 101215 bytes tests/test_plot/test_print_sch.py | 61 ++++++++++++++++++ tests/utils/context.py | 23 ++++--- .../print_pdf_no_inductors_1.kibot.yaml | 25 +++++++ .../print_pdf_no_inductors_2.kibot.yaml | 18 ++++++ .../sch_no_inductors_1.kibot.yaml | 25 +++++++ .../sch_no_inductors_2.kibot.yaml | 18 ++++++ 8 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 tests/reference/test_v5-schematic_(no_L).pdf create mode 100644 tests/yaml_samples/print_pdf_no_inductors_1.kibot.yaml create mode 100644 tests/yaml_samples/print_pdf_no_inductors_2.kibot.yaml create mode 100644 tests/yaml_samples/sch_no_inductors_1.kibot.yaml create mode 100644 tests/yaml_samples/sch_no_inductors_2.kibot.yaml diff --git a/Makefile b/Makefile index 19d40059..ad091ac0 100644 --- a/Makefile +++ b/Makefile @@ -106,6 +106,9 @@ gen_ref: cp -a $(REFILL).refill $(REFILL) src/kibot -c tests/yaml_samples/pdf_zone-refill.kibot.yaml -b tests/board_samples/zone-refill.kicad_pcb -d $(REFDIR) src/kibot -c tests/yaml_samples/print_pcb_zone-refill.kibot.yaml -b tests/board_samples/zone-refill.kicad_pcb -d $(REFDIR) + src/kibot -c tests/yaml_samples/sch_no_inductors.kibot.yaml -e tests/board_samples/test_v5.sch -d $(REFDIR) + mv "$(REFDIR)no_inductor/test_v5-schematic_(no_L).pdf" $(REFDIR) + rmdir $(REFDIR)no_inductor/ cp -a $(REFILL).ok $(REFILL) doc: diff --git a/tests/reference/test_v5-schematic_(no_L).pdf b/tests/reference/test_v5-schematic_(no_L).pdf new file mode 100644 index 0000000000000000000000000000000000000000..3c06724206e24a9522f68745b3fce84736906910 GIT binary patch literal 101215 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 Awg3PC literal 0 HcmV?d00001 diff --git a/tests/test_plot/test_print_sch.py b/tests/test_plot/test_print_sch.py index 08d85aea..41a78e1d 100644 --- a/tests/test_plot/test_print_sch.py +++ b/tests/test_plot/test_print_sch.py @@ -11,17 +11,20 @@ pytest-3 --log-cli-level debug import os import sys +import logging # Look for the 'utils' module from where the script is running prev_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if prev_dir not in sys.path: sys.path.insert(0, prev_dir) from kibot.misc import (PDF_SCH_PRINT, SVG_SCH_PRINT) +from kibot.kicad.v5_sch import Schematic, SchFileError # Utils import from utils import context PDF_DIR = '' PDF_FILE = 'Schematic.pdf' SVG_FILE = 'Schematic.svg' +NI_DIR = 'no_inductor' def test_print_sch_ok(): @@ -54,3 +57,61 @@ def test_print_sch_svg_fail(): ctx = context.TestContext('PrSCHFail_SVG', prj, 'print_sch_svg', PDF_DIR) ctx.run(SVG_SCH_PRINT, no_board_file=True, extra=['-e', os.path.join(ctx.get_board_dir(), 'print_err.sch')]) ctx.clean_up() + + +def check_l1(ctx): + ctx.run() + o_name = os.path.join(NI_DIR, 'test_v5.sch') + ctx.expect_out_file(o_name) + sch = Schematic() + try: + sch.load(ctx.get_out_path(o_name)) + except SchFileError as e: + logging.error('At line {} of `{}`: {}'.format(e.line, e.file, e.msg)) + logging.error('Line content: `{}`'.format(e.code)) + assert False + comps = sch.get_components() + l1 = next(c for c in comps if c.ref == 'L1') + assert l1 + logging.debug('Found L1') + assert l1.lib == 'n' + logging.debug('L1 is crossed') + ctx.clean_up() + + +def test_sch_variant_ni_1(): + """ Using a variant """ + prj = 'test_v5' # Is the most complete, contains every KiCad object I know + ctx = context.TestContextSCH('test_sch_variant_ni_1', prj, 'sch_no_inductors_1', PDF_DIR) + check_l1(ctx) + + +def test_sch_variant_ni_2(): + """ Using a filter """ + prj = 'test_v5' # Is the most complete, contains every KiCad object I know + ctx = context.TestContextSCH('test_sch_variant_ni_2', prj, 'sch_no_inductors_2', PDF_DIR) + check_l1(ctx) + + +def test_print_sch_variant_ni_1(): + """ Using a variant """ + prj = 'test_v5' # Is the most complete, contains every KiCad object I know + ctx = context.TestContextSCH('test_print_sch_variant_ni_1', prj, 'print_pdf_no_inductors_1', PDF_DIR) + ctx.run() + r_name = 'test_v5-schematic_(no_L).pdf' + o_name = os.path.join(NI_DIR, r_name) + ctx.expect_out_file(o_name) + ctx.compare_pdf(o_name, r_name) + ctx.clean_up() + + +def test_print_sch_variant_ni_2(): + """ Using a filter """ + prj = 'test_v5' # Is the most complete, contains every KiCad object I know + ctx = context.TestContextSCH('test_print_sch_variant_ni_2', prj, 'print_pdf_no_inductors_2', PDF_DIR) + ctx.run() + r_name = 'test_v5-schematic_(no_L).pdf' + o_name = os.path.join(NI_DIR, 'test_v5-schematic.pdf') + ctx.expect_out_file(o_name) + ctx.compare_pdf(o_name, r_name) + ctx.clean_up() diff --git a/tests/utils/context.py b/tests/utils/context.py index 7a126a89..8bfffc8d 100644 --- a/tests/utils/context.py +++ b/tests/utils/context.py @@ -20,6 +20,10 @@ MODE_SCH = 1 MODE_PCB = 0 +def quote(s): + return '"'+s+'"' + + class TestContext(object): def __init__(self, test_name, board_name, yaml_name, sub_dir, yaml_compressed=False): @@ -296,17 +300,21 @@ class TestContext(object): m = re.search(t, txt, re.MULTILINE) assert m is None - def compare_image(self, image, reference=None, diff='diff.png'): + def compare_image(self, image, reference=None, diff='diff.png', ref_out_dir=False): """ For images and single page PDFs """ if reference is None: reference = image + if ref_out_dir: + reference = self.get_out_path(reference) + else: + reference = os.path.join(REF_DIR, reference) cmd = ['compare', # Tolerate 5 % error in color '-fuzz', '5%', # Count how many pixels differ '-metric', 'AE', self.get_out_path(image), - os.path.join(REF_DIR, reference), + reference, # Avoid the part where KiCad version is printed '-crop', '100%x92%+0+0', '+repage', '-colorspace', 'RGB', @@ -344,16 +352,7 @@ class TestContext(object): assert len(ref_pages) == len(gen_pages) # Compare each page for page in range(len(ref_pages)): - cmd = ['compare', '-metric', 'MSE', - self.get_out_path('ref-'+str(page)+'.png'), - self.get_out_path('gen-'+str(page)+'.png'), - self.get_out_path(diff.format(page))] - logging.debug('Comparing images with: '+str(cmd)) - res = subprocess.check_output(cmd, stderr=subprocess.STDOUT) - m = re.match(r'([\d\.]+) \(([\d\.]+)\)', res.decode()) - assert m - logging.debug('MSE={} ({})'.format(m.group(1), m.group(2))) - assert float(m.group(2)) == 0.0 + self.compare_image('gen-'+str(page)+'.png', 'ref-'+str(page)+'.png', diff.format(page), ref_out_dir=True) def compare_txt(self, text, reference=None, diff='diff.txt'): if reference is None: diff --git a/tests/yaml_samples/print_pdf_no_inductors_1.kibot.yaml b/tests/yaml_samples/print_pdf_no_inductors_1.kibot.yaml new file mode 100644 index 00000000..4ae89156 --- /dev/null +++ b/tests/yaml_samples/print_pdf_no_inductors_1.kibot.yaml @@ -0,0 +1,25 @@ +# Example KiBot config file +kibot: + version: 1 + +filters: + - name: 'no_inductor' + comment: 'Inductors removed' + type: generic + exclude_refs: + - L* + +variants: + - name: 'no_inductor' + comment: 'Inductors removed' + type: kibom + file_id: '_(no_L)' + dnf_filter: 'no_inductor' + +outputs: + - name: 'no_inductor' + comment: "Inductors removed" + type: pdf_sch_print + dir: no_inductor + options: + variant: 'no_inductor' diff --git a/tests/yaml_samples/print_pdf_no_inductors_2.kibot.yaml b/tests/yaml_samples/print_pdf_no_inductors_2.kibot.yaml new file mode 100644 index 00000000..e83133a4 --- /dev/null +++ b/tests/yaml_samples/print_pdf_no_inductors_2.kibot.yaml @@ -0,0 +1,18 @@ +# Example KiBot config file +kibot: + version: 1 + +filters: + - name: 'no_inductor' + comment: 'Inductors removed' + type: generic + exclude_refs: + - L* + +outputs: + - name: 'no_inductor' + comment: "Inductors removed" + type: pdf_sch_print + dir: no_inductor + options: + dnf_filter: 'no_inductor' diff --git a/tests/yaml_samples/sch_no_inductors_1.kibot.yaml b/tests/yaml_samples/sch_no_inductors_1.kibot.yaml new file mode 100644 index 00000000..0f31165b --- /dev/null +++ b/tests/yaml_samples/sch_no_inductors_1.kibot.yaml @@ -0,0 +1,25 @@ +# Example KiBot config file +kibot: + version: 1 + +filters: + - name: 'no_inductor' + comment: 'Inductors removed' + type: generic + exclude_refs: + - L* + +variants: + - name: 'no_inductor' + comment: 'Inductors removed' + type: kibom + file_id: '_(no_L)' + dnf_filter: 'no_inductor' + +outputs: + - name: 'no_inductor' + comment: "Inductors removed" + type: sch_variant + dir: no_inductor + options: + variant: 'no_inductor' diff --git a/tests/yaml_samples/sch_no_inductors_2.kibot.yaml b/tests/yaml_samples/sch_no_inductors_2.kibot.yaml new file mode 100644 index 00000000..6c9e16fd --- /dev/null +++ b/tests/yaml_samples/sch_no_inductors_2.kibot.yaml @@ -0,0 +1,18 @@ +# Example KiBot config file +kibot: + version: 1 + +filters: + - name: 'no_inductor' + comment: 'Inductors removed' + type: generic + exclude_refs: + - L* + +outputs: + - name: 'no_inductor' + comment: "Inductors removed" + type: sch_variant + dir: no_inductor + options: + dnf_filter: 'no_inductor' From 20936da07e8e4341817bca8de61bb56e4ce34076 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 16:57:31 -0300 Subject: [PATCH 19/33] Updated the README and generic example. - Plural removed from BoM variant - Support for filter in sch_variant and pdf_print_sch - Support for variant in pdf_print_sch --- README.md | 7 ++++++- docs/samples/generic_plot.kibot.yaml | 10 +++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 83d89a18..638cdca4 100644 --- a/README.md +++ b/README.md @@ -405,7 +405,7 @@ Next time you need this list just use an alias, like this: - `number`: [number=1] Number of boards to build (components multiplier). - `output`: [string='%f-%i%v.%x'] filename for the output (%i=bom). Affected by global options. - `use_alt`: [boolean=false] Print grouped references in the alternate compressed style eg: R1-R7,R18. - - `variant`: [string=''] Board variant(s), used to determine which components + - `variant`: [string=''] Board variant, used to determine which components are output to the BoM.. - `xlsx`: [dict] Options for the XLSX format. * Valid keys: @@ -812,7 +812,10 @@ Next time you need this list just use an alias, like this: - `name`: [string=''] Used to identify this particular output definition. - `options`: [dict] Options for the `pdf_sch_print` output. * Valid keys: + - `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. + A short-cut to use for simple cases where a variant is an overkill. - `output`: [string='%f-%i%v.%x'] filename for the output PDF (%i=schematic %x=pdf). Affected by global options. + - `variant`: [string=''] Board variant(s), used to determine which components are crossed.. * Pick & place * Type: `position` @@ -877,6 +880,8 @@ Next time you need this list just use an alias, like this: - `name`: [string=''] Used to identify this particular output definition. - `options`: [dict] Options for the `sch_variant` output. * Valid keys: + - `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted. + A short-cut to use for simple cases where a variant is an overkill. - `variant`: [string=''] Board variant(s) to apply. * STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure) diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index f564ed59..e5c745a3 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -121,7 +121,7 @@ outputs: output: '%f-%i%v.%x' # [boolean=false] Print grouped references in the alternate compressed style eg: R1-R7,R18 use_alt: false - # [string=''] Board variant(s), used to determine which components + # [string=''] Board variant, used to determine which components # are output to the BoM. variant: '' # [dict] Options for the XLSX format @@ -619,8 +619,13 @@ outputs: type: 'pdf_sch_print' dir: 'Example/pdf_sch_print_dir' options: + # [string|list(string)=''] Name of the filter to mark components as not fitted. + # A short-cut to use for simple cases where a variant is an overkill + dnf_filter: '' # [string='%f-%i%v.%x'] filename for the output PDF (%i=schematic %x=pdf). Affected by global options output: '%f-%i%v.%x' + # [string=''] Board variant(s), used to determine which components are crossed. + variant: '' # Pick & place: # This output is what you get from the 'File/Fabrication output/Footprint poistion (.pos) file' menu in pcbnew. @@ -694,6 +699,9 @@ outputs: type: 'sch_variant' dir: 'Example/sch_variant_dir' options: + # [string|list(string)=''] Name of the filter to mark components as not fitted. + # A short-cut to use for simple cases where a variant is an overkill + dnf_filter: '' # [string=''] Board variant(s) to apply variant: '' From 8a2810fd8d63bd313a719ea32f2da527f7768ffa Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 18:46:59 -0300 Subject: [PATCH 20/33] Added support to remove the filters of the internal BoM. --- kibot/bom/bom.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/kibot/bom/bom.py b/kibot/bom/bom.py index 4f3f76c5..d62bb329 100644 --- a/kibot/bom/bom.py +++ b/kibot/bom/bom.py @@ -363,10 +363,15 @@ def group_components(cfg, components): def do_bom(file_name, ext, comps, cfg): # Apply all the filters - for c in comps: - c.in_bom = cfg.exclude_filter.filter(c) - c.fitted = cfg.dnf_filter.filter(c) - c.fixed = cfg.dnc_filter.filter(c) + if cfg.exclude_filter: + for c in comps: + c.in_bom = cfg.exclude_filter.filter(c) + if cfg.dnf_filter: + for c in comps: + c.fitted = cfg.dnf_filter.filter(c) + if cfg.dnc_filter: + for c in comps: + c.fixed = cfg.dnc_filter.filter(c) # Apply the variant cfg.variant.filter(comps) # Group components according to group_fields From 563c00cc5bf8b059ee3ec8aefb9dc9f5f5fc97e8 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 18:47:38 -0300 Subject: [PATCH 21/33] Enabled the variants exception catch. Now the variants configuration can raise exceptions. --- kibot/config_reader.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/kibot/config_reader.py b/kibot/config_reader.py index 83c3cbef..3726dcd0 100644 --- a/kibot/config_reader.py +++ b/kibot/config_reader.py @@ -109,12 +109,10 @@ class CfgYamlReader(object): logger.debug("Parsing "+kind+" "+name_type) o_var = reg_class.get_class_for(otype)() o_var.set_tree(o_tree) - # No errors yet -# try: -# o_var.config() -# except KiPlotConfigurationError as e: -# config_error("In section `"+name_type+"`: "+str(e)) - o_var.config() + try: + o_var.config() + except KiPlotConfigurationError as e: + config_error("In section `"+name_type+"`: "+str(e)) return o_var def _parse_variants(self, v): From 9ebf18ba11185d996d053e1468eaff52ccaa9281 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 18:48:45 -0300 Subject: [PATCH 22/33] Added new basic filters. `!` is always False `_none` is no filter --- kibot/fil_base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kibot/fil_base.py b/kibot/fil_base.py index 4a2b93cf..3fa8ea69 100644 --- a/kibot/fil_base.py +++ b/kibot/fil_base.py @@ -141,6 +141,8 @@ class BaseFilter(RegFilter): names = [default] elif isinstance(names, str): # User provided, but only one, make a list + if names == '_none': + return None names = [names] # Here we should have a list of strings filters = [] @@ -150,6 +152,10 @@ class BaseFilter(RegFilter): if name[0] == '!': invert = True name = name[1:] + # '!' => always False + if not name: + filters.append(NotFilter(DummyFilter())) + continue else: invert = False # Is already defined? From e0de652105a4c067f0c2e349cefd4a2045235a45 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 18:49:38 -0300 Subject: [PATCH 23/33] Added more tests for the filters and variants. --- tests/test_plot/test_int_bom.py | 19 +++++++++ tests/test_plot/test_yaml_errors.py | 14 +++++++ .../yaml_samples/error_unk_variant.kibot.yaml | 11 ++++++ .../error_wrong_fil_name.kibot.yaml | 10 +++++ .../int_bom_var_t3_csv.kibot.yaml | 39 +++++++++++++++++++ 5 files changed, 93 insertions(+) create mode 100644 tests/yaml_samples/error_unk_variant.kibot.yaml create mode 100644 tests/yaml_samples/error_wrong_fil_name.kibot.yaml create mode 100644 tests/yaml_samples/int_bom_var_t3_csv.kibot.yaml diff --git a/tests/test_plot/test_int_bom.py b/tests/test_plot/test_int_bom.py index 316da62b..d1ca4c5d 100644 --- a/tests/test_plot/test_int_bom.py +++ b/tests/test_plot/test_int_bom.py @@ -1283,3 +1283,22 @@ def test_int_bom_fil_1(): rows, header, info = ctx.load_csv('multi.csv') check_kibom_test_netlist(rows, ref_column, 1, None, ['C1-C2']) ctx.clean_up() + + +def test_int_bom_variant_t3(): + """ Test if we can move the filters to the variant. + Also test the '!' filter (always false) """ + prj = 'kibom-variante' + ctx = context.TestContextSCH('test_int_bom_variant_t3', prj, 'int_bom_var_t3_csv', BOM_DIR) + ctx.run() + rows, header, info = ctx.load_csv(prj+'-bom_(V1).csv') + ref_column = header.index(REF_COLUMN_NAME) + check_kibom_test_netlist(rows, ref_column, 2, ['R3', 'R4'], ['R1', 'R2']) + VARIANTE_PRJ_INFO[1] = 't1_v1' + check_csv_info(info, VARIANTE_PRJ_INFO, [4, 20, 2, 1, 2]) + ctx.search_err(r"Creating internal filter(.*)_mechanical") + ctx.search_err(r"Creating internal filter(.*)_kibom_dnf_Config") + ctx.search_err(r"Creating internal filter(.*)_kibom_dnc") + rows, header, info = ctx.load_csv(prj+'-bom_(V1b).csv') + # Here we remove the DNC, so R1 and R2 becomes identical + check_kibom_test_netlist(rows, ref_column, 1, ['R3', 'R4'], ['R1', 'R2']) diff --git a/tests/test_plot/test_yaml_errors.py b/tests/test_plot/test_yaml_errors.py index 864c3621..523b53c6 100644 --- a/tests/test_plot/test_yaml_errors.py +++ b/tests/test_plot/test_yaml_errors.py @@ -578,3 +578,17 @@ def test_error_fil_unknown(): ctx.run(EXIT_BAD_CONFIG) assert ctx.search_err("Unknown filter (.*) used for ") ctx.clean_up() + + +def test_error_var_unknown(): + ctx = context.TestContextSCH('test_error_var_unknown', 'links', 'error_unk_variant', '') + ctx.run(EXIT_BAD_CONFIG) + assert ctx.search_err("Unknown variant name") + ctx.clean_up() + + +def test_error_wrong_fil_name(): + ctx = context.TestContextSCH('test_error_wrong_fil_name', 'links', 'error_wrong_fil_name', '') + ctx.run(EXIT_BAD_CONFIG) + assert ctx.search_err("Filter names starting with (.*) are reserved") + ctx.clean_up() diff --git a/tests/yaml_samples/error_unk_variant.kibot.yaml b/tests/yaml_samples/error_unk_variant.kibot.yaml new file mode 100644 index 00000000..497a617f --- /dev/null +++ b/tests/yaml_samples/error_unk_variant.kibot.yaml @@ -0,0 +1,11 @@ +# Example KiBot config file +kibot: + version: 1 + +outputs: + - name: 'no_inductor' + comment: "Inductors removed" + type: sch_variant + dir: no_inductor + options: + variant: 'no_inductor' diff --git a/tests/yaml_samples/error_wrong_fil_name.kibot.yaml b/tests/yaml_samples/error_wrong_fil_name.kibot.yaml new file mode 100644 index 00000000..0fa2ccc5 --- /dev/null +++ b/tests/yaml_samples/error_wrong_fil_name.kibot.yaml @@ -0,0 +1,10 @@ +# Example KiBot config file +kibot: + version: 1 + +filters: + - name: '_no_inductor' + comment: 'Inductors removed' + type: generic + exclude_refs: + - L* diff --git a/tests/yaml_samples/int_bom_var_t3_csv.kibot.yaml b/tests/yaml_samples/int_bom_var_t3_csv.kibot.yaml new file mode 100644 index 00000000..15bbe700 --- /dev/null +++ b/tests/yaml_samples/int_bom_var_t3_csv.kibot.yaml @@ -0,0 +1,39 @@ +# Example KiBot config file +kibot: + version: 1 + + +variants: + - name: 't1_v1' + comment: 'Test 1 Variant V1' + type: kibom + file_id: '_(V1)' + variant: V1 + dnc_filter: '_kibom_dnc' + dnf_filter: '_kibom_dnf_Config' + exclude_filter: '_mechanical' + + - name: 't1_v1b' + comment: 'Test 1 Variant V1' + type: kibom + file_id: '_(V1b)' + variant: V1 + +outputs: + - name: 'bom_internal_v1' + comment: "Bill of Materials in CSV format for variant t1_v1" + type: bom + dir: BoM + options: + variant: t1_v1 + dnc_filter: '_none' + dnf_filter: '_none' + exclude_filter: '_none' + + - name: 'bom_internal_v1b' + comment: "Bill of Materials in CSV format for variant t1_v1" + type: bom + dir: BoM + options: + variant: t1_v1b + dnc_filter: '!' From 7dfd7aab1672de8e02da0e325ecc34d1f62ffa50 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 20:28:19 -0300 Subject: [PATCH 24/33] Updated the way we generate the test_v5-schematic_(no_L).pdf reference --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ad091ac0..91404387 100644 --- a/Makefile +++ b/Makefile @@ -106,7 +106,7 @@ gen_ref: cp -a $(REFILL).refill $(REFILL) src/kibot -c tests/yaml_samples/pdf_zone-refill.kibot.yaml -b tests/board_samples/zone-refill.kicad_pcb -d $(REFDIR) src/kibot -c tests/yaml_samples/print_pcb_zone-refill.kibot.yaml -b tests/board_samples/zone-refill.kicad_pcb -d $(REFDIR) - src/kibot -c tests/yaml_samples/sch_no_inductors.kibot.yaml -e tests/board_samples/test_v5.sch -d $(REFDIR) + src/kibot -c tests/yaml_samples/print_pdf_no_inductors_1.kibot.yaml -e tests/board_samples/test_v5.sch -d $(REFDIR) mv "$(REFDIR)no_inductor/test_v5-schematic_(no_L).pdf" $(REFDIR) rmdir $(REFDIR)no_inductor/ cp -a $(REFILL).ok $(REFILL) From 12534d8202ad1c3bdea0a8dd6f99b538f6c03510 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 20:29:49 -0300 Subject: [PATCH 25/33] Added new filter criteria: exclude ref == '#*' --- kibot/fil_generic.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kibot/fil_generic.py b/kibot/fil_generic.py index 9d6a2fef..b6feb68a 100644 --- a/kibot/fil_generic.py +++ b/kibot/fil_generic.py @@ -64,6 +64,8 @@ class Generic(BaseFilter): # noqa: F821 self.exclude_refs = Optionable """ [list(string)] List of references to be excluded. Use R* for all references with R prefix """ + self.exclude_all_hash_ref = False + """ Exclude all components with a reference starting with # """ # Skip virtual components if needed # TODO: We currently lack this information # if config.blacklist_virtual and m.attr == 'Virtual': @@ -146,6 +148,9 @@ class Generic(BaseFilter): # noqa: F821 # Exclude components with empty 'Value' if self.exclude_empty_val and (value == '' or value == '~'): return exclude + # Exclude all ref == #* + if self.exclude_all_hash_ref and comp.ref[0] == '#': + return exclude # List of references to be excluded if self.exclude_refs and (comp.ref in self.exclude_refs or comp.ref_prefix+'*' in self.exclude_refs): return exclude From 76ea47c04fba794f7e12da97ae257bafa5e84ac9 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 20:30:34 -0300 Subject: [PATCH 26/33] Now the default exclude filter excludes ref == '#*' --- kibot/fil_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kibot/fil_base.py b/kibot/fil_base.py index 3fa8ea69..b9bc53b1 100644 --- a/kibot/fil_base.py +++ b/kibot/fil_base.py @@ -88,6 +88,7 @@ class BaseFilter(RegFilter): o_tree = {'name': name} o_tree['type'] = 'generic' o_tree['comment'] = 'Internal default mechanical filter' + o_tree['exclude_all_hash_ref'] = True o_tree['exclude_any'] = DEFAULT_EXCLUDE logger.debug('Creating internal filter: '+str(o_tree)) return o_tree From 0b3c36d11a867da6e037ee83cbc93917bb339ddb Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 20:31:41 -0300 Subject: [PATCH 27/33] Fixed the DrawText.write method Was writing an extra quote --- kibot/kicad/v5_sch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 6d90d0cd..d1f0f5ba 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -389,9 +389,9 @@ class DrawText(object): return txt def write(self, f): - f.write('T {} {} {} {}'.format(self.orientation, self.pos_x, self.pos_y, self.radius)) + f.write('T {} {} {} {}'.format(self.orientation, self.pos_x, self.pos_y, self.size)) f.write(' {} {} {} "{}"'.format(self.type, self.sub_part, self.convert, self.text)) - f.write(' {} {} {} {}\n" '.format(['Normal', 'Italic'][self.italic], int(self.bold), self.hjustify, self.vjustify)) + f.write(' {} {} {} {}\n'.format(['Normal', 'Italic'][self.italic], int(self.bold), self.hjustify, self.vjustify)) def get_rect(self): return 0, 0, 0, 0, False From 0a6fdccb1d49fa45234a7c6fb7bb55025a10d297 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 20:32:41 -0300 Subject: [PATCH 28/33] Removed dead code. --- kibot/kicad/v5_sch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index d1f0f5ba..e877145c 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -1528,8 +1528,9 @@ class Schematic(object): self.sub_sheets[c].save(file, dest_dir) def save_variant(self, dest_dir): - if not os.path.exists(dest_dir): - os.makedirs(dest_dir) + # Currently imposible + # if not os.path.exists(dest_dir): + # os.makedirs(dest_dir) lib_yes = os.path.join(dest_dir, 'y.lib') lib_no = os.path.join(dest_dir, 'n.lib') self.gen_lib(lib_yes) From 11cb826c06e61ad52b965ef43530dd7cc1a129f5 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 20:35:43 -0300 Subject: [PATCH 29/33] Added more complexity to the "all-in-one" schematic. - Use a library - Use a compponent with user defined field. - Get the user defined field from the lib - Use a component with a circle - Use a component with text - Use a virtual component with ~ in name and # in ref --- tests/board_samples/l1.lib | 40 +++++++++++++++++++++++++++++++ tests/board_samples/sym-lib-table | 3 +++ tests/board_samples/test_v5.sch | 14 ++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/board_samples/l1.lib create mode 100644 tests/board_samples/sym-lib-table diff --git a/tests/board_samples/l1.lib b/tests/board_samples/l1.lib new file mode 100644 index 00000000..89cb0f58 --- /dev/null +++ b/tests/board_samples/l1.lib @@ -0,0 +1,40 @@ +EESchema-LIBRARY Version 2.4 +#encoding utf-8 +# +# R +# +DEF R R 0 0 N Y 1 F N +F0 "R" 80 0 50 V V C CNN +F1 "R" 0 0 50 V V C CNN +F2 "" -70 0 50 V I C CNN +F3 "" 0 0 50 H I C CNN +F4 "Hi!" 0 0 50 H I C CNN "Test" +$FPLIST + R_* + R_* +$ENDFPLIST +DRAW +S -40 -100 40 100 0 1 10 N +X ~ 1 0 150 50 D 50 50 1 1 P +X ~ 2 0 -150 50 U 50 50 1 1 P +ENDDRAW +ENDDEF +# +# SYM_CAUTION +# +DEF ~SYM_CAUTION #SYM_CAUTION 0 40 Y Y 1 F N +F0 "#SYM_CAUTION" 0 150 50 H I C CNN +F1 "SYM_CAUTION" 0 -175 50 H I C CNN +F2 "Tedy:Symbol_Caution_Type2_FSilkS_Small" 100 -250 50 H I C CNN +F3 "" 0 0 50 H I C CNN +DRAW +A 0 35 16 1616 184 0 0 0 N -15 40 15 40 +C 0 -46 10 0 0 0 F +T 0 0 -100 20 0 0 0 CAUTION Italic 0 C C +P 2 0 0 0 -5 -30 -15 40 F +P 3 0 0 0 -5 -30 5 -30 15 40 F +P 5 0 0 0 -50 -75 75 -75 0 100 -75 -75 -50 -75 f +ENDDRAW +ENDDEF +# +#End Library diff --git a/tests/board_samples/sym-lib-table b/tests/board_samples/sym-lib-table new file mode 100644 index 00000000..890dc4a1 --- /dev/null +++ b/tests/board_samples/sym-lib-table @@ -0,0 +1,3 @@ +(sym_lib_table + (lib (name l1)(type Legacy)(uri ${KIPRJMOD}/l1.lib)(options "")(descr "")) +) diff --git a/tests/board_samples/test_v5.sch b/tests/board_samples/test_v5.sch index 3b4ac3c9..a511eda4 100644 --- a/tests/board_samples/test_v5.sch +++ b/tests/board_samples/test_v5.sch @@ -1908,13 +1908,14 @@ B8 00 A2 1F 41 E0 C7 2A 65 BE 7E E1 F5 E7 46 4B 3D 22 C6 18 DB CE 36 66 F9 2E 00 EndData $EndBitmap $Comp -L Device:R R1 +L l1:R R1 U 1 1 5F33EC02 P 1300 3450 F 0 "R1" V 1093 3450 50 0000 C CNN F 1 "R" V 1184 3450 50 0000 C CNN F 2 "" V 1230 3450 50 0001 C CNN F 3 "~" H 1300 3450 50 0001 C CNN +F 4 "Hi!" H 1300 3450 50 0001 C CNN "Test" 1 1300 3450 0 1 1 0 $EndComp @@ -1987,4 +1988,15 @@ Wire Wire Line 3800 4500 3800 3250 Wire Wire Line 3800 3250 4000 3250 +$Comp +L l1:SYM_CAUTION #SYM_CAUTION1 +U 1 1 5F4ECE1F +P 2750 3450 +F 0 "#SYM_CAUTION1" H 2750 3600 50 0001 C CNN +F 1 "SYM_CAUTION" H 2750 3275 50 0001 C CNN +F 2 "" H 2850 3200 50 0001 C CNN +F 3 "" H 2750 3450 50 0001 C CNN + 1 2750 3450 + 1 0 0 -1 +$EndComp $EndSCHEMATC From ff6282eca1d6a5a44fcf1e362df175d4a861dfa7 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 20:39:15 -0300 Subject: [PATCH 30/33] Added test for the new errors in DrawText elements. --- .../board_samples/v5_errors/error_bad_text2.sch | 17 +++++++++++++++++ .../board_samples/v5_errors/error_bad_text3.sch | 17 +++++++++++++++++ tests/test_plot/test_sch_errors.py | 8 ++++++++ 3 files changed, 42 insertions(+) create mode 100644 tests/board_samples/v5_errors/error_bad_text2.sch create mode 100644 tests/board_samples/v5_errors/error_bad_text3.sch diff --git a/tests/board_samples/v5_errors/error_bad_text2.sch b/tests/board_samples/v5_errors/error_bad_text2.sch new file mode 100644 index 00000000..f0b69d91 --- /dev/null +++ b/tests/board_samples/v5_errors/error_bad_text2.sch @@ -0,0 +1,17 @@ +EESchema Schematic File Version 4 +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 1 1 +Title "" +Date "" +Rev "" +Comp "" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +Text GLabel 1600 2300 2 50 +$EndSCHEMATC diff --git a/tests/board_samples/v5_errors/error_bad_text3.sch b/tests/board_samples/v5_errors/error_bad_text3.sch new file mode 100644 index 00000000..020591f2 --- /dev/null +++ b/tests/board_samples/v5_errors/error_bad_text3.sch @@ -0,0 +1,17 @@ +EESchema Schematic File Version 4 +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 1 1 +Title "" +Date "" +Rev "" +Comp "" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +Text GLabel 1600 2300 2 50 BiDi ~ AA +$EndSCHEMATC diff --git a/tests/test_plot/test_sch_errors.py b/tests/test_plot/test_sch_errors.py index 19de8f69..ebed55b3 100644 --- a/tests/test_plot/test_sch_errors.py +++ b/tests/test_plot/test_sch_errors.py @@ -154,6 +154,14 @@ def test_sch_errors_bad_text(): setup_ctx('bad_text', 'Malformed .?Text.?') +def test_sch_errors_bad_text2(): + setup_ctx('bad_text2', 'Missing .?Text.? shape') + + +def test_sch_errors_bad_text3(): + setup_ctx('bad_text3', 'Not a number in .?Text.?') + + def test_sch_errors_bad_wire(): setup_ctx('bad_wire', 'Malformed wire') From 018d99e3b2078e2b6c930b0873f1455c2b041ada Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Tue, 1 Sep 2020 20:39:44 -0300 Subject: [PATCH 31/33] Updated the "all-in-one" PDF reference. --- tests/reference/test_v5-schematic_(no_L).pdf | Bin 101215 -> 101742 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/reference/test_v5-schematic_(no_L).pdf b/tests/reference/test_v5-schematic_(no_L).pdf index 3c06724206e24a9522f68745b3fce84736906910..79843505afd190a758071302a0c9e9f9f5b16a85 100644 GIT binary patch delta 16079 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 MkZEY7Rb-I=9}vh*K>z>% delta 15536 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 Pa&vJYQ&EX4N+AC~*SZH= From 220e2f252e90cee00d2c5be73af9a6d7150e09eb Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Wed, 2 Sep 2020 10:04:32 -0300 Subject: [PATCH 32/33] Added tests for various schematic strange cases. - Component withour lib - Missing component - Poligon without points - Pin with unknown direction --- tests/board_samples/l1.lib | 19 +++++++++ tests/board_samples/missing.sch | 66 +++++++++++++++++++++++++++++++ tests/board_samples/test_v5.sch | 2 +- tests/test_plot/test_print_sch.py | 36 ++++++++++++++++- tests/utils/context.py | 20 +++++++--- 5 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 tests/board_samples/missing.sch diff --git a/tests/board_samples/l1.lib b/tests/board_samples/l1.lib index 89cb0f58..8804f064 100644 --- a/tests/board_samples/l1.lib +++ b/tests/board_samples/l1.lib @@ -9,6 +9,7 @@ F1 "R" 0 0 50 V V C CNN F2 "" -70 0 50 V I C CNN F3 "" 0 0 50 H I C CNN F4 "Hi!" 0 0 50 H I C CNN "Test" +ALIAS Resistor $FPLIST R_* R_* @@ -37,4 +38,22 @@ P 5 0 0 0 -50 -75 75 -75 0 100 -75 -75 -50 -75 f ENDDRAW ENDDEF # +# C +# +DEF C C 0 10 N Y 1 F N +F0 "C" 25 100 50 H V L CNN +F1 "C" 25 -100 50 H V L CNN +F2 "" 38 -150 50 H I C CNN +F3 "" 0 0 50 H I C CNN +$FPLIST + C_* +$ENDFPLIST +DRAW +P 2 0 1 20 -80 -30 80 -30 N +P 2 0 1 20 -80 30 80 30 N +X ~ 1 0 150 110 D 50 50 1 1 P +X ~ 2 0 -150 110 U 50 50 1 1 P +ENDDRAW +ENDDEF +# #End Library diff --git a/tests/board_samples/missing.sch b/tests/board_samples/missing.sch new file mode 100644 index 00000000..4969f617 --- /dev/null +++ b/tests/board_samples/missing.sch @@ -0,0 +1,66 @@ +EESchema Schematic File Version 4 +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 1 1 +Title "KiBom Test Schematic" +Date "2020-03-12" +Rev "A" +Comp "https://github.com/SchrodingersGat/KiBom" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +Text Notes 550 1050 0 118 ~ 0 +This schematic serves as a test file for the KiBot export script.\nHere we have a component without lib (from old KiCad?) \nand another that isn't in any lib. +$Comp +L l1:C C1 +U 1 1 5F43BEC2 +P 1000 1700 +F 0 "C1" H 1115 1746 50 0000 L CNN +F 1 "1nF" H 1115 1655 50 0000 L CNN +F 2 "Capacitor_SMD:C_0805_2012Metric" H 1038 1550 50 0001 C CNN +F 3 "~" H 1000 1700 50 0001 C CNN +F 4 "T2" H 1000 1700 50 0001 C CNN "Config" + 1 1000 1700 + 1 0 0 -1 +$EndComp +$Comp +L l1:C C2 +U 1 1 5F43CE1C +P 1450 1700 +F 0 "C2" H 1565 1746 50 0000 L CNN +F 1 "1000 pF" H 1565 1655 50 0000 L CNN +F 2 "Capacitor_SMD:C_0805_2012Metric" H 1488 1550 50 0001 C CNN +F 3 "~" H 1450 1700 50 0001 C CNN +F 4 "T3" H 1450 1700 50 0001 C CNN "Config" + 1 1450 1700 + 1 0 0 -1 +$EndComp +$Comp +L Resistor R1 +U 1 1 5F43D144 +P 2100 1700 +F 0 "R1" H 2170 1746 50 0000 L CNN +F 1 "1k" H 2170 1655 50 0000 L CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2030 1700 50 0001 C CNN +F 3 "~" H 2100 1700 50 0001 C CNN +F 4 "default" H 2100 1700 50 0001 C CNN "Config" + 1 2100 1700 + 1 0 0 -1 +$EndComp +$Comp +L l1:FooBar R2 +U 1 1 5F43D4BB +P 2500 1700 +F 0 "R2" H 2570 1746 50 0000 L CNN +F 1 "1000" H 2570 1655 50 0000 L CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2430 1700 50 0001 C CNN +F 3 "~" H 2500 1700 50 0001 C CNN +F 4 "T1" H 2500 1700 50 0001 C CNN "Config" + 1 2500 1700 + 1 0 0 -1 +$EndComp +$EndSCHEMATC diff --git a/tests/board_samples/test_v5.sch b/tests/board_samples/test_v5.sch index a511eda4..d1ab1007 100644 --- a/tests/board_samples/test_v5.sch +++ b/tests/board_samples/test_v5.sch @@ -1908,7 +1908,7 @@ B8 00 A2 1F 41 E0 C7 2A 65 BE 7E E1 F5 E7 46 4B 3D 22 C6 18 DB CE 36 66 F9 2E 00 EndData $EndBitmap $Comp -L l1:R R1 +L l1:Resistor R1 U 1 1 5F33EC02 P 1300 3450 F 0 "R1" V 1093 3450 50 0000 C CNN diff --git a/tests/test_plot/test_print_sch.py b/tests/test_plot/test_print_sch.py index 41a78e1d..dc6a1042 100644 --- a/tests/test_plot/test_print_sch.py +++ b/tests/test_plot/test_print_sch.py @@ -12,12 +12,13 @@ pytest-3 --log-cli-level debug import os import sys import logging +import coverage # Look for the 'utils' module from where the script is running prev_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if prev_dir not in sys.path: sys.path.insert(0, prev_dir) from kibot.misc import (PDF_SCH_PRINT, SVG_SCH_PRINT) -from kibot.kicad.v5_sch import Schematic, SchFileError +from kibot.kicad.v5_sch import Schematic, SchFileError, DrawPoligon, Pin # Utils import from utils import context @@ -25,6 +26,7 @@ PDF_DIR = '' PDF_FILE = 'Schematic.pdf' SVG_FILE = 'Schematic.svg' NI_DIR = 'no_inductor' +cov = coverage.Coverage() def test_print_sch_ok(): @@ -115,3 +117,35 @@ def test_print_sch_variant_ni_2(): ctx.expect_out_file(o_name) ctx.compare_pdf(o_name, r_name) ctx.clean_up() + + +def test_sch_missing(): + """ R1 exists in l1.lib, but the lib isn't specified. + R2 is bogus, completely missing """ + prj = 'missing' + ctx = context.TestContextSCH('test_sch_missing', prj, 'sch_no_inductors_1', PDF_DIR) + ctx.run() + o_name = os.path.join(NI_DIR, prj+'.sch') + ctx.expect_out_file(o_name) + ctx.search_err("Component .?Resistor.? doesn't specify its library") + ctx.search_err("Missing component .?l1:FooBar.?") + ctx.search_err("Missing component(.*)Resistor", invert=True) + ctx.clean_up() + + +def test_sch_bizarre_cases(): + """ Poligon without points. + Pin with unknown direction. """ + pol = DrawPoligon() + pol.points = 0 + pol.coords = [] + pin = Pin() + pin.dir = 'bogus' + cov.load() + cov.start() + x1, y1, x2, y2, ok_pol = pol.get_rect() + x1, y1, x2, y2, ok_pin = pin.get_rect() + cov.stop() + cov.save() + assert ok_pol is False + assert ok_pin is False diff --git a/tests/utils/context.py b/tests/utils/context.py index 8bfffc8d..75adc322 100644 --- a/tests/utils/context.py +++ b/tests/utils/context.py @@ -263,18 +263,26 @@ class TestContext(object): logging.debug('output match: `{}` OK'.format(text)) return m - def search_err(self, text): + def search_err(self, text, invert=False): if isinstance(text, list): res = [] for t in text: m = re.search(t, self.err, re.MULTILINE) - assert m is not None, t - logging.debug('error match: `{}` (`{}`) OK'.format(t, m.group(0))) - res.append(m) + if invert: + assert m is None, t + logging.debug('error no match: `{}` OK'.format(t)) + else: + assert m is not None, t + logging.debug('error match: `{}` (`{}`) OK'.format(t, m.group(0))) + res.append(m) return res m = re.search(text, self.err, re.MULTILINE) - assert m is not None - logging.debug('error match: `{}` (`{}`) OK'.format(text, m.group(0))) + if invert: + assert m is None, text + logging.debug('error no match: `{}` OK'.format(text)) + else: + assert m is not None, text + logging.debug('error match: `{}` (`{}`) OK'.format(text, m.group(0))) return m def search_in_file(self, file, texts): From 6fa903ed8f3e59ade4ca4131171a795b7756aab1 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Wed, 2 Sep 2020 10:30:40 -0300 Subject: [PATCH 33/33] Added the schematic print variants support to the changelog. --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b87b838f..f5a5c7ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - Now variants are separated entities. - - Only the internal BoM currently supports it. + - Only the internal BoM and Schematic print currently supports it. - In the future IBoM will also support it, contact me if you think this is high priority. - New filters entities. They implement all the functionality in KiBoM and IBoM. @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Marking components as "Do Not Fit" - Marking components as "Do Not Change" - The internal BoM format supports KiBoM and IBoM style variants +- Schematic print to PDF supports variants. Not fitted components are crossed. ## [0.6.2] - 2020-08-25 ### Changed