├── .gitignore
├── docs
├── CNAME
├── img
│ ├── icon.png
│ ├── favicon.ico
│ ├── common
│ │ ├── zip.png
│ │ ├── os_mac.png
│ │ ├── os_linux.png
│ │ ├── os_linux_arm.png
│ │ ├── os_windows.png
│ │ ├── bad.code.dialog.png
│ │ └── bad.code.setting.png
│ ├── guide
│ │ ├── languages.png
│ │ ├── platform
│ │ │ ├── run.png
│ │ │ ├── work_coord.gif
│ │ │ ├── setup_wizard-1.png
│ │ │ ├── setup_wizard-2.png
│ │ │ ├── setup_wizard-3.png
│ │ │ ├── setup_wizard-4.png
│ │ │ ├── setup_wizard-5.png
│ │ │ ├── setup_wizard-6.png
│ │ │ ├── setup_wizard-7.png
│ │ │ ├── setup_wizard-8.png
│ │ │ ├── setup_wizard-9.png
│ │ │ ├── connect_firmware.png
│ │ │ ├── controller_state.png
│ │ │ ├── overrides_window.png
│ │ │ ├── connect_serial_port.png
│ │ │ ├── connect_serial_port.svg
│ │ │ └── controller_state.svg
│ │ ├── languages_platform.png
│ │ ├── languages_classic_select.png
│ │ └── languages_classic_preferences.png
│ ├── platform
│ │ ├── editor.png
│ │ ├── screenshot.png
│ │ ├── about_popup.png
│ │ ├── click_to_jog.png
│ │ ├── keybindings.png
│ │ ├── Digital_readout.png
│ │ ├── zoom_to_region.png
│ │ └── Overrides_window.png
│ ├── screenshots
│ │ ├── sending.png
│ │ ├── designer.png
│ │ ├── finished.png
│ │ ├── visualizer.png
│ │ └── advanced_machine_control.png
│ ├── sponsors
│ │ ├── bobs_cnc.jpg
│ │ ├── cloudbees.png
│ │ ├── poeditor.png
│ │ └── artifactory.png
│ ├── dev
│ │ └── UGSPlatform_open.png
│ ├── tutorials
│ │ ├── workflow_plugin
│ │ │ ├── 07.Demo.gif
│ │ │ ├── 00.Design.png
│ │ │ ├── 05.GUI_Builder.png
│ │ │ ├── 01.Create_Module.png
│ │ │ ├── 06.GUI_Builder.02.png
│ │ │ ├── 02.New_Module_Wizard_01.png
│ │ │ ├── 03.Project_Properties.png
│ │ │ └── 04.Create_Window_Class.png
│ │ └── gcode_processor_plugin
│ │ │ ├── 3.edit.png
│ │ │ ├── 1.update.png
│ │ │ └── 2.settings.png
│ ├── shields
│ │ ├── License-GPLv3-blue.svg
│ │ └── Language-Java-brightgreen.svg
│ └── ugs.svg
├── usage.md
├── css
│ └── style.css
├── installing.md
├── dev
│ ├── getting_started.md
│ ├── frontend_development.md
│ ├── backend_development.md
│ ├── gcode_processor.md
│ └── plugin.md
├── contributing.md
├── download.md
└── index.md
├── requirements.txt
├── mkdocs.yml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | site/
2 | *.iml
3 | .idea
4 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | universalgcodesender.com
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs
2 | mkdocs-bootswatch
3 |
--------------------------------------------------------------------------------
/docs/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/icon.png
--------------------------------------------------------------------------------
/docs/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/favicon.ico
--------------------------------------------------------------------------------
/docs/img/common/zip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/common/zip.png
--------------------------------------------------------------------------------
/docs/img/common/os_mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/common/os_mac.png
--------------------------------------------------------------------------------
/docs/img/common/os_linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/common/os_linux.png
--------------------------------------------------------------------------------
/docs/img/guide/languages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/languages.png
--------------------------------------------------------------------------------
/docs/img/platform/editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/platform/editor.png
--------------------------------------------------------------------------------
/docs/img/common/os_linux_arm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/common/os_linux_arm.png
--------------------------------------------------------------------------------
/docs/img/common/os_windows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/common/os_windows.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/run.png
--------------------------------------------------------------------------------
/docs/img/platform/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/platform/screenshot.png
--------------------------------------------------------------------------------
/docs/img/screenshots/sending.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/screenshots/sending.png
--------------------------------------------------------------------------------
/docs/img/sponsors/bobs_cnc.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/sponsors/bobs_cnc.jpg
--------------------------------------------------------------------------------
/docs/img/sponsors/cloudbees.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/sponsors/cloudbees.png
--------------------------------------------------------------------------------
/docs/img/sponsors/poeditor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/sponsors/poeditor.png
--------------------------------------------------------------------------------
/docs/img/dev/UGSPlatform_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/dev/UGSPlatform_open.png
--------------------------------------------------------------------------------
/docs/img/platform/about_popup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/platform/about_popup.png
--------------------------------------------------------------------------------
/docs/img/platform/click_to_jog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/platform/click_to_jog.png
--------------------------------------------------------------------------------
/docs/img/platform/keybindings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/platform/keybindings.png
--------------------------------------------------------------------------------
/docs/img/screenshots/designer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/screenshots/designer.png
--------------------------------------------------------------------------------
/docs/img/screenshots/finished.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/screenshots/finished.png
--------------------------------------------------------------------------------
/docs/img/sponsors/artifactory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/sponsors/artifactory.png
--------------------------------------------------------------------------------
/docs/img/common/bad.code.dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/common/bad.code.dialog.png
--------------------------------------------------------------------------------
/docs/img/common/bad.code.setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/common/bad.code.setting.png
--------------------------------------------------------------------------------
/docs/img/guide/languages_platform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/languages_platform.png
--------------------------------------------------------------------------------
/docs/img/platform/Digital_readout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/platform/Digital_readout.png
--------------------------------------------------------------------------------
/docs/img/platform/zoom_to_region.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/platform/zoom_to_region.png
--------------------------------------------------------------------------------
/docs/img/screenshots/visualizer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/screenshots/visualizer.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/work_coord.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/work_coord.gif
--------------------------------------------------------------------------------
/docs/img/platform/Overrides_window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/platform/Overrides_window.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/setup_wizard-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/setup_wizard-1.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/setup_wizard-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/setup_wizard-2.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/setup_wizard-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/setup_wizard-3.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/setup_wizard-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/setup_wizard-4.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/setup_wizard-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/setup_wizard-5.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/setup_wizard-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/setup_wizard-6.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/setup_wizard-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/setup_wizard-7.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/setup_wizard-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/setup_wizard-8.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/setup_wizard-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/setup_wizard-9.png
--------------------------------------------------------------------------------
/docs/img/guide/languages_classic_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/languages_classic_select.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/connect_firmware.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/connect_firmware.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/controller_state.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/controller_state.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/overrides_window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/overrides_window.png
--------------------------------------------------------------------------------
/docs/img/guide/platform/connect_serial_port.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/platform/connect_serial_port.png
--------------------------------------------------------------------------------
/docs/img/tutorials/workflow_plugin/07.Demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/workflow_plugin/07.Demo.gif
--------------------------------------------------------------------------------
/docs/img/guide/languages_classic_preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/guide/languages_classic_preferences.png
--------------------------------------------------------------------------------
/docs/img/screenshots/advanced_machine_control.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/screenshots/advanced_machine_control.png
--------------------------------------------------------------------------------
/docs/img/tutorials/workflow_plugin/00.Design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/workflow_plugin/00.Design.png
--------------------------------------------------------------------------------
/docs/img/tutorials/gcode_processor_plugin/3.edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/gcode_processor_plugin/3.edit.png
--------------------------------------------------------------------------------
/docs/img/tutorials/gcode_processor_plugin/1.update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/gcode_processor_plugin/1.update.png
--------------------------------------------------------------------------------
/docs/img/tutorials/workflow_plugin/05.GUI_Builder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/workflow_plugin/05.GUI_Builder.png
--------------------------------------------------------------------------------
/docs/img/tutorials/gcode_processor_plugin/2.settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/gcode_processor_plugin/2.settings.png
--------------------------------------------------------------------------------
/docs/img/tutorials/workflow_plugin/01.Create_Module.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/workflow_plugin/01.Create_Module.png
--------------------------------------------------------------------------------
/docs/img/tutorials/workflow_plugin/06.GUI_Builder.02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/workflow_plugin/06.GUI_Builder.02.png
--------------------------------------------------------------------------------
/docs/img/tutorials/workflow_plugin/02.New_Module_Wizard_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/workflow_plugin/02.New_Module_Wizard_01.png
--------------------------------------------------------------------------------
/docs/img/tutorials/workflow_plugin/03.Project_Properties.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/workflow_plugin/03.Project_Properties.png
--------------------------------------------------------------------------------
/docs/img/tutorials/workflow_plugin/04.Create_Window_Class.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winder/ugs_website/HEAD/docs/img/tutorials/workflow_plugin/04.Create_Window_Class.png
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: UGS
2 | theme:
3 | logo: assets/icon.png
4 | name: cosmo
5 | nav_style: dark
6 |
7 | markdown_extensions:
8 | - admonition
9 |
10 | site_author: Will Winder
11 | site_description: "Cross platform G-Code sender with advanced features for GRBL based CNC machines."
12 | google_analytics: ['UA-73554112-1', 'auto']
13 | extra_css:
14 | - css/style.css
15 | nav:
16 | - 'Home': 'index.md'
17 | - 'Download': 'download.md'
18 | - 'Installing': 'installing.md'
19 | - 'Usage': 'usage.md'
20 | - 'Contributing': 'contributing.md'
21 |
22 |
--------------------------------------------------------------------------------
/docs/usage.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | Check out our wiki for instructions on how to use Universal Gcode Sender
4 |
5 | * Connecting to the controller
6 | * Configuration
7 | * Usage
8 | * FAQ and Troubleshooting
--------------------------------------------------------------------------------
/docs/img/shields/License-GPLv3-blue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/img/shields/Language-Java-brightgreen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Website for [Universal Gcode Sender](http://winder.github.io/ugs_website/). The site is made using the [MkDocs](http://www.mkdocs.org/) static site generator, which allows you to build a static webpage using a series of markdown files.
2 |
3 | See the `docs` folder for markdown files. If you install the `mkdocs` utility on your machine you can test changes by running `mkdocs serve` and navigating to the location specified int he console output.
4 |
5 | To make a new page simply register its location in the `mkdocs.yml`, and create a new `.md` file at the location specified.
6 |
7 | If there's anything in particular you'd like to see feel free to open a ticket and ask.
8 |
9 | For more information about how to contribute documentation see the mkdocs documentation:
10 | http://www.mkdocs.org/user-guide/writing-your-docs/
11 |
12 | ### Setup
13 |
14 | - `pip install -r requirements.txt`
15 |
--------------------------------------------------------------------------------
/docs/css/style.css:
--------------------------------------------------------------------------------
1 | h1, h2, h3, h4 {
2 | margin-top: 1em;
3 | margin-bottom: 0.5em;
4 | padding: 0;
5 | }
6 |
7 | .ms-md-auto, .mx-auto {
8 | margin-left: auto !important;
9 | }
10 |
11 | .bs-sidebar .nav > .active > a,
12 | .bs-sidebar .nav > .active:hover > a,
13 | .bs-sidebar .nav > .active:focus > a {
14 | background-color: #e6e6e6;
15 | border-right: 2px solid;
16 | font-weight: normal;
17 | }
18 |
19 | .navitem.active,
20 | .dropdown.active {
21 | font-weight: bold;
22 | }
23 |
24 | .navbar-dark .navbar-nav .nav-link {
25 | color: white !important;
26 | }
27 |
28 | hr {
29 | margin-top: 2em;
30 | margin-bottom: 2em;
31 | }
32 |
33 | li > img {
34 | margin-top: 0.8em;
35 | margin-bottom: 0.8em;
36 | }
37 |
38 | .admonition, details {
39 | /*padding: 15px;
40 | margin-bottom: 20px;*/
41 | border: 1px solid transparent;
42 | border-radius: 4px;
43 | text-align: left;
44 | }
45 |
46 | .admonition.note, .admonition.note > .admonition-title {
47 | border-color: #1a6dca;
48 | padding-bottom: 0;
49 | }
50 |
51 | .admonition.note > .admonition-title {
52 | background-color: transparent;
53 | border-bottom: none;
54 | color: #2e6b89;
55 | font-weight: bold;
56 | }
57 |
58 | .admonition.note, details.note {
59 | color: #2e6b89;
60 | background-color: #e2f0f7;
61 | border-color: #bce8f1;
62 | }
--------------------------------------------------------------------------------
/docs/installing.md:
--------------------------------------------------------------------------------
1 | # How to install
2 | To install Universal G-Code Sender, download the correct package for your operating system, unpack and run. See the videos below for detailed instructions.
3 |
4 | !!! note
5 | If you have recently upgraded UGS and it is not working properly, you may need to remove cached files. The Cache is kept in a user directory called ugsplatform and deleting this directory will force UGS to recreate it. The location of the directory is as the following:
6 |
7 | * Windows: `C:\Users\[your username>]\AppData\Local\ugsplatform\Cache`
8 | * Linux: `~/.cache/ugsplatform`
9 | * MacOSX: `~/Library/Application Support/ugsplatform`
10 |
11 | ## Windows
12 |
13 |
14 | ## MacOSX
15 |
16 |
17 | ## Linux
18 |
19 |
--------------------------------------------------------------------------------
/docs/dev/getting_started.md:
--------------------------------------------------------------------------------
1 | # Project Organization
2 |
3 | Universal Gcode Sender uses [Maven](http://maven.apache.org/) to build the
4 | project. It is using maven modules to separate the core library / classic GUI
5 | and the UGS Platform project. At the top level a `UGS` target defines the
6 | `ugs-core` and `ugs-platform-parent` modules which can be built separately or
7 | all at once.
8 |
9 | The classic gui is part of the core project in the `ugs-core` module. The
10 | `maven-shade-plugin` and `maven-assembly-plugin` are generate the
11 | self-executing JAR and distribution zip.
12 |
13 | UGS Platform is built on the [NetBeans Platform](https://netbeans.org/features/platform/).
14 | It is also using maven.
15 |
16 | Development is done using NetBeans, but most of the code can be edited using
17 | any IDE that supports Maven.
18 |
19 | # Development with an IDE
20 | ----------------
21 |
22 | Any IDE supporting Maven should be able to open the UGS project directory. Once
23 | opened it should show you the `ugs-core` and `ugs-platform-parent` modules
24 | which correspond to the Classic and Platform interfaces.
25 |
26 | Development is done using NetBeans, and for some project development NetBeans
27 | is almost required. But for tweaking the UI and experimenting with the backend
28 | any IDE which supports maven can be used.
29 |
30 | ## Classic GUI
31 | ----------------
32 | In the `ugs-core` module, you can run the `MainWindow.java` file to start the
33 | Classic GUI.
34 |
35 | ## UGS Platform
36 | ----------------
37 | The platform build has a number of submodules. Load the suite of modules by
38 | running the ugs-platform-app module.
39 |
40 | # Development with the Command Line
41 | ----------------
42 |
43 | The UGS Classic and Platform interfaces can also be run from the command line.
44 | These commands should be run from the root directory.
45 |
46 | ## Classic GUI
47 | ----------------
48 | There is a helper script named `run_classic.sh`, or you can use the commands below.
49 |
50 | ### Running the UI
51 | ```
52 | mvn install
53 | mvn exec:java -Dexec.mainClass="com.willwinder.universalgcodesender.MainWindow" -pl ugs-core
54 | ```
55 |
56 | ### Executing tests
57 | ```
58 | mvn install
59 | mvn test -pl ugs-core
60 | ```
61 |
62 | ### Building the self-executing JAR
63 | ```
64 | mvn install
65 | mvn package -pl ugs-core
66 | ```
67 |
68 | ### Building a UniversalGcodeSender.zip release file
69 | ```
70 | mvn package assembly:assembly
71 | ```
72 |
73 | ## UGS Platform
74 | ----------------
75 | There is a helper script named `run_platform.sh`, or you can use the commands below.
76 |
77 | ### Running the UI
78 | ```
79 | mvn install
80 | mvn nbm:run-platform -pl ugs-platform/application
81 | ```
82 |
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | If you are interested in contributing to make UGS even more awesome, there are
4 | many places you can pitch in.
5 |
6 | Browse through the already reported [issues][github_issues_link] or check out
7 | the [discussion forum][discussion_forum_link]. You might know how to code, have
8 | ideas on how to improve the documentation or want to translate the software to
9 | your language.
10 |
11 |
12 | ## Code
13 |
14 | Pull requests are welcome! Is there a feature you would like to see, or a bug
15 | thats been bothering you? Feel free to dig in. Not sure where to start? Ask on
16 | github, use an existing ticket or create a new one.
17 |
18 | If you're planning to make a lot of changes please create an issue to discuss
19 | implementation details. A lot of effort has gone into the current design so we
20 | want to make sure everything will to work together.
21 |
22 | Checkout the [Developing wiki page for more details](https://github.com/winder/Universal-G-Code-Sender/wiki/Developing)
23 |
24 |
25 | ## Documentation
26 |
27 | If you find any errors in the [documentation](https://github.com/winder/Universal-G-Code-Sender/wiki/) or if there are missing pages, you are welcome to contribute!
28 |
29 |
30 | ## Translations
31 |
32 | We are currently using [POEditor](https://poeditor.com/join/project/2J2hB5I41Z) to manage localization.
33 | If you would like to help please consider signing up to contribute through this service.
34 |
35 | To join the project sign up here: [https://poeditor.com/join/project/2J2hB5I41Z](https://poeditor.com/join/project/2J2hB5I41Z)
36 |
37 | ### Adding a new language
38 |
39 | You can add a new language from POEditor and start translating.
40 |
41 | 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](https://git-scm.com).
42 |
43 | * Create an empty property file for your language in `ugs-core/src/resources`.
44 | * Open `src/com/willwinder/universalgcodesender/i18n/AvailableLanguages.java`
45 | * Add your new translation to the `availableLanguages` object.
46 | * Update the file in `update_languages.py` with a mapping between the POEditor key and your new file.
47 | * Run `update_languages.py`, see README in scripts directory for configuration detail. Only commit the new file even if others were updated.
48 | * [Create a GitHub pull request](https://help.github.com/articles/using-pull-requests/).
49 |
50 | Future language syncs will be done periodically with the `update_languages.py` script.
51 |
52 | [github_issues_link]: https://github.com/winder/Universal-G-Code-Sender
53 | [discussion_forum_link]: https://groups.google.com/forum/#!forum/universal-gcode-sender
54 |
--------------------------------------------------------------------------------
/docs/dev/frontend_development.md:
--------------------------------------------------------------------------------
1 | # Front-end Architecture
2 |
3 | UGS uses a Model-View-Presenter architecture. What this means is that at a high
4 | level there are three layers which each serve different purposes. A **Model**
5 | for all backend logic, a **View** displayed to the user and a **Presenter**
6 | which serves as a buffer between the model and one or more views.
7 |
8 | # Model
9 |
10 | The model contains all backend logic. Things like opening a connection, listing
11 | serial ports, streaming a file, and handling firmware specific nuances. All of
12 | this is hidden from the front end as much as possible.
13 |
14 | # View
15 |
16 | The view only has access to the presenter. It is responsible for all user
17 | interaction and feedback. The main logic in here should be things like
18 | enabling or disabling components based on the current state of the model.
19 |
20 | ## Classic GUI
21 |
22 | The **Classic GUI** is built using NetBeans. There are a number of custom Swing
23 | components, and they are all initialized with the NetBeans GUI builder. The
24 | vast majority of the **Classic GUI** code is contained in `MainWindow.java`.
25 | There isn't a lot to expand on here, this front end has grown organically over
26 | the years and is fairly rigid. The `Visualizer` component is a standalone
27 | JOGL window which is updated using events from the backend (it was a model for
28 | many of the improvements in the current applications architecture).
29 |
30 | ## UGS Platform
31 |
32 | The **UGS Platform** build is also built using NetBeans. It is a built ontop of
33 | the NetBeans Platform which provides it a robust set of tools like flexible
34 | windows, a plugin framework, and a suite of tools for module communications. At
35 | the core of this is a module named `UGSLib` which is a simple wrapper to the
36 | standard UGS JAR file. There is a suite of modules named `UGSCore` which
37 | provides many of the standard UI elements seen in the **Classic GUI**, in
38 | addition there are other modules that provide new functionality.
39 |
40 | Extending the GUI is now a matter of creating a new plugin, for details on how
41 | to do this see the [Plugin Tutorial](plugin.md).
42 |
43 | # Presenter
44 |
45 | The presenter serves as an API for the model. All the heavy lifting needed for
46 | the GUI should happen here. For example the controller model object knows how
47 | to stream a processed file, but it doesn't know how to process the file. So the
48 | presenter will pass data to the gcode processor and generate a processed object
49 | which can be passed to the controller.
50 |
51 | Similarly, all notifications from the model are reinterpreted for the view with
52 | a simpler message strategy.
53 |
54 | In this way, all updates to the backend code can be leveraged by all front ends
55 | which utilize UGS.
56 |
57 | ## BackendAPI
58 |
59 | In UGS interfaces named `BackendAPI` and `BackendAPIReadOnly` provide the
60 | presenter layer. The read only methods are split off into a sub-interface in
61 | case a developer wants to be sure they don't change any state. For instance a
62 | widget that displays the current machine location probably has no need for
63 | pausing a stream.
64 |
65 | These APIs are used by all front ends (**Classic GUI**, **PendantUI** and
66 | **UGS Platform**).
67 |
--------------------------------------------------------------------------------
/docs/download.md:
--------------------------------------------------------------------------------
1 | # Download
2 |
3 | This is the latest release of UGS. For source code, nightly builds or older releases please [visit github](https://github.com/winder/Universal-G-Code-Sender).
4 |
5 | ### UGS Platform
6 | The next generation platform-based interface.
7 |
8 | | Version 2.1.17 | Description |
9 | |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
10 | | [ Windows 64-bit](https://github.com/winder/Universal-G-Code-Sender/releases/download/v2.1.17/win64-ugs-platform-app-2.1.17.zip) | Windows 64-bit version with bundled Java |
11 | | [ MacOSX (Intel)](https://github.com/winder/Universal-G-Code-Sender/releases/download/v2.1.17/macosx-x64-ugs-platform-app-2.1.17.dmg) | MacOSX version with bundled Java |
12 | | [ MacOSX (Silicon)](https://github.com/winder/Universal-G-Code-Sender/releases/download/v2.1.17/macosx-aarch64-ugs-platform-app-2.1.17.dmg) | MacOSX ARM64 version (for Apple Silicon M1/M2) with bundled Java |
13 | | [ Linux](https://github.com/winder/Universal-G-Code-Sender/releases/download/v2.1.17/linux-x64-ugs-platform-app-2.1.17.tar.gz) | Linux version with bundled Java |
14 | | [ Linux ARM](https://github.com/winder/Universal-G-Code-Sender/releases/download/v2.1.17/linux-arm-ugs-platform-app-2.1.17.tar.gz) | Linux ARM version with bundled Java. Can be used with Raspberry Pi OS 32-bit |
15 | | [ Linux ARM64](https://github.com/winder/Universal-G-Code-Sender/releases/download/v2.1.17/linux-aarch64-ugs-platform-app-2.1.17.tar.gz) | Linux ARM64 version with bundled Java. Can be used with Raspberry Pi OS 64-bit |
16 | | [ All platforms](https://github.com/winder/Universal-G-Code-Sender/releases/download/v2.1.17/ugs-platform-app-2.1.17.zip) | A generic package without [Java][java_link] which needs to be installed separately |
17 |
18 |
19 |
20 | ### UGS Classic
21 | The classic UGS interface with slightly less features but with the same robust backend as the Platform edition.
22 |
23 | | Download | Description |
24 | |--------------------------------------------------------------------------------------------------------------------------------------------------------------| ----------- |
25 | | [ All platforms](https://github.com/winder/Universal-G-Code-Sender/releases/download/v2.1.17/UniversalGcodeSender.zip) | A generic package without [Java][java_link] which needs to be installed separately |
26 |
27 |
28 |
29 | [java_link]: https://java.com/en/download/manual.jsp
30 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | A free and full featured gcode platform used for interfacing with advanced CNC controllers like [GRBL](https://github.com/grbl/grbl)
7 | , [FluidNC](http://wiki.fluidnc.com/), [TinyG](https://github.com/synthetos/TinyG), [g2core](https://github.com/synthetos/g2) and [Smoothieware](http://smoothieware.org/).
8 | 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.
9 |
10 |
11 |
24 |
25 | -------------
26 |
27 | ## Features
28 | * Cross platform, can run on Windows, MacOSX, Linux, and Raspberry Pi
29 | * Configurable user interface
30 | * 3D Gcode Visualizer with color coded line segments and real time tool position feedback
31 | * Real time overrides
32 | * G-code editor
33 | * Designer editor (with laser engraver support)
34 | * Configuratble gcode optimization:
35 | * Remove comments
36 | * Truncate decimal precision to configurable amount
37 | * Convert arcs (G2/G3) to line segments
38 | * Remove whitespace
39 | * Support for Gamepads and Joysticks
40 | * Web pendant interface
41 |
42 | # Screenshots
43 | *Fully modular GUI, reconfigure windows to suite your needs.*
44 |
45 |
46 | *Built in gcode editor with line highlighter and syntax highlighting*
47 |
48 |
49 |
50 | *Built in designer with support for importing SVG/DXF/PNG/JPG*
51 |
52 |
53 |
54 | *Customizable keybindings.*
55 |
56 |
57 |
58 | *Zoom to selection with command and drag.*
59 |
60 |
61 |
62 | *Right click in the visualizer to jog to a specific XY location.*
63 |
64 |
65 |
66 | -------------
67 |
68 | # Sponsors
69 |
70 |
79 |
80 | -------------
81 |
82 | # Donations
83 |
84 | 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.
85 |
86 |
95 |
96 |
97 |
118 |
119 |
120 | [java_version_img]: img/shields/Language-Java-brightgreen.svg
121 | [java_version_link]: https://java.com/en/download/manual.jsp
122 | [license_img]: img/shields/License-GPLv3-blue.svg
123 | [license_link]: http://www.gnu.org/licenses/quick-guide-gplv3.en.html
124 | [travis_img]: https://travis-ci.org/winder/Universal-G-Code-Sender.svg?branch=master
125 | [travis_link]: https://travis-ci.org/winder/Universal-G-Code-Sender
126 | [github_img]: https://img.shields.io/badge/Star-123-green.svg?style=social
127 | [github_link]: https://github.com/winder/Universal-G-Code-Sender
128 | [release_img]: https://img.shields.io/github/release/winder/Universal-G-Code-Sender
129 | [release_link]: download/
130 |
--------------------------------------------------------------------------------
/docs/dev/backend_development.md:
--------------------------------------------------------------------------------
1 | # Backend architecture
2 |
3 | Similar to the front-end there are more layers on the backend to help with
4 | supporting differences between different gcode controllers and the different
5 | ways to communicate with these controllers. Because UGS depends on serial
6 | events from CNC devices, the communication between layers is also event driven.
7 | This is implemented using a series of **Listener** classes which pass messages
8 | from the lower levels to the upper levels whenever data is detected on the
9 | serial port (USB).
10 |
11 | ## Controller
12 |
13 | A controller is primarily responsible for implementing controller-specific
14 | features. Different features can be things like what happens when a
15 | `Perform Homing` command is requested, or how to issue status requests and
16 | parse their results. `GRBL` and `TinyG` are both supported, they share a
17 | lot of code with the **AbstractController.java** abstract class.
18 |
19 | Internally the **AbstractController** class implements several important
20 | things. It manages the stream lifecycle, keeping track of which commands have
21 | been sent, which have been completed and in some cases which are queued for
22 | sending. The controller also figures out when the stream has finished. Finally
23 | the **AbstractController** implements the `SerialCommunicatorListener`, which
24 | how its able to detect all of this state information (and allows commands to
25 | be sent to the CNC controller).
26 |
27 | The controller provides a `ControllerListener` interface which is used to
28 | provide real time status.
29 |
30 | Finally, the **AbstractController** defines a number of abstract methods which
31 | can be used by device specific controllers as needed to hook into important
32 | lifecycle events:
33 | ```java
34 | abstract protected void closeCommBeforeEvent();
35 | abstract protected void closeCommAfterEvent();
36 | protected void openCommAfterEvent() throws Exception {}
37 | abstract protected void cancelSendBeforeEvent();
38 | abstract protected void cancelSendAfterEvent();
39 | abstract protected void pauseStreamingEvent() throws Exception;
40 | abstract protected void resumeStreamingEvent() throws Exception;
41 | abstract protected void isReadyToSendCommandsEvent() throws Exception;
42 | abstract protected void statusUpdatesEnabledValueChanged(boolean enabled);
43 | abstract protected void statusUpdatesRateValueChanged(int rate);
44 |
45 | // This one is special, because it is responsible for parsing device
46 | // responses, such as a command complete, status string, or parsing a
47 | // status event. In the case of a command complete, it must call
48 | // `commandComplete` to push the stream lifecycle along.
49 | abstract protected void rawResponseHandler(String response);
50 | ```
51 |
52 | Here is the public interface which controlles conform to:
53 | ```java
54 | public interface IController {
55 | /*
56 | Observable
57 | */
58 | public void addListener(ControllerListener cl);
59 |
60 | /*
61 | Actions
62 | */
63 | public void performHomingCycle() throws Exception;
64 | public void returnToHome() throws Exception;
65 | public void resetCoordinatesToZero() throws Exception;
66 | public void resetCoordinateToZero(final char coord) throws Exception;
67 | public void killAlarmLock() throws Exception;
68 | public void toggleCheckMode() throws Exception;
69 | public void viewParserState() throws Exception;
70 | public void issueSoftReset() throws Exception;
71 |
72 | /*
73 | Behavior
74 | */
75 | public void setSingleStepMode(boolean enabled);
76 | public boolean getSingleStepMode();
77 |
78 | public void setStatusUpdatesEnabled(boolean enabled);
79 | public boolean getStatusUpdatesEnabled();
80 |
81 | public void setStatusUpdateRate(int rate);
82 | public int getStatusUpdateRate();
83 |
84 | public GcodeCommandCreator getCommandCreator();
85 | public long getJobLengthEstimate(File gcodeFile);
86 |
87 | /*
88 | Serial
89 | */
90 | public Boolean openCommPort(String port, int portRate) throws Exception;
91 | public Boolean closeCommPort() throws Exception;
92 | public Boolean isCommOpen();
93 |
94 | /*
95 | Stream information
96 | */
97 | public Boolean isReadyToStreamFile() throws Exception;
98 | public Boolean isStreamingFile();
99 | public long getSendDuration();
100 | public int rowsInSend();
101 | public int rowsSent();
102 | public int rowsRemaining();
103 |
104 | /*
105 | Stream control
106 | */
107 | public void beginStreaming() throws Exception;
108 | public void pauseStreaming() throws Exception;
109 | public void resumeStreaming() throws Exception;
110 | public void cancelSend();
111 |
112 | /*
113 | Stream content
114 | */
115 | public GcodeCommand createCommand(String gcode) throws Exception;
116 | public void sendCommandImmediately(GcodeCommand cmd) throws Exception;
117 | public void queueCommand(GcodeCommand cmd) throws Exception;
118 | public void queueStream(GcodeStreamReader r);
119 | public void queueRawStream(Reader r);
120 | }
121 | ```
122 |
123 |
124 | ## Communicator
125 |
126 | A communicator handles all levels of sending data to the device. Raw responses
127 | are returned to any listeners via the `SerialCommunicatorListener`.
128 |
129 | The `AbstractCommunicator` implements several listener utilities which are used
130 | by implementing classes.
131 |
132 | The `BufferedCommunicator` abstract class handles the process of buffering
133 | multiple commands at once in order to keep a constant stream of commands
134 | available to the CNC device. It does this in the **streamCommands** method by
135 | maintaining a list of active commands, and the current size of those commands. A
136 | method named `processedCommand` must be implemented in a subclass to determine
137 | whether a raw response indicates a command has completed. This notifies the
138 | **BufferedCommunicator** that it should attempt to send more commands.
139 |
140 | `GrblCommunicator` and `TinyGCommunicator` are two concrete implementations of
141 | the **BufferedCommunicator**.
142 |
143 | ## Connection
144 |
145 | This is a very thin layer which provides a way to write and receive data:
146 | ```java
147 | abstract public boolean openPort(String name, int baud) throws Exception;
148 | abstract public void closePort() throws Exception;
149 | abstract public boolean isOpen();
150 | abstract public void sendByteImmediately(byte b) throws Exception;
151 | abstract public void sendStringToComm(String command) throws Exception;
152 | ```
153 |
154 | # Streaming strategy
155 |
156 | UGS attempts to use a fixed amount of memory when streaming a file. In this way
157 | it can send gcode files of any size. Files are preprocessed at the `BackendAPI`
158 | level using the `GcodeStreamWriter` class. This will serialize all the required
159 | metadata into a file. Later on that file can be opened with the
160 | `GcodeStreamReader` class, the **Controller** and **Communicator** classes use
161 | this. Using the reader, the **Communicator** class can pull out commands one at
162 | a time and send them to the **Connection**.
163 |
--------------------------------------------------------------------------------
/docs/dev/gcode_processor.md:
--------------------------------------------------------------------------------
1 | # Gcode Processor Development
2 |
3 | The UGS core library has a flexible gcode processor plugin system. It is designed
4 | as a processing pipeline to convert one line of code at a time by passing it
5 | through multiple **Command Processor** plugins. Some advanced features in UGS,
6 | like the Auto Leveler, take advantage of this feature to inject a special
7 | processor module into the gcode processing pipeline. Other processors are simpler,
8 | such as the **M30Processor** which simply removes unwanted **M30** commands, or the
9 | **CommandLengthProcessor** which causes an error if the final processed line has
10 | too much data for your controller.
11 |
12 | This process is configured using a JSON file which holds processor configuration,
13 | order in which processors should appear in the pipeline, and whether or not they
14 | are enabled. All of this is configurable in UGS and UGP in a **Gcode Processor
15 | Configuration** menu.
16 |
17 | ## Anatomy of a CommandProcessor
18 |
19 | The processor interface is simple. One command goes in along with the current state
20 | and a list of output commands come out. A CommandProcessor might discover invalid
21 | input, in which case a GcodeParserException can be thrown for the GcodeParser to
22 | handle.
23 |
24 | ```java
25 | public interface CommandProcessor {
26 | /**
27 | * Given a command and the current state of a program returns a replacement
28 | * list of commands.
29 | * @param command Input gcode.
30 | * @param state State of the gcode parser when the command will run.
31 | * @return One or more gcode commands to replace the original command with.
32 | */
33 | public List processCommand(String command, GcodeState state) throws GcodeParserException;
34 |
35 | /**
36 | * Returns information about the current command and its configuration.
37 | * @return
38 | */
39 | public String getHelp();
40 | }
41 | ```
42 |
43 | ## Simple Example
44 |
45 | The **CommandLengthProcessor** is one of the simplest examples of a
46 | CommandProcessor. One thing of interest is that it accepts a length parameter
47 | during configuration. By adding a CommandLengthProcessor to the GcodeParser,
48 | you can ensure the maximum length of commands.
49 |
50 | ```java
51 | public class CommandLengthProcessor implements CommandProcessor {
52 | final private int length;
53 | public CommandLengthProcessor(int length) {
54 | this.length = length;
55 | }
56 |
57 | @Override
58 | public String getHelp() {
59 | // Global localization helpers are used for the help message.
60 | return Localization.getString("sender.help.command.length") + "\n" +
61 | Localization.getString("sender.command.length")
62 | + ": " + length;
63 | }
64 |
65 | @Override
66 | public List processCommand(String command, GcodeState state) throws GcodeParserException {
67 | if (command.length() > length)
68 | throw new GcodeParserException("Command '" + command + "' is longer than " + length + " characters.");
69 |
70 | return Collections.singletonList(command);
71 | }
72 | }
73 | ```
74 |
75 | ## More Complex Examples
76 | The following examples can be found in GitHub, they wont have the full code
77 | included.
78 |
79 | ### DecimalProcessor
80 | This is only slightly more complicated than the CommandLengthProcessor. It adds
81 | in some config validation by throwing a RuntimeException in the constructor,
82 | which is handled by code which configures the GcodeParser. The **processCommand**
83 | method is then able to truncate any decimals using simple string manipulation.
84 |
85 | ### FeedOverrideProcessor
86 | Like the **DecimalProcessor**, the **FeedOverrideProcessor** is able to modifie any
87 | **F-Commands** with simple string manipulation.
88 |
89 | ### LineSplitter
90 | The **LineSplitter** CommandProcessor will actually modify commands by parsing
91 | the gcode and rewriting it. There are several utilities used here to help:
92 |
93 | * **GcodeParser.processCommand** - Converts the command string into something
94 | easier to work with.
95 | * **GcodePreprocessorUtils.extractMotion** - Helper to extract all words associated
96 | to movement commands, the remainder should still be sent but may not be sent in
97 | the context of the rewritten command.
98 |
99 | The remaining logic checks the length of any `G0` or `G1` commands and converts
100 | them, or returns the original command unmodified.
101 |
102 | ## Tutorial: Creating a New CommandProcessor
103 |
104 | Creating a fully integrated and configurable **CommandProcessor** is simple, but
105 | does touch a number of different files. This tutorial will go over those pieces
106 | in the context of creating a processor named **M3Dweller**.
107 |
108 | ### Goal
109 | Create a processor which inserts a **Dwell** command (short delay) whenever the
110 | spindle is enabled. This allows any potentially slow setups such as VFD's to
111 | come up to speed. It should be configurable via the **Gcode Processor
112 | Configuration** menu.
113 |
114 | ### Creating the processor
115 | First you'll notice that the constructor initializes the command which should
116 | be added after our spindle start **M3** command. The locale is set to make sure
117 | comma is not used as a decimal separator:
118 | ```java
119 | private final String dwellCommand;
120 |
121 | public M3Dweller(double dwellDuration) {
122 | this.dwellCommand = String.format(Locale.ROOT, "G4P%.2f", dwellDuration);
123 | }
124 | ```
125 |
126 | The help method simply adds some comments for the settings GUI:
127 | ```java
128 | @Override
129 | public String getHelp() {
130 | return "Add a delay after enabling the spindle with \"M3\" commands. \"M3\" must be the only command on the line.";
131 | }
132 | ```
133 |
134 | Everything is then pulled together in the **processCommand** method to return an
135 | extra dwell command when **M3** is detected:
136 | ```java
137 | // Contains an M3 not followed by another digit (i.e. M30)
138 | Pattern m3Pattern = Pattern.compile(".*[mM]3(?!\\d)(\\D.*)?");
139 |
140 | @Override
141 | public List processCommand(String command, GcodeState state) throws GcodeParserException {
142 | if (m3Pattern.matcher(command).matches()) {
143 | return Arrays.asList(command, dwellCommand);
144 | }
145 | return Collections.singletonList(command);
146 | }
147 | ```
148 |
149 | All at once now:
150 | ```java
151 | public class M3Dweller implements CommandProcessor {
152 | private final String dwellCommand;
153 |
154 | // Contains an M3 not followed by another digit (i.e. M30)
155 | Pattern m3Pattern = Pattern.compile(".*[mM]3(?!\\d)(\\D.*)?");
156 |
157 | public M3Dweller(double dwellDuration) {
158 | this.dwellCommand = String.format(Locale.ROOT, "G4P%.2f", dwellDuration);
159 | }
160 |
161 | @Override
162 | public List processCommand(String command, GcodeState state) throws GcodeParserException {
163 | String noComments = GcodePreprocessorUtils.removeComment(command);
164 | if (m3Pattern.matcher(noComments).matches()) {
165 | return Arrays.asList(command, dwellCommand);
166 | }
167 | return Collections.singletonList(command);
168 | }
169 |
170 | @Override
171 | public String getHelp() {
172 | return "Add a delay after enabling the spindle with \"M3\" commands. \"M3\" must be the only command on the line.";
173 | }
174 | }
175 | ```
176 |
177 | ### Hooking up the JSON
178 |
179 | The json files are stored in `ugs-core/src/resources/firmware_config`, each of
180 | these will be updated to include our new processor. Notice that we have an
181 | argument named duration, and that it is disabled by default:
182 | ```json
183 | "name": "M3Dweller",
184 | "enabled": false,
185 | "optional": true,
186 | "args": {
187 | "duration": 2.5
188 | }
189 | },{
190 | ```
191 |
192 | We also increment the version string, which will prompt users that there is a
193 | new configuration file available and they may need to revisit their settings:
194 | ```diff
195 | - "Version": 3,
196 | + "Version": 4,
197 | ```
198 |
199 | ### JSON Loader
200 | The last step is to update the **CommandProcessorLoader** to include logic that
201 | creates the new M3Dweller when needed. I used one of the other processors as an
202 | example and made sure to make a corresponding entry for the **M3Dweller** in each
203 | of the corresponding code and comment locations:
204 | ```java
205 | case "M3Dweller":
206 | double duration = pc.args.get("duration").getAsDouble();
207 | p = new M3Dweller(duration);
208 | break;
209 | ```
210 |
211 | There is also a test which should be updated to make sure everything is working:
212 | ```diff
213 | + args = new JsonObject();
214 | + args.addProperty("duration", 2.5);
215 | + object = new JsonObject();
216 | + object.addProperty("name", "M3Dweller");
217 | + object.add("args", args);
218 | + array.add(object);
219 | +
220 | String jsonConfig = array.toString();
221 | List processors = CommandProcessorLoader.initializeWithProcessors(jsonConfig);
222 |
223 | - assertEquals(8, processors.size());
224 | + assertEquals(9, processors.size());
225 | assertEquals(ArcExpander.class, processors.get(0).getClass());
226 | assertEquals(CommentProcessor.class, processors.get(1).getClass());
227 | assertEquals(DecimalProcessor.class, processors.get(2).getClass());
228 | assertEquals(FeedOverrideProcessor.class, processors.get(3).getClass());
229 | assertEquals(M30Processor.class, processors.get(4).getClass());
230 | assertEquals(PatternRemover.class, processors.get(5).getClass());
231 | assertEquals(CommandLengthProcessor.class, processors.get(6).getClass());
232 | assertEquals(WhitespaceProcessor.class, processors.get(7).getClass());
233 | + assertEquals(M3Dweller.class, processors.get(8).getClass());
234 | ```
235 |
236 | ### Testing
237 | The command processors lend themselves to thorough testing, so we create a new
238 | `M3DwellerTest.java` file in the associated test package and write some tests:
239 | ```java
240 | @Test
241 | public void testReplaces() throws Exception {
242 | M3Dweller dweller = new M3Dweller(2.5);
243 | String command;
244 |
245 | command = "M3";
246 | Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,"G4P2.50");
247 |
248 | command = "m3";
249 | Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,"G4P2.50");
250 |
251 | command = "M3 S1000";
252 | Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,"G4P2.50");
253 |
254 | command = "m3 S1000";
255 | Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,"G4P2.50");
256 |
257 | command = "(this is ignored) M3 S1000";
258 | Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command,"G4P2.50");
259 | }
260 |
261 | @Test
262 | public void testNoOp() throws Exception {
263 | M3Dweller dweller = new M3Dweller(2.5);
264 | String command;
265 | command = "anything else";
266 | Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command);
267 |
268 | command = "M30";
269 | Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command);
270 |
271 | command = "G0 X0 Y0 (definitely not ready to start the spindle with an M3 yet)";
272 | Assertions.assertThat(dweller.processCommand(command, null)).containsExactly(command);
273 | }
274 | ```
275 |
276 | ### Localizing
277 |
278 | In addition to the help message which may use the global **Localization** helper,
279 | the settings menu will attempt to find something with the same name as in the
280 | JSON file. So we add an entry to
281 | `./ugs-core/src/resources/MessagesBundle_en_US.properties`, which will later be
282 | pulled into a separate localization service to be localized in other languages.
283 | ```diff
284 | WhitespaceProcessor = Whitespace Remover
285 | +M3Dweller = Spindle start delay
286 | controller.exception.smoothie.booting = Smoothie has not finished booting.
287 | ```
288 |
289 | ### Conclusion
290 |
291 | We have now fully integrated our **M3Dweller** processor into the UGS framework
292 | and users of UGS and UGP can both make use of it.
293 |
294 | The raw commit for this feature is found on [github here](https://github.com/winder/Universal-G-Code-Sender/commit/edc9f08e6eb908706f4985bdbcbbb6ffa831b72a).
295 |
296 |
297 |
298 |
--------------------------------------------------------------------------------
/docs/dev/plugin.md:
--------------------------------------------------------------------------------
1 | # Note: The UGS Platform has been updated to use maven. Some parts of this document need to be updated to reflect that change.
2 |
3 |
4 |
5 |
6 | # Plugin development
7 |
8 | The UGS Platform is built ontop of the NetBeans Platform. This gives us powerful
9 | tools to work with, including a robust plugin system. The heart of the UGS
10 | Platform is a module which wraps and exposes the Universal Gcode Sender JAR
11 | file - the same jar you could execute to run the Classic GUI! Other than using
12 | the UGSLib module, developing a plugin for the UGS Platform is exactly the same
13 | as developing any other NetBeans Platform plugin. And there is lots of great
14 | documentation for that, here is the [NetBeans Platform Plugin Quick Start](https://platform.netbeans.org/tutorials/nbm-google.html)
15 | guide.
16 |
17 | # Workflow Plugin Tutorial
18 |
19 | In this tutorial we're going to build a window to help manage jobs that use
20 | multiple tools which are split into multiple files. The rough design idea will
21 | have a central table with four columns containing:
22 | * File name
23 | * Tool name (editable)
24 | * Finished flag
25 |
26 | There will be a pair of buttons to add and remove files from the table, and
27 | we will also hook up a UGS event listener to detect when files are opened from
28 | other areas of the interface as well.
29 |
30 | Lastly, we'll add another pair of buttons to move rows around in the table, so
31 | that we can reorganize the workflow if files were added out of order the order.
32 |
33 | Here is a sketch of what we're building:
34 |
35 |
36 |
37 | ## Create and configure project
38 | Universal Gcode Sender is developed with NetBeans, and plugins are no exception.
39 | Once you've cloned the Universal Gcode Sender project you should be able to open
40 | the UGSPlatform folder with NetBeans and it will discover a project that you can open.
41 | To start building your module expand the UGSPlatform section, right-click the
42 | modules directory and select `Add New...`.
43 |
44 |
45 |
46 | This will open up a wizard where you name the module, and declare the source
47 | path. For this example the module is named WorkflowModuleTutorial and the source
48 | path is com.willwinder.ugs.nbp.workflowmoduletutorial which is the convention
49 | used in the core modules.
50 |
51 |
52 |
53 | ## Add UGS dependencies
54 |
55 | Your module should now be listed in the Modules section. If it doesn't you may
56 | need to restart NetBeans. Before we dive into the code there are a couple helper
57 | classes to import which will give you full access to the UGS API. Double click
58 | your module from the Modules section to open the code, then right-click the top
59 | level item which appeared and select the properties menu.
60 |
61 | Select `Add Dependency...`, here you should search for `UGSLib` and
62 | `CentralLookup` then add them to your plugin.
63 |
64 |
65 |
66 | ## Create window class
67 |
68 | Now we're ready to build the module. In this tutorial we're building a window
69 | to manage a multi-job workflow, so we'll start by adding a window to customize.
70 |
71 | 1. Open the new module and right click the new package, in the context menu go to
72 | `New` -> `Window...`. To bring up the new window wizard.
73 | 2. In the first screen of the wizard choose the default location your window
74 | will appear. Custom locations have been designed for UGS Platform, the largest
75 | is named `visualizer` because it is the Visualizer's default location. We'll
76 | use this location for our plugin. This means that when our plugin opens it will
77 | be tabbed with the Visualizer module.
78 | 3. Click next and choose a class name for your module, for this tutorial I'm
79 | going to call it `WorkflowModuleTutorial`.
80 |
81 |
82 |
83 | ## Build the GUI
84 |
85 | The NetBeans GUI builder makes it easy to make a custom user interface without
86 | writing a single line of code (which is the main reason UGS uses NetBeans!).
87 | Using the GUI builder we'll add some buttons and a table. This step can be as
88 | elaborate as you want. If you're a seasoned swing developer and prefer not to
89 | use the magic GUI builder, no worries, you can create the UI programatically as
90 | well - but that is a different tutorial.
91 |
92 | * Take a look at the screenshot below. The `[TopComponent] - Navigator - Editor`
93 | window shows all the objects that have been added with the GUI builder.
94 | * There are four JButtons, a JTable nested inside a JScrollPane and a JPanel
95 | which I used to make alignment a little easier (The GUI Builder is powerful,
96 | but it can also be a bit quirky).
97 | * Putting the JTable inside a JScrollPane makes it so that if too many items
98 | get added to the table it will scroll rather than dissapear off the bottom.
99 | * **Note:** The name given to these components will be used in the code, so
100 | be sure to use the names shown in the screenshot.
101 |
102 |
103 |
104 | The JTable is going to be the trickiest part of build the GUI. To configure the
105 | table right-click the JTable object from the component navigator and select
106 | `Table Contents...`. Here you can add our 3 columns and specify that the data
107 | types. You can also specify which columns are editable, in this example we want
108 | the user to be able to type in what type of tool should be used.
109 |
110 |
111 |
112 | ## Autogenerated code
113 |
114 | Before writing any code, lets take a look at what has already been automatically
115 | generated for us.
116 |
117 | 1. Just above the class there are a number of annotations. These are used by
118 | the NetBeans platform, most of them were setup according to how you filled
119 | in the Wizards earlier. They can also configure things like keyboard
120 | shortcuts, and where things are put in the dropdown menus.
121 |
122 | 2. Within the class there are several grayed out sections. This is code
123 | generated by NetBeans which the IDE prevents you from modifying outside the
124 | GUI builder or in some cases component properties. For example if you wanted
125 | to use a custom JTable, you would configure the table in the GUI builder by
126 | adding a custom constructor.
127 |
128 | 3. At the end of the file is `componentOpened` and `componentClosed`, these are
129 | lifecycle events that are called when the window has been opened or closed.
130 |
131 | 4. Also at the end of the file is `writeProperties` and `readProperties`, these
132 | are used to save the window state between runs.
133 |
134 | ## Annotated code
135 |
136 | This is the longest section because it will explain every line of code added to
137 | the `WorkflowModuleTutorial` class. The most complicated code deals with Swing
138 | component manipulation, with just a smattering of UGS lifecycle events to push
139 | things along.
140 |
141 | ### Class signature
142 |
143 | * First there are a few class state object we'll need and two Listeners we'll
144 | be implementing.
145 | ```java
146 | /**
147 | * UGSEventListener - this is how a plugin can listen to UGS lifecycle events.
148 | * ListSelectionListener - listen for table selections.
149 | */
150 | public final class WorkflowWindowTutorialTopComponent
151 | extends TopComponent
152 | implements UGSEventListener, ListSelectionListener {
153 |
154 | // These are the UGS backend objects for interacting with the backend.
155 | private final Settings settings;
156 | private final BackendAPI backend;
157 |
158 | // This is used to identify when a stream has completed.
159 | private boolean wasSending;
160 |
161 | // This is used in most methods, so cache it here.
162 | DefaultTableModel model;
163 | ```
164 |
165 | ### Constructor
166 |
167 | * In the constructor we register the class with the UGS backend and also set
168 | the class as a listener to table selection events.
169 | ``` java
170 | public WorkflowWindowTopComponent() {
171 | initComponents();
172 | setName(Bundle.CTL_WorkflowWindowTopComponent());
173 | setToolTipText(Bundle.HINT_WorkflowWindowTopComponent());
174 |
175 | // This is how to access the UGS backend and register the listener.
176 | // CentralLookup is used to get singleton instances of the UGS
177 | // Settings and BackendAPI objects.
178 | settings = CentralLookup.getDefault().lookup(Settings.class);
179 | backend = CentralLookup.getDefault().lookup(BackendAPI.class);
180 | backend.addUGSEventListener(this);
181 |
182 | // Allow contiguous ranges of selections and register a listener.
183 | this.fileTable.setSelectionMode(
184 | ListSelectionModel.SINGLE_INTERVAL_SELECTION);
185 | ListSelectionModel cellSelectionModel =
186 | this.fileTable.getSelectionModel();
187 | cellSelectionModel.addListSelectionListener(this);
188 |
189 | // Cache the model object.
190 | model = (DefaultTableModel)this.fileTable.getModel();
191 | }
192 | ```
193 |
194 | ### UGS Event Listener
195 |
196 | * This is the event sent from the UGS Backend, when a file is loaded or the
197 | state changes a notification will be sent.
198 | * If the state switches from `COMM_SENDING` to `COMM_IDLE` we'll run a
199 | `completeFile` method.
200 | * If a file is loaded, we add it to the table.
201 | ```java
202 | @Override
203 | public void UGSEvent(UGSEvent cse) {
204 | if (cse.isStateChangeEvent()) {
205 | if (wasSending && cse.getControlState() == ControlState.COMM_IDLE)
206 | this.completeFile(backend.getGcodeFile());
207 | wasSending = backend.isSending();
208 | }
209 | if (cse.isFileChangeEvent()) {
210 | this.addFileToWorkflow(backend.getGcodeFile());
211 | }
212 |
213 | }
214 | ```
215 |
216 | ### File Complete Handler
217 |
218 | * When a command is complete we'll update the JTable, select the next file that
219 | needs to be sent and popup a notification informing the user what they should
220 | do next. The selection event will be sent and handled in the selection
221 | handler.
222 | ```java
223 | public void completeFile(File gcodeFile) {
224 | if (gcodeFile == null) return;
225 |
226 | // Make sure the file is loaded in the table.
227 | int fileIndex = findFileIndex(gcodeFile);
228 | if (fileIndex < 0) return;
229 |
230 | // Mark that it has been completed.
231 | model.setValueAt(true, fileIndex, 2);
232 |
233 | fileIndex++;
234 | String message;
235 |
236 | // Make sure there is another command left.
237 | if (fileIndex < fileTable.getRowCount()) {
238 | String nextTool = (String) model.getValueAt(fileIndex, 1);
239 | String messageTemplate =
240 | "Finished sending '%s'.\n"
241 | + "The next file uses tool '%s'\n"
242 | + "Load tool and move machine to its zero location\n"
243 | + "and click send to continue this workflow.";
244 | message = String.format(
245 | messageTemplate, gcodeFile.getName(), nextTool);
246 |
247 | // Select the next row, this will trigger a selection event.
248 | fileTable.setRowSelectionInterval(fileIndex, fileIndex);
249 |
250 | // Use a different message if we're finished.
251 | } else {
252 | message = "Finished sending the last file!";
253 | }
254 |
255 | // Display a notification.
256 | java.awt.EventQueue.invokeLater(() -> {
257 | JOptionPane.showMessageDialog(new JFrame(), message,
258 | "Workflow Event", JOptionPane.PLAIN_MESSAGE);
259 | });
260 | }
261 | ```
262 |
263 | ### JTable Selection Listener
264 |
265 | * This is the selection listener, when a file is selected load it in the backend.
266 | ```java
267 | @Override
268 | public void valueChanged(ListSelectionEvent e) {
269 | int[] selectedRow = fileTable.getSelectedRows();
270 | // Only load files when there is a single selection.
271 | if (selectedRow.length == 1) {
272 | // Pull the file out of the table and set it in the backend.
273 | String file = (String) model.getValueAt(selectedRow[0], 0);
274 | try {
275 | backend.setGcodeFile(new File(file));
276 | } catch (Exception ex) {
277 | Exceptions.printStackTrace(ex);
278 | }
279 | }
280 | }
281 | ```
282 |
283 | ### JTable Helper
284 |
285 | * Helper method to add a file to the JTable, first making sure that it isn't
286 | already in the table.
287 | ```java
288 | public void addFileToWorkflow(File gcodeFile) {
289 | if (gcodeFile == null) {
290 | return;
291 | }
292 |
293 | int fileIndex = findFileIndex(gcodeFile);
294 | // Don't re-add a file.
295 | if (fileIndex >= 0) {
296 | return;
297 | }
298 |
299 | model.addRow(new Object[]{
300 | gcodeFile.getAbsolutePath(),
301 | "default",
302 | false
303 | });
304 |
305 | // Fire off the selection event to load the file.
306 | int lastRow = fileTable.getRowCount() - 1;
307 | fileTable.setRowSelectionInterval(lastRow, lastRow);
308 | }
309 | ```
310 |
311 | ### Add/Remove Button Action Handlers
312 |
313 | * Now we implement the button event methods. They are generated by double
314 | clicking the buttons in the GUI Builder. This generates the swing code that
315 | attaches the `ActionPerformed` events to the button click callbacks.
316 | * `addButtonActionPerformed` simply displays a file chooser (using some UGS
317 | library built ins) and calls the `addFileToWorkflow` method defined earlier.
318 | * `removeButtonActoinPerformed` is even simpler, it uses standard JTable
319 | functionality to remove any selected rows. The only thing clever here is that
320 | rows are removed starting from the end to avoid having the index of later
321 | selections change while deleting rows one at a time.
322 | ```java
323 | private void addButtonActionPerformed(ActionEvent evt) {
324 | // Open a file chooser pointing at the last opened directory.
325 | JFileChooser fileChooser = GcodeFileTypeFilter.getGcodeFileChooser(
326 | settings.getLastOpenedFilename());
327 |
328 | int returnVal = fileChooser.showOpenDialog(this);
329 | if (returnVal == JFileChooser.APPROVE_OPTION) {
330 | File gcodeFile = fileChooser.getSelectedFile();
331 |
332 | // Save the new directory!
333 | settings.setLastOpenedFilename(gcodeFile.getParent());
334 |
335 | addFileToWorkflow(gcodeFile);
336 | }
337 | }
338 |
339 | private void removeButtonActionPerformed(ActionEvent evt) {
340 | int[] selectedRows = fileTable.getSelectedRows();
341 | if (selectedRows.length == 0) return;
342 |
343 | Arrays.sort(selectedRows);
344 | for (int i = selectedRows.length - 1; i >= 0; i--) {
345 | int row = selectedRows[i]
346 | this.model.removeRow(row);
347 | this.model.fireTableRowsDeleted(row, row);
348 | }
349 | }
350 | ```
351 |
352 | ### Up / Down Button Action Handlers
353 |
354 | * The up and down action buttons are pure java code. They don't do anything
355 | you wouldn't do with any other Swing application. The code here deals strictly
356 | with moving selections around. Although a little tricky, and not totally
357 | relevant to UGS, they are included because the feature wouldn't be complete
358 | without them.
359 | ```java
360 | private void upButtonActionPerformed(ActionEvent evt) {
361 | int[] selectedRows = fileTable.getSelectedRows();
362 |
363 | // Exit early if nothing is selected.
364 | if (selectedRows.length == 0) return;
365 |
366 | Arrays.sort(selectedRows);
367 |
368 | // Exit early if the selected range can't move.
369 | if (selectedRows[0] == 0) return;
370 |
371 | for (int i = 0; i < selectedRows.length; i++) {
372 | selectedRows[i] = this.moveRow(selectedRows[i], -1);
373 | }
374 |
375 | int first = selectedRows[0];
376 | int last = selectedRows[selectedRows.length-1];
377 | fileTable.setRowSelectionInterval(first, last);
378 | }
379 |
380 | private void downButtonActionPerformed(ActionEvent evt) {
381 | int[] selectedRows = fileTable.getSelectedRows();
382 |
383 | // Exit early if nothing is selected.
384 | if (selectedRows.length == 0) return;
385 |
386 | Arrays.sort(selectedRows);
387 |
388 | // Exit early if the selected range can't move.
389 | if (selectedRows[selectedRows.length-1]
390 | == fileTable.getRowCount()) return;
391 |
392 | for (int i = selectedRows.length - 1; i >= 0; i--) {
393 | selectedRows[i] = this.moveRow(selectedRows[i], 1);
394 | }
395 |
396 | int first = selectedRows[0];
397 | int last = selectedRows[selectedRows.length-1];
398 | fileTable.setRowSelectionInterval(first, last);
399 | }
400 | ```
401 |
402 | ### NetBeans Platform Component Lifecycle Code
403 |
404 | * Of the automatically generated methods, `componentOpened` is the only one
405 | which needed some custom code. In case the component had been closed earlier
406 | or wasn't loaded until after a file stream started, grab the `wasSending`
407 | state and save it for later.
408 | ```java
409 | @Override
410 | public void componentOpened() {
411 | this.wasSending = backend.isSending();
412 | }
413 |
414 | @Override
415 | public void componentClosed() {
416 | // No special close handling.
417 | }
418 |
419 | void writeProperties(java.util.Properties p) {
420 | // better to version settings since initial version as advocated at
421 | // http://wiki.apidesign.org/wiki/PropertyFiles
422 | p.setProperty("version", "1.0");
423 |
424 | // We could save the loaded files here
425 | }
426 |
427 | void readProperties(java.util.Properties p) {
428 | String version = p.getProperty("version");
429 |
430 | // We could load previously loaded files here
431 | }
432 | ```
433 |
434 | ### Helper Methods
435 |
436 | * Finally, here are the helper methods used above.
437 | ```java
438 | /**
439 | * Look for the provided file in the file table.
440 | */
441 | private int findFileIndex(File gcodeFile) {
442 | if (gcodeFile == null) return -1;
443 |
444 | for (int i = 0; i < model.getRowCount(); i++) {
445 | String file = (String) model.getValueAt(i, 0);
446 | if (file != null && gcodeFile.getAbsolutePath().equals(file)) {
447 | return i;
448 | }
449 | }
450 |
451 | return -1;
452 | }
453 |
454 | /**
455 | * Move a given row by some offset. If the offset would move the row outside
456 | * of the current table size, the row is not moved.
457 | */
458 | private int moveRow(int row, int offset) {
459 | int dest = row + offset;
460 | if (dest < 0 || dest >= model.getRowCount()) {
461 | return row;
462 | }
463 |
464 | model.moveRow(row, row, dest);
465 | return dest;
466 | }
467 | ```
468 |
469 | ## Conclusion
470 |
471 | 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.
472 |
473 |