Skip to content
Draft
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
5 changes: 4 additions & 1 deletion klippy/kinematics/cartesian.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
from klippy import stepper
from . import idex_modes

from .forbidden_zones import ForbiddenZones

class CartKinematics:
def __init__(self, toolhead, config):
Expand All @@ -24,6 +24,8 @@ def __init__(self, toolhead, config):
self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.0)
self.dc_module = None
self.supports_dual_carriage = True
self.forbidden_zones = ForbiddenZones(config)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should move in to the toolhead so all kinematics benefit. Toolhead calls check_move, so the forbidden_zones check_move can go right before that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, i'm on it.


if config.has_section("dual_carriage"):
dc_config = config.getsection("dual_carriage")
dc_axis = dc_config.getchoice("axis", ["x", "y"])
Expand Down Expand Up @@ -144,6 +146,7 @@ def check_move(self, move):
or ypos > limits[1][1]
):
self._check_endstops(move)
self.forbidden_zones.check_move(move)
if not move.axes_d[2]:
# Normal XY move - use defaults
return
Expand Down
35 changes: 35 additions & 0 deletions klippy/kinematics/forbidden_zones.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Code for handling forbidden zones of movements
#
# Copyright (C) 2025 Daniel Berlin <dberlin@dberlin.org>
#
# This file may be distributed under the terms of the GNU GPLv3 license.

from shapely import from_wkt, STRtree, LineString
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be lazy-loaded if config.has_section("forbidden_zones") passes. Otherwise, all existing machines are broken until they update dependencies, which I think is a bit extreme. The way LoadCellPrinterProbe does this is fine, as a reference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

import logging

class ForbiddenZones:
# On init we just read in the shapes and prepare the STRtree
def __init__(self, config):
self.forbidden_zone_tree = None
if config.has_section("forbidden_zones"):
section = config.getsection("forbidden_zones")
forbidden_shapes_wkt = section.getlists("shapes", seps=("\n"))
logging.info(f"Forbidden zone shapes (WKT):{forbidden_shapes_wkt}")
forbidden_shapes_wkt = [shape.strip() for shape in forbidden_shapes_wkt if len(shape.strip()) != 0]
forbidden_shapes = [from_wkt(shape) for shape in forbidden_shapes_wkt]
logging.info(f"Forbidden zone shapes (parsed):{forbidden_shapes}")
self.forbidden_zone_tree = STRtree(forbidden_shapes)

# Check whether a proposed line move intersects any forbidden zone shapes
# Touching the shape counts (So a point that is on the border of a shape is not allowed)
def check_move(self, move):
if self.forbidden_zone_tree:
# Transform the move into a shapely LineString, get candidates from the STRtree
# and check each candidate for intersection
x_y_line = LineString([move.start_pos[:2], move.end_pos[:2]])
candidates = self.forbidden_zone_tree.query(x_y_line, predicate='intersects')
if len(candidates) > 0:
for geom_index in candidates:
geom = self.forbidden_zone_tree.geometries.take(geom_index)
if x_y_line.intersects(geom):
raise move.move_error(f"Move line {x_y_line} intersects forbidden zone shape {geom}")
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ dependencies = [
"numpy~=2.2 ; python_version>='3.10'",
"pyserial==3.4",
"python-can~=4.5",
"setuptools==75.8.2 ; python_version >= '3.12'", # required by python-can < 4.3
"setuptools==75.8.2 ; python_version >= '3.12'", # required by python-can < 4.3
"shapely>=2.0.7",
]

[tool.uv]
Expand Down
Loading