diff --git a/ugs-core/src/com/willwinder/universalgcodesender/utils/SimpleGcodeStreamWriter.java b/ugs-core/src/com/willwinder/universalgcodesender/utils/SimpleGcodeStreamWriter.java
new file mode 100644
index 0000000000..22acef06bd
--- /dev/null
+++ b/ugs-core/src/com/willwinder/universalgcodesender/utils/SimpleGcodeStreamWriter.java
@@ -0,0 +1,65 @@
+/*
+ Copyright 2024 Will Winder
+
+ This file is part of Universal Gcode Sender (UGS).
+
+ UGS 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.
+
+ UGS 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 UGS. If not, see .
+ */
+package com.willwinder.universalgcodesender.utils;
+
+import com.willwinder.universalgcodesender.types.GcodeCommand;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Writes the preprocessed gcode to the given file.
+ *
+ * @author Joacim Breiler
+ */
+public class SimpleGcodeStreamWriter implements IGcodeWriter {
+ private final PrintWriter fileWriter;
+
+ public SimpleGcodeStreamWriter(File f) throws FileNotFoundException {
+ try {
+ fileWriter = new PrintWriter(f, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new FileNotFoundException("Could not use UTF-8 to output gcode stream file");
+ }
+ }
+
+ @Override
+ public String getCanonicalPath() {
+ return "";
+ }
+
+ @Override
+ public void addLine(GcodeCommand command) {
+ addLine(command.getOriginalCommandString(), command.getCommandString(), command.getComment(), command.getCommandNumber());
+ }
+
+ @Override
+ public void addLine(String original, String processed, String comment, int commandNumber) {
+ fileWriter.append(processed);
+ fileWriter.append("\n");
+ }
+
+ @Override
+ public void close() throws IOException {
+ fileWriter.close();
+ }
+}
diff --git a/ugs-core/src/resources/MessagesBundle_en_US.properties b/ugs-core/src/resources/MessagesBundle_en_US.properties
index 5d7f25af76..ac5308454b 100644
--- a/ugs-core/src/resources/MessagesBundle_en_US.properties
+++ b/ugs-core/src/resources/MessagesBundle_en_US.properties
@@ -501,6 +501,8 @@ autoleveler.option.offset-y = Probe Y offset
autoleveler.option.offset-z = Probe Z offset
autoleveler.probe-failed = Probe failed
autoleveler.panel.clear = Clear scan
+autoleveler.panel.export = Export gcode
+autoleveler.panel.export.tooltip = Exports the autoleveled gcode
experimental.feature = This is an experimental feature. Please use caution and report any bugs you find on GitHub.
# Window title for platform GUI
platform-title = Universal Gcode Sender
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/MeshLevelManager.java b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/MeshLevelManager.java
index 8c5496b213..1cb1a2a9eb 100644
--- a/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/MeshLevelManager.java
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/MeshLevelManager.java
@@ -1,5 +1,5 @@
/*
- Copyright 2023 Will Winder
+ Copyright 2023-2024 Will Winder
This file is part of Universal Gcode Sender (UGS).
@@ -18,11 +18,8 @@ This file is part of Universal Gcode Sender (UGS).
*/
package com.willwinder.ugs.platform.surfacescanner;
-import com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils;
-import com.willwinder.universalgcodesender.gcode.processors.ArcExpander;
+import static com.willwinder.ugs.platform.surfacescanner.Utils.createCommandProcessor;
import com.willwinder.universalgcodesender.gcode.processors.CommandProcessorList;
-import com.willwinder.universalgcodesender.gcode.processors.LineSplitter;
-import com.willwinder.universalgcodesender.gcode.processors.MeshLeveler;
import com.willwinder.universalgcodesender.model.BackendAPI;
import com.willwinder.universalgcodesender.utils.AutoLevelSettings;
import com.willwinder.universalgcodesender.utils.GUIHelpers;
@@ -51,19 +48,7 @@ public void update() {
return;
}
- commandProcessorList = new CommandProcessorList();
-
- // Step 1: Convert arcs to line segments.
- commandProcessorList.add(new ArcExpander(true, autoLevelSettings.getAutoLevelArcSliceLength(), GcodePreprocessorUtils.getDecimalFormatter()));
-
- // Step 2: Line splitter. No line should be longer than some fraction of "resolution"
- commandProcessorList.add(new LineSplitter(autoLevelSettings.getStepResolution() / 4));
-
- // Step 3: Adjust Z heights codes based on mesh offsets.
- commandProcessorList.add(
- new MeshLeveler(autoLevelSettings.getZSurface(),
- surfaceScanner.getProbePositionGrid()));
-
+ commandProcessorList = createCommandProcessor(autoLevelSettings, surfaceScanner);
backend.applyCommandProcessor(commandProcessorList);
} catch (Exception ex) {
GUIHelpers.displayErrorDialog(ex.getMessage());
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/Utils.java b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/Utils.java
index 2d6fd7b07b..5cb21510b7 100644
--- a/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/Utils.java
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/Utils.java
@@ -18,9 +18,15 @@ This file is part of Universal Gcode Sender (UGS).
*/
package com.willwinder.ugs.platform.surfacescanner;
+import com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils;
+import com.willwinder.universalgcodesender.gcode.processors.ArcExpander;
+import com.willwinder.universalgcodesender.gcode.processors.CommandProcessorList;
+import com.willwinder.universalgcodesender.gcode.processors.LineSplitter;
+import com.willwinder.universalgcodesender.gcode.processors.MeshLeveler;
import com.willwinder.universalgcodesender.i18n.Localization;
import com.willwinder.universalgcodesender.model.Position;
import com.willwinder.universalgcodesender.model.UnitUtils;
+import com.willwinder.universalgcodesender.utils.AutoLevelSettings;
import javax.swing.*;
import java.awt.*;
@@ -77,4 +83,20 @@ public static Position getRoundPosition(Position position) {
double z = Math.round(position.getZ() * 1000d) / 1000d;
return new Position(x, y, z, position.getUnits());
}
+
+ public static CommandProcessorList createCommandProcessor(AutoLevelSettings autoLevelSettings, SurfaceScanner surfaceScanner) {
+ CommandProcessorList result = new CommandProcessorList();
+
+ // Step 1: Convert arcs to line segments.
+ result.add(new ArcExpander(true, autoLevelSettings.getAutoLevelArcSliceLength(), GcodePreprocessorUtils.getDecimalFormatter()));
+
+ // Step 2: Line splitter. No line should be longer than some fraction of "resolution"
+ result.add(new LineSplitter(autoLevelSettings.getStepResolution() / 4));
+
+ // Step 3: Adjust Z heights codes based on mesh offsets.
+ result.add(
+ new MeshLeveler(autoLevelSettings.getZSurface(),
+ surfaceScanner.getProbePositionGrid()));
+ return result;
+ }
}
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/actions/ExportGcodeAction.java b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/actions/ExportGcodeAction.java
new file mode 100644
index 0000000000..436b6e61d4
--- /dev/null
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/actions/ExportGcodeAction.java
@@ -0,0 +1,128 @@
+/*
+ Copyright 2024 Will Winder
+
+ This file is part of Universal Gcode Sender (UGS).
+
+ UGS 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.
+
+ UGS 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 UGS. If not, see .
+ */
+package com.willwinder.ugs.platform.surfacescanner.actions;
+
+import com.willwinder.ugs.nbp.lib.lookup.CentralLookup;
+import com.willwinder.ugs.platform.surfacescanner.SurfaceScanner;
+import static com.willwinder.ugs.platform.surfacescanner.Utils.createCommandProcessor;
+import static com.willwinder.ugs.platform.surfacescanner.Utils.fileChooser;
+import com.willwinder.universalgcodesender.gcode.GcodeParser;
+import com.willwinder.universalgcodesender.gcode.processors.CommandProcessorList;
+import com.willwinder.universalgcodesender.gcode.util.GcodeParserException;
+import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils;
+import com.willwinder.universalgcodesender.i18n.Localization;
+import com.willwinder.universalgcodesender.listeners.UGSEventListener;
+import com.willwinder.universalgcodesender.model.BackendAPI;
+import com.willwinder.universalgcodesender.model.UGSEvent;
+import com.willwinder.universalgcodesender.model.events.FileStateEvent;
+import com.willwinder.universalgcodesender.uielements.components.GcodeFileTypeFilter;
+import com.willwinder.universalgcodesender.utils.GUIHelpers;
+import com.willwinder.universalgcodesender.utils.SimpleGcodeStreamWriter;
+import org.apache.commons.io.FilenameUtils;
+import org.openide.util.ImageUtilities;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JFileChooser;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An action that will export the currently loaded gcode with the applied surface scan
+ *
+ * @author Joacim Breiler
+ */
+public class ExportGcodeAction extends AbstractAction implements UGSEventListener {
+ public static final String ICON_BASE = "com/willwinder/ugs/platform/surfacescanner/icons/export.svg";
+ private static final Logger LOGGER = Logger.getLogger(ExportGcodeAction.class.getSimpleName());
+ private final transient SurfaceScanner surfaceScanner;
+ private final transient BackendAPI backend;
+
+ public ExportGcodeAction(SurfaceScanner surfaceScanner) {
+ this.surfaceScanner = surfaceScanner;
+ backend = CentralLookup.getDefault().lookup(BackendAPI.class);
+ backend.addUGSEventListener(this);
+
+ String title = Localization.getString("autoleveler.panel.export");
+ putValue(NAME, title);
+ putValue("menuText", title);
+ putValue(Action.SHORT_DESCRIPTION, Localization.getString("autoleveler.panel.export.tooltip"));
+ putValue("iconBase", ICON_BASE);
+ putValue(SMALL_ICON, ImageUtilities.loadImageIcon(ICON_BASE, false));
+
+ setEnabled(isEnabled());
+ this.surfaceScanner.addListener(() -> setEnabled(isEnabled()));
+ }
+
+ private static Optional chooseSaveFile(File file) {
+ fileChooser.setFileFilter(new GcodeFileTypeFilter());
+ fileChooser.setSelectedFile(file);
+ int result = fileChooser.showSaveDialog(null);
+ if (result != JFileChooser.APPROVE_OPTION) {
+ return Optional.empty();
+ }
+ File selectedFile = fileChooser.getSelectedFile();
+ if (!selectedFile.getName().endsWith(".gcode")) {
+ selectedFile = new File(selectedFile.getAbsolutePath() + ".gcode");
+ }
+ return Optional.of(selectedFile);
+ }
+
+ private Optional getExportFile() {
+ File loadedFile = backend.getGcodeFile();
+ File file = new File(loadedFile.getPath() + File.separator + FilenameUtils.removeExtension(loadedFile.getName()) + "-autoleveled.gcode");
+ return chooseSaveFile(file);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return surfaceScanner.isValid() && backend.getGcodeFile() != null;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Optional selectedFile = getExportFile();
+ if (selectedFile.isEmpty()) {
+ return;
+ }
+
+ CommandProcessorList commandProcessor = createCommandProcessor(backend.getSettings().getAutoLevelSettings(), surfaceScanner);
+ File gcodeFile = backend.getGcodeFile();
+ GcodeParser gcp = new GcodeParser();
+ gcp.addCommandProcessor(commandProcessor);
+
+ try (SimpleGcodeStreamWriter gcw = new SimpleGcodeStreamWriter(selectedFile.get())) {
+ GcodeParserUtils.processAndExport(gcp, gcodeFile, gcw);
+ } catch (IOException | GcodeParserException ex) {
+ LOGGER.log(Level.SEVERE, "Could not export gcode", ex);
+ GUIHelpers.displayErrorDialog("Could not export the loaded gcode: " + ex.getMessage());
+ }
+ }
+
+ @Override
+ public void UGSEvent(UGSEvent evt) {
+ if (evt instanceof FileStateEvent) {
+ setEnabled(isEnabled());
+ }
+ }
+}
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/ui/AutoLevelerToolbar.java b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/ui/AutoLevelerToolbar.java
index 0faaeeb099..c7e4ee7879 100644
--- a/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/ui/AutoLevelerToolbar.java
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/java/com/willwinder/ugs/platform/surfacescanner/ui/AutoLevelerToolbar.java
@@ -35,6 +35,7 @@ public AutoLevelerToolbar(SurfaceScanner surfaceScanner) {
add(new JButton(new SaveScannedSurfaceAction(surfaceScanner)));
add(new JButton(new ClearScannedSurfaceAction(surfaceScanner)));
add(Box.createGlue());
+ add(new JButton(new ExportGcodeAction(surfaceScanner)));
add(new JButton(new GenerateTestDataAction(surfaceScanner)));
add(new JButton(new OpenSettingsAction()));
}
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export.svg b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export.svg
new file mode 100644
index 0000000000..767caeb47f
--- /dev/null
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export32.svg b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export32.svg
new file mode 100644
index 0000000000..1de81558b3
--- /dev/null
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export32.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export32_dark.svg b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export32_dark.svg
new file mode 100644
index 0000000000..e29f2ef5e6
--- /dev/null
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export32_dark.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export32_disabled_dark.svg b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export32_disabled_dark.svg
new file mode 100644
index 0000000000..057c19e0d6
--- /dev/null
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export32_disabled_dark.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export_dark.svg b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export_dark.svg
new file mode 100644
index 0000000000..86b7516721
--- /dev/null
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export_dark.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export_disabled_dark.svg b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export_disabled_dark.svg
new file mode 100644
index 0000000000..97efbc9880
--- /dev/null
+++ b/ugs-platform/ugs-platform-surfacescanner/src/main/resources/com/willwinder/ugs/platform/surfacescanner/icons/export_disabled_dark.svg
@@ -0,0 +1,42 @@
+
+