Загрузка данных
import math
import re
class CNCPlannerX:
def __init__(self, accel=150.0, min_speed=2.0, junction_deviation=0.02):
self.accel = accel # Системное ускорение (мм/сек^2)
self.min_speed = min_speed # Стартовая скорость осей (мм/сек)
self.junction_deviation = junction_deviation # Константа жесткости станины (мм)
def parse_and_plan(self, gcode_lines):
# 1. Сборка модальных координат и линеаризация очереди
segments = []
curr_x, curr_y, curr_z = 0.0, 0.0, 0.0
curr_f = 600.0 # Подача по умолчанию (мм/мин)
for idx, line in enumerate(gcode_lines):
line = line.strip().split(';')[0].upper() # Убираем комментарии G-кода
if not line:
continue
# Извлекаем параметры регулярными выражениями
g_match = re.search(r'G([0-1])', line)
m_match = re.search(r'M([0-9]+)', line)
x_match = re.search(r'X([-+]?[0-9]*\.?[0-9]+)', line)
y_match = re.search(r'Y([-+]?[0-9]*\.?[0-9]+)', line)
z_match = re.search(r'Z([-+]?[0-9]*\.?[0-9]+)', line)
f_match = re.search(r'F([0-9]*\.?[0-9]+)', line)
# Подстановка модальных значений
cmd_type = 1 if g_match else (3 if m_match else 1)
line_num = idx + 1
if f_match: curr_f = float(f_match.group(1))
target_x = float(x_match.group(1)) if x_match else curr_x
target_y = float(y_match.group(1)) if y_match else curr_y
target_z = float(z_match.group(1)) if z_match else curr_z
# Вычисляем дельты перемещения для текущего кадра
dx = target_x - curr_x
dy = target_y - curr_y
dz = target_z - curr_z
length = math.sqrt(dx*dx + dy*dy + dz*dz)
segments.append({
'type': cmd_type, 'line': line_num,
'x': target_x, 'y': target_y, 'z': target_z,
'f_min': curr_f, 'f_sec': curr_f / 60.0, # переводим подачу в мм/сек
'dx': dx, 'dy': dy, 'dz': dz, 'length': length,
'v_max': curr_f / 60.0, # Ограничение скорости сверху подачей кадра
'v_start': self.min_speed, # Будет пересчитано Look-Ahead ниже
'v_end': self.min_speed # Будет пересчитано Look-Ahead ниже
})
# Сдвигаем текущую точку
curr_x, curr_y, curr_z = target_x, target_y, target_z
# ====================================================================
# 2. АЛГОРИТМ LOOK-AHEAD: РАСЧЕТ СКОРОСТЕЙ НА СТЫКАХ УГЛОВ (Junction Deviation)
# ====================================================================
num_segs = len(segments)
for i in range(num_segs - 1):
seg_curr = segments[i]
seg_next = segments[i+1]
# Если один из смежных отрезков пустой, скорость стыка равна минимальной
if seg_curr['length'] < 0.001 or seg_next['length'] < 0.001:
v_junction = self.min_speed
else:
# Находим косинус угла излома траектории между векторами
cos_theta = (seg_curr['dx']*seg_next['dx'] +
seg_curr['dy']*seg_next['dy'] +
seg_curr['dz']*seg_next['dz']) / (seg_curr['length'] * seg_next['length'])
# Защита от вычислительной погрешности float в Python
cos_theta = max(-1.0, min(1.0, cos_theta))
if cos_theta > 0.99999:
# Линия абсолютно прямая — тормозить на стыке векторов не нужно
v_junction = min(seg_curr['v_max'], seg_next['v_max'])
elif cos_theta < -0.99999:
# Полный реверс (разворот на 180 градусов) — гасим скорость в полный ноль
v_junction = self.min_speed
else:
# Формула Junction Deviation (вписывание виртуального радиуса поворота R)
sin_theta_d2 = math.sqrt((1.0 - cos_theta) / 2.0)
cos_theta_d2 = math.sqrt((1.0 + cos_theta) / 2.0)
R = self.junction_deviation * (cos_theta_d2 / (1.0 - cos_theta_d2))
# Безопасная скорость на угле по центростремительному лимиту
v_junction = math.sqrt(self.accel * R)
# Ограничиваем скорость стыка сверху рабочими подачами смежных кадров
v_junction = min(v_junction, seg_curr['v_max'], seg_next['v_max'])
v_junction = max(v_junction, self.min_speed)
# Присваиваем скорости сопряжения стыка двум кадрам на границе
seg_curr['v_end'] = v_junction
seg_next['v_start'] = v_junction
# Самый первый и самый последний кадры программы принудительно зануляем/тормозим в min_speed
if num_segs > 0:
segments[0]['v_start'] = self.min_speed
segments[-1]['v_end'] = self.min_speed
# ====================================================================
# 3. ФОРМИРОВАНИЕ ПАКЕТОВ B: ДЛЯ ОТПРАВКИ ПО WI-FI (СТРОГО 7 РАЗДЕЛИТЕЛЕЙ)
# ====================================================================
packet_stream = []
for seg in segments:
# Формат пакета: B:TYPE;LINE_NUM;X;Y;Z;F_MAX;V_START;V_END
# Все координаты и скорости округляем до 3-х знаков после запятой
packet = (f"B:{seg['type']};{seg['line']};"
f"{seg['x']:.3f};{seg['y']:.3f};{seg['z']:.3f};"
f"{seg['f_min']:.3f};{seg['v_start']:.3f};{seg['v_end']:.3f}\n")
packet_stream.append(packet)
return packet_stream