diff --git a/README.md b/README.md index c7e48e25..b20e919b 100644 --- a/README.md +++ b/README.md @@ -750,8 +750,14 @@ Next time you need this list just use an alias, like this: - `max_col_width`: [number=60] [20,999] Maximum column width (characters). - `specs`: [boolean=false] Enable Specs worksheet creation. Contains specifications for the components. Works with only some KiCost APIs. - - `specs_columns`: [list(string)] Which columns are included in the Specs worksheet. Use `References` for the references. - By default all are included. + - `specs_columns`: [list(dict)|list(string)] Which columns are included in the Specs worksheet. Use `References` for the references, + 'Row' for the order and 'Sep' to separate groups at the same level. By default all are included. + * Valid keys: + - `comment`: [string=''] Used as explanation for this column. The XLSX output uses it. + - `field`: [string=''] Name of the field to use for this column. + - `join`: [list(string)|string=''] List of fields to join to this column. + - `level`: [number=0] Used to group columns. The XLSX output uses it to collapse columns. + - `name`: [string=''] Name to display in the header. The field is used when empty. - `style`: [string='modern-blue'] Head style: modern-blue, modern-green, modern-red and classic. - `title`: [string='KiBot Bill of Materials'] BoM title. diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index dea7d583..4c8c3beb 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -225,9 +225,19 @@ outputs: # [boolean=false] Enable Specs worksheet creation. Contains specifications for the components. # Works with only some KiCost APIs specs: false - # [list(string)] Which columns are included in the Specs worksheet. Use `References` for the references. - # By default all are included + # [list(dict)|list(string)] Which columns are included in the Specs worksheet. Use `References` for the references, + # 'Row' for the order and 'Sep' to separate groups at the same level. By default all are included specs_columns: + # [string=''] Used as explanation for this column. The XLSX output uses it + - comment: '' + # [string=''] Name of the field to use for this column + field: 'Row' + # [list(string)|string=''] List of fields to join to this column + join: '' + # [number=0] Used to group columns. The XLSX output uses it to collapse columns + level: 0 + # [string=''] Name to display in the header. The field is used when empty + name: 'Line' # [string='modern-blue'] Head style: modern-blue, modern-green, modern-red and classic style: 'modern-blue' # [string='KiBot Bill of Materials'] BoM title diff --git a/kibot/bom/xlsx_writer.py b/kibot/bom/xlsx_writer.py index af52a7cd..1e1fe25f 100644 --- a/kibot/bom/xlsx_writer.py +++ b/kibot/bom/xlsx_writer.py @@ -71,7 +71,7 @@ DEFAULT_FMT = {'text_wrap': True, 'align': 'center_across', 'valign': 'vcenter'} KICOST_COLUMNS = {'refs': ColumnList.COL_REFERENCE, 'desc': ColumnList.COL_DESCRIPTION, 'qty': ColumnList.COL_GRP_BUILD_QUANTITY} -SPECS_GENERATED = set((ColumnList.COL_REFERENCE, ColumnList.COL_ROW_NUMBER)) +SPECS_GENERATED = set((ColumnList.COL_REFERENCE_L, ColumnList.COL_ROW_NUMBER_L, 'sep')) def bg_color(col): @@ -248,14 +248,13 @@ def create_color_ref(workbook, col_colors, hl_empty, fmt_cols, do_kicost, kicost worksheet.write_string(row, 0, label, format) -def create_meta(workbook, name, columns, parts, fmt_head, fmt_cols, max_w): +def create_meta(workbook, name, columns, parts, fmt_head, fmt_cols, max_w, rename, levels, comments, join): worksheet = workbook.add_worksheet(name) - to_col = {} col_w = [] row_h = 1 for c, col in enumerate(columns): - worksheet.write_string(0, c, col, fmt_head) - to_col[col] = c + name = rename.get(col.lower(), col) if rename else col + worksheet.write_string(0, c, name, fmt_head) text_l = max(len(col), 6) if text_l > max_w: h = len(wrap(col, max_w)) @@ -266,19 +265,30 @@ def create_meta(workbook, name, columns, parts, fmt_head, fmt_cols, max_w): worksheet.set_row(0, 15.0*row_h) for r, part in enumerate(parts): # Add the references as another spec - part.specs[ColumnList.COL_REFERENCE] = (ColumnList.COL_REFERENCE, part.collapsed_refs) + part.specs[ColumnList.COL_REFERENCE_L] = (ColumnList.COL_REFERENCE, part.collapsed_refs) # Also add the row - part.specs[ColumnList.COL_ROW_NUMBER] = (ColumnList.COL_ROW_NUMBER, str(r+1)) + part.specs[ColumnList.COL_ROW_NUMBER_L] = (ColumnList.COL_ROW_NUMBER, str(r+1)) row_h = 1 - for col in columns: - v = part.specs.get(col, None) - if v is None: + for c, col in enumerate(columns): + col_l = col.lower() + if col_l == 'sep': + col_w[c] = 0 continue - c = to_col[col] + v = part.specs.get(col_l, ['', '']) text = v[1] - fmt_kind = 0 if col in SPECS_GENERATED else 2 - worksheet.write_string(r+1, c, text, fmt_cols[fmt_kind][r % 2]) + # Append text from other fields + if join: + for j in join: + if j[0] == col_l: + for c_join in j[1:]: + v = part.specs.get(c_join, None) + if v: + text += ' ' + v[1] text_l = len(text) + if not text_l: + continue + fmt_kind = 0 if col_l in SPECS_GENERATED else 2 + worksheet.write_string(r+1, c, text, fmt_cols[fmt_kind][r % 2]) if text_l > col_w[c]: if text_l > max_w: h = len(wrap(text, max_w)) @@ -288,7 +298,12 @@ def create_meta(workbook, name, columns, parts, fmt_head, fmt_cols, max_w): if row_h > 1: worksheet.set_row(r+1, 15.0*row_h) for i, width in enumerate(col_w): - worksheet.set_column(i, i, width) + ops = {'level': levels[i] if levels else 0} + if not width: + ops['hidden'] = 1 + if comments and comments[i]: + worksheet.write_comment(0, i, comments[i]) + worksheet.set_column(i, i, width, None, ops) def adjust_widths(worksheet, column_widths, max_width, levels): @@ -464,15 +479,18 @@ def create_meta_sheets(workbook, used_parts, fmt_head, fmt_cols, cfg): meta_names = ['Specs', 'Specs (DNF)'] for ws in range(2): spec_cols = {} + spec_cols_l = set() parts = used_parts[ws] for part in parts: - for spec in part.specs: + for name_l, v in part.specs.items(): + spec_cols_l.add(name_l) + spec = v[0] if spec in spec_cols: spec_cols[spec] += 1 else: spec_cols[spec] = 1 if len(spec_cols): - columns = cfg.xlsx.specs_columns + columns = cfg.xlsx.s_columns if columns is None: # Use all columns, sort them by relevance (most used) and alphabetically c = len(parts) @@ -481,9 +499,11 @@ def create_meta_sheets(workbook, used_parts, fmt_head, fmt_cols, cfg): else: # Inform about missing columns for c in columns: - if c not in spec_cols and c not in SPECS_GENERATED: + col = c.lower() + if col not in spec_cols_l and col not in SPECS_GENERATED: logger.warning(W_BADFIELD+'Invalid Specs column name `{}`'.format(c)) - create_meta(workbook, meta_names[ws], columns, parts, fmt_head, fmt_cols, cfg.xlsx.max_col_width) + create_meta(workbook, meta_names[ws], columns, parts, fmt_head, fmt_cols, cfg.xlsx.max_col_width, + cfg.xlsx.s_rename, cfg.xlsx.s_levels, cfg.xlsx.s_comments, cfg.xlsx.s_join) def _create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_subtitle, fmt_head, fmt_cols, cfg): diff --git a/kibot/out_bom.py b/kibot/out_bom.py index 3e511365..fc7e60c9 100644 --- a/kibot/out_bom.py +++ b/kibot/out_bom.py @@ -170,12 +170,45 @@ class BoMXLSX(BoMLinkable): self.specs = False """ Enable Specs worksheet creation. Contains specifications for the components. Works with only some KiCost APIs """ - self.specs_columns = Optionable - """ [list(string)] Which columns are included in the Specs worksheet. Use `References` for the references. - By default all are included """ + self.specs_columns = BoMColumns + """ [list(dict)|list(string)] Which columns are included in the Specs worksheet. Use `References` for the references, + 'Row' for the order and 'Sep' to separate groups at the same level. By default all are included """ self.logo_scale = 2 """ Scaling factor for the logo. Note that this value isn't honored by all spreadsheet software """ + def process_columns_config(self, cols): + if isinstance(cols, type): + return (None, None, None, None, None) + columns = [] + column_levels = [] + column_comments = [] + column_rename = {} + join = [] + # Create the different lists + for col in cols: + if isinstance(col, str): + # Just a string, add to the list of used + new_col = col + new_col_l = new_col.lower() + level = 0 + comment = '' + else: + # A complete entry + new_col = col.field + new_col_l = new_col.lower() + # A column rename + if col.name: + column_rename[new_col_l] = col.name + # Attach other columns + if col.join: + join.append(col.join) + level = col.level + comment = col.comment + columns.append(new_col) + column_levels.append(level) + column_comments.append(comment) + return (columns, column_levels, column_comments, column_rename, join) + def config(self, parent): super().config(parent) # Style @@ -193,8 +226,8 @@ class BoMXLSX(BoMLinkable): elif isinstance(self.kicost_api_disable, str): self.kicost_api_disable = [self.kicost_api_disable] # Specs columns - if isinstance(self.specs_columns, type): - self.specs_columns = None + (self.s_columns, self.s_levels, self.s_comments, self.s_rename, + self.s_join) = self.process_columns_config(self.specs_columns) class ComponentAliases(Optionable): diff --git a/submodules/KiCost b/submodules/KiCost index 2c635981..8401c3db 160000 --- a/submodules/KiCost +++ b/submodules/KiCost @@ -1 +1 @@ -Subproject commit 2c635981cfca5353f8d7987071fcb4333ca96626 +Subproject commit 8401c3dba2a16d67138a5087c4ae84bf16a2f884