Пример #1
0
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
Пример #2
0
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)
Пример #3
0
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
Пример #4
0
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
Пример #5
0
    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
Пример #6
0
    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
Пример #7
0
    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
Пример #8
0
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
Пример #9
0
    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
        }
Пример #10
0
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)
Пример #11
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
Пример #12
0
    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)
Пример #13
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
        }
Пример #14
0
    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
        }
Пример #15
0
    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))
Пример #16
0
 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))
Пример #17
0
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