def raster_area(edgepath, delta, ymin1, ymax1, xmin1, xmax1): # # raster a 2D region # # find row-edge intersections # xyscale = float(sxyscale.get()) overlap = float(soverlap.get()) tooldia = (float(sdia.get()) / xyscale) * (10 ** gerber_data.fraction) rastlines = [] starty = ymin1 endy = ymax1 startx = round(xmax1, 2) endx = round(xmin1, 2) numrows = int(math.floor((endy - starty) / (tooldia * overlap))) crast = pyclipper.Pyclipper() edgepath = offset_poly(edgepath, delta) result = [] for row in range(numrows + 1): rastlines.append([]) rastlines[row].append([startx, round((starty + row * (tooldia * overlap)), 4)]) rastlines[row].append([endx, round((starty + row * (tooldia * overlap)), 4)]) startx, endx = endx, startx crast.AddPaths(pcb_edges, pyclipper.PT_CLIP,True) crast.AddPaths(rastlines, pyclipper.PT_SUBJECT, False) rastlines = crast.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) crast.Clear() rastlines = pyclipper.PolyTreeToPaths(rastlines) ## crast.AddPaths(edgepath, pyclipper.PT_CLIP, True) crast.AddPaths(rastlines, pyclipper.PT_SUBJECT, False) rastlines = crast.Execute2(pyclipper.CT_DIFFERENCE, pyclipper.PFT_POSITIVE, pyclipper.PFT_POSITIVE) crast.Clear() rastlines = pyclipper.PolyTreeToPaths(rastlines) # polyclip.sort(key=lambda x: (x[0][1],x[0][0])) # polyclip.sort(key=lambda x: x[0][1]) # polyclip.sort(key=lambda x: x[0][0]) rastltor = [] rastrtol = [] for segs in rastlines: if (segs[0][0] < segs[1][0]): rastltor.append(segs) else: rastrtol.append(segs) rastltor.sort(key=lambda x: (x[0][1], x[0][0])) rastrtol.sort(key=lambda x: (x[0][1], -x[0][0])) result.extend(rastltor) result.extend(rastrtol) result.sort(key=lambda x: x[0][1]) return result
def inter_layers(_subj, _clip, closed): if len(_clip) == 0: return [] pc = pyclipper.Pyclipper() try: pc.AddPaths(_clip, pyclipper.PT_CLIP, True) except: raise RuntimeError try: pc.AddPaths(_subj, pyclipper.PT_SUBJECT, closed) except: raise RuntimeError if closed: solution = pc.Execute(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) else: solution = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) solution = pyclipper.PolyTreeToPaths(solution) return solution
def hatches(self, l=10, st=0.1, num=0): self.clean() b0 = self.bounds() b = [b0[0] - l, b0[1] - l, b0[2] + l, b0[3] + l] h = [] ls = l / math.sqrt(2) sts = st / math.sqrt(2) i = 0 if num % 2 == 0: x0 = b[0] while x0 < b[2] + b[3] - b[1]: x = x0 y = b[1] while x > b[0] and y < b[3]: x -= sts y += sts if i % 2 == 0: h.append([[x, y], [x - ls, y - ls]]) else: h.append([[x - ls, y - ls], [x, y]]) i += 1 x0 += l * math.sqrt(2) else: x0 = b[0] - (b[3] - b[1]) while x0 < b[2]: x = x0 y = b[1] while y < b[3] and x < b[2]: x += sts y += sts if i % 2 == 0: h.append([[x, y], [x + ls, y - ls]]) else: h.append([[x + ls, y - ls], [x, y]]) i += 1 x0 += l * math.sqrt(2) def check_sp(sp, b): return b[0] < sp[0][0] < b[2] and b[1] < sp[0][1] < b[3] or b[ 0] < sp[1][0] < b[2] and b[1] < sp[1][1] < b[3] h = [sp for sp in h if check_sp(sp, b)] pc = pyclipper.Pyclipper() pc.AddPaths(pyclipper.scale_to_clipper(self.items), pyclipper.PT_CLIP, True) pc.AddPaths(pyclipper.scale_to_clipper(h), pyclipper.PT_SUBJECT, False) solution = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) h = pyclipper.scale_from_clipper(pyclipper.PolyTreeToPaths(solution)) return Path(h)
def clip(subj, clip_paths, subj_closed=True): pc = pyclipper.Pyclipper() if subj: subj = pyclipper.scale_to_clipper(subj, SCALING_FACTOR) pc.AddPaths(subj, pyclipper.PT_SUBJECT, subj_closed) if clip_paths: clip_paths = pyclipper.scale_to_clipper(clip_paths, SCALING_FACTOR) pc.AddPaths(clip_paths, pyclipper.PT_CLIP, True) out_tree = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) outpaths = pyclipper.PolyTreeToPaths(out_tree) outpaths = pyclipper.scale_from_clipper(outpaths, SCALING_FACTOR) return outpaths
def angusj_path(subj, clip): pc = pyclipper.Pyclipper() pc.AddPath(subj, pyclipper.PT_SUBJECT, False) pc.AddPath(clip, pyclipper.PT_CLIP, True) solution = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) path = pyclipper.PolyTreeToPaths(solution) #TODO: Add check here. return path
def get_intersects(self): scans = [[tuple(point) for point in line] for line in self.scan_list] poly = self.polygon pco = pyclipper.Pyclipper() pco.AddPaths(pyclipper.scale_to_clipper(scans), pyclipper.PT_SUBJECT, closed = False) #polygon = pyclipper.CleanPolygon(polygon, distance = 0.00005) if self.free_form == False: clip_poly = self._offset_profile(poly) elif self.free_form == True: clip_poly = poly else: raise Exception("Not a valid build type") pco.AddPath(pyclipper.scale_to_clipper(clip_poly), pyclipper.PT_CLIP, True) solution = pco.Execute2(pyclipper.CT_INTERSECTION) self.intersects = pyclipper.scale_from_clipper(pyclipper.PolyTreeToPaths(solution)) #and sort intersects (bottom left to top right) self.intersects = self._sort_paths(self.intersects) return self.intersects
def union_layers(_subj, _clip, closed): pc = pyclipper.Pyclipper() pc.AddPaths(_clip, pyclipper.PT_CLIP, True) try: pc.AddPaths(_subj, pyclipper.PT_SUBJECT, closed) except: raise RuntimeError if closed: solution = pc.Execute(pyclipper.CT_UNION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) else: solution = pc.Execute2(pyclipper.CT_UNION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) solution = pyclipper.PolyTreeToPaths(solution) return solution
def makeClipper(subjs, clip, clipperTypeStr, s_fill_key="positive", c_fill_key="positive", subjClosed=True, s_multi=False, c_multi=False): ''' :param subjs: :param clip: window to be used for the cut :param clipperTypeStr: :param s_fill_key: :param c_fill_key: :param subjClosed: :param s_multi: :param c_multi: :return: ''' pc = pyclipper.Pyclipper() scaledSubj = pyclipper.scale_to_clipper(subjs) scaledClip = pyclipper.scale_to_clipper(clip) # print("clip",scaledClip) clipperType = CLIPPER_TYPE[clipperTypeStr] if s_multi: pc.AddPaths(scaledSubj, pyclipper.PT_SUBJECT, subjClosed) else: pc.AddPath(scaledSubj, pyclipper.PT_SUBJECT, subjClosed) if c_multi: pc.AddPaths(scaledClip, pyclipper.PT_CLIP, True) else: pc.AddPath(scaledClip, pyclipper.PT_CLIP, True) fill1 = FILL_TYPE[s_fill_key] fill2 = FILL_TYPE[c_fill_key] flattenPaths = [] solution = pc.Execute2(clipperType, fill1, fill2) paths = pyclipper.PolyTreeToPaths(solution) # print(paths) for p in paths: flattenPaths.append(pyclipper.scale_from_clipper(p)) return flattenPaths
def test_polytree_to_paths(self): paths = pyclipper.PolyTreeToPaths(self.tree) self.check_paths(paths, 4)
import pyclipper pc = pyclipper.Pyclipper() # Add a single line as the subject. pc.AddPath([(-1, -1), (2, 1)], pyclipper.PT_SUBJECT, False) # Add a square as the clipping region. pc.AddPath([(0, 0), (1, 0), (1, 1), (0, 1)], pyclipper.PT_CLIP, True) # Clip the line using the rectangle. solution = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) print(pyclipper.PolyTreeToPaths(solution))
def voronoi_function(list_players, list_opponents, field): points = [] for o in list_opponents: points.append(o.getLocation()) for p in list_players: points.append(p.getLocation()) if len(list_players + list_opponents) == 0: return if len(list_players + list_opponents) == 1: field = [ QPoint(field[0][0], field[0][1]), QPoint(field[1][0], field[1][1]), QPoint(field[2][0], field[2][1]), QPoint(field[3][0], field[3][1]) ] if list_players: list_players[0].polygon.setPolygon(QPolygonF(QPolygon(field))) return if list_opponents: list_opponents[0].polygon.setPolygon(QPolygonF(QPolygon(field))) return if len(points) == 2: startpoints = np.array(points) mitte = startpoints.mean(axis=0) #mitte = [(points[0][0]+points[1][0])/2, (points[0][1]+points[1][1])/2] v = startpoints[0] - startpoints[1] if (v[1] == 0): n = np.array([0, 1]) else: n = np.array([1, -v[0] / v[1]]) p1 = mitte + 5000 * n p2 = mitte + (-5000) * n pc = pyclipper.Pyclipper() pc.AddPath(field, pyclipper.PT_CLIP, True) pc.AddPath([p1, p2], pyclipper.PT_SUBJECT, False) line = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) line = pyclipper.PolyTreeToPaths(line) firstPoly, secondPoly = splitPolygon(line[0], field) firstpol = QPolygon() secondpol = QPolygon() for p in firstPoly: firstpol.append(QPoint(p[0], p[1])) for p in secondPoly: secondpol.append(QPoint(p[0], p[1])) for p in list_opponents + list_players: if firstpol.containsPoint( QPoint(p.getLocation()[0], p.getLocation()[1]), Qt.OddEvenFill): p.polygon.setPolygon(QPolygonF(firstpol)) if secondpol.containsPoint( QPoint(p.getLocation()[0], p.getLocation()[1]), Qt.OddEvenFill): p.polygon.setPolygon(QPolygonF(secondpol)) return pointArray = np.asarray(points) vor = Voronoi(pointArray) vertices = vor.ridge_vertices eckpunkte = vor.vertices #print("Points: " + str(vor.points)) #print("Ridge_Vertices: " + str(vertices)) #print("Vertices: " + str(eckpunkte.tolist())) #print("Ridge_Points: " + str(vor.ridge_points)) #print("Regions: " + str(vor.regions)) #print("Point_regions" + str(vor.point_region)) pointidx = 0 val = [] for point in vor.points: ##iteration über punkte lines = [] poly = [] schnittpunkte = [] far_line = [] regions = vor.regions regionidx = vor.point_region.tolist()[pointidx] if min(regions[regionidx], default=-1) >= 0: ##behandlung falls polygon geschlossen for vidx in regions[regionidx]: poly.append([vor.vertices[vidx][0], vor.vertices[vidx][1]]) pc = pyclipper.Pyclipper() pc.AddPath(field, pyclipper.PT_CLIP, True) pc.AddPath(poly, pyclipper.PT_SUBJECT, True) poly = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) poly = pyclipper.PolyTreeToPaths( poly) #Hinzufügen der Eckpunkte der closed Polygone poly = poly[0] else: ridgeidx = 0 field_copy = deepcopy(field) for punkte_paar in vor.ridge_points: ##iteration über die kanten die aus dem punkt gebildet werden punkte_paar = np.asarray(punkte_paar) if np.any(punkte_paar == pointidx): if min(vor.ridge_vertices[ridgeidx] ) >= 0: ##definierte linien des offenen polygons li = [ vor.vertices[vor.ridge_vertices[ridgeidx] [0]].tolist(), vor.vertices[vor.ridge_vertices[ridgeidx] [1]].tolist() ] lines.append([li[0], li[1]]) val.append(li) else: ##für offenes polygon center = pointArray.mean(axis=0) v = vor.vertices[vor.ridge_vertices[ridgeidx]][ 1] # finite end Voronoi vertex ausgangspunkt1 = pointArray[punkte_paar[1]] ausgangspunkt2 = pointArray[punkte_paar[0]] t = ausgangspunkt1 - ausgangspunkt2 # tangent x = np.linalg.norm(t) t = t / x n = np.array([-t[1], t[0]]) # normal midpoint = pointArray[punkte_paar].mean(axis=0) far_point = v + np.sign(np.dot(midpoint - center, n)) * n * 50000 p1 = [v[0], v[1]] p2 = [far_point[0], far_point[1]] far_line.append(p2) line = [p1, p2] lines.append(line) ridgeidx += 1 if lines: poly = ltp(lines) pc = pyclipper.Pyclipper() pc.AddPath(field, pyclipper.PT_CLIP, True) pc.AddPath(poly, pyclipper.PT_SUBJECT, True) poly = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) poly = pyclipper.PolyTreeToPaths(poly) if poly: poly = poly[0] pc = pyclipper.Pyclipper() pc.AddPath(field, pyclipper.PT_CLIP, True) pc.AddPath(far_line, pyclipper.PT_SUBJECT, False) intersect_far = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) intersect_far = pyclipper.PolyTreeToPaths(intersect_far) if intersect_far: intersect_far = intersect_far[0] for p in intersect_far: idx = poly.index(p) idx2 = idx + 1 if idx2 > len(poly) - 1: idx2 = -1 if poly[idx2] in intersect_far: iscp = poly[idx - 1] elif poly[idx - 1] in intersect_far: iscp = poly[idx2] for eck in field: if checkPointOnLine(p, [iscp, eck]): poly.pop(idx) poly.insert(idx, eck) add_player_poly(poly, pointidx, list_players, list_opponents) pointidx += 1 return val
def _process(self, image, cnt): try: # generate an inverted, single channel image. Normally OpenCV detect on "white"....but we # want use "black" as contour foundation # single_channel = 255 - cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # determine the contour # https://docs.opencv.org/3.4.0/d9/d8b/tutorial_py_contours_hierarchy.html # We need the "two level" of hierarchy for the contour to perform clipping. # The hierarchy is stored in that pattern: [Next, Previous, First_Child, Parent] # cnt, hierarchy = cv2.findContours(single_channel, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) hierarchy = hierarchy[0] print(hierarchy) validated_cnt = normalize_contour(cnt) # scale the contour to the user defined width if we have any # if len(validated_cnt) > 0: # Determine the bounding rectangle x, y, w, h = cv2.boundingRect(np.concatenate(validated_cnt)) # Ensure that width of the contour is the same as the width_in_mm. # Scale the contour to the required width. scale_factor = self.width_in_micro_m / w scaled_cnt = [ np.multiply(c.astype(np.float), [scale_factor, scale_factor]).astype(np.int32) for c in validated_cnt ] # generate a hatch pattern we want to apply pattern = self.build_hatch_pattern(scaled_cnt) hatch_cnt = [] for parent_i in range(len(hierarchy)): # [Next, Previous, First_Child, Parent] # Process the parents only. It is a Parent if "Parent" is "-1". parent_h = hierarchy[parent_i] if parent_h[3] == -1: clip_cnt = [scaled_cnt[parent_i].tolist()] # append all child contours of the parent for child_i in range(len(hierarchy)): child_h = hierarchy[child_i] if child_h[3] == parent_i: clip_cnt.append(scaled_cnt[child_i].tolist()) # clip the hatch with the calculated "clipping region" # try: pc = pyclipper.Pyclipper() pc.AddPaths(clip_cnt, pyclipper.PT_CLIP, True) pc.AddPaths(pattern, pyclipper.PT_SUBJECT, False) solution = pc.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) # add the clipped hatch to the overall contour result hatch = pyclipper.PolyTreeToPaths(solution) for c in hatch: hatch_cnt.append(np.array(c)) except: pass # h_cnt = scaled_cnt.copy() scaled_cnt = hatch_cnt + scaled_cnt # generate a preview image preview_image = np.zeros(image.shape, dtype="uint8") preview_image.fill(255) # draw some debug information #i = 0 #h_cnt = contour_into_image(h_cnt, preview_image) #for c in h_cnt: # cv2.putText(preview_image, str(i), (c[0][0], c[0][1]), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2) # i = i + 1 # place the contour into the center of the image preview_cnt = contour_into_image(scaled_cnt, preview_image) x, y, w, h = cv2.boundingRect(np.concatenate(preview_cnt)) # draw the outline contour in yellow cv2.drawContours(preview_image, preview_cnt, -1, (60, 169, 242), 1) display_factor = 0.001 if self.display_unit == "mm" else 0.0001 # draw the width dimension cv2.line(preview_image, (x, y + int(h / 2)), (x + w, y + int(h / 2)), (255, 0, 0), 1) cv2.circle(preview_image, (x, y + int(h / 2)), 5, (255, 0, 0), -1) cv2.circle(preview_image, (x + w, y + int(h / 2)), 5, (255, 0, 0), -1) cv2.putText( preview_image, "{:.1f} {}".format(self.width_in_micro_m * display_factor, self.display_unit), (x + 20, y + int(h / 2) - 30), cv2.FONT_HERSHEY_SIMPLEX, 1.65, (255, 0, 0), 4) # draw the height dimension height_in_micro_m = self.width_in_micro_m / w * h cv2.line(preview_image, (x + int(w / 2), y), (x + int(w / 2), y + h), (255, 0, 0), 1) cv2.circle(preview_image, (x + int(w / 2), y), 5, (255, 0, 0), -1) cv2.circle(preview_image, (x + int(w / 2), y + h), 5, (255, 0, 0), -1) cv2.putText( preview_image, "{:.1f} {}".format(height_in_micro_m * display_factor, self.display_unit), (x + int(w / 2) + 20, y + 50), cv2.FONT_HERSHEY_SIMPLEX, 1.65, (255, 0, 0), 4) image = preview_image validated_cnt = scaled_cnt except Exception as exc: exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] print(exc_type, fname, exc_tb.tb_lineno) print(type(self), exc) return image, validated_cnt
def get_path(bound, holes, path): pc = pyclipper.Pyclipper() pc.AddPath(path, pyclipper.PT_SUBJECT, False) pc.AddPath(bound, pyclipper.PT_CLIP, True) result = pyclipper.PolyTreeToPaths(pc.Execute2(pyclipper.CT_DIFFERENCE,\ pyclipper.PFT_EVENODD,\ pyclipper.PFT_EVENODD)) result_size = len(result) if result_size == 1: # cross once the bound middle_point = get_middle_point([result[0][0], result[0][1]]) vetctor = get_vector([result[0][0], result[0][1]]) point = get_inside_point(middle_point, vetctor, bound) return get_path(bound, holes, [path[0], point]) + \ get_path(bound, holes, [point, path[1]])[1:] elif result_size > 1: # cross multiple times the bound middle_point = None solution = [path[0]] previous_middle_point = path[0] for i in range(0, result_size-1): middle_point = get_middle_point([result[i][1], result[i+1][0]]) vetctor = get_vector([result[i][1], result[i+1][0]]) for hole in holes: if pyclipper.PointInPolygon(middle_point, hole): middle_point = get_outside_point(middle_point, vetctor, hole) break solution += get_path(bound, holes, [previous_middle_point, middle_point])[1:] previous_middle_point = middle_point solution += get_path(bound, holes, [previous_middle_point, path[1]])[1:] return solution pc.Clear() pc.AddPath(path, pyclipper.PT_SUBJECT, False) for hole in holes: pc.AddPath(hole, pyclipper.PT_CLIP, True) result = pyclipper.PolyTreeToPaths(pc.Execute2(pyclipper.CT_DIFFERENCE, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD)) result_size = len(result) if result_size == 2: # cross only one hole once for hole in holes: # seek the crossing hole pc.Clear() pc.AddPath(path, pyclipper.PT_SUBJECT, False) pc.AddPath(hole, pyclipper.PT_CLIP, True) result = pyclipper.PolyTreeToPaths( pc.Execute2(pyclipper.CT_DIFFERENCE, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD)) result_size = len(result) if result_size == 2: middle_point = get_middle_point([result[0][1], result[1][0]]) vetctor = get_vector([result[0][1], result[1][0]]) point = get_outside_point(middle_point, vetctor, hole) return get_path(bound, holes, [path[0], point]) + \ get_path(bound, holes, [point, path[1]])[1:] elif result_size > 2: # cross multipleholes or one hole multiple times middle_point = None solution = [path[0]] previous_middle_point = path[0] for i in range(1, result_size - 1): middle_point = get_middle_point([result[i][0], result[i][1]]) solution += get_path(bound, holes, [previous_middle_point, middle_point])[1:] previous_middle_point = middle_point solution += get_path(bound, holes, [previous_middle_point, path[1]])[1:] return solution return path
def raster_area(self, edgepath, pcbedges, delta, ymin1, ymax1, xmin1, xmax1): # # raster a 2D region # # find row-edge intersections # overlap = float(self.rasteroverlapinput.GetValue()) tooldia = float(self.tooldiainput.GetValue()) / self.canvas.mousescale rastlines = [] starty = ymin1 endy = ymax1 startx = round(xmax1, 2) endx = round(xmin1, 2) numrows = int(math.floor((endy - starty) / (tooldia * overlap))) crast = pyclipper.Pyclipper() edgepath = self.offset_poly(edgepath, delta) result = [] for row in range(numrows + 1): rastlines.append([]) ty = round(starty + row * (tooldia * overlap), 4) rastlines[row].append([startx, ty]) rastlines[row].append([endx, ty]) startx, endx = endx, startx tmp = [] for i in range(numrows): tmp.append(rastlines[i][0][1]) crast.AddPaths(pcbedges, pyclipper.PT_CLIP,True) crast.AddPaths(rastlines, pyclipper.PT_SUBJECT, False) rastlines = crast.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) crast.Clear() rastlines = pyclipper.PolyTreeToPaths(rastlines) ## crast.AddPaths(edgepath, pyclipper.PT_CLIP, True) crast.AddPaths(rastlines, pyclipper.PT_SUBJECT, False) rastlines = crast.Execute2(pyclipper.CT_DIFFERENCE, pyclipper.PFT_POSITIVE, pyclipper.PFT_POSITIVE) crast.Clear() rastlines = pyclipper.PolyTreeToPaths(rastlines) # polyclip.sort(key=lambda x: (x[0][1],x[0][0])) # polyclip.sort(key=lambda x: x[0][1]) # polyclip.sort(key=lambda x: x[0][0]) rastltor = [] rastrtol = [] for segs in rastlines: if segs[0][0] < segs[1][0]: rastltor.append(segs) else: rastrtol.append(segs) rastltor.sort(key=lambda x: (x[0][1], x[0][0])) rastrtol.sort(key=lambda x: (x[0][1], -x[0][0])) result.extend(rastltor) result.extend(rastrtol) result.sort(key=lambda x: x[0][1]) return result