def union_boundary(boundarys, regions): # global boundary, intersections # # union intersecting polygons on boundary # if boundarys != []: boundary = union(boundarys, pyclipper.PFT_NONZERO) paths = boundary else: paths = [] if (len(regions) > 0): region = union(regions, pyclipper.PFT_NONZERO) paths.extend(region) boundary = union(paths, pyclipper.PFT_NONZERO) # regions[0] = [] boundary = pyclipper.CleanPolygons(boundary) # boundary = pyclipper.SimplifyPolygons(boundary) for segs in boundary: if len(segs) == 0: boundary.remove([]) break return boundary
def clean_points(pts): """ Clean the polygon by getting rid of numerical artifacts. This is required to overcome Gmsh parsing errors. """ sc = constants.CLIPPER_SCALE spl_pts = pyclipper.SimplifyPolygons(pts) cln_pts = pyclipper.CleanPolygons(spl_pts) pts = sf(cln_pts, sc) return np.array(pts)
def offset_poly(path, toolrad): c_osp = pyclipper.PyclipperOffset() c_osp.AddPaths(path, pyclipper.JT_SQUARE, pyclipper.ET_CLOSEDPOLYGON) polyclip = c_osp.Execute(toolrad) polyclip = pyclipper.CleanPolygons(polyclip) c_osp.Clear() return polyclip
def expandClearedArea(cp, toolInPosGeometry, cleared): try: cp.Clear() cp.AddPaths(cleared, pyclipper.PT_SUBJECT, True) cp.AddPath(toolInPosGeometry, pyclipper.PT_CLIP, True) cleared = cp.Execute(pyclipper.CT_UNION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) cleared = pyclipper.CleanPolygons(cleared) return cleared except: print toolInPosGeometry raise
def offset(self, radius=0, rounding=0.0): offset = [] clip = pyclipper.PyclipperOffset() #Pyclipper polyclipper = pyclipper.Pyclipper() #Pyclipper for pat in self.polygons: outPoly = [[int(self.scaling * p[0]), int(self.scaling * p[1])] for p in pat] #Pyclipper outPoly = pyclipper.SimplifyPolygons([outPoly]) try: polyclipper.AddPaths(outPoly, poly_type=pyclipper.PT_SUBJECT, closed=True) except: None #print "path invalid", outPoly poly = polyclipper.Execute(pyclipper.CT_UNION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) clip.AddPaths(poly, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) offset = clip.Execute(-int((radius + rounding) * self.scaling)) offset = pyclipper.SimplifyPolygons(offset) if rounding > 0.0: roundclipper = pyclipper.PyclipperOffset() roundclipper.AddPaths(offset, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) offset = roundclipper.Execute(int(rounding * self.scaling)) offset = pyclipper.CleanPolygons(offset, distance=self.scaling * self.precision) #print (len(offset)) result = PolygonGroup(scaling=self.scaling, precision=self.precision, zlevel=self.zlevel) result.polygons = [] for poly in offset: if len(poly) > 0: output_poly = [[ float(x[0]) / self.scaling, float(x[1]) / self.scaling, self.zlevel ] for x in poly] result.addPolygon(output_poly) return result
def union(self): if not isinstance(self.raw_points[0][0], np.ndarray): raise TypeError("poly must be a 3D list") cc_poly = list() for poly in self.raw_points: if pyclipper.Orientation(poly) is False: reverse_poly = pyclipper.ReversePath(poly) cc_poly.append(reverse_poly) else: cc_poly.append(poly) union = utils.angusj(subj=cc_poly, method='union') points = pyclipper.CleanPolygons(union) if not isinstance(points[0][0], list): raise TypeError("poly must be a 3D list") return points
def union_boundary(self, boundarys, regions): # union intersecting polygons on boundary if boundarys: boundary = self.union(boundarys, pyclipper.PFT_NONZERO) paths = boundary else: boundary = [] paths = [] if regions: region = self.union(regions, pyclipper.PFT_NONZERO) paths.extend(region) boundary = self.union(paths, pyclipper.PFT_NONZERO) if boundary: boundary = pyclipper.CleanPolygons(boundary) # boundary = pyclipper.SimplifyPolygons(boundary) for segs in boundary: if len(segs) == 0: boundary.remove([]) break return boundary
def getToolCuttingShape(toolPos, newToolPos, toolRadiusScaled): global cache_hit_count global cache_pot_count global cache subv = GeomUtils.sub2v(newToolPos, toolPos) dist = GeomUtils.magnitude(subv) if dist < 1: return [[]] subv = [int(subv[0]), int(subv[1])] # make discrete to clipper resolution key = "K%d-%d" % (subv[0], subv[1]) cache_pot_count = cache_pot_count + 1 cache_hit = False if cache.has_key(key): toolCutShape = cache[key] cache_hit_count = cache_hit_count + 1 cache_hit = True if not cache_hit: of.Clear() of.AddPath([[0, 0], subv], pyclipper.JT_ROUND, pyclipper.ET_OPENROUND) toolCoverArea2 = of.Execute(toolRadiusScaled + 1)[0] #difference between old tool cutting area and new tool covering area cp.Clear() cp.AddPath(toolCoverArea2, pyclipper.PT_SUBJECT, True) cp.AddPath(toolGeometry, pyclipper.PT_CLIP, True) toolCutShape = cp.Execute(pyclipper.CT_DIFFERENCE, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) toolCutShape = pyclipper.CleanPolygons(toolCutShape) cache[key] = toolCutShape toolCoverArea = GeomUtils.translatePaths(toolCutShape, toolPos) return toolCoverArea
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 u'图形坐标出错', 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 read_edge(): global vertices, faces, boundarys, toolpaths, contours, slices, \ uiparams, noise_flag, tracks, gerber_data # # read edge file # definedFileTypes = [('gerber', '.gbl .gtl .gbr .cmp'), \ ('drill files', '.drl .dbd'), ('all files', '.*')] filename = askopenfilename(filetypes=definedFileTypes) if ((find(filename, ".gbr") != -1) | (find(filename, ".GBR") != -1)): print "reading PCB edge file", filename # Load data in SEPARATE data object [gerber_data2,edges] = load_file(filename) brdoutline = [] brdseg = -1 while len(edges) > 0: brdoutline.append([]) brdseg += 1 brdoutline[brdseg].extend(edges[0]) edges.remove(edges[0]) startpnt = brdoutline[brdseg][0] endpnt = brdoutline[brdseg][len(brdoutline[brdseg]) - 1] while (abs(startpnt[0] - endpnt[0]) > 10) | (abs(startpnt[1] - endpnt[1]) > 10): for seg in edges: if abs(seg[0][0] - endpnt[0]) < 10: if abs(seg[0][1] - endpnt[1]) < 10: brdoutline[brdseg].extend(seg) edges.remove(seg) endpnt = brdoutline[brdseg][len(brdoutline[brdseg]) - 1] continue if abs(seg[len(seg) - 1][0] - endpnt[0]) < 10: if abs(seg[len(seg) - 1][1] - endpnt[1]) < 10: edges.remove(seg) seg = seg[::-1] brdoutline[brdseg].extend(seg) endpnt = brdoutline[brdseg][len(brdoutline[brdseg]) - 1] pcb_edges = brdoutline pcb_edges = pyclipper.CleanPolygons(pcb_edges) # Remove existing edge layers = list(gerber_data.layers) for gl in layers: if gl.type == GerberLayer.TYPE_PCBEDGE: gerber_data.layers.remove(gl) # Add edge data to existing data gerber_data.layers.append(GerberReader.GerberLayer(True, "PCB Edge", pcb_edges, True, False, "blue", GerberLayer.TYPE_PCBEDGE)) toolpaths = [] if (find(filename, ".") != -1): index2 = find(filename, ".") if (find(filename, "/") != -1): index1 = find(filename, "/") while (find(filename[index1 + 1:index2], "/") != -1): index1 = index1 + 1 + find(filename[index1 + 1:index2], "/") infileedge.set(filename[index1 + 1:index2]) read(0)
def offsetPathOutIn(self, recursive=True): max_iterations = self.sliceIter.getValue() scaling = 1000.0 output = [] #define bounding box with tool radius and offset added radius = self.tool.getValue( ).diameter.value / 2.0 + self.radialOffset.value bound_min = [ self.stockMinX.getValue() - radius, self.stockMinY.getValue() - radius ] bound_max = [ self.stockMinX.getValue() + self.stockSizeX.getValue() + radius, self.stockMinY.getValue() + self.stockSizeY.getValue() + radius ] # sort patterns by slice levels patternLevels = dict() for p in self.patterns: patternLevels[p[0][2]] = [] for p in self.patterns: patternLevels[p[0][2]].append(p) for sliceLevel in sorted(patternLevels.keys(), reverse=True): #define bounding box bb = [[bound_min[0], bound_min[1], sliceLevel], [bound_min[0], bound_max[1], sliceLevel], [bound_max[0], bound_max[1], sliceLevel], [bound_max[0], bound_min[1], sliceLevel]] bbpoly = [[int(scaling * p[0]), int(scaling * p[1])] for p in bb] #Pyclipper print("Slice Level: ", sliceLevel) radius = self.tool.getValue( ).diameter.value / 2.0 + self.radialOffset.value iterations = max_iterations if iterations <= 0: iterations = 1 input = patternLevels[sliceLevel] offsetOutput = [] while len(input) > 0 and (iterations > 0): offset = [] clip = pyclipper.PyclipperOffset() #Pyclipper polyclipper = pyclipper.Pyclipper() #Pyclipper for pat in input: outPoly = [[int(scaling * p[0]), int(scaling * p[1])] for p in pat] #Pyclipper outPoly = pyclipper.SimplifyPolygons([outPoly]) try: polyclipper.AddPaths(outPoly, poly_type=pyclipper.PT_SUBJECT, closed=True) except: None #print "path invalid", outPoly poly = polyclipper.Execute(pyclipper.CT_UNION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) clip.AddPaths(poly, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) rounding = self.pathRounding.getValue() offset = clip.Execute(int((radius + rounding) * scaling)) offset = pyclipper.SimplifyPolygons(offset) if rounding > 0.0: roundclipper = pyclipper.PyclipperOffset() roundclipper.AddPaths(offset, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) offset = roundclipper.Execute(int(-rounding * scaling)) offset = pyclipper.CleanPolygons(offset, distance=scaling * self.precision.getValue()) # trim to outline polytrim = pyclipper.Pyclipper() #Pyclipper polytrim.AddPath(bbpoly, poly_type=pyclipper.PT_CLIP, closed=True) polytrim.AddPaths(offset, poly_type=pyclipper.PT_SUBJECT, closed=True) try: offset = polytrim.Execute(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) except: print("clipping intersection error") #print (len(offset)) input = [] for poly in offset: offsetOutput.append( [[x[0] / scaling, x[1] / scaling, sliceLevel] for x in reversed(poly)]) if recursive: input.append( [[x[0] / scaling, x[1] / scaling, sliceLevel] for x in poly]) radius = self.sideStep.value iterations -= 1 #self.patterns = input #offsetOutput.reverse() lastpoint = None for p in offsetOutput: closest_point_index = 0 path_closed = True remainder_path = [ ] # append the start of a boundary path to the first boundary point to the end on_boundary = False opt_path = [] for i in range(0, len(p)): #check if point lies on boundary bdist, bpoint, bindex = closest_point_on_polygon(p[i], bb) if bdist < 0.001 and False: # (TURNED OFF, buggy) point lies on boundary; skip this point # if this is the first boundary point of the path, append the start to the end if path_closed: remainder_path = opt_path remainder_path.append(p[i]) opt_path = [] path_closed = False else: if not on_boundary: # if it is the first point on boundary, add it #flush path up to here to output opt_path.append(p[i]) if len(opt_path) > 0: output.append(opt_path) lastpoint = opt_path[-1] opt_path = [] on_boundary = True else: if on_boundary and i > 0: opt_path.append(p[i - 1]) on_boundary = False opt_path.append(p[i]) opt_path += remainder_path if lastpoint is not None and path_closed: for i in range(0, len(p)): # find closest point from last position if dist(lastpoint, p[i]) < dist( lastpoint, opt_path[closest_point_index]): closest_point_index = i opt_path = opt_path[ closest_point_index:] + opt_path[:closest_point_index] # last point same as first point on closed paths (explicitly add it here) opt_path.append(opt_path[0]) if len(opt_path) > 0: lastpoint = opt_path[-1] output.append(opt_path) return output
def LoadEdgeFile(self, evt): if self.data is None: dlg = wx.MessageDialog(self, "You must load a Gerber file first", "Error", wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() return dlg = wx.FileDialog(self, "Open edge file", "", "", "Gerber Files (*.gml;*.gbl;*.gtl;*.gbr;*.cmp)|*.gml;*.gbl;*.gtl;*.gbr;*.cmp|All Files (*.*)|*", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) if dlg.ShowModal() == wx.ID_CANCEL: self.SetStatusText("Load edge file cancelled by user", 0) return filename = dlg.GetPath() dlg.Destroy() self.SetStatusText("Loading edge: " + filename + "...", 0) [data, edges] = load_file(filename) brdoutline = [] brdseg = -1 while len(edges) > 0: brdoutline.append([]) brdseg += 1 brdoutline[brdseg].extend(edges[0]) edges.remove(edges[0]) startpnt = brdoutline[brdseg][0] endpnt = brdoutline[brdseg][len(brdoutline[brdseg]) - 1] while (abs(startpnt[0] - endpnt[0]) > 10) | (abs(startpnt[1] - endpnt[1]) > 10): found = False for seg in edges: if abs(seg[0][0] - endpnt[0]) < 10: if abs(seg[0][1] - endpnt[1]) < 10: brdoutline[brdseg].extend(seg) edges.remove(seg) endpnt = brdoutline[brdseg][len(brdoutline[brdseg]) - 1] found = True break if abs(seg[len(seg) - 1][0] - endpnt[0]) < 10: if abs(seg[len(seg) - 1][1] - endpnt[1]) < 10: edges.remove(seg) seg = seg[::-1] brdoutline[brdseg].extend(seg) endpnt = brdoutline[brdseg][len(brdoutline[brdseg]) - 1] found = True break if not found: dlg = wx.MessageDialog(self, "Edge outline cannot contain any gaps.\n" "No changes were made.", "Load edge file failed", wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() self.SetStatusText("Load edge failed", 0) return xmin = 1E99 xmax = -1E99 ymin = 1E99 ymax = -1E99 if data.units == self.data.units: for poly in brdoutline: for (x, y) in poly: if x < xmin: xmin = x if x > xmax: xmax = x if y < ymin: ymin = y if y > ymax: ymax = y else: # finx bounds and convert units of data at same time conv = 25.4 if data.units == 1 else 1/25.4 print " Unit conversion of edge file data" for poly in brdoutline: for pt in poly: x = pt[0] = int(pt[0] * conv) y = pt[1] = int(pt[1] * conv) if x < xmin: xmin = x if x > xmax: xmax = x if y < ymin: ymin = y if y > ymax: ymax = y # Check if PCB fits inside edge. We're lazy so we just use box bounds (should really use # polygon bounds checking). eps = 10 if self.data.xmin2 + eps < xmin or self.data.xmax2 - eps > xmax or \ self.data.ymin2 + eps < ymin or self.data.ymax2 - eps > ymax: print self.data.xmin, xmin print self.data.ymin, ymin print self.data.xmax, xmax print self.data.ymax, ymax dlg = wx.MessageDialog(self, "The loaded edge does not fully contain the PCB board.\n" "Do you still wish to proceed using this edge file?", "PCB board extends past edge boundary", wx.YES | wx.NO | wx.ICON_WARNING) ans = dlg.ShowModal() dlg.Destroy() if ans != wx.ID_YES: self.SetStatusText("Load edge file cancelled by user", 0) return self.data.xmin = xmin self.data.xmax = xmax self.data.ymin = ymin self.data.ymax = ymax # View bounds # Includes the origin if xmin > 0: xmin = 0 if xmax < 0: xmax = 0 if ymin > 0: ymin = 0 if ymax < 0: ymax = 0 # Add margin ww = (xmax - xmin)*0.1 hh = (ymax - ymin)*0.1 xmin -= ww xmax += ww ymin -= hh ymax += hh pcb_edges = brdoutline pcb_edges = pyclipper.CleanPolygons(pcb_edges) for poly in pcb_edges: poly.append(poly[0]) # close off polygons # Remove existing edge layers = list(self.data.layers) for gl in layers: if gl.type == GerberLayer.TYPE_PCBEDGE: self.data.layers.remove(gl) # Add edge data to existing data self.data.layers.insert(-1, GerberLayer(True, "PCB Edge", pcb_edges, True, False, "blue", GerberLayer.TYPE_PCBEDGE)) self.pcb_edges = pcb_edges self.canvas.toolpaths = [] self.canvas.loadData2(self.data, xmin, xmax, ymin, ymax) self.layersPanel.loadLayersPanel(self.data, self.NotifyDataChange) self.SetStatusText("Load edge file completed successfully", 0)
def place_paths(self, solution, nfp_cache): """ :param solution: :param nfp_cache: :return: """ self.solution = solution paths = list() for combined_segment in solution: order = combined_segment[0] polygon = combined_segment[1]['points'] angle = combined_segment[2] r = rotate_polygon(polygon, angle) r['rotation'] = angle r['order'] = order paths.append(r) a = list() for i in paths: a.append(i['rotation']) print("angle = ", a) fitness = 0 min_length = None placed_segment_list = list() # 存放已经放置了的零件 movements_list = list() for segment in paths: key = json.dumps({ 'A': '-1', 'B': segment['order'], 'inside': True, 'A_rotation': 0, 'B_rotation': segment['rotation'] }) inner_fit_rectangle = nfp_cache.get(key) # position = None movement = None #################################################################################### # if len(placed_segment_list) == 0: # 最开始,没有放零件 # for point in inner_fit_rectangle: # if movement is None or (point['x'] - segment['points'][0]['x'] < movement['x']): # # 零件做下角的坐标加上这个position 就是ifp左下角的坐标 # movement = { # 'x': point['x'] - segment['points'][0]['x'], # 'y': point['y'] - segment['points'][0]['y'], # 'p_id': segment['order'], # 'rotation': segment['rotation'] # } # # movements_list.append(movement) # placed_segment_list.append(segment) # # continue #################################################################################### #################################################################################### if len(placed_segment_list) < 2: for point in inner_fit_rectangle: if movement is None or ( point['x'] - segment['points'][0]['x'] < movement['x']): # 零件做下角的坐标加上这个position 就是ifp左下角的坐标 movement = { 'x': 0, 'y': 0, 'p_id': segment['order'], 'rotation': segment['rotation'] } movements_list.append(movement) placed_segment_list.append(segment) continue #################################################################################### clipper_bin_nfp = list() clipper_bin_nfp.append([[p['x'], p['y']] for p in inner_fit_rectangle]) clipper = pyclipper.Pyclipper() j = 0 for placed_segment in placed_segment_list: """ 求待放的零件与已放了的零件的nfp """ key = json.dumps({ 'A': placed_segment['order'], 'B': segment['order'], 'inside': False, 'A_rotation': placed_segment['rotation'], 'B_rotation': segment['rotation'] }) nfp = nfp_cache.get(key) moved_nfp_bl = [[ point['x'] + movements_list[j]['x'], point['y'] + movements_list[j]['y'] ] for point in nfp] moved_nfp_bl = pyclipper.CleanPolygon(moved_nfp_bl) j = j + 1 if len(moved_nfp_bl) > 2: clipper.AddPath(moved_nfp_bl, pyclipper.PT_SUBJECT, True) combine_nfp = clipper.Execute(pyclipper.CT_UNION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) clipper = pyclipper.Pyclipper() clipper.AddPaths(combine_nfp, pyclipper.PT_CLIP, True) clipper.AddPaths(clipper_bin_nfp, pyclipper.PT_SUBJECT, True) final_nfp = clipper.Execute(pyclipper.CT_DIFFERENCE, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) final_nfp = pyclipper.CleanPolygons(final_nfp) for j in range(len(final_nfp) - 1, -1, -1): if len(final_nfp[j]) < 3: final_nfp.pop(j) if len(final_nfp) == 0: continue candidate_bl_position = list() # a = list() for polygon in final_nfp: for p in polygon: # 将所有可能放置的点都存放在一个list里面,这些点就是candidate position candidate_bl_position.append({'x': p[0], 'y': p[1]}) # a.append({'x': p[0], 'y': p[1]}) min_length = None min_area = None min_x = None for p_nf in candidate_bl_position: all_points = list() for m in range(0, len(placed_segment_list)): for p in placed_segment_list[m]['points']: all_points.append({ 'x': p['x'] + movements_list[m]['x'], 'y': p['y'] + movements_list[m]['y'] }) # path 坐标 shift_vector = { 'x': p_nf['x'] - segment['points'][0]['x'], 'y': p_nf['y'] - segment['points'][0]['y'], 'p_id': segment['order'], 'rotation': segment['rotation'], } # 找新坐标后的最小矩形 for point in segment['points']: all_points.append({ 'x': point['x'] + shift_vector['x'], 'y': point['y'] + shift_vector['y'] }) rect_bounds = get_polygon_bounds(all_points) # weigh width more, to help compress in direction of gravity area = rect_bounds['length'] * 2 + rect_bounds['width'] 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_length = rect_bounds['length'] movement = shift_vector min_x = shift_vector['x'] if movement: placed_segment_list.append(segment) movements_list.append(movement) # placements.append(position) if min_length: fitness = self.total_segments_area / (min_length * BIN_WIDTH) print("fitness = ", fitness) print("min_length = ", min_length) return { 'placements': movements_list, 'fitness': fitness, 'paths': paths, 'min_length': min_length, 'solution': self.solution }
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'], #当前剩余图形的id 'inside': True, 'A_rotation': 0, 'B_rotation': path['rotation'] }) binNfp = self.nfpCache.get( key) #{"pair['key']":nfp}#取出当前图形和容器的内切多边形列表 if binNfp is None or len(binNfp) == 0: print("error:binNfp缺失:" + key) continue #没有就忽略当前图形不放了 # 无法放下,跳过 error = False # 确保所有必要的 NFPs 存在 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 print("error:图形之间的nfp缺失:" + key) break #没有就结束,没法搞 # 无法放下,跳过 if error: print("error:无法放下") continue position = None if len(placed) == 0: #如果一个图形都还没放进去 for j in range(0, len(binNfp)): #对于每个当前图形和容器外接多边形 for k in range(0, len(binNfp[j])): #对于每个nfp的坐标 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'], #NFP中最小x与图形参考点x的距离,将图形平移到那一点要将每个x减去这个距离 'y': binNfp[j][k]['y'] - path['points'][0]['y'], #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)): #对于每一个与Bin的NFP clipper_bin_nfp.append([[p['x'], p['y']] for p in binNfp[j] ]) #将NFP的每个坐标都压入clipper_bin_nfp 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) #得到当前要摆放的图形和观察放好的图形之间的nfp if nfp is None: print("error:nfp缺失:" + key) continue for k in range(0, len(nfp)): #对于每一个nfp clone = [[ np['x'] + placements[j]['x'], np['y'] + placements[j]['y'] ] for np in nfp[k]] #每个nfp移动到已放好块的周围 clone = pyclipper.CleanPolygon( clone) #移除以下类型的点集 距离过近的相邻点 等等 if len(clone) > 2: clipper.AddPath(clone, pyclipper.PT_SUBJECT, True) #NFP交给clipper #所有放置好的图形的NFP并集 combine_nfp = clipper.Execute(pyclipper.CT_UNION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) if len(combine_nfp) == 0: print("error:nfp错误:nfp并集长度为0") continue clipper = pyclipper.Pyclipper() clipper.AddPaths(combine_nfp, pyclipper.PT_CLIP, True) #将合并好的NFP交给clipper try: clipper.AddPaths(clipper_bin_nfp, pyclipper.PT_SUBJECT, True) #将要放置的图形与盒子的NFP交给clipper except: print(u'图形坐标出错', clipper_bin_nfp) # choose placement that results in the smallest bounding box #与盒子NFP(可行)-放好图形的NFP(不可行) 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] #将所有finalNFP的点取出 min_width = None min_area = None min_x = None for nf in finalNfp: #对于每个NFP if abs(polygon_area(nf)) < 2: continue for p_nf in nf: #对于每个NFP的点 # 生成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 += FITNESSSCALE * 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 wrong break fitness += 2 * len(paths) return { 'placements': all_placements, 'fitness': fitness, 'min_width': min_width, 'paths': paths, 'area': bin_area }
def expand_islands(self, expansion): pco = pyclipper.PyclipperOffset() for poly in self.get_coast_polygons(): pco.AddPath(poly, pyclipper.JT_SQUARE, pyclipper.ET_CLOSEDPOLYGON) return pyclipper.CleanPolygons(pco.Execute(expansion))
def test_clean_polygons(self): solution = pyclipper.CleanPolygons([PATH_CLIP_1]) self.assertEqual(len(solution), 1) self.assertEqual(len(solution[0]), len(PATH_CLIP_1))
def Execute(op, obj, feat_num, feat, p_scale_factor): global toolGeometry global optimalCutAreaPerDist global iteration_limit_count global total_point_count global total_iteration_count global topZ global toolRadiusScaled global output_point_count global cache_hit_count global cache_pot_count global cache global cp global of global scale_factor scale_factor = p_scale_factor if RELOAD_MODULES: reload(Interpolation) reload(EngagementPoint) reload(GeomUtils) toolDiaScaled = op.tool.Diameter * scale_factor toolRadiusScaled = toolDiaScaled / 2 perf_start_time = time.time() perf_total_len = 0.0 print "toolRadiusScaled: ", toolRadiusScaled helixDiameter = min( op.tool.Diameter, 1000.0 if obj.HelixDiameterLimit.Value == 0.0 else obj.HelixDiameterLimit.Value) print "Helix diameter: ", helixDiameter helixDiameterScaled = helixDiameter * scale_factor stepOver = 0.01 * obj.StepOver # percentage to factor stepOverDistanceScaled = toolDiaScaled * stepOver finishPassOffset = 1.0 * obj.Tolerance / 2 cache_hit_count = 0 cache_pot_count = 0 cache = {} output_point_count = 0 topZ = op.stock.Shape.BoundBox.ZMax #initialization of = pyclipper.PyclipperOffset() cp = pyclipper.Pyclipper() toleranceScaled = int(obj.Tolerance * scale_factor) stepScaled = 10 toolGeometry = genToolGeometry(toolRadiusScaled + 1) # intitalization = calculate slot cutting area per step i.e. 100% step over sceneInit(toolRadiusScaled, topZ, scale_factor) distScaled = toolRadiusScaled / 2 slotStep = translatePath(toolGeometry, [0, distScaled]) cp.Clear() cp.AddPath(toolGeometry, pyclipper.PT_SUBJECT, True) cp.AddPath(slotStep, pyclipper.PT_CLIP, True) crossing = cp.Execute(pyclipper.CT_DIFFERENCE, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) referenceCutArea = pyclipper.Area(crossing[0]) fullSlotAreaPerDist = referenceCutArea / distScaled bound_extend = int(toolDiaScaled) #optimalCutAreaPerDistance (in scaled units) optimalCutAreaPerDist = stepOver * fullSlotAreaPerDist print "Optimal APD: ", optimalCutAreaPerDist, " Clipper scale factor: ", scale_factor, " Tolerance: ", obj.Tolerance, " Tolerance scaled: ", toleranceScaled try: # find cut region toolpath polygons of.Clear() of.AddPaths(feat, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) cut_region_tp = of.Execute(-int(finishPassOffset * scale_factor) - toolRadiusScaled) cut_region_tp_polytree = of.Execute2( -int(finishPassOffset * scale_factor) - toolRadiusScaled) cut_region_tp = pyclipper.CleanPolygons(cut_region_tp) sceneDrawPaths("BOUNDARY", cut_region_tp, scale_factor, (1, 0, 0), True) cleared, startPoint = findStartPoint(op, feat_num, cut_region_tp, cut_region_tp_polytree, helixDiameterScaled / 2, toolRadiusScaled, scale_factor) if cleared == None: return [], [0, 0] cleared = pyclipper.CleanPolygons(cleared) toolPos = startPoint of.Clear() of.AddPath([startPoint, startPoint], pyclipper.JT_ROUND, pyclipper.ET_OPENROUND) helixTp = of.Execute(helixDiameterScaled / 2) sceneDrawPaths("TOOLPATH", helixTp, scale_factor, (0, 0, 1), True) EngagementPoint.Initialize(cut_region_tp, toolRadiusScaled, stepScaled, scale_factor) total_point_count = 1 total_iteration_count = 0 iteration_limit_count = 0 toolPaths = [] no_cut_count = 0 over_cut_count = 0 toolPos = [startPoint[0], startPoint[1] - helixDiameterScaled / 2] toolDir = [1.0, 0.0] firstEngagePoint = True last_tool_gui_update = 0 pas = 0 while True: pas = pas + 1 #print "pass:"******"finding next pass engange point... pas:"******"Pass: %d\n"%pas) if toolPos == None: Console.PrintMessage("next engage point not found\n") break #nothing more to cut passToolPath = [] toClearPath = [] cumulativeCuttingArea = 0 no_cut_count = 0 reachedBoundaryPoint = None isOutside = False gyro = [toolDir] * 5 toolDir = GeomUtils.normalize(GeomUtils.sumv(gyro)) if SHOW_MARKERS: sceneClearPaths("ENGAGE") sceneDrawToolDir("ENGAGE", toolPos, toolDir, scale_factor, (0, 1, 0)) engagePoint = toolPos #iteration through the points on path/pass i = 0 deflectionAngle = math.pi #init del deflectionAngleHistory[:] distToBoundary = 0 while True: #points i = i + 1 sceneUpdateGui() if obj.StopProcessing: break #Console.PrintMessage("findNextPoint: %d =================================================================\n"%i) total_point_count = total_point_count + 1 toolDir = GeomUtils.normalize(GeomUtils.sumv(gyro)) #distance to the boundary line #if distToBoundary<toolRadiusScaled: clp, distToBoundary = GeomUtils.getClosestPointOnPaths( cut_region_tp, toolPos) distToEngagePoint = GeomUtils.magnitude( GeomUtils.sub2v(toolPos, engagePoint)) relDistToBoundary = 2.0 * distToBoundary / toolRadiusScaled minCutAreaPerDist = optimalCutAreaPerDist / 3 + 1 #if we are away from end boundary line, try making the optimal cut if relDistToBoundary > 1 or distToEngagePoint < toolRadiusScaled: targetArea = optimalCutAreaPerDist else: #decrease the cut area if we are close to boundary, adds a little bit of smoothing to the end of cut targetArea = relDistToBoundary * \ (optimalCutAreaPerDist-minCutAreaPerDist) + minCutAreaPerDist if distToBoundary < toolRadiusScaled or distToEngagePoint < toolRadiusScaled: stepScaled = toleranceScaled * 2 elif math.fabs(deflectionAngle) > 0.0001: stepScaled = 4.0 / deflectionAngle else: stepScaled = toleranceScaled * 4 if stepScaled < toleranceScaled * 2: stepScaled = toleranceScaled * 2 if stepScaled > toolRadiusScaled / 2: stepScaled = toolRadiusScaled / 2 # # Find next point with optimal cut # bound_box = [ [toolPos[0] - bound_extend, toolPos[1] - bound_extend], [toolPos[0] + bound_extend, toolPos[1] - bound_extend], [toolPos[0] + bound_extend, toolPos[1] + bound_extend], [toolPos[0] - bound_extend, toolPos[1] + bound_extend] ] cp.Clear() cp.AddPath(bound_box, pyclipper.PT_SUBJECT, True) cp.AddPaths(cleared, pyclipper.PT_CLIP, True) cleared_bounded = cp.Execute(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) if not GeomUtils.anyValidPath(cleared_bounded): cleared_bounded = cleared newToolPos, newToolDir, cuttingAreaPerDist, cuttingArea, deflectionAngle = findNextPoint( obj, op, of, cp, cleared_bounded, toolPos, toolDir, toolRadiusScaled, stepScaled, targetArea, scale_factor) # # CHECK if we reached the the boundary # if distToBoundary < toolRadiusScaled and isOutsideCutRegion( newToolPos, cut_region_tp_polytree, True): isOutside = True reachedBoundaryPoint = GeomUtils.getIntersectionPointLWP( [toolPos, newToolPos], cut_region_tp) if reachedBoundaryPoint != None: #print "reachedBoundaryPoint:", reachedBoundaryPoint #showTool("TL", reachedBoundaryPoint, scale_factor, (1, 0, 0)) newToolPos = reachedBoundaryPoint else: newToolPos = toolPos cuttingArea, cuttingAreaPerDist = calcCutingArea( toolPos, newToolPos, toolRadiusScaled, cleared_bounded) if cuttingArea > 3 * cuttingAreaPerDist + 10 and cuttingAreaPerDist > 2 * optimalCutAreaPerDist + 10: over_cut_count = over_cut_count + 1 Console.PrintMessage("Break: over cut (%d %f/%f)\n" % (over_cut_count, cuttingAreaPerDist, optimalCutAreaPerDist)) break else: over_cut_count = 0 if len(toClearPath) == 0: toClearPath.append(toolPos) toClearPath.append(newToolPos) if firstEngagePoint: if len(toClearPath) > 10: of.Clear() of.AddPath(toClearPath, pyclipper.JT_ROUND, pyclipper.ET_OPENROUND) toolCoverArea = of.Execute(toolRadiusScaled + 1)[0] cleared = expandClearedArea(cp, toolCoverArea, cleared) toClearPath = [] if cuttingArea > 0: # cut is OK, record it cumulativeCuttingArea = cumulativeCuttingArea + cuttingArea if time.time() - last_tool_gui_update > GUI_UPDATE_PERIOD: if SHOW_MARKERS: sceneClearPaths("TP") sceneDrawFilledTool("TP", newToolPos, scale_factor, (0, 0, 1)) sceneDrawToolDir("TP", newToolPos, newToolDir, scale_factor, (0, 0, 1)) sceneClearPaths("PTP") sceneDrawPath("PTP", passToolPath, scale_factor, (0, 0, 1)) # sceneClearPaths("CL_BOUNDED") # sceneDrawPaths("CL_BOUNDED", cleared_bounded,scale_factor,(1,1,0),True) last_tool_gui_update = time.time() #append next point to toolpath perf_total_len = perf_total_len + stepScaled if i == 0: passToolPath.append( toolPos) #append first point to toolpath passToolPath.append(newToolPos) toolPos = newToolPos gyro.append(newToolDir) gyro.pop(0) no_cut_count = 0 else: #no cut #print "no cut:", no_cut_count no_cut_count = no_cut_count + 1 newToolDir = toolDir break # if no_cut_count > 1: # print "break: no cut" # break if isOutside: # next valid cut not found #print "Breaking: reached boundary" break #distToBoundary = distToBoundary-stepScaled #END OF POINTS INTERATION #expand cleared area if len(toClearPath) > 0: of.Clear() of.AddPath(toClearPath, pyclipper.JT_ROUND, pyclipper.ET_OPENROUND) toolCoverArea = of.Execute(toolRadiusScaled + 1)[0] cleared = expandClearedArea(cp, toolCoverArea, cleared) toClearPath = [] if cumulativeCuttingArea > stepScaled * stepOver * referenceCutArea / 40: #did we cut something significant? sceneClearPaths("PTP") sceneDrawPath("TOOLPATH", passToolPath, scale_factor) passToolPath = GeomUtils.cleanPath(passToolPath, CLEAN_PATH_TOLERANCE) appendToolPathCheckCollision(of, cp, toolPaths, passToolPath, toolRadiusScaled, cleared) if over_cut_count > 5: Console.PrintError( "Break: WARNING: resulting toolpath may be incomplete! (Hint: try changing numeric precision or StepOver)\n" ) break #FIND NEXT ENGAGING POINT if firstEngagePoint: EngagementPoint.moveToClosestPoint(newToolPos) firstEngagePoint = False else: moveDistance = stepOverDistanceScaled / 4 + 1 if not EngagementPoint.moveForwardToCutThreshold( cleared, moveDistance, moveDistance * 2, 1.5 * optimalCutAreaPerDist * moveDistance): break #clear around the engagement point toolPos, toolDir = EngagementPoint.getCurrentPoint() performance = 1.0 * perf_total_len / scale_factor / ( time.time() - perf_start_time) # mm/s Console.PrintMessage( "Passes: %s, Toolpaths: %d, Output Points: %d, Processed Points: %d, Avg.Iterations: %f, Exceeded: %d, Perf.: %f mm/s, Cut shape cache hit: %d/%d (%f perc.)\n" % (pas, len(toolPaths), output_point_count, total_point_count, 1.0 * total_iteration_count / total_point_count, iteration_limit_count, performance, cache_hit_count, cache_pot_count, 100 * cache_hit_count / (cache_pot_count + 0.1))) #generate finishing pass sceneUpdateGui() passToolPath = [] of.Clear() of.AddPaths(feat, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) finishing = of.Execute2(-toolRadiusScaled) # add only paths containing the startPoint and their childs for child in finishing.Childs: if pyclipper.PointInPolygon(startPoint, child.Contour) != 0: appendToolPathCheckCollision( of, cp, toolPaths, GeomUtils.cleanPath(child.Contour, CLEAN_PATH_TOLERANCE / 2), toolRadiusScaled, cleared, True) for hole in child.Childs: appendToolPathCheckCollision( of, cp, toolPaths, GeomUtils.cleanPath(hole.Contour, CLEAN_PATH_TOLERANCE / 2), toolRadiusScaled, cleared, True) # append the return to the initial point (and check collision) if len(toolPaths) > 0 and len(toolPaths[0]) > 0: appendToolPathCheckCollision( of, cp, toolPaths, [[toolPaths[0][0][0], toolPaths[0][0][1]]], toolRadiusScaled, cleared, True) except: sceneClean() raise sceneClean() return toolPaths, startPoint