Metadata-Version: 2.4
Name: rollingmill
Version: 0.1.0
Summary: Open-source USB control stack for Roland DG MDX-series desktop mills
Project-URL: Homepage, https://github.com/TeaEngineering/rollingmill
Project-URL: Repository, https://github.com/TeaEngineering/rollingmill
Project-URL: Documentation, https://github.com/TeaEngineering/rollingmill/tree/main/docs
Project-URL: Issues, https://github.com/TeaEngineering/rollingmill/issues
Author-email: Chris Shucksmith <chris@shucksmith.co.uk>
License: MIT License
        
        Copyright (c) 2026 Chris Shucksmith
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: cnc,g-code,mdx-40,mdx-40a,mill,rml-1,roland,usb,vpanel
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console :: Curses
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Hardware
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Requires-Python: >=3.10
Requires-Dist: pyhershey>=0.1
Requires-Dist: pyusb>=1.2
Requires-Dist: setuptools
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

RollingMill
======

<img src="logo.png" width="35%" align="left" alt="RollingMill logo">

Open-source USB control stack for Roland DG MDX-series desktop mills, tested on the MDX-40A.

RollingMill communicates directly with the machine over libusb, replacing both the proprietary Windows kernel driver and the VPanel control application. It provides a terminal application that:

* Shows live machine state read-out
* Jogs four axes at variable speed, with cancel
* Moves to stored origins, view position, or arbitrary coordinates
* Controls spindle speed and rotary-axis drilling
* Sets workspace origins and tool diameter offsets
* Streams saved G-code (`.nc` files) to the mill
* Runs on Linux and macOS

<br clear="all">

https://github.com/user-attachments/assets/d28e8a8f-c5a4-4877-8cd2-d2d479c63437

Reverse Engineering
------
The MDX USB protocol was reverse engineered from the official Windows driver stack using Ghidra (and the MCP bridge) and [documented here in this respository](docs/). It should be stated that the Roland DG corporation neither authorised or approved of this work.

The official Windows stack consists of:

* RD25D — kernel-mode USB and printer filter driver
* VPanel — user-space control application

RollingMill implements both layers in Python, and works great on macOS.

Getting started
-----

This library is available from PyPi as `rollingmill`.

    $ pip install rollingmill
    $ rollingmill                                            # launch TUI on real hardware
    $ rollingmill --file sample-nc/example-doc-part.gcode    # stream an NC file

The interactive TUI exposes the following keybindings:

**Jogging**

| Key | Action |
|-----|--------|
| <kbd>←</kbd> / <kbd>→</kbd> | X axis −/+ |
| <kbd>↑</kbd> / <kbd>↓</kbd> | Y axis −/+ |
| <kbd>a</kbd> / <kbd>z</kbd> | Z axis +/− (`a` raises, `z` lowers) |
| <kbd>[</kbd> / <kbd>]</kbd> | A axis −/+ |
| <kbd>Esc</kbd> | cancel an in-flight jog |
| <kbd>f</kbd> | cycle jog speed: vslow / slow / medium / fast |
| <kbd>1</kbd>…<kbd>5</kbd> | step size: XYZ 0.01 / 0.1 / 1.0 / 10.0 / 50.0 mm — A 0.01 / 0.1 / 1.0 / 10.0 / 90.0° |

**Spindle**

| Key | Action |
|-----|--------|
| <kbd>s</kbd> | spindle on / off |
| <kbd>d</kbd> | A-axis rotary drilling on / off |
| <kbd>&lt;</kbd> / <kbd>&gt;</kbd> | spindle target RPM −500 / +500 |
| <kbd>-</kbd> / <kbd>+</kbd> | spindle & feed override % −10 / +10 |

**Dialogs**

| Key | Action |
|-----|--------|
| <kbd>c</kbd> | coordinate systems (activate / move-to / overwrite) |
| <kbd>m</kbd> | Move-To picker (presets + User Specify numeric entry) |
| <kbd>t</kbd> | tool diameter offsets |
| <kbd>q</kbd> | quit |

**Cut panel** (only when `--file` is given)

| Key | Action |
|-----|--------|
| <kbd>r</kbd> | run — stream the file continuously to the end |
| <kbd>x</kbd> | step — send one block; or, while running, stop after the current block |


Gotchas/Notes using the mill
-----

