From 055901efacfcd63428080e70380f85ea72a2f5c9 Mon Sep 17 00:00:00 2001 From: Joacim Breiler Date: Wed, 2 Dec 2020 16:19:30 +0100 Subject: [PATCH] Loading preprocessors (#1461) * Added new actions for rotating and mirroring the model * The "run from" feature now applies the action to the existing file (no longer reloads a new file) --- .../AbstractController.java | 1 - .../gcode/GcodeParser.java | 392 +----------------- .../gcode/GcodePreprocessorUtils.java | 4 +- .../gcode/IGcodeParser.java | 10 +- .../gcode/processors/ArcExpander.java | 13 +- .../processors/CommandProcessorList.java | 116 ++++++ .../gcode/processors/LineSplitter.java | 9 +- .../gcode/processors/MeshLeveler.java | 4 +- .../gcode/processors/MirrorProcessor.java | 125 ++++++ .../gcode/processors/RotateProcessor.java | 148 +++++++ .../gcode/processors/RunFromProcessor.java | 24 +- .../gcode/processors/TranslateProcessor.java | 138 ++++++ .../gcode/util/GcodeParserUtils.java | 324 ++++++++++++++- .../model/BackendAPI.java | 24 +- .../model/GUIBackend.java | 31 +- .../universalgcodesender/model/Position.java | 39 +- .../universalgcodesender/model/UGSEvent.java | 1 + .../services/RunFromService.java | 61 +++ .../universalgcodesender/utils/MathUtils.java | 50 ++- .../visualizer/GcodeViewParse.java | 2 +- .../gcode/GcodeParserTest.java | 115 +---- .../gcode/processors/RotateProcessorTest.java | 66 +++ .../gcode/util/GcodeParserUtilsTest.java | 116 ++++++ .../model/GUIBackendTest.java | 9 +- .../ugs/nbp/editor/actions/RunFromHere.java | 12 +- .../core/actions/AbstractRotateAction.java | 129 ++++++ .../ugs/nbp/core/actions/MirrorAction.java | 129 ++++++ .../ugs/nbp/core/actions/OutlineAction.java | 2 +- .../nbp/core/actions/RotateLeftAction.java | 33 ++ .../nbp/core/actions/RotateRightAction.java | 33 ++ .../ugs/nbp/core/actions/RunFromAction.java | 29 +- .../core/actions/TranslateToZeroAction.java | 129 ++++++ .../main/resources/resources/icons/mirror.svg | 68 +++ .../resources/resources/icons/mirror24.svg | 68 +++ .../resources/icons/mirror24_dark.svg | 68 +++ .../icons/mirror24_disabled_dark.svg | 68 +++ .../resources/resources/icons/mirror32.svg | 68 +++ .../resources/icons/mirror32_dark.svg | 68 +++ .../icons/mirror32_disabled_dark.svg | 68 +++ .../resources/resources/icons/mirror_dark.svg | 68 +++ .../resources/icons/mirror_disabled_dark.svg | 68 +++ .../resources/resources/icons/rotate_left.svg | 109 +++++ .../resources/icons/rotate_left24.svg | 109 +++++ .../resources/icons/rotate_left24_dark.svg | 109 +++++ .../icons/rotate_left24_disabled_dark.svg | 109 +++++ .../resources/icons/rotate_left32.svg | 109 +++++ .../resources/icons/rotate_left32_dark.svg | 109 +++++ .../icons/rotate_left32_disabled_dark.svg | 109 +++++ .../resources/icons/rotate_left_dark.svg | 109 +++++ .../icons/rotate_left_disabled_dark.svg | 109 +++++ .../resources/icons/rotate_right.svg | 108 +++++ .../resources/icons/rotate_right24.svg | 109 +++++ .../resources/icons/rotate_right24_dark.svg | 109 +++++ .../icons/rotate_right24_disabled_dark.svg | 109 +++++ .../resources/icons/rotate_right32.svg | 109 +++++ .../resources/icons/rotate_right32_dark.svg | 109 +++++ .../icons/rotate_right32_disabled_dark.svg | 109 +++++ .../resources/icons/rotate_right_dark.svg | 108 +++++ .../icons/rotate_right_disabled_dark.svg | 108 +++++ .../resources/resources/icons/translate.svg | 56 +++ .../resources/resources/icons/translate24.svg | 56 +++ .../resources/icons/translate24_dark.svg | 56 +++ .../icons/translate24_disabled_dark.svg | 56 +++ .../resources/resources/icons/translate32.svg | 56 +++ .../resources/icons/translate32_dark.svg | 56 +++ .../icons/translate32_disabled_dark.svg | 56 +++ .../resources/icons/translate_dark.svg | 56 +++ .../icons/translate_disabled_dark.svg | 56 +++ .../ugs/nbp/lib/lookup/CentralLookup.java | 28 +- .../nbp/lib/services/LocalizingService.java | 6 +- .../nbm/visualizer/RendererInputHandler.java | 1 - 71 files changed, 4871 insertions(+), 557 deletions(-) create mode 100644 ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/CommandProcessorList.java create mode 100644 ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MirrorProcessor.java create mode 100644 ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/RotateProcessor.java create mode 100644 ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/TranslateProcessor.java create mode 100644 ugs-core/src/com/willwinder/universalgcodesender/services/RunFromService.java create mode 100644 ugs-core/test/com/willwinder/universalgcodesender/gcode/processors/RotateProcessorTest.java create mode 100644 ugs-core/test/com/willwinder/universalgcodesender/gcode/util/GcodeParserUtilsTest.java create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/AbstractRotateAction.java create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/MirrorAction.java create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RotateLeftAction.java create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RotateRightAction.java create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/TranslateToZeroAction.java create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32_disabled_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate_dark.svg create mode 100644 ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate_disabled_dark.svg diff --git a/ugs-core/src/com/willwinder/universalgcodesender/AbstractController.java b/ugs-core/src/com/willwinder/universalgcodesender/AbstractController.java index 0a5497f048..a7498fc2e4 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/AbstractController.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/AbstractController.java @@ -22,7 +22,6 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.gcode.GcodeCommandCreator; import com.willwinder.universalgcodesender.gcode.GcodeParser; import com.willwinder.universalgcodesender.gcode.GcodeState; -import com.willwinder.universalgcodesender.gcode.util.Code; import com.willwinder.universalgcodesender.gcode.util.GcodeUtils; import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.listeners.ControllerListener; diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodeParser.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodeParser.java index cb341b3cd8..2b42206b09 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodeParser.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodeParser.java @@ -18,25 +18,17 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.universalgcodesender.gcode; -import com.google.common.collect.Iterables; import com.willwinder.universalgcodesender.gcode.processors.CommandProcessor; +import com.willwinder.universalgcodesender.gcode.processors.CommandProcessorList; import com.willwinder.universalgcodesender.gcode.processors.Stats; import com.willwinder.universalgcodesender.gcode.util.Code; import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; -import com.willwinder.universalgcodesender.gcode.util.Plane; -import com.willwinder.universalgcodesender.gcode.util.PlaneFormatter; -import com.willwinder.universalgcodesender.i18n.Localization; -import com.willwinder.universalgcodesender.model.Position; -import com.willwinder.universalgcodesender.model.UnitUtils; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils; import com.willwinder.universalgcodesender.types.PointSegment; -import org.apache.commons.lang3.StringUtils; -import java.util.*; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -import static com.willwinder.universalgcodesender.gcode.util.Code.*; -import static com.willwinder.universalgcodesender.gcode.util.Code.ModalGroup.Motion; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; /** * Object to parse gcode one command at a time in a way that can be used by any @@ -50,12 +42,11 @@ This file is part of Universal Gcode Sender (UGS). * @author wwinder */ public class GcodeParser implements IGcodeParser { - private static final Logger logger = Logger.getLogger(GcodeParser.class.getName()); // Current state private GcodeState state; - private final List processors = new ArrayList<>(); + private final CommandProcessorList processors = new CommandProcessorList(); private Stats statsProcessor; @@ -108,11 +99,16 @@ public void addCommandProcessor(CommandProcessor p) { this.processors.add(p); } + @Override + public void removeCommandProcessor(CommandProcessor p) { + this.processors.remove(p); + } + /** * Clear out any processors that have been added. */ @Override - public void resetCommandProcessors() { + public void clearCommandProcessors() { this.processors.clear(); this.statsProcessor = new Stats(); } @@ -144,8 +140,7 @@ public List addCommand(String command, int line) throws GcodeParserEx List results = new ArrayList<>(); // Add command get meta doesn't update the state, so we need to do that // manually. - //List processedCommands = this.preprocessCommand(command); - Collection metaObjects = processCommand(command, line, state, true); + Collection metaObjects = GcodeParserUtils.processCommand(command, line, state, true); if (metaObjects != null) { for (GcodeMeta c : metaObjects) { if(c.point != null) { @@ -176,364 +171,13 @@ public GcodeStats getCurrentStats() { } /** - * For backwards compatibility this method calls processCommand with includeNonMotionStates = false. - */ - public static List processCommand(String command, int line, final GcodeState inputState) - throws GcodeParserException { - return processCommand(command, line, inputState, false); - } - - /** - * Process command given an initial state. This method will not modify its - * input parameters. - * - * @param includeNonMotionStates Create gcode meta responses even if there is no motion, for example "F100" will not - * return a GcodeMeta entry unless this flag is set to true. - */ - public static List processCommand(String command, int line, final GcodeState inputState, - boolean includeNonMotionStates) - throws GcodeParserException { - List args = GcodePreprocessorUtils.splitCommand(command); - if (args.isEmpty()) return null; - - // Initialize with original state - GcodeState state = inputState.copy(); - - state.commandNumber = line; - - // handle M codes. - Set mCodes = GcodePreprocessorUtils.getMCodes(args); - for (Code c : mCodes) { - switch(c.getType()) { - case Spindle: - state.spindle = c; - break; - case Coolant: - state.coolant = c; - break; - default: - break; - } - } - - List fCodes = GcodePreprocessorUtils.parseCodes(args, 'F'); - if (!fCodes.isEmpty()) { - try { - state.speed = Double.parseDouble(Iterables.getOnlyElement(fCodes)); - } catch (IllegalArgumentException e) { - throw new GcodeParserException("Multiple F-codes on one line."); - } - } - - List sCodes = GcodePreprocessorUtils.parseCodes(args, 'S'); - if (!sCodes.isEmpty()) { - try { - state.spindleSpeed = Double.parseDouble(Iterables.getOnlyElement(sCodes)); - } catch (IllegalArgumentException e) { - throw new GcodeParserException("Multiple S-codes on one line."); - } - } - - // Gather G codes. - Set gCodes = GcodePreprocessorUtils.getGCodes(args); - - boolean hasAxisWords = GcodePreprocessorUtils.hasAxisWords(args); - - // Error to mix group 1 (Motion) and certain group 0 (NonModal) codes (G10, G28, G30, G92) - Collection motionCodes = gCodes.stream() - .filter(Code::consumesMotion) - .collect(Collectors.toList()); - - // 1 motion code per line. - if (motionCodes.size() > 1) { - throw new GcodeParserException(Localization.getString("parser.gcode.multiple-axis-commands") - + ": " + StringUtils.join(motionCodes, ", ")); - } - - // If there are axis words and nothing to use them, add the currentMotionMode. - if (hasAxisWords && motionCodes.isEmpty() && state.currentMotionMode != null) { - gCodes.add(state.currentMotionMode); - } - - // Apply each code to the state. - List results = new ArrayList<>(); - for (Code i : gCodes) { - if (i == UNKNOWN) { - logger.warning("An unknown gcode command was detected in: " + command); - } else { - GcodeMeta meta = handleGCode(i, args, line, state); - meta.command = command; - // Commands like 'G21' don't return a point segment. - if (meta.point != null) { - meta.point.setSpeed(state.speed); - } - results.add(meta); - } - } - - // Return updated state / command. - if (results.isEmpty() && includeNonMotionStates) { - GcodeMeta meta = new GcodeMeta(); - meta.state = state; - meta.command = command; - meta.code = state.currentMotionMode; - return Collections.singletonList(meta); - } - - return results; - } - - private static PointSegment addProbePointSegment(Position nextPoint, boolean fastTraverse, int line, GcodeState state) { - PointSegment ps = addLinearPointSegment(nextPoint, fastTraverse, line, state); - ps.setIsProbe(true); - return ps; - } - - /** - * Create a PointSegment representing the linear command. - */ - private static PointSegment addLinearPointSegment(Position nextPoint, boolean fastTraverse, int line, GcodeState state) { - if (nextPoint == null) { - return null; - } - - PointSegment ps = new PointSegment(nextPoint, line); - - boolean zOnly = false; - - // Check for z-only - if ((state.currentPoint.x == nextPoint.x) && - (state.currentPoint.y == nextPoint.y) && - (state.currentPoint.z != nextPoint.z)) { - zOnly = true; - } - - ps.setIsMetric(state.isMetric); - ps.setIsZMovement(zOnly); - ps.setIsFastTraverse(fastTraverse); - - // Save off the endpoint. - state.currentPoint = nextPoint; - return ps; - } - - /** - * Create a PointSegment representing the arc command. - */ - private static PointSegment addArcPointSegment(Position nextPoint, boolean clockwise, List args, int line, GcodeState state) { - if (nextPoint == null) { - return null; - } - - PointSegment ps = new PointSegment(nextPoint, line); - - PlaneFormatter plane = new PlaneFormatter(state.plane); - Position center = - GcodePreprocessorUtils.updateCenterWithCommand( - args, state.currentPoint, nextPoint, state.inAbsoluteIJKMode, clockwise, plane); - - double radius = GcodePreprocessorUtils.parseCoord(args, 'R'); - - // Calculate radius if necessary, according to the current G17/18/19 Plane - if (Double.isNaN(radius)) { - - radius = Math.sqrt( - Math.pow(plane.axis0(state.currentPoint) - plane.axis0(center), 2.0) - + Math.pow(plane.axis1(state.currentPoint) - plane.axis1(center), 2.0)); - } - - ps.setIsMetric(state.isMetric); - ps.setArcCenter(center); - ps.setIsArc(true); - ps.setRadius(radius); - ps.setIsClockwise(clockwise); - ps.setPlaneState(state.plane); - - // Save off the endpoint. - state.currentPoint = nextPoint; - return ps; - } - - /** - * Branch parser to handle specific gcode command. - * - * A copy of the state object should go in the resulting GcodeMeta object. - */ - private static GcodeMeta handleGCode(final Code code, List args, int line, GcodeState state) - throws GcodeParserException { - GcodeMeta meta = new GcodeMeta(); - - meta.code = code; - - Position nextPoint = null; - - // If it is a movement code make sure it has some coordinates. - if (code.consumesMotion()) { - nextPoint = GcodePreprocessorUtils.updatePointWithCommand(args, state.currentPoint, state.inAbsoluteMode); - - if (nextPoint == null) { - if (!code.motionOptional()) { - throw new GcodeParserException( - Localization.getString("parser.gcode.missing-axis-commands") + ": " + code); - } - } - } - - if (nextPoint == null && meta.point != null) { - nextPoint = meta.point.point(); - } - - switch (code) { - case G0: - meta.point = addLinearPointSegment(nextPoint, true, line, state); - break; - case G1: - meta.point = addLinearPointSegment(nextPoint, false, line, state); - break; - - // Arc command. - case G2: - meta.point = addArcPointSegment(nextPoint, true, args, line, state); - break; - case G3: - meta.point = addArcPointSegment(nextPoint, false, args, line, state); - break; - - case G17: - case G18: - case G19: - case G17_1: - case G18_1: - case G19_1: - state.plane = Plane.lookup(code); - break; - - //inch - case G20: - state.isMetric = false; - state.units = G20; - state.currentPoint = state.currentPoint.getPositionIn(UnitUtils.Units.INCH); - break; - //mm - case G21: - state.isMetric = true; - state.units = G21; - state.currentPoint = state.currentPoint.getPositionIn(UnitUtils.Units.MM); - break; - - // Probe: http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g38 - case G38_2: // probe toward workpiece, stop on contact, signal error if failure - case G38_3: // probe toward workpiece, stop on contact - case G38_4: // probe away from workpiece, stop on loss of contact, signal error if failure - case G38_5: // probe away from workpiece, stop on loss of contact - meta.point = addProbePointSegment(nextPoint, true, line, state); - break; - - // These are not used in the visualizer. - case G54: - case G55: - case G56: - case G57: - case G58: - case G59: - case G59_1: - case G59_2: - case G59_3: - state.offset = code; - break; - - case G90: - state.inAbsoluteMode = true; - state.distanceMode = G90; - break; - case G91: - state.inAbsoluteMode = false; - state.distanceMode = G91; - break; - - case G90_1: - state.inAbsoluteIJKMode = true; - state.arcDistanceMode = G90_1; - break; - case G91_1: - state.inAbsoluteIJKMode = false; - state.arcDistanceMode = G91_1; - break; - - case G93: - case G94: - case G95: - state.feedMode = code; - break; - default: - break; - } - if (code.getType() == Motion) { - state.currentMotionMode = code; - } - meta.state = state.copy(); - return meta; - } - - /** - * Applies all command processors to a given command and returns the - * resulting GCode. Does not change the parser state. - * - * TODO: Rather than have a separate 'preprocessCommand' which needs to be - * followed up with calls to addCommand, it would be great to have addCommand - * also do the preprocessing. This is challenging because they have different - * return types. - * - * This is also needed for some very particular processing in GUIBackend which - * gathers comments as a separate step outside the GcodeParser. - * - * TODO 2: Move this processing logic into another class, or GcodeParserUtils along with testState. + * Applies all command processors to a given command and returns the resulting GCode. Does not change the parser state. + * + * @param command a command to run through a list of processors to create a list of commands + * @param initialState the state before command was applied */ @Override public List preprocessCommand(String command, final GcodeState initialState) throws GcodeParserException { - List ret = new ArrayList<>(); - ret.add(command); - GcodeState tempState; - for (CommandProcessor p : processors) { - // Reset point segments after each pass. The final pass is what we will return. - tempState = initialState.copy(); - // Process each command in the list and add results to the end. - // Don't re-process the results with the same preprocessor. - for (int i = ret.size(); i > 0; i--) { - // The arc expander changes the lastGcodeCommand which causes the following to fail: - // G2 Y-0.7 J-14.7 - // Y28.7 J14.7 (this line treated as a G1) - tempState.currentMotionMode = initialState.currentMotionMode; - List intermediate = p.processCommand(ret.remove(0), tempState); - - // process results to update the state and collect PointSegments - for(String c : intermediate) { - tempState = testState(c, tempState); - } - - ret.addAll(intermediate); - } - } - - return ret; - } - - /** - * Helper to statically process the next step in a program without modifying the parser. - */ - static private GcodeState testState(String command, GcodeState state) throws GcodeParserException { - GcodeState ret = state; - - // Add command get meta doesn't update the state, so we need to do that manually. - Collection metaObjects = processCommand(command, 0, state); - if (metaObjects != null) { - for (GcodeMeta c : metaObjects) { - if (c.state != null) { - ret = c.state; - } - } - } - - return ret; + return processors.processCommand(command, initialState); } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodePreprocessorUtils.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodePreprocessorUtils.java index 304f227d54..2d996624ef 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodePreprocessorUtils.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/GcodePreprocessorUtils.java @@ -711,8 +711,8 @@ public static String normalizeCommand(String command, GcodeState state) throws G // Don't add the state //result.append(state.toGcode()); - result.append("F" + state.speed); - result.append("S" + state.spindleSpeed); + result.append("F").append(state.speed); + result.append("S").append(state.spindleSpeed); // Check if we need to add the motion command back in. if (!gCodes.contains(code)) { diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/IGcodeParser.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/IGcodeParser.java index e7d0905b2c..de28132f29 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/IGcodeParser.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/IGcodeParser.java @@ -2,7 +2,6 @@ import com.willwinder.universalgcodesender.gcode.GcodeParser.GcodeMeta; import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; -import com.willwinder.universalgcodesender.types.PointSegment; import java.util.List; import com.willwinder.universalgcodesender.gcode.processors.CommandProcessor; @@ -20,13 +19,20 @@ public interface IGcodeParser { /** * Add a preprocessor to use with the preprocessCommand method. + * @param p - a processor to be added */ void addCommandProcessor(CommandProcessor p); + /** + * Removes a processor to not be used anymore + * @param p - a processor to remove + */ + void removeCommandProcessor(CommandProcessor p); + /** * Clear out any processors that have been added. */ - void resetCommandProcessors(); + void clearCommandProcessors(); /** * Add a string of command(s) for parsing. diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/ArcExpander.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/ArcExpander.java index d701e5a1d1..df4ec3b6a7 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/ArcExpander.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/ArcExpander.java @@ -1,10 +1,5 @@ -/** - * Expand an arc into smaller sections. You can configure the length of each - * section, and whether it is expanded with a bunch of smaller arcs, or with - * line segments. - */ /* - Copyright 2016-2017 Will Winder + Copyright 2016-2020 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -32,6 +27,7 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.gcode.util.Code; import static com.willwinder.universalgcodesender.gcode.util.Code.G1; import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils; import com.willwinder.universalgcodesender.gcode.util.PlaneFormatter; import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.model.Position; @@ -42,6 +38,9 @@ This file is part of Universal Gcode Sender (UGS). import java.util.List; /** + * Expand an arc into smaller sections. You can configure the length of each + * section, and whether it is expanded with a bunch of smaller arcs, or with + * line segments. * * @author wwinder */ @@ -75,7 +74,7 @@ public List processCommand(String command, GcodeState state) throws Gcod List results = new ArrayList<>(); - List commands = GcodeParser.processCommand(command, 0, state); + List commands = GcodeParserUtils.processCommand(command, 0, state); // If this is not an arc, there is nothing to do. Code c = hasArcCommand(commands); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/CommandProcessorList.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/CommandProcessorList.java new file mode 100644 index 0000000000..064b505058 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/CommandProcessorList.java @@ -0,0 +1,116 @@ +package com.willwinder.universalgcodesender.gcode.processors; + +import com.willwinder.universalgcodesender.gcode.GcodeParser; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils; +import com.willwinder.universalgcodesender.gcode.GcodeState; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.function.Consumer; + +public class CommandProcessorList implements CommandProcessor, Iterable { + + private List commandProcessors = new ArrayList<>(); + + /** + * Applies all command processors to a given command and returns the + * resulting GCode. Does not change the parser state. + * + * TODO: Rather than have a separate 'preprocessCommand' which needs to be + * followed up with calls to addCommand, it would be great to have addCommand + * also do the preprocessing. This is challenging because they have different + * return types. + * + * This is also needed for some very particular processing in GUIBackend which + * gathers comments as a separate step outside the GcodeParser. + */ + @Override + public List processCommand(String command, final GcodeState initialState) throws GcodeParserException { + List ret = new ArrayList<>(); + ret.add(command); + GcodeState tempState; + for (CommandProcessor p : commandProcessors) { + // Reset point segments after each pass. The final pass is what we will return. + tempState = initialState.copy(); + // Process each command in the list and add results to the end. + // Don't re-process the results with the same preprocessor. + for (int i = ret.size(); i > 0; i--) { + // The arc expander changes the lastGcodeCommand which causes the following to fail: + // G2 Y-0.7 J-14.7 + // Y28.7 J14.7 (this line treated as a G1) + tempState.currentMotionMode = initialState.currentMotionMode; + List intermediate = p.processCommand(ret.remove(0), tempState); + + // process results to update the state and collect PointSegments + for(String c : intermediate) { + tempState = testState(c, tempState); + } + + ret.addAll(intermediate); + } + } + + return ret; + } + + @Override + public String getHelp() { + return "Combines several processors and runs them in sequence"; + } + + /** + * Helper to statically process the next step in a program without modifying the parser. + */ + static private GcodeState testState(String command, GcodeState state) throws GcodeParserException { + GcodeState ret = state; + + // Add command get meta doesn't update the state, so we need to do that manually. + Collection metaObjects = GcodeParserUtils.processCommand(command, 0, state); + if (metaObjects != null) { + for (GcodeParser.GcodeMeta c : metaObjects) { + if (c.state != null) { + ret = c.state; + } + } + } + + return ret; + } + + public void add(CommandProcessor processor) { + if(!commandProcessors.contains(processor)) { + commandProcessors.add(processor); + } + } + + public void remove(CommandProcessor processor) { + commandProcessors.remove(processor); + } + + public int size() { + return commandProcessors.size(); + } + + public void clear() { + commandProcessors.clear(); + } + + @Override + public Iterator iterator() { + return commandProcessors.iterator(); + } + + @Override + public void forEach(Consumer action) { + commandProcessors.forEach(action); + } + + @Override + public Spliterator spliterator() { + return commandProcessors.spliterator(); + } +} 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 c3143206f1..9728378ccf 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/LineSplitter.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/LineSplitter.java @@ -1,8 +1,5 @@ -/** - * Split lines into a series of smaller line segments. - */ /* - Copyright 2017 Will Winder + Copyright 2017-2020 Will Winder This file is part of Universal Gcode Sender (UGS). @@ -30,12 +27,14 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.gcode.util.Code; import static com.willwinder.universalgcodesender.gcode.util.Code.*; import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils; import com.willwinder.universalgcodesender.model.Position; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** + * Split lines into a series of smaller line segments. * * @author wwinder */ @@ -69,7 +68,7 @@ private Code hasLine(List commands) { @Override public List processCommand(String commandString, GcodeState state) throws GcodeParserException { - List commands = GcodeParser.processCommand(commandString, 0, state); + List commands = GcodeParserUtils.processCommand(commandString, 0, state); List results = new ArrayList<>(); 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 02ba759f67..d3e9507e1b 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MeshLeveler.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MeshLeveler.java @@ -18,11 +18,11 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.universalgcodesender.gcode.processors; -import com.willwinder.universalgcodesender.gcode.GcodeParser; import com.willwinder.universalgcodesender.gcode.GcodeParser.GcodeMeta; import com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils; import com.willwinder.universalgcodesender.gcode.GcodeState; import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils; import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.model.Position; import com.willwinder.universalgcodesender.model.UnitUtils; @@ -142,7 +142,7 @@ private boolean ensureJustLines(List commands) throws GcodeParserExce @Override public List processCommand(final String commandString, GcodeState state) throws GcodeParserException { - List commands = GcodeParser.processCommand(commandString, 0, state); + List commands = GcodeParserUtils.processCommand(commandString, 0, state); // If there are no lines, return unmodified input. if (!ensureJustLines(commands)) { diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MirrorProcessor.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MirrorProcessor.java new file mode 100644 index 0000000000..ce0e828613 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/MirrorProcessor.java @@ -0,0 +1,125 @@ +/* + Copyright 2020 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.gcode.processors; + +import com.willwinder.universalgcodesender.gcode.GcodeParser; +import com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils; +import com.willwinder.universalgcodesender.gcode.GcodeState; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils; +import com.willwinder.universalgcodesender.model.PartialPosition; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UnitUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils.normalizeCommand; + +/** + * A processor that will mirror the model horizontally on a given center x position. + * If the commands have arcs they will be expanded to line segments first. + * + * @author Joacim Breiler + */ +public class MirrorProcessor implements CommandProcessor { + private final PartialPosition center; + private ArcExpander arcExpander; + + /** + * Constructor + * + * @param center the position that will be the center to mirror from + */ + public MirrorProcessor(PartialPosition center) { + this.center = center; + arcExpander = new ArcExpander(true, 0.1); + } + + @Override + public List processCommand(String command, GcodeState state) throws GcodeParserException { + List commands = expandArcsToLines(command, state); + return commands.stream() + .map(c -> { + try { + return mirrorCommandCoordinates(c, state); + } catch (GcodeParserException e) { + throw new RuntimeException("Could not rotate the given command coordinate", e); + } + }) + .flatMap(Collection::stream).collect(Collectors.toList()); + } + + private List mirrorCommandCoordinates(String command, GcodeState state) throws GcodeParserException { + List gcodeMetas = GcodeParserUtils.processCommand(command, 0, state); + if (!isMovement(gcodeMetas)) { + return Collections.singletonList(command); + } + + return gcodeMetas.stream() + .map(gcodeMeta -> { + UnitUtils.Units currentUnits = UnitUtils.Units.getUnits(gcodeMeta.state.units); + Position start = state.currentPoint + .getPositionIn(currentUnits); + + Position end = gcodeMeta.point.point() + .getPositionIn(currentUnits); + double diffFromCenter = end.getX() - center.getPositionIn(currentUnits).getX(); + double newX = end.getX() - (diffFromCenter * 2); + end.setX(newX); + + String adjustedCommand = GcodePreprocessorUtils.generateLineFromPoints( + gcodeMeta.code, start, end, gcodeMeta.state.inAbsoluteMode, null); + + try { + return normalizeCommand(adjustedCommand, gcodeMeta.state); + } catch (GcodeParserException e) { + return adjustedCommand; + } + }) + .collect(Collectors.toList()); + } + + private boolean isMovement(List commands) { + if (commands == null) return false; + boolean hasLine = false; + for (GcodeParser.GcodeMeta command : commands) { + switch (command.code) { + case G0: + case G1: + case G2: + case G3: + hasLine = true; + break; + } + } + return hasLine; + } + + private List expandArcsToLines(String command, GcodeState state) throws GcodeParserException { + return arcExpander.processCommand(command, state); + } + + @Override + public String getHelp() { + return "Mirrors the model"; + } +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/RotateProcessor.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/RotateProcessor.java new file mode 100644 index 0000000000..3bf8b060f5 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/RotateProcessor.java @@ -0,0 +1,148 @@ +/* + Copyright 2020 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.gcode.processors; + +import com.willwinder.universalgcodesender.gcode.GcodeParser; +import com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils; +import com.willwinder.universalgcodesender.gcode.GcodeState; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UnitUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils.normalizeCommand; + +/** + * A processor that will rotate the model around the given center position using the given rotation in radians. + * If the commands have arcs they will be expanded to line segments first. + * + * @author Joacim Breiler + */ +public class RotateProcessor implements CommandProcessor { + + private ArcExpander arcExpander; + private Position center; + private double rotation; + + public RotateProcessor() { + init(new Position(0, 0, 0, UnitUtils.Units.MM), 0); + } + + public RotateProcessor(Position center, double rotation) { + init(center, rotation); + } + + private void init(Position center, double rotation) { + this.center = center; + this.rotation = rotation; + this.arcExpander = new ArcExpander(true, 0.1); + } + + public Position getCenter() { + return center; + } + + public void setCenter(Position center) { + this.center = center; + } + + public double getRotation() { + return rotation; + } + + public void setRotation(double rotation) { + this.rotation = rotation; + } + + @Override + public List processCommand(String command, GcodeState state) throws GcodeParserException { + // Ignore this processor if no rotation should be made + if (rotation == 0) { + return Collections.singletonList(command); + } + + List commands = expandArcsToLines(command, state); + return commands.stream() + .map(c -> { + try { + return rotateCommandCoordinates(c, state); + } catch (GcodeParserException e) { + throw new RuntimeException("Could not rotate the given command coordinate", e); + } + }) + .flatMap(Collection::stream).collect(Collectors.toList()); + } + + private List expandArcsToLines(String command, GcodeState state) throws GcodeParserException { + return arcExpander.processCommand(command, state); + } + + private List rotateCommandCoordinates(String command, GcodeState state) throws GcodeParserException { + List gcodeMetas = GcodeParserUtils.processCommand(command, 0, state); + if (!isMovement(gcodeMetas)) { + return Collections.singletonList(command); + } + return gcodeMetas.stream() + .map(gcodeMeta -> { + UnitUtils.Units currentUnits = UnitUtils.Units.getUnits(gcodeMeta.state.units); + Position start = state.currentPoint + .getPositionIn(currentUnits); + + Position end = gcodeMeta.point.point() + .getPositionIn(currentUnits); + end = end.rotate(center.getPositionIn(currentUnits), rotation); + + String adjustedCommand = GcodePreprocessorUtils.generateLineFromPoints( + gcodeMeta.code, start, end, gcodeMeta.state.inAbsoluteMode, null); + + try { + return normalizeCommand(adjustedCommand, gcodeMeta.state); + } catch (GcodeParserException e) { + return adjustedCommand; + } + }) + .collect(Collectors.toList()); + } + + private boolean isMovement(List commands) { + if (commands == null) return false; + boolean hasLine = false; + for (GcodeParser.GcodeMeta command : commands) { + switch (command.code) { + case G0: + case G1: + case G2: + case G3: + hasLine = true; + break; + } + } + return hasLine; + } + + @Override + public String getHelp() { + return "Rotates the model 180 degrees"; + } +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/RunFromProcessor.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/RunFromProcessor.java index fb19285a1d..c174b3211c 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/RunFromProcessor.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/RunFromProcessor.java @@ -24,35 +24,49 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; import com.willwinder.universalgcodesender.model.Position; +import java.util.Collections; import java.util.List; import static com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils.normalizeCommand; public class RunFromProcessor implements CommandProcessor { - private final int runFromLine; + private int lineNumber; private GcodeParser parser = new GcodeParser(); private Double clearanceHeight = 0.0; - /** * Truncates gcode to the specified line, and rewrites the preamble with the GcodeState. + * * @param lineNum line where the program should run from. */ public RunFromProcessor(int lineNum) { - runFromLine = lineNum; + lineNumber = lineNum; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; } @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { + // The processor is not activated + if (lineNumber == 0) { + return Collections.singletonList(command); + } + // Don't trust the input's machine state, this processor is discarding lines which would have updated it. Position pos = parser.getCurrentState().currentPoint; - if (state.commandNumber < runFromLine) { + if (state.commandNumber < lineNumber) { parser.addCommand(command); clearanceHeight = Math.max(clearanceHeight, pos.z); return ImmutableList.of(); } - if (state.commandNumber == runFromLine) { + if (state.commandNumber == lineNumber) { String moveToClearanceHeight = "G0Z" + clearanceHeight; String moveToXY = "G0X" + pos.x + "Y" + pos.y; diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/TranslateProcessor.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/TranslateProcessor.java new file mode 100644 index 0000000000..67ae41ffa1 --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/processors/TranslateProcessor.java @@ -0,0 +1,138 @@ +/* + Copyright 2020 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.gcode.processors; + +import com.willwinder.universalgcodesender.gcode.GcodeParser; +import com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils; +import com.willwinder.universalgcodesender.gcode.GcodeState; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UnitUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils.normalizeCommand; + +/** + * A processor that will translate the model using the given offset. + * If the commands have arcs they will be expanded to line segments first. + * + * @author Joacim Breiler + */ +public class TranslateProcessor implements CommandProcessor { + + private ArcExpander arcExpander; + private Position offset; + + public TranslateProcessor() { + init(new Position(0, 0, 0, UnitUtils.Units.MM)); + } + + public TranslateProcessor(Position offset) { + init(offset); + } + + private void init(Position offset) { + this.offset = offset; + this.arcExpander = new ArcExpander(true, 0.1); + } + + public Position getOffset() { + return offset; + } + + public void setOffset(Position offset) { + this.offset = offset; + } + + @Override + public List processCommand(String command, GcodeState state) throws GcodeParserException { + // Ignore this processor if no translation should be made + if (offset.x == 0 && offset.y == 0 && offset.z == 0) { + return Collections.singletonList(command); + } + + List commands = expandArcsToLines(command, state); + return commands.stream() + .map(c -> { + try { + return translateCoordinates(c, state); + } catch (GcodeParserException e) { + throw new RuntimeException("Could not rotate the given command coordinate", e); + } + }) + .flatMap(Collection::stream).collect(Collectors.toList()); + } + + private List expandArcsToLines(String command, GcodeState state) throws GcodeParserException { + return arcExpander.processCommand(command, state); + } + + private List translateCoordinates(String command, GcodeState state) throws GcodeParserException { + List gcodeMetas = GcodeParserUtils.processCommand(command, 0, state); + if (!isMovement(gcodeMetas)) { + return Collections.singletonList(command); + } + return gcodeMetas.stream() + .map(gcodeMeta -> { + UnitUtils.Units currentUnits = UnitUtils.Units.getUnits(gcodeMeta.state.units); + Position start = state.currentPoint + .getPositionIn(currentUnits); + + Position end = gcodeMeta.point.point() + .getPositionIn(currentUnits); + end.sub(offset.getPositionIn(currentUnits)); + + String adjustedCommand = GcodePreprocessorUtils.generateLineFromPoints( + gcodeMeta.code, start, end, gcodeMeta.state.inAbsoluteMode, null); + + try { + return normalizeCommand(adjustedCommand, gcodeMeta.state); + } catch (GcodeParserException e) { + return adjustedCommand; + } + }) + .collect(Collectors.toList()); + } + + private boolean isMovement(List commands) { + if (commands == null) return false; + boolean hasLine = false; + for (GcodeParser.GcodeMeta command : commands) { + switch (command.code) { + case G0: + case G1: + case G2: + case G3: + hasLine = true; + break; + } + } + return hasLine; + } + + @Override + public String getHelp() { + return "Translates to model in 3 dimensional space"; + } +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/gcode/util/GcodeParserUtils.java b/ugs-core/src/com/willwinder/universalgcodesender/gcode/util/GcodeParserUtils.java index 09e46f132d..6d69a9e2a0 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/gcode/util/GcodeParserUtils.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/gcode/util/GcodeParserUtils.java @@ -18,9 +18,15 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.universalgcodesender.gcode.util; +import com.google.common.collect.Iterables; import com.willwinder.universalgcodesender.gcode.GcodeParser; import com.willwinder.universalgcodesender.gcode.GcodePreprocessorUtils; +import com.willwinder.universalgcodesender.gcode.GcodeState; +import com.willwinder.universalgcodesender.i18n.Localization; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UnitUtils; import com.willwinder.universalgcodesender.types.GcodeCommand; +import com.willwinder.universalgcodesender.types.PointSegment; import com.willwinder.universalgcodesender.utils.GcodeStreamReader; import com.willwinder.universalgcodesender.utils.IGcodeStreamReader; import com.willwinder.universalgcodesender.utils.IGcodeWriter; @@ -30,16 +36,330 @@ This file is part of Universal Gcode Sender (UGS). import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static com.willwinder.universalgcodesender.gcode.util.Code.G20; +import static com.willwinder.universalgcodesender.gcode.util.Code.G21; +import static com.willwinder.universalgcodesender.gcode.util.Code.G90; +import static com.willwinder.universalgcodesender.gcode.util.Code.G90_1; +import static com.willwinder.universalgcodesender.gcode.util.Code.G91; +import static com.willwinder.universalgcodesender.gcode.util.Code.G91_1; +import static com.willwinder.universalgcodesender.gcode.util.Code.ModalGroup.Motion; +import static com.willwinder.universalgcodesender.gcode.util.Code.UNKNOWN; /** * * @author wwinder */ public class GcodeParserUtils { - private static final Logger logger = Logger.getLogger(GcodeParserUtils.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GcodeParserUtils.class.getName()); + + /** + * For backwards compatibility this method calls processCommand with includeNonMotionStates = false. + */ + public static List processCommand(String command, int line, final GcodeState inputState) + throws GcodeParserException { + return processCommand(command, line, inputState, false); + } + + /** + * Process command given an initial state. This method will not modify its + * input parameters. + * + * @param includeNonMotionStates Create gcode meta responses even if there is no motion, for example "F100" will not + * return a GcodeMeta entry unless this flag is set to true. + */ + public static List processCommand(String command, int line, final GcodeState inputState, + boolean includeNonMotionStates) + throws GcodeParserException { + List args = GcodePreprocessorUtils.splitCommand(command); + if (args.isEmpty()) return null; + + // Initialize with original state + GcodeState state = inputState.copy(); + + state.commandNumber = line; + + // handle M codes. + Set mCodes = GcodePreprocessorUtils.getMCodes(args); + for (Code c : mCodes) { + switch (c.getType()) { + case Spindle: + state.spindle = c; + break; + case Coolant: + state.coolant = c; + break; + default: + break; + } + } + + List fCodes = GcodePreprocessorUtils.parseCodes(args, 'F'); + if (!fCodes.isEmpty()) { + try { + state.speed = Double.parseDouble(Iterables.getOnlyElement(fCodes)); + } catch (IllegalArgumentException e) { + throw new GcodeParserException("Multiple F-codes on one line."); + } + } + + List sCodes = GcodePreprocessorUtils.parseCodes(args, 'S'); + if (!sCodes.isEmpty()) { + try { + state.spindleSpeed = Double.parseDouble(Iterables.getOnlyElement(sCodes)); + } catch (IllegalArgumentException e) { + throw new GcodeParserException("Multiple S-codes on one line."); + } + } + + // Gather G codes. + Set gCodes = GcodePreprocessorUtils.getGCodes(args); + + boolean hasAxisWords = GcodePreprocessorUtils.hasAxisWords(args); + + // Error to mix group 1 (Motion) and certain group 0 (NonModal) codes (G10, G28, G30, G92) + Collection motionCodes = gCodes.stream() + .filter(Code::consumesMotion) + .collect(Collectors.toList()); + + // 1 motion code per line. + if (motionCodes.size() > 1) { + throw new GcodeParserException(Localization.getString("parser.gcode.multiple-axis-commands") + + ": " + StringUtils.join(motionCodes, ", ")); + } + + // If there are axis words and nothing to use them, add the currentMotionMode. + if (hasAxisWords && motionCodes.isEmpty() && state.currentMotionMode != null) { + gCodes.add(state.currentMotionMode); + } + + // Apply each code to the state. + List results = new ArrayList<>(); + for (Code i : gCodes) { + if (i == UNKNOWN) { + LOGGER.warning("An unknown gcode command was detected in: " + command); + } else { + GcodeParser.GcodeMeta meta = handleGCode(i, args, line, state); + meta.command = command; + // Commands like 'G21' don't return a point segment. + if (meta.point != null) { + meta.point.setSpeed(state.speed); + } + results.add(meta); + } + } + + // Return updated state / command. + if (results.isEmpty() && includeNonMotionStates) { + GcodeParser.GcodeMeta meta = new GcodeParser.GcodeMeta(); + meta.state = state; + meta.command = command; + meta.code = state.currentMotionMode; + return Collections.singletonList(meta); + } + + return results; + } + + private static PointSegment addProbePointSegment(Position nextPoint, boolean fastTraverse, int line, GcodeState state) { + PointSegment ps = addLinearPointSegment(nextPoint, fastTraverse, line, state); + ps.setIsProbe(true); + return ps; + } + + /** + * Create a PointSegment representing the linear command. + */ + private static PointSegment addLinearPointSegment(Position nextPoint, boolean fastTraverse, int line, GcodeState state) { + if (nextPoint == null) { + return null; + } + + PointSegment ps = new PointSegment(nextPoint, line); + + boolean zOnly = false; + + // Check for z-only + if ((state.currentPoint.x == nextPoint.x) && + (state.currentPoint.y == nextPoint.y) && + (state.currentPoint.z != nextPoint.z)) { + zOnly = true; + } + + ps.setIsMetric(state.isMetric); + ps.setIsZMovement(zOnly); + ps.setIsFastTraverse(fastTraverse); + + // Save off the endpoint. + state.currentPoint = nextPoint; + return ps; + } + + /** + * Create a PointSegment representing the arc command. + */ + private static PointSegment addArcPointSegment(Position nextPoint, boolean clockwise, List args, int line, GcodeState state) { + if (nextPoint == null) { + return null; + } + + PointSegment ps = new PointSegment(nextPoint, line); + + PlaneFormatter plane = new PlaneFormatter(state.plane); + Position center = + GcodePreprocessorUtils.updateCenterWithCommand( + args, state.currentPoint, nextPoint, state.inAbsoluteIJKMode, clockwise, plane); + + double radius = GcodePreprocessorUtils.parseCoord(args, 'R'); + + // Calculate radius if necessary, according to the current G17/18/19 Plane + if (Double.isNaN(radius)) { + + radius = Math.sqrt( + Math.pow(plane.axis0(state.currentPoint) - plane.axis0(center), 2.0) + + Math.pow(plane.axis1(state.currentPoint) - plane.axis1(center), 2.0)); + } + + ps.setIsMetric(state.isMetric); + ps.setArcCenter(center); + ps.setIsArc(true); + ps.setRadius(radius); + ps.setIsClockwise(clockwise); + ps.setPlaneState(state.plane); + + // Save off the endpoint. + state.currentPoint = nextPoint; + return ps; + } + + /** + * Branch parser to handle specific gcode command. + *

+ * A copy of the state object should go in the resulting GcodeMeta object. + */ + private static GcodeParser.GcodeMeta handleGCode(final Code code, List args, int line, GcodeState state) + throws GcodeParserException { + GcodeParser.GcodeMeta meta = new GcodeParser.GcodeMeta(); + + meta.code = code; + + Position nextPoint = null; + + // If it is a movement code make sure it has some coordinates. + if (code.consumesMotion()) { + nextPoint = GcodePreprocessorUtils.updatePointWithCommand(args, state.currentPoint, state.inAbsoluteMode); + + if (nextPoint == null) { + if (!code.motionOptional()) { + throw new GcodeParserException( + Localization.getString("parser.gcode.missing-axis-commands") + ": " + code); + } + } + } + + if (nextPoint == null && meta.point != null) { + nextPoint = meta.point.point(); + } + + switch (code) { + case G0: + meta.point = addLinearPointSegment(nextPoint, true, line, state); + break; + case G1: + meta.point = addLinearPointSegment(nextPoint, false, line, state); + break; + + // Arc command. + case G2: + meta.point = addArcPointSegment(nextPoint, true, args, line, state); + break; + case G3: + meta.point = addArcPointSegment(nextPoint, false, args, line, state); + break; + + case G17: + case G18: + case G19: + case G17_1: + case G18_1: + case G19_1: + state.plane = Plane.lookup(code); + break; + + //inch + case G20: + state.isMetric = false; + state.units = G20; + state.currentPoint = state.currentPoint.getPositionIn(UnitUtils.Units.INCH); + break; + //mm + case G21: + state.isMetric = true; + state.units = G21; + state.currentPoint = state.currentPoint.getPositionIn(UnitUtils.Units.MM); + break; + + // Probe: http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g38 + case G38_2: // probe toward workpiece, stop on contact, signal error if failure + case G38_3: // probe toward workpiece, stop on contact + case G38_4: // probe away from workpiece, stop on loss of contact, signal error if failure + case G38_5: // probe away from workpiece, stop on loss of contact + meta.point = addProbePointSegment(nextPoint, true, line, state); + break; + + // These are not used in the visualizer. + case G54: + case G55: + case G56: + case G57: + case G58: + case G59: + case G59_1: + case G59_2: + case G59_3: + state.offset = code; + break; + + case G90: + state.inAbsoluteMode = true; + state.distanceMode = G90; + break; + case G91: + state.inAbsoluteMode = false; + state.distanceMode = G91; + break; + + case G90_1: + state.inAbsoluteIJKMode = true; + state.arcDistanceMode = G90_1; + break; + case G91_1: + state.inAbsoluteIJKMode = false; + state.arcDistanceMode = G91_1; + break; + + case G93: + case G94: + case G95: + state.feedMode = code; + break; + default: + break; + } + if (code.getType() == Motion) { + state.currentMotionMode = code; + } + meta.state = state.copy(); + return meta; + } /** * Helper method to apply processors to gcode. @@ -62,7 +382,7 @@ public static void processAndExport(GcodeParser gcp, File input, IGcodeWriter ou */ private static void preprocessAndWrite(GcodeParser gcp, IGcodeWriter gsw, String command, String comment, int idx) throws GcodeParserException { if (idx % 100000 == 0) { - logger.log(Level.FINE, "gcode processing line: " + idx); + LOGGER.log(Level.FINE, "gcode processing line: " + idx); } if (StringUtils.isEmpty(command)) { diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/BackendAPI.java b/ugs-core/src/com/willwinder/universalgcodesender/model/BackendAPI.java index c34a20324c..a8484c085a 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/BackendAPI.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/BackendAPI.java @@ -21,6 +21,7 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.IController; import com.willwinder.universalgcodesender.gcode.GcodeParser; +import com.willwinder.universalgcodesender.gcode.processors.CommandProcessor; import com.willwinder.universalgcodesender.listeners.MessageListener; import com.willwinder.universalgcodesender.listeners.MessageType; import com.willwinder.universalgcodesender.model.UnitUtils.Units; @@ -58,10 +59,29 @@ public interface BackendAPI extends BackendAPIReadOnly { * Modify the currently processed gcode with a provided gcode parser. * This can be used for post-processing tasks like rotating a gcode file. * @param gcp externally configured gcode parser. - * @throws Exception + * @throws Exception + * @deprecated this will alter the gcode parser entirely, please use {@link #applyCommandProcessor(CommandProcessor)} + * to change the behaviour of the gcode parser. */ void applyGcodeParser(GcodeParser gcp) throws Exception; + /** + * Adds a command processor and applies it to currently loaded program and subsequent + * loaded gcode programs. + * + * @param commandProcessor a command processor. + * @throws Exception + */ + void applyCommandProcessor(CommandProcessor commandProcessor) throws Exception; + + /** + * Removes a command processor. + * + * @param commandProcessor a command processor. + * @throws Exception + */ + void removeCommandProcessor(CommandProcessor commandProcessor) throws Exception; + /** * Process the currently loaded gcode file and export it to a file. * Intended primarily as "save and export" style preprocessor option. @@ -144,4 +164,4 @@ public interface BackendAPI extends BackendAPIReadOnly { * @param message the message to be written */ void dispatchMessage(MessageType messageType, String message); -} \ No newline at end of file +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java b/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java index b8d80e1d66..14f7cff8ab 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/GUIBackend.java @@ -175,7 +175,7 @@ protected void preprocessAndExportToFile(GcodeParser gcp, File input, IGcodeWrit private void initGcodeParser() { // Configure gcode parser. - gcp.resetCommandProcessors(); + gcp.clearCommandProcessors(); try { List processors = FirmwareUtils.getParserFor(firmware, settings).orElse(null); @@ -416,12 +416,17 @@ private File getTempDir() { @Override public void setGcodeFile(File file) throws Exception { logger.log(Level.INFO, "Setting gcode file."); + this.sendUGSEvent(new UGSEvent(FileState.OPENING_FILE, file.getAbsolutePath()), false); initGcodeParser(); this.gcodeFile = file; + processGcodeFile(); + } + + private void processGcodeFile() throws Exception { this.processedGcodeFile = null; this.sendUGSEvent(new UGSEvent(FileState.FILE_LOADING, - file.getAbsolutePath()), false); + this.gcodeFile.getAbsolutePath()), false); initializeProcessedLines(true, this.gcodeFile, this.gcp); @@ -482,7 +487,27 @@ public void applyGcodeParser(GcodeParser parser) throws Exception { this.setGcodeFile(target); } - + + @Override + public void applyCommandProcessor(CommandProcessor commandProcessor) throws Exception { + logger.log(Level.INFO, "Applying new command processor"); + gcp.addCommandProcessor(commandProcessor); + + if(gcodeFile != null) { + processGcodeFile(); + } + } + + @Override + public void removeCommandProcessor(CommandProcessor commandProcessor) throws Exception { + gcp.removeCommandProcessor(commandProcessor); + processGcodeFile(); + + if(gcodeFile != null) { + processGcodeFile(); + } + } + @Override public File getGcodeFile() { logger.log(Level.FINEST, "Getting gcode file."); diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/Position.java b/ugs-core/src/com/willwinder/universalgcodesender/model/Position.java index cefdf7e826..a2f42aa94f 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/Position.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/Position.java @@ -21,9 +21,9 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.model.UnitUtils.Units; import org.apache.commons.lang3.builder.ToStringBuilder; -import java.util.Objects; import javax.vecmath.Point3d; import javax.vecmath.Tuple3d; +import java.util.Objects; public class Position extends Point3d { @@ -55,7 +55,7 @@ public boolean equals(final Object other) { @Override public boolean equals(final Tuple3d o) { if (o instanceof Position) { - return super.equals(o) && units == ((Position)o).units; + return super.equals(o) && units == ((Position) o).units; } return super.equals(o); } @@ -78,7 +78,7 @@ public Units getUnits() { public Position getPositionIn(Units units) { double scale = UnitUtils.scaleUnits(this.units, units); - return new Position(x*scale, y*scale, z*scale, units); + return new Position(x * scale, y * scale, z * scale, units); } public double get(Axis axis) { @@ -93,4 +93,37 @@ public double get(Axis axis) { return 0; } } + + public void set(Axis axis, double value) { + switch (axis) { + case X: + setX(value); + break; + case Y: + setY(value); + break; + case Z: + setZ(value); + break; + default: + } + } + + /** + * Rotates this point around the center with the given angle in radians and returns a new position + * + * @param center the XY position to rotate around + * @param radians the radians to rotate clock wise + * @return a new rotated position + */ + public Position rotate(Position center, double radians) { + double cosA = Math.cos(radians); + double sinA = Math.sin(radians); + + return new Position( + center.x + (cosA * (x - center.x) + sinA * (y - center.y)), + center.y + (-sinA * (x - center.x) + cosA * (y - center.y)), + z, + units); + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/model/UGSEvent.java b/ugs-core/src/com/willwinder/universalgcodesender/model/UGSEvent.java index 685e884860..e716ea9bb5 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/model/UGSEvent.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/model/UGSEvent.java @@ -50,6 +50,7 @@ public enum EventType { } public enum FileState { + OPENING_FILE, FILE_LOADING, FILE_LOADED, FILE_STREAM_COMPLETE diff --git a/ugs-core/src/com/willwinder/universalgcodesender/services/RunFromService.java b/ugs-core/src/com/willwinder/universalgcodesender/services/RunFromService.java new file mode 100644 index 0000000000..8494b3661d --- /dev/null +++ b/ugs-core/src/com/willwinder/universalgcodesender/services/RunFromService.java @@ -0,0 +1,61 @@ +/* + Copyright 2020 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.services; + +import com.willwinder.universalgcodesender.gcode.processors.RunFromProcessor; +import com.willwinder.universalgcodesender.listeners.UGSEventListener; +import com.willwinder.universalgcodesender.model.BackendAPI; +import com.willwinder.universalgcodesender.model.UGSEvent; + +/** + * A service that will handle skipping to given line numbers in a loaded gcode program. When this service is created it + * will load a gcode processor to the backend which can be used to skip lines in the currently loaded program. + * + * @author Joacim Breiler + */ +public class RunFromService implements UGSEventListener { + private final BackendAPI backend; + private RunFromProcessor runFromProcessor = new RunFromProcessor(0); + + public RunFromService(BackendAPI backend) { + this.backend = backend; + try { + this.backend.applyCommandProcessor(runFromProcessor); + } catch (Exception e) { + // Never mind this + } + this.backend.addUGSEventListener(this); + } + + public void runFromLine(int lineNumber) { + try { + this.runFromProcessor.setLineNumber(lineNumber); + this.backend.applyCommandProcessor(runFromProcessor); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void UGSEvent(UGSEvent evt) { + if(evt.isFileChangeEvent() && evt.getFileState() == UGSEvent.FileState.OPENING_FILE) { + runFromProcessor.setLineNumber(0); + } + } +} diff --git a/ugs-core/src/com/willwinder/universalgcodesender/utils/MathUtils.java b/ugs-core/src/com/willwinder/universalgcodesender/utils/MathUtils.java index c6a9357567..0bd4328b2b 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/utils/MathUtils.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/utils/MathUtils.java @@ -19,6 +19,8 @@ This file is part of Universal Gcode Sender (UGS). package com.willwinder.universalgcodesender.utils; import com.willwinder.universalgcodesender.model.PartialPosition; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UnitUtils; import java.util.ArrayList; import java.util.Collections; @@ -86,7 +88,7 @@ public static List generateConvexHull(List poi * Given a list of points and the starting index, find the most adjacent outer point counter clockwise * from the starting point * - * @param points a list of points + * @param points a list of points * @param startingIndex the index of the point to originate from * @return the next outer point counter clockwise from the start point */ @@ -125,4 +127,50 @@ public static double round(double value, int decimals) { double power = Math.pow(10, decimals); return Math.round(value * power) / power; } + + /** + * Gets the center position given a list of line segments. + * + * @param points a list of points + * @return a new position in the center of the given points + */ + public static Position getCenter(List points) { + UnitUtils.Units units = points.get(0).getUnits(); + Position minPos = new Position(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, units); + Position maxPos = new Position(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE, units); + + for (PartialPosition point : points) { + PartialPosition positionInUnits = point.getPositionIn(units); + minPos.setX(Math.min(minPos.getX(), positionInUnits.getX())); + minPos.setY(Math.min(minPos.getY(), positionInUnits.getY())); + minPos.setZ(Math.min(minPos.getZ(), positionInUnits.getZ())); + maxPos.setX(Math.max(maxPos.getX(), positionInUnits.getX())); + maxPos.setY(Math.max(maxPos.getY(), positionInUnits.getY())); + maxPos.setZ(Math.max(maxPos.getZ(), positionInUnits.getZ())); + } + + return new Position( + minPos.getX() + ((maxPos.getX() - minPos.getX()) / 2), + minPos.getY() + ((maxPos.getY() - minPos.getY()) / 2), + minPos.getZ() + ((maxPos.getZ() - minPos.getZ()) / 2), + units); + } + + public static Position getLowerLeftCorner(List points) { + UnitUtils.Units units = points.get(0).getUnits(); + Position minPos = new Position(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, units); + + for (PartialPosition point : points) { + PartialPosition positionInUnits = point.getPositionIn(units); + minPos.setX(Math.min(minPos.getX(), positionInUnits.getX())); + minPos.setY(Math.min(minPos.getY(), positionInUnits.getY())); + minPos.setZ(Math.min(minPos.getZ(), positionInUnits.getZ())); + } + + return new Position( + minPos.getX(), + minPos.getY(), + minPos.getZ(), + units); + } } diff --git a/ugs-core/src/com/willwinder/universalgcodesender/visualizer/GcodeViewParse.java b/ugs-core/src/com/willwinder/universalgcodesender/visualizer/GcodeViewParse.java index 1e20edc08c..e947fb5cf5 100644 --- a/ugs-core/src/com/willwinder/universalgcodesender/visualizer/GcodeViewParse.java +++ b/ugs-core/src/com/willwinder/universalgcodesender/visualizer/GcodeViewParse.java @@ -136,7 +136,7 @@ public List toObjFromReader(IGcodeStreamReader reader, for (GcodeMeta meta : points) { if (meta.point != null) { addLinesFromPointSegment(start, meta.point, arcSegmentLength, lines); - start.set(meta.point.point()); + start = meta.point.point(); } } } diff --git a/ugs-core/test/com/willwinder/universalgcodesender/gcode/GcodeParserTest.java b/ugs-core/test/com/willwinder/universalgcodesender/gcode/GcodeParserTest.java index 22a2e9af5f..05f5e77e9d 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/gcode/GcodeParserTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/gcode/GcodeParserTest.java @@ -18,14 +18,20 @@ This file is part of Universal Gcode Sender (UGS). */ package com.willwinder.universalgcodesender.gcode; -import com.google.common.collect.Iterables; import com.willwinder.universalgcodesender.gcode.GcodeParser.GcodeMeta; -import com.willwinder.universalgcodesender.gcode.processors.*; +import com.willwinder.universalgcodesender.gcode.processors.ArcExpander; +import com.willwinder.universalgcodesender.gcode.processors.CommandLengthProcessor; +import com.willwinder.universalgcodesender.gcode.processors.CommentProcessor; +import com.willwinder.universalgcodesender.gcode.processors.DecimalProcessor; +import com.willwinder.universalgcodesender.gcode.processors.FeedOverrideProcessor; +import com.willwinder.universalgcodesender.gcode.processors.LineSplitter; +import com.willwinder.universalgcodesender.gcode.processors.M30Processor; +import com.willwinder.universalgcodesender.gcode.processors.MeshLeveler; +import com.willwinder.universalgcodesender.gcode.processors.WhitespaceProcessor; import com.willwinder.universalgcodesender.gcode.util.Code; import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; import com.willwinder.universalgcodesender.gcode.util.GcodeParserUtils; import com.willwinder.universalgcodesender.gcode.util.Plane; -import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.model.Position; import com.willwinder.universalgcodesender.model.UnitUtils.Units; import com.willwinder.universalgcodesender.types.GcodeCommand; @@ -48,10 +54,7 @@ This file is part of Universal Gcode Sender (UGS). import java.nio.file.Paths; import java.util.List; -import static com.willwinder.universalgcodesender.gcode.util.Code.*; import static com.willwinder.universalgcodesender.model.UnitUtils.Units.MM; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; /** @@ -196,7 +199,7 @@ public void testResetCommandProcessors() { GcodeParser instance = new GcodeParser(); instance.addCommandProcessor(new CommentProcessor()); assertEquals(1, instance.numCommandProcessors()); - instance.resetCommandProcessors(); + instance.clearCommandProcessors(); assertEquals(0, instance.numCommandProcessors()); } @@ -245,7 +248,7 @@ public void testPreprocessCommandFeedOverride() throws Exception { assertEquals(1, result.size()); assertEquals("G01X0.88889F100", result.get(0)); - instance.resetCommandProcessors(); + instance.clearCommandProcessors(); instance.addCommandProcessor(new CommentProcessor()); instance.addCommandProcessor(new FeedOverrideProcessor(200.)); instance.addCommandProcessor(new DecimalProcessor(5)); @@ -341,100 +344,4 @@ public void doubleParenCommentWithCommentProcessorTest() throws Exception { assertEquals(" G01 X10", result.get(0)); } - - @Test - public void stateInitialized() throws Exception { - GcodeState state = new GcodeState(); - Assert.assertEquals(G0, state.currentMotionMode); - List metaList = GcodeParser.processCommand("X1", 0, new GcodeState()); - Assert.assertEquals(1, metaList.size()); - GcodeMeta meta = Iterables.getOnlyElement(metaList); - Assert.assertEquals(G0, meta.code); - } - - @Test - public void multipleAxisWordCommands() throws Exception { - assertThatThrownBy(() -> GcodeParser.processCommand("G0G1X1X2", 0, new GcodeState())) - .isInstanceOf(GcodeParserException.class) - .hasMessageStartingWith(Localization.getString("parser.gcode.multiple-axis-commands")); - } - - @Test - public void missingAxisWords() throws Exception { - assertThatThrownBy(() -> GcodeParser.processCommand("G38.2", 0, new GcodeState())) - .isInstanceOf(GcodeParserException.class) - .hasMessage(Localization.getString("parser.gcode.missing-axis-commands") + ": G38.2"); - } - - @Test - public void duplicateFeedException() throws Exception { - assertThatThrownBy(() -> GcodeParser.processCommand("F1F1", 0, new GcodeState())) - .isInstanceOf(GcodeParserException.class) - .hasMessage("Multiple F-codes on one line."); - } - - @Test - public void duplicateSpindleException() throws Exception { - assertThatThrownBy(() -> GcodeParser.processCommand("S1S1", 0, new GcodeState())) - .isInstanceOf(GcodeParserException.class) - .hasMessage("Multiple S-codes on one line."); - } - - @Test - public void g28WithAxes() throws Exception { - // No exception - GcodeParser.processCommand("G28 X1 Y2 Z3", 0, new GcodeState()); - } - - @Test - public void g28NoAxes() throws Exception { - // No exception - GcodeParser.processCommand("G28", 0, new GcodeState()); - } - - @Test - public void motionNoAxes() throws Exception { - List metaList = GcodeParser.processCommand("G3", 0, new GcodeState()); - GcodeMeta meta = Iterables.getOnlyElement(metaList); - assertThat(meta.code).isEqualTo(G3); - assertThat(meta.state.currentPoint).isEqualTo(new Position(0, 0, 0, MM)); - } - - @Test - public void processCommandWithBlockComment() throws Exception { - List metaList = GcodeParser.processCommand("(hello world)G3", 0, new GcodeState()); - assertThat(metaList.size()).isEqualTo(1); - - metaList = GcodeParser.processCommand("(1)(2)G3(3)", 0, new GcodeState()); - assertThat(metaList.size()).isEqualTo(1); - } - - @Test - public void spaceInAxisWord() throws Exception { - List metaList = GcodeParser.processCommand("G \t1 X-1Y - 0.\t5Z\n1 .0", 0, new GcodeState()); - GcodeMeta meta = Iterables.getOnlyElement(metaList); - assertThat(meta.code).isEqualTo(G1); - assertThat(meta.state.currentPoint).isEqualTo(new Position(-1, -0.5, 1, MM)); - } - - @Test - public void fWordOnly() throws Exception { - List metaList = GcodeParser.processCommand("F100", 0, new GcodeState(), true); - GcodeMeta meta = Iterables.getOnlyElement(metaList); - assertThat(meta.state.speed).isEqualTo(100.0); - } - - @Test - public void fWordFromJogCommandShouldNotBeParsed() throws Exception { - List metaList = GcodeParser.processCommand("$J=G21G91X10F99", 0, new GcodeState(), true); - GcodeMeta meta = Iterables.getOnlyElement(metaList); - assertThat(meta.state.speed).isEqualTo(0.0); - } - - @Test - public void sWordOnly() throws Exception { - List metaList = GcodeParser.processCommand("S100", 0, new GcodeState(), true); - GcodeMeta meta = Iterables.getOnlyElement(metaList); - assertThat(meta.state.spindleSpeed).isEqualTo(100.0); - } } diff --git a/ugs-core/test/com/willwinder/universalgcodesender/gcode/processors/RotateProcessorTest.java b/ugs-core/test/com/willwinder/universalgcodesender/gcode/processors/RotateProcessorTest.java new file mode 100644 index 0000000000..5dfc403dfb --- /dev/null +++ b/ugs-core/test/com/willwinder/universalgcodesender/gcode/processors/RotateProcessorTest.java @@ -0,0 +1,66 @@ +package com.willwinder.universalgcodesender.gcode.processors; + +import com.willwinder.universalgcodesender.gcode.GcodeState; +import com.willwinder.universalgcodesender.gcode.util.Code; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UnitUtils; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +public class RotateProcessorTest { + @Test + public void rotateCommandShouldIncludeFeedRate() throws GcodeParserException { + RotateProcessor rotateProcessor = new RotateProcessor(new Position(0, 0, 0, UnitUtils.Units.MM), Math.PI); + + List commands = rotateProcessor.processCommand("G0 X1 Y0 F100", new GcodeState()); + + assertEquals(1, commands.size()); + assertEquals("F100.0S0.0G0X-1Y-0Z0", commands.get(0)); + } + + @Test + public void rotateCommandShouldIncludeSpindleSpeed() throws GcodeParserException { + RotateProcessor rotateProcessor = new RotateProcessor(new Position(0, 0, 0, UnitUtils.Units.MM), Math.PI); + + List commands = rotateProcessor.processCommand("G0 X1 Y0 S100", new GcodeState()); + + assertEquals(1, commands.size()); + assertEquals("F0.0S100.0G0X-1Y-0Z0", commands.get(0)); + } + + @Test + public void rotateCommandShouldIgnoreNoMotionCommands() throws GcodeParserException { + RotateProcessor rotateProcessor = new RotateProcessor(new Position(0, 0, 0, UnitUtils.Units.MM), Math.PI); + + List commands = rotateProcessor.processCommand("G21 G53", new GcodeState()); + + assertEquals(1, commands.size()); + assertEquals("G21 G53", commands.get(0)); + } + + @Test + public void rotateCommandShouldConvertArcs() throws GcodeParserException { + RotateProcessor rotateProcessor = new RotateProcessor(new Position(0, 0, 0, UnitUtils.Units.MM), Math.PI); + + List commands = rotateProcessor.processCommand("G2 X0. Y-0.5 I0.5 J0.0", new GcodeState()); + + assertEquals(28, commands.size()); + } + + @Test + public void rotateCommandShouldConvertCoordinatesInDifferentUnits() throws GcodeParserException { + RotateProcessor rotateProcessor = new RotateProcessor(new Position(25.4, 0, 0, UnitUtils.Units.MM), Math.PI); + GcodeState gcodeState = new GcodeState(); + gcodeState.units = Code.G20; // Inches + gcodeState.isMetric = false; + gcodeState.currentPoint = new Position(2,0,0, UnitUtils.Units.INCH); + + List commands = rotateProcessor.processCommand("G0 X2", gcodeState); + + assertEquals("F0.0S0.0G0X-0Y-0Z0", commands.get(0)); + } +} diff --git a/ugs-core/test/com/willwinder/universalgcodesender/gcode/util/GcodeParserUtilsTest.java b/ugs-core/test/com/willwinder/universalgcodesender/gcode/util/GcodeParserUtilsTest.java new file mode 100644 index 0000000000..47e83c614c --- /dev/null +++ b/ugs-core/test/com/willwinder/universalgcodesender/gcode/util/GcodeParserUtilsTest.java @@ -0,0 +1,116 @@ +package com.willwinder.universalgcodesender.gcode.util; + +import com.google.common.collect.Iterables; +import com.willwinder.universalgcodesender.gcode.GcodeParser; +import com.willwinder.universalgcodesender.gcode.GcodeState; +import com.willwinder.universalgcodesender.i18n.Localization; +import com.willwinder.universalgcodesender.model.Position; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +import static com.willwinder.universalgcodesender.gcode.util.Code.G0; +import static com.willwinder.universalgcodesender.gcode.util.Code.G1; +import static com.willwinder.universalgcodesender.gcode.util.Code.G3; +import static com.willwinder.universalgcodesender.model.UnitUtils.Units.MM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class GcodeParserUtilsTest { + @Test + public void stateInitialized() throws Exception { + GcodeState state = new GcodeState(); + Assert.assertEquals(G0, state.currentMotionMode); + List metaList = GcodeParserUtils.processCommand("X1", 0, new GcodeState()); + Assert.assertEquals(1, metaList.size()); + GcodeParser.GcodeMeta meta = Iterables.getOnlyElement(metaList); + Assert.assertEquals(G0, meta.code); + } + + @Test + public void multipleAxisWordCommands() { + assertThatThrownBy(() -> GcodeParserUtils.processCommand("G0G1X1X2", 0, new GcodeState())) + .isInstanceOf(GcodeParserException.class) + .hasMessageStartingWith(Localization.getString("parser.gcode.multiple-axis-commands")); + } + + @Test + public void missingAxisWords() { + assertThatThrownBy(() -> GcodeParserUtils.processCommand("G38.2", 0, new GcodeState())) + .isInstanceOf(GcodeParserException.class) + .hasMessage(Localization.getString("parser.gcode.missing-axis-commands") + ": G38.2"); + } + + @Test + public void duplicateFeedException() { + assertThatThrownBy(() -> GcodeParserUtils.processCommand("F1F1", 0, new GcodeState())) + .isInstanceOf(GcodeParserException.class) + .hasMessage("Multiple F-codes on one line."); + } + + @Test + public void duplicateSpindleException() { + assertThatThrownBy(() -> GcodeParserUtils.processCommand("S1S1", 0, new GcodeState())) + .isInstanceOf(GcodeParserException.class) + .hasMessage("Multiple S-codes on one line."); + } + + @Test + public void g28WithAxes() throws Exception { + // No exception + GcodeParserUtils.processCommand("G28 X1 Y2 Z3", 0, new GcodeState()); + } + + @Test + public void g28NoAxes() throws Exception { + // No exception + GcodeParserUtils.processCommand("G28", 0, new GcodeState()); + } + + @Test + public void motionNoAxes() throws Exception { + List metaList = GcodeParserUtils.processCommand("G3", 0, new GcodeState()); + GcodeParser.GcodeMeta meta = Iterables.getOnlyElement(metaList); + assertThat(meta.code).isEqualTo(G3); + assertThat(meta.state.currentPoint).isEqualTo(new Position(0, 0, 0, MM)); + } + + @Test + public void processCommandWithBlockComment() throws Exception { + List metaList = GcodeParserUtils.processCommand("(hello world)G3", 0, new GcodeState()); + assertThat(metaList.size()).isEqualTo(1); + + metaList = GcodeParserUtils.processCommand("(1)(2)G3(3)", 0, new GcodeState()); + assertThat(metaList.size()).isEqualTo(1); + } + + @Test + public void spaceInAxisWord() throws Exception { + List metaList = GcodeParserUtils.processCommand("G \t1 X-1Y - 0.\t5Z\n1 .0", 0, new GcodeState()); + GcodeParser.GcodeMeta meta = Iterables.getOnlyElement(metaList); + assertThat(meta.code).isEqualTo(G1); + assertThat(meta.state.currentPoint).isEqualTo(new Position(-1, -0.5, 1, MM)); + } + + @Test + public void fWordOnly() throws Exception { + List metaList = GcodeParserUtils.processCommand("F100", 0, new GcodeState(), true); + GcodeParser.GcodeMeta meta = Iterables.getOnlyElement(metaList); + assertThat(meta.state.speed).isEqualTo(100.0); + } + + @Test + public void fWordFromJogCommandShouldNotBeParsed() throws Exception { + List metaList = GcodeParserUtils.processCommand("$J=G21G91X10F99", 0, new GcodeState(), true); + GcodeParser.GcodeMeta meta = Iterables.getOnlyElement(metaList); + assertThat(meta.state.speed).isEqualTo(0.0); + } + + @Test + public void sWordOnly() throws Exception { + List metaList = GcodeParserUtils.processCommand("S100", 0, new GcodeState(), true); + GcodeParser.GcodeMeta meta = Iterables.getOnlyElement(metaList); + assertThat(meta.state.spindleSpeed).isEqualTo(100.0); + } +} diff --git a/ugs-core/test/com/willwinder/universalgcodesender/model/GUIBackendTest.java b/ugs-core/test/com/willwinder/universalgcodesender/model/GUIBackendTest.java index 58a175e4ca..8539701dbc 100644 --- a/ugs-core/test/com/willwinder/universalgcodesender/model/GUIBackendTest.java +++ b/ugs-core/test/com/willwinder/universalgcodesender/model/GUIBackendTest.java @@ -335,10 +335,11 @@ public void setGcodeFileShouldBeOk() throws Exception { // Then List events = eventArgumentCaptor.getAllValues(); - assertEquals(3, events.size()); - assertEquals(UGSEvent.FileState.FILE_LOADING, events.get(0).getFileState()); - assertEquals(UGSEvent.EventType.SETTING_EVENT, events.get(1).getEventType()); - assertEquals(UGSEvent.FileState.FILE_LOADED, events.get(2).getFileState()); + assertEquals(4, events.size()); + assertEquals(UGSEvent.FileState.OPENING_FILE, events.get(0).getFileState()); + assertEquals(UGSEvent.FileState.FILE_LOADING, events.get(1).getFileState()); + assertEquals(UGSEvent.EventType.SETTING_EVENT, events.get(2).getEventType()); + assertEquals(UGSEvent.FileState.FILE_LOADED, events.get(3).getFileState()); assertNotNull(instance.getProcessedGcodeFile()); } diff --git a/ugs-platform/ugs-platform-gcode-editor/src/main/java/com/willwinder/ugs/nbp/editor/actions/RunFromHere.java b/ugs-platform/ugs-platform-gcode-editor/src/main/java/com/willwinder/ugs/nbp/editor/actions/RunFromHere.java index 3400ba3479..94020e5190 100644 --- a/ugs-platform/ugs-platform-gcode-editor/src/main/java/com/willwinder/ugs/nbp/editor/actions/RunFromHere.java +++ b/ugs-platform/ugs-platform-gcode-editor/src/main/java/com/willwinder/ugs/nbp/editor/actions/RunFromHere.java @@ -20,9 +20,12 @@ import com.willwinder.ugs.nbp.lib.services.ActionReferenceLocalizer; import com.willwinder.ugs.nbp.lib.services.LocalizingService; import com.willwinder.universalgcodesender.gcode.GcodeParser; +import com.willwinder.universalgcodesender.gcode.processors.CommandProcessor; +import com.willwinder.universalgcodesender.gcode.processors.RotateProcessor; import com.willwinder.universalgcodesender.gcode.processors.RunFromProcessor; import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.model.BackendAPI; +import com.willwinder.universalgcodesender.services.RunFromService; import com.willwinder.universalgcodesender.utils.GUIHelpers; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; @@ -46,9 +49,11 @@ public final class RunFromHere implements ActionListener { private final EditorCookie context; public static final String NAME = Localization.getString("platform.menu.runFrom"); + private final RunFromService runFromService; public RunFromHere(EditorCookie context) { this.context = context; + this.runFromService = CentralLookup.getDefault().lookup(RunFromService.class); } @OnStart @@ -60,17 +65,12 @@ public ActionLocalizer() { @Override public void actionPerformed(ActionEvent ev) { - BackendAPI backend = CentralLookup.getDefault().lookup(BackendAPI.class); - Element root = context.getDocument().getDefaultRootElement(); int caretPosition = context.getOpenedPanes()[0].getCaretPosition(); int line = root.getElementIndex(caretPosition) + 1; - GcodeParser gcp = new GcodeParser(); - gcp.addCommandProcessor(new RunFromProcessor(line)); - try { - backend.applyGcodeParser(gcp); + runFromService.runFromLine(line); } catch (Exception e) { GUIHelpers.displayErrorDialog(e.getLocalizedMessage()); } diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/AbstractRotateAction.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/AbstractRotateAction.java new file mode 100644 index 0000000000..2fccf655fc --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/AbstractRotateAction.java @@ -0,0 +1,129 @@ +/* + Copyright 2020 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.nbp.core.actions; + +import com.willwinder.ugs.nbp.lib.lookup.CentralLookup; +import com.willwinder.universalgcodesender.gcode.processors.RotateProcessor; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.listeners.UGSEventListener; +import com.willwinder.universalgcodesender.model.BackendAPI; +import com.willwinder.universalgcodesender.model.PartialPosition; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UGSEvent; +import com.willwinder.universalgcodesender.uielements.helpers.LoaderDialogHelper; +import com.willwinder.universalgcodesender.utils.GUIHelpers; +import com.willwinder.universalgcodesender.utils.GcodeStreamReader; +import com.willwinder.universalgcodesender.utils.IGcodeStreamReader; +import com.willwinder.universalgcodesender.utils.MathUtils; +import com.willwinder.universalgcodesender.utils.ThreadHelper; +import com.willwinder.universalgcodesender.visualizer.GcodeViewParse; +import com.willwinder.universalgcodesender.visualizer.LineSegment; +import com.willwinder.universalgcodesender.visualizer.VisualizerUtils; + +import javax.swing.AbstractAction; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * An abstract action for applying rotation to a loaded model + */ +public abstract class AbstractRotateAction extends AbstractAction implements UGSEventListener { + + public static final String ICON_BASE = "resources/icons/rotation0.svg"; + public static final double ARC_SEGMENT_LENGTH = 0.5; + private final double rotation; + private BackendAPI backend; + + public AbstractRotateAction(double rotation) { + this.backend = CentralLookup.getDefault().lookup(BackendAPI.class); + this.backend.addUGSEventListener(this); + this.rotation = rotation; + setEnabled(isEnabled()); + } + + @Override + public void UGSEvent(UGSEvent cse) { + if (cse.isStateChangeEvent() || cse.isFileChangeEvent()) { + java.awt.EventQueue.invokeLater(() -> setEnabled(isEnabled())); + } + } + + @Override + public boolean isEnabled() { + return backend.getGcodeFile() != null; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (!isEnabled()) { + return; + } + + ThreadHelper.invokeLater(() -> { + try { + LoaderDialogHelper.showDialog("Rotating model", 1000, (Component) e.getSource()); + File gcodeFile = backend.getProcessedGcodeFile(); + Position center = getCenter(gcodeFile); + RotateProcessor rotateProcessor = new RotateProcessor(center, rotation); + backend.applyCommandProcessor(rotateProcessor); + LoaderDialogHelper.closeDialog(); + } catch (Exception ex) { + GUIHelpers.displayErrorDialog(ex.getLocalizedMessage()); + } + }); + } + + private Position getCenter(File gcodeFile) throws IOException, GcodeParserException { + List lineSegments = parseGcodeLinesFromFile(gcodeFile); + + // We only care about carving motion, filter those commands out + List pointList = lineSegments.parallelStream() + .filter(lineSegment -> !lineSegment.isFastTraverse()) + .flatMap(lineSegment -> { + // We map both the start and end points in MM + PartialPosition start = PartialPosition.from(lineSegment.getStart()); + PartialPosition end = PartialPosition.from(lineSegment.getEnd()); + return Stream.of(start, end); + }) + .distinct() + .collect(Collectors.toList()); + + return MathUtils.getCenter(pointList); + } + + private List parseGcodeLinesFromFile(File gcodeFile) throws IOException, GcodeParserException { + List result; + + GcodeViewParse gcvp = new GcodeViewParse(); + try (IGcodeStreamReader gsr = new GcodeStreamReader(gcodeFile)) { + result = gcvp.toObjFromReader(gsr, ARC_SEGMENT_LENGTH); + } catch (GcodeStreamReader.NotGcodeStreamFile e) { + List linesInFile = VisualizerUtils.readFiletoArrayList(gcodeFile.getAbsolutePath()); + result = gcvp.toObjRedux(linesInFile, ARC_SEGMENT_LENGTH); + } + + return result; + } +} diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/MirrorAction.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/MirrorAction.java new file mode 100644 index 0000000000..0909bff622 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/MirrorAction.java @@ -0,0 +1,129 @@ +package com.willwinder.ugs.nbp.core.actions; + +import com.willwinder.ugs.nbp.lib.lookup.CentralLookup; +import com.willwinder.ugs.nbp.lib.services.LocalizingService; +import com.willwinder.universalgcodesender.gcode.processors.MirrorProcessor; +import com.willwinder.universalgcodesender.gcode.processors.TranslateProcessor; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.listeners.UGSEventListener; +import com.willwinder.universalgcodesender.model.BackendAPI; +import com.willwinder.universalgcodesender.model.PartialPosition; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UGSEvent; +import com.willwinder.universalgcodesender.uielements.helpers.LoaderDialogHelper; +import com.willwinder.universalgcodesender.utils.GUIHelpers; +import com.willwinder.universalgcodesender.utils.GcodeStreamReader; +import com.willwinder.universalgcodesender.utils.IGcodeStreamReader; +import com.willwinder.universalgcodesender.utils.MathUtils; +import com.willwinder.universalgcodesender.utils.ThreadHelper; +import com.willwinder.universalgcodesender.visualizer.GcodeViewParse; +import com.willwinder.universalgcodesender.visualizer.LineSegment; +import com.willwinder.universalgcodesender.visualizer.VisualizerUtils; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.ImageUtilities; + +import javax.swing.AbstractAction; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@ActionID( + category = LocalizingService.CATEGORY_PROGRAM, + id = "MirrorAction") +@ActionRegistration( + iconBase = AbstractRotateAction.ICON_BASE, + displayName = "Mirror", + lazy = false) +@ActionReferences({ + @ActionReference( + path = LocalizingService.MENU_PROGRAM, + position = 1020) +}) +public class MirrorAction extends AbstractAction implements UGSEventListener { + + public static final String ICON_BASE = "resources/icons/mirror.svg"; + public static final double ARC_SEGMENT_LENGTH = 0.5; + private BackendAPI backend; + + public MirrorAction() { + this.backend = CentralLookup.getDefault().lookup(BackendAPI.class); + this.backend.addUGSEventListener(this); + + putValue("iconBase", ICON_BASE); + putValue(SMALL_ICON, ImageUtilities.loadImageIcon(ICON_BASE, false)); + putValue("menuText", "Mirror"); + putValue(NAME, "Mirror"); + + setEnabled(isEnabled()); + } + + @Override + public void UGSEvent(UGSEvent cse) { + if (cse.isStateChangeEvent() || cse.isFileChangeEvent()) { + java.awt.EventQueue.invokeLater(() -> setEnabled(isEnabled())); + } + } + + @Override + public boolean isEnabled() { + return backend.getGcodeFile() != null; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (!isEnabled()) { + return; + } + + ThreadHelper.invokeLater(() -> { + try { + LoaderDialogHelper.showDialog("Mirroring model", 1000, (Component) e.getSource()); + File gcodeFile = backend.getProcessedGcodeFile(); + Position center = getCenter(gcodeFile); + MirrorProcessor translateProcessor = new MirrorProcessor(PartialPosition.from(center)); + backend.applyCommandProcessor(translateProcessor); + LoaderDialogHelper.closeDialog(); + } catch (Exception ex) { + GUIHelpers.displayErrorDialog(ex.getLocalizedMessage()); + } + }); + } + + private Position getCenter(File gcodeFile) throws IOException, GcodeParserException { + List lineSegments = parseGcodeLinesFromFile(gcodeFile); + + // We only care about carving motion, filter those commands out + List pointList = lineSegments.parallelStream() + .filter(lineSegment -> !lineSegment.isFastTraverse()) + .flatMap(lineSegment -> { + PartialPosition start = PartialPosition.from(lineSegment.getStart()); + PartialPosition end = PartialPosition.from(lineSegment.getEnd()); + return Stream.of(start, end); + }) + .distinct() + .collect(Collectors.toList()); + + return MathUtils.getCenter(pointList); + } + + private List parseGcodeLinesFromFile(File gcodeFile) throws IOException, GcodeParserException { + List result; + + GcodeViewParse gcvp = new GcodeViewParse(); + try (IGcodeStreamReader gsr = new GcodeStreamReader(gcodeFile)) { + result = gcvp.toObjFromReader(gsr, ARC_SEGMENT_LENGTH); + } catch (GcodeStreamReader.NotGcodeStreamFile e) { + List linesInFile = VisualizerUtils.readFiletoArrayList(gcodeFile.getAbsolutePath()); + result = gcvp.toObjRedux(linesInFile, ARC_SEGMENT_LENGTH); + } + + return result; + } +} diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/OutlineAction.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/OutlineAction.java index 211ea2642a..92e40d1a10 100644 --- a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/OutlineAction.java +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/OutlineAction.java @@ -71,7 +71,7 @@ This file is part of Universal Gcode Sender (UGS). lazy = false) @ActionReferences({ @ActionReference( - path = LocalizingService.OutlineWindowPath, + path = LocalizingService.MENU_PROGRAM, position = 990) }) public final class OutlineAction extends AbstractAction implements UGSEventListener { diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RotateLeftAction.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RotateLeftAction.java new file mode 100644 index 0000000000..3f83ec8dbc --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RotateLeftAction.java @@ -0,0 +1,33 @@ +package com.willwinder.ugs.nbp.core.actions; + +import com.willwinder.ugs.nbp.lib.services.LocalizingService; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.ImageUtilities; + +@ActionID( + category = LocalizingService.CATEGORY_PROGRAM, + id = "RotateThreeQuarterAction") +@ActionRegistration( + iconBase = AbstractRotateAction.ICON_BASE, + displayName = "Rotate 270°", + lazy = false) +@ActionReferences({ + @ActionReference( + path = LocalizingService.MENU_PROGRAM, + position = 1003) +}) +public class RotateLeftAction extends AbstractRotateAction { + + public static final String ICON_BASE = "resources/icons/rotate_left.svg"; + + public RotateLeftAction() { + super((Math.PI / 2) * 3); + putValue("iconBase", ICON_BASE); + putValue(SMALL_ICON, ImageUtilities.loadImageIcon(ICON_BASE, false)); + putValue("menuText", "Rotate left"); + putValue(NAME, "Rotate left"); + } +} diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RotateRightAction.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RotateRightAction.java new file mode 100644 index 0000000000..36194d50d4 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RotateRightAction.java @@ -0,0 +1,33 @@ +package com.willwinder.ugs.nbp.core.actions; + +import com.willwinder.ugs.nbp.lib.services.LocalizingService; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.ImageUtilities; + +@ActionID( + category = LocalizingService.CATEGORY_PROGRAM, + id = "RotateQuarterAction") +@ActionRegistration( + iconBase = AbstractRotateAction.ICON_BASE, + displayName = "Rotate 90°", + lazy = false) +@ActionReferences({ + @ActionReference( + path = LocalizingService.MENU_PROGRAM, + position = 1001) +}) +public class RotateRightAction extends AbstractRotateAction { + + public static final String ICON_BASE = "resources/icons/rotate_right.svg"; + + public RotateRightAction() { + super(Math.PI / 2); + putValue("iconBase", ICON_BASE); + putValue(SMALL_ICON, ImageUtilities.loadImageIcon(ICON_BASE, false)); + putValue("menuText", "Rotate right"); + putValue(NAME, "Rotate right"); + } +} diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RunFromAction.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RunFromAction.java index 4b1d22ac8e..b52b776e07 100644 --- a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RunFromAction.java +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/RunFromAction.java @@ -21,11 +21,10 @@ 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.universalgcodesender.gcode.GcodeParser; -import com.willwinder.universalgcodesender.gcode.processors.RunFromProcessor; import com.willwinder.universalgcodesender.listeners.UGSEventListener; import com.willwinder.universalgcodesender.model.BackendAPI; import com.willwinder.universalgcodesender.model.UGSEvent; +import com.willwinder.universalgcodesender.services.RunFromService; import com.willwinder.universalgcodesender.utils.GUIHelpers; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; @@ -33,7 +32,9 @@ This file is part of Universal Gcode Sender (UGS). import org.openide.awt.ActionRegistration; import org.openide.util.ImageUtilities; -import javax.swing.*; +import javax.swing.AbstractAction; +import javax.swing.JFrame; +import javax.swing.JOptionPane; import java.awt.event.ActionEvent; @ActionID( @@ -44,9 +45,6 @@ This file is part of Universal Gcode Sender (UGS). displayName = "Run from...", lazy = false) @ActionReferences({ - //@ActionReference( - // path = "Toolbars/Run", - // position = 980), @ActionReference( path = LocalizingService.RunFromWindowPath, position = 1017) @@ -54,12 +52,15 @@ This file is part of Universal Gcode Sender (UGS). public final class RunFromAction extends AbstractAction implements UGSEventListener { public static final String ICON_BASE = "resources/icons/fast-forward.svg"; + private final RunFromService runFromService; private BackendAPI backend; public RunFromAction() { this.backend = CentralLookup.getDefault().lookup(BackendAPI.class); this.backend.addUGSEventListener(this); + this.runFromService = CentralLookup.getDefault().lookup(RunFromService.class); + putValue("iconBase", ICON_BASE); putValue(SMALL_ICON, ImageUtilities.loadImageIcon(ICON_BASE, false)); @@ -89,15 +90,13 @@ public void actionPerformed(ActionEvent e) { try { Integer result = Integer.parseInt( JOptionPane.showInputDialog( - new JFrame(), - "Enter a line number to start from.", - "Run From Action", - JOptionPane.QUESTION_MESSAGE - )); - - GcodeParser gcp = new GcodeParser(); - gcp.addCommandProcessor(new RunFromProcessor(result)); - backend.applyGcodeParser(gcp); + new JFrame(), + "Enter a line number to start from.", + "Run From Action", + JOptionPane.QUESTION_MESSAGE + )); + + runFromService.runFromLine(result); } catch (Exception ex) { GUIHelpers.displayErrorDialog(ex.getLocalizedMessage()); } diff --git a/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/TranslateToZeroAction.java b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/TranslateToZeroAction.java new file mode 100644 index 0000000000..3eb7dbedd8 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/java/com/willwinder/ugs/nbp/core/actions/TranslateToZeroAction.java @@ -0,0 +1,129 @@ +package com.willwinder.ugs.nbp.core.actions; + +import com.willwinder.ugs.nbp.lib.lookup.CentralLookup; +import com.willwinder.ugs.nbp.lib.services.LocalizingService; +import com.willwinder.universalgcodesender.gcode.processors.TranslateProcessor; +import com.willwinder.universalgcodesender.gcode.util.GcodeParserException; +import com.willwinder.universalgcodesender.listeners.UGSEventListener; +import com.willwinder.universalgcodesender.model.BackendAPI; +import com.willwinder.universalgcodesender.model.PartialPosition; +import com.willwinder.universalgcodesender.model.Position; +import com.willwinder.universalgcodesender.model.UGSEvent; +import com.willwinder.universalgcodesender.uielements.helpers.LoaderDialogHelper; +import com.willwinder.universalgcodesender.utils.GUIHelpers; +import com.willwinder.universalgcodesender.utils.GcodeStreamReader; +import com.willwinder.universalgcodesender.utils.IGcodeStreamReader; +import com.willwinder.universalgcodesender.utils.MathUtils; +import com.willwinder.universalgcodesender.utils.ThreadHelper; +import com.willwinder.universalgcodesender.visualizer.GcodeViewParse; +import com.willwinder.universalgcodesender.visualizer.LineSegment; +import com.willwinder.universalgcodesender.visualizer.VisualizerUtils; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.ImageUtilities; + +import javax.swing.AbstractAction; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@ActionID( + category = LocalizingService.CATEGORY_PROGRAM, + id = "TranslateToZeroAction") +@ActionRegistration( + iconBase = AbstractRotateAction.ICON_BASE, + displayName = "Translate to zero", + lazy = false) +@ActionReferences({ + @ActionReference( + path = LocalizingService.MENU_PROGRAM, + position = 1020) +}) +public class TranslateToZeroAction extends AbstractAction implements UGSEventListener { + + public static final String ICON_BASE = "resources/icons/translate.svg"; + public static final double ARC_SEGMENT_LENGTH = 0.5; + private BackendAPI backend; + + public TranslateToZeroAction() { + this.backend = CentralLookup.getDefault().lookup(BackendAPI.class); + this.backend.addUGSEventListener(this); + + putValue("iconBase", ICON_BASE); + putValue(SMALL_ICON, ImageUtilities.loadImageIcon(ICON_BASE, false)); + putValue("menuText", "Translate to zero"); + putValue(NAME, "Translate to zero"); + + setEnabled(isEnabled()); + } + + @Override + public void UGSEvent(UGSEvent cse) { + if (cse.isStateChangeEvent() || cse.isFileChangeEvent()) { + java.awt.EventQueue.invokeLater(() -> setEnabled(isEnabled())); + } + } + + @Override + public boolean isEnabled() { + return backend.getGcodeFile() != null; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (!isEnabled()) { + return; + } + + ThreadHelper.invokeLater(() -> { + try { + LoaderDialogHelper.showDialog("Translating model", 1000, (Component) e.getSource()); + File gcodeFile = backend.getProcessedGcodeFile(); + Position lowerLeftCorner = getLowerLeftCorner(gcodeFile); + TranslateProcessor translateProcessor = new TranslateProcessor(lowerLeftCorner); + backend.applyCommandProcessor(translateProcessor); + LoaderDialogHelper.closeDialog(); + } catch (Exception ex) { + GUIHelpers.displayErrorDialog(ex.getLocalizedMessage()); + } + }); + } + + private Position getLowerLeftCorner(File gcodeFile) throws IOException, GcodeParserException { + List lineSegments = parseGcodeLinesFromFile(gcodeFile); + + // We only care about carving motion, filter those commands out + List pointList = lineSegments.parallelStream() + .filter(lineSegment -> !lineSegment.isFastTraverse()) + .flatMap(lineSegment -> { + // We map both the start and end points in MM + PartialPosition start = PartialPosition.from(lineSegment.getStart()); + PartialPosition end = PartialPosition.from(lineSegment.getEnd()); + return Stream.of(start, end); + }) + .distinct() + .collect(Collectors.toList()); + + return MathUtils.getLowerLeftCorner(pointList); + } + + private List parseGcodeLinesFromFile(File gcodeFile) throws IOException, GcodeParserException { + List result; + + GcodeViewParse gcvp = new GcodeViewParse(); + try (IGcodeStreamReader gsr = new GcodeStreamReader(gcodeFile)) { + result = gcvp.toObjFromReader(gsr, ARC_SEGMENT_LENGTH); + } catch (GcodeStreamReader.NotGcodeStreamFile e) { + List linesInFile = VisualizerUtils.readFiletoArrayList(gcodeFile.getAbsolutePath()); + result = gcvp.toObjRedux(linesInFile, ARC_SEGMENT_LENGTH); + } + + return result; + } +} diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror.svg new file mode 100644 index 0000000000..64c26fb796 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24.svg new file mode 100644 index 0000000000..643223c09f --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24_dark.svg new file mode 100644 index 0000000000..b54163c2e2 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24_dark.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24_disabled_dark.svg new file mode 100644 index 0000000000..df504c2658 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror24_disabled_dark.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32.svg new file mode 100644 index 0000000000..b6f7be4b3f --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32_dark.svg new file mode 100644 index 0000000000..65f35bc9d8 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32_dark.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32_disabled_dark.svg new file mode 100644 index 0000000000..a7cd78761a --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror32_disabled_dark.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror_dark.svg new file mode 100644 index 0000000000..b747176a10 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror_dark.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror_disabled_dark.svg new file mode 100644 index 0000000000..27610cc039 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/mirror_disabled_dark.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left.svg new file mode 100644 index 0000000000..d010a35bad --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24.svg new file mode 100644 index 0000000000..4366ab4729 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24_dark.svg new file mode 100644 index 0000000000..bf8ca2f951 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24_disabled_dark.svg new file mode 100644 index 0000000000..0f40e67b48 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left24_disabled_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32.svg new file mode 100644 index 0000000000..44bb76d15d --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32_dark.svg new file mode 100644 index 0000000000..920b69eaff --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32_disabled_dark.svg new file mode 100644 index 0000000000..c118bee8ab --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left32_disabled_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left_dark.svg new file mode 100644 index 0000000000..edf4aacd72 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left_disabled_dark.svg new file mode 100644 index 0000000000..5d5b4f366e --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_left_disabled_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right.svg new file mode 100644 index 0000000000..7db7c262f5 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right.svg @@ -0,0 +1,108 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24.svg new file mode 100644 index 0000000000..fd24519933 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24_dark.svg new file mode 100644 index 0000000000..34c326895e --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24_disabled_dark.svg new file mode 100644 index 0000000000..975d482a4a --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right24_disabled_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32.svg new file mode 100644 index 0000000000..c48154eb9a --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32_dark.svg new file mode 100644 index 0000000000..79d776dabe --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32_disabled_dark.svg new file mode 100644 index 0000000000..6e6a27a45d --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right32_disabled_dark.svg @@ -0,0 +1,109 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right_dark.svg new file mode 100644 index 0000000000..ea8416e7f4 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right_dark.svg @@ -0,0 +1,108 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right_disabled_dark.svg new file mode 100644 index 0000000000..c27dc535f9 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/rotate_right_disabled_dark.svg @@ -0,0 +1,108 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate.svg new file mode 100644 index 0000000000..99225180e5 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24.svg new file mode 100644 index 0000000000..e7c8f6385d --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24_dark.svg new file mode 100644 index 0000000000..2499ba9396 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24_dark.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24_disabled_dark.svg new file mode 100644 index 0000000000..3056d4e171 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate24_disabled_dark.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32.svg new file mode 100644 index 0000000000..d9c7819d5a --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32_dark.svg new file mode 100644 index 0000000000..192ead69cc --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32_dark.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32_disabled_dark.svg new file mode 100644 index 0000000000..dbcd048f75 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate32_disabled_dark.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate_dark.svg new file mode 100644 index 0000000000..280468de35 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate_dark.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate_disabled_dark.svg b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate_disabled_dark.svg new file mode 100644 index 0000000000..56b0ace888 --- /dev/null +++ b/ugs-platform/ugs-platform-ugscore/src/main/resources/resources/icons/translate_disabled_dark.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/lookup/CentralLookup.java b/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/lookup/CentralLookup.java index 1265e2c33f..5b2a96074b 100644 --- a/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/lookup/CentralLookup.java +++ b/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/lookup/CentralLookup.java @@ -26,34 +26,34 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.universalgcodesender.i18n.Localization; import com.willwinder.universalgcodesender.model.GUIBackend; import com.willwinder.universalgcodesender.services.JogService; +import com.willwinder.universalgcodesender.services.RunFromService; import com.willwinder.universalgcodesender.utils.GUIHelpers; import com.willwinder.universalgcodesender.utils.Settings; import com.willwinder.universalgcodesender.utils.SettingsFactory; -import java.util.logging.Level; -import java.util.logging.Logger; import org.openide.util.lookup.AbstractLookup; import org.openide.util.lookup.InstanceContent; +import java.util.logging.Level; +import java.util.logging.Logger; + /** - * * @author will */ public class CentralLookup extends AbstractLookup { private static CentralLookup def = new CentralLookup(); - private InstanceContent content = null; - + private InstanceContent content; + public CentralLookup(InstanceContent content) { super(content); this.content = content; } - + public CentralLookup() { this(new InstanceContent()); try { GUIBackend backend = new GUIBackend(); Settings settings = SettingsFactory.loadSettings(); - //Localization.initialize(settings.getLanguage()); boolean fullyLocalized = Localization.initialize(settings.getLanguage()); if (!fullyLocalized) { @@ -61,17 +61,21 @@ public CentralLookup() { } backend.applySettings(settings); - JogService jogService = new JogService(backend); this.add(backend); this.add(settings); - this.add(jogService); + this.add(new JogService(backend)); + this.add(new RunFromService(backend)); } catch (Exception ex) { Logger.getLogger(CentralLookup.class.getName()).log(Level.SEVERE, null, ex); System.exit(1); } } - + + public static CentralLookup getDefault() { + return def; + } + final public void add(Object instance) { content.add(instance); } @@ -79,8 +83,4 @@ final public void add(Object instance) { public void remove(Object instance) { content.remove(instance); } - - public static CentralLookup getDefault(){ - return def; - } } diff --git a/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/LocalizingService.java b/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/LocalizingService.java index dbdb057772..8c5b8a1378 100644 --- a/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/LocalizingService.java +++ b/ugs-platform/ugs-platform-ugslib/src/main/java/com/willwinder/ugs/nbp/lib/services/LocalizingService.java @@ -30,13 +30,14 @@ This file is part of Universal Gcode Sender (UGS). * * @author wwinder */ -@ServiceProvider(service=LocalizingService.class) +@ServiceProvider(service=LocalizingService.class) public class LocalizingService { public static final String MENU_WINDOW = "Menu/Window"; public static final String MENU_WINDOW_PLUGIN = MENU_WINDOW +"/Plugins"; public static final String MENU_WINDOW_CLASSIC = MENU_WINDOW + "/Classic"; public static final String MENU_FILE = "Menu/File"; public static final String MENU_MACHINE = "Menu/Machine"; + public static final String MENU_PROGRAM = "Menu/Program"; public static final String MENU_MACHINE_JOG = "Menu/Machine/Jog"; public static final String MENU_MACHINE_JOG_STEP_SIZE = "Menu/Machine/Jog/Step Size"; public static final String MENU_MACHINE_ACTIONS = "Menu/Machine/Actions"; @@ -45,6 +46,7 @@ public class LocalizingService { public static final String CATEGORY_WINDOW = "Window"; public static final String CATEGORY_MACHINE = "Machine"; + public static final String CATEGORY_PROGRAM = "Program"; public static final String CATEGORY_FILE = "File"; public static final String CATEGORY_VISUALIZER = "Visualizer"; @@ -207,7 +209,7 @@ public class LocalizingService { public final static String OutlineTitle = Localization.getString(OutlineTitleKey, lang); public final static String OutlineWindowPath = MENU_MACHINE_ACTIONS; public final static String OutlineActionId = "com.willwinder.ugs.nbp.core.actions.OutlineAction"; - public final static String OutlineCategory = CATEGORY_MACHINE; + public final static String OutlineCategory = CATEGORY_PROGRAM; public final static String ConnectDisconnectTitleConnect = Localization.getString("mainWindow.ui.connect", lang); public final static String ConnectDisconnectTitleDisconnect = Localization.getString("mainWindow.ui.disconnect", lang); diff --git a/ugs-platform/ugs-platform-visualizer/src/main/java/com/willwinder/ugs/nbm/visualizer/RendererInputHandler.java b/ugs-platform/ugs-platform-visualizer/src/main/java/com/willwinder/ugs/nbm/visualizer/RendererInputHandler.java index 1a192913c8..7c2a8ded03 100644 --- a/ugs-platform/ugs-platform-visualizer/src/main/java/com/willwinder/ugs/nbm/visualizer/RendererInputHandler.java +++ b/ugs-platform/ugs-platform-visualizer/src/main/java/com/willwinder/ugs/nbm/visualizer/RendererInputHandler.java @@ -120,7 +120,6 @@ public void UGSEvent(UGSEvent cse) { switch (cse.getFileState()) { case FILE_LOADED: - case FILE_LOADING: setGcodeFile(cse.getFile()); break; }