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',
|
'to': 'mirror_y_axis',
|
||||||
'required': lambda opts: True,
|
'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)
|
po = PC.OutputOptions(otype)
|
||||||
|
|
@ -411,7 +429,7 @@ class CfgYamlReader(CfgReader):
|
||||||
raise YamlError("Output needs a type")
|
raise YamlError("Output needs a type")
|
||||||
|
|
||||||
if otype not in ['gerber', 'ps', 'hpgl', 'dxf', 'pdf', 'svg',
|
if otype not in ['gerber', 'ps', 'hpgl', 'dxf', 'pdf', 'svg',
|
||||||
'gerb_drill', 'excellon']:
|
'gerb_drill', 'excellon', 'position']:
|
||||||
raise YamlError("Unknown output type: {}".format(otype))
|
raise YamlError("Unknown output type: {}".format(otype))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
166
kiplot/kiplot.py
166
kiplot/kiplot.py
|
|
@ -2,6 +2,7 @@
|
||||||
Main Kiplot code
|
Main Kiplot code
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
@ -52,6 +53,8 @@ class Plotter(object):
|
||||||
self._do_layer_plot(board, pc, op, brd_file)
|
self._do_layer_plot(board, pc, op, brd_file)
|
||||||
elif self._output_is_drill(op):
|
elif self._output_is_drill(op):
|
||||||
self._do_drill_plot(board, pc, op)
|
self._do_drill_plot(board, pc, op)
|
||||||
|
elif self._output_is_position(op):
|
||||||
|
self._do_position_plot(board, pc, op)
|
||||||
else:
|
else:
|
||||||
raise PlotError("Don't know how to plot type {}"
|
raise PlotError("Don't know how to plot type {}"
|
||||||
.format(op.options.type))
|
.format(op.options.type))
|
||||||
|
|
@ -85,6 +88,9 @@ class Plotter(object):
|
||||||
PCfg.OutputOptions.GERB_DRILL,
|
PCfg.OutputOptions.GERB_DRILL,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def _output_is_position(self, output):
|
||||||
|
return output.options.type == PCfg.OutputOptions.POSITION
|
||||||
|
|
||||||
def _get_layer_plot_format(self, output):
|
def _get_layer_plot_format(self, output):
|
||||||
"""
|
"""
|
||||||
Gets the Pcbnew plot format for a given KiPlot output type
|
Gets the Pcbnew plot format for a given KiPlot output type
|
||||||
|
|
@ -240,6 +246,160 @@ class Plotter(object):
|
||||||
|
|
||||||
drill_writer.GenDrillReportFile(drill_report_file)
|
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):
|
def _configure_gerber_opts(self, po, output):
|
||||||
|
|
||||||
# true if gerber
|
# true if gerber
|
||||||
|
|
@ -290,6 +450,10 @@ class Plotter(object):
|
||||||
assert(output.options.type == PCfg.OutputOptions.SVG)
|
assert(output.options.type == PCfg.OutputOptions.SVG)
|
||||||
# pdf_opts = output.options.type_options
|
# 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):
|
def _configure_output_dir(self, plot_ctrl, output):
|
||||||
|
|
||||||
po = plot_ctrl.GetPlotOptions()
|
po = plot_ctrl.GetPlotOptions()
|
||||||
|
|
@ -343,6 +507,8 @@ class Plotter(object):
|
||||||
self._configure_pdf_opts(po, output)
|
self._configure_pdf_opts(po, output)
|
||||||
elif output.options.type == PCfg.OutputOptions.HPGL:
|
elif output.options.type == PCfg.OutputOptions.HPGL:
|
||||||
self._configure_hpgl_opts(po, output)
|
self._configure_hpgl_opts(po, output)
|
||||||
|
elif output.options.type == PCfg.OutputOptions.POSITION:
|
||||||
|
self._configure_position_opts(po, output)
|
||||||
|
|
||||||
po.SetDrillMarksType(opts.drill_marks)
|
po.SetDrillMarksType(opts.drill_marks)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -361,6 +361,22 @@ class DrillMapOptions(object):
|
||||||
self.type = None
|
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):
|
class OutputOptions(object):
|
||||||
|
|
||||||
GERBER = 'gerber'
|
GERBER = 'gerber'
|
||||||
|
|
@ -372,6 +388,7 @@ class OutputOptions(object):
|
||||||
|
|
||||||
EXCELLON = 'excellon'
|
EXCELLON = 'excellon'
|
||||||
GERB_DRILL = 'gerb_drill'
|
GERB_DRILL = 'gerb_drill'
|
||||||
|
POSITION = 'position'
|
||||||
|
|
||||||
def __init__(self, otype):
|
def __init__(self, otype):
|
||||||
self.type = otype
|
self.type = otype
|
||||||
|
|
@ -392,6 +409,8 @@ class OutputOptions(object):
|
||||||
self.type_options = ExcellonOptions()
|
self.type_options = ExcellonOptions()
|
||||||
elif otype == self.GERB_DRILL:
|
elif otype == self.GERB_DRILL:
|
||||||
self.type_options = GerberDrillOptions()
|
self.type_options = GerberDrillOptions()
|
||||||
|
elif otype == self.POSITION:
|
||||||
|
self.type_options = PositionOptions()
|
||||||
else:
|
else:
|
||||||
self.type_options = None
|
self.type_options = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,13 @@ outputs:
|
||||||
- layer: F.Cu
|
- layer: F.Cu
|
||||||
suffix: F_Cu
|
suffix: F_Cu
|
||||||
- layer: F.SilkS
|
- 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