From 15474ae4d73af98e8da95d385c206235993c8922 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Thu, 21 Jan 2021 14:43:47 -0300 Subject: [PATCH] Now you can consolidate more than one project in one BoM. The basic idea comes from pimpmykicadbom by Anton Savov (@antto) --- CHANGELOG.md | 6 +- README.md | 12 ++- docs/README.in | 4 +- docs/samples/generic_plot.kibot.yaml | 14 +++ kibot/bom/bom.py | 74 +++++++++++++-- kibot/bom/columnlist.py | 6 +- kibot/bom/csv_writer.py | 71 +++++++++++---- kibot/bom/html_writer.py | 94 +++++++++++++++---- kibot/bom/xlsx_writer.py | 109 +++++++++++++++++------ kibot/bom/xml_writer.py | 24 +++-- kibot/gs.py | 37 ++------ kibot/kicad/v5_sch.py | 38 +++++--- kibot/kiplot.py | 26 +++--- kibot/out_bom.py | 84 +++++++++++++++-- kibot/pre_erc.py | 5 +- tests/board_samples/kicad_5/RLC_sort.sch | 4 +- tests/test_plot/test_preflight.py | 4 +- tests/test_plot/test_print_sch.py | 2 +- 18 files changed, 472 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb653053..e2c571ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - The multipart id to references of multipart components others than part 1. -- Internal BoM: `no_conflict` option to exclude fields from conflict detection. -- Internal BoM: HTML tables can be sorted selecting a column (Java Script). +- Internal BoM: + - `no_conflict` option to exclude fields from conflict detection. + - HTML tables can be sorted selecting a column (Java Script). + - You can consolidate more than one project in one BoM. - Support for KICAD_CONFIG_HOME defined from inside KiCad. - Now layers can be selected using the default KiCad names. - More control over the name of the drill and gerber files. diff --git a/README.md b/README.md index 13f6b46d..0a96062f 100644 --- a/README.md +++ b/README.md @@ -570,6 +570,12 @@ 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 `bom` output. * Valid keys: + - `aggregate`: [list(dict)] Add components from other projects. + * Valid keys: + - `file`: [string=''] Name of the schematic to aggregate. + - `name`: [string=''] Name to identify this source. If empty we use the name of the schematic. + - `number`: [number=1] Number of boards to build (components multiplier). Use negative to substract. + - `ref_id`: [string=''] A prefix to add to all the references from this project. - `columns`: [list(dict)|list(string)] List of columns to display. Can be just the name of the field. * Valid keys: @@ -628,7 +634,9 @@ Next time you need this list just use an alias, like this: - `normalize_values`: [boolean=false] Try to normalize the R, L and C values, producing uniform units and prefixes. - `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. + - `ref_id`: [string=''] A prefix to add to all the references from this project. Used for multiple projects. - `ref_separator`: [string=' '] Separator used for the list of references. + - `source_by_id`: [boolean=false] Generate the `Source BoM` column using the reference ID instead of the project name. - `use_alt`: [boolean=false] Print grouped references in the alternate compressed style eg: R1-R7,R18. - `variant`: [string=''] Board variant, used to determine which components are output to the BoM.. @@ -1846,9 +1854,11 @@ The internal list of rotations is: - **KiBoM**: Oliver Henry Walters (@SchrodingersGat) - **Interactive HTML BoM**: @qu1ck - **PcbDraw**: Jan Mrázek (@yaqwsx) -- **KiCad Gerber Zipper**: @g200kg - **Contributors**: - **Error filters ideas**: Leandro Heck (@leoheck) - **GitHub Actions Integration/SVG output**: @nerdyscout +- **Sources of inspiration and good ideas**: + - **KiCad Gerber Zipper**: @g200kg + - **pimpmykicadbom **: Anton Savov (@antto) - **Others**: - **Robot in the logo**: Christian Plaza (from pixabay) diff --git a/docs/README.in b/docs/README.in index adac2e41..8f77435d 100644 --- a/docs/README.in +++ b/docs/README.in @@ -942,9 +942,11 @@ The internal list of rotations is: - **KiBoM**: Oliver Henry Walters (@SchrodingersGat) - **Interactive HTML BoM**: @qu1ck - **PcbDraw**: Jan Mrázek (@yaqwsx) -- **KiCad Gerber Zipper**: @g200kg - **Contributors**: - **Error filters ideas**: Leandro Heck (@leoheck) - **GitHub Actions Integration/SVG output**: @nerdyscout +- **Sources of inspiration and good ideas**: + - **KiCad Gerber Zipper**: @g200kg + - **pimpmykicadbom **: Anton Savov (@antto) - **Others**: - **Robot in the logo**: Christian Plaza (from pixabay) diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index 4b37ed9b..f069dfe3 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -39,6 +39,16 @@ outputs: type: 'bom' dir: 'Example/bom_dir' options: + # [list(dict)] Add components from other projects + aggregate: + # [string=''] Name of the schematic to aggregate + - file: '' + # [string=''] Name to identify this source. If empty we use the name of the schematic + name: '' + # [number=1] Number of boards to build (components multiplier). Use negative to substract + number: 1 + # [string=''] A prefix to add to all the references from this project + ref_id: '' # [list(dict)|list(string)] List of columns to display. # Can be just the name of the field columns: @@ -129,8 +139,12 @@ outputs: number: 1 # [string='%f-%i%v.%x'] filename for the output (%i=bom). Affected by global options output: '%f-%i%v.%x' + # [string=''] A prefix to add to all the references from this project. Used for multiple projects + ref_id: '' # [string=' '] Separator used for the list of references ref_separator: ' ' + # [boolean=false] Generate the `Source BoM` column using the reference ID instead of the project name + source_by_id: false # [boolean=false] Print grouped references in the alternate compressed style eg: R1-R7,R18 use_alt: false # [string=''] Board variant, used to determine which components diff --git a/kibot/bom/bom.py b/kibot/bom/bom.py index c7ff1112..09faafa8 100644 --- a/kibot/bom/bom.py +++ b/kibot/bom/bom.py @@ -166,8 +166,38 @@ class ComponentGroup(object): self.components.append(c) self.refs[c.ref] = c - def get_count(self): - return len(self.components) + def get_count(self, project=None): + if project is None: + # Total components + return len(self.components) + # Only for the specified project + return sum(map(lambda c: c.project == project, self.components)) + + def get_build_count(self): + if not self.is_fitted(): + # Not fitted -> 0 + return 0 + if len(self.cfg.aggregate) == 1: + # Just one project + return len(self.components)*self.cfg.number + # Multiple projects, count them using the number of board for each project + return sum(map(lambda c: self.cfg.qtys[c.project], self.components)) + + def get_sources(self): + sources = {} + for c in self.components: + if c.project in sources: + sources[c.project] += 1 + else: + sources[c.project] = 1 + field = '' + for prj, n in sources.items(): + if len(field): + field += ' ' + if self.cfg.source_by_id: + prj = self.cfg.source_to_id[prj] + field += prj+'('+str(n)+')' + return field def is_fitted(self): # compare_components ensures all has the same status @@ -196,7 +226,7 @@ class ComponentGroup(object): """ Alternative list of references using ranges """ S = Joiner() for n in self.components: - P, N = (n.ref_prefix, _suffix_to_num(n.ref_suffix)) + P, N = (n.ref_id+n.ref_prefix, _suffix_to_num(n.ref_suffix)) S.add(P, N) return S.flush(self.cfg.ref_separator) @@ -232,9 +262,10 @@ class ComponentGroup(object): else: self.fields[ColumnList.COL_REFERENCE_L] = self.get_refs() # Quantity - q = self.get_count() - self.fields[ColumnList.COL_GRP_QUANTITY_L] = str(q) - self.fields[ColumnList.COL_GRP_BUILD_QUANTITY_L] = str(q * self.cfg.number) if self.is_fitted() else "0" + self.fields[ColumnList.COL_GRP_QUANTITY_L] = str(self.get_count()) + self.total = self.get_build_count() + self.fields[ColumnList.COL_GRP_BUILD_QUANTITY_L] = str(self.total) + self.fields[ColumnList.COL_SOURCE_BOM_L] = self.get_sources() # Group status status = ' ' if not self.is_fitted(): @@ -301,6 +332,25 @@ def normalize_value(c, decimal_point): return '{} {}{}'.format(value, mult_s, unit) +def compute_multiple_stats(cfg, groups): + for sch in cfg.aggregate: + sch.comp_total = 0 + sch.comp_fitted = 0 + sch.comp_build = 0 + sch.comp_groups = 0 + for g in groups: + g_l = g.get_count(sch.name) + if g_l: + sch.comp_groups = sch.comp_groups+1 + sch.comp_total += g_l + if g.is_fitted(): + sch.comp_fitted += g_l + sch.comp_build = sch.comp_fitted*sch.number + if cfg.debug_level > 1: + logger.debug('Stats for {}: total {} fitted {} build {}'. + format(sch.name, sch.comp_total, sch.comp_fitted, sch.comp_build)) + + def group_components(cfg, components): groups = [] # Iterate through each component, and test whether a group for these already exists @@ -343,6 +393,7 @@ def group_components(cfg, components): # Enumerate the groups and compute stats n_total = 0 n_fitted = 0 + n_build = 0 c = 1 dnf = 1 cfg.n_groups = len(groups) @@ -359,9 +410,15 @@ def group_components(cfg, components): n_total += g_l if is_fitted: n_fitted += g_l + n_build += g.total cfg.n_total = n_total cfg.n_fitted = n_fitted - cfg.n_build = n_fitted * cfg.number + cfg.n_build = n_build + if cfg.debug_level > 1: + logger.debug('Global stats: total {} fitted {} build {}'.format(n_total, n_fitted, n_build)) + # Compute stats for multiple schematics + if len(cfg.aggregate) > 1: + compute_multiple_stats(cfg, groups) return groups @@ -370,4 +427,7 @@ def do_bom(file_name, ext, comps, cfg): groups = group_components(cfg, comps) # Create the BoM logger.debug("Saving BOM File: "+file_name) + number = cfg.number + cfg.number = sum(map(lambda prj: prj.number, cfg.aggregate)) write_bom(file_name, ext, groups, cfg.columns, cfg) + cfg.number = number diff --git a/kibot/bom/columnlist.py b/kibot/bom/columnlist.py index 73d71a01..06af7959 100644 --- a/kibot/bom/columnlist.py +++ b/kibot/bom/columnlist.py @@ -49,6 +49,8 @@ class ColumnList: COL_GRP_QUANTITY_L = COL_GRP_QUANTITY.lower() COL_GRP_BUILD_QUANTITY = 'Build Quantity' COL_GRP_BUILD_QUANTITY_L = COL_GRP_BUILD_QUANTITY.lower() + COL_SOURCE_BOM = 'Source BoM' + COL_SOURCE_BOM_L = COL_SOURCE_BOM.lower() # Generated columns COLUMNS_GEN_L = { @@ -56,6 +58,7 @@ class ColumnList: COL_GRP_BUILD_QUANTITY_L: 1, COL_ROW_NUMBER_L: 1, COL_STATUS_L: 1, + COL_SOURCE_BOM_L: 1, } # Default columns @@ -72,7 +75,8 @@ class ColumnList: COL_GRP_BUILD_QUANTITY, COL_STATUS, COL_DATASHEET, - COL_SHEETPATH + COL_SHEETPATH, + COL_SOURCE_BOM, ] # Default columns diff --git a/kibot/bom/csv_writer.py b/kibot/bom/csv_writer.py index dca72765..e1976b12 100644 --- a/kibot/bom/csv_writer.py +++ b/kibot/bom/csv_writer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020 Salvador E. Tropea -# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial +# Copyright (c) 2020-2021 Salvador E. Tropea +# Copyright (c) 2020-2021 Instituto Nacional de Tecnología Industrial # Copyright (c) 2016-2020 Oliver Henry Walters (@SchrodingersGat) # License: MIT # Project: KiBot (formerly KiPlot) @@ -11,6 +11,58 @@ CSV Writer: Generates a CSV, TSV or TXT BoM file. import csv +def write_stats(writer, cfg): + if len(cfg.aggregate) == 1: + # Only one project + if not cfg.csv.hide_pcb_info: + prj = cfg.aggregate[0] + writer.writerow(["Project info:"]) + writer.writerow(["Schematic:", prj.name]) + writer.writerow(["Variant:", cfg.variant.name]) + writer.writerow(["Revision:", prj.sch.revision]) + writer.writerow(["Date:", prj.sch.date]) + writer.writerow(["KiCad Version:", cfg.kicad_version]) + if not cfg.csv.hide_stats_info: + writer.writerow(["Statistics:"]) + writer.writerow(["Component Groups:", cfg.n_groups]) + writer.writerow(["Component Count:", cfg.n_total]) + writer.writerow(["Fitted Components:", cfg.n_fitted]) + writer.writerow(["Number of PCBs:", cfg.number]) + writer.writerow(["Total Components:", cfg.n_build]) + else: + # Multiple projects + if not cfg.csv.hide_pcb_info: + prj = cfg.aggregate[0] + writer.writerow(["Project info:"]) + writer.writerow(["Variant:", cfg.variant.name]) + writer.writerow(["KiCad Version:", cfg.kicad_version]) + if not cfg.csv.hide_stats_info: + writer.writerow(["Global statistics:"]) + writer.writerow(["Component Groups:", cfg.n_groups]) + writer.writerow(["Component Count:", cfg.n_total]) + writer.writerow(["Fitted Components:", cfg.n_fitted]) + writer.writerow(["Number of PCBs:", cfg.number]) + writer.writerow(["Total Components:", cfg.n_build]) + # Individual stats + for prj in cfg.aggregate: + if not cfg.csv.hide_pcb_info: + writer.writerow(["Project info:", prj.sch.title]) + writer.writerow(["Schematic:", prj.name]) + writer.writerow(["Revision:", prj.sch.revision]) + writer.writerow(["Date:", prj.sch.date]) + if prj.sch.company: + writer.writerow(["Company:", prj.sch.company]) + if prj.ref_id: + writer.writerow(["ID", prj.ref_id]) + if not cfg.csv.hide_stats_info: + writer.writerow(["Statistics:", prj.sch.title]) + writer.writerow(["Component Groups:", prj.comp_groups]) + writer.writerow(["Component Count:", prj.comp_total]) + writer.writerow(["Fitted Components:", prj.comp_fitted]) + writer.writerow(["Number of PCBs:", prj.number]) + writer.writerow(["Total Components:", prj.comp_build]) + + def write_csv(filename, ext, groups, headings, head_names, cfg): """ Write BoM out to a CSV file @@ -50,19 +102,6 @@ def write_csv(filename, ext, groups, headings, head_names, cfg): for i in range(5): writer.writerow([]) # The info - if not cfg.csv.hide_pcb_info: - writer.writerow(["Project info:"]) - writer.writerow(["Schematic:", cfg.source]) - writer.writerow(["Variant:", cfg.variant.name]) - writer.writerow(["Revision:", cfg.revision]) - writer.writerow(["Date:", cfg.date]) - writer.writerow(["KiCad Version:", cfg.kicad_version]) - if not cfg.csv.hide_stats_info: - writer.writerow(["Statistics:"]) - writer.writerow(["Component Groups:", cfg.n_groups]) - writer.writerow(["Component Count:", cfg.n_total]) - writer.writerow(["Fitted Components:", cfg.n_fitted]) - writer.writerow(["Number of PCBs:", cfg.number]) - writer.writerow(["Total Components:", cfg.n_build]) + write_stats(writer, cfg) return True diff --git a/kibot/bom/html_writer.py b/kibot/bom/html_writer.py index 6bfe10f2..2a9857c5 100644 --- a/kibot/bom/html_writer.py +++ b/kibot/bom/html_writer.py @@ -32,6 +32,7 @@ STYLE_COMMON = (" .cell-title { vertical-align: bottom; }\n" " .cell-info { vertical-align: top; padding: 1em;}\n" " .cell-stats { vertical-align: top; padding: 1em;}\n" " .title { font-size:2.5em; font-weight: bold; }\n" + " .subtitle { font-size:1.5em; font-weight: bold; }\n" " .h2 { font-size:1.5em; font-weight: bold; }\n" " .td-empty0 { text-align: center; background-color: "+BG_EMPTY+";}\n" " .td-gen0 { text-align: center; background-color: "+BG_GEN+";}\n" @@ -274,6 +275,75 @@ def embed_image(file): return int(w), int(h), 'data:image/png;base64,'+b64encode(s).decode('ascii') +def write_stats(html, cfg): + if len(cfg.aggregate) == 1: + # Only one project + html.write('\n') + html.write(' \n') + if not cfg.html.hide_pcb_info: + prj = cfg.aggregate[0] + html.write(" Schematic: {}
\n".format(prj.name)) + html.write(" Variant: {}
\n".format(cfg.variant.name)) + html.write(" Revision: {}
\n".format(prj.sch.revision)) + html.write(" Date: {}
\n".format(prj.sch.date)) + html.write(" KiCad Version: {}
\n".format(cfg.kicad_version)) + html.write(' \n') + html.write(' \n') + if not cfg.html.hide_stats_info: + html.write(" Component Groups: {}
\n".format(cfg.n_groups)) + html.write(" Component Count: {} (per PCB)
\n\n".format(cfg.n_total)) + html.write(" Fitted Components: {} (per PCB)
\n".format(cfg.n_fitted)) + html.write(" Number of PCBs: {}
\n".format(cfg.number)) + html.write(" Total Components: {t} (for {n} PCBs)
\n".format(n=cfg.number, t=cfg.n_build)) + html.write(' \n') + html.write('\n') + else: + # Multiple projects + # Global stats + html.write('\n') + html.write(' \n') + if not cfg.html.hide_pcb_info: + html.write(" Variant: {}
\n".format(cfg.variant.name)) + html.write(" KiCad Version: {}
\n".format(cfg.kicad_version)) + html.write(' \n') + html.write(' \n') + if not cfg.html.hide_stats_info: + html.write(" Component Groups: {}
\n".format(cfg.n_groups)) + html.write(" Component Count: {} (per PCB)
\n\n".format(cfg.n_total)) + html.write(" Fitted Components: {} (per PCB)
\n".format(cfg.n_fitted)) + html.write(" Number of PCBs: {}
\n".format(cfg.number)) + html.write(" Total Components: {t} (for {n} PCBs)
\n".format(n=cfg.number, t=cfg.n_build)) + html.write(' \n') + html.write('\n') + # Individual stats + for prj in cfg.aggregate: + html.write('\n') + html.write(' \n') + html.write('
'+prj.sch.title+'
\n') + html.write(' \n') + html.write('\n') + html.write('\n') + html.write(' \n') + if not cfg.html.hide_pcb_info: + html.write(" Schematic: {}
\n".format(prj.name)) + html.write(" Revision: {}
\n".format(prj.sch.revision)) + html.write(" Date: {}
\n".format(prj.sch.date)) + if prj.sch.company: + html.write(" Company: {}
\n".format(prj.sch.company)) + if prj.ref_id: + html.write(" ID: {}
\n".format(prj.ref_id)) + html.write(' \n') + html.write(' \n') + if not cfg.html.hide_stats_info: + html.write(" Component Groups: {}
\n".format(prj.comp_groups)) + html.write(" Component Count: {} (per PCB)
\n\n".format(prj.comp_total)) + html.write(" Fitted Components: {} (per PCB)
\n".format(prj.comp_fitted)) + html.write(" Number of PCBs: {}
\n".format(prj.number)) + html.write(" Total Components: {t} (for {n} PCBs)
\n".format(n=prj.number, t=prj.comp_build)) + html.write(' \n') + html.write('\n') + + def write_html(filename, groups, headings, head_names, cfg): """ Write BoM out to a HTML file @@ -340,7 +410,10 @@ def write_html(filename, groups, headings, head_names, cfg): if img or not cfg.html.hide_pcb_info or not cfg.html.hide_stats_info or cfg.html.title: html.write('\n') html.write('\n') - html.write(' \n') @@ -349,24 +422,7 @@ def write_html(filename, groups, headings, head_names, cfg): html.write('
'+cfg.html.title+'
\n') html.write(' \n') html.write('\n') - html.write('\n') - html.write(' \n') - html.write(' \n') - html.write('\n') + write_stats(html, cfg) html.write('
\n') + n = 2 + if len(cfg.aggregate) > 1: + n += 2*len(cfg.aggregate) + html.write(' \n'.format(n)) if img: html.write(' Logo\n') html.write('
\n') - if not cfg.html.hide_pcb_info: - html.write(" Schematic: {}
\n".format(cfg.source)) - html.write(" Variant: {}
\n".format(cfg.variant.name)) - html.write(" Revision: {}
\n".format(cfg.revision)) - html.write(" Date: {}
\n".format(cfg.date)) - html.write(" KiCad Version: {}
\n".format(cfg.kicad_version)) - html.write('
\n') - if not cfg.html.hide_stats_info: - html.write(" Component Groups: {}
\n".format(cfg.n_groups)) - html.write(" Component Count: {} (per PCB)
\n\n".format(cfg.n_total)) - html.write(" Fitted Components: {} (per PCB)
\n".format(cfg.n_fitted)) - html.write(" Number of PCBs: {}
\n".format(cfg.number)) - html.write(" Total Components: {t} (for {n} PCBs)
\n".format(n=cfg.number, t=cfg.n_build)) - html.write('
\n') # Fitted groups diff --git a/kibot/bom/xlsx_writer.py b/kibot/bom/xlsx_writer.py index 5874f9f1..04708c1f 100644 --- a/kibot/bom/xlsx_writer.py +++ b/kibot/bom/xlsx_writer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020 Salvador E. Tropea -# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial +# Copyright (c) 2020-2021 Salvador E. Tropea +# Copyright (c) 2020-2021 Instituto Nacional de Tecnología Industrial # Copyright (c) 2016-2020 Oliver Henry Walters (@SchrodingersGat) # License: MIT # Project: KiBot (formerly KiPlot) @@ -67,14 +67,16 @@ def add_info(worksheet, column_widths, row, col_offset, formats, text, value): def compute_head_size(cfg): - head_size = 7 - if cfg.xlsx.logo is None: - if not cfg.xlsx.title: - head_size -= 1 - if cfg.xlsx.hide_pcb_info and cfg.xlsx.hide_stats_info: - head_size -= 5 - if head_size == 1: - head_size = 0 + col_logo = 0 if cfg.xlsx.logo is None else 6 + col_info = 1 if cfg.xlsx.title else 0 + if not (cfg.xlsx.hide_pcb_info and cfg.xlsx.hide_stats_info): + col_info += 5 + if len(cfg.aggregate) > 1: + col_info += 6*len(cfg.aggregate) + head_size = max(col_logo, col_info) + if head_size: + # To separate + head_size += 1 return head_size @@ -116,6 +118,15 @@ def create_fmt_title(workbook, title): return fmt_title +def create_fmt_subtitle(workbook): + fmt_title = workbook.add_format(DEFAULT_FMT) + fmt_title.set_font_size(18) + fmt_title.set_bold() + fmt_title.set_font_name('Arial') + fmt_title.set_align('left') + return fmt_title + + def create_fmt_cols(workbook, col_colors): """ Create the possible column formats """ fmt_cols = [] @@ -202,6 +213,67 @@ def adjust_heights(worksheet, rows, max_width, head_size): worksheet.set_row(head_size+rn, 15.0*max_h) +def write_info(cfg, r_info_start, worksheet, column_widths, col1, fmt_info, fmt_subtitle): + if len(cfg.aggregate) == 1: + # Only one project + rc = r_info_start + if not cfg.xlsx.hide_pcb_info: + prj = cfg.aggregate[0] + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Schematic:", prj.name) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Variant:", cfg.variant.name) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Revision:", prj.sch.revision) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Date:", prj.sch.date) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "KiCad Version:", cfg.kicad_version) + col1 += 2 + rc = r_info_start + if not cfg.xlsx.hide_stats_info: + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Component Groups:", cfg.n_groups) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Component Count:", cfg.n_total) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Fitted Components:", cfg.n_fitted) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Number of PCBs:", cfg.number) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Total Components:", cfg.n_build) + else: + # Multiple projects + # Global stats + old_col1 = col1 + rc = r_info_start + if not cfg.xlsx.hide_pcb_info: + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Variant:", cfg.variant.name) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "KiCad Version:", cfg.kicad_version) + col1 += 2 + rc = r_info_start + if not cfg.xlsx.hide_stats_info: + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Component Groups:", cfg.n_groups) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Component Count:", cfg.n_total) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Fitted Components:", cfg.n_fitted) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Number of PCBs:", cfg.number) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Total Components:", cfg.n_build) + # Individual stats + for prj in cfg.aggregate: + r_info_start += 5 + col1 = old_col1 + worksheet.set_row(r_info_start, 24) + worksheet.merge_range(r_info_start, col1, r_info_start, len(column_widths)-1, prj.sch.title, fmt_subtitle) + r_info_start += 1 + rc = r_info_start + if not cfg.xlsx.hide_pcb_info: + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Schematic:", prj.name) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Revision:", prj.sch.revision) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Date:", prj.sch.date) + if prj.sch.company: + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Company:", prj.sch.company) + if prj.ref_id: + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "ID:", prj.ref_id) + col1 += 2 + rc = r_info_start + if not cfg.xlsx.hide_stats_info: + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Component Groups:", prj.comp_groups) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Component Count:", prj.comp_total) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Fitted Components:", prj.comp_fitted) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Number of PCBs:", prj.number) + rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Total Components:", prj.comp_build) + + def write_xlsx(filename, groups, col_fields, head_names, cfg): """ Write BoM out to a XLSX file @@ -245,6 +317,7 @@ def write_xlsx(filename, groups, col_fields, head_names, cfg): image_data = get_logo_data(cfg.xlsx.logo) # Title fmt_title = create_fmt_title(workbook, cfg.xlsx.title) + fmt_subtitle = create_fmt_subtitle(workbook) # Info fmt_info = create_fmt_info(workbook, cfg) @@ -310,21 +383,7 @@ def write_xlsx(filename, groups, col_fields, head_names, cfg): worksheet.merge_range(0, col1, 0, len(column_widths)-1, cfg.xlsx.title, fmt_title) # PCB & Stats Info if not (cfg.xlsx.hide_pcb_info and cfg.xlsx.hide_stats_info): - rc = r_info_start - if not cfg.xlsx.hide_pcb_info: - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Schematic:", cfg.source) - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Variant:", cfg.variant.name) - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Revision:", cfg.revision) - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Date:", cfg.date) - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "KiCad Version:", cfg.kicad_version) - col1 += 2 - rc = r_info_start - if not cfg.xlsx.hide_stats_info: - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Component Groups:", cfg.n_groups) - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Component Count:", cfg.n_total) - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Fitted Components:", cfg.n_fitted) - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Number of PCBs:", cfg.number) - rc = add_info(worksheet, column_widths, rc, col1, fmt_info, "Total Components:", cfg.n_build) + write_info(cfg, r_info_start, worksheet, column_widths, col1, fmt_info, fmt_subtitle) # Adjust cols and rows adjust_widths(worksheet, column_widths, max_width) diff --git a/kibot/bom/xml_writer.py b/kibot/bom/xml_writer.py index fa514896..869ad582 100644 --- a/kibot/bom/xml_writer.py +++ b/kibot/bom/xml_writer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020 Salvador E. Tropea -# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial +# Copyright (c) 2020-2021 Salvador E. Tropea +# Copyright (c) 2020-2021 Instituto Nacional de Tecnología Industrial # Copyright (c) 2016-2020 Oliver Henry Walters (@SchrodingersGat) # License: MIT # Project: KiBot (formerly KiPlot) @@ -23,9 +23,6 @@ def write_xml(filename, groups, headings, head_names, cfg): cfg = BoMOptions object with all the configuration """ attrib = {} - attrib['Schematic_Source'] = cfg.source - attrib['Schematic_Revision'] = cfg.revision - attrib['Schematic_Date'] = cfg.date attrib['PCB_Variant'] = cfg.variant.name attrib['KiCad_Version'] = cfg.kicad_version attrib['Component_Groups'] = str(cfg.n_groups) @@ -33,7 +30,22 @@ def write_xml(filename, groups, headings, head_names, cfg): attrib['Fitted_Components'] = str(cfg.n_fitted) attrib['Number_of_PCBs'] = str(cfg.number) attrib['Total_Components'] = str(cfg.n_build) - + if len(cfg.aggregate) == 1: + prj = cfg.aggregate[0] + attrib['Schematic_Source'] = prj.name + attrib['Schematic_Revision'] = prj.sch.revision + attrib['Schematic_Date'] = prj.sch.date + else: + for n, prj in enumerate(cfg.aggregate): + attrib['Schematic{}_Source'.format(n)] = prj.name + attrib['Schematic{}_Revision'.format(n)] = prj.sch.revision + attrib['Schematic{}_Date'.format(n)] = prj.sch.date + attrib['Schematic{}_ID'.format(n)] = prj.ref_id + attrib['Component_Groups{}'.format(n)] = str(prj.comp_groups) + attrib['Component_Count{}'.format(n)] = str(prj.comp_total) + attrib['Fitted_Components{}'.format(n)] = str(prj.comp_fitted) + attrib['Number_of_PCBs{}'.format(n)] = str(prj.number) + attrib['Total_Components{}'.format(n)] = str(prj.comp_build) xml = ElementTree.Element('KiCad_BOM', attrib=attrib, encoding='utf-8') for group in groups: if cfg.ignore_dnf and not group.is_fitted(): diff --git a/kibot/gs.py b/kibot/gs.py index 7c0c9f86..ab0783d6 100644 --- a/kibot/gs.py +++ b/kibot/gs.py @@ -4,7 +4,6 @@ # License: GPL-3.0 # Project: KiBot (formerly KiPlot) import os -import re from datetime import datetime from sys import exit from .misc import (EXIT_BAD_ARGS) @@ -94,37 +93,11 @@ class GS(object): def load_sch_title_block(): if GS.sch_title is not None: return - GS.sch_title = '' - GS.sch_date = '' - GS.sch_rev = '' - GS.sch_comp = '' - re_val = re.compile(r"(\w+)\s+\"([^\"]+)\"") - with open(GS.sch_file) as f: - for line in f: - m = re_val.match(line) - if not m: - if line.startswith("$EndDescr"): - break - # This line is executed, but coverage fails to detect it - continue # pragma: no cover - name, val = m.groups() - if name == "Title": - GS.sch_title = val - elif name == "Date": - GS.sch_date = val - elif name == "Rev": - GS.sch_rev = val - elif name == "Comp": - GS.sch_comp = val - if not GS.sch_date: - file_mtime = os.path.getmtime(GS.sch_file) - GS.sch_date = datetime.fromtimestamp(file_mtime).strftime('%Y-%m-%d_%H-%M-%S') - if not GS.sch_title: - GS.sch_title = GS.sch_basename - logger.debug("SCH title: `{}`".format(GS.sch_title)) - logger.debug("SCH date: `{}`".format(GS.sch_date)) - logger.debug("SCH revision: `{}`".format(GS.sch_rev)) - logger.debug("SCH company: `{}`".format(GS.sch_comp)) + assert GS.sch is not None + GS.sch_title = GS.sch.title + GS.sch_date = GS.sch.date + GS.sch_rev = GS.sch.revision + GS.sch_comp = GS.sch.company @staticmethod def load_pcb_title_block(): diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 7f3757e4..94ed6119 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -11,6 +11,7 @@ Currently oriented to collect the components for the BoM. # Encapsulate file/line import re import os +from datetime import datetime from copy import deepcopy from collections import OrderedDict from .config import KiConf, un_quote @@ -807,6 +808,10 @@ class SchematicComponent(object): self.footprint = '' self.datasheet = '' self.desc = '' + self.fields = [] + self.dfields = {} + self.fields_bkp = None + self.dfields_bkp = None # Will be computed self.fitted = True self.included = True @@ -941,7 +946,7 @@ class SchematicComponent(object): return '{} ({} {})'.format(ref, self.name, self.value) @staticmethod - def load(f, sheet_path, sheet_path_h, libs, fields, fields_lc): + def load(f, project, sheet_path, sheet_path_h, libs, fields, fields_lc): # L lib:name reference line = f.get_line() if not line or line[0] != 'L': @@ -950,6 +955,7 @@ class SchematicComponent(object): if len(res) != 2: raise SchFileError('Malformed component label', line, f) comp = SchematicComponent() + comp.project = project comp.name, comp.f_ref = res res = comp.name.split(':') comp.lib = None @@ -985,10 +991,6 @@ class SchematicComponent(object): comp.ar.append(SchematicAltRef.parse(line)) line = f.get_line() # F field_number "text" orientation posX posY size Flags (see below) hjustify vjustify/italic/bold "name" - comp.fields = [] - comp.dfields = {} - comp.fields_bkp = None - comp.dfields_bkp = None while line[0] == 'F': field = SchematicField.parse(line, f) name_lc = field.name.lower() @@ -1275,7 +1277,7 @@ class SchematicSheet(object): self.sheet = None self.id = '' - def load_sheet(self, parent, sheet_path, sheet_path_h, libs, fields, fields_lc): + def load_sheet(self, project, parent, sheet_path, sheet_path_h, libs, fields, fields_lc): assert self.name self.sheet = Schematic() parent_dir = os.path.dirname(parent) @@ -1283,7 +1285,7 @@ class SchematicSheet(object): if len(sheet_path_h) > 1: sheet_path_h += '/' sheet_path_h += self.name if self.name else 'Unknown' - self.sheet.load(os.path.join(parent_dir, self.file), sheet_path, sheet_path_h, libs, fields, fields_lc) + self.sheet.load(os.path.join(parent_dir, self.file), project, sheet_path, sheet_path_h, libs, fields, fields_lc) return self.sheet @staticmethod @@ -1366,6 +1368,10 @@ class Schematic(object): while True: line = f.get_line() if line.startswith('$EndDescr'): + self.title = self.title_block['Title'] if 'Title' in self.title_block else '' + self.date = self.title_block['Date'] if 'Date' in self.title_block else '' + self.revision = self.title_block['Rev'] if 'Rev' in self.title_block else '' + self.company = self.title_block['Comp'] if 'Comp' in self.title_block else '' return elif line.startswith('encoding'): if line[9:14] != 'utf-8': @@ -1382,7 +1388,7 @@ class Schematic(object): raise SchFileError('Wrong entry in title block', line, f) self.title_block[m.group(1)] = m.group(2) - def load(self, fname, sheet_path='', sheet_path_h='/', libs={}, fields=[], fields_lc=set()): + def load(self, fname, project, sheet_path='', sheet_path_h='/', libs={}, fields=[], fields_lc=set()): """ Load a v5.x KiCad Schematic. The caller must be sure the file exists. Only the schematics are loaded not the libs. """ @@ -1391,6 +1397,7 @@ class Schematic(object): self.libs = libs self.fields = fields self.fields_lc = fields_lc + self.project = project with open(fname, 'rt') as fh: f = SCHLineReader(fh, fname) line = f.get_line() @@ -1410,7 +1417,18 @@ class Schematic(object): line = f.get_line() if not line.startswith('EELAYER END'): raise SchFileError('Missing EELAYER END', line, f) + # Load the title block self._get_title_block(f) + # Fill in some missing info + if not self.date: + file_mtime = os.path.getmtime(fname) + self.date = datetime.fromtimestamp(file_mtime).strftime('%Y-%m-%d_%H-%M-%S') + if not self.title: + self.title = os.path.splitext(os.path.basename(fname))[0] + logger.debug("SCH title: `{}`".format(self.title)) + logger.debug("SCH date: `{}`".format(self.date)) + logger.debug("SCH revision: `{}`".format(self.revision)) + logger.debug("SCH company: `{}`".format(self.company)) line = f.get_line() self.all = [] self.components = [] @@ -1421,7 +1439,7 @@ class Schematic(object): self.sheets = [] while not line.startswith('$EndSCHEMATC'): if line.startswith('$Comp'): - obj = SchematicComponent.load(f, sheet_path, sheet_path_h, libs, fields, fields_lc) + obj = SchematicComponent.load(f, project, sheet_path, sheet_path_h, libs, fields, fields_lc) self.components.append(obj) elif line.startswith('NoConn'): obj = SchematicConnection.parse(False, line[7:], f) @@ -1448,7 +1466,7 @@ class Schematic(object): # Load sub-sheets self.sub_sheets = [] for sch in self.sheets: - self.sub_sheets.append(sch.load_sheet(fname, sheet_path, sheet_path_h, libs, fields, fields_lc)) + self.sub_sheets.append(sch.load_sheet(project, fname, sheet_path, sheet_path_h, libs, fields, fields_lc)) def get_files(self): """ A list of the names for all the sheets, including this one. """ diff --git a/kibot/kiplot.py b/kibot/kiplot.py index c487dac5..e6d654ac 100644 --- a/kibot/kiplot.py +++ b/kibot/kiplot.py @@ -145,19 +145,12 @@ def load_board(pcb_file=None): return board -def load_sch(): - if GS.sch: # Already loaded - return - GS.check_sch() - # We can't yet load the new format - if GS.sch_file[-9:] == 'kicad_sch': - return - GS.sch = Schematic() +def load_any_sch(sch, file, project): try: - GS.sch.load(GS.sch_file) - GS.sch.load_libs(GS.sch_file) + sch.load(file, project) + sch.load_libs(file) if GS.debug_level > 1: - logger.debug('Schematic dependencies: '+str(GS.sch.get_files())) + logger.debug('Schematic dependencies: '+str(sch.get_files())) except SchFileError as e: trace_dump() logger.error('At line {} of `{}`: {}'.format(e.line, e.file, e.msg)) @@ -170,6 +163,17 @@ def load_sch(): exit(EXIT_BAD_CONFIG) +def load_sch(): + if GS.sch: # Already loaded + return + GS.check_sch() + # We can't yet load the new format + if GS.sch_file[-9:] == 'kicad_sch': + return + GS.sch = Schematic() + load_any_sch(GS.sch, GS.sch_file, GS.sch_basename) + + def get_board_comps_data(comps): """ Add information from the PCB to the list of components from the schematic. Note that we do it every time the function is called to reset transformation filters like rot_footprint. """ diff --git a/kibot/out_bom.py b/kibot/out_bom.py index 58563183..84e6c670 100644 --- a/kibot/out_bom.py +++ b/kibot/out_bom.py @@ -12,12 +12,13 @@ from .gs import GS from .optionable import Optionable, BaseOptions from .registrable import RegOutput from .error import KiPlotConfigurationError -from .kiplot import get_board_comps_data -from .macros import macros, document, output_class # noqa: F401 +from .kiplot import get_board_comps_data, load_any_sch from .bom.columnlist import ColumnList, BoMError from .bom.bom import do_bom from .var_kibom import KiBoM +from .kicad.v5_sch import Schematic from .fil_base import BaseFilter, apply_exclude_filter, apply_fitted_filter, apply_fixed_filter, reset_filters +from .macros import macros, document, output_class # noqa: F401 from . import log # To debug the `with document` we can use: # from .mcpyrate.debug import macros, step_expansion @@ -186,6 +187,27 @@ class NoConflict(Optionable): super().__init__() +class Aggregate(Optionable): + def __init__(self): + super().__init__() + with document: + self.file = '' + """ Name of the schematic to aggregate """ + self.name = '' + """ Name to identify this source. If empty we use the name of the schematic """ + self.ref_id = '' + """ A prefix to add to all the references from this project """ + self.number = 1 + """ Number of boards to build (components multiplier). Use negative to substract """ + + def config(self): + super().config() + if not self.file: + raise KiPlotConfigurationError("Missing or empty `file` in aggregate list ({})".format(str(self._tree))) + if not self.name: + self.name = os.path.splitext(os.path.basename(self.file))[0] + + class BoMOptions(BaseOptions): def __init__(self): with document: @@ -256,6 +278,12 @@ class BoMOptions(BaseOptions): """ [list(string)] List of fields where we tolerate conflicts. Use it to avoid undesired warnings. By default the field indicated in `fit_field` and the field `part` are excluded """ + self.aggregate = Aggregate + """ [list(dict)] Add components from other projects """ + self.ref_id = '' + """ A prefix to add to all the references from this project. Used for multiple projects """ + self.source_by_id = False + """ Generate the `Source BoM` column using the reference ID instead of the project name """ self._format_example = 'CSV' super().__init__() @@ -341,6 +369,9 @@ class BoMOptions(BaseOptions): for field in self.no_conflict: no_conflict.add(field.lower()) self.no_conflict = no_conflict + # Make sure aggregate is a list + if isinstance(self.aggregate, type): + self.aggregate = [] # Columns self.column_rename = {} self.join = [] @@ -351,9 +382,11 @@ class BoMOptions(BaseOptions): # Ignore the part and footprint library, also sheetpath and the Reference in singular ignore = [ColumnList.COL_PART_LIB_L, ColumnList.COL_FP_LIB_L, ColumnList.COL_SHEETPATH_L, ColumnList.COL_REFERENCE_L[:-1]] - if self.number <= 1: - # For one board avoid COL_GRP_BUILD_QUANTITY - ignore.append(ColumnList.COL_GRP_BUILD_QUANTITY_L) + if len(self.aggregate) == 0: + ignore.append(ColumnList.COL_SOURCE_BOM_L) + if self.number == 1: + # For one board avoid COL_GRP_BUILD_QUANTITY + ignore.append(ColumnList.COL_GRP_BUILD_QUANTITY_L) # Exclude the particular columns self.columns = [h for h in valid_columns if not h.lower() in ignore] else: @@ -384,6 +417,24 @@ class BoMOptions(BaseOptions): # This is the ordered list with the case style defined by the user self.columns = columns + def aggregate_comps(self, comps): + self.qtys = {GS.sch_basename: self.number} + for prj in self.aggregate: + if not os.path.isfile(prj.file): + raise KiPlotConfigurationError("Missing `{}`".format(prj.file)) + logger.debug('Adding components from project {} ({}) using reference id `{}`'. + format(prj.name, prj.file, prj.ref_id)) + self.qtys[prj.name] = prj.number + prj.sch = Schematic() + load_any_sch(prj.sch, prj.file, prj.name) + new_comps = prj.sch.get_components() + if prj.ref_id: + for c in new_comps: + c.ref = prj.ref_id+c.ref + c.ref_id = prj.ref_id + comps.extend(new_comps) + prj.source = os.path.basename(prj.file) + def run(self, output_dir): format = self.format.lower() output = self.expand_filename_sch(output_dir, self.output, 'bom', format) @@ -397,6 +448,12 @@ class BoMOptions(BaseOptions): # Get the components list from the schematic comps = GS.sch.get_components() get_board_comps_data(comps) + # Apply the reference prefix + for c in comps: + c.ref = self.ref_id+c.ref + c.ref_id = self.ref_id + # Aggregate components from other projects + self.aggregate_comps(comps) # Apply all the filters reset_filters(comps) apply_exclude_filter(comps, self.exclude_filter) @@ -404,10 +461,27 @@ class BoMOptions(BaseOptions): apply_fixed_filter(comps, self.dnc_filter) # Apply the variant self.variant.filter(comps) + # We add the main project to the aggregate list so do_bom sees a complete list + base_sch = Aggregate() + base_sch.file = GS.sch_file + base_sch.name = GS.sch_basename + base_sch.ref_id = self.ref_id + base_sch.number = self.number + base_sch.sch = GS.sch + self.aggregate.insert(0, base_sch) + # To translate project to ID + if self.source_by_id: + self.source_to_id = {prj.name: prj.ref_id for prj in self.aggregate} try: do_bom(output, format, comps, self) except BoMError as e: raise KiPlotConfigurationError(str(e)) + # Undo the reference prefix + if self.ref_id: + l_id = len(self.ref_id) + for c in filter(lambda c: c.project == GS.sch_basename, comps): + c.ref = c.ref[l_id:] + c.ref_id = '' def get_targets(self, parent, out_dir): return [self.expand_filename_sch(out_dir, self.output, 'bom', self.format.lower())] diff --git a/kibot/pre_erc.py b/kibot/pre_erc.py index 6bf0aaae..a95f7cdf 100644 --- a/kibot/pre_erc.py +++ b/kibot/pre_erc.py @@ -7,7 +7,7 @@ from sys import (exit) from .macros import macros, pre_class # noqa: F401 from .gs import (GS) from .optionable import Optionable -from .kiplot import check_eeschema_do, exec_with_retry +from .kiplot import check_eeschema_do, exec_with_retry, load_sch from .error import (KiPlotConfigurationError) from .misc import (CMD_EESCHEMA_DO, ERC_ERROR) from .log import (get_logger) @@ -28,6 +28,9 @@ class Run_ERC(BasePreFlight): # noqa: F821 def run(self): check_eeschema_do() + # The schematic is loaded only before executing an output related to it. + # But here we need data from it. + load_sch() output = Optionable.expand_filename_sch(None, GS.out_dir, GS.def_global_output, 'erc', 'txt') logger.debug('ERC report: '+output) cmd = [CMD_EESCHEMA_DO, 'run_erc', '-o', output] diff --git a/tests/board_samples/kicad_5/RLC_sort.sch b/tests/board_samples/kicad_5/RLC_sort.sch index a4c3d852..9e45fb47 100644 --- a/tests/board_samples/kicad_5/RLC_sort.sch +++ b/tests/board_samples/kicad_5/RLC_sort.sch @@ -87,7 +87,7 @@ U 1 1 5E6A448B P 2200 3650 F 0 "R9" V 2280 3650 50 0000 C CNN F 1 "100R" V 2200 3650 50 0000 C CNN -F 2 "Resistor_SMD:R_0603_1608Metric" V 2130 3650 50 0001 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2130 3650 50 0001 C CNN F 3 "~" H 2200 3650 50 0001 C CNN 1 2200 3650 1 0 0 -1 @@ -98,7 +98,7 @@ U 1 1 5E6A491A P 2500 3650 F 0 "R10" V 2580 3650 50 0000 C CNN F 1 "100" V 2500 3650 50 0000 C CNN -F 2 "Resistor_SMD:R_0603_1608Metric" V 2430 3650 50 0001 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2430 3650 50 0001 C CNN F 3 "~" H 2500 3650 50 0001 C CNN 1 2500 3650 1 0 0 -1 diff --git a/tests/test_plot/test_preflight.py b/tests/test_plot/test_preflight.py index 19327876..5d59c596 100644 --- a/tests/test_plot/test_preflight.py +++ b/tests/test_plot/test_preflight.py @@ -23,7 +23,7 @@ if prev_dir not in sys.path: sys.path.insert(0, prev_dir) # Utils import from utils import context -from kibot.misc import (DRC_ERROR, ERC_ERROR, BOM_ERROR, CORRUPTED_PCB) +from kibot.misc import (DRC_ERROR, ERC_ERROR, BOM_ERROR, CORRUPTED_PCB, CORRUPTED_SCH) def test_erc_1(): @@ -49,7 +49,7 @@ def test_erc_fail_2(): """ Using a dummy SCH """ prj = '3Rs' ctx = context.TestContext('ERCFail2', prj, 'erc', '') - ctx.run(ERC_ERROR) + ctx.run(CORRUPTED_SCH) ctx.clean_up() diff --git a/tests/test_plot/test_print_sch.py b/tests/test_plot/test_print_sch.py index e2909590..be7fa971 100644 --- a/tests/test_plot/test_print_sch.py +++ b/tests/test_plot/test_print_sch.py @@ -67,7 +67,7 @@ def check_l1(ctx): ctx.expect_out_file(o_name) sch = Schematic() try: - sch.load(ctx.get_out_path(o_name)) + sch.load(ctx.get_out_path(o_name), 'no_project') except SchFileError as e: logging.error('At line {} of `{}`: {}'.format(e.line, e.file, e.msg)) logging.error('Line content: `{}`'.format(e.code))