├── Naive_Algorithm.py ├── README.md └── dataset ├── L0002_lingjian.csv ├── L0002_mianliao.csv ├── L0003_lingjian.csv └── L0003_mianliao.csv /Naive_Algorithm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import numpy as np 3 | import csv 4 | import matplotlib.pyplot as plt 5 | from matplotlib.path import Path 6 | from operator import itemgetter 7 | from shapely.geometry import asPolygon 8 | 9 | 10 | def inpolygon(xq, yq, xv, yv): 11 | """ 12 | 判断点是否在多边形内 13 | :type xq: np.ndarray 14 | :type yq: np.ndarray 15 | :type xv: np.ndarray 16 | :type yv: np.ndarray 17 | """ 18 | # 合并xv和yv为顶点数组 19 | vertices = np.vstack((xv, yv)).T 20 | # 定义Path对象 21 | path = Path(vertices) 22 | # 把xq和yq合并为test_points 23 | test_points = np.hstack([xq.reshape(xq.size, -1), yq.reshape(yq.size, -1)]) 24 | # 得到一个test_points是否严格在path内的mask,是bool值数组 25 | _in = path.contains_points(test_points) 26 | # 得到一个test_points是否在path内部或者在路径上的mask 27 | _in_on = path.contains_points(test_points, radius=-1e-10) 28 | # 得到一个test_points是否在path路径上的mask 29 | _on = _in ^ _in_on 30 | return _in_on 31 | 32 | 33 | def get_boxes_data(path): 34 | """ 35 | 获取零件数据,用字典存储 36 | :return: 37 | """ 38 | # 读取csv至字典 39 | csv_file = open(path, "r", encoding='utf-8') 40 | reader = csv.reader(csv_file) 41 | # 建立空字典 42 | data = {} 43 | boxes = [] 44 | for item in reader: 45 | # 忽略csv文件的第一行 46 | if reader.line_num == 1: 47 | continue 48 | # 字典data存储外轮廓数据 49 | data[item[1]] = item[3] 50 | # eval()函数用来执行一个字符串表达式 51 | pos_list = eval(item[3]) # item[3]为外轮廓数据 52 | pos_np = np.array(pos_list) # 转为np_array类型 53 | x_np = pos_np[:, 0] # 切片 取出列 54 | y_np = pos_np[:, 1] 55 | # 生成该零件的外接矩形 56 | x1 = min(x_np) 57 | y1 = min(y_np) 58 | x2 = max(x_np) 59 | y2 = max(y_np) 60 | bl_point = np.array([x1, y1]) # 矩形左下角坐标 61 | # 找出多边形最左侧的顶点坐标 62 | y_ = [] 63 | for id_ in range(len(x_np)): 64 | if x_np[id_] == x1: 65 | y_.append(y_np[id_]) 66 | y_left = np.array([min(y_), max(y_)]) # 多边形最左侧顶点的y轴坐标 67 | h = y2 - y1 + 5 68 | w = x2 - x1 + 5 69 | s = h * w 70 | # boxes存储矩形数据 71 | boxes.append({'id': item[1], 'x1': x1, 'y1': y1, 'h': h, 'w': w, 'space': s, 'data': pos_list, 72 | 'bl_point': bl_point, 'x_list': x_np, 'y_list': y_np, 'isuse': False, 'y_left': y_left, 73 | 'batch': item[0], 'cloth': item[5]}) 74 | csv_file.close() 75 | return boxes 76 | 77 | 78 | def PolyArea(x, y): 79 | """返回多边形面积 80 | 81 | """ 82 | return 0.5*np.abs(np.dot(x, np.roll(y, 1))-np.dot(y, np.roll(x, 1))) 83 | 84 | 85 | def tranfer_(x_np, y_np): 86 | """将x_np,y_np两个ndarray,转换成(x,y) ndarray 87 | 88 | """ 89 | tmp_li = [] 90 | for k in range(len(x_np)): 91 | tmp_li.append([x_np[k], y_np[k]]) 92 | xy_np = np.array(tmp_li) 93 | return xy_np 94 | 95 | 96 | def get_rate(path, mianliao_h): 97 | # 加载零件数据 98 | boxes = get_boxes_data(path) 99 | # print(PolyArea(boxes[0]['x_list'], boxes[0]['y_list'])) 100 | # print(boxes[0]['space']) 101 | # 按宽度对boxes进行排序 102 | boxes = sorted(boxes, key=itemgetter('w'), reverse=True) 103 | # 零件个数 104 | boxes_len = len(boxes) 105 | # 初始化 106 | move_list = [] 107 | move_xy = np.array([0.0, 0.0]) 108 | flag = 0 109 | for i in range(boxes_len): 110 | move_list.append(move_xy) 111 | cur_point = np.array([0.0, 0.0]) 112 | init_point = np.array([0.0, 0.0]) 113 | # 排列零件 114 | for i in range(boxes_len): 115 | if boxes[i]['isuse']: 116 | continue 117 | cur_point = init_point 118 | available_h = mianliao_h 119 | last_up = 0.0 # 用于计算两零件之间的高度间距(存储上一次y轴坐标) 120 | last_box = {'x_list': [cur_point[0], 20000.0], 'y_list': [cur_point[1], cur_point[1]], 'id': 0} 121 | for j in range(i, boxes_len): 122 | move = boxes[j]['bl_point'] - cur_point 123 | # rec_width = boxes[j]['w'] 124 | # rec_height = boxes[j]['h'] 125 | # x_min = cur_point[0] 126 | # y_min = cur_point[1] 127 | # x_list = np.array([x_min, x_min, x_min + rec_width, x_min + rec_width, x_min]) # 第j个矩形的所有轮廓点的x坐标集合 128 | # y_list = np.array([y_min, y_min + rec_height, y_min + rec_height, y_min, y_min]) 129 | # message = {'cur_point': cur_point, 'move': move, 'rec_x': x_list, 'rec_y': y_list} 130 | if boxes[j]['h'] <= available_h and boxes[j]['isuse'] is False: 131 | # plt.plot(message['rec_x'], message['rec_y'], linewidth=0.5) # 这一行是用来显示矩形边缘的 132 | boxes[j]['isuse'] = True 133 | move_list[j] = move 134 | 135 | # 存储移动后的多边形顶点坐标 136 | for k in range(len(boxes[j]['x_list'])): 137 | boxes[j]['x_list'][k] = boxes[j]['x_list'][k] - move_list[j][0] 138 | boxes[j]['y_list'][k] = boxes[j]['y_list'][k] - move_list[j][1] 139 | # 绘制多边形 140 | plt.plot(boxes[j]['x_list'], boxes[j]['y_list'], linewidth=0.3) 141 | 142 | # 计算gap 143 | gap_h = boxes[j]['y_left'][0] - move[1] - last_up 144 | last_up = boxes[j]['y_left'][1] - move[1] 145 | # 搜索放置在gap间的小零件 146 | for small in range(j + 1, boxes_len): 147 | if boxes[small]['h'] <= gap_h \ 148 | and boxes[small]['h'] <= available_h \ 149 | and boxes[small]['isuse'] is False: 150 | # 零件small的外接矩形的4个顶点和中心点 151 | move_s = boxes[small]['bl_point'] - cur_point 152 | tmp_x2 = boxes[small]['x1'] + boxes[small]['w'] - move_s[0] 153 | tmp_y2 = boxes[small]['y1'] + boxes[small]['h'] - move_s[1] 154 | tmp_x1 = boxes[small]['x1'] - move_s[0] 155 | tmp_y1 = boxes[small]['y1'] - move_s[1] 156 | tmp_mx = boxes[small]['x1'] + boxes[small]['w'] / 2 - move_s[0] 157 | tmp_my = boxes[small]['y1'] + boxes[small]['h'] / 2 - move_s[1] 158 | tmp_s = [] # 移动后的零件small所有顶点坐标 159 | # 判断4个顶点和中心点是否在已排列零件内部 160 | if inpolygon(tmp_x2, tmp_y2, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0 \ 161 | and inpolygon(tmp_x1, tmp_y2, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0 \ 162 | and inpolygon(tmp_x1, tmp_y1, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0 \ 163 | and inpolygon(tmp_x2, tmp_y1, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0 \ 164 | and inpolygon(tmp_mx, tmp_my, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0: 165 | # 计算移动后的多边形顶点坐标 166 | for k in range(len(boxes[small]['x_list'])): 167 | tmp_s.append( 168 | [boxes[small]['x_list'][k] - move_s[0], boxes[small]['y_list'][k] - move_s[1]]) 169 | tmp_pols = np.array(tmp_s) 170 | # 判断small零件与已排放的boxes[j]是否重叠 171 | tmp_polj = tranfer_(boxes[j]['x_list'], boxes[j]['y_list']) 172 | pol_s = asPolygon(tmp_pols) 173 | pol_j = asPolygon(tmp_polj) 174 | if pol_s.disjoint(pol_j): 175 | boxes[small]['isuse'] = True 176 | move_list[small] = move_s 177 | # 存储移动后的多边形顶点坐标 178 | for k in range(len(boxes[small]['x_list'])): 179 | boxes[small]['x_list'][k] = boxes[small]['x_list'][k] - move_list[small][0] 180 | boxes[small]['y_list'][k] = boxes[small]['y_list'][k] - move_list[small][1] 181 | # 绘制多边形 182 | plt.plot(boxes[small]['x_list'], boxes[small]['y_list'], linewidth=0.3) 183 | break 184 | 185 | available_h = available_h - boxes[j]['h'] 186 | # 零件排列到布料顶端的情况 187 | if boxes[j]['h'] > available_h and flag == 0: 188 | flag = 1 # 标记用于只需判断1次布料顶端能否排列下小零件(small) 189 | # 计算gap 190 | gap_h = mianliao_h - (boxes[j]['y_left'][1] - move[1]) 191 | # print(gap_h) 192 | # 搜索放置在gap间的小零件 193 | for small in range(j + 1, boxes_len): 194 | if boxes[small]['h'] <= gap_h \ 195 | and boxes[small]['isuse'] is False: 196 | # 零件small的外接矩形的4个顶点和中心点 197 | move_s = boxes[small]['bl_point'] + np.array([0.0, boxes[small]['h'] - 5]) - np.array( 198 | [cur_point[0], mianliao_h]) 199 | tmp_x2 = boxes[small]['x1'] + boxes[small]['w'] - move_s[0] 200 | tmp_y2 = boxes[small]['y1'] + boxes[small]['h'] - move_s[1] 201 | tmp_x1 = boxes[small]['x1'] - move_s[0] 202 | tmp_y1 = boxes[small]['y1'] - move_s[1] 203 | tmp_mx = boxes[small]['x1'] + boxes[small]['w'] / 2 - move_s[0] 204 | tmp_my = boxes[small]['y1'] + boxes[small]['h'] / 2 - move_s[1] 205 | # 判断4个顶点和中心点是否在已排列零件内部 206 | tmp_s = [] 207 | if inpolygon(tmp_x2, tmp_y2, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0 \ 208 | and inpolygon(tmp_x1, tmp_y2, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0 \ 209 | and inpolygon(tmp_x1, tmp_y1, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0 \ 210 | and inpolygon(tmp_x2, tmp_y1, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0 \ 211 | and inpolygon(tmp_mx, tmp_my, boxes[j]['x_list'], boxes[j]['y_list'])[0] + 0 == 0: 212 | # 计算移动后的多边形顶点坐标 213 | for k in range(len(boxes[small]['x_list'])): 214 | tmp_s.append( 215 | [boxes[small]['x_list'][k] - move_s[0], boxes[small]['y_list'][k] - move_s[1]]) 216 | tmp_pols = np.array(tmp_s) 217 | # 判断small零件与已排放的boxes[j]是否重叠 218 | tmp_polj = tranfer_(boxes[j]['x_list'], boxes[j]['y_list']) 219 | pol_s = asPolygon(tmp_pols) 220 | pol_j = asPolygon(tmp_polj) 221 | if pol_s.disjoint(pol_j): 222 | boxes[small]['isuse'] = True 223 | move_list[small] = move_s 224 | # 存储移动后的多边形顶点坐标 225 | for k in range(len(boxes[small]['x_list'])): 226 | boxes[small]['x_list'][k] = boxes[small]['x_list'][k] - move_list[small][0] 227 | boxes[small]['y_list'][k] = boxes[small]['y_list'][k] - move_list[small][1] 228 | # 绘制多边形 229 | plt.plot(boxes[small]['x_list'], boxes[small]['y_list'], linewidth=0.3) 230 | 231 | break 232 | break 233 | last_box = boxes[j] 234 | cur_point = cur_point + np.array([0.0, boxes[j]['h']]) 235 | init_point = init_point + np.array([boxes[i]['w'], 0.0]) 236 | flag = 0 237 | # 计算布料利用率 238 | area_box = 0 239 | width = init_point[0] 240 | for box in boxes: 241 | area_box += PolyArea(box['x_list'], box['y_list']) 242 | rate = area_box/(width*mianliao_h) 243 | # 保存排样图片 244 | path_name = path.split("\\")[-1].split('.')[0] 245 | path_plot = path_name + '.png' 246 | plt.title(r'%s——%0.4f' % (path_name, rate)) 247 | plt.savefig(path_plot, dpi=600) 248 | # plt.ylim((0, 1600)) 249 | plt.show() 250 | 251 | # 保存csv文件 252 | path_csv = path_name + '.csv' 253 | headers = ['下料批次号', '零件号', '面料号', '零件外轮廓线坐标'] 254 | values = [] 255 | 256 | for box in boxes: 257 | values.append([box['batch'], 258 | box['id'], 259 | box['cloth'], 260 | str(tranfer_(box['x_list'].round(1), box['y_list'].round(1)).tolist())]) 261 | csv_file = open(path_csv, "w", encoding='utf-8', newline='') 262 | # csv按行写入 263 | writer = csv.writer(csv_file) 264 | writer.writerow(headers) 265 | writer.writerows(values) 266 | csv_file.close() 267 | 268 | return rate 269 | 270 | 271 | if __name__ == "__main__": 272 | # First_path = r'.\L0002_lingjian.csv' 273 | Second_path = r'.\L0003_lingjian.csv' 274 | mianliao_height = 1600.0 275 | # rate1 = get_rate(First_path, mianliao_height) 276 | rate2 = get_rate(Second_path, mianliao_height) 277 | # print(round(rate1/2 + rate2/2, 3)) 278 | print(round(rate2, 4)) 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nesting 2 | Irregular packing using NFP and GA 3 | 4 | # Naive Algorithm 5 | 计算每个图形的最小包络矩形。通过遍历每个零件的具体坐标找到最边缘的坐标点,从而计算出最小包络矩形,计算的过程中考虑相邻零件的最小间隔。得到各零件的最小包络矩形之后,我们就将进行各零件的排样。这里采用的是以最小包络矩形的宽度降序进行排样。整个流程大致为:首先选取宽度最大的矩形将其放在布料的左下角,然后以其宽划分布料,再将次宽度矩形排列再其上方,依次重复次操作。 6 | 7 | # NFP and GA 8 | 可参考[2][3][4][5],这部分代码重构完再放上来。 9 | # Reference 10 | [1] 阿里云天池大赛. 2019广东工业智造创新大赛【赛场二】[EB/OL]. https://tianchi.aliyun.com/competition/entrance/231749/information, 2019–08–19/2019–12–17. 11 | 12 | [2] Jack000. SVGnest [EB/OL]. https://github.com/Jack000/SVGnest, 2019–04–11/2019–12–17. 13 | 14 | [3] auto. 遗传算法(python版)[EB/OL]. http://www.py3study.com/Article/details/id/18603.html, 2019–09–25/2019–12–17. 15 | 16 | [4] Leao A A S, Toledo F M B, Oliveira J F, et al. Irregular packing problems: a review of mathematical models[J]. European Journal of Operational Research, 2019. 17 | 18 | [5] liangxuCHEN. no_fit_polygon [EB/OL]. https://github.com/liangxuCHEN/no_fit_polygon, 2019–03–19/2019–12–17. 19 | 20 | # To do 21 | 1.用Numpy重构代码; 22 | 23 | 2.考虑瑕点问题; 24 | 25 | 3.写中英文两版readme. 26 | -------------------------------------------------------------------------------- /dataset/L0002_mianliao.csv: -------------------------------------------------------------------------------- 1 | 面料号,面料规格,瑕疵区域,零件间最小间距,最小边距 M0002,20000*1600,,5,0 -------------------------------------------------------------------------------- /dataset/L0003_mianliao.csv: -------------------------------------------------------------------------------- 1 | 面料号,面料规格,瑕疵区域,零件间最小间距,最小边距 M0003,20000*1600,,5,0 --------------------------------------------------------------------------------