Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates & fixes for Auto Leveler plugin #2022

Merged
merged 5 commits into from
Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,12 @@ public List<String> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand All @@ -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) {
Expand Down Expand Up @@ -155,30 +153,36 @@ public List<String> 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);
Expand Down Expand Up @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,7 +49,19 @@ private static void splitterHarness(
List<String> result = instance.processCommand(command, state);
assertEquals(expected, result);
}


private static void splitterHarnessRelativeMode(
double splitterLength, Position start, String command, List<String> expected) throws Exception {
LineSplitter instance = new LineSplitter(splitterLength);

GcodeState state = new GcodeState();
state.currentPoint = start;
state.inAbsoluteMode = false;

List<String> result = instance.processCommand(command, state);
assertEquals(expected, result);
}

/**
* Lines being split across each X/Y/Z axis.
*/
Expand All @@ -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<String> 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.
*/
Expand Down Expand Up @@ -180,4 +212,5 @@ public void testSloppyFractionRounding() throws Exception {
List<String> expected = Arrays.asList("G1X0.3333Y0Z0", "G1X0.6667Y0Z0", "G1X1Y0Z0");
splitterHarness(maxSegmentLength, new Position(0, 0, 0, MM), command, expected);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ 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;
import com.willwinder.ugs.nbm.visualizer.shared.RenderableUtils;
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;
Expand Down Expand Up @@ -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<CommandProcessor> activeCommandProcessors = ImmutableList.of();

@OnStart
public static class Localizer extends TopComponentLocalizer {
public Localizer() {
Expand All @@ -119,6 +121,7 @@ public AutoLevelerTopComponent() {
yMax.addChangeListener(cl);
zMin.addChangeListener(cl);
zMax.addChangeListener(cl);
zSurface.addChangeListener(cl);
unitInch.addItemListener(this);
unitMM.addItemListener(this);

Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -563,20 +563,20 @@ 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);
}
}//GEN-LAST:event_scanSurfaceButtonActionPerformed

private void dataViewerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataViewerActionPerformed
List<Map<String,Double>> probeData = new ArrayList<>();
List<Map<String, Double>> probeData = new ArrayList<>();

// Collect data from grid.
if (scanner != null && scanner.getProbePositionGrid() != null) {
Expand All @@ -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();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was something broken with this approach? Without replacing the gcode parser you could have other processors loaded which might effect the final generated program.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that applyGcodeParser was deprecated and I assumed that having all processors that loaded run would be the expected behavior...wouldn't one expect those other processors to effect the final generated program?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember deprecating this method as I thought it got unintuitive when you applied a processor the previous one got replaced. For instance this prevented the user to apply "Mirror" and "Run from" at the same time.

It is still a bit unintuitive as the changes aren't reflected in the GCode which might have made more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to reflecting the changes in the GCode in some way. I considered adding a (read-only) view of what will actually be sent out when I was debugging this.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default processors would remove comments and I always tried to maintain those in the editor view, but I can see how it's misleading to display different gcode than what gets sent.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not be best to apply all processors in a serial fashion, rather than just the last? The order could be pre-determined based on what made the most sense.

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<CommandProcessor> 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);
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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"));
Expand All @@ -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
}
}