Skip to content

Commit cc85dfd

Browse files
Merge pull request #9 from edanliahovetsky/codex/add-feature-to-visualize-robot-intake-and-protrusions
Add configurable robot protrusions to canvas perimeter rendering
2 parents cc8a05d + 53be673 commit cc85dfd

7 files changed

Lines changed: 115 additions & 12 deletions

File tree

ui/canvas/items/elements.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,17 @@ def __init__(
146146
self.index_in_model = index_in_model
147147
rw = getattr(self.canvas_view, "robot_length_m", 0.60)
148148
rh = getattr(self.canvas_view, "robot_width_m", 0.60)
149+
protrusion_front = max(0.0, float(getattr(self.canvas_view, "robot_protrusion_front_m", 0.0)))
150+
protrusion_back = max(0.0, float(getattr(self.canvas_view, "robot_protrusion_back_m", 0.0)))
151+
protrusion_left = max(0.0, float(getattr(self.canvas_view, "robot_protrusion_left_m", 0.0)))
152+
protrusion_right = max(0.0, float(getattr(self.canvas_view, "robot_protrusion_right_m", 0.0)))
149153
pen_width_m = OUTLINE_THICK_M if (outline_color and not dashed_outline) else OUTLINE_THIN_M
150154
inset = (pen_width_m if outline_color else 0.0) * 0.5
151-
self.setRect(-(rw / 2.0) + inset, -(rh / 2.0) + inset, rw - inset * 2, rh - inset * 2)
155+
x_min = -(rw / 2.0) - protrusion_back
156+
x_max = (rw / 2.0) + protrusion_front
157+
y_min = -(rh / 2.0) - protrusion_right
158+
y_max = (rh / 2.0) + protrusion_left
159+
self.setRect(x_min + inset, y_min + inset, (x_max - x_min) - inset * 2, (y_max - y_min) - inset * 2)
152160
self.setPos(self.canvas_view._scene_from_model(center_m.x(), center_m.y()))
153161
pen = QPen(outline_color or QColor("#000"), pen_width_m if outline_color else 0.0)
154162
if dashed_outline:
@@ -192,8 +200,9 @@ def __init__(
192200
self._angle_radians: float = 0.0
193201

194202
def _build_triangle(self, color: QColor):
195-
rw = getattr(self.canvas_view, "robot_length_m", 0.60)
196-
rh = getattr(self.canvas_view, "robot_width_m", 0.60)
203+
r = self.rect()
204+
rw = float(r.width())
205+
rh = float(r.height())
197206
base_size = min(rw, rh) * TRIANGLE_REL_SIZE
198207
half_base = base_size * 0.5
199208
height = base_size

ui/canvas/items/sim.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ def __init__(self, canvas_view: "CanvasView"):
2020
self.canvas_view = canvas_view
2121
robot_width_m = 0.5
2222
robot_length_m = 0.5
23+
protrusion_front_m = 0.0
24+
protrusion_back_m = 0.0
25+
protrusion_left_m = 0.0
26+
protrusion_right_m = 0.0
2327
try:
2428
if hasattr(canvas_view, "_project_manager") and canvas_view._project_manager:
2529
if hasattr(canvas_view._project_manager, "config_as_dict"):
@@ -28,21 +32,41 @@ def __init__(self, canvas_view: "CanvasView"):
2832
cfg = dict(getattr(canvas_view._project_manager, "config", {}) or {})
2933
robot_width_m = float(cfg.get("robot_width_meters", robot_width_m))
3034
robot_length_m = float(cfg.get("robot_length_meters", robot_length_m))
35+
protrusion_front_m = max(0.0, float(cfg.get("robot_protrusion_front_meters", protrusion_front_m)))
36+
protrusion_back_m = max(0.0, float(cfg.get("robot_protrusion_back_meters", protrusion_back_m)))
37+
protrusion_left_m = max(0.0, float(cfg.get("robot_protrusion_left_meters", protrusion_left_m)))
38+
protrusion_right_m = max(0.0, float(cfg.get("robot_protrusion_right_meters", protrusion_right_m)))
3139
except Exception:
3240
pass
33-
self.setRect(-robot_length_m / 2, -robot_width_m / 2, robot_length_m, robot_width_m)
41+
x_min = -(robot_length_m / 2.0) - protrusion_back_m
42+
x_max = (robot_length_m / 2.0) + protrusion_front_m
43+
y_min = -(robot_width_m / 2.0) - protrusion_right_m
44+
y_max = (robot_width_m / 2.0) + protrusion_left_m
45+
self.setRect(x_min, y_min, x_max - x_min, y_max - y_min)
3446
self.setBrush(QBrush(QColor(255, 165, 0, 120)))
3547
self.setPen(QPen(QColor("#000000"), 0.03))
3648
self.setZValue(15)
3749
self.setFlag(QGraphicsItem.ItemIsMovable, False)
3850
self.setFlag(QGraphicsItem.ItemIsSelectable, False)
3951
self.triangle_item = QGraphicsPolygonItem(self)
40-
self._build_triangle(robot_length_m, robot_width_m)
52+
self._build_triangle(float(self.rect().width()), float(self.rect().height()))
4153
self._angle_radians = 0.0
4254

43-
def set_dimensions(self, length_m: float, width_m: float):
44-
self.setRect(-length_m / 2.0, -width_m / 2.0, length_m, width_m)
45-
self._build_triangle(length_m, width_m)
55+
def set_dimensions(
56+
self,
57+
length_m: float,
58+
width_m: float,
59+
protrusion_front_m: float = 0.0,
60+
protrusion_back_m: float = 0.0,
61+
protrusion_left_m: float = 0.0,
62+
protrusion_right_m: float = 0.0,
63+
):
64+
x_min = -(length_m / 2.0) - max(0.0, protrusion_back_m)
65+
x_max = (length_m / 2.0) + max(0.0, protrusion_front_m)
66+
y_min = -(width_m / 2.0) - max(0.0, protrusion_right_m)
67+
y_max = (width_m / 2.0) + max(0.0, protrusion_left_m)
68+
self.setRect(x_min, y_min, x_max - x_min, y_max - y_min)
69+
self._build_triangle(float(self.rect().width()), float(self.rect().height()))
4670

4771
def _build_triangle(self, robot_length_m: float, robot_width_m: float):
4872
if not self.triangle_item:

ui/canvas/view.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ def __init__(self, parent=None):
105105
self._pan_start: Optional[QPoint] = None
106106
self.robot_length_m = ELEMENT_RECT_WIDTH_M
107107
self.robot_width_m = ELEMENT_RECT_HEIGHT_M
108+
self.robot_protrusion_front_m = 0.0
109+
self.robot_protrusion_back_m = 0.0
110+
self.robot_protrusion_left_m = 0.0
111+
self.robot_protrusion_right_m = 0.0
108112
self._field_offset: float = FIELD_OFFSET_M # 0.5m for 2026
109113
self.graphics_scene = QGraphicsScene(self)
110114
self.setScene(self.graphics_scene)
@@ -189,6 +193,16 @@ def set_robot_dimensions(self, length_m: float, width_m: float):
189193
try:
190194
self.robot_length_m = float(length_m)
191195
self.robot_width_m = float(width_m)
196+
cfg = {}
197+
if hasattr(self, "_project_manager") and self._project_manager:
198+
if hasattr(self._project_manager, "config_as_dict"):
199+
cfg = self._project_manager.config_as_dict()
200+
else:
201+
cfg = dict(getattr(self._project_manager, "config", {}) or {})
202+
self.robot_protrusion_front_m = max(0.0, float(cfg.get("robot_protrusion_front_meters", 0.0)))
203+
self.robot_protrusion_back_m = max(0.0, float(cfg.get("robot_protrusion_back_meters", 0.0)))
204+
self.robot_protrusion_left_m = max(0.0, float(cfg.get("robot_protrusion_left_meters", 0.0)))
205+
self.robot_protrusion_right_m = max(0.0, float(cfg.get("robot_protrusion_right_meters", 0.0)))
192206
except Exception:
193207
return
194208
self._rebuild_items()
@@ -197,7 +211,7 @@ def set_robot_dimensions(self, length_m: float, width_m: float):
197211
try:
198212
self._ensure_sim_robot_item()
199213
if self._sim_robot_item:
200-
self._sim_robot_item.set_dimensions(self.robot_length_m, self.robot_width_m)
214+
self._sim_robot_item.set_dimensions(self.robot_length_m, self.robot_width_m, self.robot_protrusion_front_m, self.robot_protrusion_back_m, self.robot_protrusion_left_m, self.robot_protrusion_right_m)
201215
except Exception:
202216
pass
203217

@@ -671,9 +685,23 @@ def _model_from_scene(self, x_s: float, y_s: float) -> Tuple[float, float]:
671685
"""
672686
return float(x_s - self._field_offset), float(FIELD_WIDTH_METERS - y_s - self._field_offset)
673687

688+
def _robot_half_extents(self) -> Tuple[float, float]:
689+
half_x = max(0.0, self.robot_length_m * 0.5 + self.robot_protrusion_front_m)
690+
half_x_back = max(0.0, self.robot_length_m * 0.5 + self.robot_protrusion_back_m)
691+
half_y = max(0.0, self.robot_width_m * 0.5 + self.robot_protrusion_left_m)
692+
half_y_right = max(0.0, self.robot_width_m * 0.5 + self.robot_protrusion_right_m)
693+
return max(half_x, half_x_back), max(half_y, half_y_right)
694+
674695
def _clamp_scene_coords(self, x_s: float, y_s: float) -> Tuple[float, float]:
675696
return max(0.0, min(x_s, FIELD_LENGTH_METERS)), max(0.0, min(y_s, FIELD_WIDTH_METERS))
676697

698+
def _clamp_scene_coords_with_robot_perimeter(self, x_s: float, y_s: float) -> Tuple[float, float]:
699+
hx, hy = self._robot_half_extents()
700+
return (
701+
max(hx, min(x_s, FIELD_LENGTH_METERS - hx)),
702+
max(hy, min(y_s, FIELD_WIDTH_METERS - hy)),
703+
)
704+
677705
def _constrain_scene_coords_for_index(
678706
self, index: int, x_s: float, y_s: float
679707
) -> Tuple[float, float]:
@@ -685,7 +713,7 @@ def _constrain_scene_coords_for_index(
685713
except Exception:
686714
return x_s, y_s
687715
if kind not in ("rotation", "event_trigger"):
688-
return x_s, y_s
716+
return self._clamp_scene_coords_with_robot_perimeter(x_s, y_s)
689717
prev_pos, next_pos = self._find_neighbor_item_positions(index)
690718
if prev_pos is None or next_pos is None:
691719
return x_s, y_s
@@ -700,7 +728,7 @@ def _constrain_scene_coords_for_index(
700728
t = max(0.0, min(1.0, t))
701729
proj_x = ax + t * dx
702730
proj_y = ay + t * dy
703-
return self._clamp_scene_coords(proj_x, proj_y)
731+
return self._clamp_scene_coords_with_robot_perimeter(proj_x, proj_y)
704732

705733
def _find_neighbor_item_positions(
706734
self, index: int
@@ -821,7 +849,7 @@ def _ensure_sim_robot_item(self):
821849
self._sim_robot_item = item
822850
item.setVisible(False)
823851
try:
824-
item.set_dimensions(self.robot_length_m, self.robot_width_m)
852+
item.set_dimensions(self.robot_length_m, self.robot_width_m, self.robot_protrusion_front_m, self.robot_protrusion_back_m, self.robot_protrusion_left_m, self.robot_protrusion_right_m)
825853
except Exception:
826854
pass
827855
except Exception:

ui/config_dialog.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,34 @@ def add_spin(
170170
(0.05, 5.0),
171171
0.01,
172172
)
173+
add_spin(
174+
"robot_protrusion_front_meters",
175+
"Robot Protrusion Front (m)",
176+
cfg.get("robot_protrusion_front_meters", 0.0) or 0.0,
177+
(0.0, 2.0),
178+
0.01,
179+
)
180+
add_spin(
181+
"robot_protrusion_back_meters",
182+
"Robot Protrusion Back (m)",
183+
cfg.get("robot_protrusion_back_meters", 0.0) or 0.0,
184+
(0.0, 2.0),
185+
0.01,
186+
)
187+
add_spin(
188+
"robot_protrusion_left_meters",
189+
"Robot Protrusion Left (m)",
190+
cfg.get("robot_protrusion_left_meters", 0.0) or 0.0,
191+
(0.0, 2.0),
192+
0.01,
193+
)
194+
add_spin(
195+
"robot_protrusion_right_meters",
196+
"Robot Protrusion Right (m)",
197+
cfg.get("robot_protrusion_right_meters", 0.0) or 0.0,
198+
(0.0, 2.0),
199+
0.01,
200+
)
173201

174202
# Optional defaults
175203
add_spin(

ui/main_window/window.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,10 @@ def _get_config_key_label(self, key: str) -> str:
606606
labels = {
607607
"robot_length_meters": "Robot Length",
608608
"robot_width_meters": "Robot Width",
609+
"robot_protrusion_front_meters": "Robot Protrusion Front",
610+
"robot_protrusion_back_meters": "Robot Protrusion Back",
611+
"robot_protrusion_left_meters": "Robot Protrusion Left",
612+
"robot_protrusion_right_meters": "Robot Protrusion Right",
609613
"max_velocity_meters_per_sec": "Default Max Velocity",
610614
"max_acceleration_meters_per_sec2": "Default Max Accel",
611615
"intermediate_handoff_radius_meters": "Default Handoff Radius",

ui/sidebar/components/element_manager.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ def get_robot_dimensions(self) -> Tuple[float, float]:
101101
cfg = dict(getattr(self.project_manager, "config", {}) or {})
102102
length_m = float(cfg.get("robot_length_meters", length_m))
103103
width_m = float(cfg.get("robot_width_meters", width_m))
104+
length_m += max(0.0, float(cfg.get("robot_protrusion_front_meters", 0.0))) + max(
105+
0.0, float(cfg.get("robot_protrusion_back_meters", 0.0))
106+
)
107+
width_m += max(0.0, float(cfg.get("robot_protrusion_left_meters", 0.0))) + max(
108+
0.0, float(cfg.get("robot_protrusion_right_meters", 0.0))
109+
)
104110
except Exception:
105111
pass
106112
return length_m, width_m

utils/project_manager.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
class ProjectConfig:
1616
robot_length_meters: float = 0.5
1717
robot_width_meters: float = 0.5
18+
robot_protrusion_front_meters: float = 0.0
19+
robot_protrusion_back_meters: float = 0.0
20+
robot_protrusion_left_meters: float = 0.0
21+
robot_protrusion_right_meters: float = 0.0
1822
default_max_velocity_meters_per_sec: float = 4.5
1923
default_max_acceleration_meters_per_sec2: float = 7.0
2024
default_intermediate_handoff_radius_meters: float = 0.2

0 commit comments

Comments
 (0)