package org.openpnp.machine.reference; import java.awt.event.ActionEvent; import java.util.ArrayList; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JOptionPane; import org.openpnp.ConfigurationListener; import org.openpnp.gui.MainFrame; import org.openpnp.gui.support.Icons; import org.openpnp.gui.support.PropertySheetWizardAdapter; import org.openpnp.gui.support.Wizard; import org.openpnp.machine.reference.psh.NozzleTipsPropertySheetHolder; import org.openpnp.machine.reference.wizards.ReferenceNozzleConfigurationWizard; import org.openpnp.model.Configuration; import org.openpnp.model.Length; import org.openpnp.model.LengthUnit; import org.openpnp.model.Location; import org.openpnp.model.Part; import org.openpnp.spi.NozzleTip; import org.openpnp.spi.PropertySheetHolder; import org.openpnp.spi.base.AbstractNozzle; import org.openpnp.util.MovableUtils; import org.pmw.tinylog.Logger; import org.simpleframework.xml.Attribute; import org.simpleframework.xml.Element; public class ReferenceNozzle extends AbstractNozzle implements ReferenceHeadMountable { @Element private Location headOffsets = new Location(LengthUnit.Millimeters); @Attribute(required = false) private int pickDwellMilliseconds; @Attribute(required = false) private int placeDwellMilliseconds; @Attribute(required = false) private String currentNozzleTipId; @Attribute(required = false) private boolean changerEnabled = false; @Element(required = false) protected Length safeZ = new Length(0, LengthUnit.Millimeters); /** * If limitRotation is enabled the nozzle will reverse directions when commanded to rotate past * 180 degrees. So, 190 degrees becomes -170 and -190 becomes 170. */ @Attribute(required = false) private boolean limitRotation = true; protected ReferenceNozzleTip nozzleTip; public ReferenceNozzle() { Configuration.get().addListener(new ConfigurationListener.Adapter() { @Override public void configurationLoaded(Configuration configuration) throws Exception { nozzleTip = (ReferenceNozzleTip) nozzleTips.get(currentNozzleTipId); } }); } public boolean isLimitRotation() { return limitRotation; } public void setLimitRotation(boolean limitRotation) { this.limitRotation = limitRotation; } public int getPickDwellMilliseconds() { return pickDwellMilliseconds; } public void setPickDwellMilliseconds(int pickDwellMilliseconds) { this.pickDwellMilliseconds = pickDwellMilliseconds; } public int getPlaceDwellMilliseconds() { return placeDwellMilliseconds; } public void setPlaceDwellMilliseconds(int placeDwellMilliseconds) { this.placeDwellMilliseconds = placeDwellMilliseconds; } @Override public Location getHeadOffsets() { return headOffsets; } @Override public void setHeadOffsets(Location headOffsets) { this.headOffsets = headOffsets; } @Override public ReferenceNozzleTip getNozzleTip() { return nozzleTip; } @Override public void pick(Part part) throws Exception { Logger.debug("{}.pick()", getName()); if (part == null) { throw new Exception("Can't pick null part"); } if (nozzleTip == null) { throw new Exception("Can't pick, no nozzle tip loaded"); } this.part = part; getDriver().pick(this); getMachine().fireMachineHeadActivity(head); Thread.sleep(pickDwellMilliseconds); } @Override public void place() throws Exception { Logger.debug("{}.place()", getName()); if (nozzleTip == null) { throw new Exception("Can't place, no nozzle tip loaded"); } getDriver().place(this); this.part = null; getMachine().fireMachineHeadActivity(head); Thread.sleep(placeDwellMilliseconds); } @Override public void moveTo(Location location, double speed) throws Exception { // Shortcut Double.NaN. Sending Double.NaN in a Location is an old API that should no // longer be used. It will be removed eventually: // https://github.com/openpnp/openpnp/issues/255 // In the mean time, since Double.NaN would cause a problem for calibration, we shortcut // it here by replacing any NaN values with the current value from the driver. Location currentLocation = getLocation().convertToUnits(location.getUnits()); if (Double.isNaN(location.getX())) { location = location.derive(currentLocation.getX(), null, null, null); } if (Double.isNaN(location.getY())) { location = location.derive(null, currentLocation.getY(), null, null); } if (Double.isNaN(location.getZ())) { location = location.derive(null, null, currentLocation.getZ(), null); } if (Double.isNaN(location.getRotation())) { location = location.derive(null, null, null, currentLocation.getRotation()); } // Check calibration. if (nozzleTip != null && nozzleTip.getCalibration().isCalibrationNeeded()) { Logger.debug("NozzleTip is not yet calibrated, calibrating now."); nozzleTip.getCalibration().calibrate(nozzleTip); } // If there is a part on the nozzle we take the incoming speed value // to be a percentage of the part's speed instead of a percentage of // the max speed. if (getPart() != null) { speed = part.getSpeed() * speed; } Logger.debug("{}.moveTo({}, {})", getName(), location, speed); if (limitRotation && !Double.isNaN(location.getRotation()) && Math.abs(location.getRotation()) > 180) { if (location.getRotation() < 0) { location = location.derive(null, null, null, location.getRotation() + 360); } else { location = location.derive(null, null, null, location.getRotation() - 360); } } if (nozzleTip != null && nozzleTip.getCalibration().isCalibrated()) { location = location .subtract(nozzleTip.getCalibration().getCalibratedOffset(location.getRotation())); Logger.debug("{}.moveTo({}, {}) (corrected)", getName(), location, speed); } getDriver().moveTo(this, location, speed); getMachine().fireMachineHeadActivity(head); } @Override public void moveToSafeZ(double speed) throws Exception { // If there is a part on the nozzle we take the incoming speed value // to be a percentage of the part's speed instead of a percentage of // the max speed. if (getPart() != null) { speed = part.getSpeed() * speed; } Logger.debug("{}.moveToSafeZ({})", getName(), speed); Length safeZ = this.safeZ.convertToUnits(getLocation().getUnits()); Location l = new Location(getLocation().getUnits(), Double.NaN, Double.NaN, safeZ.getValue(), Double.NaN); getDriver().moveTo(this, l, speed); getMachine().fireMachineHeadActivity(head); } @Override public void loadNozzleTip(NozzleTip nozzleTip) throws Exception { if (this.nozzleTip == nozzleTip) { return; } if (!changerEnabled) { throw new Exception("Can't load nozzle tip, nozzle tip changer is not enabled."); } unloadNozzleTip(); Logger.debug("{}.loadNozzleTip({}): Start", getName(), nozzleTip.getName()); ReferenceNozzleTip nt = (ReferenceNozzleTip) nozzleTip; Logger.debug("{}.loadNozzleTip({}): moveTo Start Location", new Object[] {getName(), nozzleTip.getName()}); MovableUtils.moveToLocationAtSafeZ(this, nt.getChangerStartLocation()); Logger.debug("{}.loadNozzleTip({}): moveTo Mid Location", new Object[] {getName(), nozzleTip.getName()}); moveTo(nt.getChangerMidLocation(), getHead().getMachine().getSpeed() * 0.25); Logger.debug("{}.loadNozzleTip({}): moveTo Mid Location 2", new Object[] {getName(), nozzleTip.getName()}); moveTo(nt.getChangerMidLocation2(), getHead().getMachine().getSpeed()); Logger.debug("{}.loadNozzleTip({}): moveTo End Location", new Object[] {getName(), nozzleTip.getName()}); moveTo(nt.getChangerEndLocation(), getHead().getMachine().getSpeed()); moveToSafeZ(getHead().getMachine().getSpeed()); Logger.debug("{}.loadNozzleTip({}): Finished", new Object[] {getName(), nozzleTip.getName()}); this.nozzleTip = (ReferenceNozzleTip) nozzleTip; this.nozzleTip.getCalibration().reset(); currentNozzleTipId = nozzleTip.getId(); } @Override public void unloadNozzleTip() throws Exception { if (nozzleTip == null) { return; } if (!changerEnabled) { throw new Exception("Can't unload nozzle tip, nozzle tip changer is not enabled."); } Logger.debug("{}.unloadNozzleTip(): Start", getName()); ReferenceNozzleTip nt = (ReferenceNozzleTip) nozzleTip; Logger.debug("{}.unloadNozzleTip(): moveTo End Location", getName()); MovableUtils.moveToLocationAtSafeZ(this, nt.getChangerEndLocation()); Logger.debug("{}.unloadNozzleTip(): moveTo Mid Location 2", getName()); moveTo(nt.getChangerMidLocation2(), getHead().getMachine().getSpeed()); Logger.debug("{}.unloadNozzleTip(): moveTo Mid Location", getName()); moveTo(nt.getChangerMidLocation(), getHead().getMachine().getSpeed()); Logger.debug("{}.unloadNozzleTip(): moveTo Start Location", getName()); moveTo(nt.getChangerStartLocation(), getHead().getMachine().getSpeed() * 0.25); moveToSafeZ(getHead().getMachine().getSpeed()); Logger.debug("{}.unloadNozzleTip(): Finished", getName()); nozzleTip = null; currentNozzleTipId = null; } @Override public Location getLocation() { Location location = getDriver().getLocation(this); if (nozzleTip != null && nozzleTip.getCalibration().isCalibrated()) { Location offset = nozzleTip.getCalibration().getCalibratedOffset(location.getRotation()); location = location.add(offset); } return location; } public boolean isChangerEnabled() { return changerEnabled; } public void setChangerEnabled(boolean changerEnabled) { this.changerEnabled = changerEnabled; } @Override public Wizard getConfigurationWizard() { return new ReferenceNozzleConfigurationWizard(this); } @Override public String getPropertySheetHolderTitle() { return getClass().getSimpleName() + " " + getName(); } @Override public PropertySheetHolder[] getChildPropertySheetHolders() { ArrayList<PropertySheetHolder> children = new ArrayList<>(); children.add(new NozzleTipsPropertySheetHolder(this, "Nozzle Tips", getNozzleTips(), null)); return children.toArray(new PropertySheetHolder[] {}); } @Override public PropertySheet[] getPropertySheets() { return new PropertySheet[] {new PropertySheetWizardAdapter(getConfigurationWizard())}; } @Override public Action[] getPropertySheetHolderActions() { return new Action[] { deleteAction }; } public Action deleteAction = new AbstractAction("Delete Nozzle") { { putValue(SMALL_ICON, Icons.nozzleRemove); putValue(NAME, "Delete Nozzle"); putValue(SHORT_DESCRIPTION, "Delete the currently selected nozzle."); } @Override public void actionPerformed(ActionEvent arg0) { int ret = JOptionPane.showConfirmDialog(MainFrame.get(), "Are you sure you want to delete " + getName() + "?", "Delete " + getName() + "?", JOptionPane.YES_NO_OPTION); if (ret == JOptionPane.YES_OPTION) { getHead().removeNozzle(ReferenceNozzle.this); } } }; @Override public String toString() { return getName() + " " + getId(); } public Length getSafeZ() { return safeZ; } public void setSafeZ(Length safeZ) { this.safeZ = safeZ; } @Override public void moveTo(Location location) throws Exception { moveTo(location, getHead().getMachine().getSpeed()); } @Override public void moveToSafeZ() throws Exception { moveToSafeZ(getHead().getMachine().getSpeed()); } ReferenceDriver getDriver() { return getMachine().getDriver(); } ReferenceMachine getMachine() { return (ReferenceMachine) Configuration.get().getMachine(); } }