* If your NC-code 'crashes' the Z position above absolute machine zero (i.e. at the top of the machine), then the machine's interpreter starts running all future G codes as rapid moves flattened against Z=0 (all feeds become rapid moves, descending is inhibited) until the end of the program. This is quite terrifying should it happen, but is a machine feature.
* If you send NC-code (i.e. G-code) in RML-1 mode, it will hang. Recovery for now is a power-cycle.
* Single-stepping nc-code _partially works_, but the MDX complains about being starved of NC code and lights the View-LED when you reach a cutting operation.
* The progress display during a cutting job is that of filling the machine buffer, rather than the actual block being executed, which usually runs a few blocks behind. It's a shame there are not two counters so that this could be made 100% accurate.
* There might be an issue with storing A-axis values in workspace offsets and recalling them, as sometimes I get an extra +360° rotation on recall.
* I need a Z-origin sensor to test the sensor-based Z-origin setting, and decode the thickness settings — NYI.
* The geometry of the mill means that without tooling, it is fairly difficult (but not impossible) to crash. The rotary axis makes this much easier as the firmware allow the collet to hit the vice area.
* Be warned that bad, terrible things could happen driving your hardware with this experimental software, due to known or unknown bugs or defects, for which you entirely agree to take all risks in operating and hold the contributors blameless.


MDX GCode Intepreter Notes
----
* G-codes must be two digits (`G01`, `G00` etc)
* Dont forget to turn the spindle on (Sxxxx M03) otherwise the machine will fault on the first `G01` operation
* Ensure all `XYZ` values have a decimal point, and no more than 3 digits of precision
* Feed rates need a decimal point too
* Machine coordinate moves `G53` must be always given in `G90` absolute programming. Specifying G53 in G91 incremental results in an error.

Check your code using this [online G-code viewer](https://shucksmith.uk/NCviewer/), customised for the MDX G-code varient.


ZCL-40A 4th Axis
----
When this unit is installed, the X-, Y-, and Z-axis travel of the MDX-40A is reduced:

| Axis | MDX-40A     | MDX-40A + ZCL-40A      |
|------|-------------|------------------------|
| X    | 0 → 305 mm  | 34 → 305 mm            |
| Y    | 0 → 305 mm  | 0 → 305 mm             |
| Z    | 0 → −105 mm | 0 → −68 mm             |
| A    | —           | 0 → 360° (continuous)  |

These limits are enforced in firmware, including from the physical jog controls on the machine itself. The X-restriction clears the rotary axis body, but not the rotary vice or tailstock, so care is needed.


Engraving text
-----

`rollingmill.text_to_gcode` renders a text string to a single-line G-code file using a Hershey stroke font from the `pyhershey` library. Glyphs carry per-character advance-width metrics, so spacing is naturally kerned. The output is plain RS-274 intended to be streamed via the TUI's `--file` flow.

```
python -m rollingmill.text_to_gcode \
    --text "Parcels" \
    --out /tmp/label.gcode \
    --height 8 \
    --z-cut -0.1 --z-safe 2 \
    --feed 200 \
    --workspace G54
```

`--workspace G54..G59` is optional; when set, the matching WCS-select word is emitted in the header so the cut runs against that work-offset.

Then load on the machine: `rollingmill --file /tmp/label.gcode`.

The default font is `roman_simplex`. List all available fonts with `--list-fonts`; pass any name (e.g. `--font gothic_german_triplex`) to switch. Use `--letter-spacing 1.5` to add (or, with a negative value, remove) extra mm of gap after each glyph's natural advance.

 ![Engraving text](docs/screenshots/engraving-text.png)

Drawing spirals
-----

`rollingmill.spiral` renders [hypotrochoids](https://en.wikipedia.org/wiki/Hypotrochoid), the curve traced when a small toothed wheel rolls inside a fixed toothed ring. You give it the two gear tooth counts and a pen-arm length, and it emits a single closed pen-down/pen-up loop scaled to fit your requested overall radius.

```
python -m rollingmill.spiral \
    --ring 96 --rotor 52 --pen 30 \
    --radius 40 --x0 100 --y0 100 \
    --out /tmp/spiral.gcode \
    --workspace G54
```

`--ring R` and `--rotor r` are tooth counts (`R` strictly greater than `r`). `--pen d` is the pen-arm length measured in the *same* tooth-count units as the rotor: `d < r` gives smooth curtate lobes, `d = r` gives a hypocycloid with cusps, `d > r` gives prolate loops. `--radius` is the desired max radial extent of the finished drawing in mm, centred on `(--x0, --y0)`. The curve closes exactly after `2π · r / gcd(R, r)` of the rolling parameter.

The output uses `G02` / `G03` arcs with incremental I/J offsets — each arc is fitted adaptively to the true hypotrochoid, with maximum deviation bounded by `--chord-tol` (default 0.05 mm).

Then load on the machine: `rollingmill --file /tmp/spiral.gcode`.
