Merge branch 'position' of https://github.com/rdeterre/kiplot
This commit is contained in:
commit
97b6cedf06
|
|
@ -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:
|
||||
|
|
|
|||
166
kiplot/kiplot.py
166
kiplot/kiplot.py
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue