C++ FORK
PetkosOrca
A C++ fork of OrcaSlicer in which one project targets a different printer on every plate, each drawn at that machine’s real bed size. The app ships as Podslicer; the repository keeps the petkos-orca name.
Read the source on GitHubRUNNING SOFTWARE
One project, seven machines
The plate board groups plates by the machine each one prints on and states that machine’s bed. The canvas draws every plate at its own printer’s size, so arranging onto a plate means arranging onto the real machine. Upstream closed the request for this as not planned.
MECHANISM
What “per plate” means in the code
A plate owns a slicing context: printer, process and filament presets. The context resolves to a bed shape and print volume; arrange, fill-bed and boundary checks run per plate; each plate slices and exports its own G-code. The first version, 4cf3af2, touched 23 files, and its layout rewrite was checked byte-identical against the original single-bed path so existing projects arrange exactly as before.
- PLATE A plate owns a slicing context
- CONTEXT Printer, process, filaments saved per plate in the 3MF; absent in older projects
- RESOLVE Bed shape and volume from that plate’s printer preset
- LAYOUT Drawn at real size arrange, fill-bed and boundary checks per plate
- OUTPUT Its own G-code sliced with that plate’s resolved config
// A plate's complete slicing identity, owned by the plate and by nothing else.
//
// There is no project printer to fall back to. An empty field is NOT inheritance: it is
// a plate that has not been given one yet, which happens in exactly two places - a plate
// that has just been created, and a project written before per-plate machines. Both are
// completed at once by PresetBundle::complete_plate_context, and after that every field
// is a name that must resolve exactly.
struct PlateSlicingContext
{
std::string printer_preset_name;
std::string printer_vendor_id;
std::string print_preset_name;
std::vector<std::string> filament_preset_names;
// Everything a slice needs to be named. printer_vendor_id is excluded: it is a
// guard recorded against the printer name, not an identity of its own, and an
// empty one means "no vendor was recorded", which resolves fine.
// physical_printer_id is excluded too - which machine on the network prints this
// is a dispatch question, and a plate slices without an answer to it.
bool is_complete() const
{
return !printer_preset_name.empty() && !print_preset_name.empty() && !filament_preset_names.empty();
}
The type the whole fork turns on, with the reasoning kept next to it. Upstream’s engine let an empty field mean “use the project’s printer”, which is exactly how a project could hold several printers in its data and still slice on one; deleting that meaning is what deletes the state.
else if (key == PLATER_PRINTER_PRESET_ATTR) {
m_curr_plater->slicing_context.printer_preset_name = xml_unescape(value.c_str());
}
else if (key == PLATER_PRINTER_VENDOR_ATTR) {
m_curr_plater->slicing_context.printer_vendor_id = xml_unescape(value.c_str());
And the context read back out of the 3MF project file, so a saved project reopens on the same machines. These lines are the fork’s own; blame them on GitHub.
MEASURED
86.6 → 5.8 ms, one mechanism
The GUI lagged as plates were added. Instrumenting the frame found one cause wearing several costumes: a complete ~974-option configuration was composed 47 times a frame, 36 of them inside the per-volume draw loop, to read a colour. The fix hoists that read out of the loop and memoises the other caller once per frame. Method: the fork’s own span instrumentation, driven by a scripted 36-plate scene; one run before the change and one after, same machine and harness, the same afternoon. Only back-to-back comparisons like this are quoted, because heat alone moves a laptop’s absolute numbers. The commit message records the same figures.
- Before the fix · 731 frames
- After · 720 frames
Table view
| Span (mean, ms) | Before | After |
|---|---|---|
| Whole frame | 86.580 | 5.836 |
| Drawing the objects, per pass | 30.449 | 1.158 |
| Drawing the overlays | 19.417 | 1.384 |
| Whole frame, p99 | 119.761 | 13.382 |
| Frames sampled | 731 | 720 |
- 119.8 → 13.4 ms
- worst 1% of frames (p99)
- 153 → 45 ms
- switching plate, median time to first paint
- 34,586 → 2,289
- config compositions per run
34,586 compositions over 731 frames, against 2,289 over 720. Each one deep-copies a ~974-option config to read a handful of values.
//The extruder palette is identical for every volume in this loop, and obtaining it composes
//the plate's whole ~974-option config. Asking once per volume cost 36 compositions - 53 ms -
//in a 36-plate frame. The loop is here, so the hoist belongs here.
const std::vector<ColorRGBA> extruder_colors = GUI::wxGetApp().plater()->get_extruders_colors();
for (GLVolumeWithIdAndZ& volume : to_render) {
The hoist: one palette read per draw pass instead of one per volume. Later commits cache it per plate, because plates can now carry different filaments.
std::vector<size_t> GLGizmosManager::get_selectable_idxs() const
{
//One composition per frame instead of eleven. See the note on the members in the header:
//is_selectable() reaches GLGizmoMmuSegmentation, which composes the plate's entire config to
//count filaments, and the toolbars ask this repeatedly while laying themselves out.
const uint64_t frame = GLCanvas3D::frame_id();
if (frame != 0 && frame == m_selectable_idxs_frame)
return m_selectable_idxs_cache;
m_selectable_idxs_cache = out;
m_selectable_idxs_frame = frame;
return out;
}
The memo: the gizmo toolbar asked eleven times a frame while laying itself out. Now it asks once, keyed on a frame counter.
IN AN INTERVIEW
The two-minute walkthrough
-
0:00
Open the repository on its default branch,
per-plate-machines.It is public and maintained, on top of a ~30,000-commit upstream.
-
0:20
Open commit 4cf3af2, the first per-plate change.
Scope inside a large C++ codebase, and a regression check on the path everyone else still uses.
-
0:50
Open commit 16615db and read the message.
Measurement before optimisation: cause, fix, rejected alternative and numbers in one place.
-
1:30
Show upstream issue #7238.
Why the fork exists: the feature was asked for and closed as not planned.
-
1:50
If time allows, open Podslicer on the laptop and move a plate to another printer.
The feature working, not described.