/*
Copyright (C) 2011 Jason von Nieda <jason@vonnieda.org>
This file is part of OpenPnP.
OpenPnP is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenPnP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenPnP. If not, see <http://www.gnu.org/licenses/>.
For more information about OpenPnP visit http://openpnp.org
*/
package org.openpnp.gui.importer;
import java.awt.FileDialog;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.border.TitledBorder;
import org.openpnp.gui.support.MessageBoxes;
import org.openpnp.model.Board;
import org.openpnp.model.Board.Side;
import org.openpnp.model.Configuration;
import org.openpnp.model.LengthUnit;
import org.openpnp.model.Location;
import org.openpnp.model.Package;
import org.openpnp.model.Part;
import org.openpnp.model.Placement;
import com.jgoodies.forms.factories.FormFactory;
import com.jgoodies.forms.layout.ColumnSpec;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.forms.layout.RowSpec;
@SuppressWarnings("serial")
public class KicadPosImporter implements BoardImporter {
private final static String NAME = "KiCAD .pos";
private final static String DESCRIPTION = "Import KiCAD .pos Files.";
private Board board;
private File topFile, bottomFile;
@Override
public String getImporterName() {
return NAME;
}
@Override
public String getImporterDescription() {
return DESCRIPTION;
}
@Override
public Board importBoard(Frame parent) throws Exception {
Dlg dlg = new Dlg(parent);
dlg.setVisible(true);
return board;
}
private static List<Placement> parseFile(File file, Side side, boolean createMissingParts) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
ArrayList<Placement> placements = new ArrayList<Placement>();
String line;
// See: http://bazaar.launchpad.net/~kicad-product-committers/kicad/product/view/head:/pcbnew/exporters/gen_modules_placefile.cpp
// ### Module positions - created on Tue 25 Mar 2014 03:42:43 PM PDT ###
// ### Printed by Pcbnew version pcbnew (2014-01-24 BZR 4632)-product
// ## Unit = mm, Angle = deg.
// ## Side : F.Cu
// # Ref Val Package PosX PosY Rot Side
// C1 100u Capacitors_SMD:c 128.9050 -52.0700 0.0 F.Cu
// C2 100u Capacitors_SMD:c 93.3450 -77.4700 180.0 F.Cu
// C3 100u Capacitors_SMD:c 67.9450 -77.4700 180.0 F.Cu
Pattern pattern = Pattern.compile("(\\S+)\\s+(.*?)\\s+(.*?)\\s+(-?\\d+\\.\\d+)\\s+(-?\\d+\\.\\d+)\\s+(-?\\d+\\.\\d+)\\s(.*?)");
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.length() == 0 || line.charAt(0) == '#') {
continue;
}
Matcher matcher = pattern.matcher(line);
matcher.matches();
String placementId = matcher.group(1);
String partValue = matcher.group(2);
String pkgName = matcher.group(3);
double placementX = Double.parseDouble(matcher.group(4));
double placementY = Double.parseDouble(matcher.group(5));
double placementRotation = Double.parseDouble(matcher.group(6));
String placementLayer = matcher.group(7);
Placement placement = new Placement(placementId);
placement.setLocation(new Location(
LengthUnit.Millimeters,
placementX,
placementY,
0,
placementRotation));
Configuration cfg = Configuration.get();
if (cfg != null && createMissingParts) {
String partId = pkgName + "-" + partValue;
Part part = cfg.getPart(partId);
if (part == null) {
part = new Part(partId);
Package pkg = cfg.getPackage(pkgName);
if (pkg == null) {
pkg = new Package(pkgName);
cfg.addPackage(pkg);
}
part.setPackage(pkg);
cfg.addPart(part);
}
placement.setPart(part);
}
placement.setSide(side);
placements.add(placement);
}
return placements;
}
class Dlg extends JDialog {
private JTextField textFieldTopFile;
private JTextField textFieldBottomFile;
private final Action browseTopFileAction = new SwingAction();
private final Action browseBottomFileAction = new SwingAction_1();
private final Action importAction = new SwingAction_2();
private final Action cancelAction = new SwingAction_3();
private JCheckBox chckbxCreateMissingParts;
public Dlg(Frame parent) {
super(parent, DESCRIPTION, true);
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
JPanel panel = new JPanel();
panel.setBorder(new TitledBorder(null, "Files", TitledBorder.LEADING, TitledBorder.TOP, null, null));
getContentPane().add(panel);
panel.setLayout(new FormLayout(new ColumnSpec[] {
FormFactory.RELATED_GAP_COLSPEC,
FormFactory.DEFAULT_COLSPEC,
FormFactory.RELATED_GAP_COLSPEC,
ColumnSpec.decode("default:grow"),
FormFactory.RELATED_GAP_COLSPEC,
FormFactory.DEFAULT_COLSPEC,},
new RowSpec[] {
FormFactory.RELATED_GAP_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC,
FormFactory.RELATED_GAP_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC,}));
JLabel lblTopFilemnt = new JLabel("Top File (.pos)");
panel.add(lblTopFilemnt, "2, 2, right, default");
textFieldTopFile = new JTextField();
panel.add(textFieldTopFile, "4, 2, fill, default");
textFieldTopFile.setColumns(10);
JButton btnBrowse = new JButton("Browse");
btnBrowse.setAction(browseTopFileAction);
panel.add(btnBrowse, "6, 2");
JLabel lblBottomFilemnb = new JLabel("Bottom File (.pos)");
panel.add(lblBottomFilemnb, "2, 4, right, default");
textFieldBottomFile = new JTextField();
panel.add(textFieldBottomFile, "4, 4, fill, default");
textFieldBottomFile.setColumns(10);
JButton btnBrowse_1 = new JButton("Browse");
btnBrowse_1.setAction(browseBottomFileAction);
panel.add(btnBrowse_1, "6, 4");
JPanel panel_1 = new JPanel();
panel_1.setBorder(new TitledBorder(null, "Options", TitledBorder.LEADING, TitledBorder.TOP, null, null));
getContentPane().add(panel_1);
panel_1.setLayout(new FormLayout(new ColumnSpec[] {
FormFactory.RELATED_GAP_COLSPEC,
FormFactory.DEFAULT_COLSPEC,},
new RowSpec[] {
FormFactory.RELATED_GAP_ROWSPEC,
FormFactory.DEFAULT_ROWSPEC,}));
chckbxCreateMissingParts = new JCheckBox("Create Missing Parts");
chckbxCreateMissingParts.setSelected(true);
panel_1.add(chckbxCreateMissingParts, "2, 2");
JSeparator separator = new JSeparator();
getContentPane().add(separator);
JPanel panel_2 = new JPanel();
FlowLayout flowLayout = (FlowLayout) panel_2.getLayout();
flowLayout.setAlignment(FlowLayout.RIGHT);
getContentPane().add(panel_2);
JButton btnCancel = new JButton("Cancel");
btnCancel.setAction(cancelAction);
panel_2.add(btnCancel);
JButton btnImport = new JButton("Import");
btnImport.setAction(importAction);
panel_2.add(btnImport);
setSize(400, 400);
setLocationRelativeTo(parent);
JRootPane rootPane = getRootPane();
KeyStroke stroke = KeyStroke.getKeyStroke("ESCAPE");
InputMap inputMap = rootPane
.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputMap.put(stroke, "ESCAPE");
rootPane.getActionMap().put("ESCAPE", cancelAction);
}
private class SwingAction extends AbstractAction {
public SwingAction() {
putValue(NAME, "Browse");
putValue(SHORT_DESCRIPTION, "Browse");
}
public void actionPerformed(ActionEvent e) {
FileDialog fileDialog = new FileDialog(Dlg.this);
fileDialog.setFilenameFilter(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".pos");
}
});
fileDialog.setVisible(true);
if (fileDialog.getFile() == null) {
return;
}
File file = new File(new File(fileDialog.getDirectory()), fileDialog.getFile());
textFieldTopFile.setText(file.getAbsolutePath());
}
}
private class SwingAction_1 extends AbstractAction {
public SwingAction_1() {
putValue(NAME, "Browse");
putValue(SHORT_DESCRIPTION, "Browse");
}
public void actionPerformed(ActionEvent e) {
FileDialog fileDialog = new FileDialog(Dlg.this);
fileDialog.setFilenameFilter(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".pos");
}
});
fileDialog.setVisible(true);
if (fileDialog.getFile() == null) {
return;
}
File file = new File(new File(fileDialog.getDirectory()), fileDialog.getFile());
textFieldBottomFile.setText(file.getAbsolutePath());
}
}
private class SwingAction_2 extends AbstractAction {
public SwingAction_2() {
putValue(NAME, "Import");
putValue(SHORT_DESCRIPTION, "Import");
}
public void actionPerformed(ActionEvent e) {
topFile = new File(textFieldTopFile.getText());
bottomFile = new File(textFieldBottomFile.getText());
board = new Board();
List<Placement> placements = new ArrayList<Placement>();
try {
if (topFile.exists()) {
placements.addAll(parseFile(topFile, Side.Top, chckbxCreateMissingParts.isSelected()));
}
if (bottomFile.exists()) {
placements.addAll(parseFile(bottomFile, Side.Bottom, chckbxCreateMissingParts.isSelected()));
}
}
catch (Exception e1) {
MessageBoxes.errorBox(Dlg.this, "Import Error", e1);
return;
}
for (Placement placement : placements) {
board.addPlacement(placement);
}
setVisible(false);
}
}
private class SwingAction_3 extends AbstractAction {
public SwingAction_3() {
putValue(NAME, "Cancel");
putValue(SHORT_DESCRIPTION, "Cancel");
}
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
}
}
}