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 @@ + + + + + + +