feat: add rotatable workspace orientation - #334
Conversation
|
Thank you for the PR! This is an interesting feature I never considered! I am open to see this merged, but since this touches on the coordinate logic - code that required months to mature and work on all possible machines - I will have to be more careful here. Overall, the patch is quite good and clean! The architecture was taken well into consideration, an in spite of you writing that it was made with the help of AI tools, it looks well cleaned up! My main worry is coordspace.py - this one is the hub that I would like to operate on only with minimally invasive changes. While replacing the if-conditions with matrix transformations looks much cleaner, this was actually tried before and led to a huge cascade in bugs. I am open to trying to clean this up again (the codebase has move much since this was last attempted), but would not want to mix this into a PR that also adds new features at the same time. I also see what I am 95% sure is a bug: Below are some areas of improvement that I found. EncapsulationThe PR adds
This is brittle (the Presentation logic has crept into the
|
|
Thank you for the detailed review. You were right about the I’ve pushed a follow-up addressing each point:
Local validation passed with 5,355 runnable backend tests and 561 UI tests, plus Ruff, Flake8, Pyflakes, and changed-file Pyright. CI has restarted on the follow-up commit. Thanks again for entertaining this, I know how risky these sorts of changes are, especially since it's impossible to physically test all the permutations. I really have been enjoying the software though! I have also been working on https://github.com/pborges/rayforged (which is pure AI slop atm) as a way to programatically produce rayforge project files, its hooked into my inventory management system to make it easy to laser out some asset tags.
The computer running my CNC machines runs linux and I was really tired of leaving my Mac out there to run LightBurn so I was really stoked to find this software! |
|
Following up on a few things that cut across the individual replies. Merge order. I went with your second option — minimally invasive — rather If you'd still rather land the refactor first as its own PR, I'm happy to wait On dropping the projection cache. Rather than fixing the invalidation, I One addition of my own. Keeping coordspace minimally invasive leaves the Two changes you may want to look at specifically, since neither is
|
|
@pborges — thanks for the follow-up, and glad you're enjoying Rayforge! The regression work looks good - the 32-case native matrix is great, and the 48-config scalar/matrix equivalence test is a clever guard. That equivalence test is also the thing I want to talk about, because I think it's pointing at an architectural problem I'm not comfortable merging as-is. The two-path split. Right now the same transform is described twice: once as the scalar It looks like they already with everything On rayforged — quick tip that should save you fragility: you don't need to re-implement the doc model. Rayforge is on PyPI, and the model is importable: from rayforge.core.doc import Doc # has to_dict() / from_dict()
from rayforge.core.item import DocItem
Proposed merge path Since this is on such a critical code path, we need to find a way to land this incrementally, each step small enough to review and test thoroughly, and each one shrinking the PR on rebase. My proposal for an integration order:
Credit & ownership — I can slice the PR if you want, but if I land slices as my own commits I'll add How does that sound? And would you rather author the slices yourself, or have me pick them with co-author credit? |
|
Proposed merge path sounds good to me, |
Make world_point_to_machine and machine_point_to_world delegate to the world<->machine matrices so the scalar UI path and the matrix encoder path share one source of truth. Drop the unused get_transform_from_world and add get_machine_to_world_matrix as its inverse. Port regression guards from #334: scalar/matrix path equivalence, axis-label origin native behavior, and a reference-position matrix. Co-Authored-By: pborges <pborges475@gmail.com>
Rework world_item_to_machine and machine_item_to_world to transform the bounding-box corners through the matrix-based point methods, leaving only the corner selection (which bbox corner the position refers to) as scalar logic. The point transform itself now flows through a single source of truth. Add regression guards: a 16-config item round-trip across every native origin/reversal combination, plus forward pins for origin-with-reversal configs and broader machine_item_to_world coverage. Co-Authored-By: pborges <pborges475@gmail.com>
Introduce WorkspaceOrientation (native/rotated_left/rotated_right) on MachineSpace and compose the 90-degree rotation into the single world<->machine matrix path, so every point and item transform gains rotation automatically. The machine keeps reading native coordinates; the rotation only controls how the bed is presented in world space. machine_item_to_world swaps item extents under rotation to keep its bounding-box corner selection correct. Add a 48-config point+item round-trip and rotation-direction forward pins. Remove the now-tautological scalar/matrix equivalence guard, since the point methods delegate to the matrix and there is a single path. Co-Authored-By: pborges <pborges475@gmail.com>
Introduce MachineView, a display-facing projection of MachineSpace that exposes how the bed appears after the workspace rotation (visible origin corner, axis directions). It lives in machine/models so presentation concerns stay off the coordinate model. WorkOriginElement no longer imports MachineSpace; it takes the two axis booleans it needs through set_axis_direction, matching AxisRenderer's primitive-setter pattern. WorkSurface reads MachineView and passes the primitives. This is a breaking addon-API change; PLUGIN_API_VERSION is bumped to 21. Co-Authored-By: pborges <pborges475@gmail.com>
|
Closing this one, it is superseded by #342. Thank you for the PR. |
|
Thank you! |


Summary
Rotary limitation
Rotary layers use a separate unrolled-cylinder coordinate system whose Y mapping currently happens before the workspace transform in Raygeo. To avoid silently generating incorrect output, rotary layers currently require Native workspace orientation.
The layer dialog prevents enabling rotary while the workspace is rotated, generation validates the combination again, stale generated output is cleared, and the resulting error explains how to correct the setting. Supporting a transparent rotary bypass can be handled separately by reordering the composed transform in the Raygeo rotary stage.
Verification
main(86 unrelated marked tests deselected).sys._MEIPASSdiagnostics inrayforge/app.pyandrayforge/worker_init.py.AI disclosure
This contribution was developed with assistance from OpenAI Codex and Anthropic Claude for implementation, test development, and code review. I reviewed the resulting changes, resolved the identified issues, and verified the final behavior and test results. No AI tool is included as a commit co-author.