From 7d23e45c0520313e8ce0937c9fbdaf5a308bea2e Mon Sep 17 00:00:00 2001 From: Joacim Breiler Date: Tue, 14 Nov 2023 10:14:49 +0100 Subject: [PATCH] Deployed b41b35c with MkDocs version: 1.4.2 --- download/index.html | 28 ++++++++++++++++++---------- index.html | 2 +- search/search_index.json | 2 +- sitemap.xml | 28 ++++++++++++++-------------- sitemap.xml.gz | Bin 207 -> 207 bytes 5 files changed, 34 insertions(+), 26 deletions(-) diff --git a/download/index.html b/download/index.html index 23bee06..e754ab6 100644 --- a/download/index.html +++ b/download/index.html @@ -157,33 +157,41 @@

UGS Platform

- + - + - - + + - + - + + + + + - - + + + + + + - + @@ -201,7 +209,7 @@

UGS Classic

- + diff --git a/index.html b/index.html index 333b4f0..f6c457f 100644 --- a/index.html +++ b/index.html @@ -416,5 +416,5 @@ diff --git a/search/search_index.json b/search/search_index.json index a87f03c..cb05f7e 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Universal Gcode Sender A full featured gcode platform used for interfacing with advanced CNC controllers like GRBL , TinyG , g2core and Smoothieware . Universal Gcode Sender is a self-contained Java application which includes all external dependencies and can be used on most computers running Windows, MacOSX or Linux. Features Cross platform, tested on Windows, OSX, Linux, and Raspberry Pi. 3D Gcode Visualizer with color coded line segments and real time tool position feedback. Duration estimates. Support for Gamepads and Joysticks Web pendant interface Over 3000 lines of unit test code, and another 1000 lines of comments documenting the tests. Configuratble gcode optimization: Remove comments Truncate decimal precision to configurable amount Convert arcs (G2/G3) to line segments Remove whitespace GRBL 1.1 Features Overrides and Toggles Platform version only. Easily control the real time feed and speed overrides by enabling the Overrides widget in the Window menu. Jog Mode With older versions of GRBL UGS is pretty reliable when it comes to jogging, but there are limitations. With GRBL 1.1 this is no longer the case when using the new JOG MODE syntax. This first-class jog mode guarantees the GCODE state will be unaltered, and also allows you to stop a jog while it is in progress. UGS uses this new syntax automatically when it detects a version of GRBL which supports it. During a jog use the STOP action to stop an in-progress jog: >> $J=G21G91X0.7F11 ok >> $J=G21G91Y0.7F11 ok >> $J=G21G91Z-0.7F11 ok Pin State Reporting Platform version only. New flags have been added to the controller state window to indicate when various external switches are enabled. Message resolution GRBL removed most help and error messages to make room for new features on the micro controller, they are now provided as data files in the grbl source code. UGS uses these data files to resolve all error codes and setting strings. Screenshots Platform The next generation of UGS. Fully modular front end powered by the same robust library created for the Classic GUI. Fully modular GUI, reconfigure windows to suite your needs. Built in gcode editor with line highlighter. Customizable keybindings. Zoom to selection with command and drag. Right click in the visualizer to jog to a specific XY location. Classic The classic GUI has everything you need to get started. 3D visualizer. Job complete dialog. Machine control. Sponsors Donations Universal Gcode Sender is free software developed and maintained in my free time for the hobby cnc community. If you would like to make a monetary donation, all proceeds will be used to try convincing my wife that it is worth my time. input[type=\"radio\"] { margin: 0 5px 0 15px; } #donatebox { text-align:center; width: 300px; } $1 $5 $10 $25 Custom amount","title":"Home"},{"location":"#universal-gcode-sender","text":"A full featured gcode platform used for interfacing with advanced CNC controllers like GRBL , TinyG , g2core and Smoothieware . Universal Gcode Sender is a self-contained Java application which includes all external dependencies and can be used on most computers running Windows, MacOSX or Linux.","title":"Universal Gcode Sender"},{"location":"#features","text":"Cross platform, tested on Windows, OSX, Linux, and Raspberry Pi. 3D Gcode Visualizer with color coded line segments and real time tool position feedback. Duration estimates. Support for Gamepads and Joysticks Web pendant interface Over 3000 lines of unit test code, and another 1000 lines of comments documenting the tests. Configuratble gcode optimization: Remove comments Truncate decimal precision to configurable amount Convert arcs (G2/G3) to line segments Remove whitespace","title":"Features"},{"location":"#grbl-11-features","text":"Overrides and Toggles Platform version only. Easily control the real time feed and speed overrides by enabling the Overrides widget in the Window menu. Jog Mode With older versions of GRBL UGS is pretty reliable when it comes to jogging, but there are limitations. With GRBL 1.1 this is no longer the case when using the new JOG MODE syntax. This first-class jog mode guarantees the GCODE state will be unaltered, and also allows you to stop a jog while it is in progress. UGS uses this new syntax automatically when it detects a version of GRBL which supports it. During a jog use the STOP action to stop an in-progress jog: >> $J=G21G91X0.7F11 ok >> $J=G21G91Y0.7F11 ok >> $J=G21G91Z-0.7F11 ok Pin State Reporting Platform version only. New flags have been added to the controller state window to indicate when various external switches are enabled. Message resolution GRBL removed most help and error messages to make room for new features on the micro controller, they are now provided as data files in the grbl source code. UGS uses these data files to resolve all error codes and setting strings.","title":"GRBL 1.1 Features"},{"location":"#screenshots","text":"","title":"Screenshots"},{"location":"#platform","text":"The next generation of UGS. Fully modular front end powered by the same robust library created for the Classic GUI. Fully modular GUI, reconfigure windows to suite your needs. Built in gcode editor with line highlighter. Customizable keybindings. Zoom to selection with command and drag. Right click in the visualizer to jog to a specific XY location.","title":"Platform"},{"location":"#classic","text":"The classic GUI has everything you need to get started. 3D visualizer. Job complete dialog. Machine control.","title":"Classic"},{"location":"#sponsors","text":"","title":"Sponsors"},{"location":"#donations","text":"Universal Gcode Sender is free software developed and maintained in my free time for the hobby cnc community. If you would like to make a monetary donation, all proceeds will be used to try convincing my wife that it is worth my time. input[type=\"radio\"] { margin: 0 5px 0 15px; } #donatebox { text-align:center; width: 300px; } $1 $5 $10 $25 Custom amount","title":"Donations"},{"location":"contributing/","text":"Contributing If you are interested in contributing to make UGS even more awesome, there are many places you can pitch in. Browse through the already reported issues or check out the discussion forum . You might know how to code, have ideas on how to improve the documentation or want to translate the software to your language. Code Pull requests are welcome! Is there a feature you would like to see, or a bug thats been bothering you? Feel free to dig in. Not sure where to start? Ask on github, use an existing ticket or create a new one. If you're planning to make a lot of changes please create an issue to discuss implementation details. A lot of effort has gone into the current design so we want to make sure everything will to work together. Documentation If you find any errors in the documentation or if there are missing pages, you are welcome to contribute! It is written using MkDocs and Markdown . Follow these instructions to get started: Fork and clone the repository https://github.com/winder/ugs_website . Make sure you have Python installed with this command in your terminal: python --version Install MkDocs and the theme modules sudo pip install mkdocs sudo pip install mkdocs-bootswatch From your cloned ugs_website directory run mkdocs serve Go to http://localhost:8000/ in your browser Now, make your changes and see them live updating in your web browser. Create a pull request in github and we will review and add your changes. Translations We are currently using POEditor to manage localization. If you would like to help please consider signing up to contribute through this service. To join the project sign up here: https://poeditor.com/join/project/2J2hB5I41Z Adding a new language You can add a new language from POEditor and start translating. If you want to stop here, create a ticket on GitHub and someone will update the project. To finish the job completely you'll need to know how to use git . Create an empty property file for your language in ugs-core/src/resources . Open src/com/willwinder/universalgcodesender/i18n/AvailableLanguages.java Add your new translation to the availableLanguages object. Update the file in update_languages.py with a mapping between the POEditor key and your new file. Run update_languages.py , see README in scripts directory for configuration detail. Only commit the new file even if others were updated. Create a GitHub pull request . Future language syncs will be done periodically with the update_languages.py script.","title":"Contributing"},{"location":"contributing/#contributing","text":"If you are interested in contributing to make UGS even more awesome, there are many places you can pitch in. Browse through the already reported issues or check out the discussion forum . You might know how to code, have ideas on how to improve the documentation or want to translate the software to your language.","title":"Contributing"},{"location":"contributing/#code","text":"Pull requests are welcome! Is there a feature you would like to see, or a bug thats been bothering you? Feel free to dig in. Not sure where to start? Ask on github, use an existing ticket or create a new one. If you're planning to make a lot of changes please create an issue to discuss implementation details. A lot of effort has gone into the current design so we want to make sure everything will to work together.","title":"Code"},{"location":"contributing/#documentation","text":"If you find any errors in the documentation or if there are missing pages, you are welcome to contribute! It is written using MkDocs and Markdown . Follow these instructions to get started: Fork and clone the repository https://github.com/winder/ugs_website . Make sure you have Python installed with this command in your terminal: python --version Install MkDocs and the theme modules sudo pip install mkdocs sudo pip install mkdocs-bootswatch From your cloned ugs_website directory run mkdocs serve Go to http://localhost:8000/ in your browser Now, make your changes and see them live updating in your web browser. Create a pull request in github and we will review and add your changes.","title":"Documentation"},{"location":"contributing/#translations","text":"We are currently using POEditor to manage localization. If you would like to help please consider signing up to contribute through this service. To join the project sign up here: https://poeditor.com/join/project/2J2hB5I41Z","title":"Translations"},{"location":"contributing/#adding-a-new-language","text":"You can add a new language from POEditor and start translating. If you want to stop here, create a ticket on GitHub and someone will update the project. To finish the job completely you'll need to know how to use git . Create an empty property file for your language in ugs-core/src/resources . Open src/com/willwinder/universalgcodesender/i18n/AvailableLanguages.java Add your new translation to the availableLanguages object. Update the file in update_languages.py with a mapping between the POEditor key and your new file. Run update_languages.py , see README in scripts directory for configuration detail. Only commit the new file even if others were updated. Create a GitHub pull request . Future language syncs will be done periodically with the update_languages.py script.","title":"Adding a new language"},{"location":"download/","text":"Download This is the latest release of UGS. For source code, nightly builds or older releases please visit github . UGS Platform The next generation platform-based interface. Version 2.0.21 Description Windows x64 Windows 64-bit version with bundled Java Windows Windows version with bundled Java MacOSX MacOSX version with bundled Java Linux Linux version with bundled Java Linux ARM Linux ARM version with bundled Java. Can be used with RaspberryPi All platforms A generic package without Java which needs to be installed separately UGS Classic The classic UGS interface with slightly less features but with the same robust backend as the Platform edition. Download Description All platforms A generic package without Java which needs to be installed separately","title":"Download"},{"location":"download/#download","text":"This is the latest release of UGS. For source code, nightly builds or older releases please visit github .","title":"Download"},{"location":"download/#ugs-platform","text":"The next generation platform-based interface. Version 2.0.21 Description Windows x64 Windows 64-bit version with bundled Java Windows Windows version with bundled Java MacOSX MacOSX version with bundled Java Linux Linux version with bundled Java Linux ARM Linux ARM version with bundled Java. Can be used with RaspberryPi All platforms A generic package without Java which needs to be installed separately","title":"UGS Platform"},{"location":"download/#ugs-classic","text":"The classic UGS interface with slightly less features but with the same robust backend as the Platform edition. Download Description All platforms A generic package without Java which needs to be installed separately","title":"UGS Classic"},{"location":"translating/","text":"Translating","title":"Translating"},{"location":"translating/#translating","text":"","title":"Translating"},{"location":"dev/backend_development/","text":"Backend architecture Similar to the front-end there are more layers on the backend to help with supporting differences between different gcode controllers and the different ways to communicate with these controllers. Because UGS depends on serial events from CNC devices, the communication between layers is also event driven. This is implemented using a series of Listener classes which pass messages from the lower levels to the upper levels whenever data is detected on the serial port (USB). Controller A controller is primarily responsible for implementing controller-specific features. Different features can be things like what happens when a Perform Homing command is requested, or how to issue status requests and parse their results. GRBL and TinyG are both supported, they share a lot of code with the AbstractController.java abstract class. Internally the AbstractController class implements several important things. It manages the stream lifecycle, keeping track of which commands have been sent, which have been completed and in some cases which are queued for sending. The controller also figures out when the stream has finished. Finally the AbstractController implements the SerialCommunicatorListener , which how its able to detect all of this state information (and allows commands to be sent to the CNC controller). The controller provides a ControllerListener interface which is used to provide real time status. Finally, the AbstractController defines a number of abstract methods which can be used by device specific controllers as needed to hook into important lifecycle events: abstract protected void closeCommBeforeEvent(); abstract protected void closeCommAfterEvent(); protected void openCommAfterEvent() throws Exception {} abstract protected void cancelSendBeforeEvent(); abstract protected void cancelSendAfterEvent(); abstract protected void pauseStreamingEvent() throws Exception; abstract protected void resumeStreamingEvent() throws Exception; abstract protected void isReadyToSendCommandsEvent() throws Exception; abstract protected void statusUpdatesEnabledValueChanged(boolean enabled); abstract protected void statusUpdatesRateValueChanged(int rate); // This one is special, because it is responsible for parsing device // responses, such as a command complete, status string, or parsing a // status event. In the case of a command complete, it must call // `commandComplete` to push the stream lifecycle along. abstract protected void rawResponseHandler(String response); Here is the public interface which controlles conform to: public interface IController { /* Observable */ public void addListener(ControllerListener cl); /* Actions */ public void performHomingCycle() throws Exception; public void returnToHome() throws Exception; public void resetCoordinatesToZero() throws Exception; public void resetCoordinateToZero(final char coord) throws Exception; public void killAlarmLock() throws Exception; public void toggleCheckMode() throws Exception; public void viewParserState() throws Exception; public void issueSoftReset() throws Exception; /* Behavior */ public void setSingleStepMode(boolean enabled); public boolean getSingleStepMode(); public void setStatusUpdatesEnabled(boolean enabled); public boolean getStatusUpdatesEnabled(); public void setStatusUpdateRate(int rate); public int getStatusUpdateRate(); public GcodeCommandCreator getCommandCreator(); public long getJobLengthEstimate(File gcodeFile); /* Serial */ public Boolean openCommPort(String port, int portRate) throws Exception; public Boolean closeCommPort() throws Exception; public Boolean isCommOpen(); /* Stream information */ public Boolean isReadyToStreamFile() throws Exception; public Boolean isStreamingFile(); public long getSendDuration(); public int rowsInSend(); public int rowsSent(); public int rowsRemaining(); /* Stream control */ public void beginStreaming() throws Exception; public void pauseStreaming() throws Exception; public void resumeStreaming() throws Exception; public void cancelSend(); /* Stream content */ public GcodeCommand createCommand(String gcode) throws Exception; public void sendCommandImmediately(GcodeCommand cmd) throws Exception; public void queueCommand(GcodeCommand cmd) throws Exception; public void queueStream(GcodeStreamReader r); public void queueRawStream(Reader r); } Communicator A communicator handles all levels of sending data to the device. Raw responses are returned to any listeners via the SerialCommunicatorListener . The AbstractCommunicator implements several listener utilities which are used by implementing classes. The BufferedCommunicator abstract class handles the process of buffering multiple commands at once in order to keep a constant stream of commands available to the CNC device. It does this in the streamCommands method by maintaining a list of active commands, and the current size of those commands. A method named processedCommand must be implemented in a subclass to determine whether a raw response indicates a command has completed. This notifies the BufferedCommunicator that it should attempt to send more commands. GrblCommunicator and TinyGCommunicator are two concrete implementations of the BufferedCommunicator . Connection This is a very thin layer which provides a way to write and receive data: abstract public boolean openPort(String name, int baud) throws Exception; abstract public void closePort() throws Exception; abstract public boolean isOpen(); abstract public void sendByteImmediately(byte b) throws Exception; abstract public void sendStringToComm(String command) throws Exception; Streaming strategy UGS attempts to use a fixed amount of memory when streaming a file. In this way it can send gcode files of any size. Files are preprocessed at the BackendAPI level using the GcodeStreamWriter class. This will serialize all the required metadata into a file. Later on that file can be opened with the GcodeStreamReader class, the Controller and Communicator classes use this. Using the reader, the Communicator class can pull out commands one at a time and send them to the Connection .","title":"Backend"},{"location":"dev/backend_development/#backend-architecture","text":"Similar to the front-end there are more layers on the backend to help with supporting differences between different gcode controllers and the different ways to communicate with these controllers. Because UGS depends on serial events from CNC devices, the communication between layers is also event driven. This is implemented using a series of Listener classes which pass messages from the lower levels to the upper levels whenever data is detected on the serial port (USB).","title":"Backend architecture"},{"location":"dev/backend_development/#controller","text":"A controller is primarily responsible for implementing controller-specific features. Different features can be things like what happens when a Perform Homing command is requested, or how to issue status requests and parse their results. GRBL and TinyG are both supported, they share a lot of code with the AbstractController.java abstract class. Internally the AbstractController class implements several important things. It manages the stream lifecycle, keeping track of which commands have been sent, which have been completed and in some cases which are queued for sending. The controller also figures out when the stream has finished. Finally the AbstractController implements the SerialCommunicatorListener , which how its able to detect all of this state information (and allows commands to be sent to the CNC controller). The controller provides a ControllerListener interface which is used to provide real time status. Finally, the AbstractController defines a number of abstract methods which can be used by device specific controllers as needed to hook into important lifecycle events: abstract protected void closeCommBeforeEvent(); abstract protected void closeCommAfterEvent(); protected void openCommAfterEvent() throws Exception {} abstract protected void cancelSendBeforeEvent(); abstract protected void cancelSendAfterEvent(); abstract protected void pauseStreamingEvent() throws Exception; abstract protected void resumeStreamingEvent() throws Exception; abstract protected void isReadyToSendCommandsEvent() throws Exception; abstract protected void statusUpdatesEnabledValueChanged(boolean enabled); abstract protected void statusUpdatesRateValueChanged(int rate); // This one is special, because it is responsible for parsing device // responses, such as a command complete, status string, or parsing a // status event. In the case of a command complete, it must call // `commandComplete` to push the stream lifecycle along. abstract protected void rawResponseHandler(String response); Here is the public interface which controlles conform to: public interface IController { /* Observable */ public void addListener(ControllerListener cl); /* Actions */ public void performHomingCycle() throws Exception; public void returnToHome() throws Exception; public void resetCoordinatesToZero() throws Exception; public void resetCoordinateToZero(final char coord) throws Exception; public void killAlarmLock() throws Exception; public void toggleCheckMode() throws Exception; public void viewParserState() throws Exception; public void issueSoftReset() throws Exception; /* Behavior */ public void setSingleStepMode(boolean enabled); public boolean getSingleStepMode(); public void setStatusUpdatesEnabled(boolean enabled); public boolean getStatusUpdatesEnabled(); public void setStatusUpdateRate(int rate); public int getStatusUpdateRate(); public GcodeCommandCreator getCommandCreator(); public long getJobLengthEstimate(File gcodeFile); /* Serial */ public Boolean openCommPort(String port, int portRate) throws Exception; public Boolean closeCommPort() throws Exception; public Boolean isCommOpen(); /* Stream information */ public Boolean isReadyToStreamFile() throws Exception; public Boolean isStreamingFile(); public long getSendDuration(); public int rowsInSend(); public int rowsSent(); public int rowsRemaining(); /* Stream control */ public void beginStreaming() throws Exception; public void pauseStreaming() throws Exception; public void resumeStreaming() throws Exception; public void cancelSend(); /* Stream content */ public GcodeCommand createCommand(String gcode) throws Exception; public void sendCommandImmediately(GcodeCommand cmd) throws Exception; public void queueCommand(GcodeCommand cmd) throws Exception; public void queueStream(GcodeStreamReader r); public void queueRawStream(Reader r); }","title":"Controller"},{"location":"dev/backend_development/#communicator","text":"A communicator handles all levels of sending data to the device. Raw responses are returned to any listeners via the SerialCommunicatorListener . The AbstractCommunicator implements several listener utilities which are used by implementing classes. The BufferedCommunicator abstract class handles the process of buffering multiple commands at once in order to keep a constant stream of commands available to the CNC device. It does this in the streamCommands method by maintaining a list of active commands, and the current size of those commands. A method named processedCommand must be implemented in a subclass to determine whether a raw response indicates a command has completed. This notifies the BufferedCommunicator that it should attempt to send more commands. GrblCommunicator and TinyGCommunicator are two concrete implementations of the BufferedCommunicator .","title":"Communicator"},{"location":"dev/backend_development/#connection","text":"This is a very thin layer which provides a way to write and receive data: abstract public boolean openPort(String name, int baud) throws Exception; abstract public void closePort() throws Exception; abstract public boolean isOpen(); abstract public void sendByteImmediately(byte b) throws Exception; abstract public void sendStringToComm(String command) throws Exception;","title":"Connection"},{"location":"dev/backend_development/#streaming-strategy","text":"UGS attempts to use a fixed amount of memory when streaming a file. In this way it can send gcode files of any size. Files are preprocessed at the BackendAPI level using the GcodeStreamWriter class. This will serialize all the required metadata into a file. Later on that file can be opened with the GcodeStreamReader class, the Controller and Communicator classes use this. Using the reader, the Communicator class can pull out commands one at a time and send them to the Connection .","title":"Streaming strategy"},{"location":"dev/frontend_development/","text":"Front-end Architecture UGS uses a Model-View-Presenter architecture. What this means is that at a high level there are three layers which each serve different purposes. A Model for all backend logic, a View displayed to the user and a Presenter which serves as a buffer between the model and one or more views. Model The model contains all backend logic. Things like opening a connection, listing serial ports, streaming a file, and handling firmware specific nuances. All of this is hidden from the front end as much as possible. View The view only has access to the presenter. It is responsible for all user interaction and feedback. The main logic in here should be things like enabling or disabling components based on the current state of the model. Classic GUI The Classic GUI is built using NetBeans. There are a number of custom Swing components, and they are all initialized with the NetBeans GUI builder. The vast majority of the Classic GUI code is contained in MainWindow.java . There isn't a lot to expand on here, this front end has grown organically over the years and is fairly rigid. The Visualizer component is a standalone JOGL window which is updated using events from the backend (it was a model for many of the improvements in the current applications architecture). UGS Platform The UGS Platform build is also built using NetBeans. It is a built ontop of the NetBeans Platform which provides it a robust set of tools like flexible windows, a plugin framework, and a suite of tools for module communications. At the core of this is a module named UGSLib which is a simple wrapper to the standard UGS JAR file. There is a suite of modules named UGSCore which provides many of the standard UI elements seen in the Classic GUI , in addition there are other modules that provide new functionality. Extending the GUI is now a matter of creating a new plugin, for details on how to do this see the Plugin Tutorial . Presenter The presenter serves as an API for the model. All the heavy lifting needed for the GUI should happen here. For example the controller model object knows how to stream a processed file, but it doesn't know how to process the file. So the presenter will pass data to the gcode processor and generate a processed object which can be passed to the controller. Similarly, all notifications from the model are reinterpreted for the view with a simpler message strategy. In this way, all updates to the backend code can be leveraged by all front ends which utilize UGS. BackendAPI In UGS interfaces named BackendAPI and BackendAPIReadOnly provide the presenter layer. The read only methods are split off into a sub-interface in case a developer wants to be sure they don't change any state. For instance a widget that displays the current machine location probably has no need for pausing a stream. These APIs are used by all front ends ( Classic GUI , PendantUI and UGS Platform ).","title":"Frontend"},{"location":"dev/frontend_development/#front-end-architecture","text":"UGS uses a Model-View-Presenter architecture. What this means is that at a high level there are three layers which each serve different purposes. A Model for all backend logic, a View displayed to the user and a Presenter which serves as a buffer between the model and one or more views.","title":"Front-end Architecture"},{"location":"dev/frontend_development/#model","text":"The model contains all backend logic. Things like opening a connection, listing serial ports, streaming a file, and handling firmware specific nuances. All of this is hidden from the front end as much as possible.","title":"Model"},{"location":"dev/frontend_development/#view","text":"The view only has access to the presenter. It is responsible for all user interaction and feedback. The main logic in here should be things like enabling or disabling components based on the current state of the model.","title":"View"},{"location":"dev/frontend_development/#classic-gui","text":"The Classic GUI is built using NetBeans. There are a number of custom Swing components, and they are all initialized with the NetBeans GUI builder. The vast majority of the Classic GUI code is contained in MainWindow.java . There isn't a lot to expand on here, this front end has grown organically over the years and is fairly rigid. The Visualizer component is a standalone JOGL window which is updated using events from the backend (it was a model for many of the improvements in the current applications architecture).","title":"Classic GUI"},{"location":"dev/frontend_development/#ugs-platform","text":"The UGS Platform build is also built using NetBeans. It is a built ontop of the NetBeans Platform which provides it a robust set of tools like flexible windows, a plugin framework, and a suite of tools for module communications. At the core of this is a module named UGSLib which is a simple wrapper to the standard UGS JAR file. There is a suite of modules named UGSCore which provides many of the standard UI elements seen in the Classic GUI , in addition there are other modules that provide new functionality. Extending the GUI is now a matter of creating a new plugin, for details on how to do this see the Plugin Tutorial .","title":"UGS Platform"},{"location":"dev/frontend_development/#presenter","text":"The presenter serves as an API for the model. All the heavy lifting needed for the GUI should happen here. For example the controller model object knows how to stream a processed file, but it doesn't know how to process the file. So the presenter will pass data to the gcode processor and generate a processed object which can be passed to the controller. Similarly, all notifications from the model are reinterpreted for the view with a simpler message strategy. In this way, all updates to the backend code can be leveraged by all front ends which utilize UGS.","title":"Presenter"},{"location":"dev/frontend_development/#backendapi","text":"In UGS interfaces named BackendAPI and BackendAPIReadOnly provide the presenter layer. The read only methods are split off into a sub-interface in case a developer wants to be sure they don't change any state. For instance a widget that displays the current machine location probably has no need for pausing a stream. These APIs are used by all front ends ( Classic GUI , PendantUI and UGS Platform ).","title":"BackendAPI"},{"location":"dev/gcode_processor/","text":"Gcode Processor Development The UGS core library has a flexible gcode processor plugin system. It is designed as a processing pipeline to convert one line of code at a time by passing it through multiple Command Processor plugins. Some advanced features in UGS, like the Auto Leveler, take advantage of this feature to inject a special processor module into the gcode processing pipeline. Other processors are simpler, such as the M30Processor which simply removes unwanted M30 commands, or the CommandLengthProcessor which causes an error if the final processed line has too much data for your controller. This process is configured using a JSON file which holds processor configuration, order in which processors should appear in the pipeline, and whether or not they are enabled. All of this is configurable in UGS and UGP in a Gcode Processor Configuration menu. Anatomy of a CommandProcessor The processor interface is simple. One command goes in along with the current state and a list of output commands come out. A CommandProcessor might discover invalid input, in which case a GcodeParserException can be thrown for the GcodeParser to handle. public interface CommandProcessor { /** * Given a command and the current state of a program returns a replacement * list of commands. * @param command Input gcode. * @param state State of the gcode parser when the command will run. * @return One or more gcode commands to replace the original command with. */ public List processCommand(String command, GcodeState state) throws GcodeParserException; /** * Returns information about the current command and its configuration. * @return */ public String getHelp(); } Simple Example The CommandLengthProcessor is one of the simplest examples of a CommandProcessor. One thing of interest is that it accepts a length parameter during configuration. By adding a CommandLengthProcessor to the GcodeParser, you can ensure the maximum length of commands. public class CommandLengthProcessor implements CommandProcessor { final private int length; public CommandLengthProcessor(int length) { this.length = length; } @Override public String getHelp() { // Global localization helpers are used for the help message. return Localization.getString(\"sender.help.command.length\") + \"\\n\" + Localization.getString(\"sender.command.length\") + \": \" + length; } @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { if (command.length() > length) throw new GcodeParserException(\"Command '\" + command + \"' is longer than \" + length + \" characters.\"); return Collections.singletonList(command); } } More Complex Examples The following examples can be found in GitHub, they wont have the full code included. DecimalProcessor This is only slightly more complicated than the CommandLengthProcessor. It adds in some config validation by throwing a RuntimeException in the constructor, which is handled by code which configures the GcodeParser. The processCommand method is then able to truncate any decimals using simple string manipulation. FeedOverrideProcessor Like the DecimalProcessor , the FeedOverrideProcessor is able to modifie any F-Commands with simple string manipulation. LineSplitter The LineSplitter CommandProcessor will actually modify commands by parsing the gcode and rewriting it. There are several utilities used here to help: GcodeParser.processCommand - Converts the command string into something easier to work with. GcodePreprocessorUtils.extractMotion - Helper to extract all words associated to movement commands, the remainder should still be sent but may not be sent in the context of the rewritten command. The remaining logic checks the length of any G0 or G1 commands and converts them, or returns the original command unmodified. Tutorial: Creating a New CommandProcessor Creating a fully integrated and configurable CommandProcessor is simple, but does touch a number of different files. This tutorial will go over those pieces in the context of creating a processor named M3Dweller . Goal Create a processor which inserts a Dwell command (short delay) whenever the spindle is enabled. This allows any potentially slow setups such as VFD's to come up to speed. It should be configurable via the Gcode Processor Configuration menu. Creating the processor First you'll notice that the constructor initializes the command which should be added after our spindle start M3 command. The locale is set to make sure comma is not used as a decimal separator: private final String dwellCommand; public M3Dweller(double dwellDuration) { this.dwellCommand = String.format(Locale.ROOT, \"G4P%.2f\", dwellDuration); } The help method simply adds some comments for the settings GUI: @Override public String getHelp() { return \"Add a delay after enabling the spindle with \\\"M3\\\" commands. \\\"M3\\\" must be the only command on the line.\"; } Everything is then pulled together in the processCommand method to return an extra dwell command when M3 is detected: // Contains an M3 not followed by another digit (i.e. M30) Pattern m3Pattern = Pattern.compile(\".*[mM]3(?!\\\\d)(\\\\D.*)?\"); @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { if (m3Pattern.matcher(command).matches()) { return Arrays.asList(command, dwellCommand); } return Collections.singletonList(command); } All at once now: public class M3Dweller implements CommandProcessor { private final String dwellCommand; // Contains an M3 not followed by another digit (i.e. M30) Pattern m3Pattern = Pattern.compile(\".*[mM]3(?!\\\\d)(\\\\D.*)?\"); public M3Dweller(double dwellDuration) { this.dwellCommand = String.format(Locale.ROOT, \"G4P%.2f\", dwellDuration); } @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { String noComments = GcodePreprocessorUtils.removeComment(command); if (m3Pattern.matcher(noComments).matches()) { return Arrays.asList(command, dwellCommand); } return Collections.singletonList(command); } @Override public String getHelp() { return \"Add a delay after enabling the spindle with \\\"M3\\\" commands. \\\"M3\\\" must be the only command on the line.\"; } } Hooking up the JSON The json files are stored in ugs-core/src/resources/firmware_config , each of these will be updated to include our new processor. Notice that we have an argument named duration, and that it is disabled by default: \"name\": \"M3Dweller\", \"enabled\": false, \"optional\": true, \"args\": { \"duration\": 2.5 } },{ We also increment the version string, which will prompt users that there is a new configuration file available and they may need to revisit their settings: - \"Version\": 3, + \"Version\": 4, JSON Loader The last step is to update the CommandProcessorLoader to include logic that creates the new M3Dweller when needed. I used one of the other processors as an example and made sure to make a corresponding entry for the M3Dweller in each of the corresponding code and comment locations: case \"M3Dweller\": double duration = pc.args.get(\"duration\").getAsDouble(); p = new M3Dweller(duration); break; There is also a test which should be updated to make sure everything is working: + args = new JsonObject(); + args.addProperty(\"duration\", 2.5); + object = new JsonObject(); + object.addProperty(\"name\", \"M3Dweller\"); + object.add(\"args\", args); + array.add(object); + String jsonConfig = array.toString(); List processors = CommandProcessorLoader.initializeWithProcessors(jsonConfig); - assertEquals(8, processors.size()); + assertEquals(9, processors.size()); assertEquals(ArcExpander.class, processors.get(0).getClass()); assertEquals(CommentProcessor.class, processors.get(1).getClass()); assertEquals(DecimalProcessor.class, processors.get(2).getClass()); assertEquals(FeedOverrideProcessor.class, processors.get(3).getClass()); assertEquals(M30Processor.class, processors.get(4).getClass()); assertEquals(PatternRemover.class, processors.get(5).getClass()); assertEquals(CommandLengthProcessor.class, processors.get(6).getClass()); assertEquals(WhitespaceProcessor.class, processors.get(7).getClass()); + assertEquals(M3Dweller.class, processors.get(8).getClass()); Testing The command processors lend themselves to thorough testing, so we create a new M3DwellerTest.java file in the associated test package and write some tests: @Test public void testReplaces() throws Exception { M3Dweller dweller = new M3Dweller(2.5); String command; command = \"M3\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"m3\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"M3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"m3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"(this is ignored) M3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); } @Test public void testNoOp() throws Exception { M3Dweller dweller = new M3Dweller(2.5); String command; command = \"anything else\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); command = \"M30\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); command = \"G0 X0 Y0 (definitely not ready to start the spindle with an M3 yet)\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); } Localizing In addition to the help message which may use the global Localization helper, the settings menu will attempt to find something with the same name as in the JSON file. So we add an entry to ./ugs-core/src/resources/MessagesBundle_en_US.properties , which will later be pulled into a separate localization service to be localized in other languages. WhitespaceProcessor = Whitespace Remover +M3Dweller = Spindle start delay controller.exception.smoothie.booting = Smoothie has not finished booting. Conclusion We have now fully integrated our M3Dweller processor into the UGS framework and users of UGS and UGP can both make use of it. The raw commit for this feature is found on github here .","title":"Gcode Processors"},{"location":"dev/gcode_processor/#gcode-processor-development","text":"The UGS core library has a flexible gcode processor plugin system. It is designed as a processing pipeline to convert one line of code at a time by passing it through multiple Command Processor plugins. Some advanced features in UGS, like the Auto Leveler, take advantage of this feature to inject a special processor module into the gcode processing pipeline. Other processors are simpler, such as the M30Processor which simply removes unwanted M30 commands, or the CommandLengthProcessor which causes an error if the final processed line has too much data for your controller. This process is configured using a JSON file which holds processor configuration, order in which processors should appear in the pipeline, and whether or not they are enabled. All of this is configurable in UGS and UGP in a Gcode Processor Configuration menu.","title":"Gcode Processor Development"},{"location":"dev/gcode_processor/#anatomy-of-a-commandprocessor","text":"The processor interface is simple. One command goes in along with the current state and a list of output commands come out. A CommandProcessor might discover invalid input, in which case a GcodeParserException can be thrown for the GcodeParser to handle. public interface CommandProcessor { /** * Given a command and the current state of a program returns a replacement * list of commands. * @param command Input gcode. * @param state State of the gcode parser when the command will run. * @return One or more gcode commands to replace the original command with. */ public List processCommand(String command, GcodeState state) throws GcodeParserException; /** * Returns information about the current command and its configuration. * @return */ public String getHelp(); }","title":"Anatomy of a CommandProcessor"},{"location":"dev/gcode_processor/#simple-example","text":"The CommandLengthProcessor is one of the simplest examples of a CommandProcessor. One thing of interest is that it accepts a length parameter during configuration. By adding a CommandLengthProcessor to the GcodeParser, you can ensure the maximum length of commands. public class CommandLengthProcessor implements CommandProcessor { final private int length; public CommandLengthProcessor(int length) { this.length = length; } @Override public String getHelp() { // Global localization helpers are used for the help message. return Localization.getString(\"sender.help.command.length\") + \"\\n\" + Localization.getString(\"sender.command.length\") + \": \" + length; } @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { if (command.length() > length) throw new GcodeParserException(\"Command '\" + command + \"' is longer than \" + length + \" characters.\"); return Collections.singletonList(command); } }","title":"Simple Example"},{"location":"dev/gcode_processor/#more-complex-examples","text":"The following examples can be found in GitHub, they wont have the full code included.","title":"More Complex Examples"},{"location":"dev/gcode_processor/#decimalprocessor","text":"This is only slightly more complicated than the CommandLengthProcessor. It adds in some config validation by throwing a RuntimeException in the constructor, which is handled by code which configures the GcodeParser. The processCommand method is then able to truncate any decimals using simple string manipulation.","title":"DecimalProcessor"},{"location":"dev/gcode_processor/#feedoverrideprocessor","text":"Like the DecimalProcessor , the FeedOverrideProcessor is able to modifie any F-Commands with simple string manipulation.","title":"FeedOverrideProcessor"},{"location":"dev/gcode_processor/#linesplitter","text":"The LineSplitter CommandProcessor will actually modify commands by parsing the gcode and rewriting it. There are several utilities used here to help: GcodeParser.processCommand - Converts the command string into something easier to work with. GcodePreprocessorUtils.extractMotion - Helper to extract all words associated to movement commands, the remainder should still be sent but may not be sent in the context of the rewritten command. The remaining logic checks the length of any G0 or G1 commands and converts them, or returns the original command unmodified.","title":"LineSplitter"},{"location":"dev/gcode_processor/#tutorial-creating-a-new-commandprocessor","text":"Creating a fully integrated and configurable CommandProcessor is simple, but does touch a number of different files. This tutorial will go over those pieces in the context of creating a processor named M3Dweller .","title":"Tutorial: Creating a New CommandProcessor"},{"location":"dev/gcode_processor/#goal","text":"Create a processor which inserts a Dwell command (short delay) whenever the spindle is enabled. This allows any potentially slow setups such as VFD's to come up to speed. It should be configurable via the Gcode Processor Configuration menu.","title":"Goal"},{"location":"dev/gcode_processor/#creating-the-processor","text":"First you'll notice that the constructor initializes the command which should be added after our spindle start M3 command. The locale is set to make sure comma is not used as a decimal separator: private final String dwellCommand; public M3Dweller(double dwellDuration) { this.dwellCommand = String.format(Locale.ROOT, \"G4P%.2f\", dwellDuration); } The help method simply adds some comments for the settings GUI: @Override public String getHelp() { return \"Add a delay after enabling the spindle with \\\"M3\\\" commands. \\\"M3\\\" must be the only command on the line.\"; } Everything is then pulled together in the processCommand method to return an extra dwell command when M3 is detected: // Contains an M3 not followed by another digit (i.e. M30) Pattern m3Pattern = Pattern.compile(\".*[mM]3(?!\\\\d)(\\\\D.*)?\"); @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { if (m3Pattern.matcher(command).matches()) { return Arrays.asList(command, dwellCommand); } return Collections.singletonList(command); } All at once now: public class M3Dweller implements CommandProcessor { private final String dwellCommand; // Contains an M3 not followed by another digit (i.e. M30) Pattern m3Pattern = Pattern.compile(\".*[mM]3(?!\\\\d)(\\\\D.*)?\"); public M3Dweller(double dwellDuration) { this.dwellCommand = String.format(Locale.ROOT, \"G4P%.2f\", dwellDuration); } @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { String noComments = GcodePreprocessorUtils.removeComment(command); if (m3Pattern.matcher(noComments).matches()) { return Arrays.asList(command, dwellCommand); } return Collections.singletonList(command); } @Override public String getHelp() { return \"Add a delay after enabling the spindle with \\\"M3\\\" commands. \\\"M3\\\" must be the only command on the line.\"; } }","title":"Creating the processor"},{"location":"dev/gcode_processor/#hooking-up-the-json","text":"The json files are stored in ugs-core/src/resources/firmware_config , each of these will be updated to include our new processor. Notice that we have an argument named duration, and that it is disabled by default: \"name\": \"M3Dweller\", \"enabled\": false, \"optional\": true, \"args\": { \"duration\": 2.5 } },{ We also increment the version string, which will prompt users that there is a new configuration file available and they may need to revisit their settings: - \"Version\": 3, + \"Version\": 4,","title":"Hooking up the JSON"},{"location":"dev/gcode_processor/#json-loader","text":"The last step is to update the CommandProcessorLoader to include logic that creates the new M3Dweller when needed. I used one of the other processors as an example and made sure to make a corresponding entry for the M3Dweller in each of the corresponding code and comment locations: case \"M3Dweller\": double duration = pc.args.get(\"duration\").getAsDouble(); p = new M3Dweller(duration); break; There is also a test which should be updated to make sure everything is working: + args = new JsonObject(); + args.addProperty(\"duration\", 2.5); + object = new JsonObject(); + object.addProperty(\"name\", \"M3Dweller\"); + object.add(\"args\", args); + array.add(object); + String jsonConfig = array.toString(); List processors = CommandProcessorLoader.initializeWithProcessors(jsonConfig); - assertEquals(8, processors.size()); + assertEquals(9, processors.size()); assertEquals(ArcExpander.class, processors.get(0).getClass()); assertEquals(CommentProcessor.class, processors.get(1).getClass()); assertEquals(DecimalProcessor.class, processors.get(2).getClass()); assertEquals(FeedOverrideProcessor.class, processors.get(3).getClass()); assertEquals(M30Processor.class, processors.get(4).getClass()); assertEquals(PatternRemover.class, processors.get(5).getClass()); assertEquals(CommandLengthProcessor.class, processors.get(6).getClass()); assertEquals(WhitespaceProcessor.class, processors.get(7).getClass()); + assertEquals(M3Dweller.class, processors.get(8).getClass());","title":"JSON Loader"},{"location":"dev/gcode_processor/#testing","text":"The command processors lend themselves to thorough testing, so we create a new M3DwellerTest.java file in the associated test package and write some tests: @Test public void testReplaces() throws Exception { M3Dweller dweller = new M3Dweller(2.5); String command; command = \"M3\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"m3\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"M3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"m3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"(this is ignored) M3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); } @Test public void testNoOp() throws Exception { M3Dweller dweller = new M3Dweller(2.5); String command; command = \"anything else\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); command = \"M30\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); command = \"G0 X0 Y0 (definitely not ready to start the spindle with an M3 yet)\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); }","title":"Testing"},{"location":"dev/gcode_processor/#localizing","text":"In addition to the help message which may use the global Localization helper, the settings menu will attempt to find something with the same name as in the JSON file. So we add an entry to ./ugs-core/src/resources/MessagesBundle_en_US.properties , which will later be pulled into a separate localization service to be localized in other languages. WhitespaceProcessor = Whitespace Remover +M3Dweller = Spindle start delay controller.exception.smoothie.booting = Smoothie has not finished booting.","title":"Localizing"},{"location":"dev/gcode_processor/#conclusion","text":"We have now fully integrated our M3Dweller processor into the UGS framework and users of UGS and UGP can both make use of it. The raw commit for this feature is found on github here .","title":"Conclusion"},{"location":"dev/getting_started/","text":"Project Organization Universal Gcode Sender uses Maven to build the project. It is using maven modules to separate the core library / classic GUI and the UGS Platform project. At the top level a UGS target defines the ugs-core and ugs-platform-parent modules which can be built separately or all at once. The classic gui is part of the core project in the ugs-core module. The maven-shade-plugin and maven-assembly-plugin are generate the self-executing JAR and distribution zip. UGS Platform is built on the NetBeans Platform . It is also using maven. Development is done using NetBeans, but most of the code can be edited using any IDE that supports Maven. Development with an IDE Any IDE supporting Maven should be able to open the UGS project directory. Once opened it should show you the ugs-core and ugs-platform-parent modules which correspond to the Classic and Platform interfaces. Development is done using NetBeans, and for some project development NetBeans is almost required. But for tweaking the UI and experimenting with the backend any IDE which supports maven can be used. Classic GUI In the ugs-core module, you can run the MainWindow.java file to start the Classic GUI. UGS Platform The platform build has a number of submodules. Load the suite of modules by running the ugs-platform-app module. Development with the Command Line The UGS Classic and Platform interfaces can also be run from the command line. These commands should be run from the root directory. Classic GUI There is a helper script named run_classic.sh , or you can use the commands below. Running the UI mvn install mvn exec:java -Dexec.mainClass=\"com.willwinder.universalgcodesender.MainWindow\" -pl ugs-core Executing tests mvn install mvn test -pl ugs-core Building the self-executing JAR mvn install mvn package -pl ugs-core Building a UniversalGcodeSender.zip release file mvn package assembly:assembly UGS Platform There is a helper script named run_platform.sh , or you can use the commands below. Running the UI mvn install mvn nbm:run-platform -pl ugs-platform/application","title":"Getting Started"},{"location":"dev/getting_started/#project-organization","text":"Universal Gcode Sender uses Maven to build the project. It is using maven modules to separate the core library / classic GUI and the UGS Platform project. At the top level a UGS target defines the ugs-core and ugs-platform-parent modules which can be built separately or all at once. The classic gui is part of the core project in the ugs-core module. The maven-shade-plugin and maven-assembly-plugin are generate the self-executing JAR and distribution zip. UGS Platform is built on the NetBeans Platform . It is also using maven. Development is done using NetBeans, but most of the code can be edited using any IDE that supports Maven.","title":"Project Organization"},{"location":"dev/getting_started/#development-with-an-ide","text":"Any IDE supporting Maven should be able to open the UGS project directory. Once opened it should show you the ugs-core and ugs-platform-parent modules which correspond to the Classic and Platform interfaces. Development is done using NetBeans, and for some project development NetBeans is almost required. But for tweaking the UI and experimenting with the backend any IDE which supports maven can be used.","title":"Development with an IDE"},{"location":"dev/getting_started/#classic-gui","text":"In the ugs-core module, you can run the MainWindow.java file to start the Classic GUI.","title":"Classic GUI"},{"location":"dev/getting_started/#ugs-platform","text":"The platform build has a number of submodules. Load the suite of modules by running the ugs-platform-app module.","title":"UGS Platform"},{"location":"dev/getting_started/#development-with-the-command-line","text":"The UGS Classic and Platform interfaces can also be run from the command line. These commands should be run from the root directory.","title":"Development with the Command Line"},{"location":"dev/getting_started/#classic-gui_1","text":"There is a helper script named run_classic.sh , or you can use the commands below.","title":"Classic GUI"},{"location":"dev/getting_started/#running-the-ui","text":"mvn install mvn exec:java -Dexec.mainClass=\"com.willwinder.universalgcodesender.MainWindow\" -pl ugs-core","title":"Running the UI"},{"location":"dev/getting_started/#executing-tests","text":"mvn install mvn test -pl ugs-core","title":"Executing tests"},{"location":"dev/getting_started/#building-the-self-executing-jar","text":"mvn install mvn package -pl ugs-core","title":"Building the self-executing JAR"},{"location":"dev/getting_started/#building-a-universalgcodesenderzip-release-file","text":"mvn package assembly:assembly","title":"Building a UniversalGcodeSender.zip release file"},{"location":"dev/getting_started/#ugs-platform_1","text":"There is a helper script named run_platform.sh , or you can use the commands below.","title":"UGS Platform"},{"location":"dev/getting_started/#running-the-ui_1","text":"mvn install mvn nbm:run-platform -pl ugs-platform/application","title":"Running the UI"},{"location":"dev/plugin/","text":"Note: The UGS Platform has been updated to use maven. Some parts of this document need to be updated to reflect that change. Plugin development The UGS Platform is built ontop of the NetBeans Platform. This gives us powerful tools to work with, including a robust plugin system. The heart of the UGS Platform is a module which wraps and exposes the Universal Gcode Sender JAR file - the same jar you could execute to run the Classic GUI! Other than using the UGSLib module, developing a plugin for the UGS Platform is exactly the same as developing any other NetBeans Platform plugin. And there is lots of great documentation for that, here is the NetBeans Platform Plugin Quick Start guide. Workflow Plugin Tutorial In this tutorial we're going to build a window to help manage jobs that use multiple tools which are split into multiple files. The rough design idea will have a central table with four columns containing: * File name * Tool name (editable) * Finished flag There will be a pair of buttons to add and remove files from the table, and we will also hook up a UGS event listener to detect when files are opened from other areas of the interface as well. Lastly, we'll add another pair of buttons to move rows around in the table, so that we can reorganize the workflow if files were added out of order the order. Here is a sketch of what we're building: Create and configure project Universal Gcode Sender is developed with NetBeans, and plugins are no exception. Once you've cloned the Universal Gcode Sender project you should be able to open the UGSPlatform folder with NetBeans and it will discover a project that you can open. To start building your module expand the UGSPlatform section, right-click the modules directory and select Add New... . This will open up a wizard where you name the module, and declare the source path. For this example the module is named WorkflowModuleTutorial and the source path is com.willwinder.ugs.nbp.workflowmoduletutorial which is the convention used in the core modules. Add UGS dependencies Your module should now be listed in the Modules section. If it doesn't you may need to restart NetBeans. Before we dive into the code there are a couple helper classes to import which will give you full access to the UGS API. Double click your module from the Modules section to open the code, then right-click the top level item which appeared and select the properties menu. Select Add Dependency... , here you should search for UGSLib and CentralLookup then add them to your plugin. Create window class Now we're ready to build the module. In this tutorial we're building a window to manage a multi-job workflow, so we'll start by adding a window to customize. Open the new module and right click the new package, in the context menu go to New -> Window... . To bring up the new window wizard. In the first screen of the wizard choose the default location your window will appear. Custom locations have been designed for UGS Platform, the largest is named visualizer because it is the Visualizer's default location. We'll use this location for our plugin. This means that when our plugin opens it will be tabbed with the Visualizer module. Click next and choose a class name for your module, for this tutorial I'm going to call it WorkflowModuleTutorial . Build the GUI The NetBeans GUI builder makes it easy to make a custom user interface without writing a single line of code (which is the main reason UGS uses NetBeans!). Using the GUI builder we'll add some buttons and a table. This step can be as elaborate as you want. If you're a seasoned swing developer and prefer not to use the magic GUI builder, no worries, you can create the UI programatically as well - but that is a different tutorial. Take a look at the screenshot below. The [TopComponent] - Navigator - Editor window shows all the objects that have been added with the GUI builder. There are four JButtons, a JTable nested inside a JScrollPane and a JPanel which I used to make alignment a little easier (The GUI Builder is powerful, but it can also be a bit quirky). Putting the JTable inside a JScrollPane makes it so that if too many items get added to the table it will scroll rather than dissapear off the bottom. Note: The name given to these components will be used in the code, so be sure to use the names shown in the screenshot. The JTable is going to be the trickiest part of build the GUI. To configure the table right-click the JTable object from the component navigator and select Table Contents... . Here you can add our 3 columns and specify that the data types. You can also specify which columns are editable, in this example we want the user to be able to type in what type of tool should be used. Autogenerated code Before writing any code, lets take a look at what has already been automatically generated for us. Just above the class there are a number of annotations. These are used by the NetBeans platform, most of them were setup according to how you filled in the Wizards earlier. They can also configure things like keyboard shortcuts, and where things are put in the dropdown menus. Within the class there are several grayed out sections. This is code generated by NetBeans which the IDE prevents you from modifying outside the GUI builder or in some cases component properties. For example if you wanted to use a custom JTable, you would configure the table in the GUI builder by adding a custom constructor. At the end of the file is componentOpened and componentClosed , these are lifecycle events that are called when the window has been opened or closed. Also at the end of the file is writeProperties and readProperties , these are used to save the window state between runs. Annotated code This is the longest section because it will explain every line of code added to the WorkflowModuleTutorial class. The most complicated code deals with Swing component manipulation, with just a smattering of UGS lifecycle events to push things along. Class signature First there are a few class state object we'll need and two Listeners we'll be implementing. /** * UGSEventListener - this is how a plugin can listen to UGS lifecycle events. * ListSelectionListener - listen for table selections. */ public final class WorkflowWindowTutorialTopComponent extends TopComponent implements UGSEventListener, ListSelectionListener { // These are the UGS backend objects for interacting with the backend. private final Settings settings; private final BackendAPI backend; // This is used to identify when a stream has completed. private boolean wasSending; // This is used in most methods, so cache it here. DefaultTableModel model; Constructor In the constructor we register the class with the UGS backend and also set the class as a listener to table selection events. public WorkflowWindowTopComponent() { initComponents(); setName(Bundle.CTL_WorkflowWindowTopComponent()); setToolTipText(Bundle.HINT_WorkflowWindowTopComponent()); // This is how to access the UGS backend and register the listener. // CentralLookup is used to get singleton instances of the UGS // Settings and BackendAPI objects. settings = CentralLookup.getDefault().lookup(Settings.class); backend = CentralLookup.getDefault().lookup(BackendAPI.class); backend.addUGSEventListener(this); // Allow contiguous ranges of selections and register a listener. this.fileTable.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION); ListSelectionModel cellSelectionModel = this.fileTable.getSelectionModel(); cellSelectionModel.addListSelectionListener(this); // Cache the model object. model = (DefaultTableModel)this.fileTable.getModel(); } UGS Event Listener This is the event sent from the UGS Backend, when a file is loaded or the state changes a notification will be sent. If the state switches from COMM_SENDING to COMM_IDLE we'll run a completeFile method. If a file is loaded, we add it to the table. @Override public void UGSEvent(UGSEvent cse) { if (cse.isStateChangeEvent()) { if (wasSending && cse.getControlState() == ControlState.COMM_IDLE) this.completeFile(backend.getGcodeFile()); wasSending = backend.isSending(); } if (cse.isFileChangeEvent()) { this.addFileToWorkflow(backend.getGcodeFile()); } } File Complete Handler When a command is complete we'll update the JTable, select the next file that needs to be sent and popup a notification informing the user what they should do next. The selection event will be sent and handled in the selection handler. public void completeFile(File gcodeFile) { if (gcodeFile == null) return; // Make sure the file is loaded in the table. int fileIndex = findFileIndex(gcodeFile); if (fileIndex < 0) return; // Mark that it has been completed. model.setValueAt(true, fileIndex, 2); fileIndex++; String message; // Make sure there is another command left. if (fileIndex < fileTable.getRowCount()) { String nextTool = (String) model.getValueAt(fileIndex, 1); String messageTemplate = \"Finished sending '%s'.\\n\" + \"The next file uses tool '%s'\\n\" + \"Load tool and move machine to its zero location\\n\" + \"and click send to continue this workflow.\"; message = String.format( messageTemplate, gcodeFile.getName(), nextTool); // Select the next row, this will trigger a selection event. fileTable.setRowSelectionInterval(fileIndex, fileIndex); // Use a different message if we're finished. } else { message = \"Finished sending the last file!\"; } // Display a notification. java.awt.EventQueue.invokeLater(() -> { JOptionPane.showMessageDialog(new JFrame(), message, \"Workflow Event\", JOptionPane.PLAIN_MESSAGE); }); } JTable Selection Listener This is the selection listener, when a file is selected load it in the backend. @Override public void valueChanged(ListSelectionEvent e) { int[] selectedRow = fileTable.getSelectedRows(); // Only load files when there is a single selection. if (selectedRow.length == 1) { // Pull the file out of the table and set it in the backend. String file = (String) model.getValueAt(selectedRow[0], 0); try { backend.setGcodeFile(new File(file)); } catch (Exception ex) { Exceptions.printStackTrace(ex); } } } JTable Helper Helper method to add a file to the JTable, first making sure that it isn't already in the table. public void addFileToWorkflow(File gcodeFile) { if (gcodeFile == null) { return; } int fileIndex = findFileIndex(gcodeFile); // Don't re-add a file. if (fileIndex >= 0) { return; } model.addRow(new Object[]{ gcodeFile.getAbsolutePath(), \"default\", false }); // Fire off the selection event to load the file. int lastRow = fileTable.getRowCount() - 1; fileTable.setRowSelectionInterval(lastRow, lastRow); } Add/Remove Button Action Handlers Now we implement the button event methods. They are generated by double clicking the buttons in the GUI Builder. This generates the swing code that attaches the ActionPerformed events to the button click callbacks. addButtonActionPerformed simply displays a file chooser (using some UGS library built ins) and calls the addFileToWorkflow method defined earlier. removeButtonActoinPerformed is even simpler, it uses standard JTable functionality to remove any selected rows. The only thing clever here is that rows are removed starting from the end to avoid having the index of later selections change while deleting rows one at a time. private void addButtonActionPerformed(ActionEvent evt) { // Open a file chooser pointing at the last opened directory. JFileChooser fileChooser = GcodeFileTypeFilter.getGcodeFileChooser( settings.getLastOpenedFilename()); int returnVal = fileChooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File gcodeFile = fileChooser.getSelectedFile(); // Save the new directory! settings.setLastOpenedFilename(gcodeFile.getParent()); addFileToWorkflow(gcodeFile); } } private void removeButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); if (selectedRows.length == 0) return; Arrays.sort(selectedRows); for (int i = selectedRows.length - 1; i >= 0; i--) { int row = selectedRows[i] this.model.removeRow(row); this.model.fireTableRowsDeleted(row, row); } } Up / Down Button Action Handlers The up and down action buttons are pure java code. They don't do anything you wouldn't do with any other Swing application. The code here deals strictly with moving selections around. Although a little tricky, and not totally relevant to UGS, they are included because the feature wouldn't be complete without them. private void upButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); // Exit early if nothing is selected. if (selectedRows.length == 0) return; Arrays.sort(selectedRows); // Exit early if the selected range can't move. if (selectedRows[0] == 0) return; for (int i = 0; i < selectedRows.length; i++) { selectedRows[i] = this.moveRow(selectedRows[i], -1); } int first = selectedRows[0]; int last = selectedRows[selectedRows.length-1]; fileTable.setRowSelectionInterval(first, last); } private void downButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); // Exit early if nothing is selected. if (selectedRows.length == 0) return; Arrays.sort(selectedRows); // Exit early if the selected range can't move. if (selectedRows[selectedRows.length-1] == fileTable.getRowCount()) return; for (int i = selectedRows.length - 1; i >= 0; i--) { selectedRows[i] = this.moveRow(selectedRows[i], 1); } int first = selectedRows[0]; int last = selectedRows[selectedRows.length-1]; fileTable.setRowSelectionInterval(first, last); } NetBeans Platform Component Lifecycle Code Of the automatically generated methods, componentOpened is the only one which needed some custom code. In case the component had been closed earlier or wasn't loaded until after a file stream started, grab the wasSending state and save it for later. @Override public void componentOpened() { this.wasSending = backend.isSending(); } @Override public void componentClosed() { // No special close handling. } void writeProperties(java.util.Properties p) { // better to version settings since initial version as advocated at // http://wiki.apidesign.org/wiki/PropertyFiles p.setProperty(\"version\", \"1.0\"); // We could save the loaded files here } void readProperties(java.util.Properties p) { String version = p.getProperty(\"version\"); // We could load previously loaded files here } Helper Methods Finally, here are the helper methods used above. /** * Look for the provided file in the file table. */ private int findFileIndex(File gcodeFile) { if (gcodeFile == null) return -1; for (int i = 0; i < model.getRowCount(); i++) { String file = (String) model.getValueAt(i, 0); if (file != null && gcodeFile.getAbsolutePath().equals(file)) { return i; } } return -1; } /** * Move a given row by some offset. If the offset would move the row outside * of the current table size, the row is not moved. */ private int moveRow(int row, int offset) { int dest = row + offset; if (dest < 0 || dest >= model.getRowCount()) { return row; } model.moveRow(row, row, dest); return dest; } Conclusion Here is a quick screencast of what this plugin does for us. In the GUI builder I swapped in some up/down arrows compared to the tutorial.","title":"Plugin"},{"location":"dev/plugin/#note-the-ugs-platform-has-been-updated-to-use-maven-some-parts-of-this-document-need-to-be-updated-to-reflect-that-change","text":"","title":"Note: The UGS Platform has been updated to use maven. Some parts of this document need to be updated to reflect that change."},{"location":"dev/plugin/#plugin-development","text":"The UGS Platform is built ontop of the NetBeans Platform. This gives us powerful tools to work with, including a robust plugin system. The heart of the UGS Platform is a module which wraps and exposes the Universal Gcode Sender JAR file - the same jar you could execute to run the Classic GUI! Other than using the UGSLib module, developing a plugin for the UGS Platform is exactly the same as developing any other NetBeans Platform plugin. And there is lots of great documentation for that, here is the NetBeans Platform Plugin Quick Start guide.","title":"Plugin development"},{"location":"dev/plugin/#workflow-plugin-tutorial","text":"In this tutorial we're going to build a window to help manage jobs that use multiple tools which are split into multiple files. The rough design idea will have a central table with four columns containing: * File name * Tool name (editable) * Finished flag There will be a pair of buttons to add and remove files from the table, and we will also hook up a UGS event listener to detect when files are opened from other areas of the interface as well. Lastly, we'll add another pair of buttons to move rows around in the table, so that we can reorganize the workflow if files were added out of order the order. Here is a sketch of what we're building:","title":"Workflow Plugin Tutorial"},{"location":"dev/plugin/#create-and-configure-project","text":"Universal Gcode Sender is developed with NetBeans, and plugins are no exception. Once you've cloned the Universal Gcode Sender project you should be able to open the UGSPlatform folder with NetBeans and it will discover a project that you can open. To start building your module expand the UGSPlatform section, right-click the modules directory and select Add New... . This will open up a wizard where you name the module, and declare the source path. For this example the module is named WorkflowModuleTutorial and the source path is com.willwinder.ugs.nbp.workflowmoduletutorial which is the convention used in the core modules.","title":"Create and configure project"},{"location":"dev/plugin/#add-ugs-dependencies","text":"Your module should now be listed in the Modules section. If it doesn't you may need to restart NetBeans. Before we dive into the code there are a couple helper classes to import which will give you full access to the UGS API. Double click your module from the Modules section to open the code, then right-click the top level item which appeared and select the properties menu. Select Add Dependency... , here you should search for UGSLib and CentralLookup then add them to your plugin.","title":"Add UGS dependencies"},{"location":"dev/plugin/#create-window-class","text":"Now we're ready to build the module. In this tutorial we're building a window to manage a multi-job workflow, so we'll start by adding a window to customize. Open the new module and right click the new package, in the context menu go to New -> Window... . To bring up the new window wizard. In the first screen of the wizard choose the default location your window will appear. Custom locations have been designed for UGS Platform, the largest is named visualizer because it is the Visualizer's default location. We'll use this location for our plugin. This means that when our plugin opens it will be tabbed with the Visualizer module. Click next and choose a class name for your module, for this tutorial I'm going to call it WorkflowModuleTutorial .","title":"Create window class"},{"location":"dev/plugin/#build-the-gui","text":"The NetBeans GUI builder makes it easy to make a custom user interface without writing a single line of code (which is the main reason UGS uses NetBeans!). Using the GUI builder we'll add some buttons and a table. This step can be as elaborate as you want. If you're a seasoned swing developer and prefer not to use the magic GUI builder, no worries, you can create the UI programatically as well - but that is a different tutorial. Take a look at the screenshot below. The [TopComponent] - Navigator - Editor window shows all the objects that have been added with the GUI builder. There are four JButtons, a JTable nested inside a JScrollPane and a JPanel which I used to make alignment a little easier (The GUI Builder is powerful, but it can also be a bit quirky). Putting the JTable inside a JScrollPane makes it so that if too many items get added to the table it will scroll rather than dissapear off the bottom. Note: The name given to these components will be used in the code, so be sure to use the names shown in the screenshot. The JTable is going to be the trickiest part of build the GUI. To configure the table right-click the JTable object from the component navigator and select Table Contents... . Here you can add our 3 columns and specify that the data types. You can also specify which columns are editable, in this example we want the user to be able to type in what type of tool should be used.","title":"Build the GUI"},{"location":"dev/plugin/#autogenerated-code","text":"Before writing any code, lets take a look at what has already been automatically generated for us. Just above the class there are a number of annotations. These are used by the NetBeans platform, most of them were setup according to how you filled in the Wizards earlier. They can also configure things like keyboard shortcuts, and where things are put in the dropdown menus. Within the class there are several grayed out sections. This is code generated by NetBeans which the IDE prevents you from modifying outside the GUI builder or in some cases component properties. For example if you wanted to use a custom JTable, you would configure the table in the GUI builder by adding a custom constructor. At the end of the file is componentOpened and componentClosed , these are lifecycle events that are called when the window has been opened or closed. Also at the end of the file is writeProperties and readProperties , these are used to save the window state between runs.","title":"Autogenerated code"},{"location":"dev/plugin/#annotated-code","text":"This is the longest section because it will explain every line of code added to the WorkflowModuleTutorial class. The most complicated code deals with Swing component manipulation, with just a smattering of UGS lifecycle events to push things along.","title":"Annotated code"},{"location":"dev/plugin/#class-signature","text":"First there are a few class state object we'll need and two Listeners we'll be implementing. /** * UGSEventListener - this is how a plugin can listen to UGS lifecycle events. * ListSelectionListener - listen for table selections. */ public final class WorkflowWindowTutorialTopComponent extends TopComponent implements UGSEventListener, ListSelectionListener { // These are the UGS backend objects for interacting with the backend. private final Settings settings; private final BackendAPI backend; // This is used to identify when a stream has completed. private boolean wasSending; // This is used in most methods, so cache it here. DefaultTableModel model;","title":"Class signature"},{"location":"dev/plugin/#constructor","text":"In the constructor we register the class with the UGS backend and also set the class as a listener to table selection events. public WorkflowWindowTopComponent() { initComponents(); setName(Bundle.CTL_WorkflowWindowTopComponent()); setToolTipText(Bundle.HINT_WorkflowWindowTopComponent()); // This is how to access the UGS backend and register the listener. // CentralLookup is used to get singleton instances of the UGS // Settings and BackendAPI objects. settings = CentralLookup.getDefault().lookup(Settings.class); backend = CentralLookup.getDefault().lookup(BackendAPI.class); backend.addUGSEventListener(this); // Allow contiguous ranges of selections and register a listener. this.fileTable.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION); ListSelectionModel cellSelectionModel = this.fileTable.getSelectionModel(); cellSelectionModel.addListSelectionListener(this); // Cache the model object. model = (DefaultTableModel)this.fileTable.getModel(); }","title":"Constructor"},{"location":"dev/plugin/#ugs-event-listener","text":"This is the event sent from the UGS Backend, when a file is loaded or the state changes a notification will be sent. If the state switches from COMM_SENDING to COMM_IDLE we'll run a completeFile method. If a file is loaded, we add it to the table. @Override public void UGSEvent(UGSEvent cse) { if (cse.isStateChangeEvent()) { if (wasSending && cse.getControlState() == ControlState.COMM_IDLE) this.completeFile(backend.getGcodeFile()); wasSending = backend.isSending(); } if (cse.isFileChangeEvent()) { this.addFileToWorkflow(backend.getGcodeFile()); } }","title":"UGS Event Listener"},{"location":"dev/plugin/#file-complete-handler","text":"When a command is complete we'll update the JTable, select the next file that needs to be sent and popup a notification informing the user what they should do next. The selection event will be sent and handled in the selection handler. public void completeFile(File gcodeFile) { if (gcodeFile == null) return; // Make sure the file is loaded in the table. int fileIndex = findFileIndex(gcodeFile); if (fileIndex < 0) return; // Mark that it has been completed. model.setValueAt(true, fileIndex, 2); fileIndex++; String message; // Make sure there is another command left. if (fileIndex < fileTable.getRowCount()) { String nextTool = (String) model.getValueAt(fileIndex, 1); String messageTemplate = \"Finished sending '%s'.\\n\" + \"The next file uses tool '%s'\\n\" + \"Load tool and move machine to its zero location\\n\" + \"and click send to continue this workflow.\"; message = String.format( messageTemplate, gcodeFile.getName(), nextTool); // Select the next row, this will trigger a selection event. fileTable.setRowSelectionInterval(fileIndex, fileIndex); // Use a different message if we're finished. } else { message = \"Finished sending the last file!\"; } // Display a notification. java.awt.EventQueue.invokeLater(() -> { JOptionPane.showMessageDialog(new JFrame(), message, \"Workflow Event\", JOptionPane.PLAIN_MESSAGE); }); }","title":"File Complete Handler"},{"location":"dev/plugin/#jtable-selection-listener","text":"This is the selection listener, when a file is selected load it in the backend. @Override public void valueChanged(ListSelectionEvent e) { int[] selectedRow = fileTable.getSelectedRows(); // Only load files when there is a single selection. if (selectedRow.length == 1) { // Pull the file out of the table and set it in the backend. String file = (String) model.getValueAt(selectedRow[0], 0); try { backend.setGcodeFile(new File(file)); } catch (Exception ex) { Exceptions.printStackTrace(ex); } } }","title":"JTable Selection Listener"},{"location":"dev/plugin/#jtable-helper","text":"Helper method to add a file to the JTable, first making sure that it isn't already in the table. public void addFileToWorkflow(File gcodeFile) { if (gcodeFile == null) { return; } int fileIndex = findFileIndex(gcodeFile); // Don't re-add a file. if (fileIndex >= 0) { return; } model.addRow(new Object[]{ gcodeFile.getAbsolutePath(), \"default\", false }); // Fire off the selection event to load the file. int lastRow = fileTable.getRowCount() - 1; fileTable.setRowSelectionInterval(lastRow, lastRow); }","title":"JTable Helper"},{"location":"dev/plugin/#addremove-button-action-handlers","text":"Now we implement the button event methods. They are generated by double clicking the buttons in the GUI Builder. This generates the swing code that attaches the ActionPerformed events to the button click callbacks. addButtonActionPerformed simply displays a file chooser (using some UGS library built ins) and calls the addFileToWorkflow method defined earlier. removeButtonActoinPerformed is even simpler, it uses standard JTable functionality to remove any selected rows. The only thing clever here is that rows are removed starting from the end to avoid having the index of later selections change while deleting rows one at a time. private void addButtonActionPerformed(ActionEvent evt) { // Open a file chooser pointing at the last opened directory. JFileChooser fileChooser = GcodeFileTypeFilter.getGcodeFileChooser( settings.getLastOpenedFilename()); int returnVal = fileChooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File gcodeFile = fileChooser.getSelectedFile(); // Save the new directory! settings.setLastOpenedFilename(gcodeFile.getParent()); addFileToWorkflow(gcodeFile); } } private void removeButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); if (selectedRows.length == 0) return; Arrays.sort(selectedRows); for (int i = selectedRows.length - 1; i >= 0; i--) { int row = selectedRows[i] this.model.removeRow(row); this.model.fireTableRowsDeleted(row, row); } }","title":"Add/Remove Button Action Handlers"},{"location":"dev/plugin/#up-down-button-action-handlers","text":"The up and down action buttons are pure java code. They don't do anything you wouldn't do with any other Swing application. The code here deals strictly with moving selections around. Although a little tricky, and not totally relevant to UGS, they are included because the feature wouldn't be complete without them. private void upButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); // Exit early if nothing is selected. if (selectedRows.length == 0) return; Arrays.sort(selectedRows); // Exit early if the selected range can't move. if (selectedRows[0] == 0) return; for (int i = 0; i < selectedRows.length; i++) { selectedRows[i] = this.moveRow(selectedRows[i], -1); } int first = selectedRows[0]; int last = selectedRows[selectedRows.length-1]; fileTable.setRowSelectionInterval(first, last); } private void downButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); // Exit early if nothing is selected. if (selectedRows.length == 0) return; Arrays.sort(selectedRows); // Exit early if the selected range can't move. if (selectedRows[selectedRows.length-1] == fileTable.getRowCount()) return; for (int i = selectedRows.length - 1; i >= 0; i--) { selectedRows[i] = this.moveRow(selectedRows[i], 1); } int first = selectedRows[0]; int last = selectedRows[selectedRows.length-1]; fileTable.setRowSelectionInterval(first, last); }","title":"Up / Down Button Action Handlers"},{"location":"dev/plugin/#netbeans-platform-component-lifecycle-code","text":"Of the automatically generated methods, componentOpened is the only one which needed some custom code. In case the component had been closed earlier or wasn't loaded until after a file stream started, grab the wasSending state and save it for later. @Override public void componentOpened() { this.wasSending = backend.isSending(); } @Override public void componentClosed() { // No special close handling. } void writeProperties(java.util.Properties p) { // better to version settings since initial version as advocated at // http://wiki.apidesign.org/wiki/PropertyFiles p.setProperty(\"version\", \"1.0\"); // We could save the loaded files here } void readProperties(java.util.Properties p) { String version = p.getProperty(\"version\"); // We could load previously loaded files here }","title":"NetBeans Platform Component Lifecycle Code"},{"location":"dev/plugin/#helper-methods","text":"Finally, here are the helper methods used above. /** * Look for the provided file in the file table. */ private int findFileIndex(File gcodeFile) { if (gcodeFile == null) return -1; for (int i = 0; i < model.getRowCount(); i++) { String file = (String) model.getValueAt(i, 0); if (file != null && gcodeFile.getAbsolutePath().equals(file)) { return i; } } return -1; } /** * Move a given row by some offset. If the offset would move the row outside * of the current table size, the row is not moved. */ private int moveRow(int row, int offset) { int dest = row + offset; if (dest < 0 || dest >= model.getRowCount()) { return row; } model.moveRow(row, row, dest); return dest; }","title":"Helper Methods"},{"location":"dev/plugin/#conclusion","text":"Here is a quick screencast of what this plugin does for us. In the GUI builder I swapped in some up/down arrows compared to the tutorial.","title":"Conclusion"},{"location":"guide/classic/","text":"Classic Interface This is the classic user interface. Initially designed as a bare-bones gcode sender it has grown in features since the initial release in 2012. It is built with Swing using the NetBeans Builder built-in graphical GUI layout plugin. The graphical nature of the plugin allows non-developers to tweak the interface without needing to write any code. Features Self-executing JAR, all native dependancies for Windows, OSX, Linux and RaspberryPi are built in. GRBL and TinyG support, extensible interface for adding more. 3D Gcode Visualizer. Real time machine feedback and control. Built in pendant - connect to UGS from your smart phone or tablet. Configurable Gcode Processing - Remove comments, truncate decimals, expand arcs Constant memory usage - run any sized gcode file. Extensive development test suite for all core features. Localized: Afrikaans, Italian, Spanish, German, French, Greek, Dutch, English How to run Download and install the version of Java listed on the download page, or a later version. Download and extract the Classic GUI from one of the links here. On most Operating Systems you can now double click the UniversalGcodeSender.jar file. If double clicking does not work, execute start-windows.bat on Windows or start.sh on Linux or Mac OSX. Usage == TODO: Pull requests accepted! ==","title":"Classic"},{"location":"guide/classic/#classic-interface","text":"This is the classic user interface. Initially designed as a bare-bones gcode sender it has grown in features since the initial release in 2012. It is built with Swing using the NetBeans Builder built-in graphical GUI layout plugin. The graphical nature of the plugin allows non-developers to tweak the interface without needing to write any code.","title":"Classic Interface"},{"location":"guide/classic/#features","text":"Self-executing JAR, all native dependancies for Windows, OSX, Linux and RaspberryPi are built in. GRBL and TinyG support, extensible interface for adding more. 3D Gcode Visualizer. Real time machine feedback and control. Built in pendant - connect to UGS from your smart phone or tablet. Configurable Gcode Processing - Remove comments, truncate decimals, expand arcs Constant memory usage - run any sized gcode file. Extensive development test suite for all core features. Localized: Afrikaans, Italian, Spanish, German, French, Greek, Dutch, English","title":"Features"},{"location":"guide/classic/#how-to-run","text":"Download and install the version of Java listed on the download page, or a later version. Download and extract the Classic GUI from one of the links here. On most Operating Systems you can now double click the UniversalGcodeSender.jar file. If double clicking does not work, execute start-windows.bat on Windows or start.sh on Linux or Mac OSX.","title":"How to run"},{"location":"guide/classic/#usage","text":"== TODO: Pull requests accepted! ==","title":"Usage"},{"location":"guide/common/","text":"Common Features Because the UGS Platform and the Classic GUI both build on the same foundation they have a lot in common. Gcode Processing Error handling When GRBL reports an error while processing a line of gcode UGS will automatically pause the stream. In some cases you might want to continue and ignore the error in the future. The following dialog is displayed allowing you to do so: If you select 'yes' at this point, a line will be added to the controller options:","title":"Common Features"},{"location":"guide/common/#common-features","text":"Because the UGS Platform and the Classic GUI both build on the same foundation they have a lot in common.","title":"Common Features"},{"location":"guide/common/#gcode-processing","text":"","title":"Gcode Processing"},{"location":"guide/common/#error-handling","text":"When GRBL reports an error while processing a line of gcode UGS will automatically pause the stream. In some cases you might want to continue and ignore the error in the future. The following dialog is displayed allowing you to do so: If you select 'yes' at this point, a line will be added to the controller options:","title":"Error handling"},{"location":"guide/languages/","text":"Languages UGS has excellent support for many languages. In many cases the locale on your machine will properly detect your locale and apply the language automatically, but in some cases you'll need to change it in the preferences. Platform Open 'Preferences', the location of this button varies based on the Operating System. Once the Options window is open, select 'UGS', the 'Sender Options' and finally set your language. Click 'Apply' and restart UGS for changes to be applied. Classic Open 'Sender Settings' in the Settings menu, and select your language. Restart for changes to be applied.","title":"Languages"},{"location":"guide/languages/#languages","text":"UGS has excellent support for many languages. In many cases the locale on your machine will properly detect your locale and apply the language automatically, but in some cases you'll need to change it in the preferences.","title":"Languages"},{"location":"guide/languages/#platform","text":"Open 'Preferences', the location of this button varies based on the Operating System. Once the Options window is open, select 'UGS', the 'Sender Options' and finally set your language. Click 'Apply' and restart UGS for changes to be applied.","title":"Platform"},{"location":"guide/languages/#classic","text":"Open 'Sender Settings' in the Settings menu, and select your language. Restart for changes to be applied.","title":"Classic"},{"location":"guide/platform/","text":"UGS Platform The UGS Platform is the next generation of Universal Gcode Sender. It is built ontop of the Netbeans Platform which allows us to leverage its mature modular framework. This platform allows more features to be added without compromising on code quality, or being bogged down by a home grown framework. The Classic GUI is used as a library, so core features benefit both interfaces. Platform Benefits This is the current target for new UGS features. Out of the box dynamic windowing system allows arranging the UI dynamically. Plugin Framework available for decoupling features. Huge library of modules to leverage: Code Editors, Auto-updates, Keybindings Usage How to run Download and extract the UGS Platform build from the downloads page. Go to the ugsplatform/bin directory. On Windows run ugsplatform.exe or ugsplatform64.exe . On Linux or Mac OSX run ugsplatform . Connecting to the controller Start off by connecting to your controller hardware using the toolbar at the top of the program. Select the correct hardware in the firmware combo box: Refresh the serial ports list and select the correct port for your hardware. If you can't find the correct port in the list, make sure you have the drivers installed. The ports are usually named like this: - MacOSX : /dev/tty.usbmodem* or /dev/tty.usbserial* - Linux : /dev/ttyUSB* or /dev/ttyACM* - Windows : COM1 , COM2 and so on. Select the correct baud rate for your controller. - GRBL - version 0.9 or later are using 115200, earlier versions are using 9600. - TinyG/g2core will adapt to the baud rate you are connecting with so it really doesn't matter. Setup wizard Do you need help configuring your hardware? In that case the setup wizard might be helpful, it will allow you to configure limit switches, homing, soft limits and calibrating your machine. To start the wizard open the menu Machine -> Setup wizard... If you aren't connected to your controller a connection dialog will be presented: The version of the controller will be shown after connecting and the available setup steps will be loaded for your controller: If you have a settings file from your machine manufacturer or if you have a backup of your settings you may import it here: On the motor wiring configuration page you can test the direction of your motors and change its direction if needed. On the step calibration page you can move the machine and measure the actual distance. It will then recommend a step setting for your machine: If you have limit switches you may enable them on this page and test if they are firing correctly: If limit switches are enabled you may enable homing as well. This page helps you figuring out in which direction the homing should be made: If homing is enabled you may also configure soft limits so that the controller knows if it can process a command without triggering limit switches: Digital read-out The Digital read-out (or Controller state) panel displays the current status of your machine such as the work/machine coordinates, machine/spindle speeds and gcode states. The panel provides the following functions: Coordinates of both the machine and your current work Buttons for resetting the work coordinates for each axis Changable work coordinates using simple mathematical expressions. You can either set an exact coordinate or, as an example, use the following # / 2 to divide the current position in half. The # -character will be replaced with current position. If you start your expression with * or / the current position is prepended. Display the current machine state (Idle, Run, Jog, Alarm, etc.) Display the current feed rate and spindle speed Display the different GCode states Display alarm with the triggered limit switches Overrides With the overrides plugin you can tweak the running session of a gcode program in real time. You can speed up/down the feed rate, spindle and the fast movement. To use, open the menu Window -> Overrides .","title":"Platform"},{"location":"guide/platform/#ugs-platform","text":"The UGS Platform is the next generation of Universal Gcode Sender. It is built ontop of the Netbeans Platform which allows us to leverage its mature modular framework. This platform allows more features to be added without compromising on code quality, or being bogged down by a home grown framework. The Classic GUI is used as a library, so core features benefit both interfaces.","title":"UGS Platform"},{"location":"guide/platform/#platform-benefits","text":"This is the current target for new UGS features. Out of the box dynamic windowing system allows arranging the UI dynamically. Plugin Framework available for decoupling features. Huge library of modules to leverage: Code Editors, Auto-updates, Keybindings","title":"Platform Benefits"},{"location":"guide/platform/#usage","text":"","title":"Usage"},{"location":"guide/platform/#how-to-run","text":"Download and extract the UGS Platform build from the downloads page. Go to the ugsplatform/bin directory. On Windows run ugsplatform.exe or ugsplatform64.exe . On Linux or Mac OSX run ugsplatform .","title":"How to run"},{"location":"guide/platform/#connecting-to-the-controller","text":"Start off by connecting to your controller hardware using the toolbar at the top of the program. Select the correct hardware in the firmware combo box: Refresh the serial ports list and select the correct port for your hardware. If you can't find the correct port in the list, make sure you have the drivers installed. The ports are usually named like this: - MacOSX : /dev/tty.usbmodem* or /dev/tty.usbserial* - Linux : /dev/ttyUSB* or /dev/ttyACM* - Windows : COM1 , COM2 and so on. Select the correct baud rate for your controller. - GRBL - version 0.9 or later are using 115200, earlier versions are using 9600. - TinyG/g2core will adapt to the baud rate you are connecting with so it really doesn't matter.","title":"Connecting to the controller"},{"location":"guide/platform/#setup-wizard","text":"Do you need help configuring your hardware? In that case the setup wizard might be helpful, it will allow you to configure limit switches, homing, soft limits and calibrating your machine. To start the wizard open the menu Machine -> Setup wizard... If you aren't connected to your controller a connection dialog will be presented: The version of the controller will be shown after connecting and the available setup steps will be loaded for your controller: If you have a settings file from your machine manufacturer or if you have a backup of your settings you may import it here: On the motor wiring configuration page you can test the direction of your motors and change its direction if needed. On the step calibration page you can move the machine and measure the actual distance. It will then recommend a step setting for your machine: If you have limit switches you may enable them on this page and test if they are firing correctly: If limit switches are enabled you may enable homing as well. This page helps you figuring out in which direction the homing should be made: If homing is enabled you may also configure soft limits so that the controller knows if it can process a command without triggering limit switches:","title":"Setup wizard"},{"location":"guide/platform/#digital-read-out","text":"The Digital read-out (or Controller state) panel displays the current status of your machine such as the work/machine coordinates, machine/spindle speeds and gcode states. The panel provides the following functions: Coordinates of both the machine and your current work Buttons for resetting the work coordinates for each axis Changable work coordinates using simple mathematical expressions. You can either set an exact coordinate or, as an example, use the following # / 2 to divide the current position in half. The # -character will be replaced with current position. If you start your expression with * or / the current position is prepended. Display the current machine state (Idle, Run, Jog, Alarm, etc.) Display the current feed rate and spindle speed Display the different GCode states Display alarm with the triggered limit switches","title":"Digital read-out"},{"location":"guide/platform/#overrides","text":"With the overrides plugin you can tweak the running session of a gcode program in real time. You can speed up/down the feed rate, spindle and the fast movement. To use, open the menu Window -> Overrides .","title":"Overrides"},{"location":"guide/troubleshooting/","text":"Common Problems \"Grbl has not finished booting.\" This happens when UGS connects to a serial port and does not receive the GRBL startup string. Typically this is caused by a configuration problem and can be solved by one of the following: Check the baud rate is 115200, or 9600 for very old versions of grbl. Make sure you are connecting to the correct port. Make sure you have installed any drivers required for your controller. Make sure GRBL is properly flashed on your controller. As a last resort try removing all previous UGS settings (see property Files ) and reinstall. Gcode program stopped working The UGS Parser has a configurable list of rules to skip certain patterns, these rules are typically added by a Yes/No dialog asking if you would like to skip the erroneous commands in the future. Sometimes GRBL will get into an ALARM state and there will be lots of these popups which should not be skipped in the future. Skipping good commands may lead to broken gcode. Those rules should be removed from the gcode processor by going into the controller options. In UGS Classic the option is in Settings > Gcode Processor Configuration . In UGS Platform the option is under Preferences... > UGS > Controller Options . For both, you need to uncheck or remove the invalid settings in the bottom list: Platform Specific Issues Toolbars or Windows don't appear. This usually happens if you try running the platform without the required version of Java. The user cache is initialized but some objects become corrupt and initialization fails in the future even after upgrading Java. This can be fixed by clearing out the user cache directory which can be found on the UGS \"About\" screen seen in the image below. Information Property Files Occasionally it is useful to attach some of these property files to bug reports to help with reproducing a problem. Classic Classic UGS properties are stored in your home directory, which changes based on the operating system being used: Windows : /home/user/ugs Mac : ~/Library/Preferences/ugs Linux : ~/ugs Files include UniversalGcodeSender.json which contain different settings, and a firmware_config directory which contains several configurations for different firmwares and testing profiles. Platform Platform UGS properties are stored in your home directory: Windows : /home/user/ugs Mac : ~/Library/Preferences/ugs Linux : ~/ugs The platform version of UGS contains additional property files automatically created by the NetBeans Platform. These files are also contained in various locations based on the operating system being used. You can find the exact locations of these files in the About / Help menu. It is sometimes necessary to clear out these properties between major feature updates. Logs are typically located in these directories: Windows 7 : C:\\Users\\username\\AppData\\Roaming\\ugsplatform\\2.0-SNAPSHOT\\var\\log\\messages.log Windows XP : C:\\Documents and Settings\\username\\Application Data\\ugsplatform\\2.0-SNAPSHOT\\var\\log\\messages.log Mac : ~/Library/Application Support/ugsplatform\\2.0-SNAPSHOT/var/log/messages.log Linux : ~/.ugsplatform/2.0-SNAPSHOT/var/log/messages.log Operating System Compatibility Problems Linux: Non Reparenting Window Managers There are a number of window managers which are \" non reparenting \", this causes some problems with Java. Some details about this problem can be seen here . Use the _JAVA_AWT_WM_NONREPARENTING environment property to fix the problem: export _JAVA_AWT_WM_NONREPARENTING=1 Program Slows Down and Send Freezes If you notice slowness while running your program, it may mean UGS is running out of available memory. There are a couple things to try: Check the controller settings, and make sure \"Arc Expander\" is not enabled. This can take a small program and turn it into a very large one by converting arcs into many small movements. Increase the memory allocated to UGS by navigating the the intsallation directory (See paths above). There is a folder named etc containing ugsplatform.conf , open this file with a simple text editor and modify the value of Xms to something like -J-Xms256m , or larger. Additional details can be found here .","title":"Help"},{"location":"guide/troubleshooting/#common-problems","text":"","title":"Common Problems"},{"location":"guide/troubleshooting/#grbl-has-not-finished-booting","text":"This happens when UGS connects to a serial port and does not receive the GRBL startup string. Typically this is caused by a configuration problem and can be solved by one of the following: Check the baud rate is 115200, or 9600 for very old versions of grbl. Make sure you are connecting to the correct port. Make sure you have installed any drivers required for your controller. Make sure GRBL is properly flashed on your controller. As a last resort try removing all previous UGS settings (see property Files ) and reinstall.","title":"\"Grbl has not finished booting.\""},{"location":"guide/troubleshooting/#gcode-program-stopped-working","text":"The UGS Parser has a configurable list of rules to skip certain patterns, these rules are typically added by a Yes/No dialog asking if you would like to skip the erroneous commands in the future. Sometimes GRBL will get into an ALARM state and there will be lots of these popups which should not be skipped in the future. Skipping good commands may lead to broken gcode. Those rules should be removed from the gcode processor by going into the controller options. In UGS Classic the option is in Settings > Gcode Processor Configuration . In UGS Platform the option is under Preferences... > UGS > Controller Options . For both, you need to uncheck or remove the invalid settings in the bottom list:","title":"Gcode program stopped working"},{"location":"guide/troubleshooting/#platform-specific-issues","text":"","title":"Platform Specific Issues"},{"location":"guide/troubleshooting/#toolbars-or-windows-dont-appear","text":"This usually happens if you try running the platform without the required version of Java. The user cache is initialized but some objects become corrupt and initialization fails in the future even after upgrading Java. This can be fixed by clearing out the user cache directory which can be found on the UGS \"About\" screen seen in the image below.","title":"Toolbars or Windows don't appear."},{"location":"guide/troubleshooting/#information","text":"","title":"Information"},{"location":"guide/troubleshooting/#property-files","text":"Occasionally it is useful to attach some of these property files to bug reports to help with reproducing a problem.","title":"Property Files"},{"location":"guide/troubleshooting/#classic","text":"Classic UGS properties are stored in your home directory, which changes based on the operating system being used: Windows : /home/user/ugs Mac : ~/Library/Preferences/ugs Linux : ~/ugs Files include UniversalGcodeSender.json which contain different settings, and a firmware_config directory which contains several configurations for different firmwares and testing profiles.","title":"Classic"},{"location":"guide/troubleshooting/#platform","text":"Platform UGS properties are stored in your home directory: Windows : /home/user/ugs Mac : ~/Library/Preferences/ugs Linux : ~/ugs The platform version of UGS contains additional property files automatically created by the NetBeans Platform. These files are also contained in various locations based on the operating system being used. You can find the exact locations of these files in the About / Help menu. It is sometimes necessary to clear out these properties between major feature updates. Logs are typically located in these directories: Windows 7 : C:\\Users\\username\\AppData\\Roaming\\ugsplatform\\2.0-SNAPSHOT\\var\\log\\messages.log Windows XP : C:\\Documents and Settings\\username\\Application Data\\ugsplatform\\2.0-SNAPSHOT\\var\\log\\messages.log Mac : ~/Library/Application Support/ugsplatform\\2.0-SNAPSHOT/var/log/messages.log Linux : ~/.ugsplatform/2.0-SNAPSHOT/var/log/messages.log","title":"Platform"},{"location":"guide/troubleshooting/#operating-system-compatibility-problems","text":"","title":"Operating System Compatibility Problems"},{"location":"guide/troubleshooting/#linux-non-reparenting-window-managers","text":"There are a number of window managers which are \" non reparenting \", this causes some problems with Java. Some details about this problem can be seen here . Use the _JAVA_AWT_WM_NONREPARENTING environment property to fix the problem: export _JAVA_AWT_WM_NONREPARENTING=1","title":"Linux: Non Reparenting Window Managers"},{"location":"guide/troubleshooting/#program-slows-down-and-send-freezes","text":"If you notice slowness while running your program, it may mean UGS is running out of available memory. There are a couple things to try: Check the controller settings, and make sure \"Arc Expander\" is not enabled. This can take a small program and turn it into a very large one by converting arcs into many small movements. Increase the memory allocated to UGS by navigating the the intsallation directory (See paths above). There is a folder named etc containing ugsplatform.conf , open this file with a simple text editor and modify the value of Xms to something like -J-Xms256m , or larger. Additional details can be found here .","title":"Program Slows Down and Send Freezes"}]} \ No newline at end of file +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Universal Gcode Sender A full featured gcode platform used for interfacing with advanced CNC controllers like GRBL , TinyG , g2core and Smoothieware . Universal Gcode Sender is a self-contained Java application which includes all external dependencies and can be used on most computers running Windows, MacOSX or Linux. Features Cross platform, tested on Windows, OSX, Linux, and Raspberry Pi. 3D Gcode Visualizer with color coded line segments and real time tool position feedback. Duration estimates. Support for Gamepads and Joysticks Web pendant interface Over 3000 lines of unit test code, and another 1000 lines of comments documenting the tests. Configuratble gcode optimization: Remove comments Truncate decimal precision to configurable amount Convert arcs (G2/G3) to line segments Remove whitespace GRBL 1.1 Features Overrides and Toggles Platform version only. Easily control the real time feed and speed overrides by enabling the Overrides widget in the Window menu. Jog Mode With older versions of GRBL UGS is pretty reliable when it comes to jogging, but there are limitations. With GRBL 1.1 this is no longer the case when using the new JOG MODE syntax. This first-class jog mode guarantees the GCODE state will be unaltered, and also allows you to stop a jog while it is in progress. UGS uses this new syntax automatically when it detects a version of GRBL which supports it. During a jog use the STOP action to stop an in-progress jog: >> $J=G21G91X0.7F11 ok >> $J=G21G91Y0.7F11 ok >> $J=G21G91Z-0.7F11 ok Pin State Reporting Platform version only. New flags have been added to the controller state window to indicate when various external switches are enabled. Message resolution GRBL removed most help and error messages to make room for new features on the micro controller, they are now provided as data files in the grbl source code. UGS uses these data files to resolve all error codes and setting strings. Screenshots Platform The next generation of UGS. Fully modular front end powered by the same robust library created for the Classic GUI. Fully modular GUI, reconfigure windows to suite your needs. Built in gcode editor with line highlighter. Customizable keybindings. Zoom to selection with command and drag. Right click in the visualizer to jog to a specific XY location. Classic The classic GUI has everything you need to get started. 3D visualizer. Job complete dialog. Machine control. Sponsors Donations Universal Gcode Sender is free software developed and maintained in my free time for the hobby cnc community. If you would like to make a monetary donation, all proceeds will be used to try convincing my wife that it is worth my time. input[type=\"radio\"] { margin: 0 5px 0 15px; } #donatebox { text-align:center; width: 300px; } $1 $5 $10 $25 Custom amount","title":"Home"},{"location":"#universal-gcode-sender","text":"A full featured gcode platform used for interfacing with advanced CNC controllers like GRBL , TinyG , g2core and Smoothieware . Universal Gcode Sender is a self-contained Java application which includes all external dependencies and can be used on most computers running Windows, MacOSX or Linux.","title":"Universal Gcode Sender"},{"location":"#features","text":"Cross platform, tested on Windows, OSX, Linux, and Raspberry Pi. 3D Gcode Visualizer with color coded line segments and real time tool position feedback. Duration estimates. Support for Gamepads and Joysticks Web pendant interface Over 3000 lines of unit test code, and another 1000 lines of comments documenting the tests. Configuratble gcode optimization: Remove comments Truncate decimal precision to configurable amount Convert arcs (G2/G3) to line segments Remove whitespace","title":"Features"},{"location":"#grbl-11-features","text":"Overrides and Toggles Platform version only. Easily control the real time feed and speed overrides by enabling the Overrides widget in the Window menu. Jog Mode With older versions of GRBL UGS is pretty reliable when it comes to jogging, but there are limitations. With GRBL 1.1 this is no longer the case when using the new JOG MODE syntax. This first-class jog mode guarantees the GCODE state will be unaltered, and also allows you to stop a jog while it is in progress. UGS uses this new syntax automatically when it detects a version of GRBL which supports it. During a jog use the STOP action to stop an in-progress jog: >> $J=G21G91X0.7F11 ok >> $J=G21G91Y0.7F11 ok >> $J=G21G91Z-0.7F11 ok Pin State Reporting Platform version only. New flags have been added to the controller state window to indicate when various external switches are enabled. Message resolution GRBL removed most help and error messages to make room for new features on the micro controller, they are now provided as data files in the grbl source code. UGS uses these data files to resolve all error codes and setting strings.","title":"GRBL 1.1 Features"},{"location":"#screenshots","text":"","title":"Screenshots"},{"location":"#platform","text":"The next generation of UGS. Fully modular front end powered by the same robust library created for the Classic GUI. Fully modular GUI, reconfigure windows to suite your needs. Built in gcode editor with line highlighter. Customizable keybindings. Zoom to selection with command and drag. Right click in the visualizer to jog to a specific XY location.","title":"Platform"},{"location":"#classic","text":"The classic GUI has everything you need to get started. 3D visualizer. Job complete dialog. Machine control.","title":"Classic"},{"location":"#sponsors","text":"","title":"Sponsors"},{"location":"#donations","text":"Universal Gcode Sender is free software developed and maintained in my free time for the hobby cnc community. If you would like to make a monetary donation, all proceeds will be used to try convincing my wife that it is worth my time. input[type=\"radio\"] { margin: 0 5px 0 15px; } #donatebox { text-align:center; width: 300px; } $1 $5 $10 $25 Custom amount","title":"Donations"},{"location":"contributing/","text":"Contributing If you are interested in contributing to make UGS even more awesome, there are many places you can pitch in. Browse through the already reported issues or check out the discussion forum . You might know how to code, have ideas on how to improve the documentation or want to translate the software to your language. Code Pull requests are welcome! Is there a feature you would like to see, or a bug thats been bothering you? Feel free to dig in. Not sure where to start? Ask on github, use an existing ticket or create a new one. If you're planning to make a lot of changes please create an issue to discuss implementation details. A lot of effort has gone into the current design so we want to make sure everything will to work together. Documentation If you find any errors in the documentation or if there are missing pages, you are welcome to contribute! It is written using MkDocs and Markdown . Follow these instructions to get started: Fork and clone the repository https://github.com/winder/ugs_website . Make sure you have Python installed with this command in your terminal: python --version Install MkDocs and the theme modules sudo pip install mkdocs sudo pip install mkdocs-bootswatch From your cloned ugs_website directory run mkdocs serve Go to http://localhost:8000/ in your browser Now, make your changes and see them live updating in your web browser. Create a pull request in github and we will review and add your changes. Translations We are currently using POEditor to manage localization. If you would like to help please consider signing up to contribute through this service. To join the project sign up here: https://poeditor.com/join/project/2J2hB5I41Z Adding a new language You can add a new language from POEditor and start translating. If you want to stop here, create a ticket on GitHub and someone will update the project. To finish the job completely you'll need to know how to use git . Create an empty property file for your language in ugs-core/src/resources . Open src/com/willwinder/universalgcodesender/i18n/AvailableLanguages.java Add your new translation to the availableLanguages object. Update the file in update_languages.py with a mapping between the POEditor key and your new file. Run update_languages.py , see README in scripts directory for configuration detail. Only commit the new file even if others were updated. Create a GitHub pull request . Future language syncs will be done periodically with the update_languages.py script.","title":"Contributing"},{"location":"contributing/#contributing","text":"If you are interested in contributing to make UGS even more awesome, there are many places you can pitch in. Browse through the already reported issues or check out the discussion forum . You might know how to code, have ideas on how to improve the documentation or want to translate the software to your language.","title":"Contributing"},{"location":"contributing/#code","text":"Pull requests are welcome! Is there a feature you would like to see, or a bug thats been bothering you? Feel free to dig in. Not sure where to start? Ask on github, use an existing ticket or create a new one. If you're planning to make a lot of changes please create an issue to discuss implementation details. A lot of effort has gone into the current design so we want to make sure everything will to work together.","title":"Code"},{"location":"contributing/#documentation","text":"If you find any errors in the documentation or if there are missing pages, you are welcome to contribute! It is written using MkDocs and Markdown . Follow these instructions to get started: Fork and clone the repository https://github.com/winder/ugs_website . Make sure you have Python installed with this command in your terminal: python --version Install MkDocs and the theme modules sudo pip install mkdocs sudo pip install mkdocs-bootswatch From your cloned ugs_website directory run mkdocs serve Go to http://localhost:8000/ in your browser Now, make your changes and see them live updating in your web browser. Create a pull request in github and we will review and add your changes.","title":"Documentation"},{"location":"contributing/#translations","text":"We are currently using POEditor to manage localization. If you would like to help please consider signing up to contribute through this service. To join the project sign up here: https://poeditor.com/join/project/2J2hB5I41Z","title":"Translations"},{"location":"contributing/#adding-a-new-language","text":"You can add a new language from POEditor and start translating. If you want to stop here, create a ticket on GitHub and someone will update the project. To finish the job completely you'll need to know how to use git . Create an empty property file for your language in ugs-core/src/resources . Open src/com/willwinder/universalgcodesender/i18n/AvailableLanguages.java Add your new translation to the availableLanguages object. Update the file in update_languages.py with a mapping between the POEditor key and your new file. Run update_languages.py , see README in scripts directory for configuration detail. Only commit the new file even if others were updated. Create a GitHub pull request . Future language syncs will be done periodically with the update_languages.py script.","title":"Adding a new language"},{"location":"download/","text":"Download This is the latest release of UGS. For source code, nightly builds or older releases please visit github . UGS Platform The next generation platform-based interface. Version 2.1.2 Description Windows 64-bit Windows 64-bit version with bundled Java Windows 32-bit Windows 32-bit version with bundled Java MacOSX (Intel) MacOSX version with bundled Java MacOSX (Silicon) MacOSX ARM64 version (for Apple Silicon M1/M2) with bundled Java Linux Linux version with bundled Java Linux ARM Linux ARM version with bundled Java. Can be used with Raspberry Pi OS 32-bit Linux ARM64 Linux ARM64 version with bundled Java. Can be used with Raspberry Pi OS 64-bit All platforms A generic package without Java which needs to be installed separately UGS Classic The classic UGS interface with slightly less features but with the same robust backend as the Platform edition. Download Description All platforms A generic package without Java which needs to be installed separately","title":"Download"},{"location":"download/#download","text":"This is the latest release of UGS. For source code, nightly builds or older releases please visit github .","title":"Download"},{"location":"download/#ugs-platform","text":"The next generation platform-based interface. Version 2.1.2 Description Windows 64-bit Windows 64-bit version with bundled Java Windows 32-bit Windows 32-bit version with bundled Java MacOSX (Intel) MacOSX version with bundled Java MacOSX (Silicon) MacOSX ARM64 version (for Apple Silicon M1/M2) with bundled Java Linux Linux version with bundled Java Linux ARM Linux ARM version with bundled Java. Can be used with Raspberry Pi OS 32-bit Linux ARM64 Linux ARM64 version with bundled Java. Can be used with Raspberry Pi OS 64-bit All platforms A generic package without Java which needs to be installed separately","title":"UGS Platform"},{"location":"download/#ugs-classic","text":"The classic UGS interface with slightly less features but with the same robust backend as the Platform edition. Download Description All platforms A generic package without Java which needs to be installed separately","title":"UGS Classic"},{"location":"translating/","text":"Translating","title":"Translating"},{"location":"translating/#translating","text":"","title":"Translating"},{"location":"dev/backend_development/","text":"Backend architecture Similar to the front-end there are more layers on the backend to help with supporting differences between different gcode controllers and the different ways to communicate with these controllers. Because UGS depends on serial events from CNC devices, the communication between layers is also event driven. This is implemented using a series of Listener classes which pass messages from the lower levels to the upper levels whenever data is detected on the serial port (USB). Controller A controller is primarily responsible for implementing controller-specific features. Different features can be things like what happens when a Perform Homing command is requested, or how to issue status requests and parse their results. GRBL and TinyG are both supported, they share a lot of code with the AbstractController.java abstract class. Internally the AbstractController class implements several important things. It manages the stream lifecycle, keeping track of which commands have been sent, which have been completed and in some cases which are queued for sending. The controller also figures out when the stream has finished. Finally the AbstractController implements the SerialCommunicatorListener , which how its able to detect all of this state information (and allows commands to be sent to the CNC controller). The controller provides a ControllerListener interface which is used to provide real time status. Finally, the AbstractController defines a number of abstract methods which can be used by device specific controllers as needed to hook into important lifecycle events: abstract protected void closeCommBeforeEvent(); abstract protected void closeCommAfterEvent(); protected void openCommAfterEvent() throws Exception {} abstract protected void cancelSendBeforeEvent(); abstract protected void cancelSendAfterEvent(); abstract protected void pauseStreamingEvent() throws Exception; abstract protected void resumeStreamingEvent() throws Exception; abstract protected void isReadyToSendCommandsEvent() throws Exception; abstract protected void statusUpdatesEnabledValueChanged(boolean enabled); abstract protected void statusUpdatesRateValueChanged(int rate); // This one is special, because it is responsible for parsing device // responses, such as a command complete, status string, or parsing a // status event. In the case of a command complete, it must call // `commandComplete` to push the stream lifecycle along. abstract protected void rawResponseHandler(String response); Here is the public interface which controlles conform to: public interface IController { /* Observable */ public void addListener(ControllerListener cl); /* Actions */ public void performHomingCycle() throws Exception; public void returnToHome() throws Exception; public void resetCoordinatesToZero() throws Exception; public void resetCoordinateToZero(final char coord) throws Exception; public void killAlarmLock() throws Exception; public void toggleCheckMode() throws Exception; public void viewParserState() throws Exception; public void issueSoftReset() throws Exception; /* Behavior */ public void setSingleStepMode(boolean enabled); public boolean getSingleStepMode(); public void setStatusUpdatesEnabled(boolean enabled); public boolean getStatusUpdatesEnabled(); public void setStatusUpdateRate(int rate); public int getStatusUpdateRate(); public GcodeCommandCreator getCommandCreator(); public long getJobLengthEstimate(File gcodeFile); /* Serial */ public Boolean openCommPort(String port, int portRate) throws Exception; public Boolean closeCommPort() throws Exception; public Boolean isCommOpen(); /* Stream information */ public Boolean isReadyToStreamFile() throws Exception; public Boolean isStreamingFile(); public long getSendDuration(); public int rowsInSend(); public int rowsSent(); public int rowsRemaining(); /* Stream control */ public void beginStreaming() throws Exception; public void pauseStreaming() throws Exception; public void resumeStreaming() throws Exception; public void cancelSend(); /* Stream content */ public GcodeCommand createCommand(String gcode) throws Exception; public void sendCommandImmediately(GcodeCommand cmd) throws Exception; public void queueCommand(GcodeCommand cmd) throws Exception; public void queueStream(GcodeStreamReader r); public void queueRawStream(Reader r); } Communicator A communicator handles all levels of sending data to the device. Raw responses are returned to any listeners via the SerialCommunicatorListener . The AbstractCommunicator implements several listener utilities which are used by implementing classes. The BufferedCommunicator abstract class handles the process of buffering multiple commands at once in order to keep a constant stream of commands available to the CNC device. It does this in the streamCommands method by maintaining a list of active commands, and the current size of those commands. A method named processedCommand must be implemented in a subclass to determine whether a raw response indicates a command has completed. This notifies the BufferedCommunicator that it should attempt to send more commands. GrblCommunicator and TinyGCommunicator are two concrete implementations of the BufferedCommunicator . Connection This is a very thin layer which provides a way to write and receive data: abstract public boolean openPort(String name, int baud) throws Exception; abstract public void closePort() throws Exception; abstract public boolean isOpen(); abstract public void sendByteImmediately(byte b) throws Exception; abstract public void sendStringToComm(String command) throws Exception; Streaming strategy UGS attempts to use a fixed amount of memory when streaming a file. In this way it can send gcode files of any size. Files are preprocessed at the BackendAPI level using the GcodeStreamWriter class. This will serialize all the required metadata into a file. Later on that file can be opened with the GcodeStreamReader class, the Controller and Communicator classes use this. Using the reader, the Communicator class can pull out commands one at a time and send them to the Connection .","title":"Backend"},{"location":"dev/backend_development/#backend-architecture","text":"Similar to the front-end there are more layers on the backend to help with supporting differences between different gcode controllers and the different ways to communicate with these controllers. Because UGS depends on serial events from CNC devices, the communication between layers is also event driven. This is implemented using a series of Listener classes which pass messages from the lower levels to the upper levels whenever data is detected on the serial port (USB).","title":"Backend architecture"},{"location":"dev/backend_development/#controller","text":"A controller is primarily responsible for implementing controller-specific features. Different features can be things like what happens when a Perform Homing command is requested, or how to issue status requests and parse their results. GRBL and TinyG are both supported, they share a lot of code with the AbstractController.java abstract class. Internally the AbstractController class implements several important things. It manages the stream lifecycle, keeping track of which commands have been sent, which have been completed and in some cases which are queued for sending. The controller also figures out when the stream has finished. Finally the AbstractController implements the SerialCommunicatorListener , which how its able to detect all of this state information (and allows commands to be sent to the CNC controller). The controller provides a ControllerListener interface which is used to provide real time status. Finally, the AbstractController defines a number of abstract methods which can be used by device specific controllers as needed to hook into important lifecycle events: abstract protected void closeCommBeforeEvent(); abstract protected void closeCommAfterEvent(); protected void openCommAfterEvent() throws Exception {} abstract protected void cancelSendBeforeEvent(); abstract protected void cancelSendAfterEvent(); abstract protected void pauseStreamingEvent() throws Exception; abstract protected void resumeStreamingEvent() throws Exception; abstract protected void isReadyToSendCommandsEvent() throws Exception; abstract protected void statusUpdatesEnabledValueChanged(boolean enabled); abstract protected void statusUpdatesRateValueChanged(int rate); // This one is special, because it is responsible for parsing device // responses, such as a command complete, status string, or parsing a // status event. In the case of a command complete, it must call // `commandComplete` to push the stream lifecycle along. abstract protected void rawResponseHandler(String response); Here is the public interface which controlles conform to: public interface IController { /* Observable */ public void addListener(ControllerListener cl); /* Actions */ public void performHomingCycle() throws Exception; public void returnToHome() throws Exception; public void resetCoordinatesToZero() throws Exception; public void resetCoordinateToZero(final char coord) throws Exception; public void killAlarmLock() throws Exception; public void toggleCheckMode() throws Exception; public void viewParserState() throws Exception; public void issueSoftReset() throws Exception; /* Behavior */ public void setSingleStepMode(boolean enabled); public boolean getSingleStepMode(); public void setStatusUpdatesEnabled(boolean enabled); public boolean getStatusUpdatesEnabled(); public void setStatusUpdateRate(int rate); public int getStatusUpdateRate(); public GcodeCommandCreator getCommandCreator(); public long getJobLengthEstimate(File gcodeFile); /* Serial */ public Boolean openCommPort(String port, int portRate) throws Exception; public Boolean closeCommPort() throws Exception; public Boolean isCommOpen(); /* Stream information */ public Boolean isReadyToStreamFile() throws Exception; public Boolean isStreamingFile(); public long getSendDuration(); public int rowsInSend(); public int rowsSent(); public int rowsRemaining(); /* Stream control */ public void beginStreaming() throws Exception; public void pauseStreaming() throws Exception; public void resumeStreaming() throws Exception; public void cancelSend(); /* Stream content */ public GcodeCommand createCommand(String gcode) throws Exception; public void sendCommandImmediately(GcodeCommand cmd) throws Exception; public void queueCommand(GcodeCommand cmd) throws Exception; public void queueStream(GcodeStreamReader r); public void queueRawStream(Reader r); }","title":"Controller"},{"location":"dev/backend_development/#communicator","text":"A communicator handles all levels of sending data to the device. Raw responses are returned to any listeners via the SerialCommunicatorListener . The AbstractCommunicator implements several listener utilities which are used by implementing classes. The BufferedCommunicator abstract class handles the process of buffering multiple commands at once in order to keep a constant stream of commands available to the CNC device. It does this in the streamCommands method by maintaining a list of active commands, and the current size of those commands. A method named processedCommand must be implemented in a subclass to determine whether a raw response indicates a command has completed. This notifies the BufferedCommunicator that it should attempt to send more commands. GrblCommunicator and TinyGCommunicator are two concrete implementations of the BufferedCommunicator .","title":"Communicator"},{"location":"dev/backend_development/#connection","text":"This is a very thin layer which provides a way to write and receive data: abstract public boolean openPort(String name, int baud) throws Exception; abstract public void closePort() throws Exception; abstract public boolean isOpen(); abstract public void sendByteImmediately(byte b) throws Exception; abstract public void sendStringToComm(String command) throws Exception;","title":"Connection"},{"location":"dev/backend_development/#streaming-strategy","text":"UGS attempts to use a fixed amount of memory when streaming a file. In this way it can send gcode files of any size. Files are preprocessed at the BackendAPI level using the GcodeStreamWriter class. This will serialize all the required metadata into a file. Later on that file can be opened with the GcodeStreamReader class, the Controller and Communicator classes use this. Using the reader, the Communicator class can pull out commands one at a time and send them to the Connection .","title":"Streaming strategy"},{"location":"dev/frontend_development/","text":"Front-end Architecture UGS uses a Model-View-Presenter architecture. What this means is that at a high level there are three layers which each serve different purposes. A Model for all backend logic, a View displayed to the user and a Presenter which serves as a buffer between the model and one or more views. Model The model contains all backend logic. Things like opening a connection, listing serial ports, streaming a file, and handling firmware specific nuances. All of this is hidden from the front end as much as possible. View The view only has access to the presenter. It is responsible for all user interaction and feedback. The main logic in here should be things like enabling or disabling components based on the current state of the model. Classic GUI The Classic GUI is built using NetBeans. There are a number of custom Swing components, and they are all initialized with the NetBeans GUI builder. The vast majority of the Classic GUI code is contained in MainWindow.java . There isn't a lot to expand on here, this front end has grown organically over the years and is fairly rigid. The Visualizer component is a standalone JOGL window which is updated using events from the backend (it was a model for many of the improvements in the current applications architecture). UGS Platform The UGS Platform build is also built using NetBeans. It is a built ontop of the NetBeans Platform which provides it a robust set of tools like flexible windows, a plugin framework, and a suite of tools for module communications. At the core of this is a module named UGSLib which is a simple wrapper to the standard UGS JAR file. There is a suite of modules named UGSCore which provides many of the standard UI elements seen in the Classic GUI , in addition there are other modules that provide new functionality. Extending the GUI is now a matter of creating a new plugin, for details on how to do this see the Plugin Tutorial . Presenter The presenter serves as an API for the model. All the heavy lifting needed for the GUI should happen here. For example the controller model object knows how to stream a processed file, but it doesn't know how to process the file. So the presenter will pass data to the gcode processor and generate a processed object which can be passed to the controller. Similarly, all notifications from the model are reinterpreted for the view with a simpler message strategy. In this way, all updates to the backend code can be leveraged by all front ends which utilize UGS. BackendAPI In UGS interfaces named BackendAPI and BackendAPIReadOnly provide the presenter layer. The read only methods are split off into a sub-interface in case a developer wants to be sure they don't change any state. For instance a widget that displays the current machine location probably has no need for pausing a stream. These APIs are used by all front ends ( Classic GUI , PendantUI and UGS Platform ).","title":"Frontend"},{"location":"dev/frontend_development/#front-end-architecture","text":"UGS uses a Model-View-Presenter architecture. What this means is that at a high level there are three layers which each serve different purposes. A Model for all backend logic, a View displayed to the user and a Presenter which serves as a buffer between the model and one or more views.","title":"Front-end Architecture"},{"location":"dev/frontend_development/#model","text":"The model contains all backend logic. Things like opening a connection, listing serial ports, streaming a file, and handling firmware specific nuances. All of this is hidden from the front end as much as possible.","title":"Model"},{"location":"dev/frontend_development/#view","text":"The view only has access to the presenter. It is responsible for all user interaction and feedback. The main logic in here should be things like enabling or disabling components based on the current state of the model.","title":"View"},{"location":"dev/frontend_development/#classic-gui","text":"The Classic GUI is built using NetBeans. There are a number of custom Swing components, and they are all initialized with the NetBeans GUI builder. The vast majority of the Classic GUI code is contained in MainWindow.java . There isn't a lot to expand on here, this front end has grown organically over the years and is fairly rigid. The Visualizer component is a standalone JOGL window which is updated using events from the backend (it was a model for many of the improvements in the current applications architecture).","title":"Classic GUI"},{"location":"dev/frontend_development/#ugs-platform","text":"The UGS Platform build is also built using NetBeans. It is a built ontop of the NetBeans Platform which provides it a robust set of tools like flexible windows, a plugin framework, and a suite of tools for module communications. At the core of this is a module named UGSLib which is a simple wrapper to the standard UGS JAR file. There is a suite of modules named UGSCore which provides many of the standard UI elements seen in the Classic GUI , in addition there are other modules that provide new functionality. Extending the GUI is now a matter of creating a new plugin, for details on how to do this see the Plugin Tutorial .","title":"UGS Platform"},{"location":"dev/frontend_development/#presenter","text":"The presenter serves as an API for the model. All the heavy lifting needed for the GUI should happen here. For example the controller model object knows how to stream a processed file, but it doesn't know how to process the file. So the presenter will pass data to the gcode processor and generate a processed object which can be passed to the controller. Similarly, all notifications from the model are reinterpreted for the view with a simpler message strategy. In this way, all updates to the backend code can be leveraged by all front ends which utilize UGS.","title":"Presenter"},{"location":"dev/frontend_development/#backendapi","text":"In UGS interfaces named BackendAPI and BackendAPIReadOnly provide the presenter layer. The read only methods are split off into a sub-interface in case a developer wants to be sure they don't change any state. For instance a widget that displays the current machine location probably has no need for pausing a stream. These APIs are used by all front ends ( Classic GUI , PendantUI and UGS Platform ).","title":"BackendAPI"},{"location":"dev/gcode_processor/","text":"Gcode Processor Development The UGS core library has a flexible gcode processor plugin system. It is designed as a processing pipeline to convert one line of code at a time by passing it through multiple Command Processor plugins. Some advanced features in UGS, like the Auto Leveler, take advantage of this feature to inject a special processor module into the gcode processing pipeline. Other processors are simpler, such as the M30Processor which simply removes unwanted M30 commands, or the CommandLengthProcessor which causes an error if the final processed line has too much data for your controller. This process is configured using a JSON file which holds processor configuration, order in which processors should appear in the pipeline, and whether or not they are enabled. All of this is configurable in UGS and UGP in a Gcode Processor Configuration menu. Anatomy of a CommandProcessor The processor interface is simple. One command goes in along with the current state and a list of output commands come out. A CommandProcessor might discover invalid input, in which case a GcodeParserException can be thrown for the GcodeParser to handle. public interface CommandProcessor { /** * Given a command and the current state of a program returns a replacement * list of commands. * @param command Input gcode. * @param state State of the gcode parser when the command will run. * @return One or more gcode commands to replace the original command with. */ public List processCommand(String command, GcodeState state) throws GcodeParserException; /** * Returns information about the current command and its configuration. * @return */ public String getHelp(); } Simple Example The CommandLengthProcessor is one of the simplest examples of a CommandProcessor. One thing of interest is that it accepts a length parameter during configuration. By adding a CommandLengthProcessor to the GcodeParser, you can ensure the maximum length of commands. public class CommandLengthProcessor implements CommandProcessor { final private int length; public CommandLengthProcessor(int length) { this.length = length; } @Override public String getHelp() { // Global localization helpers are used for the help message. return Localization.getString(\"sender.help.command.length\") + \"\\n\" + Localization.getString(\"sender.command.length\") + \": \" + length; } @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { if (command.length() > length) throw new GcodeParserException(\"Command '\" + command + \"' is longer than \" + length + \" characters.\"); return Collections.singletonList(command); } } More Complex Examples The following examples can be found in GitHub, they wont have the full code included. DecimalProcessor This is only slightly more complicated than the CommandLengthProcessor. It adds in some config validation by throwing a RuntimeException in the constructor, which is handled by code which configures the GcodeParser. The processCommand method is then able to truncate any decimals using simple string manipulation. FeedOverrideProcessor Like the DecimalProcessor , the FeedOverrideProcessor is able to modifie any F-Commands with simple string manipulation. LineSplitter The LineSplitter CommandProcessor will actually modify commands by parsing the gcode and rewriting it. There are several utilities used here to help: GcodeParser.processCommand - Converts the command string into something easier to work with. GcodePreprocessorUtils.extractMotion - Helper to extract all words associated to movement commands, the remainder should still be sent but may not be sent in the context of the rewritten command. The remaining logic checks the length of any G0 or G1 commands and converts them, or returns the original command unmodified. Tutorial: Creating a New CommandProcessor Creating a fully integrated and configurable CommandProcessor is simple, but does touch a number of different files. This tutorial will go over those pieces in the context of creating a processor named M3Dweller . Goal Create a processor which inserts a Dwell command (short delay) whenever the spindle is enabled. This allows any potentially slow setups such as VFD's to come up to speed. It should be configurable via the Gcode Processor Configuration menu. Creating the processor First you'll notice that the constructor initializes the command which should be added after our spindle start M3 command. The locale is set to make sure comma is not used as a decimal separator: private final String dwellCommand; public M3Dweller(double dwellDuration) { this.dwellCommand = String.format(Locale.ROOT, \"G4P%.2f\", dwellDuration); } The help method simply adds some comments for the settings GUI: @Override public String getHelp() { return \"Add a delay after enabling the spindle with \\\"M3\\\" commands. \\\"M3\\\" must be the only command on the line.\"; } Everything is then pulled together in the processCommand method to return an extra dwell command when M3 is detected: // Contains an M3 not followed by another digit (i.e. M30) Pattern m3Pattern = Pattern.compile(\".*[mM]3(?!\\\\d)(\\\\D.*)?\"); @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { if (m3Pattern.matcher(command).matches()) { return Arrays.asList(command, dwellCommand); } return Collections.singletonList(command); } All at once now: public class M3Dweller implements CommandProcessor { private final String dwellCommand; // Contains an M3 not followed by another digit (i.e. M30) Pattern m3Pattern = Pattern.compile(\".*[mM]3(?!\\\\d)(\\\\D.*)?\"); public M3Dweller(double dwellDuration) { this.dwellCommand = String.format(Locale.ROOT, \"G4P%.2f\", dwellDuration); } @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { String noComments = GcodePreprocessorUtils.removeComment(command); if (m3Pattern.matcher(noComments).matches()) { return Arrays.asList(command, dwellCommand); } return Collections.singletonList(command); } @Override public String getHelp() { return \"Add a delay after enabling the spindle with \\\"M3\\\" commands. \\\"M3\\\" must be the only command on the line.\"; } } Hooking up the JSON The json files are stored in ugs-core/src/resources/firmware_config , each of these will be updated to include our new processor. Notice that we have an argument named duration, and that it is disabled by default: \"name\": \"M3Dweller\", \"enabled\": false, \"optional\": true, \"args\": { \"duration\": 2.5 } },{ We also increment the version string, which will prompt users that there is a new configuration file available and they may need to revisit their settings: - \"Version\": 3, + \"Version\": 4, JSON Loader The last step is to update the CommandProcessorLoader to include logic that creates the new M3Dweller when needed. I used one of the other processors as an example and made sure to make a corresponding entry for the M3Dweller in each of the corresponding code and comment locations: case \"M3Dweller\": double duration = pc.args.get(\"duration\").getAsDouble(); p = new M3Dweller(duration); break; There is also a test which should be updated to make sure everything is working: + args = new JsonObject(); + args.addProperty(\"duration\", 2.5); + object = new JsonObject(); + object.addProperty(\"name\", \"M3Dweller\"); + object.add(\"args\", args); + array.add(object); + String jsonConfig = array.toString(); List processors = CommandProcessorLoader.initializeWithProcessors(jsonConfig); - assertEquals(8, processors.size()); + assertEquals(9, processors.size()); assertEquals(ArcExpander.class, processors.get(0).getClass()); assertEquals(CommentProcessor.class, processors.get(1).getClass()); assertEquals(DecimalProcessor.class, processors.get(2).getClass()); assertEquals(FeedOverrideProcessor.class, processors.get(3).getClass()); assertEquals(M30Processor.class, processors.get(4).getClass()); assertEquals(PatternRemover.class, processors.get(5).getClass()); assertEquals(CommandLengthProcessor.class, processors.get(6).getClass()); assertEquals(WhitespaceProcessor.class, processors.get(7).getClass()); + assertEquals(M3Dweller.class, processors.get(8).getClass()); Testing The command processors lend themselves to thorough testing, so we create a new M3DwellerTest.java file in the associated test package and write some tests: @Test public void testReplaces() throws Exception { M3Dweller dweller = new M3Dweller(2.5); String command; command = \"M3\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"m3\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"M3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"m3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"(this is ignored) M3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); } @Test public void testNoOp() throws Exception { M3Dweller dweller = new M3Dweller(2.5); String command; command = \"anything else\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); command = \"M30\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); command = \"G0 X0 Y0 (definitely not ready to start the spindle with an M3 yet)\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); } Localizing In addition to the help message which may use the global Localization helper, the settings menu will attempt to find something with the same name as in the JSON file. So we add an entry to ./ugs-core/src/resources/MessagesBundle_en_US.properties , which will later be pulled into a separate localization service to be localized in other languages. WhitespaceProcessor = Whitespace Remover +M3Dweller = Spindle start delay controller.exception.smoothie.booting = Smoothie has not finished booting. Conclusion We have now fully integrated our M3Dweller processor into the UGS framework and users of UGS and UGP can both make use of it. The raw commit for this feature is found on github here .","title":"Gcode Processors"},{"location":"dev/gcode_processor/#gcode-processor-development","text":"The UGS core library has a flexible gcode processor plugin system. It is designed as a processing pipeline to convert one line of code at a time by passing it through multiple Command Processor plugins. Some advanced features in UGS, like the Auto Leveler, take advantage of this feature to inject a special processor module into the gcode processing pipeline. Other processors are simpler, such as the M30Processor which simply removes unwanted M30 commands, or the CommandLengthProcessor which causes an error if the final processed line has too much data for your controller. This process is configured using a JSON file which holds processor configuration, order in which processors should appear in the pipeline, and whether or not they are enabled. All of this is configurable in UGS and UGP in a Gcode Processor Configuration menu.","title":"Gcode Processor Development"},{"location":"dev/gcode_processor/#anatomy-of-a-commandprocessor","text":"The processor interface is simple. One command goes in along with the current state and a list of output commands come out. A CommandProcessor might discover invalid input, in which case a GcodeParserException can be thrown for the GcodeParser to handle. public interface CommandProcessor { /** * Given a command and the current state of a program returns a replacement * list of commands. * @param command Input gcode. * @param state State of the gcode parser when the command will run. * @return One or more gcode commands to replace the original command with. */ public List processCommand(String command, GcodeState state) throws GcodeParserException; /** * Returns information about the current command and its configuration. * @return */ public String getHelp(); }","title":"Anatomy of a CommandProcessor"},{"location":"dev/gcode_processor/#simple-example","text":"The CommandLengthProcessor is one of the simplest examples of a CommandProcessor. One thing of interest is that it accepts a length parameter during configuration. By adding a CommandLengthProcessor to the GcodeParser, you can ensure the maximum length of commands. public class CommandLengthProcessor implements CommandProcessor { final private int length; public CommandLengthProcessor(int length) { this.length = length; } @Override public String getHelp() { // Global localization helpers are used for the help message. return Localization.getString(\"sender.help.command.length\") + \"\\n\" + Localization.getString(\"sender.command.length\") + \": \" + length; } @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { if (command.length() > length) throw new GcodeParserException(\"Command '\" + command + \"' is longer than \" + length + \" characters.\"); return Collections.singletonList(command); } }","title":"Simple Example"},{"location":"dev/gcode_processor/#more-complex-examples","text":"The following examples can be found in GitHub, they wont have the full code included.","title":"More Complex Examples"},{"location":"dev/gcode_processor/#decimalprocessor","text":"This is only slightly more complicated than the CommandLengthProcessor. It adds in some config validation by throwing a RuntimeException in the constructor, which is handled by code which configures the GcodeParser. The processCommand method is then able to truncate any decimals using simple string manipulation.","title":"DecimalProcessor"},{"location":"dev/gcode_processor/#feedoverrideprocessor","text":"Like the DecimalProcessor , the FeedOverrideProcessor is able to modifie any F-Commands with simple string manipulation.","title":"FeedOverrideProcessor"},{"location":"dev/gcode_processor/#linesplitter","text":"The LineSplitter CommandProcessor will actually modify commands by parsing the gcode and rewriting it. There are several utilities used here to help: GcodeParser.processCommand - Converts the command string into something easier to work with. GcodePreprocessorUtils.extractMotion - Helper to extract all words associated to movement commands, the remainder should still be sent but may not be sent in the context of the rewritten command. The remaining logic checks the length of any G0 or G1 commands and converts them, or returns the original command unmodified.","title":"LineSplitter"},{"location":"dev/gcode_processor/#tutorial-creating-a-new-commandprocessor","text":"Creating a fully integrated and configurable CommandProcessor is simple, but does touch a number of different files. This tutorial will go over those pieces in the context of creating a processor named M3Dweller .","title":"Tutorial: Creating a New CommandProcessor"},{"location":"dev/gcode_processor/#goal","text":"Create a processor which inserts a Dwell command (short delay) whenever the spindle is enabled. This allows any potentially slow setups such as VFD's to come up to speed. It should be configurable via the Gcode Processor Configuration menu.","title":"Goal"},{"location":"dev/gcode_processor/#creating-the-processor","text":"First you'll notice that the constructor initializes the command which should be added after our spindle start M3 command. The locale is set to make sure comma is not used as a decimal separator: private final String dwellCommand; public M3Dweller(double dwellDuration) { this.dwellCommand = String.format(Locale.ROOT, \"G4P%.2f\", dwellDuration); } The help method simply adds some comments for the settings GUI: @Override public String getHelp() { return \"Add a delay after enabling the spindle with \\\"M3\\\" commands. \\\"M3\\\" must be the only command on the line.\"; } Everything is then pulled together in the processCommand method to return an extra dwell command when M3 is detected: // Contains an M3 not followed by another digit (i.e. M30) Pattern m3Pattern = Pattern.compile(\".*[mM]3(?!\\\\d)(\\\\D.*)?\"); @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { if (m3Pattern.matcher(command).matches()) { return Arrays.asList(command, dwellCommand); } return Collections.singletonList(command); } All at once now: public class M3Dweller implements CommandProcessor { private final String dwellCommand; // Contains an M3 not followed by another digit (i.e. M30) Pattern m3Pattern = Pattern.compile(\".*[mM]3(?!\\\\d)(\\\\D.*)?\"); public M3Dweller(double dwellDuration) { this.dwellCommand = String.format(Locale.ROOT, \"G4P%.2f\", dwellDuration); } @Override public List processCommand(String command, GcodeState state) throws GcodeParserException { String noComments = GcodePreprocessorUtils.removeComment(command); if (m3Pattern.matcher(noComments).matches()) { return Arrays.asList(command, dwellCommand); } return Collections.singletonList(command); } @Override public String getHelp() { return \"Add a delay after enabling the spindle with \\\"M3\\\" commands. \\\"M3\\\" must be the only command on the line.\"; } }","title":"Creating the processor"},{"location":"dev/gcode_processor/#hooking-up-the-json","text":"The json files are stored in ugs-core/src/resources/firmware_config , each of these will be updated to include our new processor. Notice that we have an argument named duration, and that it is disabled by default: \"name\": \"M3Dweller\", \"enabled\": false, \"optional\": true, \"args\": { \"duration\": 2.5 } },{ We also increment the version string, which will prompt users that there is a new configuration file available and they may need to revisit their settings: - \"Version\": 3, + \"Version\": 4,","title":"Hooking up the JSON"},{"location":"dev/gcode_processor/#json-loader","text":"The last step is to update the CommandProcessorLoader to include logic that creates the new M3Dweller when needed. I used one of the other processors as an example and made sure to make a corresponding entry for the M3Dweller in each of the corresponding code and comment locations: case \"M3Dweller\": double duration = pc.args.get(\"duration\").getAsDouble(); p = new M3Dweller(duration); break; There is also a test which should be updated to make sure everything is working: + args = new JsonObject(); + args.addProperty(\"duration\", 2.5); + object = new JsonObject(); + object.addProperty(\"name\", \"M3Dweller\"); + object.add(\"args\", args); + array.add(object); + String jsonConfig = array.toString(); List processors = CommandProcessorLoader.initializeWithProcessors(jsonConfig); - assertEquals(8, processors.size()); + assertEquals(9, processors.size()); assertEquals(ArcExpander.class, processors.get(0).getClass()); assertEquals(CommentProcessor.class, processors.get(1).getClass()); assertEquals(DecimalProcessor.class, processors.get(2).getClass()); assertEquals(FeedOverrideProcessor.class, processors.get(3).getClass()); assertEquals(M30Processor.class, processors.get(4).getClass()); assertEquals(PatternRemover.class, processors.get(5).getClass()); assertEquals(CommandLengthProcessor.class, processors.get(6).getClass()); assertEquals(WhitespaceProcessor.class, processors.get(7).getClass()); + assertEquals(M3Dweller.class, processors.get(8).getClass());","title":"JSON Loader"},{"location":"dev/gcode_processor/#testing","text":"The command processors lend themselves to thorough testing, so we create a new M3DwellerTest.java file in the associated test package and write some tests: @Test public void testReplaces() throws Exception { M3Dweller dweller = new M3Dweller(2.5); String command; command = \"M3\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"m3\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"M3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"m3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); command = \"(this is ignored) M3 S1000\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,\"G4P2.50\"); } @Test public void testNoOp() throws Exception { M3Dweller dweller = new M3Dweller(2.5); String command; command = \"anything else\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); command = \"M30\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); command = \"G0 X0 Y0 (definitely not ready to start the spindle with an M3 yet)\"; Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command); }","title":"Testing"},{"location":"dev/gcode_processor/#localizing","text":"In addition to the help message which may use the global Localization helper, the settings menu will attempt to find something with the same name as in the JSON file. So we add an entry to ./ugs-core/src/resources/MessagesBundle_en_US.properties , which will later be pulled into a separate localization service to be localized in other languages. WhitespaceProcessor = Whitespace Remover +M3Dweller = Spindle start delay controller.exception.smoothie.booting = Smoothie has not finished booting.","title":"Localizing"},{"location":"dev/gcode_processor/#conclusion","text":"We have now fully integrated our M3Dweller processor into the UGS framework and users of UGS and UGP can both make use of it. The raw commit for this feature is found on github here .","title":"Conclusion"},{"location":"dev/getting_started/","text":"Project Organization Universal Gcode Sender uses Maven to build the project. It is using maven modules to separate the core library / classic GUI and the UGS Platform project. At the top level a UGS target defines the ugs-core and ugs-platform-parent modules which can be built separately or all at once. The classic gui is part of the core project in the ugs-core module. The maven-shade-plugin and maven-assembly-plugin are generate the self-executing JAR and distribution zip. UGS Platform is built on the NetBeans Platform . It is also using maven. Development is done using NetBeans, but most of the code can be edited using any IDE that supports Maven. Development with an IDE Any IDE supporting Maven should be able to open the UGS project directory. Once opened it should show you the ugs-core and ugs-platform-parent modules which correspond to the Classic and Platform interfaces. Development is done using NetBeans, and for some project development NetBeans is almost required. But for tweaking the UI and experimenting with the backend any IDE which supports maven can be used. Classic GUI In the ugs-core module, you can run the MainWindow.java file to start the Classic GUI. UGS Platform The platform build has a number of submodules. Load the suite of modules by running the ugs-platform-app module. Development with the Command Line The UGS Classic and Platform interfaces can also be run from the command line. These commands should be run from the root directory. Classic GUI There is a helper script named run_classic.sh , or you can use the commands below. Running the UI mvn install mvn exec:java -Dexec.mainClass=\"com.willwinder.universalgcodesender.MainWindow\" -pl ugs-core Executing tests mvn install mvn test -pl ugs-core Building the self-executing JAR mvn install mvn package -pl ugs-core Building a UniversalGcodeSender.zip release file mvn package assembly:assembly UGS Platform There is a helper script named run_platform.sh , or you can use the commands below. Running the UI mvn install mvn nbm:run-platform -pl ugs-platform/application","title":"Getting Started"},{"location":"dev/getting_started/#project-organization","text":"Universal Gcode Sender uses Maven to build the project. It is using maven modules to separate the core library / classic GUI and the UGS Platform project. At the top level a UGS target defines the ugs-core and ugs-platform-parent modules which can be built separately or all at once. The classic gui is part of the core project in the ugs-core module. The maven-shade-plugin and maven-assembly-plugin are generate the self-executing JAR and distribution zip. UGS Platform is built on the NetBeans Platform . It is also using maven. Development is done using NetBeans, but most of the code can be edited using any IDE that supports Maven.","title":"Project Organization"},{"location":"dev/getting_started/#development-with-an-ide","text":"Any IDE supporting Maven should be able to open the UGS project directory. Once opened it should show you the ugs-core and ugs-platform-parent modules which correspond to the Classic and Platform interfaces. Development is done using NetBeans, and for some project development NetBeans is almost required. But for tweaking the UI and experimenting with the backend any IDE which supports maven can be used.","title":"Development with an IDE"},{"location":"dev/getting_started/#classic-gui","text":"In the ugs-core module, you can run the MainWindow.java file to start the Classic GUI.","title":"Classic GUI"},{"location":"dev/getting_started/#ugs-platform","text":"The platform build has a number of submodules. Load the suite of modules by running the ugs-platform-app module.","title":"UGS Platform"},{"location":"dev/getting_started/#development-with-the-command-line","text":"The UGS Classic and Platform interfaces can also be run from the command line. These commands should be run from the root directory.","title":"Development with the Command Line"},{"location":"dev/getting_started/#classic-gui_1","text":"There is a helper script named run_classic.sh , or you can use the commands below.","title":"Classic GUI"},{"location":"dev/getting_started/#running-the-ui","text":"mvn install mvn exec:java -Dexec.mainClass=\"com.willwinder.universalgcodesender.MainWindow\" -pl ugs-core","title":"Running the UI"},{"location":"dev/getting_started/#executing-tests","text":"mvn install mvn test -pl ugs-core","title":"Executing tests"},{"location":"dev/getting_started/#building-the-self-executing-jar","text":"mvn install mvn package -pl ugs-core","title":"Building the self-executing JAR"},{"location":"dev/getting_started/#building-a-universalgcodesenderzip-release-file","text":"mvn package assembly:assembly","title":"Building a UniversalGcodeSender.zip release file"},{"location":"dev/getting_started/#ugs-platform_1","text":"There is a helper script named run_platform.sh , or you can use the commands below.","title":"UGS Platform"},{"location":"dev/getting_started/#running-the-ui_1","text":"mvn install mvn nbm:run-platform -pl ugs-platform/application","title":"Running the UI"},{"location":"dev/plugin/","text":"Note: The UGS Platform has been updated to use maven. Some parts of this document need to be updated to reflect that change. Plugin development The UGS Platform is built ontop of the NetBeans Platform. This gives us powerful tools to work with, including a robust plugin system. The heart of the UGS Platform is a module which wraps and exposes the Universal Gcode Sender JAR file - the same jar you could execute to run the Classic GUI! Other than using the UGSLib module, developing a plugin for the UGS Platform is exactly the same as developing any other NetBeans Platform plugin. And there is lots of great documentation for that, here is the NetBeans Platform Plugin Quick Start guide. Workflow Plugin Tutorial In this tutorial we're going to build a window to help manage jobs that use multiple tools which are split into multiple files. The rough design idea will have a central table with four columns containing: * File name * Tool name (editable) * Finished flag There will be a pair of buttons to add and remove files from the table, and we will also hook up a UGS event listener to detect when files are opened from other areas of the interface as well. Lastly, we'll add another pair of buttons to move rows around in the table, so that we can reorganize the workflow if files were added out of order the order. Here is a sketch of what we're building: Create and configure project Universal Gcode Sender is developed with NetBeans, and plugins are no exception. Once you've cloned the Universal Gcode Sender project you should be able to open the UGSPlatform folder with NetBeans and it will discover a project that you can open. To start building your module expand the UGSPlatform section, right-click the modules directory and select Add New... . This will open up a wizard where you name the module, and declare the source path. For this example the module is named WorkflowModuleTutorial and the source path is com.willwinder.ugs.nbp.workflowmoduletutorial which is the convention used in the core modules. Add UGS dependencies Your module should now be listed in the Modules section. If it doesn't you may need to restart NetBeans. Before we dive into the code there are a couple helper classes to import which will give you full access to the UGS API. Double click your module from the Modules section to open the code, then right-click the top level item which appeared and select the properties menu. Select Add Dependency... , here you should search for UGSLib and CentralLookup then add them to your plugin. Create window class Now we're ready to build the module. In this tutorial we're building a window to manage a multi-job workflow, so we'll start by adding a window to customize. Open the new module and right click the new package, in the context menu go to New -> Window... . To bring up the new window wizard. In the first screen of the wizard choose the default location your window will appear. Custom locations have been designed for UGS Platform, the largest is named visualizer because it is the Visualizer's default location. We'll use this location for our plugin. This means that when our plugin opens it will be tabbed with the Visualizer module. Click next and choose a class name for your module, for this tutorial I'm going to call it WorkflowModuleTutorial . Build the GUI The NetBeans GUI builder makes it easy to make a custom user interface without writing a single line of code (which is the main reason UGS uses NetBeans!). Using the GUI builder we'll add some buttons and a table. This step can be as elaborate as you want. If you're a seasoned swing developer and prefer not to use the magic GUI builder, no worries, you can create the UI programatically as well - but that is a different tutorial. Take a look at the screenshot below. The [TopComponent] - Navigator - Editor window shows all the objects that have been added with the GUI builder. There are four JButtons, a JTable nested inside a JScrollPane and a JPanel which I used to make alignment a little easier (The GUI Builder is powerful, but it can also be a bit quirky). Putting the JTable inside a JScrollPane makes it so that if too many items get added to the table it will scroll rather than dissapear off the bottom. Note: The name given to these components will be used in the code, so be sure to use the names shown in the screenshot. The JTable is going to be the trickiest part of build the GUI. To configure the table right-click the JTable object from the component navigator and select Table Contents... . Here you can add our 3 columns and specify that the data types. You can also specify which columns are editable, in this example we want the user to be able to type in what type of tool should be used. Autogenerated code Before writing any code, lets take a look at what has already been automatically generated for us. Just above the class there are a number of annotations. These are used by the NetBeans platform, most of them were setup according to how you filled in the Wizards earlier. They can also configure things like keyboard shortcuts, and where things are put in the dropdown menus. Within the class there are several grayed out sections. This is code generated by NetBeans which the IDE prevents you from modifying outside the GUI builder or in some cases component properties. For example if you wanted to use a custom JTable, you would configure the table in the GUI builder by adding a custom constructor. At the end of the file is componentOpened and componentClosed , these are lifecycle events that are called when the window has been opened or closed. Also at the end of the file is writeProperties and readProperties , these are used to save the window state between runs. Annotated code This is the longest section because it will explain every line of code added to the WorkflowModuleTutorial class. The most complicated code deals with Swing component manipulation, with just a smattering of UGS lifecycle events to push things along. Class signature First there are a few class state object we'll need and two Listeners we'll be implementing. /** * UGSEventListener - this is how a plugin can listen to UGS lifecycle events. * ListSelectionListener - listen for table selections. */ public final class WorkflowWindowTutorialTopComponent extends TopComponent implements UGSEventListener, ListSelectionListener { // These are the UGS backend objects for interacting with the backend. private final Settings settings; private final BackendAPI backend; // This is used to identify when a stream has completed. private boolean wasSending; // This is used in most methods, so cache it here. DefaultTableModel model; Constructor In the constructor we register the class with the UGS backend and also set the class as a listener to table selection events. public WorkflowWindowTopComponent() { initComponents(); setName(Bundle.CTL_WorkflowWindowTopComponent()); setToolTipText(Bundle.HINT_WorkflowWindowTopComponent()); // This is how to access the UGS backend and register the listener. // CentralLookup is used to get singleton instances of the UGS // Settings and BackendAPI objects. settings = CentralLookup.getDefault().lookup(Settings.class); backend = CentralLookup.getDefault().lookup(BackendAPI.class); backend.addUGSEventListener(this); // Allow contiguous ranges of selections and register a listener. this.fileTable.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION); ListSelectionModel cellSelectionModel = this.fileTable.getSelectionModel(); cellSelectionModel.addListSelectionListener(this); // Cache the model object. model = (DefaultTableModel)this.fileTable.getModel(); } UGS Event Listener This is the event sent from the UGS Backend, when a file is loaded or the state changes a notification will be sent. If the state switches from COMM_SENDING to COMM_IDLE we'll run a completeFile method. If a file is loaded, we add it to the table. @Override public void UGSEvent(UGSEvent cse) { if (cse.isStateChangeEvent()) { if (wasSending && cse.getControlState() == ControlState.COMM_IDLE) this.completeFile(backend.getGcodeFile()); wasSending = backend.isSending(); } if (cse.isFileChangeEvent()) { this.addFileToWorkflow(backend.getGcodeFile()); } } File Complete Handler When a command is complete we'll update the JTable, select the next file that needs to be sent and popup a notification informing the user what they should do next. The selection event will be sent and handled in the selection handler. public void completeFile(File gcodeFile) { if (gcodeFile == null) return; // Make sure the file is loaded in the table. int fileIndex = findFileIndex(gcodeFile); if (fileIndex < 0) return; // Mark that it has been completed. model.setValueAt(true, fileIndex, 2); fileIndex++; String message; // Make sure there is another command left. if (fileIndex < fileTable.getRowCount()) { String nextTool = (String) model.getValueAt(fileIndex, 1); String messageTemplate = \"Finished sending '%s'.\\n\" + \"The next file uses tool '%s'\\n\" + \"Load tool and move machine to its zero location\\n\" + \"and click send to continue this workflow.\"; message = String.format( messageTemplate, gcodeFile.getName(), nextTool); // Select the next row, this will trigger a selection event. fileTable.setRowSelectionInterval(fileIndex, fileIndex); // Use a different message if we're finished. } else { message = \"Finished sending the last file!\"; } // Display a notification. java.awt.EventQueue.invokeLater(() -> { JOptionPane.showMessageDialog(new JFrame(), message, \"Workflow Event\", JOptionPane.PLAIN_MESSAGE); }); } JTable Selection Listener This is the selection listener, when a file is selected load it in the backend. @Override public void valueChanged(ListSelectionEvent e) { int[] selectedRow = fileTable.getSelectedRows(); // Only load files when there is a single selection. if (selectedRow.length == 1) { // Pull the file out of the table and set it in the backend. String file = (String) model.getValueAt(selectedRow[0], 0); try { backend.setGcodeFile(new File(file)); } catch (Exception ex) { Exceptions.printStackTrace(ex); } } } JTable Helper Helper method to add a file to the JTable, first making sure that it isn't already in the table. public void addFileToWorkflow(File gcodeFile) { if (gcodeFile == null) { return; } int fileIndex = findFileIndex(gcodeFile); // Don't re-add a file. if (fileIndex >= 0) { return; } model.addRow(new Object[]{ gcodeFile.getAbsolutePath(), \"default\", false }); // Fire off the selection event to load the file. int lastRow = fileTable.getRowCount() - 1; fileTable.setRowSelectionInterval(lastRow, lastRow); } Add/Remove Button Action Handlers Now we implement the button event methods. They are generated by double clicking the buttons in the GUI Builder. This generates the swing code that attaches the ActionPerformed events to the button click callbacks. addButtonActionPerformed simply displays a file chooser (using some UGS library built ins) and calls the addFileToWorkflow method defined earlier. removeButtonActoinPerformed is even simpler, it uses standard JTable functionality to remove any selected rows. The only thing clever here is that rows are removed starting from the end to avoid having the index of later selections change while deleting rows one at a time. private void addButtonActionPerformed(ActionEvent evt) { // Open a file chooser pointing at the last opened directory. JFileChooser fileChooser = GcodeFileTypeFilter.getGcodeFileChooser( settings.getLastOpenedFilename()); int returnVal = fileChooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File gcodeFile = fileChooser.getSelectedFile(); // Save the new directory! settings.setLastOpenedFilename(gcodeFile.getParent()); addFileToWorkflow(gcodeFile); } } private void removeButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); if (selectedRows.length == 0) return; Arrays.sort(selectedRows); for (int i = selectedRows.length - 1; i >= 0; i--) { int row = selectedRows[i] this.model.removeRow(row); this.model.fireTableRowsDeleted(row, row); } } Up / Down Button Action Handlers The up and down action buttons are pure java code. They don't do anything you wouldn't do with any other Swing application. The code here deals strictly with moving selections around. Although a little tricky, and not totally relevant to UGS, they are included because the feature wouldn't be complete without them. private void upButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); // Exit early if nothing is selected. if (selectedRows.length == 0) return; Arrays.sort(selectedRows); // Exit early if the selected range can't move. if (selectedRows[0] == 0) return; for (int i = 0; i < selectedRows.length; i++) { selectedRows[i] = this.moveRow(selectedRows[i], -1); } int first = selectedRows[0]; int last = selectedRows[selectedRows.length-1]; fileTable.setRowSelectionInterval(first, last); } private void downButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); // Exit early if nothing is selected. if (selectedRows.length == 0) return; Arrays.sort(selectedRows); // Exit early if the selected range can't move. if (selectedRows[selectedRows.length-1] == fileTable.getRowCount()) return; for (int i = selectedRows.length - 1; i >= 0; i--) { selectedRows[i] = this.moveRow(selectedRows[i], 1); } int first = selectedRows[0]; int last = selectedRows[selectedRows.length-1]; fileTable.setRowSelectionInterval(first, last); } NetBeans Platform Component Lifecycle Code Of the automatically generated methods, componentOpened is the only one which needed some custom code. In case the component had been closed earlier or wasn't loaded until after a file stream started, grab the wasSending state and save it for later. @Override public void componentOpened() { this.wasSending = backend.isSending(); } @Override public void componentClosed() { // No special close handling. } void writeProperties(java.util.Properties p) { // better to version settings since initial version as advocated at // http://wiki.apidesign.org/wiki/PropertyFiles p.setProperty(\"version\", \"1.0\"); // We could save the loaded files here } void readProperties(java.util.Properties p) { String version = p.getProperty(\"version\"); // We could load previously loaded files here } Helper Methods Finally, here are the helper methods used above. /** * Look for the provided file in the file table. */ private int findFileIndex(File gcodeFile) { if (gcodeFile == null) return -1; for (int i = 0; i < model.getRowCount(); i++) { String file = (String) model.getValueAt(i, 0); if (file != null && gcodeFile.getAbsolutePath().equals(file)) { return i; } } return -1; } /** * Move a given row by some offset. If the offset would move the row outside * of the current table size, the row is not moved. */ private int moveRow(int row, int offset) { int dest = row + offset; if (dest < 0 || dest >= model.getRowCount()) { return row; } model.moveRow(row, row, dest); return dest; } Conclusion Here is a quick screencast of what this plugin does for us. In the GUI builder I swapped in some up/down arrows compared to the tutorial.","title":"Plugin"},{"location":"dev/plugin/#note-the-ugs-platform-has-been-updated-to-use-maven-some-parts-of-this-document-need-to-be-updated-to-reflect-that-change","text":"","title":"Note: The UGS Platform has been updated to use maven. Some parts of this document need to be updated to reflect that change."},{"location":"dev/plugin/#plugin-development","text":"The UGS Platform is built ontop of the NetBeans Platform. This gives us powerful tools to work with, including a robust plugin system. The heart of the UGS Platform is a module which wraps and exposes the Universal Gcode Sender JAR file - the same jar you could execute to run the Classic GUI! Other than using the UGSLib module, developing a plugin for the UGS Platform is exactly the same as developing any other NetBeans Platform plugin. And there is lots of great documentation for that, here is the NetBeans Platform Plugin Quick Start guide.","title":"Plugin development"},{"location":"dev/plugin/#workflow-plugin-tutorial","text":"In this tutorial we're going to build a window to help manage jobs that use multiple tools which are split into multiple files. The rough design idea will have a central table with four columns containing: * File name * Tool name (editable) * Finished flag There will be a pair of buttons to add and remove files from the table, and we will also hook up a UGS event listener to detect when files are opened from other areas of the interface as well. Lastly, we'll add another pair of buttons to move rows around in the table, so that we can reorganize the workflow if files were added out of order the order. Here is a sketch of what we're building:","title":"Workflow Plugin Tutorial"},{"location":"dev/plugin/#create-and-configure-project","text":"Universal Gcode Sender is developed with NetBeans, and plugins are no exception. Once you've cloned the Universal Gcode Sender project you should be able to open the UGSPlatform folder with NetBeans and it will discover a project that you can open. To start building your module expand the UGSPlatform section, right-click the modules directory and select Add New... . This will open up a wizard where you name the module, and declare the source path. For this example the module is named WorkflowModuleTutorial and the source path is com.willwinder.ugs.nbp.workflowmoduletutorial which is the convention used in the core modules.","title":"Create and configure project"},{"location":"dev/plugin/#add-ugs-dependencies","text":"Your module should now be listed in the Modules section. If it doesn't you may need to restart NetBeans. Before we dive into the code there are a couple helper classes to import which will give you full access to the UGS API. Double click your module from the Modules section to open the code, then right-click the top level item which appeared and select the properties menu. Select Add Dependency... , here you should search for UGSLib and CentralLookup then add them to your plugin.","title":"Add UGS dependencies"},{"location":"dev/plugin/#create-window-class","text":"Now we're ready to build the module. In this tutorial we're building a window to manage a multi-job workflow, so we'll start by adding a window to customize. Open the new module and right click the new package, in the context menu go to New -> Window... . To bring up the new window wizard. In the first screen of the wizard choose the default location your window will appear. Custom locations have been designed for UGS Platform, the largest is named visualizer because it is the Visualizer's default location. We'll use this location for our plugin. This means that when our plugin opens it will be tabbed with the Visualizer module. Click next and choose a class name for your module, for this tutorial I'm going to call it WorkflowModuleTutorial .","title":"Create window class"},{"location":"dev/plugin/#build-the-gui","text":"The NetBeans GUI builder makes it easy to make a custom user interface without writing a single line of code (which is the main reason UGS uses NetBeans!). Using the GUI builder we'll add some buttons and a table. This step can be as elaborate as you want. If you're a seasoned swing developer and prefer not to use the magic GUI builder, no worries, you can create the UI programatically as well - but that is a different tutorial. Take a look at the screenshot below. The [TopComponent] - Navigator - Editor window shows all the objects that have been added with the GUI builder. There are four JButtons, a JTable nested inside a JScrollPane and a JPanel which I used to make alignment a little easier (The GUI Builder is powerful, but it can also be a bit quirky). Putting the JTable inside a JScrollPane makes it so that if too many items get added to the table it will scroll rather than dissapear off the bottom. Note: The name given to these components will be used in the code, so be sure to use the names shown in the screenshot. The JTable is going to be the trickiest part of build the GUI. To configure the table right-click the JTable object from the component navigator and select Table Contents... . Here you can add our 3 columns and specify that the data types. You can also specify which columns are editable, in this example we want the user to be able to type in what type of tool should be used.","title":"Build the GUI"},{"location":"dev/plugin/#autogenerated-code","text":"Before writing any code, lets take a look at what has already been automatically generated for us. Just above the class there are a number of annotations. These are used by the NetBeans platform, most of them were setup according to how you filled in the Wizards earlier. They can also configure things like keyboard shortcuts, and where things are put in the dropdown menus. Within the class there are several grayed out sections. This is code generated by NetBeans which the IDE prevents you from modifying outside the GUI builder or in some cases component properties. For example if you wanted to use a custom JTable, you would configure the table in the GUI builder by adding a custom constructor. At the end of the file is componentOpened and componentClosed , these are lifecycle events that are called when the window has been opened or closed. Also at the end of the file is writeProperties and readProperties , these are used to save the window state between runs.","title":"Autogenerated code"},{"location":"dev/plugin/#annotated-code","text":"This is the longest section because it will explain every line of code added to the WorkflowModuleTutorial class. The most complicated code deals with Swing component manipulation, with just a smattering of UGS lifecycle events to push things along.","title":"Annotated code"},{"location":"dev/plugin/#class-signature","text":"First there are a few class state object we'll need and two Listeners we'll be implementing. /** * UGSEventListener - this is how a plugin can listen to UGS lifecycle events. * ListSelectionListener - listen for table selections. */ public final class WorkflowWindowTutorialTopComponent extends TopComponent implements UGSEventListener, ListSelectionListener { // These are the UGS backend objects for interacting with the backend. private final Settings settings; private final BackendAPI backend; // This is used to identify when a stream has completed. private boolean wasSending; // This is used in most methods, so cache it here. DefaultTableModel model;","title":"Class signature"},{"location":"dev/plugin/#constructor","text":"In the constructor we register the class with the UGS backend and also set the class as a listener to table selection events. public WorkflowWindowTopComponent() { initComponents(); setName(Bundle.CTL_WorkflowWindowTopComponent()); setToolTipText(Bundle.HINT_WorkflowWindowTopComponent()); // This is how to access the UGS backend and register the listener. // CentralLookup is used to get singleton instances of the UGS // Settings and BackendAPI objects. settings = CentralLookup.getDefault().lookup(Settings.class); backend = CentralLookup.getDefault().lookup(BackendAPI.class); backend.addUGSEventListener(this); // Allow contiguous ranges of selections and register a listener. this.fileTable.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION); ListSelectionModel cellSelectionModel = this.fileTable.getSelectionModel(); cellSelectionModel.addListSelectionListener(this); // Cache the model object. model = (DefaultTableModel)this.fileTable.getModel(); }","title":"Constructor"},{"location":"dev/plugin/#ugs-event-listener","text":"This is the event sent from the UGS Backend, when a file is loaded or the state changes a notification will be sent. If the state switches from COMM_SENDING to COMM_IDLE we'll run a completeFile method. If a file is loaded, we add it to the table. @Override public void UGSEvent(UGSEvent cse) { if (cse.isStateChangeEvent()) { if (wasSending && cse.getControlState() == ControlState.COMM_IDLE) this.completeFile(backend.getGcodeFile()); wasSending = backend.isSending(); } if (cse.isFileChangeEvent()) { this.addFileToWorkflow(backend.getGcodeFile()); } }","title":"UGS Event Listener"},{"location":"dev/plugin/#file-complete-handler","text":"When a command is complete we'll update the JTable, select the next file that needs to be sent and popup a notification informing the user what they should do next. The selection event will be sent and handled in the selection handler. public void completeFile(File gcodeFile) { if (gcodeFile == null) return; // Make sure the file is loaded in the table. int fileIndex = findFileIndex(gcodeFile); if (fileIndex < 0) return; // Mark that it has been completed. model.setValueAt(true, fileIndex, 2); fileIndex++; String message; // Make sure there is another command left. if (fileIndex < fileTable.getRowCount()) { String nextTool = (String) model.getValueAt(fileIndex, 1); String messageTemplate = \"Finished sending '%s'.\\n\" + \"The next file uses tool '%s'\\n\" + \"Load tool and move machine to its zero location\\n\" + \"and click send to continue this workflow.\"; message = String.format( messageTemplate, gcodeFile.getName(), nextTool); // Select the next row, this will trigger a selection event. fileTable.setRowSelectionInterval(fileIndex, fileIndex); // Use a different message if we're finished. } else { message = \"Finished sending the last file!\"; } // Display a notification. java.awt.EventQueue.invokeLater(() -> { JOptionPane.showMessageDialog(new JFrame(), message, \"Workflow Event\", JOptionPane.PLAIN_MESSAGE); }); }","title":"File Complete Handler"},{"location":"dev/plugin/#jtable-selection-listener","text":"This is the selection listener, when a file is selected load it in the backend. @Override public void valueChanged(ListSelectionEvent e) { int[] selectedRow = fileTable.getSelectedRows(); // Only load files when there is a single selection. if (selectedRow.length == 1) { // Pull the file out of the table and set it in the backend. String file = (String) model.getValueAt(selectedRow[0], 0); try { backend.setGcodeFile(new File(file)); } catch (Exception ex) { Exceptions.printStackTrace(ex); } } }","title":"JTable Selection Listener"},{"location":"dev/plugin/#jtable-helper","text":"Helper method to add a file to the JTable, first making sure that it isn't already in the table. public void addFileToWorkflow(File gcodeFile) { if (gcodeFile == null) { return; } int fileIndex = findFileIndex(gcodeFile); // Don't re-add a file. if (fileIndex >= 0) { return; } model.addRow(new Object[]{ gcodeFile.getAbsolutePath(), \"default\", false }); // Fire off the selection event to load the file. int lastRow = fileTable.getRowCount() - 1; fileTable.setRowSelectionInterval(lastRow, lastRow); }","title":"JTable Helper"},{"location":"dev/plugin/#addremove-button-action-handlers","text":"Now we implement the button event methods. They are generated by double clicking the buttons in the GUI Builder. This generates the swing code that attaches the ActionPerformed events to the button click callbacks. addButtonActionPerformed simply displays a file chooser (using some UGS library built ins) and calls the addFileToWorkflow method defined earlier. removeButtonActoinPerformed is even simpler, it uses standard JTable functionality to remove any selected rows. The only thing clever here is that rows are removed starting from the end to avoid having the index of later selections change while deleting rows one at a time. private void addButtonActionPerformed(ActionEvent evt) { // Open a file chooser pointing at the last opened directory. JFileChooser fileChooser = GcodeFileTypeFilter.getGcodeFileChooser( settings.getLastOpenedFilename()); int returnVal = fileChooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File gcodeFile = fileChooser.getSelectedFile(); // Save the new directory! settings.setLastOpenedFilename(gcodeFile.getParent()); addFileToWorkflow(gcodeFile); } } private void removeButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); if (selectedRows.length == 0) return; Arrays.sort(selectedRows); for (int i = selectedRows.length - 1; i >= 0; i--) { int row = selectedRows[i] this.model.removeRow(row); this.model.fireTableRowsDeleted(row, row); } }","title":"Add/Remove Button Action Handlers"},{"location":"dev/plugin/#up-down-button-action-handlers","text":"The up and down action buttons are pure java code. They don't do anything you wouldn't do with any other Swing application. The code here deals strictly with moving selections around. Although a little tricky, and not totally relevant to UGS, they are included because the feature wouldn't be complete without them. private void upButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); // Exit early if nothing is selected. if (selectedRows.length == 0) return; Arrays.sort(selectedRows); // Exit early if the selected range can't move. if (selectedRows[0] == 0) return; for (int i = 0; i < selectedRows.length; i++) { selectedRows[i] = this.moveRow(selectedRows[i], -1); } int first = selectedRows[0]; int last = selectedRows[selectedRows.length-1]; fileTable.setRowSelectionInterval(first, last); } private void downButtonActionPerformed(ActionEvent evt) { int[] selectedRows = fileTable.getSelectedRows(); // Exit early if nothing is selected. if (selectedRows.length == 0) return; Arrays.sort(selectedRows); // Exit early if the selected range can't move. if (selectedRows[selectedRows.length-1] == fileTable.getRowCount()) return; for (int i = selectedRows.length - 1; i >= 0; i--) { selectedRows[i] = this.moveRow(selectedRows[i], 1); } int first = selectedRows[0]; int last = selectedRows[selectedRows.length-1]; fileTable.setRowSelectionInterval(first, last); }","title":"Up / Down Button Action Handlers"},{"location":"dev/plugin/#netbeans-platform-component-lifecycle-code","text":"Of the automatically generated methods, componentOpened is the only one which needed some custom code. In case the component had been closed earlier or wasn't loaded until after a file stream started, grab the wasSending state and save it for later. @Override public void componentOpened() { this.wasSending = backend.isSending(); } @Override public void componentClosed() { // No special close handling. } void writeProperties(java.util.Properties p) { // better to version settings since initial version as advocated at // http://wiki.apidesign.org/wiki/PropertyFiles p.setProperty(\"version\", \"1.0\"); // We could save the loaded files here } void readProperties(java.util.Properties p) { String version = p.getProperty(\"version\"); // We could load previously loaded files here }","title":"NetBeans Platform Component Lifecycle Code"},{"location":"dev/plugin/#helper-methods","text":"Finally, here are the helper methods used above. /** * Look for the provided file in the file table. */ private int findFileIndex(File gcodeFile) { if (gcodeFile == null) return -1; for (int i = 0; i < model.getRowCount(); i++) { String file = (String) model.getValueAt(i, 0); if (file != null && gcodeFile.getAbsolutePath().equals(file)) { return i; } } return -1; } /** * Move a given row by some offset. If the offset would move the row outside * of the current table size, the row is not moved. */ private int moveRow(int row, int offset) { int dest = row + offset; if (dest < 0 || dest >= model.getRowCount()) { return row; } model.moveRow(row, row, dest); return dest; }","title":"Helper Methods"},{"location":"dev/plugin/#conclusion","text":"Here is a quick screencast of what this plugin does for us. In the GUI builder I swapped in some up/down arrows compared to the tutorial.","title":"Conclusion"},{"location":"guide/classic/","text":"Classic Interface This is the classic user interface. Initially designed as a bare-bones gcode sender it has grown in features since the initial release in 2012. It is built with Swing using the NetBeans Builder built-in graphical GUI layout plugin. The graphical nature of the plugin allows non-developers to tweak the interface without needing to write any code. Features Self-executing JAR, all native dependancies for Windows, OSX, Linux and RaspberryPi are built in. GRBL and TinyG support, extensible interface for adding more. 3D Gcode Visualizer. Real time machine feedback and control. Built in pendant - connect to UGS from your smart phone or tablet. Configurable Gcode Processing - Remove comments, truncate decimals, expand arcs Constant memory usage - run any sized gcode file. Extensive development test suite for all core features. Localized: Afrikaans, Italian, Spanish, German, French, Greek, Dutch, English How to run Download and install the version of Java listed on the download page, or a later version. Download and extract the Classic GUI from one of the links here. On most Operating Systems you can now double click the UniversalGcodeSender.jar file. If double clicking does not work, execute start-windows.bat on Windows or start.sh on Linux or Mac OSX. Usage == TODO: Pull requests accepted! ==","title":"Classic"},{"location":"guide/classic/#classic-interface","text":"This is the classic user interface. Initially designed as a bare-bones gcode sender it has grown in features since the initial release in 2012. It is built with Swing using the NetBeans Builder built-in graphical GUI layout plugin. The graphical nature of the plugin allows non-developers to tweak the interface without needing to write any code.","title":"Classic Interface"},{"location":"guide/classic/#features","text":"Self-executing JAR, all native dependancies for Windows, OSX, Linux and RaspberryPi are built in. GRBL and TinyG support, extensible interface for adding more. 3D Gcode Visualizer. Real time machine feedback and control. Built in pendant - connect to UGS from your smart phone or tablet. Configurable Gcode Processing - Remove comments, truncate decimals, expand arcs Constant memory usage - run any sized gcode file. Extensive development test suite for all core features. Localized: Afrikaans, Italian, Spanish, German, French, Greek, Dutch, English","title":"Features"},{"location":"guide/classic/#how-to-run","text":"Download and install the version of Java listed on the download page, or a later version. Download and extract the Classic GUI from one of the links here. On most Operating Systems you can now double click the UniversalGcodeSender.jar file. If double clicking does not work, execute start-windows.bat on Windows or start.sh on Linux or Mac OSX.","title":"How to run"},{"location":"guide/classic/#usage","text":"== TODO: Pull requests accepted! ==","title":"Usage"},{"location":"guide/common/","text":"Common Features Because the UGS Platform and the Classic GUI both build on the same foundation they have a lot in common. Gcode Processing Error handling When GRBL reports an error while processing a line of gcode UGS will automatically pause the stream. In some cases you might want to continue and ignore the error in the future. The following dialog is displayed allowing you to do so: If you select 'yes' at this point, a line will be added to the controller options:","title":"Common Features"},{"location":"guide/common/#common-features","text":"Because the UGS Platform and the Classic GUI both build on the same foundation they have a lot in common.","title":"Common Features"},{"location":"guide/common/#gcode-processing","text":"","title":"Gcode Processing"},{"location":"guide/common/#error-handling","text":"When GRBL reports an error while processing a line of gcode UGS will automatically pause the stream. In some cases you might want to continue and ignore the error in the future. The following dialog is displayed allowing you to do so: If you select 'yes' at this point, a line will be added to the controller options:","title":"Error handling"},{"location":"guide/languages/","text":"Languages UGS has excellent support for many languages. In many cases the locale on your machine will properly detect your locale and apply the language automatically, but in some cases you'll need to change it in the preferences. Platform Open 'Preferences', the location of this button varies based on the Operating System. Once the Options window is open, select 'UGS', the 'Sender Options' and finally set your language. Click 'Apply' and restart UGS for changes to be applied. Classic Open 'Sender Settings' in the Settings menu, and select your language. Restart for changes to be applied.","title":"Languages"},{"location":"guide/languages/#languages","text":"UGS has excellent support for many languages. In many cases the locale on your machine will properly detect your locale and apply the language automatically, but in some cases you'll need to change it in the preferences.","title":"Languages"},{"location":"guide/languages/#platform","text":"Open 'Preferences', the location of this button varies based on the Operating System. Once the Options window is open, select 'UGS', the 'Sender Options' and finally set your language. Click 'Apply' and restart UGS for changes to be applied.","title":"Platform"},{"location":"guide/languages/#classic","text":"Open 'Sender Settings' in the Settings menu, and select your language. Restart for changes to be applied.","title":"Classic"},{"location":"guide/platform/","text":"UGS Platform The UGS Platform is the next generation of Universal Gcode Sender. It is built ontop of the Netbeans Platform which allows us to leverage its mature modular framework. This platform allows more features to be added without compromising on code quality, or being bogged down by a home grown framework. The Classic GUI is used as a library, so core features benefit both interfaces. Platform Benefits This is the current target for new UGS features. Out of the box dynamic windowing system allows arranging the UI dynamically. Plugin Framework available for decoupling features. Huge library of modules to leverage: Code Editors, Auto-updates, Keybindings Usage How to run Download and extract the UGS Platform build from the downloads page. Go to the ugsplatform/bin directory. On Windows run ugsplatform.exe or ugsplatform64.exe . On Linux or Mac OSX run ugsplatform . Connecting to the controller Start off by connecting to your controller hardware using the toolbar at the top of the program. Select the correct hardware in the firmware combo box: Refresh the serial ports list and select the correct port for your hardware. If you can't find the correct port in the list, make sure you have the drivers installed. The ports are usually named like this: - MacOSX : /dev/tty.usbmodem* or /dev/tty.usbserial* - Linux : /dev/ttyUSB* or /dev/ttyACM* - Windows : COM1 , COM2 and so on. Select the correct baud rate for your controller. - GRBL - version 0.9 or later are using 115200, earlier versions are using 9600. - TinyG/g2core will adapt to the baud rate you are connecting with so it really doesn't matter. Setup wizard Do you need help configuring your hardware? In that case the setup wizard might be helpful, it will allow you to configure limit switches, homing, soft limits and calibrating your machine. To start the wizard open the menu Machine -> Setup wizard... If you aren't connected to your controller a connection dialog will be presented: The version of the controller will be shown after connecting and the available setup steps will be loaded for your controller: If you have a settings file from your machine manufacturer or if you have a backup of your settings you may import it here: On the motor wiring configuration page you can test the direction of your motors and change its direction if needed. On the step calibration page you can move the machine and measure the actual distance. It will then recommend a step setting for your machine: If you have limit switches you may enable them on this page and test if they are firing correctly: If limit switches are enabled you may enable homing as well. This page helps you figuring out in which direction the homing should be made: If homing is enabled you may also configure soft limits so that the controller knows if it can process a command without triggering limit switches: Digital read-out The Digital read-out (or Controller state) panel displays the current status of your machine such as the work/machine coordinates, machine/spindle speeds and gcode states. The panel provides the following functions: Coordinates of both the machine and your current work Buttons for resetting the work coordinates for each axis Changable work coordinates using simple mathematical expressions. You can either set an exact coordinate or, as an example, use the following # / 2 to divide the current position in half. The # -character will be replaced with current position. If you start your expression with * or / the current position is prepended. Display the current machine state (Idle, Run, Jog, Alarm, etc.) Display the current feed rate and spindle speed Display the different GCode states Display alarm with the triggered limit switches Overrides With the overrides plugin you can tweak the running session of a gcode program in real time. You can speed up/down the feed rate, spindle and the fast movement. To use, open the menu Window -> Overrides .","title":"Platform"},{"location":"guide/platform/#ugs-platform","text":"The UGS Platform is the next generation of Universal Gcode Sender. It is built ontop of the Netbeans Platform which allows us to leverage its mature modular framework. This platform allows more features to be added without compromising on code quality, or being bogged down by a home grown framework. The Classic GUI is used as a library, so core features benefit both interfaces.","title":"UGS Platform"},{"location":"guide/platform/#platform-benefits","text":"This is the current target for new UGS features. Out of the box dynamic windowing system allows arranging the UI dynamically. Plugin Framework available for decoupling features. Huge library of modules to leverage: Code Editors, Auto-updates, Keybindings","title":"Platform Benefits"},{"location":"guide/platform/#usage","text":"","title":"Usage"},{"location":"guide/platform/#how-to-run","text":"Download and extract the UGS Platform build from the downloads page. Go to the ugsplatform/bin directory. On Windows run ugsplatform.exe or ugsplatform64.exe . On Linux or Mac OSX run ugsplatform .","title":"How to run"},{"location":"guide/platform/#connecting-to-the-controller","text":"Start off by connecting to your controller hardware using the toolbar at the top of the program. Select the correct hardware in the firmware combo box: Refresh the serial ports list and select the correct port for your hardware. If you can't find the correct port in the list, make sure you have the drivers installed. The ports are usually named like this: - MacOSX : /dev/tty.usbmodem* or /dev/tty.usbserial* - Linux : /dev/ttyUSB* or /dev/ttyACM* - Windows : COM1 , COM2 and so on. Select the correct baud rate for your controller. - GRBL - version 0.9 or later are using 115200, earlier versions are using 9600. - TinyG/g2core will adapt to the baud rate you are connecting with so it really doesn't matter.","title":"Connecting to the controller"},{"location":"guide/platform/#setup-wizard","text":"Do you need help configuring your hardware? In that case the setup wizard might be helpful, it will allow you to configure limit switches, homing, soft limits and calibrating your machine. To start the wizard open the menu Machine -> Setup wizard... If you aren't connected to your controller a connection dialog will be presented: The version of the controller will be shown after connecting and the available setup steps will be loaded for your controller: If you have a settings file from your machine manufacturer or if you have a backup of your settings you may import it here: On the motor wiring configuration page you can test the direction of your motors and change its direction if needed. On the step calibration page you can move the machine and measure the actual distance. It will then recommend a step setting for your machine: If you have limit switches you may enable them on this page and test if they are firing correctly: If limit switches are enabled you may enable homing as well. This page helps you figuring out in which direction the homing should be made: If homing is enabled you may also configure soft limits so that the controller knows if it can process a command without triggering limit switches:","title":"Setup wizard"},{"location":"guide/platform/#digital-read-out","text":"The Digital read-out (or Controller state) panel displays the current status of your machine such as the work/machine coordinates, machine/spindle speeds and gcode states. The panel provides the following functions: Coordinates of both the machine and your current work Buttons for resetting the work coordinates for each axis Changable work coordinates using simple mathematical expressions. You can either set an exact coordinate or, as an example, use the following # / 2 to divide the current position in half. The # -character will be replaced with current position. If you start your expression with * or / the current position is prepended. Display the current machine state (Idle, Run, Jog, Alarm, etc.) Display the current feed rate and spindle speed Display the different GCode states Display alarm with the triggered limit switches","title":"Digital read-out"},{"location":"guide/platform/#overrides","text":"With the overrides plugin you can tweak the running session of a gcode program in real time. You can speed up/down the feed rate, spindle and the fast movement. To use, open the menu Window -> Overrides .","title":"Overrides"},{"location":"guide/troubleshooting/","text":"Common Problems \"Grbl has not finished booting.\" This happens when UGS connects to a serial port and does not receive the GRBL startup string. Typically this is caused by a configuration problem and can be solved by one of the following: Check the baud rate is 115200, or 9600 for very old versions of grbl. Make sure you are connecting to the correct port. Make sure you have installed any drivers required for your controller. Make sure GRBL is properly flashed on your controller. As a last resort try removing all previous UGS settings (see property Files ) and reinstall. Gcode program stopped working The UGS Parser has a configurable list of rules to skip certain patterns, these rules are typically added by a Yes/No dialog asking if you would like to skip the erroneous commands in the future. Sometimes GRBL will get into an ALARM state and there will be lots of these popups which should not be skipped in the future. Skipping good commands may lead to broken gcode. Those rules should be removed from the gcode processor by going into the controller options. In UGS Classic the option is in Settings > Gcode Processor Configuration . In UGS Platform the option is under Preferences... > UGS > Controller Options . For both, you need to uncheck or remove the invalid settings in the bottom list: Platform Specific Issues Toolbars or Windows don't appear. This usually happens if you try running the platform without the required version of Java. The user cache is initialized but some objects become corrupt and initialization fails in the future even after upgrading Java. This can be fixed by clearing out the user cache directory which can be found on the UGS \"About\" screen seen in the image below. Information Property Files Occasionally it is useful to attach some of these property files to bug reports to help with reproducing a problem. Classic Classic UGS properties are stored in your home directory, which changes based on the operating system being used: Windows : /home/user/ugs Mac : ~/Library/Preferences/ugs Linux : ~/ugs Files include UniversalGcodeSender.json which contain different settings, and a firmware_config directory which contains several configurations for different firmwares and testing profiles. Platform Platform UGS properties are stored in your home directory: Windows : /home/user/ugs Mac : ~/Library/Preferences/ugs Linux : ~/ugs The platform version of UGS contains additional property files automatically created by the NetBeans Platform. These files are also contained in various locations based on the operating system being used. You can find the exact locations of these files in the About / Help menu. It is sometimes necessary to clear out these properties between major feature updates. Logs are typically located in these directories: Windows 7 : C:\\Users\\username\\AppData\\Roaming\\ugsplatform\\2.0-SNAPSHOT\\var\\log\\messages.log Windows XP : C:\\Documents and Settings\\username\\Application Data\\ugsplatform\\2.0-SNAPSHOT\\var\\log\\messages.log Mac : ~/Library/Application Support/ugsplatform\\2.0-SNAPSHOT/var/log/messages.log Linux : ~/.ugsplatform/2.0-SNAPSHOT/var/log/messages.log Operating System Compatibility Problems Linux: Non Reparenting Window Managers There are a number of window managers which are \" non reparenting \", this causes some problems with Java. Some details about this problem can be seen here . Use the _JAVA_AWT_WM_NONREPARENTING environment property to fix the problem: export _JAVA_AWT_WM_NONREPARENTING=1 Program Slows Down and Send Freezes If you notice slowness while running your program, it may mean UGS is running out of available memory. There are a couple things to try: Check the controller settings, and make sure \"Arc Expander\" is not enabled. This can take a small program and turn it into a very large one by converting arcs into many small movements. Increase the memory allocated to UGS by navigating the the intsallation directory (See paths above). There is a folder named etc containing ugsplatform.conf , open this file with a simple text editor and modify the value of Xms to something like -J-Xms256m , or larger. Additional details can be found here .","title":"Help"},{"location":"guide/troubleshooting/#common-problems","text":"","title":"Common Problems"},{"location":"guide/troubleshooting/#grbl-has-not-finished-booting","text":"This happens when UGS connects to a serial port and does not receive the GRBL startup string. Typically this is caused by a configuration problem and can be solved by one of the following: Check the baud rate is 115200, or 9600 for very old versions of grbl. Make sure you are connecting to the correct port. Make sure you have installed any drivers required for your controller. Make sure GRBL is properly flashed on your controller. As a last resort try removing all previous UGS settings (see property Files ) and reinstall.","title":"\"Grbl has not finished booting.\""},{"location":"guide/troubleshooting/#gcode-program-stopped-working","text":"The UGS Parser has a configurable list of rules to skip certain patterns, these rules are typically added by a Yes/No dialog asking if you would like to skip the erroneous commands in the future. Sometimes GRBL will get into an ALARM state and there will be lots of these popups which should not be skipped in the future. Skipping good commands may lead to broken gcode. Those rules should be removed from the gcode processor by going into the controller options. In UGS Classic the option is in Settings > Gcode Processor Configuration . In UGS Platform the option is under Preferences... > UGS > Controller Options . For both, you need to uncheck or remove the invalid settings in the bottom list:","title":"Gcode program stopped working"},{"location":"guide/troubleshooting/#platform-specific-issues","text":"","title":"Platform Specific Issues"},{"location":"guide/troubleshooting/#toolbars-or-windows-dont-appear","text":"This usually happens if you try running the platform without the required version of Java. The user cache is initialized but some objects become corrupt and initialization fails in the future even after upgrading Java. This can be fixed by clearing out the user cache directory which can be found on the UGS \"About\" screen seen in the image below.","title":"Toolbars or Windows don't appear."},{"location":"guide/troubleshooting/#information","text":"","title":"Information"},{"location":"guide/troubleshooting/#property-files","text":"Occasionally it is useful to attach some of these property files to bug reports to help with reproducing a problem.","title":"Property Files"},{"location":"guide/troubleshooting/#classic","text":"Classic UGS properties are stored in your home directory, which changes based on the operating system being used: Windows : /home/user/ugs Mac : ~/Library/Preferences/ugs Linux : ~/ugs Files include UniversalGcodeSender.json which contain different settings, and a firmware_config directory which contains several configurations for different firmwares and testing profiles.","title":"Classic"},{"location":"guide/troubleshooting/#platform","text":"Platform UGS properties are stored in your home directory: Windows : /home/user/ugs Mac : ~/Library/Preferences/ugs Linux : ~/ugs The platform version of UGS contains additional property files automatically created by the NetBeans Platform. These files are also contained in various locations based on the operating system being used. You can find the exact locations of these files in the About / Help menu. It is sometimes necessary to clear out these properties between major feature updates. Logs are typically located in these directories: Windows 7 : C:\\Users\\username\\AppData\\Roaming\\ugsplatform\\2.0-SNAPSHOT\\var\\log\\messages.log Windows XP : C:\\Documents and Settings\\username\\Application Data\\ugsplatform\\2.0-SNAPSHOT\\var\\log\\messages.log Mac : ~/Library/Application Support/ugsplatform\\2.0-SNAPSHOT/var/log/messages.log Linux : ~/.ugsplatform/2.0-SNAPSHOT/var/log/messages.log","title":"Platform"},{"location":"guide/troubleshooting/#operating-system-compatibility-problems","text":"","title":"Operating System Compatibility Problems"},{"location":"guide/troubleshooting/#linux-non-reparenting-window-managers","text":"There are a number of window managers which are \" non reparenting \", this causes some problems with Java. Some details about this problem can be seen here . Use the _JAVA_AWT_WM_NONREPARENTING environment property to fix the problem: export _JAVA_AWT_WM_NONREPARENTING=1","title":"Linux: Non Reparenting Window Managers"},{"location":"guide/troubleshooting/#program-slows-down-and-send-freezes","text":"If you notice slowness while running your program, it may mean UGS is running out of available memory. There are a couple things to try: Check the controller settings, and make sure \"Arc Expander\" is not enabled. This can take a small program and turn it into a very large one by converting arcs into many small movements. Increase the memory allocated to UGS by navigating the the intsallation directory (See paths above). There is a folder named etc containing ugsplatform.conf , open this file with a simple text editor and modify the value of Xms to something like -J-Xms256m , or larger. Additional details can be found here .","title":"Program Slows Down and Send Freezes"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 02a9151..a1ed243 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,72 +2,72 @@ None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily None - 2023-10-18 + 2023-11-14 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 0b5ce4951bd2f241f63e70769e437636085b5847..4ea2c781ae4ee09ed73d7bf05c0a85de633a7275 100644 GIT binary patch literal 207 zcmV;=05Ja_iwFpbI#Xo=|8r?{Wo=<_E_iKh0PU4M62c%5h5MX>p$~*;p$;LPrIj9l zA(${jAOae_z2s+{9Ke!o_U+p@`;|-Ucdx-qI|VvVGDQriXh zp|=eyi*lC}l9PRmtllOjI&NBdck;m+uJniEjk6H(51HTzp5O_d;0gXW_}t=8WM8%T Jp^HNY003v)V*daD literal 207 zcmV;=05Ja_iwFqdhc9IU|8r?{Wo=<_E_iKh0PU4M62c%5h5MX>p%0MQM2C>h(n=4& z5KNdM5CM(eUh*?e4q(YP`}Xad{mP}yd(hw|ombY>us{R?X@qT+>1uepp7RPeETd1S zy$m3Rjjv%J!g#`XnkM8`kOLn*vaZ8k^l|WbGciF&$TARXKCr5#sJ4P#tdSJYYQNw< z47Oz@DGzx;@}i27&D+I9&rK)qPCi)6mHtq?b5
Version 2.0.21Version 2.1.2 Description
Windows x64 Windows x64Windows x64 Windows 64-bit Windows 64-bit version with bundled Java
Windows WindowsWindows version with bundled JavaWindows x86 Windows 32-bitWindows 32-bit version with bundled Java
MacOSX MacOSXMacOSX x64 MacOSX (Intel) MacOSX version with bundled Java
Linux x64 LinuxMacOSX ARM64 MacOSX (Silicon)MacOSX ARM64 version (for Apple Silicon M1/M2) with bundled Java
Linux x64 Linux Linux version with bundled Java
Linux ARM Linux ARMLinux ARM version with bundled Java. Can be used with RaspberryPiLinux ARM Linux ARMLinux ARM version with bundled Java. Can be used with Raspberry Pi OS 32-bit
Linux ARM64 Linux ARM64Linux ARM64 version with bundled Java. Can be used with Raspberry Pi OS 64-bit
All platforms All platformsAll platforms All platforms A generic package without Java which needs to be installed separately
All platforms All platformsAll platforms All platforms A generic package without Java which needs to be installed separately