class NoGoShape(Enum):
RECT = "rect" # axis-aligned 2D box (x, y, w, h)
CYLINDER = "cylinder" # 3D cylinder (x, y, z, radius, height)
BOX = "box" # 3D box (x, y, z, w, h, d)
class NoGoZone:
"""Follow the Laser/RotaryModule pattern: class with changed Signal,
setter methods, to_dict/from_dict, pickle support, extra dict."""
uid: str
name: str
shape: NoGoShape
params: Dict[str, float] # shape-specific dimensions
enabled: bool = True
color: Tuple[float, ...] = (1.0, 0.0, 0.0, 0.3)- 2D zones use
RECTwith{x, y, w, h}— checked against workpiece bounding boxes and toolpath XY coordinates. - 3D zones use
CYLINDERorBOX— additionally checked against Z travel (e.g. rotary chuck extends upward). - All coordinates in machine space (mm), within
axis_extents.
Add to Machine:
self.nogo_zones: Dict[str, NoGoZone] = {}
# Serialization keys in to_dict/from_dict:
# "nogo_zones": [...]Follow the same dict-by-uid pattern as rotary_modules.
Add optional fields to MachineProfile so preset machines can ship with
default no-go zones (e.g. Carvera Air knows where its rotary chuck sits).
Pure-logic class, no UI dependency:
class ZoneCheckResult:
zone: NoGoZone
severity: str # "warning" | "error"
message: str
intersection_type: str # "bbox" | "toolpath"
class ZoneChecker:
def __init__(self, machine: Machine):
self.machine = machine
def get_active_zones(self) -> List[NoGoZone]:
"""Returns enabled zones + rotary module volumes if configured."""
def check_bbox(self, bbox: Rect) -> List[ZoneCheckResult]:
"""Check a 2D bounding box against all 2D zones."""
def check_toolpath(self, ops: Ops) -> List[ZoneCheckResult]:
"""Check ops vertices against zones (2D + optional Z)."""
def check_rotary_wcs_alignment(self, layer: Layer) -> Optional[str]:
"""If layer is rotary, verify WCS points to rotary axis position."""Intersection logic: Reuse rayforge/core/geo/polygon.py
(polygons_intersect) for 2D. For 3D (Z-axis check against rotary module
height), a simple range check suffices.
| Check | Trigger | Location |
|---|---|---|
| BBox vs. zones | Workpiece moved/resized, zone changed | Pipeline WorkPiecePipelineStage or DocEditor command post-hook |
| Toolpath vs. zones | Pipeline produces Ops |
StepPipelineStage — after ops production, before encoding |
| Rotary WCS alignment | Layer rotary_enabled toggled, WCS changed |
Layer signal handler in DocEditor |
Results emitted via signal:
zone_warnings_changed = Signal() # sends List[ZoneCheckResult]The app has Toolbar.machine_warning_box for persistent warnings and
MainWindow._add_toast() for transient messages. Extend:
- Rotary WCS mismatch: persistent warning in toolbar. Clicking opens hardware page to fix WCS.
- Zone violation: toast with severity. For
severity="error", block job execution (disable "Generate G-code" button, like existingcan_export).
When a zone violation is active, the intersecting zone renderer pulses or turns
red. Follows how the jog widget uses add_css_class("warning") for limit
violations.
A collapsible Gtk.InfoBar above the canvas listing active zone violations.
More detailed than toasts, less intrusive than a dialog.
New NogoZonesPage in rayforge/ui_gtk/machine/, registered in the machine
settings dialog sidebar:
No-Go Zones
├── [ListBox] List of zones (name, shape, enabled toggle, delete button)
├── [Button] "Add Zone"
│ └── Dialog: choose shape → set dimensions & position
└── [SpinRow] per-zone: X, Y, W, H (rect) or X, Y, Z, Radius, Height (cylinder/box)
Follows PreferencesGroupWithButton + TrackedPreferencesPage pattern.
-
Machine-scoped: Zones belong to the physical machine, persist in its YAML config. Documents are portable across machines.
-
Coordinate space: All zone coordinates in machine space. Existing
MachineSpacetransforms handle conversion. -
3D-first collision: Full 3D collision detection from day one. 2D rect zones are degenerate 3D zones (Z height = 0), not a separate 2D system.
-
Reuse geometry:
polygons_intersect()fromcore/geo/polygon.pyfor XY-plane intersection. Simple range checks for Z-axis bounds. No new 3D geometry library needed. -
Non-blocking warnings: Zone violations warn but don't prevent design. Only block at job execution time.