441 lines
23 KiB
Markdown
441 lines
23 KiB
Markdown
# Open-source landscape for The Pirate Ray
|
|
|
|
- Research date: 2026-10-17
|
|
- Scope: reusable software for a JPT E2/M7 MOPA fiber laser on a
|
|
JCZ/EZCAD2-compatible controller, followed by possible CO2 backends
|
|
- Method: public project documentation, source inspection, tests, release
|
|
history, and small shallow/sparse source checkouts; no vendor executable was
|
|
run and no laser or USB device was contacted
|
|
|
|
## Executive conclusion
|
|
|
|
The project should **not** begin by writing a LightBurn clone or reverse
|
|
engineering the complete JCZ protocol from scratch. Two MIT-licensed projects
|
|
already cover most of the problem from opposite directions:
|
|
|
|
1. [MeerK40t](https://github.com/meerk40t/meerk40t), specifically its
|
|
`balormk` module, already controls EZCAD2-compatible JCZ galvo controllers,
|
|
includes MOPA pulse-width support, and has a mock USB connection. Its USB
|
|
implementation uses VID `9588`, PID `9899`, write endpoint `0x02`, and read
|
|
endpoint `0x88`. The VID/PID exactly match the values independently found
|
|
in our private installer evidence.
|
|
2. [Rayforge](https://github.com/barebaric/rayforge) already supplies the
|
|
actively developed desktop editor, project model, geometry/toolpath
|
|
pipeline, LightBurn project and device import, material recipes,
|
|
simulation, pre-flight checks, packaging, and addon architecture. Its
|
|
neutral operation model and UI already represent laser frequency and pulse
|
|
width. It does not currently provide a JCZ driver.
|
|
|
|
The leading architecture is therefore a **Rayforge JCZ/Balor backend or
|
|
addon**, with command semantics adapted from the current BalorMK
|
|
implementation. Before committing to that architecture, stock MeerK40t should
|
|
also be evaluated as the shortest route to a usable open-source application.
|
|
An independent desktop application is the fallback, not the starting point.
|
|
|
|
This is a research recommendation, not yet an architecture decision. The
|
|
proposed decision and its proof obligations are recorded in
|
|
[ADR 0002](../adr/0002-reuse-rayforge-and-balormk.md).
|
|
|
|
## What the project actually needs
|
|
|
|
The useful unit of reuse is larger than a USB library. A workshop application
|
|
needs all of the following:
|
|
|
|
| Capability | Importance for the JPT | Existing best candidate |
|
|
| --- | --- | --- |
|
|
| JCZ discovery, USB transport and command encoding | Critical | MeerK40t BalorMK |
|
|
| Galvo job sequencing, timing, status and abort | Critical | MeerK40t BalorMK |
|
|
| MOPA frequency and pulse-width representation | Critical | Both projects |
|
|
| Lens correction and coordinate mapping | Critical | BalorMK as reference; must validate against our profiles |
|
|
| Mock controller and deterministic traces | Critical before hardware | BalorMK mock plus a new adapter-level fake |
|
|
| Desktop canvas, document and undo/redo | Large effort | Rayforge |
|
|
| SVG/DXF/PDF/raster and LightBurn import | Large effort | Rayforge |
|
|
| Geometry, hatch, offsets and path planning | Large effort | Rayforge plus raygeo |
|
|
| Machine profiles, materials and recipes | Large effort | Rayforge |
|
|
| Preview, simulation and pre-flight checks | Safety-relevant | Rayforge, extended for galvo semantics |
|
|
| Packaging for Windows | Required | Both ship desktop builds; Rayforge is the preferred modern base |
|
|
| Future Ruida support | Relevant to the CO2 machines if confirmed | Rayforge/ruida-pa or MeerK40t; controller identity still unknown |
|
|
|
|
## Candidate assessment
|
|
|
|
The classifications mean:
|
|
|
|
- **adopt**: evaluate as a product or direct maintained dependency;
|
|
- **adapt**: use code or concepts behind a narrow interface, with attribution;
|
|
- **reference**: learn from it, but do not make it a primary dependency;
|
|
- **defer/reject**: does not solve the current controller problem well enough.
|
|
|
|
| Candidate | License | JCZ/MOPA | Desktop/product coverage | Maintenance signal at snapshot | Disposition |
|
|
| --- | --- | --- | --- | --- | --- |
|
|
| MeerK40t | MIT | Direct EZCAD2/JCZ; MOPA support | Full app, older wxPython architecture | Project declares maintenance mode, but still receives changes | **Adopt/evaluate now** and **adapt BalorMK** |
|
|
| BalorMK in MeerK40t | MIT at repository root; upstream documents its rewrite history | Direct, exact controller family | Controller, driver and limited galvo UI | Current code in main | **Primary protocol baseline** |
|
|
| galvoplotter | MIT | Direct low-level LMC; fiber and CO2 | Library only | Latest PyPI release found is old/beta | Prototype/reference; current BalorMK is ahead |
|
|
| Rayforge | MIT | No JCZ driver yet; neutral model already has MOPA fields | Broad modern app | Very active, current source and releases | **Preferred host platform, pending spike** |
|
|
| raygeo | MIT | Hardware-neutral | High-performance geometry kernel | Active and used by Rayforge | Adopt through Rayforge; direct use only if needed |
|
|
| ruida-pa | MIT | No JCZ | Binary Ruida backend | Active and used by Rayforge | Future CO2 candidate after controller identification |
|
|
| VisiCut / LibLaserCut | LGPL family | No credible JCZ path found | Mature Java architecture and drivers | Long-running project | Reference for abstractions |
|
|
| LaserWeb4 | AGPL-3.0 | No JCZ support found | Electron/web laser UI | Older ecosystem | Defer; weaker fit and license implications |
|
|
| lbrnts | MIT | File format only | TypeScript LightBurn parse/write | Small focused project | Reference if round-trip writing becomes necessary |
|
|
| lbrn2-to-svg | Open repository; verify exact license before reuse | File format only | Narrow conversion | Small project | Reference; Rayforge already imports `.lbrn2` |
|
|
| USBLM-V1 MeerK40t replacement | Open repository | Related JCZ hardware, but VID/PID `9588:9999` rather than ours | Firmware/stack-specific | Niche | Reference only; not our identified device |
|
|
| MarkEzd/EZCAD SDK wrappers | Often open wrapper code around proprietary DLLs | Direct through vendor DLL | Windows-only API wrappers | Depends on opaque vendor binary | Do not use as the public product foundation |
|
|
|
|
Repository activity is a maintenance indicator, not a security or quality
|
|
guarantee. License labels are project-declared and must be rechecked at the
|
|
exact commit before code is copied or distributed.
|
|
|
|
## Finding 1: MeerK40t can probably talk to this controller today
|
|
|
|
[MeerK40t describes itself](https://github.com/meerk40t/meerk40t) as an
|
|
MIT-licensed, cross-platform laser application and explicitly lists
|
|
EZCAD2-compatible JCZ galvo lasers among supported devices. Its current
|
|
`balormk` module is not a placeholder:
|
|
|
|
- `usb_connection.py` uses PyUSB/libusb and the exact `9588:9899` identity
|
|
present in our sanitized machine profile;
|
|
- `controller.py` implements the LMC command layer, command lists, status,
|
|
delays, power, frequency, MOPA pulse width, wait and abort behavior;
|
|
- `driver.py` translates MeerK40t cut operations into galvo jobs;
|
|
- `device.py` exposes laser-source, correction, timing and pulse-width
|
|
settings;
|
|
- `mock_connection.py` records traffic without contacting hardware;
|
|
- the plugin registers a MOPA-capable fiber device.
|
|
|
|
The upstream [BalorMK documentation](https://github.com/meerk40t/meerk40t/blob/main/meerk40t/balormk/README.md)
|
|
states support for JCZ EZCAD2, fiber MOPA, power/frequency/pulse timing,
|
|
calibration and a simulation interface. The
|
|
[USB source](https://github.com/meerk40t/meerk40t/blob/main/meerk40t/balormk/usb_connection.py)
|
|
is especially strong corroborating evidence because its identifiers were
|
|
found independently rather than inferred from the project.
|
|
|
|
This changes M3 in the original roadmap. Protocol work should begin by
|
|
validating and testing an existing independent implementation, not by passive
|
|
capture or blank-sheet reverse engineering.
|
|
|
|
### Limits and risks
|
|
|
|
- MeerK40t explicitly says it is in maintenance mode and warns that weekly
|
|
builds can regress. A known stable release and a pinned source commit must
|
|
be evaluated separately.
|
|
- The low-level controller is embedded in MeerK40t service and cut-code
|
|
abstractions. Copying only `driver.py` would not produce a clean standalone
|
|
library.
|
|
- The current module has a mock transport, but its hardware-safety behavior
|
|
and test depth do not meet this project's intended fail-closed standard.
|
|
- Source comments and compatibility branches show uncertainty around some
|
|
controller/source variants. “EZCAD2 compatible” is not proof that every
|
|
setting is correct for our JPT source, scan head and lens.
|
|
- The driver includes commands capable of moving the galvos and enabling
|
|
emission. It is evidence and reusable code, not a safety certification.
|
|
|
|
### `galvoplotter`: cleaner boundary, older implementation
|
|
|
|
[galvoplotter](https://github.com/meerk40t/galvoplotter) is an MIT-licensed,
|
|
PyUSB-based low-level library for EZCAD2 LMC controllers. It has controller,
|
|
connection, spooler and job abstractions and was explicitly designed for
|
|
embedding. That makes its package boundary attractive.
|
|
|
|
The drawback is age: the latest package found on
|
|
[PyPI](https://pypi.org/project/galvoplotter/) is the beta `0.2.0` release
|
|
from 2023, while BalorMK has continued evolving and explicitly contains MOPA
|
|
support. It should be used to understand the smallest viable API, not pinned
|
|
blindly as the production backend. A good upstream outcome could be helping
|
|
extract today's BalorMK controller into a maintained standalone package.
|
|
|
|
## Finding 2: Rayforge already is most of the desired application
|
|
|
|
[Rayforge](https://github.com/barebaric/rayforge) is an MIT-licensed,
|
|
cross-platform Python/GTK4 desktop application. Current functionality includes
|
|
a 2D editor, layers, material recipes, projects, SVG/DXF/PDF/raster import,
|
|
3D toolpath simulation, rotary support, cameras, pre-flight bounds/no-go-zone
|
|
checks, and packaged Windows builds. It already imports LightBurn projects and
|
|
device profiles.
|
|
|
|
Source inspection found particularly relevant implementation details:
|
|
|
|
- `.lbrn`/`.lbrn2` parsing covers shapes, paths, transforms, images and text
|
|
backup paths;
|
|
- imported LightBurn cut settings are mapped into Rayforge workflows,
|
|
including speed, power, passes, offsets, raster interval/angle and dither;
|
|
- `.lbdev` device profiles have a separate importer;
|
|
- laser steps and recipes serialize `frequency` in Hz; the laser-step UI
|
|
describes `pulse_width` as nanoseconds, while the Ruida encoder calls the
|
|
same Ops value microseconds, so the unit contract requires clarification;
|
|
- the neutral `Ops` command set contains frequency and pulse-width commands;
|
|
- binary rather than G-code drivers are supported through an `OpsEncoder`;
|
|
- driver state, cancel/hold, discovery, machine profiles, simulator and
|
|
maturity warnings already exist.
|
|
|
|
Rayforge's [driver guide](https://rayforge.org/docs/developer/driver/) defines
|
|
separate encoder, pipeline, transport and asynchronous driver roles. Its
|
|
[addon guide](https://rayforge.org/docs/developer/addon-overview/) explicitly
|
|
describes third-party machine drivers loaded without modifying the core. This
|
|
is almost the exact integration seam the project needs.
|
|
|
|
### Why this is preferable to a new app
|
|
|
|
A new desktop app would immediately recreate document serialization,
|
|
selection and transforms, units, importers, undo/redo, toolpath generation,
|
|
preview, material management, settings UI, packaging and update work. None of
|
|
those areas differentiates The Pirate Ray. The differentiating work is:
|
|
|
|
- a trustworthy JCZ adapter;
|
|
- JPT/lens-specific profile validation;
|
|
- galvo/MOPA safety policy;
|
|
- reproducible offline and hardware tests;
|
|
- workshop-focused defaults and documentation.
|
|
|
|
Keeping that work in an addon or upstream contribution makes the result useful
|
|
outside Makerspace Næstved and reduces the permanent maintenance burden.
|
|
|
|
### Rayforge integration risks
|
|
|
|
- The public addon guide demonstrates `machine_manager.register(...)`, while
|
|
the inspected current source exposes a module-level `register_driver(...)`
|
|
registry and did not reveal the documented machine registration call in the
|
|
loaded paths. This may be documentation drift or an incomplete checkout.
|
|
A tiny addon-loading test must prove the supported seam before architecture
|
|
is accepted.
|
|
- Rayforge currently models much of its world around moving-head/G-code
|
|
machines. Galvo framing, list buffering, correction files, timing delays,
|
|
infinite “red light” preview and coordinate transforms need an explicit fit
|
|
analysis.
|
|
- GTK4/Libadwaita is a substantial Windows packaging dependency stack, even
|
|
though the project publishes Windows builds.
|
|
- The addon API is evolving. The manifest API and Rayforge/raygeo versions
|
|
must be pinned and compatibility tested.
|
|
- Existing frequency and pulse-width fields are necessary but not sufficient:
|
|
their units, zero/default semantics and allowed ranges must be checked
|
|
end-to-end against the JPT source.
|
|
|
|
## Finding 3: do not build a geometry engine
|
|
|
|
[raygeo](https://github.com/barebaric/raygeo) is Rayforge's MIT-licensed Rust
|
|
and PyO3 geometry library with Windows/macOS/Linux wheels. It already supplies
|
|
path construction, arcs and Beziers, boolean and offset operations, fitting,
|
|
nesting, raster modulation, smoothing, linearization, geometric queries and
|
|
cylindrical transforms.
|
|
|
|
If Rayforge is adopted, use raygeo through Rayforge's model rather than
|
|
inventing a competing neutral geometry model. If Rayforge is rejected after
|
|
the spike, raygeo remains a strong direct dependency candidate, subject to API
|
|
stability and binary-wheel review.
|
|
|
|
## Finding 4: LightBurn migration is mostly an extension problem
|
|
|
|
Rayforge's existing LightBurn import removes the need to create a general
|
|
`.lbrn2` parser. The focused task is to build a private, known-working fixture
|
|
matrix and identify which galvo-specific fields are currently dropped.
|
|
|
|
Likely gaps to test include:
|
|
|
|
- MOPA pulse width and frequency on every operation type;
|
|
- hatch modes, cross-hatch order and galvo-specific timing;
|
|
- device origin, field size and lens correction references;
|
|
- text fidelity when only a `BackupPath` is portable;
|
|
- layer ordering and mark-count semantics;
|
|
- LightBurn units and default/omitted value behavior.
|
|
|
|
The small TypeScript project [lbrnts](https://github.com/tscircuit/lbrnts)
|
|
supports parsing and writing LightBurn files and may be useful as an
|
|
independent behavioral comparison if round-trip export becomes important. It
|
|
does not justify adding a JavaScript runtime to the initial Python stack.
|
|
|
|
## Finding 5: Windows USB driver handling is a product requirement
|
|
|
|
BalorMK uses PyUSB/libusb. The installed EZCAD/LightBurn stack normally uses a
|
|
Windows vendor driver. Only one driver can own the relevant interface, so a
|
|
Zadig/libusb change may make the current production software stop seeing the
|
|
laser until it is reverted.
|
|
|
|
[meerk40t-pnputil](https://github.com/meerk40t/meerk40t-pnputil) is an
|
|
MIT-licensed proof of concept for switching Windows drivers. It is evidence
|
|
that reversible handling is possible, not production code we should silently
|
|
run. Before live testing, record the current driver package and create a
|
|
documented rollback procedure. Driver changes require explicit operator
|
|
approval and are outside the current offline phase.
|
|
|
|
## Future CO2 machines
|
|
|
|
Do not infer the Eduard or Bodor controller from the machine brand. Physically
|
|
identify controller model, connection type and firmware first.
|
|
|
|
If either machine uses Ruida, Rayforge already integrates the MIT-licensed
|
|
[ruida-pa](https://github.com/StevenIsaacs/ruida-pa) project and MeerK40t also
|
|
has Ruida support. That could make a shared Rayforge platform even more
|
|
valuable. If the machines use another controller family, repeat this landscape
|
|
and evidence process for that controller.
|
|
|
|
## Proprietary vendor SDKs are not the main path
|
|
|
|
EZCAD/MarkEzd SDK wrappers found in public repositories usually expose an open
|
|
wrapper around a proprietary `MarkEzd.dll`. That may provide useful names and
|
|
semantic clues, but it does not give us an auditable, cross-platform, freely
|
|
redistributable controller stack. It also retains the vendor driver and
|
|
hardware-lock assumptions.
|
|
|
|
The public product should therefore not load or redistribute a copied vendor
|
|
DLL. Any future use of official SDK documentation must be separately reviewed
|
|
for license, provenance and clean-room implications.
|
|
|
|
## Recommended architecture to prove
|
|
|
|
```text
|
|
Rayforge UI / documents / imports / recipes / simulation
|
|
|
|
|
raygeo Ops
|
|
|
|
|
ThePirateRay JCZ adapter boundary
|
|
/ \
|
|
deterministic fake Balor transport
|
|
and trace sink (adapted from current BalorMK)
|
|
|
|
|
explicit hardware gate
|
|
|
|
|
JCZ 9588:9899 controller
|
|
```
|
|
|
|
The adapter must keep protocol output separate from transport. The same
|
|
synthetic job must be encodable into a deterministic trace without importing
|
|
PyUSB or discovering hardware. Hardware transport is an optional component and
|
|
is disabled by default in development and CI.
|
|
|
|
The first public build should not expose arbitrary raw controller commands.
|
|
Machine identity, correction/profile hash, lens, field size and source type
|
|
must agree before a hardware job can be armed.
|
|
|
|
## Reuse boundaries
|
|
|
|
### Reuse directly or contribute upstream
|
|
|
|
- Rayforge application, document, import, recipe, simulation and packaging;
|
|
- raygeo through Rayforge;
|
|
- Rayforge's `Ops`, binary encoder and asynchronous driver contracts;
|
|
- BalorMK command constants, packet semantics and mock-derived test vectors,
|
|
after exact-license and attribution review;
|
|
- PyUSB only behind the real hardware transport.
|
|
|
|
### Rewrite narrowly for fit and safety
|
|
|
|
- the Rayforge-to-Balor operation encoder;
|
|
- a small controller facade that does not depend on MeerK40t kernel services;
|
|
- validation of JPT frequency/pulse-width ranges and units;
|
|
- correction/profile loading with provenance and fail-closed matching;
|
|
- structured, redacted diagnostics;
|
|
- a hardware capability gate and test protocol.
|
|
|
|
### Do not build now
|
|
|
|
- a new canvas or general CAD system;
|
|
- a new SVG/DXF/PDF importer suite;
|
|
- a new geometry/boolean/hatch kernel;
|
|
- a new material database UI;
|
|
- a general LightBurn parser;
|
|
- a blank-sheet JCZ protocol implementation;
|
|
- an auto-installer that changes Windows USB drivers;
|
|
- support for all three lasers before their controllers are identified.
|
|
|
|
## Offline proof plan
|
|
|
|
No USB access, framing or emission is needed for these experiments.
|
|
|
|
### Spike A — stock MeerK40t
|
|
|
|
1. Pin stable MeerK40t and the inspected source commit.
|
|
2. Run only its mock controller in an isolated configuration directory.
|
|
3. Create a synthetic square plus hatch job with explicit power, speed,
|
|
frequency and pulse width.
|
|
4. Save the mock command stream as a test artifact with upstream version.
|
|
5. Assess whether the existing app already meets enough workshop needs to
|
|
prefer a small fork/contribution over a second frontend.
|
|
|
|
Go if mock execution is deterministic, the MOPA parameters survive the full
|
|
pipeline, and the UI/product gap is acceptable. Stop and document any path
|
|
that tries to enumerate real USB devices in mock mode.
|
|
|
|
### Spike B — Rayforge addon seam
|
|
|
|
1. Pin Rayforge at the inspected commit.
|
|
2. Create the smallest external addon that registers a no-device binary
|
|
driver.
|
|
3. Prove install, discovery, enable/disable, project reload and packaging on
|
|
Windows.
|
|
4. Resolve the documented `machine_manager.register` versus source-level
|
|
`register_driver` discrepancy with a test or upstream clarification.
|
|
|
|
Go if an external addon can own a binary driver without patching core. If a
|
|
small upstream patch is needed and maintainers accept the design, that is also
|
|
a go. A permanent private fork is less attractive but still cheaper than a new
|
|
application.
|
|
|
|
### Spike C — golden command equivalence
|
|
|
|
1. Construct a tiny Rayforge `Ops` job: move, line, power, speed, frequency,
|
|
pulse width, delay and end.
|
|
2. Encode it through a new no-I/O JCZ encoder.
|
|
3. Generate the equivalent BalorMK mock trace.
|
|
4. Compare semantic commands and packet bytes, documenting intentional
|
|
differences.
|
|
5. Add negative cases for wrong profile, out-of-field geometry, unsupported
|
|
commands, invalid MOPA ranges and cancellation.
|
|
|
|
Go if the adapter can stay small and deterministic. Do not connect the USB
|
|
transport as part of this spike.
|
|
|
|
### Spike D — LightBurn fixture coverage
|
|
|
|
1. Use sanitized synthetic projects plus private known-working projects.
|
|
2. Inventory every source field and its imported representation.
|
|
3. Add public synthetic regression fixtures for missing galvo/MOPA concepts.
|
|
4. Propose upstream Rayforge importer changes where generally useful.
|
|
|
|
## Decision gates
|
|
|
|
Choose **stock/forked MeerK40t** when its existing workflow is acceptable and
|
|
the fastest usable deployment outweighs UI modernization.
|
|
|
|
Choose **Rayforge plus a JCZ addon** when Spike B proves the extension seam and
|
|
Spike C shows that galvo semantics fit without invasive core changes. This is
|
|
the current preferred long-term result.
|
|
|
|
Choose a **small standalone controller library** only if the controller code
|
|
cannot responsibly live in either upstream. It should still be usable by both
|
|
projects and should not grow a GUI.
|
|
|
|
Choose an **independent desktop app** only after written evidence that both
|
|
full applications fail concrete requirements that cannot reasonably be fixed
|
|
upstream. “We can build it ourselves” is not sufficient evidence.
|
|
|
|
## Version-pinned research snapshot
|
|
|
|
Source inspected locally in shallow sparse checkouts:
|
|
|
|
- MeerK40t: `d511944e08788e88077727f682957759884ff5ca`
|
|
(2026-09-25)
|
|
- Rayforge: `c28918c87f8273dd1d244f5a057addc192807fa5`
|
|
(2026-10-17)
|
|
|
|
The checkouts are an ignored research cache and are not vendored into this
|
|
repository. Any reused code must later be added deliberately with license,
|
|
copyright, source commit and modification notices.
|
|
|
|
## Source index
|
|
|
|
- [MeerK40t repository and project status](https://github.com/meerk40t/meerk40t)
|
|
- [BalorMK overview](https://github.com/meerk40t/meerk40t/blob/main/meerk40t/balormk/README.md)
|
|
- [BalorMK USB implementation](https://github.com/meerk40t/meerk40t/blob/main/meerk40t/balormk/usb_connection.py)
|
|
- [galvoplotter repository](https://github.com/meerk40t/galvoplotter)
|
|
- [galvoplotter package](https://pypi.org/project/galvoplotter/)
|
|
- [Rayforge repository](https://github.com/barebaric/rayforge)
|
|
- [Rayforge driver guide](https://rayforge.org/docs/developer/driver/)
|
|
- [Rayforge addon guide](https://rayforge.org/docs/developer/addon-overview/)
|
|
- [raygeo repository](https://github.com/barebaric/raygeo)
|
|
- [ruida-pa repository](https://github.com/StevenIsaacs/ruida-pa)
|
|
- [meerk40t-pnputil](https://github.com/meerk40t/meerk40t-pnputil)
|
|
- [VisiCut](https://visicut.org/)
|
|
- [LaserWeb4](https://github.com/LaserWeb/LaserWeb4)
|
|
- [lbrnts](https://github.com/tscircuit/lbrnts)
|
|
- [USB-LMC-V1 replacement project](https://github.com/Meliadolin/usblm-v1-meerk40t)
|
|
- [Official LightBurn galvo documentation](https://docs.lightburnsoftware.com/latest/Collections/LightBurnForGalvos/)
|
|
- [Official EZCAD SDK product page](https://www.ezcad.com/products/ezcad-sdk/page/24/)
|
|
|