Added columns configuration for position files.
You can customize which columns are used, their names and order. Closes #22
This commit is contained in:
parent
67d879ece1
commit
de9628e5c1
|
|
@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Help for filters and variants.
|
||||
- Support for new `pcbnew_do export` options.
|
||||
- Filters for KiBot warnings.
|
||||
- Columns in position files can be selected, renamed and sorted as you
|
||||
like.
|
||||
|
||||
### Fixed
|
||||
- KiBom variants when using multiple variants and a components used more
|
||||
|
|
|
|||
|
|
@ -1070,6 +1070,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 `position` output.
|
||||
* Valid keys:
|
||||
- `columns`: [list(dict)|list(string)] which columns are included in the output.
|
||||
* Valid keys:
|
||||
- `id`: [string=''] [Ref,Val,Package,PosX,PosY,Rot,Side] Internal name.
|
||||
- `name`: [string=''] Name to use in the outut file. The id is used when empty.
|
||||
- `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.
|
||||
- `format`: [string='ASCII'] [ASCII,CSV] format for the position file.
|
||||
|
|
|
|||
|
|
@ -698,6 +698,12 @@ outputs:
|
|||
type: 'position'
|
||||
dir: 'Example/position_dir'
|
||||
options:
|
||||
# [list(dict)|list(string)] which columns are included in the output
|
||||
columns:
|
||||
# [string=''] [Ref,Val,Package,PosX,PosY,Rot,Side] Internal name
|
||||
id: ''
|
||||
# [string=''] Name to use in the outut file. The id is used when empty
|
||||
name: ''
|
||||
# [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: ''
|
||||
|
|
|
|||
|
|
@ -8,15 +8,35 @@
|
|||
import operator
|
||||
from datetime import datetime
|
||||
from pcbnew import IU_PER_MM, IU_PER_MILS
|
||||
from collections import OrderedDict
|
||||
from .gs import GS
|
||||
from .misc import UI_SMD, UI_VIRTUAL, KICAD_VERSION_5_99, MOD_THROUGH_HOLE, MOD_SMD, MOD_EXCLUDE_FROM_POS_FILES
|
||||
from .optionable import Optionable
|
||||
from .out_base import VariantOptions
|
||||
from .error import KiPlotConfigurationError
|
||||
from .macros import macros, document, output_class # noqa: F401
|
||||
from . import log
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class PosColumns(Optionable):
|
||||
""" Which columns we want and its names """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._unkown_is_error = True
|
||||
with document:
|
||||
self.id = ''
|
||||
""" [Ref,Val,Package,PosX,PosY,Rot,Side] Internal name """
|
||||
self.name = ''
|
||||
""" Name to use in the outut file. The id is used when empty """
|
||||
|
||||
def config(self):
|
||||
super().config()
|
||||
if not self.id:
|
||||
raise KiPlotConfigurationError("Missing or empty `id` in columns list ({})".format(str(self._tree)))
|
||||
|
||||
|
||||
class PositionOptions(VariantOptions):
|
||||
def __init__(self):
|
||||
with document:
|
||||
|
|
@ -30,8 +50,28 @@ class PositionOptions(VariantOptions):
|
|||
""" output file name (%i='top_pos'|'bottom_pos'|'both_pos', %x='pos'|'csv') """
|
||||
self.units = 'millimeters'
|
||||
""" [millimeters,inches] units used for the positions """
|
||||
self.columns = PosColumns
|
||||
""" [list(dict)|list(string)] which columns are included in the output """
|
||||
super().__init__()
|
||||
|
||||
def config(self):
|
||||
super().config()
|
||||
if isinstance(self.columns, type):
|
||||
# Default list of columns
|
||||
self.columns = OrderedDict([('Ref', 'Ref'), ('Val', 'Val'), ('Package', 'Package'), ('PosX', 'PosX'),
|
||||
('PosY', 'PosY'), ('Rot', 'Rot'), ('Side', 'Side')])
|
||||
else:
|
||||
new_columns = OrderedDict()
|
||||
for col in self.columns:
|
||||
if isinstance(col, str):
|
||||
# Just a string, add to the list of used
|
||||
new_name = new_col = col
|
||||
else:
|
||||
new_col = col.id
|
||||
new_name = col.name if col.name else new_col
|
||||
new_columns[new_col] = new_name
|
||||
self.columns = new_columns
|
||||
|
||||
def _do_position_plot_ascii(self, board, output_dir, columns, modulesStr, maxSizes):
|
||||
topf = None
|
||||
botf = None
|
||||
|
|
@ -141,8 +181,7 @@ class PositionOptions(VariantOptions):
|
|||
|
||||
def run(self, output_dir, board):
|
||||
super().run(output_dir, board)
|
||||
columns = ["Ref", "Val", "Package", "PosX", "PosY", "Rot", "Side"]
|
||||
colcount = len(columns)
|
||||
columns = self.columns.values()
|
||||
# Note: the parser already checked the units are milimeters or inches
|
||||
conv = 1.0
|
||||
if self.units == 'millimeters':
|
||||
|
|
@ -168,24 +207,31 @@ class PositionOptions(VariantOptions):
|
|||
# If passed check the position options
|
||||
if (self.only_smd and is_pure_smd(m)) or (not self.only_smd and is_not_virtual(m)):
|
||||
center = m.GetCenter()
|
||||
# See PLACE_FILE_EXPORTER::GenPositionData() in
|
||||
# export_footprints_placefile.cpp for C++ version of this.
|
||||
modules.append([
|
||||
"{}".format(ref),
|
||||
"{}".format(m.GetValue()),
|
||||
"{}".format(m.GetFPID().GetLibItemName()),
|
||||
"{:.4f}".format(center.x * conv),
|
||||
"{:.4f}".format(-center.y * conv),
|
||||
"{:.4f}".format(m.GetOrientationDegrees()),
|
||||
"{}".format("bottom" if m.IsFlipped() else "top")
|
||||
])
|
||||
|
||||
# KiCad: PLACE_FILE_EXPORTER::GenPositionData() in export_footprints_placefile.cpp
|
||||
row = []
|
||||
for k in self.columns:
|
||||
if k == 'Ref':
|
||||
row.append(ref)
|
||||
elif k == 'Val':
|
||||
row.append(m.GetValue())
|
||||
elif k == 'Package':
|
||||
row.append(str(m.GetFPID().GetLibItemName())) # pcbnew.UTF8 type
|
||||
elif k == 'PosX':
|
||||
row.append("{:.4f}".format(center.x * conv))
|
||||
elif k == 'PosY':
|
||||
row.append("{:.4f}".format(-center.y * conv))
|
||||
elif k == 'Rot':
|
||||
row.append("{:.4f}".format(m.GetOrientationDegrees()))
|
||||
elif k == 'Side':
|
||||
row.append("bottom" if m.IsFlipped() else "top")
|
||||
modules.append(row)
|
||||
# Find max width for all columns
|
||||
maxlengths = [0] * colcount
|
||||
for row in range(len(modules)):
|
||||
for col in range(colcount):
|
||||
maxlengths[col] = max(maxlengths[col], len(modules[row][col]))
|
||||
|
||||
maxlengths = []
|
||||
for col, name in enumerate(columns):
|
||||
max_l = len(name)
|
||||
for row in modules:
|
||||
max_l = max(max_l, len(row[col]))
|
||||
maxlengths.append(max_l)
|
||||
# Note: the parser already checked the format is ASCII or CSV
|
||||
if self.format == 'ASCII':
|
||||
self._do_position_plot_ascii(board, output_dir, columns, modules, maxlengths)
|
||||
|
|
|
|||
|
|
@ -119,6 +119,17 @@ def test_3Rs_position_csv():
|
|||
ctx.clean_up()
|
||||
|
||||
|
||||
def test_position_csv_cols():
|
||||
ctx = context.TestContext('test_position_csv_cols', '3Rs', 'simple_position_csv_cols', POS_DIR)
|
||||
ctx.run()
|
||||
pos_top = ctx.get_pos_top_csv_filename()
|
||||
pos_bot = ctx.get_pos_bot_csv_filename()
|
||||
ctx.expect_out_file(pos_top)
|
||||
ctx.expect_out_file(pos_bot)
|
||||
assert ctx.search_in_file(pos_top, ["Ref,Value,Center X"]) is not None
|
||||
ctx.clean_up()
|
||||
|
||||
|
||||
def test_3Rs_position_unified_csv():
|
||||
""" Also test the quiet mode """
|
||||
ctx = context.TestContext('3Rs_position_unified_csv', '3Rs', 'simple_position_unified_csv', POS_DIR)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# Example KiBot config file for a basic 2-layer board
|
||||
kibot:
|
||||
version: 1
|
||||
|
||||
outputs:
|
||||
|
||||
- name: 'position'
|
||||
type: position
|
||||
dir: positiondir
|
||||
options:
|
||||
format: CSV # CSV or ASCII format
|
||||
units: millimeters # millimeters or inches
|
||||
separate_files_for_front_and_back: true
|
||||
only_smd: true
|
||||
columns:
|
||||
- "Ref"
|
||||
- id: Val
|
||||
name: Value
|
||||
- id: PosX
|
||||
name: "Center X"
|
||||
Loading…
Reference in New Issue