diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodePreprocessorUtils.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodePreprocessorUtils.java index 9b3f26cc1..48dcc0590 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodePreprocessorUtils.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodePreprocessorUtils.java @@ -277,11 +277,11 @@ static public String generateLineFromPoints(final Code command, final Position s } if (!Double.isNaN(end.y)) { sb.append("Y"); - sb.append(df.format(end.y-start.x)); + sb.append(df.format(end.y-start.y)); } if (!Double.isNaN(end.z)) { sb.append("Z"); - sb.append(df.format(end.z-start.x)); + sb.append(df.format(end.z-start.z)); } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/LineSplitter.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/LineSplitter.java index 58bf3db47..d3e6230ae 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/LineSplitter.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/LineSplitter.java @@ -84,8 +84,12 @@ public List processCommand(String commandString, GcodeState state) throw GcodeMeta command = Iterables.getLast(commands); - if (command == null || command.point == null) { - throw new GcodeParserException("Internal parser error: missing data."); + if (command == null) { + throw new GcodeParserException("Internal parser error: missing data. " + commandString); + } + if (command.point == null) { + // No point data associated with this command (Maybe just setting feed rate), leave it as-is + return Collections.singletonList(commandString); } // line length diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MeshLeveler.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MeshLeveler.java index d3e9507e1..6fe8da305 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MeshLeveler.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MeshLeveler.java @@ -42,8 +42,6 @@ public class MeshLeveler implements CommandProcessor { final private int xLen, yLen; final private double resolution; - // Used during processing. - private double lastZHeight; private Units unit; public final static String ERROR_MESH_SHAPE= "Surface mesh must be a rectangular 2D array."; @@ -54,10 +52,10 @@ public class MeshLeveler implements CommandProcessor { public final static String ERROR_X_ASCENTION = "Found a x coordinate that isn't ascending."; public final static String ERROR_UNEXPECTED_ARC = "The mesh leveler cannot process arcs. Enable the arc expander."; - public final static String ERROR_MISSING_POINT_DATA = "Internal parser error: missing data."; + public final static String ERROR_MISSING_POINT_DATA = "Internal parser error: missing data. "; /** - * @param materialSurfaceHeight Z height used in offset. + * @param materialSurfaceHeightMM Z height used in offset. * @param surfaceMesh 2D array in the format Position[x][y] */ public MeshLeveler(double materialSurfaceHeightMM, Position[][] surfaceMesh, Units unit) { @@ -155,30 +153,36 @@ public List processCommand(final String commandString, GcodeState state) GcodeMeta command = commands.get(0); - if (command == null || command.point == null) { - throw new GcodeParserException(ERROR_MISSING_POINT_DATA); + if (command == null) { + throw new GcodeParserException(ERROR_MISSING_POINT_DATA + commandString); + } + if (command.point == null) { + return Collections.singletonList(commandString); } Position start = state.currentPoint; Position end = command.point.point(); - if (start.z != end.z) { - this.lastZHeight = end.z; - } - // Get offset relative to the expected surface height. // Visualizer normalizes everything to MM but probe mesh might be INCH double probeScaleFactor = UnitUtils.scaleUnits(UnitUtils.Units.MM, this.unit); double zScaleFactor = UnitUtils.scaleUnits(UnitUtils.Units.MM, state.isMetric ? Units.MM : Units.INCH); - double zPointOffset = - surfaceHeightAt(end.x / zScaleFactor, end.y / zScaleFactor) - - (this.materialSurfaceHeight / probeScaleFactor); - zPointOffset *= zScaleFactor; + double zPointOffset; + if (state.inAbsoluteMode) { + zPointOffset = surfaceHeightAt(end.x, end.y, zScaleFactor) - (this.materialSurfaceHeight / probeScaleFactor); + } else { + // TODO: If the first move in the gcode file is relative it won't properly take the materialSurfaceHeight + // into account. To fix the CommandProcessor needs to inject an adjustment before that first relative move + // happens. Until that happens the user must make sure the materialSurfaceHeight is zero. + + // In relative mode we only need to adjust by the z delta between the starting and ending point + zPointOffset = surfaceHeightAt(end.x, end.y, zScaleFactor) - surfaceHeightAt(start.x, start.y, zScaleFactor); + } + zPointOffset *= zScaleFactor; // Update z coordinate. - end.z = this.lastZHeight + zPointOffset; - //end.z /= resultScaleFactor; + end.z += zPointOffset; String adjustedCommand = GcodePreprocessorUtils.generateLineFromPoints( command.code, start, end, command.state.inAbsoluteMode, null); @@ -214,7 +218,9 @@ protected Position[][] findBoundingArea(double x, double y) throws GcodeParserEx * Bilinear interpolation: * http://supercomputingblog.com/graphics/coding-bilinear-interpolation/ */ - protected double surfaceHeightAt(double x, double y) throws GcodeParserException { + protected double surfaceHeightAt(double unscaledX, double unscaledY, double zScaleFactor) throws GcodeParserException { + double x = unscaledX / zScaleFactor; + double y = unscaledY / zScaleFactor; Position[][] q = findBoundingArea(x, y); Position Q11 = q[0][0]; diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java b/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java index e27811671..4308d2f1a 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java @@ -424,7 +424,7 @@ public void applyGcodeParser(GcodeParser parser) throws Exception { @Override public void applyCommandProcessor(CommandProcessor commandProcessor) throws Exception { - logger.log(Level.INFO, "Applying new command processor: {0}", commandProcessor.getClass().getSimpleName()); + logger.log(Level.INFO, String.format("Applying new command processor %s", commandProcessor.getClass().getSimpleName())); gcp.addCommandProcessor(commandProcessor); if (gcodeFile != null) { @@ -450,13 +450,13 @@ public File getGcodeFile() { @Override public File getProcessedGcodeFile() { - logger.log(Level.FINEST, "Getting processed gcode file."); + logger.log(Level.INFO, String.format("Getting processed gcode file (%s).", this.processedGcodeFile)); return this.processedGcodeFile; } @Override public void send() throws Exception { - logger.log(Level.INFO, "Sending gcode file."); + logger.log(Level.INFO, String.format("Sending gcode file (%s).", this.processedGcodeFile)); try { // This will throw an exception and prevent that other stuff from // happening (clearing the table before its ready for clearing. diff --git a/ugs-core/test/com/willwinder/universalgcodesender/gcode/processors/LineSplitterTest.java b/ugs-core/test/com/willwinder/universalgcodesender/gcode/processors/LineSplitterTest.java index 994db83c8..bbbce5212 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/gcode/processors/LineSplitterTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/gcode/processors/LineSplitterTest.java @@ -19,6 +19,7 @@ This file is part of Universal Gcode Sender (UGS). package com.willwinder.universalgcodesender.gcode.processors; import com.willwinder.universalgcodesender.gcode.GcodeState; +import com.willwinder.universalgcodesender.gcode.util.Code; import com.willwinder.universalgcodesender.model.Position; import static com.willwinder.universalgcodesender.model.UnitUtils.Units.MM; import java.util.Arrays; @@ -48,7 +49,19 @@ private static void splitterHarness( List result = instance.processCommand(command, state); assertEquals(expected, result); } - + + private static void splitterHarnessRelativeMode( + double splitterLength, Position start, String command, List expected) throws Exception { + LineSplitter instance = new LineSplitter(splitterLength); + + GcodeState state = new GcodeState(); + state.currentPoint = start; + state.inAbsoluteMode = false; + + List result = instance.processCommand(command, state); + assertEquals(expected, result); + } + /** * Lines being split across each X/Y/Z axis. */ @@ -68,6 +81,25 @@ public void testSingleAxis() throws Exception { splitterHarness(1, new Position(0, 0, -1, MM), "G1Z1", expected); } + /** + * Lines being split across each X/Y/Z axis. + */ + @Test + public void testSingleAxisRelative() throws Exception { + System.out.println("splitSingleAxis"); + + List expected; + + expected = Arrays.asList("G1X1Y0Z0", "G1X1Y0Z0"); + splitterHarnessRelativeMode(1, new Position(-1, 0, 0, MM), "G1X2", expected); + + expected = Arrays.asList("G1X0Y1Z0", "G1X0Y1Z0"); + splitterHarnessRelativeMode(1, new Position(0, -1, 0, MM), "G1Y2", expected); + + expected = Arrays.asList("G1X0Y0Z1", "G1X0Y0Z1"); + splitterHarnessRelativeMode(1, new Position(0, 0, -1, MM), "G1Z2", expected); + } + /** * Line running across all axes. */ @@ -180,4 +212,5 @@ public void testSloppyFractionRounding() throws Exception { List expected = Arrays.asList("G1X0.3333Y0Z0", "G1X0.6667Y0Z0", "G1X1Y0Z0"); splitterHarness(maxSegmentLength, new Position(0, 0, 0, MM), command, expected); } + } diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/AutoLevelerTopComponent.java b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/AutoLevelerTopComponent.java index 8d50291ac..91c638d17 100644 --- a/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/AutoLevelerTopComponent.java +++ b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/AutoLevelerTopComponent.java @@ -18,6 +18,7 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.ugs.platform.surfacescanner; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -25,9 +26,8 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.ugs.nbp.lib.lookup.CentralLookup; import com.willwinder.ugs.nbp.lib.services.LocalizingService; import com.willwinder.ugs.nbp.lib.services.TopComponentLocalizer; -import com.willwinder.universalgcodesender.gcode.GcodeParser; import com.willwinder.universalgcodesender.gcode.processors.ArcExpander; -import com.willwinder.universalgcodesender.gcode.processors.CommentProcessor; +import com.willwinder.universalgcodesender.gcode.processors.CommandProcessor; import com.willwinder.universalgcodesender.gcode.processors.LineSplitter; import com.willwinder.universalgcodesender.gcode.processors.MeshLeveler; import com.willwinder.universalgcodesender.i18n.Localization; @@ -93,6 +93,8 @@ public final class AutoLevelerTopComponent extends TopComponent implements ItemL public final static String AutoLevelerActionId = "com.willwinder.ugs.platform.surfacescanner.AutoLevelerTopComponent"; public final static String AutoLevelerCategory = LocalizingService.CATEGORY_WINDOW; + private ImmutableList activeCommandProcessors = ImmutableList.of(); + @OnStart public static class Localizer extends TopComponentLocalizer { public Localizer() { @@ -119,6 +121,7 @@ public AutoLevelerTopComponent() { yMax.addChangeListener(cl); zMin.addChangeListener(cl); zMax.addChangeListener(cl); + zSurface.addChangeListener(cl); unitInch.addItemListener(this); unitMM.addItemListener(this); @@ -163,8 +166,8 @@ private void updateSettings() { public void UGSEvent(UGSEvent evt) { if (evt instanceof ProbeEvent) { if (!scanner.isCollectedAllProbe()) return; - - Position probe = ((ProbeEvent)evt).getProbePosition(); + + Position probe = ((ProbeEvent) evt).getProbePosition(); Position offset = this.settings.getAutoLevelSettings().autoLevelProbeOffset; if (probe.getUnits() == Units.UNKNOWN || offset.getUnits() == Units.UNKNOWN) { @@ -178,13 +181,9 @@ public void UGSEvent(UGSEvent evt) { probe.y + offset.y, probe.z + offset.z, probe.getUnits())); - } - - else if(evt instanceof SettingChangedEvent) { + } else if (evt instanceof SettingChangedEvent) { updateSettings(); - } - - else if(evt instanceof FileStateEvent){ + } else if (evt instanceof FileStateEvent) { applyToGcode.setEnabled(true); } } @@ -193,7 +192,7 @@ private double getValue(JSpinner spinner) { Object o = spinner.getValue(); try { return Double.parseDouble(o.toString()); - } catch(Exception ignored) { + } catch (Exception ignored) { } return 0.0f; } @@ -225,7 +224,8 @@ private AutoLevelSettings updateScanner(Units units) { /** * JRadioButton's have strange state changes, so using item change. - * @param e + * + * @param e */ @Override public void itemStateChanged(ItemEvent e) { @@ -563,12 +563,12 @@ private void scanSurfaceButtonActionPerformed(java.awt.event.ActionEvent evt) {/ try { scanner.enableCollectProbe(backend.getWorkPosition(), backend.getMachinePosition()); - + AutoLevelSettings als = settings.getAutoLevelSettings(); for (Position p : scanner.getProbeStartPositions()) { - backend.sendGcodeCommand(true, String.format("G90 G2%d G0 X%f Y%f Z%f",(p.getUnits() == Units.MM)? 1:0, p.x, p.y, p.z)); + backend.sendGcodeCommand(true, String.format("G90 G2%d G0 X%f Y%f Z%f", (p.getUnits() == Units.MM) ? 1 : 0, p.x, p.y, p.z)); backend.probe("Z", als.probeSpeed, this.scanner.getProbeDistance(), u); - backend.sendGcodeCommand(true, String.format("G90 G2%d G0 Z%f",(p.getUnits() == Units.MM)? 1:0, p.z)); + backend.sendGcodeCommand(true, String.format("G90 G2%d G0 Z%f", (p.getUnits() == Units.MM) ? 1 : 0, p.z)); } } catch (Exception ex) { Exceptions.printStackTrace(ex); @@ -576,7 +576,7 @@ private void scanSurfaceButtonActionPerformed(java.awt.event.ActionEvent evt) {/ }//GEN-LAST:event_scanSurfaceButtonActionPerformed private void dataViewerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataViewerActionPerformed - List> probeData = new ArrayList<>(); + List> probeData = new ArrayList<>(); // Collect data from grid. if (scanner != null && scanner.getProbePositionGrid() != null) { @@ -599,26 +599,32 @@ private void dataViewerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FI }//GEN-LAST:event_dataViewerActionPerformed private void applyToGcodeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_applyToGcodeActionPerformed - GcodeParser gcp = new GcodeParser(); Settings.AutoLevelSettings autoLevelSettings = this.settings.getAutoLevelSettings(); - // Step 0: Get rid of comments. - gcp.addCommandProcessor(new CommentProcessor()); - - // Step 1: The arc processor and line processors NO LONGER need to be split! + ImmutableList.Builder commandProcessors = ImmutableList.builder(); + try { + for(CommandProcessor p : activeCommandProcessors) { + backend.removeCommandProcessor(p); + } - // Step 2: Must convert arcs to line segments. - gcp.addCommandProcessor(new ArcExpander(true, autoLevelSettings.autoLevelArcSliceLength)); + // Step 1: Convert arcs to line segments. + commandProcessors.add(new ArcExpander(true, autoLevelSettings.autoLevelArcSliceLength)); - // Step 3: Line splitter. No line should be longer than some fraction of "resolution" - gcp.addCommandProcessor(new LineSplitter(getValue(stepResolution)/10)); + // Step 2: Line splitter. No line should be longer than some fraction of "resolution" + commandProcessors.add(new LineSplitter(getValue(stepResolution) / 10)); - // Step 4: Adjust Z heights codes based on mesh offsets. - gcp.addCommandProcessor(new MeshLeveler(getValue(this.zSurface), scanner.getProbePositionGrid(), scanner.getUnits())); + // Step 3: Adjust Z heights codes based on mesh offsets. + commandProcessors.add( + new MeshLeveler(getValue(this.zSurface), + scanner.getProbePositionGrid(), + scanner.getUnits())); - try { - backend.applyGcodeParser(gcp); - applyToGcode.setEnabled(false); + activeCommandProcessors = commandProcessors.build(); + for(CommandProcessor p : activeCommandProcessors) { + backend.applyCommandProcessor(p); + } + GUIHelpers.displayHelpDialog( + "The autoleveler feature is under development and may not work properly, use at your own risk."); } catch (Exception ex) { GUIHelpers.displayErrorDialog(ex.getMessage()); Exceptions.printStackTrace(ex); @@ -650,11 +656,11 @@ private void useLoadedFileActionPerformed(java.awt.event.ActionEvent evt) {//GEN }//GEN-LAST:event_useLoadedFileActionPerformed private void generateTestDataButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_generateTestDataButtonActionPerformed - if(scanner.getProbeStartPositions() == null) + if (scanner.getProbeStartPositions() == null) return; scanner.enableTestProbe(); - + // Generate some random test data. Random random = new Random(); @@ -702,7 +708,6 @@ private void visibleAutoLevelerActionPerformed(java.awt.event.ActionEvent evt) { @Override public void componentOpened() { - GUIHelpers.displayHelpDialog("The autoleveler feature currently doesn't work properly, close the autoleveler window to disable this message in the future."); scanner = new SurfaceScanner(); if (r == null) { r = new AutoLevelPreview(Localization.getString("platform.visualizer.renderable.autolevel-preview")); @@ -718,10 +723,10 @@ public void componentClosed() { } public void writeProperties(java.util.Properties p) { - // No properties + // No properties } public void readProperties(java.util.Properties p) { - // No properties + // No properties } }