parent
d72913a280
commit
02aa6bce0d
13
README.md
13
README.md
|
|
@ -1443,13 +1443,22 @@ Notes:
|
|||
- `hide_header`: [boolean=false] Hide the header line (names of the columns).
|
||||
- `hide_pcb_info`: [boolean=false] Hide project information.
|
||||
- `hide_stats_info`: [boolean=false] Hide statistics information.
|
||||
- **`format`**: [string=''] [HTML,CSV,TXT,TSV,XML,XLSX] format for the BoM.
|
||||
Defaults to CSV or a guess according to the options..
|
||||
- **`format`**: [string=''] [HTML,CSV,TXT,TSV,XML,XLSX,HRTXT] format for the BoM.
|
||||
Defaults to CSV or a guess according to the options.
|
||||
HRTXT stands for Human Readable TeXT.
|
||||
- **`group_fields`**: [list(string)] List of fields used for sorting individual components into groups.
|
||||
Components which match (comparing *all* fields) will be grouped together.
|
||||
Field names are case-insensitive.
|
||||
If empty: ['Part', 'Part Lib', 'Value', 'Footprint', 'Footprint Lib',
|
||||
'Voltage', 'Tolerance', 'Current', 'Power'] is used.
|
||||
- **`hrtxt`**: [dict] Options for the HRTXT formats.
|
||||
* Valid keys:
|
||||
- **`separator`**: [string='I'] Column Separator.
|
||||
- `header_sep`: [string='-'] Separator between the header and the data.
|
||||
- `hide_header`: [boolean=false] Hide the header line (names of the columns).
|
||||
- `hide_pcb_info`: [boolean=false] Hide project information.
|
||||
- `hide_stats_info`: [boolean=false] Hide statistics information.
|
||||
- `justify`: [string='left'] [left,right,center] Text justification.
|
||||
- **`html`**: [dict] Options for the HTML format.
|
||||
* Valid keys:
|
||||
- **`datasheet_as_link`**: [string=''] Column with links to the datasheet.
|
||||
|
|
|
|||
|
|
@ -244,8 +244,9 @@ outputs:
|
|||
footprint_populate_values: 'no,yes'
|
||||
# [string|list(string)='SMD,THT,VIRTUAL'] Values for the `Footprint Type` column
|
||||
footprint_type_values: 'SMD,THT,VIRTUAL'
|
||||
# [string=''] [HTML,CSV,TXT,TSV,XML,XLSX] format for the BoM.
|
||||
# [string=''] [HTML,CSV,TXT,TSV,XML,XLSX,HRTXT] format for the BoM.
|
||||
# Defaults to CSV or a guess according to the options.
|
||||
# HRTXT stands for Human Readable TeXT
|
||||
format: 'CSV'
|
||||
# [boolean=true] Connectors with the same footprints will be grouped together, independent of the name of the connector
|
||||
group_connectors: true
|
||||
|
|
@ -258,6 +259,20 @@ outputs:
|
|||
# [list(string)] List of fields to be used when the fields in `group_fields` are empty.
|
||||
# The first field in this list is the fallback for the first in `group_fields`, and so on
|
||||
group_fields_fallbacks:
|
||||
# [dict] Options for the HRTXT formats
|
||||
hrtxt:
|
||||
# [string='-'] Separator between the header and the data
|
||||
header_sep: '-'
|
||||
# [boolean=false] Hide the header line (names of the columns)
|
||||
hide_header: false
|
||||
# [boolean=false] Hide project information
|
||||
hide_pcb_info: false
|
||||
# [boolean=false] Hide statistics information
|
||||
hide_stats_info: false
|
||||
# [string='left'] [left,right,center] Text justification
|
||||
justify: 'left'
|
||||
# [string='I'] Column Separator
|
||||
separator: 'I'
|
||||
# [dict] Options for the HTML format
|
||||
html:
|
||||
# [boolean=true] Use colors to show the field type
|
||||
|
|
|
|||
|
|
@ -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-2022 Salvador E. Tropea
|
||||
# Copyright (c) 2020-2022 Instituto Nacional de Tecnología Industrial
|
||||
# Copyright (c) 2016-2020 Oliver Henry Walters (@SchrodingersGat)
|
||||
# License: MIT
|
||||
# Project: KiBot (formerly KiPlot)
|
||||
|
|
@ -35,13 +35,13 @@ def write_bom(filename, ext, groups, headings, cfg):
|
|||
headings = [h.lower() for h in headings]
|
||||
result = False
|
||||
# CSV file writing
|
||||
if ext in ["csv", "tsv", "txt"]:
|
||||
if ext in ["csv", "tsv", "txt", "hrtxt"]:
|
||||
result = write_csv(filename, ext, groups, headings, head_names, cfg)
|
||||
elif ext in ["htm", "html"]:
|
||||
result = write_html(filename, groups, headings, head_names, cfg)
|
||||
elif ext in ["xml"]:
|
||||
elif ext == "xml":
|
||||
result = write_xml(filename, groups, headings, head_names, cfg)
|
||||
elif ext in ["xlsx"]:
|
||||
elif ext == "xlsx":
|
||||
# We delay the module load to give out_bom the chance to install XLSXWriter dependencies
|
||||
from .xlsx_writer import write_xlsx
|
||||
result = write_xlsx(filename, groups, headings, head_names, cfg)
|
||||
|
|
|
|||
|
|
@ -9,44 +9,101 @@
|
|||
CSV Writer: Generates a CSV, TSV or TXT BoM file.
|
||||
"""
|
||||
import csv
|
||||
ALIGN_CODE = {'right': '>', 'left': '<', 'center': '^'}
|
||||
|
||||
|
||||
def write_stats(writer, cfg):
|
||||
class HRTXT(object):
|
||||
def __init__(self, fh, delimiter=',', hsep='-', align='left'):
|
||||
self.f = fh
|
||||
self.delimiter = delimiter
|
||||
self.hsep = hsep
|
||||
self.cols_w = []
|
||||
self.data = []
|
||||
self.align = ALIGN_CODE[align]
|
||||
|
||||
def writerow(self, row):
|
||||
self.data.append(row)
|
||||
for c, d in enumerate(row):
|
||||
d = str(d)
|
||||
l_cell = len(d)
|
||||
try:
|
||||
self.cols_w[c] = max(self.cols_w[c], l_cell)
|
||||
except IndexError:
|
||||
self.cols_w.append(l_cell)
|
||||
|
||||
def add_sep(self):
|
||||
self.data.append(None)
|
||||
|
||||
def flush(self):
|
||||
self.col_fmt = []
|
||||
for ln in self.cols_w:
|
||||
self.col_fmt.append("{:"+self.align+str(ln)+"s}")
|
||||
prev = None
|
||||
for r in self.data:
|
||||
# Is a separator?
|
||||
if r is None:
|
||||
# Skip if we don't want separators
|
||||
if not self.hsep:
|
||||
continue
|
||||
# Create a fake row using the separator
|
||||
r = []
|
||||
for _, ln in zip(prev, self.cols_w):
|
||||
r.append(self.hsep*ln)
|
||||
if len(r):
|
||||
self.f.write(self.delimiter)
|
||||
for cell, fmt in zip(r, self.col_fmt):
|
||||
cell = str(cell)
|
||||
self.f.write(fmt.format(cell.replace("\n", self.delimiter)))
|
||||
self.f.write(self.delimiter)
|
||||
self.f.write("\n")
|
||||
prev = r
|
||||
|
||||
|
||||
def write_stats(writer, cfg, ops, write_sep):
|
||||
if len(cfg.aggregate) == 1:
|
||||
# Only one project
|
||||
if not cfg.csv.hide_pcb_info:
|
||||
if not ops.hide_pcb_info:
|
||||
prj = cfg.aggregate[0]
|
||||
writer.writerow(["Project info:"])
|
||||
write_sep()
|
||||
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:
|
||||
write_sep()
|
||||
if not ops.hide_stats_info:
|
||||
writer.writerow(["Statistics:"])
|
||||
write_sep()
|
||||
writer.writerow(["Component Groups:", cfg.n_groups])
|
||||
writer.writerow(["Component Count:", cfg.total_str])
|
||||
writer.writerow(["Fitted Components:", cfg.fitted_str])
|
||||
writer.writerow(["Number of PCBs:", cfg.number])
|
||||
writer.writerow(["Total Components:", cfg.n_build])
|
||||
write_sep()
|
||||
else:
|
||||
# Multiple projects
|
||||
if not cfg.csv.hide_pcb_info:
|
||||
if not ops.hide_pcb_info:
|
||||
prj = cfg.aggregate[0]
|
||||
writer.writerow(["Project info:"])
|
||||
write_sep()
|
||||
writer.writerow(["Variant:", cfg.variant.name])
|
||||
writer.writerow(["KiCad Version:", cfg.kicad_version])
|
||||
if not cfg.csv.hide_stats_info:
|
||||
write_sep()
|
||||
if not ops.hide_stats_info:
|
||||
writer.writerow(["Global statistics:"])
|
||||
write_sep()
|
||||
writer.writerow(["Component Groups:", cfg.n_groups])
|
||||
writer.writerow(["Component Count:", cfg.total_str])
|
||||
writer.writerow(["Fitted Components:", cfg.fitted_str])
|
||||
writer.writerow(["Number of PCBs:", cfg.number])
|
||||
writer.writerow(["Total Components:", cfg.n_build])
|
||||
write_sep()
|
||||
# Individual stats
|
||||
for prj in cfg.aggregate:
|
||||
if not cfg.csv.hide_pcb_info:
|
||||
if not ops.hide_pcb_info:
|
||||
writer.writerow(["Project info:", prj.sch.title])
|
||||
write_sep()
|
||||
writer.writerow(["Schematic:", prj.name])
|
||||
writer.writerow(["Revision:", prj.sch.revision])
|
||||
writer.writerow(["Date:", prj.sch.date])
|
||||
|
|
@ -54,13 +111,20 @@ def write_stats(writer, cfg):
|
|||
writer.writerow(["Company:", prj.sch.company])
|
||||
if prj.ref_id:
|
||||
writer.writerow(["ID", prj.ref_id])
|
||||
if not cfg.csv.hide_stats_info:
|
||||
write_sep()
|
||||
if not ops.hide_stats_info:
|
||||
writer.writerow(["Statistics:", prj.sch.title])
|
||||
write_sep()
|
||||
writer.writerow(["Component Groups:", prj.comp_groups])
|
||||
writer.writerow(["Component Count:", prj.total_str])
|
||||
writer.writerow(["Fitted Components:", prj.fitted_str])
|
||||
writer.writerow(["Number of PCBs:", prj.number])
|
||||
writer.writerow(["Total Components:", prj.comp_build])
|
||||
write_sep()
|
||||
|
||||
|
||||
def dummy():
|
||||
pass
|
||||
|
||||
|
||||
def write_csv(filename, ext, groups, headings, head_names, cfg):
|
||||
|
|
@ -72,37 +136,47 @@ def write_csv(filename, ext, groups, headings, head_names, cfg):
|
|||
head_names = [list of headings to display in the BoM file]
|
||||
cfg = BoMOptions object with all the configuration
|
||||
"""
|
||||
is_hrtxt = ext == "hrtxt"
|
||||
ops = cfg.hrtxt if is_hrtxt else cfg.csv
|
||||
# Delimiter is assumed from file extension
|
||||
# Override delimiter if separator specified
|
||||
if ext == "csv" and cfg.csv.separator:
|
||||
delimiter = cfg.csv.separator
|
||||
if is_hrtxt or (ext == "csv" and ops.separator):
|
||||
delimiter = ops.separator
|
||||
else:
|
||||
if ext == "csv":
|
||||
delimiter = ","
|
||||
elif ext == "tsv" or ext == "txt":
|
||||
delimiter = "\t"
|
||||
|
||||
if cfg.csv.quote_all:
|
||||
quoting = csv.QUOTE_MINIMAL
|
||||
if hasattr(ops, 'quote_all') and ops.quote_all:
|
||||
quoting = csv.QUOTE_ALL
|
||||
else:
|
||||
quoting = csv.QUOTE_MINIMAL
|
||||
|
||||
with open(filename, "wt") as f:
|
||||
writer = csv.writer(f, delimiter=delimiter, lineterminator="\n", quoting=quoting)
|
||||
if is_hrtxt:
|
||||
writer = HRTXT(f, delimiter=delimiter, hsep=ops.header_sep, align=ops.justify)
|
||||
else:
|
||||
writer = csv.writer(f, delimiter=delimiter, lineterminator="\n", quoting=quoting)
|
||||
write_sep = writer.add_sep if is_hrtxt else dummy
|
||||
# Headers
|
||||
if not cfg.csv.hide_header:
|
||||
if not ops.hide_header:
|
||||
writer.writerow(head_names)
|
||||
write_sep()
|
||||
# Body
|
||||
for group in groups:
|
||||
if cfg.ignore_dnf and not group.is_fitted():
|
||||
continue
|
||||
row = group.get_row(headings)
|
||||
writer.writerow(row)
|
||||
write_sep()
|
||||
# PCB info
|
||||
if not (cfg.csv.hide_pcb_info and cfg.csv.hide_stats_info):
|
||||
if not (ops.hide_pcb_info and ops.hide_stats_info):
|
||||
# Add some blank rows
|
||||
for _ in range(5):
|
||||
writer.writerow([])
|
||||
# The info
|
||||
write_stats(writer, cfg)
|
||||
write_stats(writer, cfg, ops, write_sep)
|
||||
if is_hrtxt:
|
||||
writer.flush()
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -269,6 +269,33 @@ class BoMCSV(Optionable):
|
|||
raise KiPlotConfigurationError('The CSV separator must be one character (`{}`)'.format(self.separator))
|
||||
|
||||
|
||||
class BoMTXT(Optionable):
|
||||
""" HRTXT options """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
with document:
|
||||
self.separator = 'I'
|
||||
""" *Column Separator """
|
||||
self.header_sep = '-'
|
||||
""" Separator between the header and the data """
|
||||
self.justify = 'left'
|
||||
""" [left,right,center] Text justification """
|
||||
self.hide_header = False
|
||||
""" Hide the header line (names of the columns) """
|
||||
self.hide_pcb_info = False
|
||||
""" Hide project information """
|
||||
self.hide_stats_info = False
|
||||
""" Hide statistics information """
|
||||
|
||||
def config(self, parent):
|
||||
super().config(parent)
|
||||
if self.separator:
|
||||
self.separator = self.separator.replace(r'\t', '\t')
|
||||
self.separator = self.separator.replace(r'\n', '\n')
|
||||
self.separator = self.separator.replace(r'\r', '\r')
|
||||
self.separator = self.separator.replace(r'\\', '\\')
|
||||
|
||||
|
||||
class BoMXLSX(BoMLinkable):
|
||||
""" XLSX options """
|
||||
def __init__(self):
|
||||
|
|
@ -411,8 +438,9 @@ class BoMOptions(BaseOptions):
|
|||
self.output = GS.def_global_output
|
||||
""" *filename for the output (%i=bom)"""
|
||||
self.format = ''
|
||||
""" *[HTML,CSV,TXT,TSV,XML,XLSX] format for the BoM.
|
||||
Defaults to CSV or a guess according to the options. """
|
||||
""" *[HTML,CSV,TXT,TSV,XML,XLSX,HRTXT] format for the BoM.
|
||||
Defaults to CSV or a guess according to the options.
|
||||
HRTXT stands for Human Readable TeXT """
|
||||
# Equivalent to KiBoM INI:
|
||||
self.ignore_dnf = True
|
||||
""" *Exclude DNF (Do Not Fit) components """
|
||||
|
|
@ -438,6 +466,8 @@ class BoMOptions(BaseOptions):
|
|||
""" *[dict] Options for the XLSX format """
|
||||
self.csv = BoMCSV
|
||||
""" *[dict] Options for the CSV, TXT and TSV formats """
|
||||
self.hrtxt = BoMTXT
|
||||
""" *[dict] Options for the HRTXT formats """
|
||||
# * Filters
|
||||
self.pre_transform = Optionable
|
||||
""" [string|list(string)='_none'] Name of the filter to transform fields before applying other filters.
|
||||
|
|
@ -553,6 +583,9 @@ class BoMOptions(BaseOptions):
|
|||
# Same for XLSX
|
||||
if not isinstance(self.xlsx, type):
|
||||
return 'xlsx'
|
||||
# Same for HRTXT
|
||||
if not isinstance(self.hrtxt, type):
|
||||
return 'hrtxt'
|
||||
# Default to a simple and common format: CSV
|
||||
return 'csv'
|
||||
# Explicit selection
|
||||
|
|
@ -639,7 +672,7 @@ class BoMOptions(BaseOptions):
|
|||
super().config(parent)
|
||||
self.format = self._guess_format()
|
||||
self._expand_id = 'bom'
|
||||
self._expand_ext = self.format.lower()
|
||||
self._expand_ext = 'txt' if self.format.lower() == 'hrtxt' else self.format.lower()
|
||||
# HTML options
|
||||
if self.format == 'html' and isinstance(self.html, type):
|
||||
# If no options get the defaults
|
||||
|
|
@ -1018,7 +1051,7 @@ class BoM(BaseOutput): # noqa: F821
|
|||
if join_fields:
|
||||
logger.debug(' - Fields to join with Value: {}'.format(join_fields))
|
||||
# Create a generic version
|
||||
SIMP_FMT = ['HTML', 'CSV', 'TXT', 'TSV', 'XML']
|
||||
SIMP_FMT = ['HTML', 'CSV', 'HRTXT', 'TSV', 'XML']
|
||||
XYRS_FMT = ['HTML']
|
||||
if GS.check_tool(name, 'XLSXWriter') is not None:
|
||||
SIMP_FMT.append('XLSX')
|
||||
|
|
|
|||
Loading…
Reference in New Issue