Right-click a feature to break its connection
The Joinery tab feature list now has a context menu: right-click a mortise/tenon to "Break this connection" (only shown when it's connected) or "Delete feature". controller.break_feature_connection breaks just the connection(s) that feature is part of, in one undo step. 86 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5e8a1c7926
commit
3e7375344e
|
|
@ -301,6 +301,23 @@ class Controller(QObject):
|
|||
self._do(lambda: self.scene.disconnect(part=part_id) if part_id
|
||||
else self.scene.disconnect())
|
||||
|
||||
def feature_connection_ids(self, fid: str) -> list[str]:
|
||||
return [c.id for c in self.scene.connections if fid in (c.anchor, c.moving)]
|
||||
|
||||
def break_feature_connection(self, fid: str) -> None:
|
||||
"""Break the connection(s) that this specific feature is part of."""
|
||||
cids = self.feature_connection_ids(fid)
|
||||
if not cids:
|
||||
return
|
||||
|
||||
def op():
|
||||
with self.scene.batch():
|
||||
for cid in cids:
|
||||
self.scene.disconnect(cid=cid)
|
||||
return f"Broke {len(cids)} connection(s) on {fid}."
|
||||
|
||||
self._do(op)
|
||||
|
||||
def groups(self) -> list[list[str]]:
|
||||
return self.scene.groups()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from __future__ import annotations
|
|||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (QCheckBox, QComboBox, QDialog, QDialogButtonBox,
|
||||
QDoubleSpinBox, QFormLayout, QGridLayout, QHBoxLayout,
|
||||
QLabel, QListWidget, QListWidgetItem, QMessageBox,
|
||||
QPushButton, QVBoxLayout, QWidget)
|
||||
QLabel, QListWidget, QListWidgetItem, QMenu,
|
||||
QMessageBox, QPushButton, QVBoxLayout, QWidget)
|
||||
|
||||
from ..scene import FACES
|
||||
from .controller import Controller
|
||||
|
|
@ -33,6 +33,8 @@ class FeaturePanel(QWidget):
|
|||
|
||||
self.list = QListWidget()
|
||||
self.list.itemSelectionChanged.connect(self._on_row)
|
||||
self.list.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.list.customContextMenuRequested.connect(self._feat_menu)
|
||||
root.addWidget(self.list, 1)
|
||||
|
||||
self.hint = QLabel("")
|
||||
|
|
@ -136,6 +138,19 @@ class FeaturePanel(QWidget):
|
|||
if items:
|
||||
self.c.select_feature(items[0].data(Qt.UserRole))
|
||||
|
||||
def _feat_menu(self, pos) -> None:
|
||||
item = self.list.itemAt(pos)
|
||||
if not item:
|
||||
return
|
||||
fid = item.data(Qt.UserRole)
|
||||
self.c.select_feature(fid)
|
||||
menu = QMenu(self)
|
||||
if self.c.feature_connection_ids(fid):
|
||||
menu.addAction("Break this connection",
|
||||
lambda: self.c.break_feature_connection(fid))
|
||||
menu.addAction("Delete feature", self.c.delete_active_feature)
|
||||
menu.exec(self.list.viewport().mapToGlobal(pos))
|
||||
|
||||
def _preview(self) -> None:
|
||||
"""Live: show a red ghost of the pending values (no commit until Apply)."""
|
||||
if self._loading or not self.c.active_feature:
|
||||
|
|
|
|||
|
|
@ -124,6 +124,17 @@ def test_highlight_feature(tmp_path):
|
|||
assert c.preview is None
|
||||
|
||||
|
||||
def test_break_feature_connection(tmp_path):
|
||||
c = _controller(tmp_path)
|
||||
c.place("2x4", 24); c.add_feature("mortise") # f1 on p1
|
||||
c.place("2x4", 12); c.add_feature("tenon") # f2 on p2
|
||||
c.scene.connect("f1", "f2")
|
||||
assert c.feature_connection_ids("f1") == ["c1"]
|
||||
c.break_feature_connection("f1")
|
||||
assert c.scene.connections == []
|
||||
assert c.feature_connection_ids("f1") == []
|
||||
|
||||
|
||||
def test_unknown_tool_is_safe(tmp_path):
|
||||
c = _controller(tmp_path)
|
||||
assert "unknown" in c.execute_call("wood-bogus", {}).lower()
|
||||
|
|
|
|||
Loading…
Reference in New Issue