This commit is contained in:
Salvador E. Tropea 2020-03-13 10:46:10 -03:00
commit 97b6cedf06
4 changed files with 214 additions and 2 deletions

View File

@ -326,6 +326,24 @@ class CfgYamlReader(CfgReader):
'to': 'mirror_y_axis',
'required': lambda opts: True,
},
{
'key': 'format',
'types': ['position'],
'to': 'format',
'required': lambda opts: True,
},
{
'key': 'units',
'types': ['position'],
'to': 'units',
'required': lambda opts: True,
},
{
'key': 'separate_files_for_front_and_back',
'types': ['position'],
'to': 'separate_files_for_front_and_back',
'required': lambda opts: True,
}
]
po = PC.OutputOptions(otype)
@ -411,7 +429,7 @@ class CfgYamlReader(CfgReader):
raise YamlError("Output needs a type")
if otype not in ['gerber', 'ps', 'hpgl', 'dxf', 'pdf', 'svg',
'gerb_drill', 'excellon']:
'gerb_drill', 'excellon', 'position']:
raise YamlError("Unknown output type: {}".format(otype))
try:

View File

@ -2,6 +2,7 @@
Main Kiplot code
"""
from datetime import datetime
import logging
import os
@ -52,6 +53,8 @@ class Plotter(object):
self._do_layer_plot(board, pc, op, brd_file)
elif self._output_is_drill(op):
self._do_drill_plot(board, pc, op)
elif self._output_is_position(op):
self._do_position_plot(board, pc, op)
else:
raise PlotError("Don't know how to plot type {}"
.format(op.options.type))
@ -85,6 +88,9 @@ class Plotter(object):
PCfg.OutputOptions.GERB_DRILL,
]
def _output_is_position(self, output):
return output.options.type == PCfg.OutputOptions.POSITION
def _get_layer_plot_format(self, output):
"""
Gets the Pcbnew plot format for a given KiPlot output type
@ -240,6 +246,160 @@ class Plotter(object):
drill_writer.GenDrillReportFile(drill_report_file)
def _do_position_plot_ascii(self, board, plot_ctrl, output, columns, modulesStr, maxSizes):
to = output.options.type_options
outdir = plot_ctrl.GetPlotOptions().GetOutputDirectory()
if not os.path.exists(outdir):
os.makedirs(outdir)
name = os.path.splitext(os.path.basename(board.GetFileName()))[0]
topf = None
botf = None
bothf = None
if to.separate_files_for_front_and_back:
topf = open(os.path.join(outdir, "{}-top.pos".format(name)), 'w')
botf = open(os.path.join(outdir, "{}-bottom.pos".format(name)),
'w')
else:
bothf = open(os.path.join(outdir, "{}-both.pos").format(name), 'w')
files = [f for f in [topf, botf, bothf] if f is not None]
for f in files:
f.write('### Module positions - created on {} ###\n'.format(
datetime.now().strftime("%a %d %b %Y %I:%M:%S %p %Z")
))
f.write('### Printed by KiPlot\n')
unit = {'millimeters': 'mm',
'inches': 'in'}[to.units]
f.write('## Unit: {}, Angle = deg\n'.format(unit))
if topf is not None:
topf.write('## Side: top\n')
if botf is not None:
botf.write('## Side: bottom\n')
if bothf is not None:
bothf.write('## Side: both\n')
for f in files:
f.write('# ')
for idx, col in enumerate(columns):
if idx > 0:
f.write(" ")
f.write("{0: <{width}}".format(col, width=maxSizes[idx]))
f.write('\n')
# Account for the "# " at the start of the comment column
maxSizes[0] = maxSizes[0] + 2
for m in modulesStr:
fle = bothf
if fle is None:
if m[-1] == "top":
fle = topf
else:
fle = botf
for idx, col in enumerate(m):
if idx > 0:
fle.write(" ")
fle.write("{0: <{width}}".format(col, width=maxSizes[idx]))
fle.write("\n")
for f in files:
f.write("## End\n")
if topf is not None:
topf.close()
if botf is not None:
botf.close()
if bothf is not None:
bothf.close()
def _do_position_plot_csv(self, board, plot_ctrl, output, columns, modulesStr):
to = output.options.type_options
outdir = plot_ctrl.GetPlotOptions().GetOutputDirectory()
if not os.path.exists(outdir):
os.makedirs(outdir)
name = os.path.splitext(os.path.basename(board.GetFileName()))[0]
topf = None
botf = None
bothf = None
if to.separate_files_for_front_and_back:
topf = open(os.path.join(outdir, "{}-top-pos.csv".format(name)),
'w')
botf = open(os.path.join(outdir, "{}-bottom-pos.csv".format(name)),
'w')
else:
bothf = open(os.path.join(outdir, "{}-both-pos.csv").format(name),
'w')
files = [f for f in [topf, botf, bothf] if f is not None]
for f in files:
f.write(",".join(columns))
f.write("\n")
for m in modulesStr:
fle = bothf
if fle is None:
if m[-1] == "top":
fle = topf
else:
fle = botf
fle.write(",".join('"{}"'.format(e) for e in m))
fle.write("\n")
if topf is not None:
topf.close()
if botf is not None:
botf.close()
if bothf is not None:
bothf.close()
def _do_position_plot(self, board, plot_ctrl, output):
to = output.options.type_options
columns = ["ref", "val", "package", "posx", "posy", "rot", "side"]
colcount = len(columns)
conv = 1.0
if to.units == 'millimeters':
conv = 1.0 / pcbnew.IU_PER_MM
elif to.units == 'inches':
conv = 0.001 / pcbnew.IU_PER_MILS
else:
raise PlotError('Invalid units: {}'.format(to.units))
# Format all strings
modules = []
for m in board.GetModules():
center = m.GetCenter()
# See PLACE_FILE_EXPORTER::GenPositionData() in
# export_footprints_placefile.cpp for C++ version of this.
modules.append([
"{}".format(m.GetReference()),
"{}".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")
])
# 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]))
if to.format.lower() == 'ascii':
self._do_position_plot_ascii(board, plot_ctrl, output, columns, modules,
maxlengths)
elif to.format.lower() == 'csv':
self._do_position_plot_csv(board, plot_ctrl, output, columns, modules)
else:
raise PlotError("Format is invalid: {}".format(to.format))
def _configure_gerber_opts(self, po, output):
# true if gerber
@ -290,6 +450,10 @@ class Plotter(object):
assert(output.options.type == PCfg.OutputOptions.SVG)
# pdf_opts = output.options.type_options
def _configure_position_opts(self, po, output):
assert(output.options.type == PCfg.OutputOptions.POSITION)
def _configure_output_dir(self, plot_ctrl, output):
po = plot_ctrl.GetPlotOptions()
@ -343,6 +507,8 @@ class Plotter(object):
self._configure_pdf_opts(po, output)
elif output.options.type == PCfg.OutputOptions.HPGL:
self._configure_hpgl_opts(po, output)
elif output.options.type == PCfg.OutputOptions.POSITION:
self._configure_position_opts(po, output)
po.SetDrillMarksType(opts.drill_marks)

