Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
its layer; generated content like the Material Test Grid restricts
its step to the workpiece it creates, and that selection is now
visible instead of hidden
- Vector editing: edit the nodes of imported paths: drag nodes and curve
handles, delete and insert nodes, and turn segments into curves or
lines (#521)

### Changed

Expand Down
90 changes: 45 additions & 45 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ rust = "==1.99.0"
patchelf = "==0.19.2"

[feature.app.pypi-dependencies]
raygeo = "==1.66.0"
raygeo = "==1.67.0"
raydriver = "==0.5.1"
rayforge = { editable = true, path = "." }
ruida-pa = "==0.22.0"
Expand Down
4 changes: 4 additions & 0 deletions rayforge/core/undo/property_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ def __init__(
setter_method_name: str | None = None,
on_change_callback: Callable[[], None] | None = None,
name: str | None = None,
coalesce: bool = True,
):
super().__init__(name, on_change_callback)
self.target = target
self.coalesce = coalesce
self.property_name = property_name
self.new_value = new_value
self.setter_method_name = setter_method_name
Expand Down Expand Up @@ -54,6 +56,8 @@ def undo(self) -> None:
def can_coalesce_with(self, next_command: Command) -> bool:
return (
isinstance(next_command, ChangePropertyCommand)
and self.coalesce
and next_command.coalesce
and self.target is next_command.target
and self.property_name == next_command.property_name
)
Expand Down
36 changes: 36 additions & 0 deletions rayforge/doceditor/boundaries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Undoable replacement of a workpiece's edited boundaries, shared by the
commands that edit vector paths: segment and contour deletion, path
cleanup, and node editing.
"""

from raygeo.geo import Geometry

from ..core.undo import ChangePropertyCommand
from ..core.workpiece import WorkPiece


def edited_boundaries_command(
workpiece: WorkPiece, new_geo: Geometry, name: str
) -> ChangePropertyCommand:
"""
Returns an undoable command that replaces the edited boundaries of
*workpiece* with *new_geo*. Executing it clears the render caches
and sends the updated signal. Every boundary edit is a discrete
user action, so the command opts out of coalescing: two quick edits
stay two undo steps.
"""

def _on_changed():
workpiece.clear_render_cache()
workpiece.updated.send(workpiece)

return ChangePropertyCommand(
target=workpiece,
property_name="_edited_boundaries",
new_value=new_geo,
old_value=workpiece._edited_boundaries,
on_change_callback=_on_changed,
name=name,
coalesce=False,
)
43 changes: 9 additions & 34 deletions rayforge/doceditor/edit_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)
from ..core.workflow import Workflow
from ..core.workpiece import WorkPiece
from .boundaries import edited_boundaries_command

if TYPE_CHECKING:
from ..core.asset import IAsset
Expand Down Expand Up @@ -345,23 +346,10 @@ def delete_contours(
for contour in remaining:
new_geo.extend(contour)

old_value = workpiece._edited_boundaries

def _on_changed():
workpiece.clear_render_cache()
workpiece.updated.send(workpiece)

history = self._editor.history_manager
with history.transaction(_("Delete contour(s)")) as t:
cmd = ChangePropertyCommand(
target=workpiece,
property_name="_edited_boundaries",
new_value=new_geo,
old_value=old_value,
on_change_callback=_on_changed,
name=_("Delete contour(s)"),
)
t.execute(cmd)
command = edited_boundaries_command(
workpiece, new_geo, _("Delete contour(s)")
)
self._editor.history_manager.execute(command)

def delete_segments(
self,
Expand Down Expand Up @@ -394,23 +382,10 @@ def delete_segments(
keep = set(range(len(data))) - to_remove
new_geo = boundaries.filter(keep)

old_value = workpiece._edited_boundaries

def _on_changed():
workpiece.clear_render_cache()
workpiece.updated.send(workpiece)

history = self._editor.history_manager
with history.transaction(_("Delete segment(s)")) as t:
cmd = ChangePropertyCommand(
target=workpiece,
property_name="_edited_boundaries",
new_value=new_geo,
old_value=old_value,
on_change_callback=_on_changed,
name=_("Delete segment(s)"),
)
t.execute(cmd)
command = edited_boundaries_command(
workpiece, new_geo, _("Delete segment(s)")
)
self._editor.history_manager.execute(command)

def add_geometry_provider_instance(
self,
Expand Down
2 changes: 2 additions & 0 deletions rayforge/doceditor/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from .group_cmd import GroupCmd
from .layer_cmd import LayerCmd
from .layout_cmd import LayoutCmd
from .node_edit_cmd import NodeEditCmd
from .path_cleanup_cmd import PathCleanupCmd
from .reload_cmd import ReloadCmd
from .split_cmd import SplitCmd
Expand Down Expand Up @@ -154,6 +155,7 @@ def __init__(
self.layer = LayerCmd(self)
self.layout = LayoutCmd(self, self.task_manager)
self.path_cleanup = PathCleanupCmd(self)
self.node_edit = NodeEditCmd(self)
self.reload = ReloadCmd(self)
self.split = SplitCmd(self)
self.stock = StockCmd(self)
Expand Down
Loading
Loading