Document the existing JCZ/MOPA implementation in MeerK40t, the Rayforge host-platform opportunity, alternatives, risks, and offline proof gates. Reframe the roadmap around validating reuse before building a new desktop stack.
23 KiB
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:
- MeerK40t, specifically its
balormkmodule, already controls EZCAD2-compatible JCZ galvo controllers, includes MOPA pulse-width support, and has a mock USB connection. Its USB implementation uses VID9588, PID9899, write endpoint0x02, and read endpoint0x88. The VID/PID exactly match the values independently found in our private installer evidence. - 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.
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 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.pyuses PyUSB/libusb and the exact9588:9899identity present in our sanitized machine profile;controller.pyimplements the LMC command layer, command lists, status, delays, power, frequency, MOPA pulse width, wait and abort behavior;driver.pytranslates MeerK40t cut operations into galvo jobs;device.pyexposes laser-source, correction, timing and pulse-width settings;mock_connection.pyrecords traffic without contacting hardware;- the plugin registers a MOPA-capable fiber device.
The upstream BalorMK documentation states support for JCZ EZCAD2, fiber MOPA, power/frequency/pulse timing, calibration and a simulation interface. The USB source 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.pywould 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 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 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 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/.lbrn2parsing 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;
.lbdevdevice profiles have a separate importer;- laser steps and recipes serialize
frequencyin Hz andpulse_widthin microseconds; - the neutral
Opscommand 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 defines separate encoder, pipeline, transport and asynchronous driver roles. Its addon guide 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-levelregister_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 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
BackupPathis portable; - layer ordering and mark-count semantics;
- LightBurn units and default/omitted value behavior.
The small TypeScript project 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 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 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
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
- Pin stable MeerK40t and the inspected source commit.
- Run only its mock controller in an isolated configuration directory.
- Create a synthetic square plus hatch job with explicit power, speed, frequency and pulse width.
- Save the mock command stream as a test artifact with upstream version.
- 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
- Pin Rayforge at the inspected commit.
- Create the smallest external addon that registers a no-device binary driver.
- Prove install, discovery, enable/disable, project reload and packaging on Windows.
- Resolve the documented
machine_manager.registerversus source-levelregister_driverdiscrepancy 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
- Construct a tiny Rayforge
Opsjob: move, line, power, speed, frequency, pulse width, delay and end. - Encode it through a new no-I/O JCZ encoder.
- Generate the equivalent BalorMK mock trace.
- Compare semantic commands and packet bytes, documenting intentional differences.
- 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
- Use sanitized synthetic projects plus private known-working projects.
- Inventory every source field and its imported representation.
- Add public synthetic regression fixtures for missing galvo/MOPA concepts.
- 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
- BalorMK overview
- BalorMK USB implementation
- galvoplotter repository
- galvoplotter package
- Rayforge repository
- Rayforge driver guide
- Rayforge addon guide
- raygeo repository
- ruida-pa repository
- meerk40t-pnputil
- VisiCut
- LaserWeb4
- lbrnts
- USB-LMC-V1 replacement project
- Official LightBurn galvo documentation
- Official EZCAD SDK product page