View File

@ -361,6 +361,22 @@ class DrillMapOptions(object):
self.type = None
class PositionOptions(TypeOptions):
def __init__(self):
self.format = None
self.units = None
self.separate_files_for_front_and_back = None
def validate(self):
errs = []
if self.format not in ["ASCII", "CSV"]:
errs.append("Format must be either ASCII or CSV")
if self.units not in ["millimeters", "inches"]:
errs.append("Units must be either millimeters or inches")
return errs
class OutputOptions(object):
GERBER = 'gerber'
@ -372,6 +388,7 @@ class OutputOptions(object):
EXCELLON = 'excellon'
GERB_DRILL = 'gerb_drill'
POSITION = 'position'
def __init__(self, otype):
self.type = otype
@ -392,6 +409,8 @@ class OutputOptions(object):
self.type_options = ExcellonOptions()
elif otype == self.GERB_DRILL:
self.type_options = GerberDrillOptions()
elif otype == self.POSITION:
self.type_options = PositionOptions()
else:
self.type_options = None

View File

@ -33,4 +33,13 @@ outputs:
- layer: F.Cu
suffix: F_Cu
- layer: F.SilkS
suffix: F_SilkS
suffix: F_SilkS
- name: 'position'
comment: "Pick and place file"
type: position
dir: positiondir
options:
format: ASCII # CSV or ASCII format
units: millimeters # millimeters or inches
separate_files_for_front_and_back: true