def generate_nfp(nfp_paris): """ :param nfp_paris: :return: """ nfp_list = list() for pair in nfp_paris: poly_a = pair['A'] poly_a['points'] = nfp_utls.rotate_polygon( poly_a['points'], pair['key']['A_rotation'])['points'] poly_b = pair['B'] poly_b['points'] = nfp_utls.rotate_polygon( poly_b['points'], pair['key']['B_rotation'])['points'] if pair['key']['inside']: nfp = nfp_utls.nfp_rectangle(poly_a['points'], poly_b['points']) if nfp_utls.polygon_area(nfp) > 0: nfp.reverse() else: # pair['key']['inside'] == False , so compute no fit polygon between two segments # nfp = nfp_utls.nfp_polygon(poly_a, poly_b) # 使用自己写的生成nfp的函数 nfp = minkowski_difference( poly_a, poly_b) # 使用 Minkowski_difference和求两个零件的nfp, 考虑用lib # print("nfp = ", nfp) if nfp_utls.polygon_area(nfp) > 0: nfp.reverse() nfp_list.append({'key': pair['key'], 'value': nfp}) return nfp_list
def set_segments(self, segments_lists): """ :param segments_lists: [[point of segment 1], [], ..., [point of segment 314]] :return: self.shapes: a list of dictionary, with each dictionary contain information of each segment self.shapes: [{area: , p_id: , points:[{'x': , 'y': }...]},... {area: , p_id: , points:[{'x': , 'y': }...]}] self.total_segments_area : total area of all segments """ self.shapes = [] p_id = 1 total_area = 0 # patch1 = [[200, 0], [400, 0], [400, 200], [200, 200]] # patch2 = [[100, 1000], [300, 1000], [300, 2000], [100, 2000]] patch1 = [[950, 1150], [1050, 1150], [1050, 1250], [950, 1250]] patch2 = [[1920, 320], [2080, 320], [2080, 480], [1920, 480]] patches = [patch1, patch2] for segment_cord in segments_lists: shape = { 'area': 0, 'p_id': str(p_id), 'points': [{ 'x': p[0], 'y': p[1] } for p in segment_cord] } p_id = p_id + 1 seg_area = nfp_utls.polygon_area(shape['points']) if seg_area > 0: # 因为设置的是顺时针,所以用公式计算的面积应该小于0 shape['points'].reverse( ) # 确定多边形的线段方向, 多边形方向为逆时针时,S < 0 ;多边形方向为顺时针时,S > 0 shape['area'] = abs(seg_area) total_area += shape['area'] self.shapes.append(shape) for patch in patches: shape = { 'area': 0, 'p_id': str(p_id), 'points': [{ 'x': p[0], 'y': p[1] } for p in patch] } p_id = p_id + 1 seg_area = nfp_utls.polygon_area(shape['points']) if seg_area > 0: shape['points'].reverse() shape['area'] = abs(seg_area) total_area += shape['area'] self.shapes.append(shape) self.total_segments_area = total_area
def minkowski_difference(A, B): """ 两个多边形的相切空间 http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/MinkowskiDiff.htm :param A: :param B: :return: """ Ac = [[p['x'], p['y']] for p in A['points']] Bc = [[p['x'] * -1, p['y'] * -1] for p in B['points']] solution = pyclipper.MinkowskiSum(Ac, Bc, True) largest_area = None clipper_nfp = None for p in solution: p = [{'x': i[0], 'y': i[1]} for i in p] sarea = nfp_utls.polygon_area(p) if largest_area is None or largest_area > sarea: clipper_nfp = p largest_area = sarea clipper_nfp = [{ 'x': clipper_nfp[i]['x'] + Bc[0][0] * -1, 'y': clipper_nfp[i]['y'] + Bc[0][1] * -1, } for i in range(0, len(clipper_nfp))] return [clipper_nfp]
def add_objects(self, objects): """add_objects(objects): adds polygon objects to the nester""" if not isinstance(objects, list): objects = [objects] if not self.shapes: self.shapes = [] p_id = 0 total_area = 0 for obj in objects: points = self.clean_polygon(obj) shape = { 'area': 0, 'p_id': str(p_id), 'points': [{ 'x': p[0], 'y': p[1] } for p in points], } # 确定多边形的线段方向 area = nfp_utls.polygon_area(shape['points']) if area > 0: shape['points'].reverse() shape['area'] = abs(area) total_area += shape['area'] self.shapes.append(shape) # 如果是一般布,需要这个尺寸 self.shapes_max_length = total_area / BIN_HEIGHT * 3
def minkowski_difference(A, B): """ 两个多边形的相切空间 ff.htm. :param A: :param B: :return: """ Ac = [[p['x'], p['y']] for p in A['points']] Bc = [[p['x'] * -1, p['y'] * -1] for p in B['points']] solution = pyclipper.MinkowskiSum(Ac, Bc, True) # print('DDD') # print(A) # print(B) largest_area = None clipper_nfp = None for p in solution: p = [{'x': i[0], 'y': i[1]} for i in p] sarea = nfp_utls.polygon_area(p) if largest_area is None or largest_area > sarea: clipper_nfp = p largest_area = sarea clipper_nfp = [{ 'x': clipper_nfp[i]['x'] + Bc[0][0] * -1, 'y': clipper_nfp[i]['y'] + Bc[0][1] * -1 } for i in range(0, len(clipper_nfp))] #print(clipper_nfp) return [clipper_nfp]
def add_objects(self, objects): """add_objects(objects): adds polygon objects to the nester""" if not isinstance(objects, list): objects = [objects] if not self.shapes: self.shapes = [] p_id = 0 total_area = 0 for obj in objects: points = self.clean_polygon(obj) shape = { 'area': 0, 'p_id': str(p_id), 'points': [{ 'x': p[0], 'y': p[1] } for p in points], } # Xác định hướng đường thẳng của đa giác area = nfp_utls.polygon_area(shape['points']) if area > 0: shape['points'].reverse() shape['area'] = abs(area) total_area += shape['area'] self.shapes.append(shape) # Nếu là vải thông thường, kích thước này là bắt buộc self.shapes_max_length = total_area / BIN_HEIGHT * 3
def minkow(A, B): Ac = [[p['x'], p['y']] for p in A] Bc = [[p['x'] * -1, p['y'] * -1] for p in B] solution = pyclipper.MinkowskiSum(Ac, Bc, True) largest_area = None clipper_nfp = None for p in solution: p = [{'x': i[0], 'y': i[1]} for i in p] sarea = nfp_utls.polygon_area(p) if largest_area is None or largest_area > sarea: clipper_nfp = p largest_area = sarea clipper_nfp = [{ 'x': clipper_nfp[i]['x'] + Bc[0][0] * -1, 'y': clipper_nfp[i]['y'] + Bc[0][1] * -1 } for i in range(0, len(clipper_nfp))] return clipper_nfp
def set_target_loop(best, nest): """ Đặt tất cả đồ họa xuống và thoát :param best: Một kết quả đang chạy :param nest: Nester class :return: """ res = best total_area = 0 rate = None num_placed = 0 step = 0 while 1: print("Loop ", step + 1) nest.run() best = nest.best if best['fitness'] <= res['fitness']: res = best for s_data in res['placements']: tmp_total_area = 0.0 tmp_num_placed = 0 for move_step in s_data: tmp_total_area += nest.shapes[int( move_step['p_id'])]['area'] tmp_num_placed += 1 tmp_rates = tmp_total_area / abs( nfp_utls.polygon_area(nest.container['points'])) if (num_placed < tmp_num_placed or total_area < tmp_total_area or rate < tmp_rates): num_placed = tmp_num_placed total_area = tmp_total_area rate = tmp_rates # Tất cả đồ họa bị lỗi trước khi thoát if num_placed == len(nest.shapes): break # Đang vẽ draw_result(res['placements'], nest.shapes, nest.container, nest.container_bounds)
def set_target_loop(best, nest): """ 把所有图形全部放下就退出 :param best: 一个运行结果 :param nest: Nester class :return: """ res = best total_area = 0 rate = None num_placed = 0 while 1: nest.run() best = nest.best if best['fitness'] <= res['fitness']: res = best for s_data in res['placements']: tmp_total_area = 0.0 tmp_num_placed = 0 for move_step in s_data: tmp_total_area += nest.shapes[int( move_step['p_id'])]['area'] tmp_num_placed += 1 tmp_rates = tmp_total_area / abs( nfp_utls.polygon_area(nest.container['points'])) if (num_placed < tmp_num_placed or total_area < tmp_total_area or rate < tmp_rates): num_placed = tmp_num_placed total_area = tmp_total_area rate = tmp_rates # 全部图形放下才退出 if num_placed == len(nest.shapes): break # 画图 draw_result(res['placements'], nest.shapes, nest.container, nest.container_bounds)
def add_objects(self, objects, objects_str): """加载形状 Args: objects (list): 形状信息 objects_str (str): 因为打分程序要求输入点的顺序不能改变,使用它来存储这一信息 """ if not isinstance(objects, list): objects = [objects] if not self.shapes: self.shapes = [] self.originalshapes = objects_str p_id = 0 total_area = 0 for obj in objects: points = self.clean_polygon(obj) shape = { 'area': 0, 'p_id': str(p_id), 'points': [{ 'x': p[0], 'y': p[1] } for p in points] } # 确定多边形的线段方向 area = nfp_utls.polygon_area(shape['points']) if area > 0: shape['points'].reverse() shape['area'] = abs(area) total_area += shape['area'] self.shapes.append(shape) #积木形状总面积 self.shapes_total_area = total_area
def place_paths(self): # 排列图形 if self.bin_polygon is None: return None # rotate paths by given rotation rotated = list() for i in range(0, len(self.paths)): r = rotate_polygon(self.paths[i][1]['points'], self.paths[i][2]) r['rotation'] = self.paths[i][2] r['source'] = self.paths[i][1]['p_id'] r['p_id'] = self.paths[i][0] rotated.append(r) paths = rotated # 保存所有转移数据 all_placements = list() # 基因组的适应值 fitness = 0 bin_area = abs(polygon_area(self.bin_polygon['points'])) min_width = None while len(paths) > 0: placed = list() placements = list() # add 1 for each new bin opened (lower fitness is better) fitness += 1 for i in range(0, len(paths)): path = paths[i] # 图形的坐标 key = json.dumps({ 'A': '-1', 'B': path['p_id'], 'inside': True, 'A_rotation': 0, 'B_rotation': path['rotation'], }) binNfp = self.nfpCache.get(key) if binNfp is None or len(binNfp) == 0: continue # part unplaceable, skip error = False # ensure all necessary NFPs exist for p in placed: key = json.dumps({ 'A': p['p_id'], 'B': path['p_id'], 'inside': False, 'A_rotation': p['rotation'], 'B_rotation': path['rotation'], }) nfp = self.nfpCache.get(key) if nfp is None: error = True break # part unplaceable, skip if error: continue position = None if len(placed) == 0: for j in range(0, len(binNfp)): for k in range(0, len(binNfp[j])): if position is None or ( binNfp[j][k]['x'] - path['points'][0]['x'] < position['x']): position = { 'x': binNfp[j][k]['x'] - path['points'][0]['x'], 'y': binNfp[j][k]['y'] - path['points'][0]['y'], 'p_id': path['p_id'], 'rotation': path['rotation'], } placements.append(position) placed.append(path) continue clipper_bin_nfp = list() for j in range(0, len(binNfp)): clipper_bin_nfp.append([[p['x'], p['y']] for p in binNfp[j]]) clipper = pyclipper.Pyclipper() for j in range(0, len(placed)): p = placed[j] key = json.dumps({ 'A': p['p_id'], 'B': path['p_id'], 'inside': False, 'A_rotation': p['rotation'], 'B_rotation': path['rotation'], }) nfp = self.nfpCache.get(key) if nfp is None: continue for k in range(0, len(nfp)): clone = [[ np['x'] + placements[j]['x'], np['y'] + placements[j]['y'] ] for np in nfp[k]] clone = pyclipper.CleanPolygon(clone) if len(clone) > 2: clipper.AddPath(clone, pyclipper.PT_SUBJECT, True) combine_nfp = clipper.Execute(pyclipper.CT_UNION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) if len(combine_nfp) == 0: continue clipper = pyclipper.Pyclipper() clipper.AddPaths(combine_nfp, pyclipper.PT_CLIP, True) try: clipper.AddPaths(clipper_bin_nfp, pyclipper.PT_SUBJECT, True) except: print('图形坐标出错', clipper_bin_nfp) # choose placement that results in the smallest bounding box finalNfp = clipper.Execute( pyclipper.CT_DIFFERENCE, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO, ) if len(finalNfp) == 0: continue finalNfp = pyclipper.CleanPolygons(finalNfp) for j in range(len(finalNfp) - 1, -1, -1): if len(finalNfp[j]) < 3: finalNfp.pop(j) if len(finalNfp) == 0: continue finalNfp = [[{ 'x': p[0], 'y': p[1] } for p in polygon] for polygon in finalNfp] min_width = None min_area = None min_x = None for nf in finalNfp: if abs(polygon_area(nf)) < 2: continue for p_nf in nf: # 生成nfp多边形 all_points = list() for m in range(0, len(placed)): for p in placed[m]['points']: all_points.append({ 'x': p['x'] + placements[m]['x'], 'y': p['y'] + placements[m]['y'], }) # path 坐标 shift_vector = { 'x': p_nf['x'] - path['points'][0]['x'], 'y': p_nf['y'] - path['points'][0]['y'], 'p_id': path['p_id'], 'rotation': path['rotation'], } # 找新坐标后的最小矩形 for m in range(0, len(path['points'])): all_points.append({ 'x': path['points'][m]['x'] + shift_vector['x'], 'y': path['points'][m]['y'] + shift_vector['y'], }) rect_bounds = get_polygon_bounds(all_points) # weigh width more, to help compress in direction of gravity area = rect_bounds['width'] * 2 + rect_bounds['height'] if (min_area is None or area < min_area or almost_equal(min_area, area)) and ( min_x is None or shift_vector['x'] <= min_x): min_area = area min_width = rect_bounds['width'] position = shift_vector min_x = shift_vector['x'] if position: placed.append(path) placements.append(position) if min_width: fitness += min_width / bin_area for p in placed: p_id = paths.index(p) if p_id >= 0: paths.pop(p_id) if placements and len(placements) > 0: all_placements.append(placements) else: # something went wrong break fitness += 2 * len(paths) return { 'placements': all_placements, 'fitness': fitness, 'paths': paths, 'area': bin_area, }
def process_nfp(self, pair): """ 计算所有图形两两组合的相切多边形(NFP) :param pair: 两个组合图形的参数 :return: """ if pair is None or len(pair) == 0: return None # 考虑有没有洞和凹面 search_edges = self.config['exploreConcave'] use_holes = self.config['useHoles'] # 图形参数 A = copy.deepcopy(pair['A']) A['points'] = nfp_utls.rotate_polygon( A['points'], pair['key']['A_rotation'])['points'] B = copy.deepcopy(pair['B']) B['points'] = nfp_utls.rotate_polygon( B['points'], pair['key']['B_rotation'])['points'] if pair['key']['inside']: # 内切或者外切 if nfp_utls.is_rectangle(A['points'], 0.0001): nfp = nfp_utls.nfp_rectangle(A['points'], B['points']) else: nfp = nfp_utls.nfp_polygon(A, B, True, search_edges) # ensure all interior NFPs have the same winding direction if nfp and len(nfp) > 0: for i in range(0, len(nfp)): if nfp_utls.polygon_area(nfp[i]) > 0: nfp[i].reverse() else: pass # print('NFP Warning:', pair['key']) else: if search_edges: nfp = nfp_utls.nfp_polygon(A, B, False, search_edges) else: nfp = minkowski_difference(A, B) # 检查NFP多边形是否合理 if nfp is None or len(nfp) == 0: pass # print('error in NFP 260') # print('NFP Error:', pair['key']) # print('A;', A) # print('B:', B) return None for i in range(0, len(nfp)): # if search edges is active, only the first NFP is guaranteed to pass sanity check if not search_edges or i == 0: if abs(nfp_utls.polygon_area(nfp[i])) < abs( nfp_utls.polygon_area(A['points'])): pass # print('error in NFP area 269') # print('NFP Area Error: ', abs(nfp_utls.polygon_area(nfp[i])), pair['key']) # print('NFP:', json.dumps(nfp[i])) # print('A: ', A) # print('B: ', B) nfp.pop(i) return None if len(nfp) == 0: return None # for outer NFPs, the first is guaranteed to be the largest. # Any subsequent NFPs that lie inside the first are hole for i in range(0, len(nfp)): if nfp_utls.polygon_area(nfp[i]) > 0: nfp[i].reverse() if i > 0: if nfp_utls.point_in_polygon(nfp[i][0], nfp[0]): if nfp_utls.polygon_area(nfp[i]) < 0: nfp[i].reverse() # generate nfps for children (holes of parts) if any exist # 有洞的暂时不管 if use_holes and len(A) > 0: pass return {'key': pair['key'], 'value': nfp}
def process_nfp(self, pair): """ Tính đa giác tiếp tuyến của tất cả các cặp hình :param pair: Tham số kết hợp của hai hình :return: """ if pair is None or len(pair) == 0: return None # Tham số cài đặt, có lỗ hay mặt lõm hay không search_edges = self.config['exploreConcave'] use_holes = self.config['useHoles'] # Thông số đồ họa, quay các hình theo góc quay # A nếu là bin thì không chứa thông tin offset A = copy.deepcopy(pair['A']) A['points'] = nfp_utls.rotate_polygon( A['points'], pair['key']['A_rotation'])['points'] # Là path, chứa thông tin offset B = copy.deepcopy(pair['B']) # Rotate B theo key pair đã tạo với A B['points'] = nfp_utls.rotate_polygon( B['points'], pair['key']['B_rotation'])['points'] if pair['key']['inside']: # Inside or outside # Thường thì đây là cặp bin và path, tính NPF của part với bin hoặc giữa các path với nhau if nfp_utls.is_rectangle(A['points'], 0.0001): # Tính npf của hình với bin nfp = nfp_utls.nfp_rectangle(A['points'], B['points']) else: nfp = nfp_utls.nfp_polygon(A, B, True, search_edges) # ensure all interior NFPs have the same winding direction # Test_Di chuyển về sát lề # for nf in nfp: # for p in nf: # p['x'] = p['x'] - 10 # p['y'] = p['y'] - 10 if nfp and len(nfp) > 0: for i in range(0, len(nfp)): if nfp_utls.polygon_area(nfp[i]) > 0: nfp[i].reverse() else: pass # print('NFP Warning:', pair['key']) else: if search_edges: nfp = nfp_utls.nfp_polygon(A, B, False, search_edges) else: nfp = minkowski_difference(A, B) # Kiểm tra xem đa giác NFP có hợp lý không if nfp is None or len(nfp) == 0: pass # print('error in NFP 260') # print('NFP Error:', pair['key']) # print('A;', A) # print('B:', B) return None for i in range(0, len(nfp)): # if search edges is active, only the first NFP is guaranteed to pass sanity check if not search_edges or i == 0: if abs(nfp_utls.polygon_area(nfp[i])) < abs( nfp_utls.polygon_area(A['points'])): pass # print('error in NFP area 269') # print('NFP Area Error: ', abs(nfp_utls.polygon_area(nfp[i])), pair['key']) # print('NFP:', json.dumps(nfp[i])) # print('A: ', A) # print('B: ', B) nfp.pop(i) return None if len(nfp) == 0: return None # for outer NFPs, the first is guaranteed to be the largest. # Any subsequent NFPs that lie inside the first are hole for i in range(0, len(nfp)): if nfp_utls.polygon_area(nfp[i]) > 0: nfp[i].reverse() if i > 0: if nfp_utls.point_in_polygon(nfp[i][0], nfp[0]): if nfp_utls.polygon_area(nfp[i]) < 0: nfp[i].reverse() # generate nfps for children (holes of parts) if any exist # Leave the hole in if use_holes and len(A) > 0: pass return {'key': pair['key'], 'value': nfp}