Лабораторна робота 12
Лабораторна робота №12. Палетизація в DobotStudio Pro
Мета: навчитися параметрично описувати палету для майбутньої програми DobotStudio Pro; генерувати рядки, стовпці, крок і послідовність комірок; будувати офлайн-план «підхід — укладання — відхід», контролювати заповнення та безпечно блокувати цикл при помилці без підключення до MG400.
Результати навчання та передумови
Після виконання студент уміє:
- представляти палету базовою позою, кількістю рядків/стовпців і кроками;
- виводити координати комірок формулою, а не вручну дублювати точки;
- генерувати порядок row-major або serpentine та пояснювати його;
- перевіряти унікальність, кількість і входження всіх поз у synthetic envelope;
- додавати окремі точки approach/place/retract;
- вести стан
EMPTY → PLANNED → PLACEDдля кожної комірки; - припиняти offline-цикл при пропуску/повторі комірки та не відновлювати його автоматично;
- готувати worksheet для DobotStudio Pro без перенесення синтетичних координат у робот.
Передумови: успішна ЛР-11, Python, декартові координати, цикли/списки та правила безпеки MERC-I5. Фізичний рух і підключення не входять до цієї редакції.
Середовище виконання
| Середовище | Статус | Примітка |
|---|---|---|
| Google Colab | дозволено | In-memory генератор і тести зі стандартною бібліотекою |
| Локальний ПК | основне | Python; DobotStudio Pro — лише offline після звірки версії, без connect |
| Raspberry Pi | дозволено | Тільки mock, без мережі й hardware I/O |
| Фізичний комплекс MERC-I5 | не потрібен | Палета, грипер і MG400 не активуються |
Необхідні знання та матеріали
- ЛР-11, програма ЛР-12 та довідник MG400;
- перелік непідтверджених координат, правила експлуатації і перевірка без руху;
- Python зі стандартними
json,csv,io,math,dataclasses,pathlib; - зовнішній синтетичний JSON-профіль з цієї роботи;
- вимірювальні дані реальної палети не потрібні й не використовуються.
- Палетизаційне поле
Ризики та правила безпеки
Ризики
| Небезпека | Наслідок | Захист у роботі |
|---|---|---|
| Невірний знак/вісь кроку | точки виходять за палету або перешкоду | синтетичний envelope, таблиця й унікальність усіх комірок |
| Ручне дублювання точок | пропуск або повтор комірки | параметричний генератор і перевірка count |
| Низька точка approach | зіткнення з деталями | окремий clearance у synthetic profile |
| Переповнена палета | укладання поверх зайнятої комірки | стан комірки та блокування повтору |
| Автовідновлення після stop | невідоме положення деталі | BLOCKED; тільки нова звірка, без auto-resume |
Заборонені дії
- підключати DobotStudio Pro до MG400, активувати робот або запускати програму;
- вводити synthetic
base/pitch/clearanceу фізичний контролер; - надсилати рухові, gripper або I/O команди;
- навчати точки вручну, торкатися робочої зони чи переміщувати палету;
- змінювати User/Tool/TCP, скидати помилки або автоматично продовжувати після stop;
- вважати просту геометричну перевірку доказом відсутності зіткнень.
Умови негайної зупинки
Точка поза envelope, дубльована комірка, неправильна кількість, недостатній clearance, неузгоджені одиниці/frame, повторне укладання, пропущений індекс, невідома версія профілю. Для майбутнього стенда додатково діють усі умови з docs/06: людина/предмет у зоні, невідома поза, втрата зв'язку, падіння деталі, незвичний звук/нагрів.
Безпечний стан
BLOCKED, connected=false, жодної robot command, усі невиконані комірки лишаються EMPTY/PLANNED, останній підтверджений індекс збережено лише в журналі. Фізична деталь не вважається розміщеною на підставі offline-моделі.
Передпусковий чекліст
- Конфіг має
SYNTHETIC_PALLET_OFFLINE_ONLY. - Немає IP, реальних User/Tool/TCP і апаратних команд.
-
rows × columnsзбігається з кількістю унікальних комірок. - Для кожної комірки перевірено approach/place/retract.
- Негативні та граничні тести проходять.
- Добот і грипер фізично не залучені.
Хід виконання роботи
- Задати параметричну модель і зовнішній synthetic profile.
- Згенерувати комірки та offline-траєкторії.
- Перевірити порядок, унікальність і межі.
- Перевірити заповнення, stop і негативні сценарії.
- Створити in-memory worksheet для майбутньої адаптації.
- Заповнити метрики, звіт і перелік блокерів.
Методичні вказівки й теоретичні відомості
1. Параметрична палета
Для базової пози P₀=(x₀,y₀,z₀,r₀), рядка i і стовпця j проста прямокутна модель має вигляд:
P(i,j) = (x₀ + j·pitch_x, y₀ + i·pitch_y, z₀, r₀)
Формула діє лише у визначеній User frame та для ортогональної рівної палети. Поворот, нахил або деформація потребують каліброваного перетворення; їх не можна приховати зміною одного pitch.
2. Порядок і стани
row_major проходить кожен рядок в одному напрямку. serpentine змінює напрямок через рядок і може скорочувати offline довжину переходів, але не гарантує безпечність фізичної траєкторії.
| Стан комірки | Значення |
|---|---|
EMPTY | не запланована/не підтверджена як зайнята |
PLANNED | наступна в offline-послідовності |
PLACED | лише mock підтвердив крок; не факт фізичного стенда |
BLOCKED | цикл припинено через розбіжність |
3. Потік палетизації
Текстовий опис рисунка: генератор читає зовнішню геометрію, створює унікальні комірки й для кожної формує approach/place/retract; після символічного підтвердження комірка стає PLACED, а будь-який повтор, пропуск, вихід за envelope чи stop переводить весь offline-цикл у BLOCKED без автоматичного продовження.
flowchart LR
A["Synthetic pallet config"] --> B["Generate unique cells"]
B --> C["Validate envelope + clearance"]
C --> D["Approach"]
D --> E["Symbolic place"]
E --> F["Retract"]
F --> G{"Cells remain?"}
G -->|"yes"| D
G -->|"no"| H["OFFLINE_COMPLETE"]
B -->|"duplicate/invalid"| X["BLOCKED"]
C -->|"out of bounds"| X
Рис. 1. Ілюстративний offline-автомат параметричної палетизації
4. Конфігурація поза кодом
Файл pallet-studio.synthetic.json:
{
"profile": "SYNTHETIC_PALLET_OFFLINE_ONLY",
"units": "mm",
"frame": "SYNTHETIC_PALLET",
"rows": 2,
"columns": 3,
"pitch_x_mm": 20.0,
"pitch_y_mm": 25.0,
"clearance_mm": 30.0,
"order": "serpentine",
"base_pose": [-30.0, -20.0, 10.0, 0.0],
"envelope_mm": {
"x": [-100.0, 100.0],
"y": [-100.0, 100.0],
"z": [0.0, 120.0],
"r_deg": [-180.0, 180.0]
}
}
Числа — лише synthetic fixture. Runtime читає зовнішній файл; safe-smoke перевіряє його JSON-представлення в пам'яті без файлового запису.
5. Першоджерела
- Офіційна сторінка MG400 та DobotStudio Pro User Guide.
- Shenzhen Yuejiang Technology Co., Ltd.,
Dobot MG400 User Guide, Issue V1.7, 2023-11-16; локальний довідник координат. - Базова лінія DobotStudio Pro — рекомендована версія та межі підтвердження.
- Python:
csv, Python:json— in-memory worksheet і конфігурація.
Виконання лабораторної роботи
Крок 1. Перевірити synthetic profile
Збережіть JSON у своєму дозволеному workspace для навчального runtime або використайте його текстове представлення в safe-smoke. Не замінюйте prefix SYNTHETIC.
Expected cells = rows × columns = 2 × 3 = 6
Physical import = forbidden
Connection = none
Очікуваний результат: schema містить тільки геометрію mock і не містить IP/реальних frame.
Критерій правильності: усі ключі відомі, типи/межі валідні, profile позначений offline.
Якщо результат не отримано: не виправляйте невідомий ключ мовчки; відхиліть конфіг і звірте schema.
Крок 2. Запустити повний генератор і mock-цикл
Збережіть як lab12_pallet_offline.py.
# safe-smoke
from __future__ import annotations
import csv
import io
import json
import math
from dataclasses import dataclass
from pathlib import Path
class PalletError(ValueError):
pass
@dataclass(frozen=True)
class Pose:
x: float
y: float
z: float
r: float
@dataclass(frozen=True)
class Cell:
index: int
row: int
column: int
place: Pose
approach: Pose
@dataclass(frozen=True)
class PalletConfig:
profile: str
rows: int
columns: int
pitch_x: float
pitch_y: float
clearance: float
order: str
base: Pose
envelope: dict[str, tuple[float, float]]
def _pose(value: object) -> Pose:
if not isinstance(value, list) or len(value) != 4:
raise PalletError("base_pose має містити 4 числа")
if not all(isinstance(item, (int, float)) and math.isfinite(item) for item in value):
raise PalletError("pose має бути скінченною")
return Pose(*(float(item) for item in value))
def parse_config_text(text: str) -> PalletConfig:
raw = json.loads(text)
expected = {"profile", "units", "frame", "rows", "columns", "pitch_x_mm", "pitch_y_mm", "clearance_mm", "order", "base_pose", "envelope_mm"}
if not isinstance(raw, dict) or set(raw) != expected:
raise PalletError("неправильний набір ключів")
if raw["profile"] != "SYNTHETIC_PALLET_OFFLINE_ONLY" or raw["units"] != "mm" or raw["frame"] != "SYNTHETIC_PALLET":
raise PalletError("дозволено лише synthetic offline profile")
if not isinstance(raw["rows"], int) or not isinstance(raw["columns"], int):
raise PalletError("rows/columns мають бути цілими")
if not 1 <= raw["rows"] <= 20 or not 1 <= raw["columns"] <= 20:
raise PalletError("rows/columns поза навчальною межею")
for key in ("pitch_x_mm", "pitch_y_mm", "clearance_mm"):
if not isinstance(raw[key], (int, float)) or not math.isfinite(raw[key]) or raw[key] <= 0:
raise PalletError(f"{key} має бути додатним")
if raw["order"] not in {"row_major", "serpentine"}:
raise PalletError("невідомий порядок")
axes = {"x", "y", "z", "r_deg"}
if not isinstance(raw["envelope_mm"], dict) or set(raw["envelope_mm"]) != axes:
raise PalletError("неповний envelope")
envelope: dict[str, tuple[float, float]] = {}
for axis, limits in raw["envelope_mm"].items():
if not isinstance(limits, list) or len(limits) != 2 or limits[0] >= limits[1]:
raise PalletError(f"невалідна межа {axis}")
envelope[axis] = (float(limits[0]), float(limits[1]))
return PalletConfig(raw["profile"], raw["rows"], raw["columns"], float(raw["pitch_x_mm"]), float(raw["pitch_y_mm"]), float(raw["clearance_mm"]), raw["order"], _pose(raw["base_pose"]), envelope)
def load_config(path: Path) -> PalletConfig:
return parse_config_text(path.read_text(encoding="utf-8"))
def check_pose(pose: Pose, config: PalletConfig) -> None:
values = {"x": pose.x, "y": pose.y, "z": pose.z, "r_deg": pose.r}
for axis, value in values.items():
low, high = config.envelope[axis]
if not low <= value <= high:
raise PalletError(f"{axis} поза synthetic envelope")
def generate_cells(config: PalletConfig) -> list[Cell]:
cells: list[Cell] = []
index = 0
for row in range(config.rows):
columns = list(range(config.columns))
if config.order == "serpentine" and row % 2 == 1:
columns.reverse()
for column in columns:
place = Pose(config.base.x + column * config.pitch_x, config.base.y + row * config.pitch_y, config.base.z, config.base.r)
approach = Pose(place.x, place.y, place.z + config.clearance, place.r)
check_pose(place, config)
check_pose(approach, config)
cells.append(Cell(index, row, column, place, approach))
index += 1
keys = {(cell.row, cell.column) for cell in cells}
poses = {(cell.place.x, cell.place.y, cell.place.z, cell.place.r) for cell in cells}
if len(cells) != config.rows * config.columns or len(keys) != len(cells) or len(poses) != len(cells):
raise PalletError("кількість або унікальність комірок порушена")
return cells
class OfflineCycle:
def __init__(self, cells: list[Cell]) -> None:
self.cells = cells
self.next_index = 0
self.placed: set[int] = set()
self.blocked_reason: str | None = None
def next_cell(self) -> Cell | None:
if self.blocked_reason:
raise PalletError("цикл BLOCKED")
return self.cells[self.next_index] if self.next_index < len(self.cells) else None
def confirm_mock_place(self, index: int) -> None:
if self.blocked_reason:
raise PalletError("цикл BLOCKED")
if index != self.next_index or index in self.placed:
self.blocked_reason = "пропуск або повтор комірки"
raise PalletError(self.blocked_reason)
self.placed.add(index)
self.next_index += 1
def stop(self, reason: str) -> None:
self.blocked_reason = reason
def worksheet_csv(cells: list[Cell]) -> str:
stream = io.StringIO(newline="")
writer = csv.writer(stream)
writer.writerow(["index", "row", "column", "x_synth", "y_synth", "z_synth", "approach_z_synth"])
for cell in cells:
writer.writerow([cell.index, cell.row, cell.column, cell.place.x, cell.place.y, cell.place.z, cell.approach.z])
return stream.getvalue()
fixture = {
"profile": "SYNTHETIC_PALLET_OFFLINE_ONLY",
"units": "mm",
"frame": "SYNTHETIC_PALLET",
"rows": 2,
"columns": 3,
"pitch_x_mm": 20.0,
"pitch_y_mm": 25.0,
"clearance_mm": 30.0,
"order": "serpentine",
"base_pose": [-30.0, -20.0, 10.0, 0.0],
"envelope_mm": {"x": [-100.0, 100.0], "y": [-100.0, 100.0], "z": [0.0, 120.0], "r_deg": [-180.0, 180.0]},
}
config = parse_config_text(json.dumps(fixture))
cells = generate_cells(config)
assert [(c.row, c.column) for c in cells] == [(0, 0), (0, 1), (0, 2), (1, 2), (1, 1), (1, 0)]
cycle = OfflineCycle(cells)
while (cell := cycle.next_cell()) is not None:
cycle.confirm_mock_place(cell.index)
assert len(cycle.placed) == 6 and cycle.next_cell() is None
bad_cycle = OfflineCycle(cells)
try:
bad_cycle.confirm_mock_place(1)
except PalletError as exc:
assert "пропуск" in str(exc)
else:
raise AssertionError("пропуск індексу має блокувати цикл")
outside = dict(fixture)
outside["base_pose"] = [100.0, -20.0, 10.0, 0.0]
try:
generate_cells(parse_config_text(json.dumps(outside)))
except PalletError as exc:
assert "envelope" in str(exc)
else:
raise AssertionError("поза envelope має бути відхилена")
csv_text = worksheet_csv(cells)
assert len(csv_text.splitlines()) == 7 and "x_synth" in csv_text
print(json.dumps({"result": "PASS", "cells": 6, "order": "serpentine", "connected": False}, ensure_ascii=False))
python -m py_compile lab12_pallet_offline.py
python lab12_pallet_offline.py
Очікуваний результат: result=PASS, cells=6, order=serpentine, connected=false.
Критерій правильності: код повертає 0, не пише файлів у smoke, не використовує мережу/робот і блокує пропуск індексу.
Якщо результат не отримано: не відкривайте connection; перевірте JSON schema, індексацію й envelope.
Крок 3. Перевірити порядок і геометрію
Побудуйте таблицю шести (row,column) і переконайтеся, що другий рядок serpentine іде 2,1,0. Обчисліть координати вручну для двох комірок.
row 0: (0,0) -> (0,1) -> (0,2)
row 1: (1,2) -> (1,1) -> (1,0)
Очікуваний результат: кожна пара зустрічається рівно один раз; place та approach відрізняються лише z.
Критерій правильності: 6 унікальних індексів, 6 унікальних пар і 6 унікальних place poses.
Якщо результат не отримано: зупиніть цикл; не «виправляйте» дубль зміною координати вручну — виправте формулу/config.
Крок 4. Перевірити заповнення й safe stop
Проаналізуйте OfflineCycle: підтвердження дозволено лише для next_index. Додайте тест stop("mock camera unavailable"), після якого next_cell() має завершитися помилкою.
python lab12_pallet_offline.py
Очікуваний результат: штатний цикл завершує 6 комірок; пропуск або stop створює BLOCKED.
Критерій правильності: після block немає автоматичного переходу до наступної комірки.
Якщо результат не отримано: зробіть blocked_reason незворотним у поточному екземплярі й вимагайте нової звірки/нового циклу.
Крок 5. Перевірити негативні й граничні сценарії
Додайте тести: rows=0, columns=21, pitch=0, approach за z_max, base на межі, невідомий order, зайвий ключ, NaN.
Гранична точка == envelope limit: дозволено
Точка > limit на будь-яку величину: BLOCKED
Автоматичний clipping: заборонено
Очікуваний результат: невалідні profile-и відхиляються до генерації worksheet.
Критерій правильності: усі негативні тести завершуються PalletError, а valid boundary проходить.
Якщо результат не отримано: посиліть loader/check_pose; не розширюйте envelope, щоб приховати помилку.
Крок 6. Підготувати worksheet і журнал
Перегляньте csv_text у пам'яті. Для студентського звіту можна зберегти його лише у власному дозволеному workspace; automatic smoke файл не створює.
{
"mode": "offline",
"profile": "SYNTHETIC_PALLET_OFFLINE_ONLY",
"cells_generated": 6,
"cells_physically_placed": 0,
"connected": false
}
Очікуваний результат: worksheet має header + 6 рядків і synthetic labels.
Критерій правильності: у журналі фізично розміщених деталей 0, відсутні IP/секрети.
Якщо результат не отримано: не публікуйте двозначну таблицю; додайте suffix _synth і явний status offline.
Сценарії перевірки
| Тип | Сценарій | Очікування |
|---|---|---|
| позитивний | 2×3 serpentine | 6 унікальних cells, complete |
| позитивний | row-major | порядок без реверсу |
| негативний | пропуск index 0 | BLOCKED |
| негативний | повтор cell | BLOCKED |
| негативний | approach поза z envelope | PalletError |
| граничний | 1×1 | одна валідна cell |
| граничний | точка на envelope limit | прийнято; за межею — ні |
Таблиці вимірювань і метрики
| Index | Row | Column | X synth | Y synth | Z place synth | Z approach synth | Унікальна | У межах | Статус mock |
|---|---|---|---|---|---|---|---|---|---|
| 0 | |||||||||
| … | |||||||||
| 5 |
Метрики: generated == rows×columns; 100% унікальність; 100% poses/approaches у synthetic envelope; 0 пропусків/повторів; 0 robot commands; 0 connection; 100% негативних тестів відхилено.
[!WARNING] [ПОТРЕБУЄ ДОПРАЦЮВАННЯ: ФІЗИЧНА АПРОБАЦІЯ ПАЛЕТИЗАЦІЇ В DOBOTSTUDIO PRO] Не підтверджено: фактичне навчання бази палети, точність усіх комірок, траєкторії, collision clearance, захоплення/укладання, контроль заповнення, stop і відновлення на MERC-I5; фізична апробація не дозволена. Що потрібно додати: затверджений фактичний profile, вимірювальний протокол, безпечні швидкості/прискорення, дозволений тестовий вантаж, оцінку ризику, локального оператора, перевірений E-Stop і журнал першого запуску. Як завершити: пройти допуск, виконати read-only review і візуалізацію всіх точок, потім покроково без вантажу та з безпечним test piece на мінімальній затвердженій швидкості; перевірити кожну комірку, stop без auto-resume та журнал; вилучити block лише після повного протоколу.
Вимоги до звіту
Див. загальні вимоги. Додатково подайте:
- формулу палети й пояснення order;
- зовнішній synthetic JSON та повний код;
- таблицю всіх комірок/approaches і in-memory CSV;
- результати позитивних, негативних і граничних тестів;
- журнал із
cells_physically_placed=0іconnected=false; - перелік даних для фактичного DobotStudio profile;
- висновок про межі геометричної offline-перевірки.
Критерії оцінювання
| Складова | Частка | Критерії |
|---|---|---|
| Підготовка | 10% | джерела, safety, external synthetic config, відсутність фактичних припущень |
| Реалізація | 40% | параметричний генератор, order, approach/retract, state/progress і worksheet |
| Перевірка | 25% | count/unique/envelope, позитивні/негативні/граничні тести, code 0 |
| Аналіз | 15% | геометрія, frame, collision limits, stop/recovery і метрики |
| Звіт | 10% | повні таблиці, журнал, джерела й чесний статус фізичної частини |
| Разом | 100% |
Підключення, рух або перенесення synthetic координат у контролер є критичною помилкою.
Контрольні питання
- Які параметри однозначно задають прямокутну палету?
- Чим serpentine відрізняється від row-major?
- Чому однаковий
zне доводить площинність реальної палети? - Навіщо перевіряти approach окремо від place?
- Як захиститися від повторного укладання?
- Чому
BLOCKEDне можна автоматично скинути після перезапуску? - Які дані потрібні для User/TCP profile?
- Чому offline-візуалізація не доводить collision safety?
Висновки
ЛР-12 створює відтворювану параметричну модель палети, строгий генератор, контроль заповнення, in-memory worksheet і негативні тести. Уся геометрія явно synthetic, зовнішня й не переноситься до MG400. Фактичний DobotStudio workflow, координати та рух залишаються заблокованими до інвентаризації, калібрування й операторської апробації.