/* * 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.machine.reference.feeder.wizards; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.border.EtchedBorder; import javax.swing.border.TitledBorder; import org.jdesktop.beansbinding.AutoBinding.UpdateStrategy; import org.openpnp.gui.MainFrame; import org.openpnp.gui.components.CameraView; import org.openpnp.gui.components.CameraViewActionEvent; import org.openpnp.gui.components.CameraViewActionListener; import org.openpnp.gui.components.CameraViewFilter; import org.openpnp.gui.components.ComponentDecorators; import org.openpnp.gui.components.LocationButtonsPanel; import org.openpnp.gui.support.AbstractConfigurationWizard; import org.openpnp.gui.support.DoubleConverter; import org.openpnp.gui.support.Helpers; import org.openpnp.gui.support.IdentifiableListCellRenderer; import org.openpnp.gui.support.IntegerConverter; import org.openpnp.gui.support.LengthConverter; import org.openpnp.gui.support.MessageBoxes; import org.openpnp.gui.support.MutableLocationProxy; import org.openpnp.gui.support.PartsComboBoxModel; import org.openpnp.machine.reference.feeder.ReferenceStripFeeder; import org.openpnp.machine.reference.feeder.ReferenceStripFeeder.TapeType; import org.openpnp.model.Configuration; import org.openpnp.model.Length; import org.openpnp.model.Location; import org.openpnp.model.Part; import org.openpnp.spi.Camera; import org.openpnp.util.VisionUtils; import org.openpnp.vision.FluentCv; import com.google.common.collect.Lists; import com.google.common.util.concurrent.FutureCallback; import com.jgoodies.forms.layout.ColumnSpec; import com.jgoodies.forms.layout.FormLayout; import com.jgoodies.forms.layout.FormSpecs; import com.jgoodies.forms.layout.RowSpec; @SuppressWarnings("serial") public class ReferenceStripFeederConfigurationWizard extends AbstractConfigurationWizard { private final ReferenceStripFeeder feeder; private JPanel panelPart; private JComboBox comboBoxPart; private JTextField textFieldFeedStartX; private JTextField textFieldFeedStartY; private JTextField textFieldFeedStartZ; private JTextField textFieldFeedEndX; private JTextField textFieldFeedEndY; private JTextField textFieldFeedEndZ; private JTextField textFieldTapeWidth; private JLabel lblPartPitch; private JTextField textFieldPartPitch; private JPanel panelTapeSettings; private JPanel panelLocations; private LocationButtonsPanel locationButtonsPanelFeedStart; private LocationButtonsPanel locationButtonsPanelFeedEnd; private JLabel lblFeedCount; private JTextField textFieldFeedCount; private JButton btnResetFeedCount; private JLabel lblTapeType; private JComboBox comboBoxTapeType; private JLabel lblRotationInTape; private JTextField textFieldLocationRotation; private JButton btnAutoSetup; private Location firstPartLocation; private Location secondPartLocation; private List<Location> part1HoleLocations; private Camera autoSetupCamera; public ReferenceStripFeederConfigurationWizard(ReferenceStripFeeder feeder) { this.feeder = feeder; panelPart = new JPanel(); panelPart.setBorder( new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null), "General Settings", TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0))); contentPanel.add(panelPart); panelPart.setLayout(new FormLayout(new ColumnSpec[] { FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC,}, new RowSpec[] { FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC, FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC, FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC,})); try { } catch (Throwable t) { // Swallow this error. This happens during parsing in // in WindowBuilder but doesn't happen during normal run. } lblPart = new JLabel("Part"); panelPart.add(lblPart, "2, 2, right, default"); comboBoxPart = new JComboBox(); comboBoxPart.setModel(new PartsComboBoxModel()); comboBoxPart.setRenderer(new IdentifiableListCellRenderer<Part>()); panelPart.add(comboBoxPart, "4, 2, left, default"); lblRotationInTape = new JLabel("Rotation In Tape"); panelPart.add(lblRotationInTape, "2, 4, left, default"); textFieldLocationRotation = new JTextField(); panelPart.add(textFieldLocationRotation, "4, 4, fill, default"); textFieldLocationRotation.setColumns(4); lblRetryCount = new JLabel("Retry Count"); panelPart.add(lblRetryCount, "2, 6, right, default"); retryCountTf = new JTextField(); retryCountTf.setText("3"); panelPart.add(retryCountTf, "4, 6, fill, default"); retryCountTf.setColumns(3); panelTapeSettings = new JPanel(); contentPanel.add(panelTapeSettings); panelTapeSettings.setBorder(new TitledBorder( new EtchedBorder(EtchedBorder.LOWERED, null, null), "Tape Settings", TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0))); panelTapeSettings.setLayout(new FormLayout( new ColumnSpec[] {FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC,}, new RowSpec[] {FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC, FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC, FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC, FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC,})); btnAutoSetup = new JButton(autoSetup); panelTapeSettings.add(btnAutoSetup, "2, 2, 11, 1"); lblTapeType = new JLabel("Tape Type"); panelTapeSettings.add(lblTapeType, "2, 4, right, default"); comboBoxTapeType = new JComboBox(TapeType.values()); panelTapeSettings.add(comboBoxTapeType, "4, 4, fill, default"); JLabel lblTapeWidth = new JLabel("Tape Width"); panelTapeSettings.add(lblTapeWidth, "8, 4, right, default"); textFieldTapeWidth = new JTextField(); panelTapeSettings.add(textFieldTapeWidth, "10, 4"); textFieldTapeWidth.setColumns(5); lblPartPitch = new JLabel("Part Pitch"); panelTapeSettings.add(lblPartPitch, "2, 6, right, default"); textFieldPartPitch = new JTextField(); panelTapeSettings.add(textFieldPartPitch, "4, 6"); textFieldPartPitch.setColumns(5); lblFeedCount = new JLabel("Feed Count"); panelTapeSettings.add(lblFeedCount, "8, 6, right, default"); textFieldFeedCount = new JTextField(); panelTapeSettings.add(textFieldFeedCount, "10, 6"); textFieldFeedCount.setColumns(10); btnResetFeedCount = new JButton(new AbstractAction("Reset") { @Override public void actionPerformed(ActionEvent e) { textFieldFeedCount.setText("0"); applyAction.actionPerformed(e); } }); panelTapeSettings.add(btnResetFeedCount, "12, 6"); lblUseVision = new JLabel("Use Vision?"); panelTapeSettings.add(lblUseVision, "2, 8"); chckbxUseVision = new JCheckBox(""); panelTapeSettings.add(chckbxUseVision, "4, 8"); panelLocations = new JPanel(); contentPanel.add(panelLocations); panelLocations.setBorder(new TitledBorder(null, "Locations", TitledBorder.LEADING, TitledBorder.TOP, null, null)); panelLocations.setLayout(new FormLayout( new ColumnSpec[] {FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, FormSpecs.DEFAULT_COLSPEC, FormSpecs.RELATED_GAP_COLSPEC, ColumnSpec.decode("left:default:grow"),}, new RowSpec[] {FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC, FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC, FormSpecs.RELATED_GAP_ROWSPEC, FormSpecs.DEFAULT_ROWSPEC,})); JLabel lblX = new JLabel("X"); panelLocations.add(lblX, "4, 2"); JLabel lblY = new JLabel("Y"); panelLocations.add(lblY, "6, 2"); JLabel lblZ_1 = new JLabel("Z"); panelLocations.add(lblZ_1, "8, 2"); JLabel lblFeedStartLocation = new JLabel("Reference Hole Location"); lblFeedStartLocation.setToolTipText( "The location of the first tape hole past the first part in the direction of more parts."); panelLocations.add(lblFeedStartLocation, "2, 4, right, default"); textFieldFeedStartX = new JTextField(); panelLocations.add(textFieldFeedStartX, "4, 4"); textFieldFeedStartX.setColumns(8); textFieldFeedStartY = new JTextField(); panelLocations.add(textFieldFeedStartY, "6, 4"); textFieldFeedStartY.setColumns(8); textFieldFeedStartZ = new JTextField(); panelLocations.add(textFieldFeedStartZ, "8, 4"); textFieldFeedStartZ.setColumns(8); locationButtonsPanelFeedStart = new LocationButtonsPanel(textFieldFeedStartX, textFieldFeedStartY, textFieldFeedStartZ, null); panelLocations.add(locationButtonsPanelFeedStart, "10, 4"); JLabel lblFeedEndLocation = new JLabel("Next Hole Location"); lblFeedEndLocation.setToolTipText( "The location of another hole after the reference hole. This can be any hole along the tape as long as it's past the reference hole."); panelLocations.add(lblFeedEndLocation, "2, 6, right, default"); textFieldFeedEndX = new JTextField(); panelLocations.add(textFieldFeedEndX, "4, 6"); textFieldFeedEndX.setColumns(8); textFieldFeedEndY = new JTextField(); panelLocations.add(textFieldFeedEndY, "6, 6"); textFieldFeedEndY.setColumns(8); textFieldFeedEndZ = new JTextField(); panelLocations.add(textFieldFeedEndZ, "8, 6"); textFieldFeedEndZ.setColumns(8); locationButtonsPanelFeedEnd = new LocationButtonsPanel(textFieldFeedEndX, textFieldFeedEndY, textFieldFeedEndZ, null); panelLocations.add(locationButtonsPanelFeedEnd, "10, 6"); } @Override public void createBindings() { LengthConverter lengthConverter = new LengthConverter(); IntegerConverter intConverter = new IntegerConverter(); DoubleConverter doubleConverter = new DoubleConverter(Configuration.get().getLengthDisplayFormat()); MutableLocationProxy location = new MutableLocationProxy(); bind(UpdateStrategy.READ_WRITE, feeder, "location", location, "location"); addWrappedBinding(location, "rotation", textFieldLocationRotation, "text", doubleConverter); addWrappedBinding(feeder, "part", comboBoxPart, "selectedItem"); addWrappedBinding(feeder, "retryCount", retryCountTf, "text", intConverter); addWrappedBinding(feeder, "tapeType", comboBoxTapeType, "selectedItem"); addWrappedBinding(feeder, "tapeWidth", textFieldTapeWidth, "text", lengthConverter); addWrappedBinding(feeder, "partPitch", textFieldPartPitch, "text", lengthConverter); addWrappedBinding(feeder, "feedCount", textFieldFeedCount, "text", intConverter); MutableLocationProxy feedStartLocation = new MutableLocationProxy(); bind(UpdateStrategy.READ_WRITE, feeder, "referenceHoleLocation", feedStartLocation, "location"); addWrappedBinding(feedStartLocation, "lengthX", textFieldFeedStartX, "text", lengthConverter); addWrappedBinding(feedStartLocation, "lengthY", textFieldFeedStartY, "text", lengthConverter); addWrappedBinding(feedStartLocation, "lengthZ", textFieldFeedStartZ, "text", lengthConverter); MutableLocationProxy feedEndLocation = new MutableLocationProxy(); bind(UpdateStrategy.READ_WRITE, feeder, "lastHoleLocation", feedEndLocation, "location"); addWrappedBinding(feedEndLocation, "lengthX", textFieldFeedEndX, "text", lengthConverter); addWrappedBinding(feedEndLocation, "lengthY", textFieldFeedEndY, "text", lengthConverter); addWrappedBinding(feedEndLocation, "lengthZ", textFieldFeedEndZ, "text", lengthConverter); addWrappedBinding(feeder, "visionEnabled", chckbxUseVision, "selected"); ComponentDecorators.decorateWithAutoSelect(textFieldLocationRotation); ComponentDecorators.decorateWithAutoSelectAndLengthConversion(textFieldTapeWidth); ComponentDecorators.decorateWithAutoSelect(retryCountTf); ComponentDecorators.decorateWithAutoSelectAndLengthConversion(textFieldPartPitch); ComponentDecorators.decorateWithAutoSelect(textFieldFeedCount); ComponentDecorators.decorateWithAutoSelectAndLengthConversion(textFieldFeedStartX); ComponentDecorators.decorateWithAutoSelectAndLengthConversion(textFieldFeedStartY); ComponentDecorators.decorateWithAutoSelectAndLengthConversion(textFieldFeedStartZ); ComponentDecorators.decorateWithAutoSelectAndLengthConversion(textFieldFeedEndX); ComponentDecorators.decorateWithAutoSelectAndLengthConversion(textFieldFeedEndY); ComponentDecorators.decorateWithAutoSelectAndLengthConversion(textFieldFeedEndZ); } private Action autoSetup = new AbstractAction("Auto Setup") { @Override public void actionPerformed(ActionEvent e) { try { autoSetupCamera = Configuration.get().getMachine().getDefaultHead().getDefaultCamera(); } catch (Exception ex) { MessageBoxes.errorBox(getTopLevelAncestor(), "Auto Setup Failure", ex); return; } btnAutoSetup.setAction(autoSetupCancel); CameraView cameraView = MainFrame.get().getCameraViews().getCameraView(autoSetupCamera); cameraView.addActionListener(autoSetupPart1Clicked); cameraView.setText("Click on the center of the first part in the tape."); cameraView.flash(); final boolean showDetails = (e.getModifiers() & ActionEvent.ALT_MASK) != 0; cameraView.setCameraViewFilter(new CameraViewFilter() { @Override public BufferedImage filterCameraImage(Camera camera, BufferedImage image) { return showHoles(camera, image, showDetails); } }); } }; private Action autoSetupCancel = new AbstractAction("Cancel Auto Setup") { @Override public void actionPerformed(ActionEvent e) { btnAutoSetup.setAction(autoSetup); CameraView cameraView = MainFrame.get().getCameraViews().getCameraView(autoSetupCamera); cameraView.setText(null); cameraView.setCameraViewFilter(null); cameraView.removeActionListener(autoSetupPart1Clicked); cameraView.removeActionListener(autoSetupPart2Clicked); } }; private CameraViewActionListener autoSetupPart1Clicked = new CameraViewActionListener() { @Override public void actionPerformed(final CameraViewActionEvent action) { firstPartLocation = action.getLocation(); final CameraView cameraView = MainFrame.get().getCameraViews().getCameraView(autoSetupCamera); cameraView.removeActionListener(this); Configuration.get().getMachine().submit(new Callable<Void>() { public Void call() throws Exception { cameraView.setText("Checking first part..."); autoSetupCamera.moveTo(action.getLocation()); part1HoleLocations = findHoles(autoSetupCamera); cameraView.setText("Now click on the center of the second part in the tape."); cameraView.flash(); cameraView.addActionListener(autoSetupPart2Clicked); return null; } }, new FutureCallback<Void>() { @Override public void onSuccess(Void result) {} @Override public void onFailure(final Throwable t) { SwingUtilities.invokeLater(new Runnable() { public void run() { autoSetupCancel.actionPerformed(null); MessageBoxes.errorBox(getTopLevelAncestor(), "Auto Setup Failure", t); } }); } }); } }; private CameraViewActionListener autoSetupPart2Clicked = new CameraViewActionListener() { @Override public void actionPerformed(final CameraViewActionEvent action) { secondPartLocation = action.getLocation(); final CameraView cameraView = MainFrame.get().getCameraViews().getCameraView(autoSetupCamera); cameraView.removeActionListener(this); Configuration.get().getMachine().submit(new Callable<Void>() { public Void call() throws Exception { cameraView.setText("Checking second part..."); autoSetupCamera.moveTo(action.getLocation()); List<Location> part2HoleLocations = findHoles(autoSetupCamera); List<Location> referenceHoles = deriveReferenceHoles(part1HoleLocations, part2HoleLocations); final Location referenceHole1 = referenceHoles.get(0).derive(null, null, null, 0d); final Location referenceHole2 = referenceHoles.get(1).derive(null, null, null, 0d); feeder.setReferenceHoleLocation(referenceHole1); feeder.setLastHoleLocation(referenceHole2); Length partPitch = firstPartLocation.getLinearLengthTo(secondPartLocation); partPitch.setValue(2 * (Math.round(partPitch.getValue() / 2))); final Length partPitch_ = partPitch; SwingUtilities.invokeLater(new Runnable() { public void run() { Helpers.copyLocationIntoTextFields(referenceHole1, textFieldFeedStartX, textFieldFeedStartY, null); Helpers.copyLocationIntoTextFields(referenceHole2, textFieldFeedEndX, textFieldFeedEndY, null); textFieldPartPitch.setText(partPitch_.getValue() + ""); } }); feeder.setFeedCount(1); autoSetupCamera.moveTo(feeder.getPickLocation()); feeder.setFeedCount(0); cameraView.setText("Setup complete!"); Thread.sleep(1500); cameraView.setText(null); cameraView.setCameraViewFilter(null); btnAutoSetup.setAction(autoSetup); return null; } }, new FutureCallback<Void>() { @Override public void onSuccess(Void result) {} @Override public void onFailure(final Throwable t) { SwingUtilities.invokeLater(new Runnable() { public void run() { autoSetupCancel.actionPerformed(null); MessageBoxes.errorBox(getTopLevelAncestor(), "Auto Setup Failure", t); } }); } }); } }; private List<Location> findHoles(Camera camera) { List<Location> holeLocations = new ArrayList<>(); new FluentCv().setCamera(camera).settleAndCapture().toGray() .blurGaussian(feeder.getHoleBlurKernelSize()) .findCirclesHough(feeder.getHoleDiameterMin(), feeder.getHoleDiameterMax(), feeder.getHolePitchMin()) .filterCirclesByDistance(feeder.getHoleDistanceMin(), feeder.getHoleDistanceMax()) .filterCirclesToLine(feeder.getHoleLineDistanceMax()) .convertCirclesToLocations(holeLocations); return holeLocations; } /** * Show candidate holes in the image. Red are any holes that are found. Blue is holes that * passed the distance check but failed the line check. Green passed all checks and are good. * * @param camera * @param image * @return */ private BufferedImage showHoles(Camera camera, BufferedImage image, boolean showDetails) { if (showDetails) { return new FluentCv().setCamera(camera).toMat(image, "original").toGray() .blurGaussian(feeder.getHoleBlurKernelSize()) .findCirclesHough(feeder.getHoleDiameterMin(), feeder.getHoleDiameterMax(), feeder.getHolePitchMin(), "houghUnfiltered") .drawCircles("original", Color.red, "unfiltered").recall("houghUnfiltered") .filterCirclesByDistance(feeder.getHoleDistanceMin(), feeder.getHoleDistanceMax(), "houghDistanceFiltered") .drawCircles("unfiltered", Color.blue, "distanceFiltered") .recall("houghDistanceFiltered") .filterCirclesToLine(feeder.getHoleLineDistanceMax()) .drawCircles("distanceFiltered", Color.green).toBufferedImage(); } else { return new FluentCv().setCamera(camera).toMat(image, "original").toGray() .blurGaussian(feeder.getHoleBlurKernelSize()) .findCirclesHough(feeder.getHoleDiameterMin(), feeder.getHoleDiameterMax(), feeder.getHolePitchMin()) .filterCirclesByDistance(feeder.getHoleDistanceMin(), feeder.getHoleDistanceMax()) .filterCirclesToLine(feeder.getHoleLineDistanceMax()) .drawCircles("original", Color.green).toBufferedImage(); } } private List<Location> deriveReferenceHoles(List<Location> part1HoleLocations, List<Location> part2HoleLocations) { // We are only interested in the pair of holes closest to each part part1HoleLocations = part1HoleLocations.subList(0, Math.min(2, part1HoleLocations.size())); part2HoleLocations = part2HoleLocations.subList(0, Math.min(2, part2HoleLocations.size())); // Part 1's reference hole is the one closest to either of part 2's holes. Location part1ReferenceHole = VisionUtils .sortLocationsByDistance(part2HoleLocations.get(0), part1HoleLocations).get(0); // Part 2's reference hole is the one farthest from part 1's reference hole. Location part2ReferenceHole = Lists .reverse( VisionUtils.sortLocationsByDistance(part1ReferenceHole, part2HoleLocations)) .get(0); List<Location> referenceHoles = new ArrayList<>(); referenceHoles.add(part1ReferenceHole); referenceHoles.add(part2ReferenceHole); return referenceHoles; } private JCheckBox chckbxUseVision; private JLabel lblUseVision; private JLabel lblPart; private JLabel lblRetryCount; private JTextField retryCountTf; }