增加预安装供料器功能、路径规划模型支持单点、整线优化支持批量处理
This commit is contained in:
@ -47,10 +47,10 @@ x_max_velocity, y_max_velocity = 1.4, 1.2
|
||||
x_max_acceleration, y_max_acceleration = x_max_velocity / 0.079, y_max_velocity / 0.079
|
||||
|
||||
# TODO: 不同种类供料器宽度
|
||||
feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.25, 7.25), 'SM16': (7.25, 7.25),
|
||||
'SM24': (7.25, 7.25), 'SM32': (7.25, 7.25)}
|
||||
# feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.00, 20.00), 'SM16': (7.00, 22.00),
|
||||
# 'SM24': (7.00, 29.00), 'SM32': (7.00, 44.00)}
|
||||
# feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.25, 7.25), 'SM16': (7.25, 7.25),
|
||||
# 'SM24': (7.25, 7.25), 'SM32': (7.25, 7.25)}
|
||||
feeder_width = {'SM8': (7.25, 7.25), 'SM12': (7.00, 20.00), 'SM16': (7.00, 22.00),
|
||||
'SM24': (7.00, 29.00), 'SM32': (7.00, 44.00)}
|
||||
|
||||
# 可用吸嘴数量限制
|
||||
nozzle_limit = {'CN065': 6, 'CN040': 6, 'CN020': 6, 'CN400': 6, 'CN140': 6}
|
||||
@ -67,7 +67,7 @@ t_fix_camera_check = 0.12 # 固定相机检测时间
|
||||
T_pp, T_tr, T_nc, T_pl = 2, 5, 25, 0
|
||||
|
||||
# 时间参数 (数据拟合获得)
|
||||
Fit_cy, Fit_nz, Fit_pu, Fit_pl, Fit_mv = 0.326, 0.8694, 0.159, 0.041, 0.001
|
||||
Fit_cy, Fit_nz, Fit_pu, Fit_pl, Fit_mv = 0.326, 0.870, 0.159, 0.041, 0.001
|
||||
|
||||
|
||||
class OptResult:
|
||||
@ -108,7 +108,7 @@ class OptInfo:
|
||||
print(f'-Pick time: {self.pickup_time: .3f}, Pick distance: {self.pickup_distance: .3f}')
|
||||
print(f'-Place time: {self.place_time: .3f}, Place distance: {self.place_distance: .3f}')
|
||||
print(
|
||||
f'-Round time: {self.total_time - self.place_time - self.place_time: .3f}, Place distance: '
|
||||
f'-Round time: {self.total_time - self.place_time - self.place_time: .3f}, Round distance: '
|
||||
f'{self.total_distance - self.pickup_distance - self.place_distance: .3f}')
|
||||
|
||||
minutes, seconds = int(self.total_time // 60), int(self.total_time) % 60
|
||||
@ -372,364 +372,6 @@ def feeder_assignment(component_data, pcb_data, component_result, cycle_result):
|
||||
return feeder_slot_result
|
||||
|
||||
|
||||
def dynamic_programming_cycle_path(pcb_data, cycle_placement, assigned_feeder):
|
||||
head_sequence = []
|
||||
num_pos = sum([placement != -1 for placement in cycle_placement]) + 1
|
||||
|
||||
pos, head_set = [], []
|
||||
feeder_set = set()
|
||||
for head, feeder in enumerate(assigned_feeder):
|
||||
if feeder == -1:
|
||||
continue
|
||||
|
||||
head_set.append(head)
|
||||
placement = cycle_placement[head]
|
||||
if feeder != -1 and placement == -1:
|
||||
print(assigned_feeder)
|
||||
print(cycle_placement)
|
||||
|
||||
pos.append([pcb_data.iloc[placement]['x'] - head * head_interval + stopper_pos[0],
|
||||
pcb_data.iloc[placement]['y'] + stopper_pos[1]])
|
||||
|
||||
feeder_set.add(feeder - head * interval_ratio)
|
||||
|
||||
pos.insert(0, [slotf1_pos[0] + ((min(list(feeder_set)) + max(list(feeder_set))) / 2 - 1) * slot_interval,
|
||||
slotf1_pos[1]])
|
||||
|
||||
def get_distance(pos_1, pos_2):
|
||||
return math.sqrt((pos_1[0] - pos_2[0]) ** 2 + (pos_1[1] - pos_2[1]) ** 2)
|
||||
|
||||
# 各节点之间的距离
|
||||
dist = [[get_distance(pos_1, pos_2) for pos_2 in pos] for pos_1 in pos]
|
||||
|
||||
min_dist = [[np.inf for _ in range(num_pos)] for s in range(1 << num_pos)]
|
||||
min_path = [[[] for _ in range(num_pos)] for s in range(1 << num_pos)]
|
||||
|
||||
# 状压dp搜索
|
||||
for s in range(1, 1 << num_pos, 2):
|
||||
# 考虑节点集合s必须包括节点0
|
||||
if not (s & 1):
|
||||
continue
|
||||
for j in range(1, num_pos):
|
||||
# 终点j需在当前考虑节点集合s内
|
||||
if not (s & (1 << j)):
|
||||
continue
|
||||
if s == int((1 << j) | 1):
|
||||
# 若考虑节点集合s仅含节点0和节点j,dp边界,赋予初值
|
||||
# print('j:', j)
|
||||
min_path[s][j] = [j]
|
||||
min_dist[s][j] = dist[0][j]
|
||||
|
||||
# 枚举下一个节点i,更新
|
||||
for i in range(1, num_pos):
|
||||
# 下一个节点i需在考虑节点集合s外
|
||||
if s & (1 << i):
|
||||
continue
|
||||
if min_dist[s][j] + dist[j][i] < min_dist[s | (1 << i)][i]:
|
||||
min_path[s | (1 << i)][i] = min_path[s][j] + [i]
|
||||
min_dist[s | (1 << i)][i] = min_dist[s][j] + dist[j][i]
|
||||
|
||||
ans_dist = float('inf')
|
||||
ans_path = []
|
||||
# 求最终最短哈密顿回路
|
||||
for i in range(1, num_pos):
|
||||
if min_dist[(1 << num_pos) - 1][i] + dist[i][0] < ans_dist:
|
||||
# 更新,回路化
|
||||
ans_path = min_path[s][i]
|
||||
ans_dist = min_dist[(1 << num_pos) - 1][i] + dist[i][0]
|
||||
|
||||
for parent in ans_path:
|
||||
head_sequence.append(head_set[parent - 1])
|
||||
|
||||
start_head, end_head = head_sequence[0], head_sequence[-1]
|
||||
if pcb_data.iloc[cycle_placement[start_head]]['x'] - start_head * head_interval > \
|
||||
pcb_data.iloc[cycle_placement[end_head]]['x'] - end_head * head_interval:
|
||||
head_sequence = list(reversed(head_sequence))
|
||||
return head_sequence
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def greedy_placement_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result,
|
||||
hinter=True):
|
||||
placement_result, head_sequence_result = [], []
|
||||
if len(pcb_data) == 0:
|
||||
return placement_result, head_sequence_result
|
||||
mount_point_index = [[] for _ in range(len(component_data))]
|
||||
mount_point_pos = [[] for _ in range(len(component_data))]
|
||||
|
||||
for i in range(len(pcb_data)):
|
||||
part = pcb_data.iloc[i]['part']
|
||||
component_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
# 记录贴装点序号索引和对应的位置坐标
|
||||
mount_point_index[component_index].append(i)
|
||||
mount_point_pos[component_index].append([pcb_data.iloc[i]['x'], pcb_data.iloc[i]['y']])
|
||||
|
||||
search_dir = 1 # 0:自左向右搜索 1:自右向左搜索
|
||||
for cycle_set in range(len(component_result)):
|
||||
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
||||
for cycle in range(floor_cycle, ceil_cycle):
|
||||
if sum(component_result[cycle_set]) == -max_head_index:
|
||||
continue
|
||||
# search_dir = 1 - search_dir
|
||||
assigned_placement = [-1] * max_head_index
|
||||
max_pos = [max(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
|
||||
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
|
||||
min_pos = [min(mount_point_pos[component_index], key=lambda x: x[0]) for component_index in
|
||||
range(len(mount_point_pos)) if len(mount_point_pos[component_index]) > 0][0][0]
|
||||
point2head_range = min(math.floor((max_pos - min_pos) / head_interval) + 1, max_head_index)
|
||||
|
||||
# 最近邻确定
|
||||
way_point = None
|
||||
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
|
||||
for head_counter, head in enumerate(head_range):
|
||||
if component_result[cycle_set][head] == -1:
|
||||
continue
|
||||
|
||||
component_index = component_result[cycle_set][head]
|
||||
if way_point is None or head_counter % point2head_range == 0:
|
||||
index = 0
|
||||
if way_point is None:
|
||||
if search_dir:
|
||||
index = np.argmax(mount_point_pos[component_index], axis=0)[0]
|
||||
else:
|
||||
index = np.argmin(mount_point_pos[component_index], axis=0)[0]
|
||||
else:
|
||||
for next_head in head_range:
|
||||
component_index = component_result[cycle_set][next_head]
|
||||
if assigned_placement[next_head] == -1 and component_index != -1:
|
||||
num_points = len(mount_point_pos[component_index])
|
||||
index = np.argmin(
|
||||
[abs(mount_point_pos[component_index][i][0] - way_point[0]) * .1 + abs(
|
||||
mount_point_pos[component_index][i][1] - way_point[1]) for i in
|
||||
range(num_points)])
|
||||
head = next_head
|
||||
break
|
||||
# index = np.argmax(mount_point_pos[component_index], axis=0)[0]
|
||||
assigned_placement[head] = mount_point_index[component_index][index]
|
||||
|
||||
# 记录路标点
|
||||
way_point = mount_point_pos[component_index][index]
|
||||
way_point[0] += (max_head_index - head - 1) * head_interval if search_dir else -head * head_interval
|
||||
|
||||
mount_point_index[component_index].pop(index)
|
||||
mount_point_pos[component_index].pop(index)
|
||||
else:
|
||||
head_index, point_index = -1, -1
|
||||
min_cheby_distance, min_euler_distance = float('inf'), float('inf')
|
||||
for next_head in range(max_head_index):
|
||||
if assigned_placement[next_head] != -1 or component_result[cycle_set][next_head] == -1:
|
||||
continue
|
||||
next_comp_index = component_result[cycle_set][next_head]
|
||||
for counter in range(len(mount_point_pos[next_comp_index])):
|
||||
if search_dir:
|
||||
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
|
||||
+ (max_head_index - next_head - 1) * head_interval)
|
||||
else:
|
||||
delta_x = abs(mount_point_pos[next_comp_index][counter][0] - way_point[0]
|
||||
- next_head * head_interval)
|
||||
|
||||
delta_y = abs(mount_point_pos[next_comp_index][counter][1] - way_point[1])
|
||||
|
||||
euler_distance = pow(axis_moving_time(delta_x, 0), 2) + pow(axis_moving_time(delta_y, 1), 2)
|
||||
cheby_distance = max(axis_moving_time(delta_x, 0),
|
||||
axis_moving_time(delta_y, 1)) + 5e-2 * euler_distance
|
||||
if cheby_distance < min_cheby_distance or (abs(cheby_distance - min_cheby_distance) < 1e-9
|
||||
and euler_distance < min_euler_distance):
|
||||
# if euler_distance < min_euler_distance:
|
||||
min_cheby_distance, min_euler_distance = cheby_distance, euler_distance
|
||||
head_index, point_index = next_head, counter
|
||||
|
||||
component_index = component_result[cycle_set][head_index]
|
||||
assert (0 <= head_index < max_head_index)
|
||||
|
||||
assigned_placement[head_index] = mount_point_index[component_index][point_index]
|
||||
way_point = mount_point_pos[component_index][point_index]
|
||||
way_point[0] += (max_head_index - head_index - 1) * head_interval if search_dir \
|
||||
else -head_index * head_interval
|
||||
|
||||
mount_point_index[component_index].pop(point_index)
|
||||
mount_point_pos[component_index].pop(point_index)
|
||||
|
||||
placement_result.append(assigned_placement) # 各个头上贴装的元件类型
|
||||
head_sequence_result.append(
|
||||
dynamic_programming_cycle_path(pcb_data, assigned_placement, feeder_slot_result[cycle_set]))
|
||||
|
||||
return placement_result, head_sequence_result
|
||||
|
||||
|
||||
@timer_wrapper
|
||||
def beam_search_for_route_generation(component_data, pcb_data, component_result, cycle_result, feeder_slot_result):
|
||||
beam_width = 4 # 集束宽度
|
||||
base_points = [float('inf'), float('inf')]
|
||||
|
||||
mount_point_index = [[] for _ in range(len(component_data))]
|
||||
mount_point_pos = [[] for _ in range(len(component_data))]
|
||||
|
||||
for i in range(len(pcb_data)):
|
||||
part = pcb_data.loc[i]['part']
|
||||
component_index = component_data[component_data['part'] == part].index.tolist()[0]
|
||||
|
||||
# 记录贴装点序号索引和对应的位置坐标
|
||||
mount_point_index[component_index].append(i)
|
||||
mount_point_pos[component_index].append([pcb_data.loc[i]['x'], pcb_data.loc[i]['y']])
|
||||
|
||||
# 记录最左下角坐标
|
||||
if mount_point_pos[component_index][-1][0] < base_points[0]:
|
||||
base_points[0] = mount_point_pos[component_index][-1][0]
|
||||
if mount_point_pos[component_index][-1][1] < base_points[1]:
|
||||
base_points[1] = mount_point_pos[component_index][-1][1]
|
||||
|
||||
beam_placement_sequence, beam_head_sequence = [], []
|
||||
beam_mount_point_index, beam_mount_point_pos = [], []
|
||||
|
||||
for beam_counter in range(beam_width):
|
||||
beam_mount_point_index.append(copy.deepcopy(mount_point_index))
|
||||
beam_mount_point_pos.append(copy.deepcopy(mount_point_pos))
|
||||
|
||||
beam_placement_sequence.append([])
|
||||
beam_head_sequence.append([])
|
||||
|
||||
beam_distance = [0 for _ in range(beam_width)] # 记录当前集束搜索点的点数
|
||||
def argpartition(list, kth):
|
||||
if kth < len(list):
|
||||
return np.argpartition(list, kth)
|
||||
else:
|
||||
index, indexes = 0, []
|
||||
while len(indexes) < kth:
|
||||
indexes.append(index)
|
||||
index += 1
|
||||
if index >= len(list):
|
||||
index = 0
|
||||
return np.array(indexes)
|
||||
|
||||
with tqdm(total=100) as pbar:
|
||||
search_dir = 0
|
||||
pbar.set_description('route schedule')
|
||||
for cycle_set in range(len(component_result)):
|
||||
floor_cycle, ceil_cycle = sum(cycle_result[:cycle_set]), sum(cycle_result[:(cycle_set + 1)])
|
||||
for cycle in range(floor_cycle, ceil_cycle):
|
||||
search_dir = 1 - search_dir
|
||||
beam_way_point = None
|
||||
for beam_counter in range(beam_width):
|
||||
beam_placement_sequence[beam_counter].append([-1 for _ in range(max_head_index)])
|
||||
|
||||
head_range = range(max_head_index - 1, -1, -1) if search_dir else range(max_head_index)
|
||||
for head in head_range:
|
||||
component_index = component_result[cycle_set][head]
|
||||
if component_index == -1:
|
||||
continue
|
||||
|
||||
if beam_way_point is None:
|
||||
# 首个贴装点的选取,距离基准点最近的beam_width个点
|
||||
beam_way_point = [[0, 0]] * beam_width
|
||||
|
||||
for beam_counter in range(beam_width):
|
||||
if search_dir:
|
||||
index = np.argmax(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
|
||||
else:
|
||||
index = np.argmin(beam_mount_point_pos[beam_counter][component_index], axis=0)[0]
|
||||
|
||||
beam_placement_sequence[beam_counter][-1][head] = beam_mount_point_index[beam_counter][component_index][index]
|
||||
|
||||
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][index]
|
||||
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
|
||||
search_dir else -head * head_interval
|
||||
|
||||
beam_mount_point_index[beam_counter][component_index].pop(index)
|
||||
beam_mount_point_pos[beam_counter][component_index].pop(index)
|
||||
else:
|
||||
# 后续贴装点
|
||||
search_beam_distance = []
|
||||
search_beam_index = [0] * (beam_width ** 2)
|
||||
for beam_counter in range(beam_width ** 2):
|
||||
search_beam_distance.append(beam_distance[beam_counter // beam_width])
|
||||
|
||||
for beam_counter in range(beam_width):
|
||||
# 对于集束beam_counter + 1最近的beam_width个点
|
||||
num_points = len(beam_mount_point_pos[beam_counter][component_index])
|
||||
|
||||
dist = []
|
||||
for i in range(num_points):
|
||||
if search_dir:
|
||||
delta_x = axis_moving_time(
|
||||
beam_mount_point_pos[beam_counter][component_index][i][0] -
|
||||
beam_way_point[beam_counter][0] + (max_head_index - head - 1) * head_interval,
|
||||
0)
|
||||
else:
|
||||
delta_x = axis_moving_time(
|
||||
beam_mount_point_pos[beam_counter][component_index][i][0] -
|
||||
beam_way_point[beam_counter][0] - head * head_interval, 0)
|
||||
|
||||
delta_y = axis_moving_time(beam_mount_point_pos[beam_counter][component_index][i][1] -
|
||||
beam_way_point[beam_counter][1], 1)
|
||||
|
||||
dist.append(max(delta_x, delta_y))
|
||||
|
||||
indexes = argpartition(dist, kth=beam_width)[:beam_width]
|
||||
|
||||
# 记录中间信息
|
||||
for i, index in enumerate(indexes):
|
||||
search_beam_distance[i + beam_counter * beam_width] += dist[index]
|
||||
search_beam_index[i + beam_counter * beam_width] = index
|
||||
|
||||
indexes = np.argsort(search_beam_distance)
|
||||
|
||||
beam_mount_point_pos_cpy = copy.deepcopy(beam_mount_point_pos)
|
||||
beam_mount_point_index_cpy = copy.deepcopy(beam_mount_point_index)
|
||||
|
||||
beam_placement_sequence_cpy = copy.deepcopy(beam_placement_sequence)
|
||||
beam_head_sequence_cpy = copy.deepcopy(beam_head_sequence)
|
||||
beam_counter = 0
|
||||
assigned_placement = []
|
||||
|
||||
for i, index in enumerate(indexes):
|
||||
# 拷贝原始集束数据
|
||||
beam_mount_point_pos[beam_counter] = copy.deepcopy(beam_mount_point_pos_cpy[index // beam_width])
|
||||
beam_mount_point_index[beam_counter] = copy.deepcopy(beam_mount_point_index_cpy[index // beam_width])
|
||||
beam_placement_sequence[beam_counter] = copy.deepcopy(beam_placement_sequence_cpy[index // beam_width])
|
||||
beam_head_sequence[beam_counter] = copy.deepcopy(beam_head_sequence_cpy[index // beam_width])
|
||||
|
||||
# 更新各集束最新扫描的的贴装点
|
||||
component_index = component_result[cycle_set][head]
|
||||
|
||||
beam_placement_sequence[beam_counter][-1][head] = \
|
||||
beam_mount_point_index[beam_counter][component_index][search_beam_index[index]]
|
||||
|
||||
if beam_placement_sequence[beam_counter][
|
||||
-1] in assigned_placement and beam_width - beam_counter < len(indexes) - i:
|
||||
continue
|
||||
|
||||
assigned_placement.append(beam_placement_sequence[beam_counter][-1])
|
||||
|
||||
# 更新参考基准点
|
||||
beam_way_point[beam_counter] = beam_mount_point_pos[beam_counter][component_index][search_beam_index[index]]
|
||||
beam_way_point[beam_counter][0] += (max_head_index - head - 1) * head_interval if \
|
||||
search_dir else -head * head_interval
|
||||
|
||||
# 更新各集束贴装路径长度,移除各集束已分配的贴装点
|
||||
beam_distance[beam_counter] = search_beam_distance[index]
|
||||
|
||||
beam_mount_point_pos[beam_counter][component_index].pop(search_beam_index[index])
|
||||
beam_mount_point_index[beam_counter][component_index].pop(search_beam_index[index])
|
||||
|
||||
beam_counter += 1
|
||||
|
||||
if beam_counter >= beam_width:
|
||||
break
|
||||
assert(beam_counter >= beam_width)
|
||||
|
||||
# 更新头贴装顺序
|
||||
for beam_counter in range(beam_width):
|
||||
beam_head_sequence[beam_counter].append(
|
||||
dynamic_programming_cycle_path(pcb_data, beam_placement_sequence[beam_counter][-1],
|
||||
feeder_slot_result[cycle_set]))
|
||||
|
||||
pbar.update(1 / sum(cycle_result) * 100)
|
||||
|
||||
index = np.argmin(beam_distance)
|
||||
return beam_placement_sequence[index], beam_head_sequence[index]
|
||||
|
||||
|
||||
def optimal_nozzle_assignment(component_data, pcb_data):
|
||||
# === Nozzle Assignment ===
|
||||
# number of points for nozzle & number of heads for nozzle
|
||||
|
Reference in New Issue
Block a user