#!/usr/bin/env python
# coding=utf-8
#
# Copyright (C) 2005 Aaron Spike, aaron@ekips.org (super paths et al)
#               2007 hugomatic... (gcode.py)
#               2009 Nick Drobchenko, nick@cnc-club.ru (main developer)
#               2011 Chris Lusby Taylor, clusbytaylor@enterprise.net (engraving functions)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
"""
Comments starting "#LT" or "#CLT" are by Chris Lusby Taylor who rewrote the engraving function in 2011.
History of CLT changes to engraving and other functions it uses:
9 May 2011 Changed test of tool diameter to square it
10 May Note that there are many unused functions, including:
      bound_to_bound_distance, csp_curvature_radius_at_t,
        csp_special_points, csplength, rebuild_csp, csp_slope,
        csp_simple_bound_to_point_distance, csp_bound_to_point_distance,
        bez_at_t, bez_to_point_distance, bez_normalized_slope, matrix_mul, transpose
       Fixed csp_point_inside_bound() to work if x outside bounds
20 May Now encoding the bisectors of angles.
23 May Using r/cos(a) instead of normalised normals for bisectors of angles.
23 May Note that Z values generated for engraving are in pixels, not mm.
       Removed the biarc curves - straight lines are better.
24 May Changed Bezier slope calculation to be less sensitive to tiny differences in points.
       Added use of self.options.engraving_newton_iterations to control accuracy
25 May Big restructure and new recursive function.
       Changed the way I treat corners - I now find if the centre of a proposed circle is
                within the area bounded by the line being tested and the two angle bisectors at
            its ends. See get_radius_to_line().
29 May Eliminating redundant points. If A,B,C colinear, drop B
30 May Eliminating redundant lines in divided Beziers. Changed subdivision of lines
  7Jun Try to show engraving in 3D
 8 Jun Displaying in stereo 3D.
       Fixed a bug in bisect - it could go wrong due to rounding errors if
            1+x1.x2+y1.y2<0 which should never happen. BTW, I spotted a non-normalised normal
            returned by csp_normalized_normal. Need to check for that.
 9 Jun Corrected spelling of 'definition' but still match previous 'defention' and       'defenition' if found in file
     Changed get_tool to find 1.6.04 tools or new tools with corrected spelling
10 Jun Put 3D into a separate layer called 3D, created unless it already exists
       Changed csp_normalized_slope to reject lines shorter than 1e-9.
10 Jun Changed all dimensions seen by user to be mm/inch, not pixels. This includes
      tool diameter, maximum engraving distance, tool shape and all Z values.
12 Jun ver 208 Now scales correctly if orientation points moved or stretched.
12 Jun ver 209. Now detect if engraving toolshape not a function of radius
                Graphics now indicate Gcode toolpath, limited by min(tool diameter/2,max-dist)
24 Jan 2017 Removed hard-coded scale values from orientation point calculation
TODO Change line division to be recursive, depending on what line is touched. See line_divide
"""

__version__ = '1.7'

import cmath
import copy
import math
import os
import re
import sys
import time
from functools import partial

import numpy

import inkex
from inkex.bezier import bezierlength, bezierparameterize, beziertatlength
from inkex import Transform, PathElement, TextElement, Tspan, Group, Layer, Marker, CubicSuperPath, Style

if sys.version_info[0] > 2:
    xrange = range
    unicode = str

def ireplace(self, old, new, count=0):
    pattern = re.compile(re.escape(old), re.I)
    return re.sub(pattern, new, self, count)


################################################################################
#
# Styles and additional parameters
#
################################################################################

TAU = math.pi * 2
STRAIGHT_TOLERANCE = 0.0001
STRAIGHT_DISTANCE_TOLERANCE = 0.0001
ENGRAVING_TOLERANCE = 0.0001
LOFT_LENGTHS_TOLERANCE = 0.0000001

EMC_TOLERANCE_EQUAL = 0.00001

options = {}
defaults = {
    'header': """%
(Header)
(Generated by gcodetools from Inkscape.)
(Using default header. To add your own header create file "header" in the output dir.)
M3
(Header end.)
""",
    'footer': """
(Footer)
M5
G00 X0.0000 Y0.0000
M2
(Using default footer. To add your own footer create file "footer" in the output dir.)
(end)
%"""
}

INTERSECTION_RECURSION_DEPTH = 10
INTERSECTION_TOLERANCE = 0.00001

def marker_style(stroke, marker='DrawCurveMarker', width=1):
    """Set a marker style with some basic defaults"""
    return Style(stroke=stroke, fill='none', stroke_width=width,
                 marker_end='url(#{})'.format(marker))

MARKER_STYLE = {
    "in_out_path_style": marker_style('#0072a7', 'InOutPathMarker'),
    "loft_style": {
        'main curve': marker_style('#88f', 'Arrow2Mend'),
    },
    "biarc_style": {
        'biarc0': marker_style('#88f'),
        'biarc1': marker_style('#8f8'),
        'line': marker_style('#f88'),
        'area': marker_style('#777', width=0.1),
    },
    "biarc_style_dark": {
        'biarc0': marker_style('#33a'),
        'biarc1': marker_style('#3a3'),
        'line': marker_style('#a33'),
        'area': marker_style('#222', width=0.3),
    },
    "biarc_style_dark_area": {
        'biarc0': marker_style('#33a', width=0.1),
        'biarc1': marker_style('#3a3', width=0.1),
        'line': marker_style('#a33', width=0.1),
        'area': marker_style('#222', width=0.3),
    },
    "biarc_style_i": {
        'biarc0': marker_style('#880'),
        'biarc1': marker_style('#808'),
        'line': marker_style('#088'),
        'area': marker_style('#999', width=0.3),
    },
    "biarc_style_dark_i": {
        'biarc0': marker_style('#dd5'),
        'biarc1': marker_style('#d5d'),
        'line': marker_style('#5dd'),
        'area': marker_style('#aaa', width=0.3),
    },
    "biarc_style_lathe_feed": {
        'biarc0': marker_style('#07f', width=0.4),
        'biarc1': marker_style('#0f7', width=0.4),
        'line': marker_style('#f44', width=0.4),
        'area': marker_style('#aaa', width=0.3),
    },
    "biarc_style_lathe_passing feed": {
        'biarc0': marker_style('#07f', width=0.4),
        'biarc1': marker_style('#0f7', width=0.4),
        'line': marker_style('#f44', width=0.4),
        'area': marker_style('#aaa', width=0.3),
    },
    "biarc_style_lathe_fine feed": {
        'biarc0': marker_style('#7f0', width=0.4),
        'biarc1': marker_style('#f70', width=0.4),
        'line': marker_style('#744', width=0.4),
        'area': marker_style('#aaa', width=0.3),
    },
    "area artefact": Style(stroke='#ff0000', fill='#ffff00', stroke_width=1),
    "area artefact arrow": Style(stroke='#ff0000', fill='#ffff00', stroke_width=1),
    "dxf_points": Style(stroke="#ff0000", fill="#ff0000"),
}


################################################################################
# Gcode additional functions
################################################################################

def gcode_comment_str(s, replace_new_line=False):
    if replace_new_line:
        s = re.sub(r"[\n\r]+", ".", s)
    res = ""
    if s[-1] == "\n":
        s = s[:-1]
    for a in s.split("\n"):
        if a != "":
            res += "(" + re.sub(r"[\(\)\\\n\r]", ".", a) + ")\n"
        else:
            res += "\n"
    return res


################################################################################
# Cubic Super Path additional functions
################################################################################


def csp_from_polyline(line):
    return [[[point[:] for _ in range(3)] for point in subline] for subline in line]


def csp_remove_zero_segments(csp, tolerance=1e-7):
    res = []
    for subpath in csp:
        if len(subpath) > 0:
            res.append([subpath[0]])
            for sp1, sp2 in zip(subpath, subpath[1:]):
                if point_to_point_d2(sp1[1], sp2[1]) <= tolerance and point_to_point_d2(sp1[2], sp2[1]) <= tolerance and point_to_point_d2(sp1[1], sp2[0]) <= tolerance:
                    res[-1][-1][2] = sp2[2]
                else:
                    res[-1].append(sp2)
    return res


def point_inside_csp(p, csp, on_the_path=True):
    # we'll do the raytracing and see how many intersections are there on the ray's way.
    # if number of intersections is even then point is outside.
    # ray will be x=p.x and y=>p.y
    # you can assign any value to on_the_path, by default if point is on the path
    # function will return thai it's inside the path.
    x, y = p
    ray_intersections_count = 0
    for subpath in csp:

        for i in range(1, len(subpath)):
            sp1 = subpath[i - 1]
            sp2 = subpath[i]
            ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2)
            if ax == 0 and bx == 0 and cx == 0 and dx == x:
                # we've got a special case here
                b = csp_true_bounds([[sp1, sp2]])
                if b[1][1] <= y <= b[3][1]:
                    # points is on the path
                    return on_the_path
                else:
                    # we can skip this segment because it won't influence the answer.
                    pass
            else:
                for t in csp_line_intersection([x, y], [x, y + 5], sp1, sp2):
                    if t == 0 or t == 1:
                        # we've got another special case here
                        x1, y1 = csp_at_t(sp1, sp2, t)
                        if y1 == y:
                            # the point is on the path
                            return on_the_path
                        # if t == 0 we should have considered this case previously.
                        if t == 1:
                            # we have to check the next segment if it is on the same side of the ray
                            st_d = csp_normalized_slope(sp1, sp2, 1)[0]
                            if st_d == 0:
                                st_d = csp_normalized_slope(sp1, sp2, 0.99)[0]

                            for j in range(1, len(subpath) + 1):
                                if (i + j) % len(subpath) == 0:
                                    continue  # skip the closing segment
                                sp11 = subpath[(i - 1 + j) % len(subpath)]
                                sp22 = subpath[(i + j) % len(subpath)]
                                ax1, ay1, bx1, by1, cx1, cy1, dx1, dy1 = csp_parameterize(sp1, sp2)
                                if ax1 == 0 and bx1 == 0 and cx1 == 0 and dx1 == x:
                                    continue  # this segment parallel to the ray, so skip it
                                en_d = csp_normalized_slope(sp11, sp22, 0)[0]
                                if en_d == 0:
                                    en_d = csp_normalized_slope(sp11, sp22, 0.01)[0]
                                if st_d * en_d <= 0:
                                    ray_intersections_count += 1
                                    break
                    else:
                        x1, y1 = csp_at_t(sp1, sp2, t)
                        if y1 == y:
                            # the point is on the path
                            return on_the_path
                        else:
                            if y1 > y and 3 * ax * t ** 2 + 2 * bx * t + cx != 0:  # if it's 0 the path only touches the ray
                                ray_intersections_count += 1
    return ray_intersections_count % 2 == 1


def csp_close_all_subpaths(csp, tolerance=0.000001):
    for i in range(len(csp)):
        if point_to_point_d2(csp[i][0][1], csp[i][-1][1]) > tolerance ** 2:
            csp[i][-1][2] = csp[i][-1][1][:]
            csp[i] += [[csp[i][0][1][:] for _ in range(3)]]
        else:
            if csp[i][0][1] != csp[i][-1][1]:
                csp[i][-1][1] = csp[i][0][1][:]
    return csp


def csp_simple_bound(csp):
    minx = None
    miny = None
    maxx = None
    maxy = None

    for subpath in csp:
        for sp in subpath:
            for p in sp:
                minx = min(minx, p[0]) if minx is not None else p[0]
                miny = min(miny, p[1]) if miny is not None else p[1]
                maxx = max(maxx, p[0]) if maxx is not None else p[0]
                maxy = max(maxy, p[1]) if maxy is not None else p[1]
    return minx, miny, maxx, maxy


def csp_segment_to_bez(sp1, sp2):
    return sp1[1:] + sp2[:2]


def csp_to_point_distance(csp, p, dist_bounds=(0, 1e100)):
    min_dist = [1e100, 0, 0, 0]
    for j in range(len(csp)):
        for i in range(1, len(csp[j])):
            d = csp_seg_to_point_distance(csp[j][i - 1], csp[j][i], p, sample_points=5)
            if d[0] < dist_bounds[0]:
                return [d[0], j, i, d[1]]
            else:
                if d[0] < min_dist[0]:
                    min_dist = [d[0], j, i, d[1]]
    return min_dist


def csp_seg_to_point_distance(sp1, sp2, p, sample_points=5):
    ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2)
    dx = dx - p[0]
    dy = dy - p[1]
    if sample_points < 2:
        sample_points = 2
    d = min([(p[0] - sp1[1][0]) ** 2 + (p[1] - sp1[1][1]) ** 2, 0.], [(p[0] - sp2[1][0]) ** 2 + (p[1] - sp2[1][1]) ** 2, 1.])
    for k in range(sample_points):
        t = float(k) / (sample_points - 1)
        i = 0
        while i == 0 or abs(f) > 0.000001 and i < 20:
            t2 = t ** 2
            t3 = t ** 3
            f = (ax * t3 + bx * t2 + cx * t + dx) * (3 * ax * t2 + 2 * bx * t + cx) + (ay * t3 + by * t2 + cy * t + dy) * (3 * ay * t2 + 2 * by * t + cy)
            df = (6 * ax * t + 2 * bx) * (ax * t3 + bx * t2 + cx * t + dx) + (3 * ax * t2 + 2 * bx * t + cx) ** 2 + (6 * ay * t + 2 * by) * (ay * t3 + by * t2 + cy * t + dy) + (3 * ay * t2 + 2 * by * t + cy) ** 2
            if df != 0:
                t = t - f / df
            else:
                break
            i += 1
        if 0 <= t <= 1:
            p1 = csp_at_t(sp1, sp2, t)
            d1 = (p1[0] - p[0]) ** 2 + (p1[1] - p[1]) ** 2
            if d1 < d[0]:
                d = [d1, t]
    return d


def csp_seg_to_csp_seg_distance(sp1, sp2, sp3, sp4, dist_bounds=(0, 1e100), sample_points=5, tolerance=.01):
    # check the ending points first
    dist = csp_seg_to_point_distance(sp1, sp2, sp3[1], sample_points)
    dist += [0.]
    if dist[0] <= dist_bounds[0]:
        return dist
    d = csp_seg_to_point_distance(sp1, sp2, sp4[1], sample_points)
    if d[0] < dist[0]:
        dist = d + [1.]
        if dist[0] <= dist_bounds[0]:
            return dist
    d = csp_seg_to_point_distance(sp3, sp4, sp1[1], sample_points)
    if d[0] < dist[0]:
        dist = [d[0], 0., d[1]]
        if dist[0] <= dist_bounds[0]:
            return dist
    d = csp_seg_to_point_distance(sp3, sp4, sp2[1], sample_points)
    if d[0] < dist[0]:
        dist = [d[0], 1., d[1]]
        if dist[0] <= dist_bounds[0]:
            return dist
    sample_points -= 2
    if sample_points < 1:
        sample_points = 1
    ax1, ay1, bx1, by1, cx1, cy1, dx1, dy1 = csp_parameterize(sp1, sp2)
    ax2, ay2, bx2, by2, cx2, cy2, dx2, dy2 = csp_parameterize(sp3, sp4)
    #    try to find closes points using Newtons method
    for k in range(sample_points):
        for j in range(sample_points):
            t1 = float(k + 1) / (sample_points + 1)
            t2 = float(j) / (sample_points + 1)

            t12 = t1 * t1
            t13 = t1 * t1 * t1
            t22 = t2 * t2
            t23 = t2 * t2 * t2
            i = 0

            F1 = [0, 0]
            F2 = [[0, 0], [0, 0]]
            F = 1e100
            x = ax1 * t13 + bx1 * t12 + cx1 * t1 + dx1 - (ax2 * t23 + bx2 * t22 + cx2 * t2 + dx2)
            y = ay1 * t13 + by1 * t12 + cy1 * t1 + dy1 - (ay2 * t23 + by2 * t22 + cy2 * t2 + dy2)
            while i < 2 or abs(F - Flast) > tolerance and i < 30:
                f1x = 3 * ax1 * t12 + 2 * bx1 * t1 + cx1
                f1y = 3 * ay1 * t12 + 2 * by1 * t1 + cy1
                f2x = 3 * ax2 * t22 + 2 * bx2 * t2 + cx2
                f2y = 3 * ay2 * t22 + 2 * by2 * t2 + cy2
                F1[0] = 2 * f1x * x + 2 * f1y * y
                F1[1] = -2 * f2x * x - 2 * f2y * y
                F2[0][0] = 2 * (6 * ax1 * t1 + 2 * bx1) * x + 2 * f1x * f1x + 2 * (6 * ay1 * t1 + 2 * by1) * y + 2 * f1y * f1y
                F2[0][1] = -2 * f1x * f2x - 2 * f1y * f2y
                F2[1][0] = -2 * f2x * f1x - 2 * f2y * f1y
                F2[1][1] = -2 * (6 * ax2 * t2 + 2 * bx2) * x + 2 * f2x * f2x - 2 * (6 * ay2 * t2 + 2 * by2) * y + 2 * f2y * f2y
                F2 = inv_2x2(F2)
                if F2 is not None:
                    t1 -= (F2[0][0] * F1[0] + F2[0][1] * F1[1])
                    t2 -= (F2[1][0] * F1[0] + F2[1][1] * F1[1])
                    t12 = t1 * t1
                    t13 = t1 * t1 * t1
                    t22 = t2 * t2
                    t23 = t2 * t2 * t2
                    x = ax1 * t13 + bx1 * t12 + cx1 * t1 + dx1 - (ax2 * t23 + bx2 * t22 + cx2 * t2 + dx2)
                    y = ay1 * t13 + by1 * t12 + cy1 * t1 + dy1 - (ay2 * t23 + by2 * t22 + cy2 * t2 + dy2)
                    Flast = F
                    F = x * x + y * y
                else:
                    break
                i += 1
            if F < dist[0] and 0 <= t1 <= 1 and 0 <= t2 <= 1:
                dist = [F, t1, t2]
                if dist[0] <= dist_bounds[0]:
                    return dist
    return dist


def csp_to_csp_distance(csp1, csp2, dist_bounds=(0, 1e100), tolerance=.01):
    dist = [1e100, 0, 0, 0, 0, 0, 0]
    for i1 in range(len(csp1)):
        for j1 in range(1, len(csp1[i1])):
            for i2 in range(len(csp2)):
                for j2 in range(1, len(csp2[i2])):
                    d = csp_seg_bound_to_csp_seg_bound_max_min_distance(csp1[i1][j1 - 1], csp1[i1][j1], csp2[i2][j2 - 1], csp2[i2][j2])
                    if d[0] >= dist_bounds[1]:
                        continue
                    if d[1] < dist_bounds[0]:
                        return [d[1], i1, j1, 1, i2, j2, 1]
                    d = csp_seg_to_csp_seg_distance(csp1[i1][j1 - 1], csp1[i1][j1], csp2[i2][j2 - 1], csp2[i2][j2], dist_bounds, tolerance=tolerance)
                    if d[0] < dist[0]:
                        dist = [d[0], i1, j1, d[1], i2, j2, d[2]]
                    if dist[0] <= dist_bounds[0]:
                        return dist
            if dist[0] >= dist_bounds[1]:
                return dist
    return dist


def csp_split(sp1, sp2, t=.5):
    [x1, y1] = sp1[1]
    [x2, y2] = sp1[2]
    [x3, y3] = sp2[0]
    [x4, y4] = sp2[1]
    x12 = x1 + (x2 - x1) * t
    y12 = y1 + (y2 - y1) * t
    x23 = x2 + (x3 - x2) * t
    y23 = y2 + (y3 - y2) * t
    x34 = x3 + (x4 - x3) * t
    y34 = y3 + (y4 - y3) * t
    x1223 = x12 + (x23 - x12) * t
    y1223 = y12 + (y23 - y12) * t
    x2334 = x23 + (x34 - x23) * t
    y2334 = y23 + (y34 - y23) * t
    x = x1223 + (x2334 - x1223) * t
    y = y1223 + (y2334 - y1223) * t
    return [sp1[0], sp1[1], [x12, y12]], [[x1223, y1223], [x, y], [x2334, y2334]], [[x34, y34], sp2[1], sp2[2]]


def csp_true_bounds(csp):
    # Finds minx,miny,maxx,maxy of the csp and return their (x,y,i,j,t)
    minx = [float("inf"), 0, 0, 0]
    maxx = [float("-inf"), 0, 0, 0]
    miny = [float("inf"), 0, 0, 0]
    maxy = [float("-inf"), 0, 0, 0]
    for i in range(len(csp)):
        for j in range(1, len(csp[i])):
            ax, ay, bx, by, cx, cy, x0, y0 = bezierparameterize((csp[i][j - 1][1], csp[i][j - 1][2], csp[i][j][0], csp[i][j][1]))
            roots = cubic_solver(0, 3 * ax, 2 * bx, cx) + [0, 1]
            for root in roots:
                if type(root) is complex and abs(root.imag) < 1e-10:
                    root = root.real
                if type(root) is not complex and 0 <= root <= 1:
                    y = ay * (root ** 3) + by * (root ** 2) + cy * root + y0
                    x = ax * (root ** 3) + bx * (root ** 2) + cx * root + x0
                    maxx = max([x, y, i, j, root], maxx)
                    minx = min([x, y, i, j, root], minx)

            roots = cubic_solver(0, 3 * ay, 2 * by, cy) + [0, 1]
            for root in roots:
                if type(root) is complex and root.imag == 0:
                    root = root.real
                if type(root) is not complex and 0 <= root <= 1:
                    y = ay * (root ** 3) + by * (root ** 2) + cy * root + y0
                    x = ax * (root ** 3) + bx * (root ** 2) + cx * root + x0
                    maxy = max([y, x, i, j, root], maxy)
                    miny = min([y, x, i, j, root], miny)
    maxy[0], maxy[1] = maxy[1], maxy[0]
    miny[0], miny[1] = miny[1], miny[0]

    return minx, miny, maxx, maxy


############################################################################
# csp_segments_intersection(sp1,sp2,sp3,sp4)
#
# Returns array containing all intersections between two segments of cubic
# super path. Results are [ta,tb], or [ta0, ta1, tb0, tb1, "Overlap"]
# where ta, tb are values of t for the intersection point.
############################################################################
def csp_segments_intersection(sp1, sp2, sp3, sp4):
    a = csp_segment_to_bez(sp1, sp2)
    b = csp_segment_to_bez(sp3, sp4)

    def polish_intersection(a, b, ta, tb, tolerance=INTERSECTION_TOLERANCE):
        ax, ay, bx, by, cx, cy, dx, dy = bezierparameterize(a)
        ax1, ay1, bx1, by1, cx1, cy1, dx1, dy1 = bezierparameterize(b)
        i = 0
        F = [.0, .0]
        F1 = [[.0, .0], [.0, .0]]
        while i == 0 or (abs(F[0]) ** 2 + abs(F[1]) ** 2 > tolerance and i < 10):
            ta3 = ta ** 3
            ta2 = ta ** 2
            tb3 = tb ** 3
            tb2 = tb ** 2
            F[0] = ax * ta3 + bx * ta2 + cx * ta + dx - ax1 * tb3 - bx1 * tb2 - cx1 * tb - dx1
            F[1] = ay * ta3 + by * ta2 + cy * ta + dy - ay1 * tb3 - by1 * tb2 - cy1 * tb - dy1
            F1[0][0] = 3 * ax * ta2 + 2 * bx * ta + cx
            F1[0][1] = -3 * ax1 * tb2 - 2 * bx1 * tb - cx1
            F1[1][0] = 3 * ay * ta2 + 2 * by * ta + cy
            F1[1][1] = -3 * ay1 * tb2 - 2 * by1 * tb - cy1
            det = F1[0][0] * F1[1][1] - F1[0][1] * F1[1][0]
            if det != 0:
                F1 = [[F1[1][1] / det, -F1[0][1] / det], [-F1[1][0] / det, F1[0][0] / det]]
                ta = ta - (F1[0][0] * F[0] + F1[0][1] * F[1])
                tb = tb - (F1[1][0] * F[0] + F1[1][1] * F[1])
            else:
                break
            i += 1

        return ta, tb

    def recursion(a, b, ta0, ta1, tb0, tb1, depth_a, depth_b):
        global bezier_intersection_recursive_result
        if a == b:
            bezier_intersection_recursive_result += [[ta0, tb0, ta1, tb1, "Overlap"]]
            return
        tam = (ta0 + ta1) / 2
        tbm = (tb0 + tb1) / 2
        if depth_a > 0 and depth_b > 0:
            a1, a2 = bez_split(a, 0.5)
            b1, b2 = bez_split(b, 0.5)
            if bez_bounds_intersect(a1, b1):
                recursion(a1, b1, ta0, tam, tb0, tbm, depth_a - 1, depth_b - 1)
            if bez_bounds_intersect(a2, b1):
                recursion(a2, b1, tam, ta1, tb0, tbm, depth_a - 1, depth_b - 1)
            if bez_bounds_intersect(a1, b2):
                recursion(a1, b2, ta0, tam, tbm, tb1, depth_a - 1, depth_b - 1)
            if bez_bounds_intersect(a2, b2):
                recursion(a2, b2, tam, ta1, tbm, tb1, depth_a - 1, depth_b - 1)
        elif depth_a > 0:
            a1, a2 = bez_split(a, 0.5)
            if bez_bounds_intersect(a1, b):
                recursion(a1, b, ta0, tam, tb0, tb1, depth_a - 1, depth_b)
            if bez_bounds_intersect(a2, b):
                recursion(a2, b, tam, ta1, tb0, tb1, depth_a - 1, depth_b)
        elif depth_b > 0:
            b1, b2 = bez_split(b, 0.5)
            if bez_bounds_intersect(a, b1):
                recursion(a, b1, ta0, ta1, tb0, tbm, depth_a, depth_b - 1)
            if bez_bounds_intersect(a, b2):
                recursion(a, b2, ta0, ta1, tbm, tb1, depth_a, depth_b - 1)
        else:  # Both segments have been subdivided enough. Let's get some intersections :).
            intersection, t1, t2 = straight_segments_intersection([a[0]] + [a[3]], [b[0]] + [b[3]])
            if intersection:
                if intersection == "Overlap":
                    t1 = (max(0, min(1, t1[0])) + max(0, min(1, t1[1]))) / 2
                    t2 = (max(0, min(1, t2[0])) + max(0, min(1, t2[1]))) / 2
                bezier_intersection_recursive_result += [[ta0 + t1 * (ta1 - ta0), tb0 + t2 * (tb1 - tb0)]]

    global bezier_intersection_recursive_result
    bezier_intersection_recursive_result = []
    recursion(a, b, 0., 1., 0., 1., INTERSECTION_RECURSION_DEPTH, INTERSECTION_RECURSION_DEPTH)
    intersections = bezier_intersection_recursive_result
    for i in range(len(intersections)):
        if len(intersections[i]) < 5 or intersections[i][4] != "Overlap":
            intersections[i] = polish_intersection(a, b, intersections[i][0], intersections[i][1])
    return intersections


def csp_segments_true_intersection(sp1, sp2, sp3, sp4):
    intersections = csp_segments_intersection(sp1, sp2, sp3, sp4)
    res = []
    for intersection in intersections:
        if (
                (len(intersection) == 5 and intersection[4] == "Overlap" and (0 <= intersection[0] <= 1 or 0 <= intersection[1] <= 1) and (0 <= intersection[2] <= 1 or 0 <= intersection[3] <= 1))
                or (0 <= intersection[0] <= 1 and 0 <= intersection[1] <= 1)
        ):
            res += [intersection]
    return res


def csp_get_t_at_curvature(sp1, sp2, c, sample_points=16):
    # returns a list containing [t1,t2,t3,...,tn],  0<=ti<=1...
    if sample_points < 2:
        sample_points = 2
    tolerance = .0000000001
    res = []
    ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2)
    for k in range(sample_points):
        t = float(k) / (sample_points - 1)
        i = 0
        F = 1e100
        while i < 2 or abs(F) > tolerance and i < 17:
            try:  # some numerical calculation could exceed the limits
                t2 = t * t
                # slopes...
                f1x = 3 * ax * t2 + 2 * bx * t + cx
                f1y = 3 * ay * t2 + 2 * by * t + cy
                f2x = 6 * ax * t + 2 * bx
                f2y = 6 * ay * t + 2 * by
                f3x = 6 * ax
                f3y = 6 * ay
                d = (f1x ** 2 + f1y ** 2) ** 1.5
                F1 = (
                        ((f1x * f3y - f3x * f1y) * d - (f1x * f2y - f2x * f1y) * 3. * (f2x * f1x + f2y * f1y) * ((f1x ** 2 + f1y ** 2) ** .5)) /
                        ((f1x ** 2 + f1y ** 2) ** 3)
                )
                F = (f1x * f2y - f1y * f2x) / d - c
                t -= F / F1
            except:
                break
            i += 1
        if 0 <= t <= 1 and F <= tolerance:
            if len(res) == 0:
                res.append(t)
            for i in res:
                if abs(t - i) <= 0.001:
                    break
            if not abs(t - i) <= 0.001:
                res.append(t)
    return res


def csp_max_curvature(sp1, sp2):
    ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2)
    tolerance = .0001
    F = 0.
    i = 0
    while i < 2 or F - Flast < tolerance and i < 10:
        t = .5
        f1x = 3 * ax * t ** 2 + 2 * bx * t + cx
        f1y = 3 * ay * t ** 2 + 2 * by * t + cy
        f2x = 6 * ax * t + 2 * bx
        f2y = 6 * ay * t + 2 * by
        f3x = 6 * ax
        f3y = 6 * ay
        d = pow(f1x ** 2 + f1y ** 2, 1.5)
        if d != 0:
            Flast = F
            F = (f1x * f2y - f1y * f2x) / d
            F1 = (
                    (d * (f1x * f3y - f3x * f1y) - (f1x * f2y - f2x * f1y) * 3. * (f2x * f1x + f2y * f1y) * pow(f1x ** 2 + f1y ** 2, .5)) /
                    (f1x ** 2 + f1y ** 2) ** 3
            )
            i += 1
            if F1 != 0:
                t -= F / F1
            else:
                break
        else:
            break
    return t


def csp_curvature_at_t(sp1, sp2, t, depth=3):
    ax, ay, bx, by, cx, cy, dx, dy = bezierparameterize(csp_segment_to_bez(sp1, sp2))

    # curvature = (x'y''-y'x'') / (x'^2+y'^2)^1.5

    f1x = 3 * ax * t ** 2 + 2 * bx * t + cx
    f1y = 3 * ay * t ** 2 + 2 * by * t + cy
    f2x = 6 * ax * t + 2 * bx
    f2y = 6 * ay * t + 2 * by
    d = (f1x ** 2 + f1y ** 2) ** 1.5
    if d != 0:
        return (f1x * f2y - f1y * f2x) / d
    else:
        t1 = f1x * f2y - f1y * f2x
        if t1 > 0:
            return 1e100
        if t1 < 0:
            return -1e100
        # Use the Lapitals rule to solve 0/0 problem for 2 times...
        t1 = 2 * (bx * ay - ax * by) * t + (ay * cx - ax * cy)
        if t1 > 0:
            return 1e100
        if t1 < 0:
            return -1e100
        t1 = bx * ay - ax * by
        if t1 > 0:
            return 1e100
        if t1 < 0:
            return -1e100
        if depth > 0:
            # little hack ;^) hope it won't influence anything...
            return csp_curvature_at_t(sp1, sp2, t * 1.004, depth - 1)
        return 1e100


def csp_subpath_ccw(subpath):
    # Remove all zero length segments
    s = 0
    if (P(subpath[-1][1]) - P(subpath[0][1])).l2() > 1e-10:
        subpath[-1][2] = subpath[-1][1]
        subpath[0][0] = subpath[0][1]
        subpath += [[subpath[0][1], subpath[0][1], subpath[0][1]]]
    pl = subpath[-1][2]
    for sp1 in subpath:
        for p in sp1:
            s += (p[0] - pl[0]) * (p[1] + pl[1])
            pl = p
    return s < 0


def csp_at_t(sp1, sp2, t):
    ax = sp1[1][0]
    bx = sp1[2][0]
    cx = sp2[0][0]
    dx = sp2[1][0]

    ay = sp1[1][1]
    by = sp1[2][1]
    cy = sp2[0][1]
    dy = sp2[1][1]

    x1 = ax + (bx - ax) * t
    y1 = ay + (by - ay) * t

    x2 = bx + (cx - bx) * t
    y2 = by + (cy - by) * t

    x3 = cx + (dx - cx) * t
    y3 = cy + (dy - cy) * t

    x4 = x1 + (x2 - x1) * t
    y4 = y1 + (y2 - y1) * t

    x5 = x2 + (x3 - x2) * t
    y5 = y2 + (y3 - y2) * t

    x = x4 + (x5 - x4) * t
    y = y4 + (y5 - y4) * t

    return [x, y]


def csp_at_length(sp1, sp2, l=0.5, tolerance=0.01):
    bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:])
    t = beziertatlength(bez, l, tolerance)
    return csp_at_t(sp1, sp2, t)


def cspseglength(sp1, sp2, tolerance=0.01):
    bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:])
    return bezierlength(bez, tolerance)


def csp_line_intersection(l1, l2, sp1, sp2):
    dd = l1[0]
    cc = l2[0] - l1[0]
    bb = l1[1]
    aa = l2[1] - l1[1]
    if aa == cc == 0:
        return []
    if aa:
        coef1 = cc / aa
        coef2 = 1
    else:
        coef1 = 1
        coef2 = aa / cc
    bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:])
    ax, ay, bx, by, cx, cy, x0, y0 = bezierparameterize(bez)
    a = coef1 * ay - coef2 * ax
    b = coef1 * by - coef2 * bx
    c = coef1 * cy - coef2 * cx
    d = coef1 * (y0 - bb) - coef2 * (x0 - dd)
    roots = cubic_solver(a, b, c, d)
    retval = []
    for i in roots:
        if type(i) is complex and abs(i.imag) < 1e-7:
            i = i.real
        if type(i) is not complex and -1e-10 <= i <= 1. + 1e-10:
            retval.append(i)
    return retval


def csp_split_by_two_points(sp1, sp2, t1, t2):
    if t1 > t2:
        t1, t2 = t2, t1
    if t1 == t2:
        sp1, sp2, sp3 = csp_split(sp1, sp2, t1)
        return [sp1, sp2, sp2, sp3]
    elif t1 <= 1e-10 and t2 >= 1. - 1e-10:
        return [sp1, sp1, sp2, sp2]
    elif t1 <= 1e-10:
        sp1, sp2, sp3 = csp_split(sp1, sp2, t2)
        return [sp1, sp1, sp2, sp3]
    elif t2 >= 1. - 1e-10:
        sp1, sp2, sp3 = csp_split(sp1, sp2, t1)
        return [sp1, sp2, sp3, sp3]
    else:
        sp1, sp2, sp3 = csp_split(sp1, sp2, t1)
        sp2, sp3, sp4 = csp_split(sp2, sp3, (t2 - t1) / (1 - t1))
        return [sp1, sp2, sp3, sp4]


def csp_seg_split(sp1, sp2, points):
    # points is float=t or list [t1, t2, ..., tn]
    if type(points) is float:
        points = [points]
    points.sort()
    res = [sp1, sp2]
    last_t = 0
    for t in points:
        if 1e-10 < t < 1. - 1e-10:
            sp3, sp4, sp5 = csp_split(res[-2], res[-1], (t - last_t) / (1 - last_t))
            last_t = t
            res[-2:] = [sp3, sp4, sp5]
    return res


def csp_subpath_split_by_points(subpath, points):
    # points are [[i,t]...] where i-segment's number
    points.sort()
    points = [[1, 0.]] + points + [[len(subpath) - 1, 1.]]
    parts = []
    for int1, int2 in zip(points, points[1:]):
        if int1 == int2:
            continue
        if int1[1] == 1.:
            int1[0] += 1
            int1[1] = 0.
        if int1 == int2:
            continue
        if int2[1] == 0.:
            int2[0] -= 1
            int2[1] = 1.
        if int1[0] == 0 and int2[0] == len(subpath) - 1:  # and small(int1[1]) and small(int2[1]-1) :
            continue
        if int1[0] == int2[0]:  # same segment
            sp = csp_split_by_two_points(subpath[int1[0] - 1], subpath[int1[0]], int1[1], int2[1])
            if sp[1] != sp[2]:
                parts += [[sp[1], sp[2]]]
        else:
            sp5, sp1, sp2 = csp_split(subpath[int1[0] - 1], subpath[int1[0]], int1[1])
            sp3, sp4, sp5 = csp_split(subpath[int2[0] - 1], subpath[int2[0]], int2[1])
            if int1[0] == int2[0] - 1:
                parts += [[sp1, [sp2[0], sp2[1], sp3[2]], sp4]]
            else:
                parts += [[sp1, sp2] + subpath[int1[0] + 1:int2[0] - 1] + [sp3, sp4]]
    return parts


def arc_from_s_r_n_l(s, r, n, l):
    if abs(n[0] ** 2 + n[1] ** 2 - 1) > 1e-10:
        n = normalize(n)
    return arc_from_c_s_l([s[0] + n[0] * r, s[1] + n[1] * r], s, l)


def arc_from_c_s_l(c, s, l):
    r = point_to_point_d(c, s)
    if r == 0:
        return []
    alpha = l / r
    cos_ = math.cos(alpha)
    sin_ = math.sin(alpha)
    e = [c[0] + (s[0] - c[0]) * cos_ - (s[1] - c[1]) * sin_, c[1] + (s[0] - c[0]) * sin_ + (s[1] - c[1]) * cos_]
    n = [c[0] - s[0], c[1] - s[1]]
    slope = rotate_cw(n) if l > 0 else rotate_ccw(n)
    return csp_from_arc(s, e, c, r, slope)


def csp_from_arc(start, end, center, r, slope_st):
    # Creates csp that approximise specified arc
    r = abs(r)
    alpha = (atan2(end[0] - center[0], end[1] - center[1]) - atan2(start[0] - center[0], start[1] - center[1])) % TAU

    sectors = int(abs(alpha) * 2 / math.pi) + 1
    alpha_start = atan2(start[0] - center[0], start[1] - center[1])
    cos_ = math.cos(alpha_start)
    sin_ = math.sin(alpha_start)
    k = (4. * math.tan(alpha / sectors / 4.) / 3.)
    if dot(slope_st, [- sin_ * k * r, cos_ * k * r]) < 0:
        if alpha > 0:
            alpha -= TAU
        else:
            alpha += TAU
    if abs(alpha * r) < 0.001:
        return []

    sectors = int(abs(alpha) * 2 / math.pi) + 1
    k = (4. * math.tan(alpha / sectors / 4.) / 3.)
    result = []
    for i in range(sectors + 1):
        cos_ = math.cos(alpha_start + alpha * i / sectors)
        sin_ = math.sin(alpha_start + alpha * i / sectors)
        sp = [[], [center[0] + cos_ * r, center[1] + sin_ * r], []]
        sp[0] = [sp[1][0] + sin_ * k * r, sp[1][1] - cos_ * k * r]
        sp[2] = [sp[1][0] - sin_ * k * r, sp[1][1] + cos_ * k * r]
        result += [sp]
    result[0][0] = result[0][1][:]
    result[-1][2] = result[-1][1]

    return result


def point_to_arc_distance(p, arc):
    # Distance calculattion from point to arc
    P0, P2, c, a = arc
    p = P(p)
    r = (P0 - c).mag()
    if r > 0:
        i = c + (p - c).unit() * r
        alpha = ((i - c).angle() - (P0 - c).angle())
        if a * alpha < 0:
            if alpha > 0:
                alpha = alpha - TAU
            else:
                alpha = TAU + alpha
        if between(alpha, 0, a) or min(abs(alpha), abs(alpha - a)) < STRAIGHT_TOLERANCE:
            return (p - i).mag(), [i.x, i.y]
        else:
            d1 = (p - P0).mag()
            d2 = (p - P2).mag()
            if d1 < d2:
                return d1, [P0.x, P0.y]
            else:
                return d2, [P2.x, P2.y]


def csp_to_arc_distance(sp1, sp2, arc1, arc2, tolerance=0.01):  # arc = [start,end,center,alpha]
    n = 10
    i = 0
    d = (0, [0, 0])
    d1 = (0, [0, 0])
    dl = 0
    while i < 1 or (abs(d1[0] - dl[0]) > tolerance and i < 4):
        i += 1
        dl = d1 * 1
        for j in range(n + 1):
            t = float(j) / n
            p = csp_at_t(sp1, sp2, t)
            d = min(point_to_arc_distance(p, arc1), point_to_arc_distance(p, arc2))
            d1 = max(d1, d)
        n = n * 2
    return d1[0]


def csp_point_inside_bound(sp1, sp2, p):
    bez = [sp1[1], sp1[2], sp2[0], sp2[1]]
    x, y = p
    c = 0
    # CLT added test of x in range
    xmin = 1e100
    xmax = -1e100
    for i in range(4):
        [x0, y0] = bez[i - 1]
        [x1, y1] = bez[i]
        xmin = min(xmin, x0)
        xmax = max(xmax, x0)
        if x0 - x1 != 0 and (y - y0) * (x1 - x0) >= (x - x0) * (y1 - y0) and x > min(x0, x1) and x <= max(x0, x1):
            c += 1
    return xmin <= x <= xmax and c % 2 == 0


def line_line_intersect(p1, p2, p3, p4):  # Return only true intersection.
    if (p1[0] == p2[0] and p1[1] == p2[1]) or (p3[0] == p4[0] and p3[1] == p4[1]):
        return False
    x = (p2[0] - p1[0]) * (p4[1] - p3[1]) - (p2[1] - p1[1]) * (p4[0] - p3[0])
    if x == 0:  # Lines are parallel
        if (p3[0] - p1[0]) * (p2[1] - p1[1]) == (p3[1] - p1[1]) * (p2[0] - p1[0]):
            if p3[0] != p4[0]:
                t11 = (p1[0] - p3[0]) / (p4[0] - p3[0])
                t12 = (p2[0] - p3[0]) / (p4[0] - p3[0])
                t21 = (p3[0] - p1[0]) / (p2[0] - p1[0])
                t22 = (p4[0] - p1[0]) / (p2[0] - p1[0])
            else:
                t11 = (p1[1] - p3[1]) / (p4[1] - p3[1])
                t12 = (p2[1] - p3[1]) / (p4[1] - p3[1])
                t21 = (p3[1] - p1[1]) / (p2[1] - p1[1])
                t22 = (p4[1] - p1[1]) / (p2[1] - p1[1])
            return "Overlap" if (0 <= t11 <= 1 or 0 <= t12 <= 1) and (0 <= t21 <= 1 or 0 <= t22 <= 1) else False
        else:
            return False
    else:
        return (
                0 <= ((p4[0] - p3[0]) * (p1[1] - p3[1]) - (p4[1] - p3[1]) * (p1[0] - p3[0])) / x <= 1 and
                0 <= ((p2[0] - p1[0]) * (p1[1] - p3[1]) - (p2[1] - p1[1]) * (p1[0] - p3[0])) / x <= 1)


def line_line_intersection_points(p1, p2, p3, p4):  # Return only points [ (x,y) ]
    if (p1[0] == p2[0] and p1[1] == p2[1]) or (p3[0] == p4[0] and p3[1] == p4[1]):
        return []
    x = (p2[0] - p1[0]) * (p4[1] - p3[1]) - (p2[1] - p1[1]) * (p4[0] - p3[0])
    if x == 0:  # Lines are parallel
        if (p3[0] - p1[0]) * (p2[1] - p1[1]) == (p3[1] - p1[1]) * (p2[0] - p1[0]):
            if p3[0] != p4[0]:
                t11 = (p1[0] - p3[0]) / (p4[0] - p3[0])
                t12 = (p2[0] - p3[0]) / (p4[0] - p3[0])
                t21 = (p3[0] - p1[0]) / (p2[0] - p1[0])
                t22 = (p4[0] - p1[0]) / (p2[0] - p1[0])
            else:
                t11 = (p1[1] - p3[1]) / (p4[1] - p3[1])
                t12 = (p2[1] - p3[1]) / (p4[1] - p3[1])
                t21 = (p3[1] - p1[1]) / (p2[1] - p1[1])
                t22 = (p4[1] - p1[1]) / (p2[1] - p1[1])
            res = []
            if (0 <= t11 <= 1 or 0 <= t12 <= 1) and (0 <= t21 <= 1 or 0 <= t22 <= 1):
                if 0 <= t11 <= 1:
                    res += [p1]
                if 0 <= t12 <= 1:
                    res += [p2]
                if 0 <= t21 <= 1:
                    res += [p3]
                if 0 <= t22 <= 1:
                    res += [p4]
            return res
        else:
            return []
    else:
        t1 = ((p4[0] - p3[0]) * (p1[1] - p3[1]) - (p4[1] - p3[1]) * (p1[0] - p3[0])) / x
        t2 = ((p2[0] - p1[0]) * (p1[1] - p3[1]) - (p2[1] - p1[1]) * (p1[0] - p3[0])) / x
        if 0 <= t1 <= 1 and 0 <= t2 <= 1:
            return [[p1[0] * (1 - t1) + p2[0] * t1, p1[1] * (1 - t1) + p2[1] * t1]]
        else:
            return []


def point_to_point_d2(a, b):
    return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2


def point_to_point_d(a, b):
    return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2)


def point_to_line_segment_distance_2(p1, p2, p3):
    # p1 - point, p2,p3 - line segment
    # draw_pointer(p1)
    w0 = [p1[0] - p2[0], p1[1] - p2[1]]
    v = [p3[0] - p2[0], p3[1] - p2[1]]
    c1 = w0[0] * v[0] + w0[1] * v[1]
    if c1 <= 0:
        return w0[0] * w0[0] + w0[1] * w0[1]
    c2 = v[0] * v[0] + v[1] * v[1]
    if c2 <= c1:
        return (p1[0] - p3[0]) ** 2 + (p1[1] - p3[1]) ** 2
    return (p1[0] - p2[0] - v[0] * c1 / c2) ** 2 + (p1[1] - p2[1] - v[1] * c1 / c2)


def line_to_line_distance_2(p1, p2, p3, p4):
    if line_line_intersect(p1, p2, p3, p4):
        return 0
    return min(
            point_to_line_segment_distance_2(p1, p3, p4),
            point_to_line_segment_distance_2(p2, p3, p4),
            point_to_line_segment_distance_2(p3, p1, p2),
            point_to_line_segment_distance_2(p4, p1, p2))


def csp_seg_bound_to_csp_seg_bound_max_min_distance(sp1, sp2, sp3, sp4):
    bez1 = csp_segment_to_bez(sp1, sp2)
    bez2 = csp_segment_to_bez(sp3, sp4)
    min_dist = 1e100
    max_dist = 0.
    for i in range(4):
        if csp_point_inside_bound(sp1, sp2, bez2[i]) or csp_point_inside_bound(sp3, sp4, bez1[i]):
            min_dist = 0.
            break
    for i in range(4):
        for j in range(4):
            d = line_to_line_distance_2(bez1[i - 1], bez1[i], bez2[j - 1], bez2[j])
            if d < min_dist:
                min_dist = d
            d = (bez2[j][0] - bez1[i][0]) ** 2 + (bez2[j][1] - bez1[i][1]) ** 2
            if max_dist < d:
                max_dist = d
    return min_dist, max_dist


def csp_reverse(csp):
    for i in range(len(csp)):
        n = []
        for j in csp[i]:
            n = [[j[2][:], j[1][:], j[0][:]]] + n
        csp[i] = n[:]
    return csp


def csp_normalized_slope(sp1, sp2, t):
    ax, ay, bx, by, cx, cy, dx, dy = bezierparameterize((sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]))
    if sp1[1] == sp2[1] == sp1[2] == sp2[0]:
        return [1., 0.]
    f1x = 3 * ax * t * t + 2 * bx * t + cx
    f1y = 3 * ay * t * t + 2 * by * t + cy
    if abs(f1x * f1x + f1y * f1y) > 1e-9:  # LT changed this from 1e-20, which caused problems
        l = math.sqrt(f1x * f1x + f1y * f1y)
        return [f1x / l, f1y / l]

    if t == 0:
        f1x = sp2[0][0] - sp1[1][0]
        f1y = sp2[0][1] - sp1[1][1]
        if abs(f1x * f1x + f1y * f1y) > 1e-9:  # LT changed this from 1e-20, which caused problems
            l = math.sqrt(f1x * f1x + f1y * f1y)
            return [f1x / l, f1y / l]
        else:
            f1x = sp2[1][0] - sp1[1][0]
            f1y = sp2[1][1] - sp1[1][1]
            if f1x * f1x + f1y * f1y != 0:
                l = math.sqrt(f1x * f1x + f1y * f1y)
                return [f1x / l, f1y / l]
    elif t == 1:
        f1x = sp2[1][0] - sp1[2][0]
        f1y = sp2[1][1] - sp1[2][1]
        if abs(f1x * f1x + f1y * f1y) > 1e-9:
            l = math.sqrt(f1x * f1x + f1y * f1y)
            return [f1x / l, f1y / l]
        else:
            f1x = sp2[1][0] - sp1[1][0]
            f1y = sp2[1][1] - sp1[1][1]
            if f1x * f1x + f1y * f1y != 0:
                l = math.sqrt(f1x * f1x + f1y * f1y)
                return [f1x / l, f1y / l]
    else:
        return [1., 0.]


def csp_normalized_normal(sp1, sp2, t):
    nx, ny = csp_normalized_slope(sp1, sp2, t)
    return [-ny, nx]


def csp_parameterize(sp1, sp2):
    return bezierparameterize(csp_segment_to_bez(sp1, sp2))


def csp_concat_subpaths(*s):
    def concat(s1, s2):
        if not s1:
            return s2
        if not s2:
            return s1
        if (s1[-1][1][0] - s2[0][1][0]) ** 2 + (s1[-1][1][1] - s2[0][1][1]) ** 2 > 0.00001:
            return s1[:-1] + [[s1[-1][0], s1[-1][1], s1[-1][1]], [s2[0][1], s2[0][1], s2[0][2]]] + s2[1:]
        else:
            return s1[:-1] + [[s1[-1][0], s2[0][1], s2[0][2]]] + s2[1:]

    if len(s) == 0:
        return []
    if len(s) == 1:
        return s[0]
    result = s[0]
    for s1 in s[1:]:
        result = concat(result, s1)
    return result


def csp_subpaths_end_to_start_distance2(s1, s2):
    return (s1[-1][1][0] - s2[0][1][0]) ** 2 + (s1[-1][1][1] - s2[0][1][1]) ** 2


def csp_clip_by_line(csp, l1, l2):
    result = []
    for i in range(len(csp)):
        s = csp[i]
        intersections = []
        for j in range(1, len(s)):
            intersections += [[j, int_] for int_ in csp_line_intersection(l1, l2, s[j - 1], s[j])]
        splitted_s = csp_subpath_split_by_points(s, intersections)
        for s in splitted_s[:]:
            clip = False
            for p in csp_true_bounds([s]):
                if (l1[1] - l2[1]) * p[0] + (l2[0] - l1[0]) * p[1] + (l1[0] * l2[1] - l2[0] * l1[1]) < -0.01:
                    clip = True
                    break
            if clip:
                splitted_s.remove(s)
        result += splitted_s
    return result


def csp_subpath_line_to(subpath, points, prepend=False):
    # Appends subpath with line or polyline.
    if len(points) > 0:
        if not prepend:
            if len(subpath) > 0:
                subpath[-1][2] = subpath[-1][1][:]
            if type(points[0]) == type([1, 1]):
                for p in points:
                    subpath += [[p[:], p[:], p[:]]]
            else:
                subpath += [[points, points, points]]
        else:
            if len(subpath) > 0:
                subpath[0][0] = subpath[0][1][:]
            if type(points[0]) == type([1, 1]):
                for p in points:
                    subpath = [[p[:], p[:], p[:]]] + subpath
            else:
                subpath = [[points, points, points]] + subpath
    return subpath


def csp_join_subpaths(csp):
    result = csp[:]
    done_smf = True
    joined_result = []
    while done_smf:
        done_smf = False
        while len(result) > 0:
            s1 = result[-1][:]
            del (result[-1])
            j = 0
            joined_smf = False
            while j < len(joined_result):
                if csp_subpaths_end_to_start_distance2(joined_result[j], s1) < 0.000001:
                    joined_result[j] = csp_concat_subpaths(joined_result[j], s1)
                    done_smf = True
                    joined_smf = True
                    break
                if csp_subpaths_end_to_start_distance2(s1, joined_result[j]) < 0.000001:
                    joined_result[j] = csp_concat_subpaths(s1, joined_result[j])
                    done_smf = True
                    joined_smf = True
                    break
                j += 1
            if not joined_smf:
                joined_result += [s1[:]]
        if done_smf:
            result = joined_result[:]
            joined_result = []
    return joined_result


def triangle_cross(a, b, c):
    return (a[0] - b[0]) * (c[1] - b[1]) - (c[0] - b[0]) * (a[1] - b[1])


def csp_segment_convex_hull(sp1, sp2):
    a = sp1[1][:]
    b = sp1[2][:]
    c = sp2[0][:]
    d = sp2[1][:]

    abc = triangle_cross(a, b, c)
    abd = triangle_cross(a, b, d)
    bcd = triangle_cross(b, c, d)
    cad = triangle_cross(c, a, d)
    if abc == 0 and abd == 0:
        return [min(a, b, c, d), max(a, b, c, d)]
    if abc == 0:
        return [d, min(a, b, c), max(a, b, c)]
    if abd == 0:
        return [c, min(a, b, d), max(a, b, d)]
    if bcd == 0:
        return [a, min(b, c, d), max(b, c, d)]
    if cad == 0:
        return [b, min(c, a, d), max(c, a, d)]

    m1 = abc * abd > 0
    m2 = abc * bcd > 0
    m3 = abc * cad > 0

    if m1 and m2 and m3:
        return [a, b, c]
    if m1 and m2 and not m3:
        return [a, b, c, d]
    if m1 and not m2 and m3:
        return [a, b, d, c]
    if not m1 and m2 and m3:
        return [a, d, b, c]
    if m1 and not (m2 and m3):
        return [a, b, d]
    if not (m1 and m2) and m3:
        return [c, a, d]
    if not (m1 and m3) and m2:
        return [b, c, d]

    raise ValueError("csp_segment_convex_hull happened which is something that shouldn't happen!")


################################################################################
# Bezier additional functions
################################################################################

def bez_bounds_intersect(bez1, bez2):
    return bounds_intersect(bez_bound(bez2), bez_bound(bez1))


def bez_bound(bez):
    return [
        min(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
        min(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
        max(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
        max(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
    ]


def bounds_intersect(a, b):
    return not ((a[0] > b[2]) or (b[0] > a[2]) or (a[1] > b[3]) or (b[1] > a[3]))


def tpoint(xy1, xy2, t):
    (x1, y1) = xy1
    (x2, y2) = xy2
    return [x1 + t * (x2 - x1), y1 + t * (y2 - y1)]


def bez_split(a, t=0.5):
    a1 = tpoint(a[0], a[1], t)
    at = tpoint(a[1], a[2], t)
    b2 = tpoint(a[2], a[3], t)
    a2 = tpoint(a1, at, t)
    b1 = tpoint(b2, at, t)
    a3 = tpoint(a2, b1, t)
    return [a[0], a1, a2, a3], [a3, b1, b2, a[3]]


################################################################################
# Some vector functions
################################################################################

def normalize(xy):
    (x, y) = xy
    l = math.sqrt(x ** 2 + y ** 2)
    if l == 0:
        return [0., 0.]
    else:
        return [x / l, y / l]


def cross(a, b):
    return a[1] * b[0] - a[0] * b[1]


def dot(a, b):
    return a[0] * b[0] + a[1] * b[1]


def rotate_ccw(d):
    return [-d[1], d[0]]


def rotate_cw(d):
    return [d[1], -d[0]]


def vectors_ccw(a, b):
    return a[0] * b[1] - b[0] * a[1] < 0


################################################################################
# Common functions
################################################################################

def inv_2x2(a):  # invert matrix 2x2
    det = a[0][0] * a[1][1] - a[1][0] * a[0][1]
    if det == 0:
        return None
    return [
        [a[1][1] / det, -a[0][1] / det],
        [-a[1][0] / det, a[0][0] / det]
    ]


def small(a):
    global small_tolerance
    return abs(a) < small_tolerance


def atan2(*arg):
    if len(arg) == 1 and (type(arg[0]) == type([0., 0.]) or type(arg[0]) == type((0., 0.))):
        return (math.pi / 2 - math.atan2(arg[0][0], arg[0][1])) % TAU
    elif len(arg) == 2:
        return (math.pi / 2 - math.atan2(arg[0], arg[1])) % TAU
    else:
        raise ValueError("Bad argumets for atan! ({})".format(*arg))


def draw_text(text, x, y, group=None, style=None, font_size=10, gcodetools_tag=None):
    if style is None:
        style = "font-family:DejaVu Sans;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:DejaVu Sans;fill:#000000;fill-opacity:1;stroke:none;"
    style += "font-size:{:f}px;".format(font_size)
    attributes = {'x': str(x), 'y': str(y), 'style': style}
    if gcodetools_tag is not None:
        attributes["gcodetools"] = str(gcodetools_tag)

    if group is None:
        group = options.doc_root

    text_elem = group.add(TextElement(**attributes))
    text_elem.set("xml:space", "preserve")
    text = str(text).split("\n")
    for string in text:
        span = text_elem.add(Tspan(x=str(x), y=str(y)))
        span.set('sodipodi:role', 'line')
        y += font_size
        span.text = str(string)


def draw_csp(csp, stroke="#f00", fill="none", comment="", width=0.354, group=None, style=None):
    if group is None:
        group = options.doc_root
    node = group.add(PathElement())

    node.style = style if style is not None else \
        {'fill': fill, 'fill-opacity': 1, 'stroke': stroke, 'stroke-width': width}

    node.path = CubicSuperPath(csp)

    if comment != '':
        node.set('comment', comment)

    return node


def draw_pointer(x, color="#f00", figure="cross", group=None, comment="", fill=None, width=.1, size=10., text=None, font_size=None, pointer_type=None, attrib=None):
    size = size / 2
    if attrib is None:
        attrib = {}
    if pointer_type is None:
        pointer_type = "Pointer"
    attrib["gcodetools"] = pointer_type
    if group is None:
        group = options.self.svg.get_current_layer()
    if text is not None:
        if font_size is None:
            font_size = 7
        group = group.add(Group(gcodetools=pointer_type + " group"))
        draw_text(text, x[0] + size * 2.2, x[1] - size, group=group, font_size=font_size)
    if figure == "line":
        s = ""
        for i in range(1, len(x) / 2):
            s += " {}, {} ".format(x[i * 2], x[i * 2 + 1])
        attrib.update({"d": "M {},{} L {}".format(x[0], x[1], s), "style": "fill:none;stroke:{};stroke-width:{:f};".format(color, width), "comment": str(comment)})
    elif figure == "arrow":
        if fill is None:
            fill = "#12b3ff"
        fill_opacity = "0.8"
        d = "m {},{} ".format(x[0], x[1]) + re.sub("([0-9\\-.e]+)", (lambda match: str(float(match.group(1)) * size * 2.)), "0.88464,-0.40404 c -0.0987,-0.0162 -0.186549,-0.0589 -0.26147,-0.1173 l 0.357342,-0.35625 c 0.04631,-0.039 0.0031,-0.13174 -0.05665,-0.12164 -0.0029,-1.4e-4 -0.0058,-1.4e-4 -0.0087,0 l -2.2e-5,2e-5 c -0.01189,0.004 -0.02257,0.0119 -0.0305,0.0217 l -0.357342,0.35625 c -0.05818,-0.0743 -0.102813,-0.16338 -0.117662,-0.26067 l -0.409636,0.88193 z")
        attrib.update({"d": d, "style": "fill:{};stroke:none;fill-opacity:{};".format(fill, fill_opacity), "comment": str(comment)})
    else:
        attrib.update({"d": "m {},{} l {:f},{:f} {:f},{:f} {:f},{:f} {:f},{:f} , {:f},{:f}".format(x[0], x[1], size, size, -2 * size, -2 * size, size, size, size, -size, -2 * size, 2 * size), "style": "fill:none;stroke:{};stroke-width:{:f};".format(color, width), "comment": str(comment)})
    group.add(PathElement(**attrib))


def straight_segments_intersection(a, b, true_intersection=True):  # (True intersection means check ta and tb are in [0,1])
    ax = a[0][0]
    bx = a[1][0]
    cx = b[0][0]
    dx = b[1][0]
    ay = a[0][1]
    by = a[1][1]
    cy = b[0][1]
    dy = b[1][1]
    if (ax == bx and ay == by) or (cx == dx and cy == dy):
        return False, 0, 0
    if (bx - ax) * (dy - cy) - (by - ay) * (dx - cx) == 0:  # Lines are parallel
        ta = (ax - cx) / (dx - cx) if cx != dx else (ay - cy) / (dy - cy)
        tb = (bx - cx) / (dx - cx) if cx != dx else (by - cy) / (dy - cy)
        tc = (cx - ax) / (bx - ax) if ax != bx else (cy - ay) / (by - ay)
        td = (dx - ax) / (bx - ax) if ax != bx else (dy - ay) / (by - ay)
        return ("Overlap" if 0 <= ta <= 1 or 0 <= tb <= 1 or 0 <= tc <= 1 or 0 <= td <= 1 or not true_intersection else False), (ta, tb), (tc, td)
    else:
        ta = ((ay - cy) * (dx - cx) - (ax - cx) * (dy - cy)) / ((bx - ax) * (dy - cy) - (by - ay) * (dx - cx))
        tb = (ax - cx + ta * (bx - ax)) / (dx - cx) if dx != cx else (ay - cy + ta * (by - ay)) / (dy - cy)
        return (0 <= ta <= 1 and 0 <= tb <= 1 or not true_intersection), ta, tb


def between(c, x, y):
    return x - STRAIGHT_TOLERANCE <= c <= y + STRAIGHT_TOLERANCE or y - STRAIGHT_TOLERANCE <= c <= x + STRAIGHT_TOLERANCE


def cubic_solver_real(a, b, c, d):
    # returns only real roots of a cubic equation.
    roots = cubic_solver(a, b, c, d)
    res = []
    for root in roots:
        if type(root) is complex:
            if -1e-10 < root.imag < 1e-10:
                res.append(root.real)
        else:
            res.append(root)
    return res


def cubic_solver(a, b, c, d):
    if a != 0:
        #    Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots
        a, b, c = (b / a, c / a, d / a)
        m = 2 * a ** 3 - 9 * a * b + 27 * c
        k = a ** 2 - 3 * b
        n = m ** 2 - 4 * k ** 3
        w1 = -.5 + .5 * cmath.sqrt(3) * 1j
        w2 = -.5 - .5 * cmath.sqrt(3) * 1j
        if n >= 0:
            t = m + math.sqrt(n)
            m1 = pow(t / 2, 1. / 3) if t >= 0 else -pow(-t / 2, 1. / 3)
            t = m - math.sqrt(n)
            n1 = pow(t / 2, 1. / 3) if t >= 0 else -pow(-t / 2, 1. / 3)
        else:
            m1 = pow(complex((m + cmath.sqrt(n)) / 2), 1. / 3)
            n1 = pow(complex((m - cmath.sqrt(n)) / 2), 1. / 3)
        x1 = -1. / 3 * (a + m1 + n1)
        x2 = -1. / 3 * (a + w1 * m1 + w2 * n1)
        x3 = -1. / 3 * (a + w2 * m1 + w1 * n1)
        return [x1, x2, x3]
    elif b != 0:
        det = c ** 2 - 4 * b * d
        if det > 0:
            return [(-c + math.sqrt(det)) / (2 * b), (-c - math.sqrt(det)) / (2 * b)]
        elif d == 0:
            return [-c / (b * b)]
        else:
            return [(-c + cmath.sqrt(det)) / (2 * b), (-c - cmath.sqrt(det)) / (2 * b)]
    elif c != 0:
        return [-d / c]
    else:
        return []


################################################################################
# print_ prints any arguments into specified log file
################################################################################

def print_(*arg):
    with open(options.log_filename, "ab") as f:
        for s in arg:
            s = unicode(s).encode('unicode_escape') + b" "
            f.write(s)
        f.write(b"\n")


################################################################################
# Point (x,y) operations
################################################################################
class P(object):
    def __init__(self, x, y=None):
        if not y is None:
            self.x = float(x)
            self.y = float(y)
        else:
            self.x = float(x[0])
            self.y = float(x[1])

    def __add__(self, other):
        return P(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return P(self.x - other.x, self.y - other.y)

    def __neg__(self):
        return P(-self.x, -self.y)

    def __mul__(self, other):
        if isinstance(other, P):
            return self.x * other.x + self.y * other.y
        return P(self.x * other, self.y * other)

    __rmul__ = __mul__

    def __div__(self, other):
        return P(self.x / other, self.y / other)

    def __truediv__(self, other):
        return self.__div__(other)

    def mag(self):
        return math.hypot(self.x, self.y)

    def unit(self):
        h_mag = self.mag()
        if h_mag:
            return self / h_mag
        return P(0, 0)

    def dot(self, other):
        return self.x * other.x + self.y * other.y

    def rot(self, theta):
        c = math.cos(theta)
        s = math.sin(theta)
        return P(self.x * c - self.y * s, self.x * s + self.y * c)

    def angle(self):
        return math.atan2(self.y, self.x)

    def __repr__(self):
        return '{:f},{:f}'.format(self.x, self.y)

    def pr(self):
        return "{:.2f},{:.2f}".format(self.x, self.y)

    def to_list(self):
        return [self.x, self.y]

    def ccw(self):
        return P(-self.y, self.x)

    def l2(self):
        return self.x * self.x + self.y * self.y


class Line(object):
    def __init__(self, st, end):
        if st.__class__ == P:
            st = st.to_list()
        if end.__class__ == P:
            end = end.to_list()
        self.st = P(st)
        self.end = P(end)
        self.l = self.length()
        if self.l != 0:
            self.n = ((self.end - self.st) / self.l).ccw()
        else:
            self.n = [0, 1]

    def offset(self, r):
        self.st -= self.n * r
        self.end -= self.n * r

    def l2(self):
        return (self.st - self.end).l2()

    def length(self):
        return (self.st - self.end).mag()

    def draw(self, group, style, layer, transform, num=0, reverse_angle=1):
        st = gcodetools.transform(self.st.to_list(), layer, True)
        end = gcodetools.transform(self.end.to_list(), layer, True)

        attr = {'style': style['line'],
                'd': 'M {},{} L {},{}'.format(st[0], st[1], end[0], end[1]),
                "gcodetools": "Preview",
                }
        if transform:
            attr["transform"] = transform
        group.add(PathElement(**attr))

    def intersect(self, b):
        if b.__class__ == Line:
            if self.l < 10e-8 or b.l < 10e-8:
                return []
            v1 = self.end - self.st
            v2 = b.end - b.st
            x = v1.x * v2.y - v2.x * v1.y
            if x == 0:
                # lines are parallel
                res = []

                if (self.st.x - b.st.x) * v1.y - (self.st.y - b.st.y) * v1.x == 0:
                    # lines are the same
                    if v1.x != 0:
                        if 0 <= (self.st.x - b.st.x) / v2.x <= 1:
                            res.append(self.st)
                        if 0 <= (self.end.x - b.st.x) / v2.x <= 1:
                            res.append(self.end)
                        if 0 <= (b.st.x - self.st.x) / v1.x <= 1:
                            res.append(b.st)
                        if 0 <= (b.end.x - b.st.x) / v1.x <= 1:
                            res.append(b.end)
                    else:
                        if 0 <= (self.st.y - b.st.y) / v2.y <= 1:
                            res.append(self.st)
                        if 0 <= (self.end.y - b.st.y) / v2.y <= 1:
                            res.append(self.end)
                        if 0 <= (b.st.y - self.st.y) / v1.y <= 1:
                            res.append(b.st)
                        if 0 <= (b.end.y - b.st.y) / v1.y <= 1:
                            res.append(b.end)
                return res
            else:
                t1 = (-v1.x * (b.end.y - self.end.y) + v1.y * (b.end.x - self.end.x)) / x
                t2 = (-v1.y * (self.st.x - b.st.x) + v1.x * (self.st.y - b.st.y)) / x

                gcodetools.error(str((x, t1, t2)))
                if 0 <= t1 <= 1 and 0 <= t2 <= 1:
                    return [self.st + v1 * t1]
                else:
                    return []
        else:
            return []


################################################################################
#
# Offset function
#
# This function offsets given cubic super path.
# It's based on src/livarot/PathOutline.cpp from Inkscape's source code.
#
#
################################################################################
def csp_offset(csp, r):
    offset_tolerance = 0.05
    offset_subdivision_depth = 10
    time_ = time.time()
    time_start = time_
    print_("Offset start at {}".format(time_))
    print_("Offset radius {}".format(r))

    def csp_offset_segment(sp1, sp2, r):
        result = []
        t = csp_get_t_at_curvature(sp1, sp2, 1 / r)
        if len(t) == 0:
            t = [0., 1.]
        t.sort()
        if t[0] > .00000001:
            t = [0.] + t
        if t[-1] < .99999999:
            t.append(1.)
        for st, end in zip(t, t[1:]):
            c = csp_curvature_at_t(sp1, sp2, (st + end) / 2)
            sp = csp_split_by_two_points(sp1, sp2, st, end)
            if sp[1] != sp[2]:
                if c > 1 / r and r < 0 or c < 1 / r and r > 0:
                    offset = offset_segment_recursion(sp[1], sp[2], r, offset_subdivision_depth, offset_tolerance)
                else:  # This part will be clipped for sure... TODO Optimize it...
                    offset = offset_segment_recursion(sp[1], sp[2], r, offset_subdivision_depth, offset_tolerance)

                if not result:
                    result = offset[:]
                else:
                    if csp_subpaths_end_to_start_distance2(result, offset) < 0.0001:
                        result = csp_concat_subpaths(result, offset)
                    else:

                        intersection = csp_get_subapths_last_first_intersection(result, offset)
                        if intersection:
                            i, t1, j, t2 = intersection
                            sp1_, sp2_, sp3_ = csp_split(result[i - 1], result[i], t1)
                            result = result[:i - 1] + [sp1_, sp2_]
                            sp1_, sp2_, sp3_ = csp_split(offset[j - 1], offset[j], t2)
                            result = csp_concat_subpaths(result, [sp2_, sp3_] + offset[j + 1:])
                        else:
                            pass  # ???
        return result

    def create_offset_segment(sp1, sp2, r):
        # See    Gernot Hoffmann "Bezier Curves"  p.34 -> 7.1 Bezier Offset Curves
        p0 = P(sp1[1])
        p1 = P(sp1[2])
        p2 = P(sp2[0])
        p3 = P(sp2[1])

        s0 = p1 - p0
        s1 = p2 - p1
        s3 = p3 - p2

        n0 = s0.ccw().unit() if s0.l2() != 0 else P(csp_normalized_normal(sp1, sp2, 0))
        n3 = s3.ccw().unit() if s3.l2() != 0 else P(csp_normalized_normal(sp1, sp2, 1))
        n1 = s1.ccw().unit() if s1.l2() != 0 else (n0.unit() + n3.unit()).unit()

        q0 = p0 + r * n0
        q3 = p3 + r * n3
        c = csp_curvature_at_t(sp1, sp2, 0)
        q1 = q0 + (p1 - p0) * (1 - (r * c if abs(c) < 100 else 0))
        c = csp_curvature_at_t(sp1, sp2, 1)
        q2 = q3 + (p2 - p3) * (1 - (r * c if abs(c) < 100 else 0))

        return [[q0.to_list(), q0.to_list(), q1.to_list()], [q2.to_list(), q3.to_list(), q3.to_list()]]

    def csp_get_subapths_last_first_intersection(s1, s2):
        _break = False
        for i in range(1, len(s1)):
            sp11 = s1[-i - 1]
            sp12 = s1[-i]
            for j in range(1, len(s2)):
                sp21 = s2[j - 1]
                sp22 = s2[j]
                intersection = csp_segments_true_intersection(sp11, sp12, sp21, sp22)
                if intersection:
                    _break = True
                    break
            if _break:
                break
        if _break:
            intersection = max(intersection)
            return [len(s1) - i, intersection[0], j, intersection[1]]
        else:
            return []

    def csp_join_offsets(prev, next, sp1, sp2, sp1_l, sp2_l, r):
        if len(next) > 1:
            if (P(prev[-1][1]) - P(next[0][1])).l2() < 0.001:
                return prev, [], next
            intersection = csp_get_subapths_last_first_intersection(prev, next)
            if intersection:
                i, t1, j, t2 = intersection
                sp1_, sp2_, sp3_ = csp_split(prev[i - 1], prev[i], t1)
                sp3_, sp4_, sp5_ = csp_split(next[j - 1], next[j], t2)
                return prev[:i - 1] + [sp1_, sp2_], [], [sp4_, sp5_] + next[j + 1:]

        # Offsets do not intersect... will add an arc...
        start = (P(csp_at_t(sp1_l, sp2_l, 1.)) + r * P(csp_normalized_normal(sp1_l, sp2_l, 1.))).to_list()
        end = (P(csp_at_t(sp1, sp2, 0.)) + r * P(csp_normalized_normal(sp1, sp2, 0.))).to_list()
        arc = csp_from_arc(start, end, sp1[1], r, csp_normalized_slope(sp1_l, sp2_l, 1.))
        if not arc:
            return prev, [], next
        else:
            # Clip prev by arc
            if csp_subpaths_end_to_start_distance2(prev, arc) > 0.00001:
                intersection = csp_get_subapths_last_first_intersection(prev, arc)
                if intersection:
                    i, t1, j, t2 = intersection
                    sp1_, sp2_, sp3_ = csp_split(prev[i - 1], prev[i], t1)
                    sp3_, sp4_, sp5_ = csp_split(arc[j - 1], arc[j], t2)
                    prev = prev[:i - 1] + [sp1_, sp2_]
                    arc = [sp4_, sp5_] + arc[j + 1:]
            # Clip next by arc
            if not next:
                return prev, [], arc
            if csp_subpaths_end_to_start_distance2(arc, next) > 0.00001:
                intersection = csp_get_subapths_last_first_intersection(arc, next)
                if intersection:
                    i, t1, j, t2 = intersection
                    sp1_, sp2_, sp3_ = csp_split(arc[i - 1], arc[i], t1)
                    sp3_, sp4_, sp5_ = csp_split(next[j - 1], next[j], t2)
                    arc = arc[:i - 1] + [sp1_, sp2_]
                    next = [sp4_, sp5_] + next[j + 1:]

            return prev, arc, next

    def offset_segment_recursion(sp1, sp2, r, depth, tolerance):
        sp1_r, sp2_r = create_offset_segment(sp1, sp2, r)
        err = max(
                csp_seg_to_point_distance(sp1_r, sp2_r, (P(csp_at_t(sp1, sp2, .25)) + P(csp_normalized_normal(sp1, sp2, .25)) * r).to_list())[0],
                csp_seg_to_point_distance(sp1_r, sp2_r, (P(csp_at_t(sp1, sp2, .50)) + P(csp_normalized_normal(sp1, sp2, .50)) * r).to_list())[0],
                csp_seg_to_point_distance(sp1_r, sp2_r, (P(csp_at_t(sp1, sp2, .75)) + P(csp_normalized_normal(sp1, sp2, .75)) * r).to_list())[0],
        )

        if err > tolerance ** 2 and depth > 0:
            if depth > offset_subdivision_depth - 2:
                t = csp_max_curvature(sp1, sp2)
                t = max(.1, min(.9, t))
            else:
                t = .5
            sp3, sp4, sp5 = csp_split(sp1, sp2, t)
            r1 = offset_segment_recursion(sp3, sp4, r, depth - 1, tolerance)
            r2 = offset_segment_recursion(sp4, sp5, r, depth - 1, tolerance)
            return r1[:-1] + [[r1[-1][0], r1[-1][1], r2[0][2]]] + r2[1:]
        else:
            return [sp1_r, sp2_r]

    ############################################################################
    # Some small definitions
    ############################################################################
    csp_len = len(csp)

    ############################################################################
    # Prepare the path
    ############################################################################
    # Remove all small segments (segment length < 0.001)

    for i in xrange(len(csp)):
        for j in xrange(len(csp[i])):
            sp = csp[i][j]
            if (P(sp[1]) - P(sp[0])).mag() < 0.001:
                csp[i][j][0] = sp[1]
            if (P(sp[2]) - P(sp[0])).mag() < 0.001:
                csp[i][j][2] = sp[1]
    for i in xrange(len(csp)):
        for j in xrange(1, len(csp[i])):
            if cspseglength(csp[i][j - 1], csp[i][j]) < 0.001:
                csp[i] = csp[i][:j] + csp[i][j + 1:]
        if cspseglength(csp[i][-1], csp[i][0]) > 0.001:
            csp[i][-1][2] = csp[i][-1][1]
            csp[i] += [[csp[i][0][1], csp[i][0][1], csp[i][0][1]]]

    # TODO Get rid of self intersections.

    original_csp = csp[:]
    # Clip segments which has curvature>1/r. Because their offset will be self-intersecting and very nasty.

    print_("Offset prepared the path in {}".format(time.time() - time_))
    print_("Path length = {}".format(sum([len(i) for i in csp])))
    time_ = time.time()

    ############################################################################
    # Offset
    ############################################################################
    # Create offsets for all segments in the path. And join them together inside each subpath.
    unclipped_offset = [[] for i in xrange(csp_len)]

    intersection = [[] for i in xrange(csp_len)]
    for i in xrange(csp_len):
        subpath = csp[i]
        subpath_offset = []
        for sp1, sp2 in zip(subpath, subpath[1:]):
            segment_offset = csp_offset_segment(sp1, sp2, r)
            if not subpath_offset:
                subpath_offset = segment_offset

                prev_l = len(subpath_offset)
            else:
                prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:], segment_offset, sp1, sp2, sp1_l, sp2_l, r)

                subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l + 1], prev, arc, next)
                prev_l = len(next)
            sp1_l = sp1[:]
            sp2_l = sp2[:]

        # Join last and first offsets togother to close the curve

        prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:], subpath_offset[:2], subpath[0], subpath[1], sp1_l, sp2_l, r)
        subpath_offset[:2] = next[:]
        subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l + 1], prev, arc)

        # Collect subpath's offset and save it to unclipped offset list.
        unclipped_offset[i] = subpath_offset[:]

    print_("Offsetted path in {}".format(time.time() - time_))
    time_ = time.time()

    ############################################################################
    # Now to the clipping.
    ############################################################################
    # First of all find all intersection's between all segments of all offset subpaths, including self intersections.

    # TODO define offset tolerance here
    global small_tolerance
    small_tolerance = 0.01
    summ = 0
    summ1 = 0
    for subpath_i in xrange(csp_len):
        for subpath_j in xrange(subpath_i, csp_len):
            subpath = unclipped_offset[subpath_i]
            subpath1 = unclipped_offset[subpath_j]
            for i in xrange(1, len(subpath)):
                # If subpath_i==subpath_j we are looking for self intersections, so
                # we'll need search intersections only for xrange(i,len(subpath1))
                for j in (xrange(i, len(subpath1)) if subpath_i == subpath_j else xrange(len(subpath1))):
                    if subpath_i == subpath_j and j == i:
                        # Find self intersections of a segment
                        sp1, sp2, sp3 = csp_split(subpath[i - 1], subpath[i], .5)
                        intersections = csp_segments_intersection(sp1, sp2, sp2, sp3)
                        summ += 1
                        for t in intersections:
                            summ1 += 1
                            if not (small(t[0] - 1) and small(t[1])) and 0 <= t[0] <= 1 and 0 <= t[1] <= 1:
                                intersection[subpath_i] += [[i, t[0] / 2], [j, t[1] / 2 + .5]]
                    else:
                        intersections = csp_segments_intersection(subpath[i - 1], subpath[i], subpath1[j - 1], subpath1[j])
                        summ += 1
                        for t in intersections:
                            summ1 += 1
                            # TODO tolerance dependence to cpsp_length(t)
                            if len(t) == 2 and 0 <= t[0] <= 1 and 0 <= t[1] <= 1 and not (
                                    subpath_i == subpath_j and (
                                    (j - i - 1) % (len(subpath) - 1) == 0 and small(t[0] - 1) and small(t[1]) or
                                    (i - j - 1) % (len(subpath) - 1) == 0 and small(t[1] - 1) and small(t[0]))):
                                intersection[subpath_i] += [[i, t[0]]]
                                intersection[subpath_j] += [[j, t[1]]]

                            elif len(t) == 5 and t[4] == "Overlap":
                                intersection[subpath_i] += [[i, t[0]], [i, t[1]]]
                                intersection[subpath_j] += [[j, t[1]], [j, t[3]]]

    print_("Intersections found in {}".format(time.time() - time_))
    print_("Examined {} segments".format(summ))
    print_("found {} intersections".format(summ1))
    time_ = time.time()

    ########################################################################
    # Split unclipped offset by intersection points into splitted_offset
    ########################################################################
    splitted_offset = []
    for i in xrange(csp_len):
        subpath = unclipped_offset[i]
        if len(intersection[i]) > 0:
            parts = csp_subpath_split_by_points(subpath, intersection[i])
            # Close    parts list to close path (The first and the last parts are joined together)
            if [1, 0.] not in intersection[i]:
                parts[0][0][0] = parts[-1][-1][0]
                parts[0] = csp_concat_subpaths(parts[-1], parts[0])
                splitted_offset += parts[:-1]
            else:
                splitted_offset += parts[:]
        else:
            splitted_offset += [subpath[:]]

    print_("Split in {}".format(time.time() - time_))
    time_ = time.time()

    ########################################################################
    # Clipping
    ########################################################################
    result = []
    for subpath_i in range(len(splitted_offset)):
        clip = False
        s1 = splitted_offset[subpath_i]
        for subpath_j in range(len(splitted_offset)):
            s2 = splitted_offset[subpath_j]
            if (P(s1[0][1]) - P(s2[-1][1])).l2() < 0.0001 and ((subpath_i + 1) % len(splitted_offset) != subpath_j):
                if dot(csp_normalized_normal(s2[-2], s2[-1], 1.), csp_normalized_slope(s1[0], s1[1], 0.)) * r < -0.0001:
                    clip = True
                    break
            if (P(s2[0][1]) - P(s1[-1][1])).l2() < 0.0001 and ((subpath_j + 1) % len(splitted_offset) != subpath_i):
                if dot(csp_normalized_normal(s2[0], s2[1], 0.), csp_normalized_slope(s1[-2], s1[-1], 1.)) * r > 0.0001:
                    clip = True
                    break

        if not clip:
            result += [s1[:]]
        elif options.offset_draw_clippend_path:
            draw_csp([s1], width=.1)
            draw_pointer(csp_at_t(s2[-2], s2[-1], 1.) +
                         (P(csp_at_t(s2[-2], s2[-1], 1.)) + P(csp_normalized_normal(s2[-2], s2[-1], 1.)) * 10).to_list(), "Green", "line")
            draw_pointer(csp_at_t(s1[0], s1[1], 0.) +
                         (P(csp_at_t(s1[0], s1[1], 0.)) + P(csp_normalized_slope(s1[0], s1[1], 0.)) * 10).to_list(), "Red", "line")

    # Now join all together and check closure and orientation of result
    joined_result = csp_join_subpaths(result)
    # Check if each subpath from joined_result is closed

    for s in joined_result[:]:
        if csp_subpaths_end_to_start_distance2(s, s) > 0.001:
            # Remove open parts
            if options.offset_draw_clippend_path:
                draw_csp([s], width=1)
                draw_pointer(s[0][1], comment=csp_subpaths_end_to_start_distance2(s, s))
                draw_pointer(s[-1][1], comment=csp_subpaths_end_to_start_distance2(s, s))
            joined_result.remove(s)
        else:
            # Remove small parts
            minx, miny, maxx, maxy = csp_true_bounds([s])
            if (minx[0] - maxx[0]) ** 2 + (miny[1] - maxy[1]) ** 2 < 0.1:
                joined_result.remove(s)
    print_("Clipped and joined path in {}".format(time.time() - time_))

    ########################################################################
    # Now to the Dummy clipping: remove parts from split offset if their
    # centers are  closer to the original path than offset radius.
    ########################################################################

    if abs(r * .01) < 1:
        r1 = (0.99 * r) ** 2
        r2 = (1.01 * r) ** 2
    else:
        r1 = (abs(r) - 1) ** 2
        r2 = (abs(r) + 1) ** 2

    for s in joined_result[:]:
        dist = csp_to_point_distance(original_csp, s[int(len(s) / 2)][1], dist_bounds=[r1, r2])
        if not r1 < dist[0] < r2:
            joined_result.remove(s)
            if options.offset_draw_clippend_path:
                draw_csp([s], comment=math.sqrt(dist[0]))
                draw_pointer(csp_at_t(csp[dist[1]][dist[2] - 1], csp[dist[1]][dist[2]], dist[3]) + s[int(len(s) / 2)][1], "blue", "line", comment=[math.sqrt(dist[0]), i, j, sp])

    print_("-----------------------------")
    print_("Total offset time {}".format(time.time() - time_start))
    print_()
    return joined_result


################################################################################
#
# Biarc function
#
# Calculates biarc approximation of cubic super path segment
#  splits segment if needed or approximates it with straight line
#
################################################################################
def biarc(sp1, sp2, z1, z2, depth=0):
    def biarc_split(sp1, sp2, z1, z2, depth):
        if depth < options.biarc_max_split_depth:
            sp1, sp2, sp3 = csp_split(sp1, sp2)
            l1 = cspseglength(sp1, sp2)
            l2 = cspseglength(sp2, sp3)
            if l1 + l2 == 0:
                zm = z1
            else:
                zm = z1 + (z2 - z1) * l1 / (l1 + l2)
            return biarc(sp1, sp2, z1, zm, depth + 1) + biarc(sp2, sp3, zm, z2, depth + 1)
        else:
            return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]]

    P0 = P(sp1[1])
    P4 = P(sp2[1])
    TS = (P(sp1[2]) - P0)
    TE = -(P(sp2[0]) - P4)
    v = P0 - P4
    tsa = TS.angle()
    tea = TE.angle()
    va = v.angle()
    if TE.mag() < STRAIGHT_DISTANCE_TOLERANCE and TS.mag() < STRAIGHT_DISTANCE_TOLERANCE:
        # Both tangents are zero - line straight
        return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]]
    if TE.mag() < STRAIGHT_DISTANCE_TOLERANCE:
        TE = -(TS + v).unit()
        r = TS.mag() / v.mag() * 2
    elif TS.mag() < STRAIGHT_DISTANCE_TOLERANCE:
        TS = -(TE + v).unit()
        r = 1 / (TE.mag() / v.mag() * 2)
    else:
        r = TS.mag() / TE.mag()
    TS = TS.unit()
    TE = TE.unit()
    tang_are_parallel = ((tsa - tea) % math.pi < STRAIGHT_TOLERANCE or math.pi - (tsa - tea) % math.pi < STRAIGHT_TOLERANCE)
    if (tang_are_parallel and
            ((v.mag() < STRAIGHT_DISTANCE_TOLERANCE or TE.mag() < STRAIGHT_DISTANCE_TOLERANCE or TS.mag() < STRAIGHT_DISTANCE_TOLERANCE) or
             1 - abs(TS * v / (TS.mag() * v.mag())) < STRAIGHT_TOLERANCE)):
        # Both tangents are parallel and start and end are the same - line straight
        # or one of tangents still smaller then tolerance

        # Both tangents and v are parallel - line straight
        return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]]

    c = v * v
    b = 2 * v * (r * TS + TE)
    a = 2 * r * (TS * TE - 1)
    if v.mag() == 0:
        return biarc_split(sp1, sp2, z1, z2, depth)
    asmall = abs(a) < 10 ** -10
    bsmall = abs(b) < 10 ** -10
    csmall = abs(c) < 10 ** -10
    if asmall and b != 0:
        beta = -c / b
    elif csmall and a != 0:
        beta = -b / a
    elif not asmall:
        discr = b * b - 4 * a * c
        if discr < 0:
            raise ValueError(a, b, c, discr)
        disq = discr ** .5
        beta1 = (-b - disq) / 2 / a
        beta2 = (-b + disq) / 2 / a
        if beta1 * beta2 > 0:
            raise ValueError(a, b, c, disq, beta1, beta2)
        beta = max(beta1, beta2)
    elif asmall and bsmall:
        return biarc_split(sp1, sp2, z1, z2, depth)
    alpha = beta * r
    ab = alpha + beta
    P1 = P0 + alpha * TS
    P3 = P4 - beta * TE
    P2 = (beta / ab) * P1 + (alpha / ab) * P3

    def calculate_arc_params(P0, P1, P2):
        D = (P0 + P2) / 2
        if (D - P1).mag() == 0:
            return None, None
        R = D - ((D - P0).mag() ** 2 / (D - P1).mag()) * (P1 - D).unit()
        p0a = (P0 - R).angle() % (2 * math.pi)
        p1a = (P1 - R).angle() % (2 * math.pi)
        p2a = (P2 - R).angle() % (2 * math.pi)
        alpha = (p2a - p0a) % (2 * math.pi)
        if (p0a < p2a and (p1a < p0a or p2a < p1a)) or (p2a < p1a < p0a):
            alpha = -2 * math.pi + alpha
        if abs(R.x) > 1000000 or abs(R.y) > 1000000 or (R - P0).mag() < options.min_arc_radius ** 2:
            return None, None
        else:
            return R, alpha

    R1, a1 = calculate_arc_params(P0, P1, P2)
    R2, a2 = calculate_arc_params(P2, P3, P4)
    if R1 is None or R2 is None or (R1 - P0).mag() < STRAIGHT_TOLERANCE or (R2 - P2).mag() < STRAIGHT_TOLERANCE:
        return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]]

    d = csp_to_arc_distance(sp1, sp2, [P0, P2, R1, a1], [P2, P4, R2, a2])
    if d > options.biarc_tolerance and depth < options.biarc_max_split_depth:
        return biarc_split(sp1, sp2, z1, z2, depth)
    else:
        if R2.mag() * a2 == 0:
            zm = z2
        else:
            zm = z1 + (z2 - z1) * (abs(R1.mag() * a1)) / (abs(R2.mag() * a2) + abs(R1.mag() * a1))

        l = (P0 - P2).l2()
        if l < EMC_TOLERANCE_EQUAL ** 2 or l < EMC_TOLERANCE_EQUAL ** 2 * R1.l2() / 100:
            # arc should be straight otherwise it could be treated as full circle
            arc1 = [sp1[1], 'line', 0, 0, [P2.x, P2.y], [z1, zm]]
        else:
            arc1 = [sp1[1], 'arc', [R1.x, R1.y], a1, [P2.x, P2.y], [z1, zm]]

        l = (P4 - P2).l2()
        if l < EMC_TOLERANCE_EQUAL ** 2 or l < EMC_TOLERANCE_EQUAL ** 2 * R2.l2() / 100:
            # arc should be straight otherwise it could be treated as full circle
            arc2 = [[P2.x, P2.y], 'line', 0, 0, [P4.x, P4.y], [zm, z2]]
        else:
            arc2 = [[P2.x, P2.y], 'arc', [R2.x, R2.y], a2, [P4.x, P4.y], [zm, z2]]

        return [arc1, arc2]


class Postprocessor(object):
    def __init__(self, error_function_handler):
        self.error = error_function_handler
        self.functions = {
            "remap": self.remap,
            "remapi": self.remapi,
            "scale": self.scale,
            "move": self.move,
            "flip": self.flip_axis,
            "flip_axis": self.flip_axis,
            "round": self.round_coordinates,
            "parameterize": self.parameterize,
            "regex": self.re_sub_on_gcode_lines
        }

    def process(self, command):
        command = re.sub(r"\\\\", ":#:#:slash:#:#:", command)
        command = re.sub(r"\\;", ":#:#:semicolon:#:#:", command)
        command = command.split(";")
        for s in command:
            s = re.sub(":#:#:slash:#:#:", "\\\\", s)
            s = re.sub(":#:#:semicolon:#:#:", "\\;", s)
            s = s.strip()
            if s != "":
                self.parse_command(s)

    def parse_command(self, command):
        r = re.match(r"([A-Za-z0-9_]+)\s*\(\s*(.*)\)", command)
        if not r:
            self.error("Parse error while postprocessing.\n(Command: '{}')".format(command), "error")
        function = r.group(1).lower()
        parameters = r.group(2)
        if function in self.functions:
            print_("Postprocessor: executing function {}({})".format(function, parameters))
            self.functions[function](parameters)
        else:
            self.error("Unrecognized function '{}' while postprocessing.\n(Command: '{}')".format(function, command), "error")

    def re_sub_on_gcode_lines(self, parameters):
        gcode = self.gcode.split("\n")
        self.gcode = ""
        try:
            for line in gcode:
                self.gcode += eval("re.sub({},line)".format(parameters)) + "\n"

        except Exception as ex:
            self.error("Bad parameters for regexp. "
                       "They should be as re.sub pattern and replacement parameters! "
                       "For example: r\"G0(\\d)\", r\"G\\1\" \n"
                       "(Parameters: '{}')\n {}".format(parameters, ex), "error")

    def remapi(self, parameters):
        self.remap(parameters, case_sensitive=True)

    def remap(self, parameters, case_sensitive=False):
        # remap parameters should be like "x->y,y->x"
        parameters = parameters.replace("\\,", ":#:#:coma:#:#:")
        parameters = parameters.split(",")
        pattern = []
        remap = []
        for s in parameters:
            s = s.replace(":#:#:coma:#:#:", "\\,")
            r = re.match("""\\s*(\'|\")(.*)\\1\\s*->\\s*(\'|\")(.*)\\3\\s*""", s)
            if not r:
                self.error("Bad parameters for remap.\n(Parameters: '{}')".format(parameters), "error")
            pattern += [r.group(2)]
            remap += [r.group(4)]

        for i in range(len(pattern)):
            if case_sensitive:
                self.gcode = ireplace(self.gcode, pattern[i], ":#:#:remap_pattern{}:#:#:".format(i))
            else:
                self.gcode = self.gcode.replace(pattern[i], ":#:#:remap_pattern{}:#:#:".format(i))

        for i in range(len(remap)):
            self.gcode = self.gcode.replace(":#:#:remap_pattern{}:#:#:".format(i), remap[i])

    def transform(self, move, scale):
        axis = ["xi", "yj", "zk", "a"]
        flip = scale[0] * scale[1] * scale[2] < 0
        gcode = ""
        warned = []
        r_scale = scale[0]
        plane = "g17"
        for s in self.gcode.split("\n"):
            # get plane selection:
            s_wo_comments = re.sub(r"\([^\)]*\)", "", s)
            r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
            if r:
                plane = r.group(1).lower()
                if plane == "g17":
                    r_scale = scale[0]  # plane XY -> scale x
                if plane == "g18":
                    r_scale = scale[0]  # plane XZ -> scale x
                if plane == "g19":
                    r_scale = scale[1]  # plane YZ -> scale y
            # Raise warning if scale factors are not the game for G02 and G03
            if plane not in warned:
                r = re.search(r"(?i)(G02|G03)", s_wo_comments)
                if r:
                    if plane == "g17" and scale[0] != scale[1]:
                        self.error("Post-processor: Scale factors for X and Y axis are not the same. G02 and G03 codes will be corrupted.")
                    if plane == "g18" and scale[0] != scale[2]:
                        self.error("Post-processor: Scale factors for X and Z axis are not the same. G02 and G03 codes will be corrupted.")
                    if plane == "g19" and scale[1] != scale[2]:
                        self.error("Post-processor: Scale factors for Y and Z axis are not the same. G02 and G03 codes will be corrupted.")
                    warned += [plane]
            # Transform
            for i in range(len(axis)):
                if move[i] != 0 or scale[i] != 1:
                    for a in axis[i]:
                        r = re.search(r"(?i)(" + a + r")\s*(-?)\s*(\d*\.?\d*)", s)
                        if r and r.group(3) != "":
                            s = re.sub(r"(?i)(" + a + r")\s*(-?)\s*(\d*\.?\d*)", r"\1 {:f}".format(float(r.group(2) + r.group(3)) * scale[i] + (move[i] if a not in ["i", "j", "k"] else 0)), s)
            # scale radius R
            if r_scale != 1:
                r = re.search(r"(?i)(r)\s*(-?\s*(\d*\.?\d*))", s)
                if r and r.group(3) != "":
                    try:
                        s = re.sub(r"(?i)(r)\s*(-?)\s*(\d*\.?\d*)", r"\1 {:f}".format(float(r.group(2) + r.group(3)) * r_scale), s)
                    except:
                        pass

            gcode += s + "\n"

        self.gcode = gcode
        if flip:
            self.remapi("'G02'->'G03', 'G03'->'G02'")

    def parameterize(self, parameters):
        planes = []
        feeds = {}
        coords = []
        gcode = ""
        coords_def = {"x": "x", "y": "y", "z": "z", "i": "x", "j": "y", "k": "z", "a": "a"}
        for s in self.gcode.split("\n"):
            s_wo_comments = re.sub(r"\([^\)]*\)", "", s)
            # get Planes
            r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
            if r:
                plane = r.group(1).lower()
                if plane not in planes:
                    planes += [plane]
            # get Feeds
            r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
            if r:
                feed = float(r.group(2) + r.group(3))
                if feed not in feeds:
                    feeds[feed] = "#" + str(len(feeds) + 20)

            # Coordinates
            for c in "xyzijka":
                r = re.search(r"(?i)(" + c + r")\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
                if r:
                    c = coords_def[r.group(1).lower()]
                    if c not in coords:
                        coords += [c]
        # Add offset parametrization
        offset = {"x": "#6", "y": "#7", "z": "#8", "a": "#9"}
        for c in coords:
            gcode += "{}  = 0 ({} axis offset)\n".format(offset[c], c.upper())

        # Add scale parametrization
        if not planes:
            planes = ["g17"]
        if len(planes) > 1:  # have G02 and G03 in several planes scale_x = scale_y = scale_z required
            gcode += "#10 = 1 (Scale factor)\n"
            scale = {"x": "#10", "i": "#10", "y": "#10", "j": "#10", "z": "#10", "k": "#10", "r": "#10"}
        else:
            gcode += "#10 = 1 ({} Scale factor)\n".format({"g17": "XY", "g18": "XZ", "g19": "YZ"}[planes[0]])
            gcode += "#11 = 1 ({} Scale factor)\n".format({"g17": "Z", "g18": "Y", "g19": "X"}[planes[0]])
            scale = {"x": "#10", "i": "#10", "y": "#10", "j": "#10", "z": "#10", "k": "#10", "r": "#10"}
            if "g17" in planes:
                scale["z"] = "#11"
                scale["k"] = "#11"
            if "g18" in planes:
                scale["y"] = "#11"
                scale["j"] = "#11"
            if "g19" in planes:
                scale["x"] = "#11"
                scale["i"] = "#11"
        # Add a scale
        if "a" in coords:
            gcode += "#12  = 1 (A axis scale)\n"
            scale["a"] = "#12"

        # Add feed parametrization
        for f in feeds:
            gcode += "{} = {:f} (Feed definition)\n".format(feeds[f], f)

        # Parameterize Gcode
        for s in self.gcode.split("\n"):
            # feed replace :
            r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s)
            if r and len(r.group(3)) > 0:
                s = re.sub(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", "F [{}]".format(feeds[float(r.group(2) + r.group(3))]), s)
            # Coords XYZA replace
            for c in "xyza":
                r = re.search(r"(?i)((" + c + r")\s*(-?)\s*(\d*\.?\d*))", s)
                if r and len(r.group(4)) > 0:
                    s = re.sub(r"(?i)(" + c + r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*{}+{}]".format(scale[c], offset[c]), s)

            # Coords IJKR replace
            for c in "ijkr":
                r = re.search(r"(?i)((" + c + r")\s*(-?)\s*(\d*\.?\d*))", s)
                if r and len(r.group(4)) > 0:
                    s = re.sub(r"(?i)(" + c + r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*{}]".format(scale[c]), s)

            gcode += s + "\n"

        self.gcode = gcode

    def round_coordinates(self, parameters):
        try:
            round_ = int(parameters)
        except:
            self.error("Bad parameters for round. Round should be an integer! \n(Parameters: '{}')".format(parameters), "error")
        gcode = ""
        for s in self.gcode.split("\n"):
            for a in "xyzijkaf":
                r = re.search(r"(?i)(" + a + r")\s*(-?\s*(\d*\.?\d*))", s)
                if r:

                    if r.group(2) != "":
                        s = re.sub(
                                r"(?i)(" + a + r")\s*(-?)\s*(\d*\.?\d*)",
                                (r"\1 %0." + str(round_) + "f" if round_ > 0 else r"\1 %d") % round(float(r.group(2)), round_),
                                s)
            gcode += s + "\n"
        self.gcode = gcode

    def scale(self, parameters):
        parameters = parameters.split(",")
        scale = [1., 1., 1., 1.]
        try:
            for i in range(len(parameters)):
                if float(parameters[i]) == 0:
                    self.error("Bad parameters for scale. Scale should not be 0 at any axis! \n(Parameters: '{}')".format(parameters), "error")
                scale[i] = float(parameters[i])
        except:
            self.error("Bad parameters for scale.\n(Parameters: '{}')".format(parameters), "error")
        self.transform([0, 0, 0, 0], scale)

    def move(self, parameters):
        parameters = parameters.split(",")
        move = [0., 0., 0., 0.]
        try:
            for i in range(len(parameters)):
                move[i] = float(parameters[i])
        except:
            self.error("Bad parameters for move.\n(Parameters: '{}')".format(parameters), "error")
        self.transform(move, [1., 1., 1., 1.])

    def flip_axis(self, parameters):
        parameters = parameters.lower()
        axis = {"x": 1., "y": 1., "z": 1., "a": 1.}
        for p in parameters:
            if p in [",", " ", "    ", "\r", "'", '"']:
                continue
            if p not in ["x", "y", "z", "a"]:
                self.error("Bad parameters for flip_axis. Parameter should be string consists of 'xyza' \n(Parameters: '{}')".format(parameters), "error")
            axis[p] = -axis[p]
        self.scale("{:f},{:f},{:f},{:f}".format(axis["x"], axis["y"], axis["z"], axis["a"]))


################################################################################
# Polygon class
################################################################################
class Polygon(object):
    def __init__(self, polygon=None):
        self.polygon = [] if polygon is None else polygon[:]

    def move(self, x, y):
        for i in range(len(self.polygon)):
            for j in range(len(self.polygon[i])):
                self.polygon[i][j][0] += x
                self.polygon[i][j][1] += y

    def bounds(self):
        minx = 1e400
        miny = 1e400
        maxx = -1e400
        maxy = -1e400
        for poly in self.polygon:
            for p in poly:
                if minx > p[0]:
                    minx = p[0]
                if miny > p[1]:
                    miny = p[1]
                if maxx < p[0]:
                    maxx = p[0]
                if maxy < p[1]:
                    maxy = p[1]
        return minx * 1, miny * 1, maxx * 1, maxy * 1

    def width(self):
        b = self.bounds()
        return b[2] - b[0]

    def rotate_(self, sin, cos):
        self.polygon = [
            [
                [point[0] * cos - point[1] * sin, point[0] * sin + point[1] * cos] for point in subpoly
            ]
            for subpoly in self.polygon
        ]

    def rotate(self, a):
        cos = math.cos(a)
        sin = math.sin(a)
        self.rotate_(sin, cos)

    def drop_into_direction(self, direction, surface):
        # Polygon is a list of simple polygons
        # Surface is a polygon + line y = 0
        # Direction is [dx,dy]
        if len(self.polygon) == 0 or len(self.polygon[0]) == 0:
            return
        if direction[0] ** 2 + direction[1] ** 2 < 1e-10:
            return
        direction = normalize(direction)
        sin = direction[0]
        cos = -direction[1]
        self.rotate_(-sin, cos)
        surface.rotate_(-sin, cos)
        self.drop_down(surface, zerro_plane=False)
        self.rotate_(sin, cos)
        surface.rotate_(sin, cos)

    def centroid(self):
        centroids = []
        sa = 0
        for poly in self.polygon:
            cx = 0
            cy = 0
            a = 0
            for i in range(len(poly)):
                [x1, y1] = poly[i - 1]
                [x2, y2] = poly[i]
                cx += (x1 + x2) * (x1 * y2 - x2 * y1)
                cy += (y1 + y2) * (x1 * y2 - x2 * y1)
                a += (x1 * y2 - x2 * y1)
            a *= 3.
            if abs(a) > 0:
                cx /= a
                cy /= a
                sa += abs(a)
                centroids += [[cx, cy, a]]
        if sa == 0:
            return [0., 0.]
        cx = 0
        cy = 0
        for c in centroids:
            cx += c[0] * c[2]
            cy += c[1] * c[2]
        cx /= sa
        cy /= sa
        return [cx, cy]

    def drop_down(self, surface, zerro_plane=True):
        # Polygon is a list of simple polygons
        # Surface is a polygon + line y = 0
        # Down means min y (0,-1)
        if len(self.polygon) == 0 or len(self.polygon[0]) == 0:
            return
        # Get surface top point
        top = surface.bounds()[3]
        if zerro_plane:
            top = max(0, top)
        # Get polygon bottom point
        bottom = self.bounds()[1]
        self.move(0, top - bottom + 10)
        # Now get shortest distance from surface to polygon in positive x=0 direction
        # Such distance = min(distance(vertex, edge)...)  where edge from surface and
        # vertex from polygon and vice versa...
        dist = 1e300
        for poly in surface.polygon:
            for i in range(len(poly)):
                for poly1 in self.polygon:
                    for i1 in range(len(poly1)):
                        st = poly[i - 1]
                        end = poly[i]
                        vertex = poly1[i1]
                        if st[0] <= vertex[0] <= end[0] or end[0] <= vertex[0] <= st[0]:
                            if st[0] == end[0]:
                                d = min(vertex[1] - st[1], vertex[1] - end[1])
                            else:
                                d = vertex[1] - st[1] - (end[1] - st[1]) * (vertex[0] - st[0]) / (end[0] - st[0])
                            if dist > d:
                                dist = d
                        # and vice versa just change the sign because vertex now under the edge
                        st = poly1[i1 - 1]
                        end = poly1[i1]
                        vertex = poly[i]
                        if st[0] <= vertex[0] <= end[0] or end[0] <= vertex[0] <= st[0]:
                            if st[0] == end[0]:
                                d = min(- vertex[1] + st[1], -vertex[1] + end[1])
                            else:
                                d = - vertex[1] + st[1] + (end[1] - st[1]) * (vertex[0] - st[0]) / (end[0] - st[0])
                            if dist > d:
                                dist = d

        if zerro_plane and dist > 10 + top:
            dist = 10 + top
        self.move(0, -dist)

    def draw(self, color="#075", width=.1, group=None):
        csp = [csp_subpath_line_to([], poly + [poly[0]]) for poly in self.polygon]
        draw_csp(csp, width=width, group=group)

    def add(self, add):
        if type(add) == type([]):
            self.polygon += add[:]
        else:
            self.polygon += add.polygon[:]

    def point_inside(self, p):
        inside = False
        for poly in self.polygon:
            for i in range(len(poly)):
                st = poly[i - 1]
                end = poly[i]
                if p == st or p == end:
                    return True  # point is a vertex = point is on the edge
                if st[0] > end[0]:
                    st, end = end, st  # This will be needed to check that edge if open only at right end
                c = (p[1] - st[1]) * (end[0] - st[0]) - (end[1] - st[1]) * (p[0] - st[0])
                if st[0] <= p[0] < end[0]:
                    if c < 0:
                        inside = not inside
                    elif c == 0:
                        return True  # point is on the edge
                elif st[0] == end[0] == p[0] and (st[1] <= p[1] <= end[1] or end[1] <= p[1] <= st[1]):  # point is on the edge
                    return True
        return inside

    def hull(self):
        # Add vertices at all self intersection points.
        hull = []
        for i1 in range(len(self.polygon)):
            poly1 = self.polygon[i1]
            poly_ = []
            for j1 in range(len(poly1)):
                s = poly1[j1 - 1]
                e = poly1[j1]
                poly_ += [s]

                # Check self intersections
                for j2 in range(j1 + 1, len(poly1)):
                    s1 = poly1[j2 - 1]
                    e1 = poly1[j2]
                    int_ = line_line_intersection_points(s, e, s1, e1)
                    for p in int_:
                        if point_to_point_d2(p, s) > 0.000001 and point_to_point_d2(p, e) > 0.000001:
                            poly_ += [p]
                # Check self intersections with other polys
                for i2 in range(len(self.polygon)):
                    if i1 == i2:
                        continue
                    poly2 = self.polygon[i2]
                    for j2 in range(len(poly2)):
                        s1 = poly2[j2 - 1]
                        e1 = poly2[j2]
                        int_ = line_line_intersection_points(s, e, s1, e1)
                        for p in int_:
                            if point_to_point_d2(p, s) > 0.000001 and point_to_point_d2(p, e) > 0.000001:
                                poly_ += [p]
            hull += [poly_]
        # Create the dictionary containing all edges in both directions
        edges = {}
        for poly in self.polygon:
            for i in range(len(poly)):
                s = tuple(poly[i - 1])
                e = tuple(poly[i])
                if point_to_point_d2(e, s) < 0.000001:
                    continue
                break_s = False
                break_e = False
                for p in edges:
                    if point_to_point_d2(p, s) < 0.000001:
                        break_s = True
                        s = p
                    if point_to_point_d2(p, e) < 0.000001:
                        break_e = True
                        e = p
                    if break_s and break_e:
                        break
                l = point_to_point_d(s, e)
                if not break_s and not break_e:
                    edges[s] = [[s, e, l]]
                    edges[e] = [[e, s, l]]
                else:
                    if e in edges:
                        for edge in edges[e]:
                            if point_to_point_d2(edge[1], s) < 0.000001:
                                break
                        if point_to_point_d2(edge[1], s) > 0.000001:
                            edges[e] += [[e, s, l]]
                    else:
                        edges[e] = [[e, s, l]]
                    if s in edges:
                        for edge in edges[s]:
                            if point_to_point_d2(edge[1], e) < 0.000001:
                                break
                        if point_to_point_d2(edge[1], e) > 0.000001:
                            edges[s] += [[s, e, l]]
                    else:
                        edges[s] = [[s, e, l]]

        def angle_quadrant(sin, cos):
            # quadrants are (0,pi/2], (pi/2,pi], (pi,3*pi/2], (3*pi/2, 2*pi], i.e. 0 is in the 4-th quadrant
            if sin > 0 and cos >= 0:
                return 1
            if sin >= 0 and cos < 0:
                return 2
            if sin < 0 and cos <= 0:
                return 3
            if sin <= 0 and cos > 0:
                return 4

        def angle_is_less(sin, cos, sin1, cos1):
            # 0 = 2*pi is the largest angle
            if [sin1, cos1] == [0, 1]:
                return True
            if [sin, cos] == [0, 1]:
                return False
            if angle_quadrant(sin, cos) > angle_quadrant(sin1, cos1):
                return False
            if angle_quadrant(sin, cos) < angle_quadrant(sin1, cos1):
                return True
            if sin >= 0 and cos > 0:
                return sin < sin1
            if sin > 0 and cos <= 0:
                return sin > sin1
            if sin <= 0 and cos < 0:
                return sin > sin1
            if sin < 0 and cos >= 0:
                return sin < sin1

        def get_closes_edge_by_angle(edges, last):
            # Last edge is normalized vector of the last edge.
            min_angle = [0, 1]
            next = last
            last_edge = [(last[0][0] - last[1][0]) / last[2], (last[0][1] - last[1][1]) / last[2]]
            for p in edges:

                cur = [(p[1][0] - p[0][0]) / p[2], (p[1][1] - p[0][1]) / p[2]]
                cos = dot(cur, last_edge)
                sin = cross(cur, last_edge)

                if angle_is_less(sin, cos, min_angle[0], min_angle[1]):
                    min_angle = [sin, cos]
                    next = p

            return next

        # Join edges together into new polygon cutting the vertexes inside new polygon
        self.polygon = []
        len_edges = sum([len(edges[p]) for p in edges])
        loops = 0

        while len(edges) > 0:
            poly = []
            if loops > len_edges:
                raise ValueError("Hull error")
            loops += 1
            # Find left most vertex.
            start = (1e100, 1)
            for edge in edges:
                start = min(start, min(edges[edge]))
            last = [(start[0][0] - 1, start[0][1]), start[0], 1]
            first_run = True
            loops1 = 0
            while last[1] != start[0] or first_run:
                first_run = False
                if loops1 > len_edges:
                    raise ValueError("Hull error")
                loops1 += 1
                next = get_closes_edge_by_angle(edges[last[1]], last)

                last = next
                poly += [list(last[0])]
            self.polygon += [poly]
            # Remove all edges that are intersects new poly (any vertex inside new poly)
            poly_ = Polygon([poly])
            for p in edges.keys()[:]:
                if poly_.point_inside(list(p)):
                    del edges[p]
        self.draw(color="Green", width=1)


################################################################################
#
# Gcodetools class
#
################################################################################

class Gcodetools(inkex.EffectExtension):
    multi_inx = True # XXX Remove this after refactoring

    def export_gcode(self, gcode, no_headers=False):
        if self.options.postprocessor != "" or self.options.postprocessor_custom != "":
            postprocessor = Postprocessor(self.error)
            postprocessor.gcode = gcode
            if self.options.postprocessor != "":
                postprocessor.process(self.options.postprocessor)
            if self.options.postprocessor_custom != "":
                postprocessor.process(self.options.postprocessor_custom)

        if not no_headers:
            postprocessor.gcode = self.header + postprocessor.gcode + self.footer

        with open(os.path.join(self.options.directory, self.options.file), "w") as f:
            f.write(postprocessor.gcode)

    ################################################################################
    # In/out paths:
    # TODO move it to the bottom
    ################################################################################
    def tab_plasma_prepare_path(self):
        self.get_info_plus()

        def add_arc(sp1, sp2, end=False, l=10., r=10.):
            if not end:
                n = csp_normalized_normal(sp1, sp2, 0.)
                return csp_reverse([arc_from_s_r_n_l(sp1[1], r, n, -l)])[0]
            else:
                n = csp_normalized_normal(sp1, sp2, 1.)
                return arc_from_s_r_n_l(sp2[1], r, n, l)

        def add_normal(sp1, sp2, end=False, l=10., r=10.):
            # r is needed only for be compatible with add_arc
            if not end:
                n = csp_normalized_normal(sp1, sp2, 0.)
                p = [n[0] * l + sp1[1][0], n[1] * l + sp1[1][1]]
                return csp_subpath_line_to([], [p, sp1[1]])
            else:
                n = csp_normalized_normal(sp1, sp2, 1.)
                p = [n[0] * l + sp2[1][0], n[1] * l + sp2[1][1]]
                return csp_subpath_line_to([], [sp2[1], p])

        def add_tangent(sp1, sp2, end=False, l=10., r=10.):
            # r is needed only for be compatible with add_arc
            if not end:
                n = csp_normalized_slope(sp1, sp2, 0.)
                p = [-n[0] * l + sp1[1][0], -n[1] * l + sp1[1][1]]
                return csp_subpath_line_to([], [p, sp1[1]])
            else:
                n = csp_normalized_slope(sp1, sp2, 1.)
                p = [n[0] * l + sp2[1][0], n[1] * l + sp2[1][1]]
                return csp_subpath_line_to([], [sp2[1], p])

        if not self.options.in_out_path and not self.options.plasma_prepare_corners and self.options.in_out_path_do_not_add_reference_point:
            self.error("Warning! Extension is not said to do anything! Enable one of Create in-out paths or Prepare corners checkboxes or disable Do not add in-out reference point!")
            return

        # Add in-out-reference point if there is no one yet.
        if ((len(self.in_out_reference_points) == 0 and self.options.in_out_path
             or not self.options.in_out_path and not self.options.plasma_prepare_corners)
                and not self.options.in_out_path_do_not_add_reference_point):
            self.options.orientation_points_count = "in-out reference point"
            #self.orientation()

        if self.options.in_out_path or self.options.plasma_prepare_corners:
            self.set_markers()
            add_func = {"Round": add_arc, "Perpendicular": add_normal, "Tangent": add_tangent}[self.options.in_out_path_type]
            if self.options.in_out_path_type == "Round" and self.options.in_out_path_len > self.options.in_out_path_radius * 3 / 2 * math.pi:
                self.error("In-out len is to big for in-out radius will cropp it to be r*3/2*pi!")

            if self.selected_paths == {} and self.options.auto_select_paths:
                self.selected_paths = self.paths
                self.error("No paths are selected! Trying to work on all available paths.")

            if self.selected_paths == {}:
                self.error("Nothing is selected. Please select something.")
            a = self.options.plasma_prepare_corners_tolerance
            corner_tolerance = cross([1., 0.], [math.cos(a), math.sin(a)])

            for layer in self.layers:
                if layer in self.selected_paths:
                    max_dist = self.transform_scalar(self.options.in_out_path_point_max_dist, layer, reverse=True)
                    l = self.transform_scalar(self.options.in_out_path_len, layer, reverse=True)
                    plasma_l = self.transform_scalar(self.options.plasma_prepare_corners_distance, layer, reverse=True)
                    r = self.transform_scalar(self.options.in_out_path_radius, layer, reverse=True)
                    l = min(l, r * 3 / 2 * math.pi)

                    for path in self.selected_paths[layer]:
                        csp = self.apply_transforms(path, path.path.to_superpath())
                        csp = csp_remove_zero_segments(csp)
                        res = []

                        for subpath in csp:
                            # Find closes point to in-out reference point
                            # If subpath is open skip this step
                            if self.options.in_out_path:
                                # split and reverse path for further add in-out points
                                if point_to_point_d2(subpath[0][1], subpath[-1][1]) < 1.e-10:
                                    d = [1e100, 1, 1, 1.]
                                    for p in self.in_out_reference_points:
                                        d1 = csp_to_point_distance([subpath], p, dist_bounds=[0, max_dist])
                                        if d1[0] < d[0]:
                                            d = d1[:]
                                            p_ = p
                                    if d[0] < max_dist ** 2:
                                        # Lets find is there any angles near this point to put in-out path in
                                        # the angle if it's possible
                                        # remove last node to make iterations easier
                                        subpath[0][0] = subpath[-1][0]
                                        del subpath[-1]
                                        max_cross = [-1e100, None]
                                        for j in range(len(subpath)):
                                            sp1 = subpath[j - 2]
                                            sp2 = subpath[j - 1]
                                            sp3 = subpath[j]
                                            if point_to_point_d2(sp2[1], p_) < max_dist ** 2:
                                                s1 = csp_normalized_slope(sp1, sp2, 1.)
                                                s2 = csp_normalized_slope(sp2, sp3, 0.)
                                                max_cross = max(max_cross, [cross(s1, s2), j - 1])
                                        # return back last point
                                        subpath.append(subpath[0])
                                        if max_cross[1] is not None and max_cross[0] > corner_tolerance:
                                            # there's an angle near the point
                                            j = max_cross[1]
                                            if j < 0:
                                                j -= 1
                                            if j != 0:
                                                subpath = csp_concat_subpaths(subpath[j:], subpath[:j + 1])
                                        else:
                                            # have to cut path's segment
                                            d, i, j, t = d
                                            sp1, sp2, sp3 = csp_split(subpath[j - 1], subpath[j], t)
                                            subpath = csp_concat_subpaths([sp2, sp3], subpath[j:], subpath[:j], [sp1, sp2])

                            if self.options.plasma_prepare_corners:
                                # prepare corners
                                # find corners and add some nodes
                                # corner at path's start/end is ignored
                                res_ = [subpath[0]]
                                for sp2, sp3 in zip(subpath[1:], subpath[2:]):
                                    sp1 = res_[-1]
                                    s1 = csp_normalized_slope(sp1, sp2, 1.)
                                    s2 = csp_normalized_slope(sp2, sp3, 0.)
                                    if cross(s1, s2) > corner_tolerance:
                                        # got a corner to process
                                        S1 = P(s1)
                                        S2 = P(s2)
                                        N = (S1 - S2).unit() * plasma_l
                                        SP2 = P(sp2[1])
                                        P1 = (SP2 + N)
                                        res_ += [
                                            [sp2[0], sp2[1], (SP2 + S1 * plasma_l).to_list()],
                                            [(P1 - N.ccw() / 2).to_list(), P1.to_list(), (P1 + N.ccw() / 2).to_list()],
                                            [(SP2 - S2 * plasma_l).to_list(), sp2[1], sp2[2]]
                                        ]
                                    else:
                                        res_ += [sp2]
                                res_ += [sp3]
                                subpath = res_
                            if self.options.in_out_path:
                                # finally add let's add in-out paths...
                                subpath = csp_concat_subpaths(
                                    add_func(subpath[0], subpath[1], False, l, r),
                                    subpath,
                                    add_func(subpath[-2], subpath[-1], True, l, r)
                                )

                            res += [subpath]

                        if self.options.in_out_path_replace_original_path:
                            path.path = CubicSuperPath(self.apply_transforms(path, res, True))
                        else:
                            draw_csp(res, width=1, style=MARKER_STYLE["in_out_path_style"])

    def add_arguments(self, pars):
        add_argument = pars.add_argument
        add_argument("-d", "--directory", default="/home/", help="Directory for gcode file")
        add_argument("-f", "--filename", dest="file", default="-1.0", help="File name")
        add_argument("--add-numeric-suffix-to-filename", type=inkex.Boolean, default=True, help="Add numeric suffix to filename")
        add_argument("--Zscale", type=float, default="1.0", help="Scale factor Z")
        add_argument("--Zoffset", type=float, default="0.0", help="Offset along Z")
        add_argument("-s", "--Zsafe", type=float, default="0.5", help="Z above all obstacles")
        add_argument("-z", "--Zsurface", type=float, default="0.0", help="Z of the surface")
        add_argument("-c", "--Zdepth", type=float, default="-0.125", help="Z depth of cut")
        add_argument("--Zstep", type=float, default="-0.125", help="Z step of cutting")
        add_argument("-p", "--feed", type=float, default="4.0", help="Feed rate in unit/min")

        add_argument("--biarc-tolerance", type=float, default="1", help="Tolerance used when calculating biarc interpolation.")
        add_argument("--biarc-max-split-depth", type=int, default="4", help="Defines maximum depth of splitting while approximating using biarcs.")
        add_argument("--path-to-gcode-order", default="path by path", help="Defines cutting order path by path or layer by layer.")
        add_argument("--path-to-gcode-depth-function", default="zd", help="Path to gcode depth function.")
        add_argument("--path-to-gcode-sort-paths", type=inkex.Boolean, default=True, help="Sort paths to reduce rapid distance.")
        add_argument("--comment-gcode", default="", help="Comment Gcode")
        add_argument("--comment-gcode-from-properties", type=inkex.Boolean, default=False, help="Get additional comments from Object Properties")

        add_argument("--tool-diameter", type=float, default="3", help="Tool diameter used for area cutting")
        add_argument("--max-area-curves", type=int, default="100", help="Maximum area curves for each area")
        add_argument("--area-inkscape-radius", type=float, default="0", help="Area curves overlapping (depends on tool diameter [0, 0.9])")
        add_argument("--area-tool-overlap", type=float, default="-10", help="Radius for preparing curves using inkscape")
        add_argument("--unit", default="G21 (All units in mm)", help="Units")
        add_argument("--active-tab", type=self.arg_method('tab'), default=self.tab_help, help="Defines which tab is active")

        add_argument("--area-fill-angle", type=float, default="0", help="Fill area with lines heading this angle")
        add_argument("--area-fill-shift", type=float, default="0", help="Shift the lines by tool d * shift")
        add_argument("--area-fill-method", default="zig-zag", help="Filling method either zig-zag or spiral")

        add_argument("--area-find-artefacts-diameter", type=float, default="1", help="Artefacts seeking radius")
        add_argument("--area-find-artefacts-action", default="mark with an arrow", help="Artefacts action type")

        add_argument("--auto_select_paths", type=inkex.Boolean, default=True, help="Select all paths if nothing is selected.")

        add_argument("--loft-distances", default="10", help="Distances between paths.")
        add_argument("--loft-direction", default="crosswise", help="Direction of loft's interpolation.")
        add_argument("--loft-interpolation-degree", type=float, default="2", help="Which interpolation use to loft the paths smooth interpolation or staright.")

        add_argument("--min-arc-radius", type=float, default=".1", help="All arc having radius less than minimum will be considered as straight line")

        add_argument("--engraving-sharp-angle-tollerance", type=float, default="150", help="All angles thar are less than engraving-sharp-angle-tollerance will be thought sharp")
        add_argument("--engraving-max-dist", type=float, default="10", help="Distance from original path where engraving is not needed (usually it's cutting tool diameter)")
        add_argument("--engraving-newton-iterations", type=int, default="4", help="Number of sample points used to calculate distance")
        add_argument("--engraving-draw-calculation-paths", type=inkex.Boolean, default=False, help="Draw additional graphics to debug engraving path")
        add_argument("--engraving-cutter-shape-function", default="w", help="Cutter shape function z(w). Ex. cone: w. ")

        add_argument("--lathe-width", type=float, default=10., help="Lathe width")
        add_argument("--lathe-fine-cut-width", type=float, default=1., help="Fine cut width")
        add_argument("--lathe-fine-cut-count", type=int, default=1., help="Fine cut count")
        add_argument("--lathe-create-fine-cut-using", default="Move path", help="Create fine cut using")
        add_argument("--lathe-x-axis-remap", default="X", help="Lathe X axis remap")
        add_argument("--lathe-z-axis-remap", default="Z", help="Lathe Z axis remap")

        add_argument("--lathe-rectangular-cutter-width", type=float, default="4", help="Rectangular cutter width")

        add_argument("--create-log", type=inkex.Boolean, dest="log_create_log", default=False, help="Create log files")
        add_argument("--log-filename", default='', help="Create log files")

        add_argument("--orientation-points-count", default="2", help="Orientation points count")
        add_argument("--tools-library-type", default='cylinder cutter', help="Create tools definition")

        add_argument("--dxfpoints-action", default='replace', help="dxfpoint sign toggle")

        add_argument("--help-language", default='http://www.cnc-club.ru/forum/viewtopic.php?f=33&t=35', help="Open help page in webbrowser.")

        add_argument("--offset-radius", type=float, default=10., help="Offset radius")
        add_argument("--offset-step", type=float, default=10., help="Offset step")
        add_argument("--offset-draw-clippend-path", type=inkex.Boolean, default=False, help="Draw clipped path")
        add_argument("--offset-just-get-distance", type=inkex.Boolean, default=False, help="Don't do offset just get distance")

        add_argument("--postprocessor", default='', help="Postprocessor command.")
        add_argument("--postprocessor-custom", default='', help="Postprocessor custom command.")

        add_argument("--graffiti-max-seg-length", type=float, default=1., help="Graffiti maximum segment length.")
        add_argument("--graffiti-min-radius", type=float, default=10., help="Graffiti minimal connector's radius.")
        add_argument("--graffiti-start-pos", default="(0;0)", help="Graffiti Start position (x;y).")
        add_argument("--graffiti-create-linearization-preview", type=inkex.Boolean, default=True, help="Graffiti create linearization preview.")
        add_argument("--graffiti-create-preview", type=inkex.Boolean, default=True, help="Graffiti create preview.")
        add_argument("--graffiti-preview-size", type=int, default=800, help="Graffiti preview's size.")
        add_argument("--graffiti-preview-emmit", type=int, default=800, help="Preview's paint emmit (pts/s).")

        add_argument("--in-out-path", type=inkex.Boolean, default=True, help="Create in-out paths")
        add_argument("--in-out-path-do-not-add-reference-point", type=inkex.Boolean, default=False, help="Just add reference in-out point")
        add_argument("--in-out-path-point-max-dist", type=float, default=10., help="In-out path max distance to reference point")
        add_argument("--in-out-path-type", default="Round", help="In-out path type")
        add_argument("--in-out-path-len", type=float, default=10., help="In-out path length")
        add_argument("--in-out-path-replace-original-path", type=inkex.Boolean, default=False, help="Replace original path")
        add_argument("--in-out-path-radius", type=float, default=10., help="In-out path radius for round path")

        add_argument("--plasma-prepare-corners", type=inkex.Boolean, default=True, help="Prepare corners")
        add_argument("--plasma-prepare-corners-distance", type=float, default=10., help="Stepout distance for corners")
        add_argument("--plasma-prepare-corners-tolerance", type=float, default=10., help="Maximum angle for corner (0-180 deg)")

    def __init__(self):
        super(Gcodetools, self).__init__()
        self.default_tool = {
            "name": "Default tool",
            "id": "default tool",
            "diameter": 10.,
            "shape": "10",
            "penetration angle": 90.,
            "penetration feed": 100.,
            "depth step": 1.,
            "feed": 400.,
            "in trajectotry": "",
            "out trajectotry": "",
            "gcode before path": "",
            "gcode after path": "",
            "sog": "",
            "spinlde rpm": "",
            "CW or CCW": "",
            "tool change gcode": " ",
            "4th axis meaning": " ",
            "4th axis scale": 1.,
            "4th axis offset": 0.,
            "passing feed": "800",
            "fine feed": "800",
        }
        self.tools_field_order = [
            'name',
            'id',
            'diameter',
            'feed',
            'shape',
            'penetration angle',
            'penetration feed',
            "passing feed",
            'depth step',
            "in trajectotry",
            "out trajectotry",
            "gcode before path",
            "gcode after path",
            "sog",
            "spinlde rpm",
            "CW or CCW",
            "tool change gcode",
        ]

    def parse_curve(self, p, layer, w=None, f=None):
        c = []
        if len(p) == 0:
            return []
        p = self.transform_csp(p, layer)

        # Sort to reduce Rapid distance
        k = list(range(1, len(p)))
        keys = [0]
        while len(k) > 0:
            end = p[keys[-1]][-1][1]
            dist = None
            for i in range(len(k)):
                start = p[k[i]][0][1]
                dist = max((-((end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2), i), dist)
            keys += [k[dist[1]]]
            del k[dist[1]]
        for k in keys:
            subpath = p[k]
            c += [[[subpath[0][1][0], subpath[0][1][1]], 'move', 0, 0]]
            for i in range(1, len(subpath)):
                sp1 = [[subpath[i - 1][j][0], subpath[i - 1][j][1]] for j in range(3)]
                sp2 = [[subpath[i][j][0], subpath[i][j][1]] for j in range(3)]
                c += biarc(sp1, sp2, 0, 0) if w is None else biarc(sp1, sp2, -f(w[k][i - 1]), -f(w[k][i]))
            c += [[[subpath[-1][1][0], subpath[-1][1][1]], 'end', 0, 0]]
        return c

    ################################################################################
    # Draw csp
    ################################################################################

    def draw_csp(self, csp, layer=None, group=None, fill='none', stroke='#178ade', width=0.354, style=None):
        if layer is not None:
            csp = self.transform_csp(csp, layer, reverse=True)
        if group is None and layer is None:
            group = self.document.getroot()
        elif group is None and layer is not None:
            group = layer
        csp = self.apply_transforms(group, csp, reverse=True)
        if style is not None:
            return draw_csp(csp, group=group, style=style)
        else:
            return draw_csp(csp, group=group, fill=fill, stroke=stroke, width=width)

    def draw_curve(self, curve, layer, group=None, style=MARKER_STYLE["biarc_style"]):
        self.set_markers()

        for i in [0, 1]:
            sid = 'biarc{}_r'.format(i)
            style[sid] = style['biarc{}'.format(i)].copy()
            style[sid]["marker-start"] = "url(#DrawCurveMarker_r)"
            del style[sid]["marker-end"]

        if group is None:
            group = self.layers[min(1, len(self.layers) - 1)].add(Group(gcodetools="Preview group"))
            if not hasattr(self, "preview_groups"):
                self.preview_groups = {layer: group}
            elif layer not in self.preview_groups:
                self.preview_groups[layer] = group
            group = self.preview_groups[layer]

        s = ''
        arcn = 0

        transform = self.get_transforms(group)
        if transform:
            transform = self.reverse_transform(transform)
            transform = str(Transform(transform))

        a = [0., 0.]
        b = [1., 0.]
        c = [0., 1.]
        k = (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])
        a = self.transform(a, layer, True)
        b = self.transform(b, layer, True)
        c = self.transform(c, layer, True)
        if ((b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])) * k > 0:
            reverse_angle = 1
        else:
            reverse_angle = -1
        for sk in curve:
            si = sk[:]
            si[0] = self.transform(si[0], layer, True)
            si[2] = self.transform(si[2], layer, True) if type(si[2]) == type([]) and len(si[2]) == 2 else si[2]

            if s != '':
                if s[1] == 'line':
                    elem = group.add(PathElement(gcodetools="Preview"))
                    elem.transform = transform
                    elem.style = style['line']
                    elem.path = 'M {},{} L {},{}'.format(s[0][0], s[0][1], si[0][0], si[0][1])
                elif s[1] == 'arc':
                    arcn += 1
                    sp = s[0]
                    c = s[2]
                    s[3] = s[3] * reverse_angle

                    a = ((P(si[0]) - P(c)).angle() - (P(s[0]) - P(c)).angle()) % TAU  # s[3]
                    if s[3] * a < 0:
                        if a > 0:
                            a = a - TAU
                        else:
                            a = TAU + a
                    r = math.sqrt((sp[0] - c[0]) ** 2 + (sp[1] - c[1]) ** 2)
                    a_st = (math.atan2(sp[0] - c[0], - (sp[1] - c[1])) - math.pi / 2) % (math.pi * 2)
                    if a > 0:
                        a_end = a_st + a
                        st = style['biarc{}'.format(arcn % 2)]
                    else:
                        a_end = a_st * 1
                        a_st = a_st + a
                        st = style['biarc{}_r'.format(arcn % 2)]

                    elem = group.add(PathElement.arc(c, r, start=a_st, end=a_end,
                                                     open=True, gcodetools="Preview"))
                    elem.transform = transform
                    elem.style = st

            s = si

    def check_dir(self):
        print_("Checking directory: '{}'".format(self.options.directory))
        if os.path.isdir(self.options.directory):
            if os.path.isfile(os.path.join(self.options.directory, 'header')):
                with open(os.path.join(self.options.directory, 'header')) as f:
                    self.header = f.read()
            else:
                self.header = defaults['header']
            if os.path.isfile(os.path.join(self.options.directory, 'footer')):
                with open(os.path.join(self.options.directory, 'footer')) as f:
                    self.footer = f.read()
            else:
                self.footer = defaults['footer']
            self.header += self.options.unit + "\n"
        else:
            self.error("Directory does not exist! Please specify existing directory at Preferences tab!", "error")
            return False

        if self.options.add_numeric_suffix_to_filename:
            dir_list = os.listdir(self.options.directory)
            if "." in self.options.file:
                r = re.match(r"^(.*)(\..*)$", self.options.file)
                ext = r.group(2)
                name = r.group(1)
            else:
                ext = ""
                name = self.options.file
            max_n = 0
            for s in dir_list:
                r = re.match(r"^{}_0*(\d+){}$".format(re.escape(name), re.escape(ext)), s)
                if r:
                    max_n = max(max_n, int(r.group(1)))
            filename = name + "_" + ("0" * (4 - len(str(max_n + 1))) + str(max_n + 1)) + ext
            self.options.file = filename

        try:
            with open(os.path.join(self.options.directory, self.options.file), "w") as f:
                pass
        except:
            self.error("Can not write to specified file!\n{}".format(os.path.join(self.options.directory, self.options.file)), "error")
            return False
        return True

    ################################################################################
    #
    # Generate Gcode
    # Generates Gcode on given curve.
    #
    # Curve definition [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]]
    #
    ################################################################################
    def generate_gcode(self, curve, layer, depth):
        Zauto_scale = self.Zauto_scale[layer]
        tool = self.tools[layer][0]
        g = ""

        def c(c):
            c = [c[i] if i < len(c) else None for i in range(6)]
            if c[5] == 0:
                c[5] = None
            s = [" X", " Y", " Z", " I", " J", " K"]
            s1 = ["", "", "", "", "", ""]
            m = [1, 1, self.options.Zscale * Zauto_scale, 1, 1, self.options.Zscale * Zauto_scale]
            a = [0, 0, self.options.Zoffset, 0, 0, 0]
            r = ''
            for i in range(6):
                if c[i] is not None:
                    r += s[i] + ("{:f}".format(c[i] * m[i] + a[i])) + s1[i]
            return r

        def calculate_angle(a, current_a):
            return min(
                    [abs(a - current_a % TAU + TAU), a + current_a - current_a % TAU + TAU],
                    [abs(a - current_a % TAU - TAU), a + current_a - current_a % TAU - TAU],
                    [abs(a - current_a % TAU), a + current_a - current_a % TAU])[1]

        if len(curve) == 0:
            return ""

        try:
            self.last_used_tool is None
        except:
            self.last_used_tool = None
        print_("working on curve")
        print_(curve)

        if tool != self.last_used_tool:
            g += ("(Change tool to {})\n".format(re.sub("\"'\\(\\)\\\\", " ", tool["name"]))) + tool["tool change gcode"] + "\n"

        lg = 'G00'
        zs = self.options.Zsafe
        f = " F{:f}".format(tool['feed'])
        current_a = 0
        go_to_safe_distance = "G00" + c([None, None, zs]) + "\n"
        penetration_feed = " F{}".format(tool['penetration feed'])
        for i in range(1, len(curve)):
            #    Creating Gcode for curve between s=curve[i-1] and si=curve[i] start at s[0] end at s[4]=si[0]
            s = curve[i - 1]
            si = curve[i]
            feed = f if lg not in ['G01', 'G02', 'G03'] else ''
            if s[1] == 'move':
                g += go_to_safe_distance + "G00" + c(si[0]) + "\n" + tool['gcode before path'] + "\n"
                lg = 'G00'
            elif s[1] == 'end':
                g += go_to_safe_distance + tool['gcode after path'] + "\n"
                lg = 'G00'
            elif s[1] == 'line':
                if tool['4th axis meaning'] == "tangent knife":
                    a = atan2(si[0][0] - s[0][0], si[0][1] - s[0][1])
                    a = calculate_angle(a, current_a)
                    g += "G01 A{}\n".format(a * tool['4th axis scale'] + tool['4th axis offset'])
                    current_a = a
                if lg == "G00":
                    g += "G01" + c([None, None, s[5][0] + depth]) + penetration_feed + "(Penetrate)\n"
                g += "G01" + c(si[0] + [s[5][1] + depth]) + feed + "\n"
                lg = 'G01'
            elif s[1] == 'arc':
                r = [(s[2][0] - s[0][0]), (s[2][1] - s[0][1])]
                if tool['4th axis meaning'] == "tangent knife":
                    if s[3] < 0:  # CW
                        a1 = atan2(s[2][1] - s[0][1], -s[2][0] + s[0][0]) + math.pi
                    else:  # CCW
                        a1 = atan2(-s[2][1] + s[0][1], s[2][0] - s[0][0]) + math.pi
                    a = calculate_angle(a1, current_a)
                    g += "G01 A{}\n".format(a * tool['4th axis scale'] + tool['4th axis offset'])
                    current_a = a
                    axis4 = " A{}".format((current_a + s[3]) * tool['4th axis scale'] + tool['4th axis offset'])
                    current_a = current_a + s[3]
                else:
                    axis4 = ""
                if lg == "G00":
                    g += "G01" + c([None, None, s[5][0] + depth]) + penetration_feed + "(Penetrate)\n"
                if (r[0] ** 2 + r[1] ** 2) > self.options.min_arc_radius ** 2:
                    r1 = (P(s[0]) - P(s[2]))
                    r2 = (P(si[0]) - P(s[2]))
                    if abs(r1.mag() - r2.mag()) < 0.001:
                        g += ("G02" if s[3] < 0 else "G03") + c(si[0] + [s[5][1] + depth, (s[2][0] - s[0][0]), (s[2][1] - s[0][1])]) + feed + axis4 + "\n"
                    else:
                        r = (r1.mag() + r2.mag()) / 2
                        g += ("G02" if s[3] < 0 else "G03") + c(si[0] + [s[5][1] + depth]) + " R{:f}".format(r) + feed + axis4 + "\n"
                    lg = 'G02'
                else:
                    if tool['4th axis meaning'] == "tangent knife":
                        a = atan2(si[0][0] - s[0][0], si[0][1] - s[0][1]) + math.pi
                        a = calculate_angle(a, current_a)
                        g += "G01 A{}\n".format(a * tool['4th axis scale'] + tool['4th axis offset'])
                        current_a = a
                    g += "G01" + c(si[0] + [s[5][1] + depth]) + feed + "\n"
                    lg = 'G01'
        if si[1] == 'end':
            g += go_to_safe_distance + tool['gcode after path'] + "\n"
        return g

    def get_transforms(self, g):
        root = self.document.getroot()
        trans = []
        while g != root:
            if 'transform' in g.keys():
                t = g.get('transform')
                t = Transform(t).matrix
                trans = (Transform(t) * Transform(trans)).matrix if trans != [] else t

                print_(trans)
            g = g.getparent()
        return trans

    def reverse_transform(self, transform):
        trans = numpy.array(transform + ([0, 0, 1],))
        if numpy.linalg.det(trans) != 0:
            trans = numpy.linalg.inv(trans).tolist()[:2]
            return trans
        else:
            return transform

    def apply_transforms(self, g, csp, reverse=False):
        trans = self.get_transforms(g)
        if trans:
            if not reverse:
                # TODO: This was applyTransformToPath but was deprecated.   Candidate for refactoring.
                for comp in csp:
                    for ctl in comp:
                        for pt in ctl:
                            pt[0], pt[1] = Transform(trans).apply_to_point(pt)

            else:
                # TODO: This was applyTransformToPath but was deprecated.   Candidate for refactoring.
                for comp in csp:
                    for ctl in comp:
                        for pt in ctl:
                            pt[0], pt[1] = Transform(self.reverse_transform(trans)).apply_to_point(pt)
        return csp

    def transform_scalar(self, x, layer, reverse=False):
        return self.transform([x, 0], layer, reverse)[0] - self.transform([0, 0], layer, reverse)[0]

    def transform(self, source_point, layer, reverse=False):
        if layer not in self.transform_matrix:
            for i in range(self.layers.index(layer), -1, -1):
                if self.layers[i] in self.orientation_points:
                    break
            if self.layers[i] not in self.orientation_points:
                self.error(f"Orientation points for '{layer.label}' layer have not been found! Please add orientation points using Orientation tab!", "error")
            elif self.layers[i] in self.transform_matrix:
                self.transform_matrix[layer] = self.transform_matrix[self.layers[i]]
                self.Zcoordinates[layer] = self.Zcoordinates[self.layers[i]]
            else:
                orientation_layer = self.layers[i]
                if len(self.orientation_points[orientation_layer]) > 1:
                    self.error(f"There are more than one orientation point groups in '{orientation_layer.label}' layer")
                points = self.orientation_points[orientation_layer][0]
                if len(points) == 2:
                    points += [[[(points[1][0][1] - points[0][0][1]) + points[0][0][0], -(points[1][0][0] - points[0][0][0]) + points[0][0][1]], [-(points[1][1][1] - points[0][1][1]) + points[0][1][0], points[1][1][0] - points[0][1][0] + points[0][1][1]]]]
                if len(points) == 3:
                    print_("Layer '{orientation_layer.label}' Orientation points: ")
                    for point in points:
                        print_(point)
                    #    Zcoordinates definition taken from Orientatnion point 1 and 2
                    self.Zcoordinates[layer] = [max(points[0][1][2], points[1][1][2]), min(points[0][1][2], points[1][1][2])]
                    matrix = numpy.array([
                        [points[0][0][0], points[0][0][1], 1, 0, 0, 0, 0, 0, 0],
                        [0, 0, 0, points[0][0][0], points[0][0][1], 1, 0, 0, 0],
                        [0, 0, 0, 0, 0, 0, points[0][0][0], points[0][0][1], 1],
                        [points[1][0][0], points[1][0][1], 1, 0, 0, 0, 0, 0, 0],
                        [0, 0, 0, points[1][0][0], points[1][0][1], 1, 0, 0, 0],
                        [0, 0, 0, 0, 0, 0, points[1][0][0], points[1][0][1], 1],
                        [points[2][0][0], points[2][0][1], 1, 0, 0, 0, 0, 0, 0],
                        [0, 0, 0, points[2][0][0], points[2][0][1], 1, 0, 0, 0],
                        [0, 0, 0, 0, 0, 0, points[2][0][0], points[2][0][1], 1]
                    ])

                    if numpy.linalg.det(matrix) != 0:
                        m = numpy.linalg.solve(matrix,
                                               numpy.array(
                                                       [[points[0][1][0]], [points[0][1][1]], [1], [points[1][1][0]], [points[1][1][1]], [1], [points[2][1][0]], [points[2][1][1]], [1]]
                                               )
                                               ).tolist()
                        self.transform_matrix[layer] = [[m[j * 3 + i][0] for i in range(3)] for j in range(3)]

                    else:
                        self.error("Orientation points are wrong! (if there are two orientation points they should not be the same. If there are three orientation points they should not be in a straight line.)", "error")
                else:
                    self.error("Orientation points are wrong! (if there are two orientation points they should not be the same. If there are three orientation points they should not be in a straight line.)", "error")

            self.transform_matrix_reverse[layer] = numpy.linalg.inv(self.transform_matrix[layer]).tolist()
            print_(f"\n Layer '{layer.label}' transformation matrixes:")
            print_(self.transform_matrix)
            print_(self.transform_matrix_reverse)

            # Zautoscale is obsolete
            self.Zauto_scale[layer] = 1
            print_("Z automatic scale = {} (computed according orientation points)".format(self.Zauto_scale[layer]))

        x = source_point[0]
        y = source_point[1]
        if not reverse:
            t = self.transform_matrix[layer]
        else:
            t = self.transform_matrix_reverse[layer]
        return [t[0][0] * x + t[0][1] * y + t[0][2], t[1][0] * x + t[1][1] * y + t[1][2]]

    def transform_csp(self, csp_, layer, reverse=False):
        csp = [[[csp_[i][j][0][:], csp_[i][j][1][:], csp_[i][j][2][:]] for j in range(len(csp_[i]))] for i in range(len(csp_))]
        for i in xrange(len(csp)):
            for j in xrange(len(csp[i])):
                for k in xrange(len(csp[i][j])):
                    csp[i][j][k] = self.transform(csp[i][j][k], layer, reverse)
        return csp

    def error(self, s, msg_type="warning"):
        """
        Errors handling function
        warnings are printed into log file and warning message is displayed but
        extension continues working,
        errors causes log and execution is halted
        """
        if msg_type == "warning":
            print_(s)
            inkex.errormsg(s + "\n")

        elif msg_type == "error":
            print_(s)
            raise inkex.AbortExtension(s)

        else:
            print_("Unknown message type: {}".format(msg_type))
            print_(s)
            raise inkex.AbortExtension(s)

    ################################################################################
    # Set markers
    ################################################################################
    def set_markers(self):
        """Make sure all markers are available"""
        def ensure_marker(elem_id, x=-4, polA='', polB='-', fill='#000044'):
            if self.svg.getElementById(elem_id) is None:
                marker = self.svg.defs.add(Marker(
                    id=elem_id, orient="auto", refX=str(x), refY="-1.687441",
                    style="overflow:visible"))
                path = marker.add(PathElement(
                    d="m {0}4.588864,-1.687441 0.0,0.0 L {0}9.177728,0.0 "\
                      "c {1}0.73311,-0.996261 {1}0.728882,-2.359329 0.0,-3.374882"\
                      .format(polA, polB)))
                path.style = "fill:{};fill-rule:evenodd;stroke:none;".format(fill)

        ensure_marker("CheckToolsAndOPMarker")
        ensure_marker("DrawCurveMarker")
        ensure_marker("DrawCurveMarker_r", x=4, polA='-', polB='')
        ensure_marker("InOutPathMarker", fill='#0072a7')

    def get_info(self):
        """Get Gcodetools info from the svg"""
        self.selected_paths = {}
        self.paths = {}
        self.tools = {}
        self.orientation_points = {}
        self.graffiti_reference_points = {}
        self.layers = [self.document.getroot()]
        self.Zcoordinates = {}
        self.transform_matrix = {}
        self.transform_matrix_reverse = {}
        self.Zauto_scale = {}
        self.in_out_reference_points = []
        self.my3Dlayer = None

        def recursive_search(g, layer, selected=False):
            items = g.getchildren()
            items.reverse()
            for i in items:
                if selected:
                    self.svg.selected[i.get("id")] = i
                if isinstance(i, Layer):
                    if i.label == '3D':
                        self.my3Dlayer = i
                    else:
                        self.layers += [i]
                        recursive_search(i, i)

                elif i.get('gcodetools') == "Gcodetools orientation group":
                    points = self.get_orientation_points(i)
                    if points is not None:
                        self.orientation_points[layer] = self.orientation_points[layer] + [points[:]] if layer in self.orientation_points else [points[:]]
                        print_(f"Found orientation points in '{layer.label}' layer: {points}")
                    else:
                        self.error(f"Warning! Found bad orientation points in '{layer.label}' layer. Resulting Gcode could be corrupt!")

                # Need to recognise old files ver 1.6.04 and earlier
                elif i.get("gcodetools") == "Gcodetools tool definition" or i.get("gcodetools") == "Gcodetools tool definition":
                    tool = self.get_tool(i)
                    self.tools[layer] = self.tools[layer] + [tool.copy()] if layer in self.tools else [tool.copy()]
                    print_(f"Found tool in '{layer.label}' layer: {tool}")

                elif i.get("gcodetools") == "Gcodetools graffiti reference point":
                    point = self.get_graffiti_reference_points(i)
                    if point:
                        self.graffiti_reference_points[layer] = self.graffiti_reference_points[layer] + [point[:]] if layer in self.graffiti_reference_points else [point]
                    else:
                        self.error(f"Warning! Found bad graffiti reference point in '{layer.label}' layer. Resulting Gcode could be corrupt!")

                elif isinstance(i, inkex.PathElement):
                    if "gcodetools" not in i.keys():
                        self.paths[layer] = self.paths[layer] + [i] if layer in self.paths else [i]
                        if i.get("id") in self.svg.selected.ids:
                            self.selected_paths[layer] = self.selected_paths[layer] + [i] if layer in self.selected_paths else [i]

                elif i.get("gcodetools") == "In-out reference point group":
                    items_ = i.getchildren()
                    items_.reverse()
                    for j in items_:
                        if j.get("gcodetools") == "In-out reference point":
                            self.in_out_reference_points.append(self.apply_transforms(j, j.path.to_superpath())[0][0][1])

                elif isinstance(i, inkex.Group):
                    recursive_search(i, layer, (i.get("id") in self.svg.selected))

                elif i.get("id") in self.svg.selected:
                    # xgettext:no-pango-format
                    self.error("This extension works with Paths and Dynamic Offsets and groups of them only! "
                               "All other objects will be ignored!\n"
                               "Solution 1: press Path->Object to path or Shift+Ctrl+C.\n"
                               "Solution 2: Path->Dynamic offset or Ctrl+J.\n"
                               "Solution 3: export all contours to PostScript level 2 (File->Save As->.ps) and File->Import this file.")

        recursive_search(self.document.getroot(), self.document.getroot())

        if len(self.layers) == 1:
            self.error("Document has no layers! Add at least one layer using layers panel (Ctrl+Shift+L)", "error")
        root = self.document.getroot()

        if root in self.selected_paths or root in self.paths:
            self.error("Warning! There are some paths in the root of the document, but not in any layer! Using bottom-most layer for them.")

        if root in self.selected_paths:
            if self.layers[-1] in self.selected_paths:
                self.selected_paths[self.layers[-1]] += self.selected_paths[root][:]
            else:
                self.selected_paths[self.layers[-1]] = self.selected_paths[root][:]
            del self.selected_paths[root]

        if root in self.paths:
            if self.layers[-1] in self.paths:
                self.paths[self.layers[-1]] += self.paths[root][:]
            else:
                self.paths[self.layers[-1]] = self.paths[root][:]
            del self.paths[root]

    def get_orientation_points(self, g):
        items = g.getchildren()
        items.reverse()
        p2 = []
        p3 = []
        p = None
        for i in items:
            if isinstance(i, inkex.Group):
                if i.get("gcodetools") == "Gcodetools orientation point (2 points)":
                    p2 += [i]
                if i.get("gcodetools") == "Gcodetools orientation point (3 points)":
                    p3 += [i]
        if len(p2) == 2:
            p = p2
        elif len(p3) == 3:
            p = p3
        if p is None:
            return None
        points = []
        for i in p:
            point = [[], []]
            for node in i:
                if node.get('gcodetools') == "Gcodetools orientation point arrow":
                    csp = node.path.transform(node.composed_transform()).to_superpath()
                    point[0] = csp[0][0][1]
                if node.get('gcodetools') == "Gcodetools orientation point text":
                    inkex.errormsg(node.get_text())
                    r = re.match(r'(?i)\s*\(\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*\)\s*', node.get_text())
                    point[1] = [float(r.group(1)), float(r.group(2)), float(r.group(3))]
            if point[0] != [] and point[1] != []:
                points += [point]
        if len(points) == len(p2) == 2 or len(points) == len(p3) == 3:
            return points
        else:
            return None

    def get_graffiti_reference_points(self, g):
        point = [[], '']
        for node in g:
            if node.get('gcodetools') == "Gcodetools graffiti reference point arrow":
                point[0] = self.apply_transforms(node, node.path.to_superpath())[0][0][1]
            if node.get('gcodetools') == "Gcodetools graffiti reference point text":
                point[1] = node.get_text()
        if point[0] != [] and point[1] != '':
            return point
        else:
            return []

    def get_tool(self, g):
        tool = self.default_tool.copy()
        tool["self_group"] = g
        for i in g:
            # Get parameters
            if i.get("gcodetools") == "Gcodetools tool background":
                tool["style"] = dict(i.style)
            elif i.get("gcodetools") == "Gcodetools tool parameter":
                key = None
                value = None
                for j in i:
                    # need to recognise old tools from ver 1.6.04
                    if j.get("gcodetools") == "Gcodetools tool definition field name" or j.get("gcodetools") == "Gcodetools tool defention field name":
                        key = j.get_text()
                    if j.get("gcodetools") == "Gcodetools tool definition field value" or j.get("gcodetools") == "Gcodetools tool defention field value":
                        value = j.get_text()
                        if value == "(None)":
                            value = ""
                if value is None or key is None:
                    continue
                if key in self.default_tool.keys():
                    try:
                        tool[key] = type(self.default_tool[key])(value)
                    except:
                        tool[key] = self.default_tool[key]
                        self.error("Warning! Tool's and default tool's parameter's ({}) types are not the same ( type('{}') != type('{}') ).".format(key, value, self.default_tool[key]))
                else:
                    tool[key] = value
                    self.error("Warning! Tool has parameter that default tool has not ( '{}': '{}' ).".format(key, value))
        return tool

    def set_tool(self, layer):
        for i in range(self.layers.index(layer), -1, -1):
            if self.layers[i] in self.tools:
                break
        if self.layers[i] in self.tools:
            if self.layers[i] != layer:
                self.tools[layer] = self.tools[self.layers[i]]
            if len(self.tools[layer]) > 1:
                label = self.layers[i].label
                self.error(f"Layer '{label}' contains more than one tool!")
            return self.tools[layer]
        else:
            self.error(f"Can not find tool for '{layer.label}' layer! Please add one with Tools library tab!", "error")

    ################################################################################
    #
    # Path to Gcode
    #
    ################################################################################
    def tab_path_to_gcode(self):
        self.get_info_plus()
        def get_boundaries(points):
            minx = None
            miny = None
            maxx = None
            maxy = None
            out = [[], [], [], []]
            for p in points:
                if minx == p[0]:
                    out[0] += [p]
                if minx is None or p[0] < minx:
                    minx = p[0]
                    out[0] = [p]

                if miny == p[1]:
                    out[1] += [p]
                if miny is None or p[1] < miny:
                    miny = p[1]
                    out[1] = [p]

                if maxx == p[0]:
                    out[2] += [p]
                if maxx is None or p[0] > maxx:
                    maxx = p[0]
                    out[2] = [p]

                if maxy == p[1]:
                    out[3] += [p]
                if maxy is None or p[1] > maxy:
                    maxy = p[1]
                    out[3] = [p]
            return out

        def remove_duplicates(points):
            i = 0
            out = []
            for p in points:
                for j in xrange(i, len(points)):
                    if p == points[j]:
                        points[j] = [None, None]
                if p != [None, None]:
                    out += [p]
            i += 1
            return out

        def get_way_len(points):
            l = 0
            for i in xrange(1, len(points)):
                l += math.sqrt((points[i][0] - points[i - 1][0]) ** 2 + (points[i][1] - points[i - 1][1]) ** 2)
            return l

        def sort_dxfpoints(points):
            points = remove_duplicates(points)
            ways = [
                # l=0, d=1, r=2, u=3
                [3, 0],  # ul
                [3, 2],  # ur
                [1, 0],  # dl
                [1, 2],  # dr
                [0, 3],  # lu
                [0, 1],  # ld
                [2, 3],  # ru
                [2, 1],  # rd
            ]
            minimal_way = []
            minimal_len = None
            for w in ways:
                tpoints = points[:]
                cw = []
                for j in xrange(0, len(points)):
                    p = get_boundaries(get_boundaries(tpoints)[w[0]])[w[1]]
                    tpoints.remove(p[0])
                    cw += p
                curlen = get_way_len(cw)
                if minimal_len is None or curlen < minimal_len:
                    minimal_len = curlen
                    minimal_way = cw

            return minimal_way

        def sort_lines(lines):
            if len(lines) == 0:
                return []
            lines = [[key] + lines[key] for key in range(len(lines))]
            keys = [0]
            end_point = lines[0][3:]
            print_("!!!", lines, "\n", end_point)
            del lines[0]
            while len(lines) > 0:
                dist = [[point_to_point_d2(end_point, lines[i][1:3]), i] for i in range(len(lines))]
                i = min(dist)[1]
                keys.append(lines[i][0])
                end_point = lines[i][3:]
                del lines[i]
            return keys

        def sort_curves(curves):
            lines = []
            for curve in curves:
                lines += [curve[0][0][0] + curve[-1][-1][0]]
            return sort_lines(lines)

        def print_dxfpoints(points):
            gcode = ""
            for point in points:
                gcode += "(drilling dxfpoint)\nG00 Z{:f}\nG00 X{:f} Y{:f}\nG01 Z{:f} F{:f}\nG04 P{:f}\nG00 Z{:f}\n".format(self.options.Zsafe, point[0], point[1], self.Zcoordinates[layer][1], self.tools[layer][0]["penetration feed"], 0.2, self.options.Zsafe)
            return gcode

        def get_path_properties(node):
            res = {}
            done = False
            while not done and node != self.svg:
                for i in node.getchildren():
                    if isinstance(i, inkex.Desc):
                        res["Description"] = i.text
                    elif isinstance(i, inkex.Title):
                        res["Title"] = i.text
                    done = True
                node = node.getparent()
            return res

        if self.selected_paths == {} and self.options.auto_select_paths:
            paths = self.paths
            self.error("No paths are selected! Trying to work on all available paths.")
        else:
            paths = self.selected_paths
        self.check_dir()
        gcode = ""

        parent = list(self.selected_paths)[0] if self.selected_paths else self.layers[0]
        biarc_group = parent.add(Group())
        print_(("self.layers=", self.layers))
        print_(("paths=", paths))
        colors = {}
        for layer in self.layers:
            if layer in paths:
                print_(("layer", layer))
                # transform simple path to get all var about orientation
                self.transform_csp([[[[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]]]], layer)

                self.set_tool(layer)
                curves = []
                dxfpoints = []

                try:
                    depth_func = eval('lambda c,d,s: ' + self.options.path_to_gcode_depth_function.strip('"'))
                except:
                    self.error("Bad depth function! Enter correct function at Path to Gcode tab!")

                for path in paths[layer]:
                    if "d" not in path.keys():
                        self.error("Warning: One or more paths do not have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!")
                        continue
                    csp = path.path.to_superpath()
                    csp = self.apply_transforms(path, csp)
                    id_ = path.get("id")

                    def set_comment(match, path):
                        if match.group(1) in path.keys():
                            return path.get(match.group(1))
                        else:
                            return "None"

                    if self.options.comment_gcode != "":
                        comment = re.sub("\\[([A-Za-z_\\-\\:]+)\\]", partial(set_comment, path=path), self.options.comment_gcode)
                        comment = comment.replace(":newline:", "\n")
                        comment = gcode_comment_str(comment)
                    else:
                        comment = ""
                    if self.options.comment_gcode_from_properties:
                        tags = get_path_properties(path)
                        for tag in tags:
                            comment += gcode_comment_str("{}: {}".format(tag, tags[tag]))

                    stroke = path.style('stroke')
                    colors[id_] = inkex.Color(stroke if stroke != None else "#000").to_rgb()
                    if path.get("dxfpoint") == "1":
                        tmp_curve = self.transform_csp(csp, layer)
                        x = tmp_curve[0][0][0][0]
                        y = tmp_curve[0][0][0][1]
                        print_("got dxfpoint (scaled) at ({:f},{:f})".format(x, y))
                        dxfpoints += [[x, y]]
                    else:

                        zd = self.Zcoordinates[layer][1]
                        zs = self.Zcoordinates[layer][0]
                        c = 1. - float(sum(colors[id_])) / 255 / 3
                        curves += [
                            [
                                [id_, depth_func(c, zd, zs), comment],
                                [self.parse_curve([subpath], layer) for subpath in csp]
                            ]
                        ]
                dxfpoints = sort_dxfpoints(dxfpoints)
                gcode += print_dxfpoints(dxfpoints)

                for curve in curves:
                    for subcurve in curve[1]:
                        self.draw_curve(subcurve, layer)

                if self.options.path_to_gcode_order == 'subpath by subpath':
                    curves_ = []
                    for curve in curves:
                        curves_ += [[curve[0], [subcurve]] for subcurve in curve[1]]
                    curves = curves_

                    self.options.path_to_gcode_order = 'path by path'

                if self.options.path_to_gcode_order == 'path by path':
                    if self.options.path_to_gcode_sort_paths:
                        keys = sort_curves([curve[1] for curve in curves])
                    else:
                        keys = range(len(curves))
                    for key in keys:
                        d = curves[key][0][1]
                        for step in range(0, int(math.ceil(abs((zs - d) / self.tools[layer][0]["depth step"])))):
                            z = max(d, zs - abs(self.tools[layer][0]["depth step"] * (step + 1)))

                            gcode += gcode_comment_str("\nStart cutting path id: {}".format(curves[key][0][0]))
                            if curves[key][0][2] != "()":
                                gcode += curves[key][0][2]  # add comment

                            for curve in curves[key][1]:
                                gcode += self.generate_gcode(curve, layer, z)

                            gcode += gcode_comment_str("End cutting path id: {}\n\n".format(curves[key][0][0]))

                else:  # pass by pass
                    mind = min([curve[0][1] for curve in curves])
                    for step in range(0, 1 + int(math.ceil(abs((zs - mind) / self.tools[layer][0]["depth step"])))):
                        z = zs - abs(self.tools[layer][0]["depth step"] * step)
                        curves_ = []
                        for curve in curves:
                            if curve[0][1] < z:
                                curves_.append(curve)

                        z = zs - abs(self.tools[layer][0]["depth step"] * (step + 1))
                        gcode += "\n(Pass at depth {})\n".format(z)

                        if self.options.path_to_gcode_sort_paths:
                            keys = sort_curves([curve[1] for curve in curves_])
                        else:
                            keys = range(len(curves_))
                        for key in keys:

                            gcode += gcode_comment_str("Start cutting path id: {}".format(curves[key][0][0]))
                            if curves[key][0][2] != "()":
                                gcode += curves[key][0][2]  # add comment

                            for subcurve in curves_[key][1]:
                                gcode += self.generate_gcode(subcurve, layer, max(z, curves_[key][0][1]))

                            gcode += gcode_comment_str("End cutting path id: {}\n\n".format(curves[key][0][0]))

        self.export_gcode(gcode)

    ################################################################################
    #
    # dxfpoints
    #
    ################################################################################
    def tab_dxfpoints(self):
        self.get_info_plus()
        if self.selected_paths == {}:
            self.error("Nothing is selected. Please select something to convert to drill point (dxfpoint) or clear point sign.")
        for layer in self.layers:
            if layer in self.selected_paths:
                for path in self.selected_paths[layer]:
                    if self.options.dxfpoints_action == 'replace':

                        path.set("dxfpoint", "1")
                        r = re.match("^\\s*.\\s*(\\S+)", path.get("d"))
                        if r is not None:
                            print_(("got path=", r.group(1)))
                            path.set("d", "m {} 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z".format(r.group(1)))
                            path.set("style", MARKER_STYLE["dxf_points"])

                    if self.options.dxfpoints_action == 'save':
                        path.set("dxfpoint", "1")

                    if self.options.dxfpoints_action == 'clear' and path.get("dxfpoint") == "1":
                        path.set("dxfpoint", "0")

    ################################################################################
    #
    # Artefacts
    #
    ################################################################################
    def tab_area_artefacts(self):
        self.get_info_plus()
        if self.selected_paths == {} and self.options.auto_select_paths:
            paths = self.paths
            self.error("No paths are selected! Trying to work on all available paths.")
        else:
            paths = self.selected_paths
        for layer in paths:
            for path in paths[layer]:
                parent = path.getparent()
                if "d" not in path.keys():
                    self.error("Warning: One or more paths do not have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!")
                    continue
                csp = path.path.to_superpath()
                remove = []
                for i in range(len(csp)):
                    subpath = [[point[:] for point in points] for points in csp[i]]
                    subpath = self.apply_transforms(path, [subpath])[0]
                    bounds = csp_simple_bound([subpath])
                    if (bounds[2] - bounds[0]) ** 2 + (bounds[3] - bounds[1]) ** 2 < self.options.area_find_artefacts_diameter ** 2:
                        if self.options.area_find_artefacts_action == "mark with an arrow":
                            arrow = Path('m {},{} 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z'.format(subpath[0][1][0], subpath[0][1][1])).to_superpath()
                            arrow = self.apply_transforms(path, arrow, True)
                            node = parent.add(PathElement())
                            node.path = CubicSuperPath(arrow)
                            node.style = MARKER_STYLE["area artefact arrow"]
                            node.set('gcodetools', 'area artefact arrow')
                        elif self.options.area_find_artefacts_action == "mark with style":
                            node = parent.add(PathElement())
                            node.path = CubicSuperPath(csp[i])
                            node.style = MARKER_STYLE["area artefact"]
                            remove.append(i)
                        elif self.options.area_find_artefacts_action == "delete":
                            remove.append(i)
                            print_("Deleted artefact {}".format(subpath))
                remove.reverse()
                for i in remove:
                    del csp[i]
                if len(csp) == 0:
                    parent.remove(path)
                else:
                    path.path = CubicSuperPath(csp)

        return

    def tab_area(self):
        """Calculate area curves"""
        self.get_info_plus()
        if len(self.selected_paths) <= 0:
            self.error("This extension requires at least one selected path.")
            return
        for layer in self.layers:
            if layer in self.selected_paths:
                self.set_tool(layer)
                if self.tools[layer][0]['diameter'] <= 0:
                    self.error(f"Tool diameter must be > 0 but tool's diameter on '{layer.label}' layer is not!", "error")

                for path in self.selected_paths[layer]:
                    print_(("doing path", path.get("style"), path.get("d")))

                    area_group = path.getparent().add(Group())

                    csp = path.path.to_superpath()
                    print_(csp)
                    if not csp:
                        print_("omitting non-path")
                        self.error("Warning: omitting non-path")
                        continue

                    if path.get('sodipodi:type') != "inkscape:offset":
                        print_("Path {} is not an offset. Preparation started.".format(path.get("id")))
                        # Path is not offset. Preparation will be needed.
                        # Finding top most point in path (min y value)

                        min_x, min_y, min_i, min_j, min_t = csp_true_bounds(csp)[1]

                        # Reverse path if needed.
                        if min_y != float("-inf"):
                            # Move outline subpath to the beginning of csp
                            subp = csp[min_i]
                            del csp[min_i]
                            j = min_j
                            # Split by the topmost point and join again
                            if min_t in [0, 1]:
                                if min_t == 0:
                                    j = j - 1
                                subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
                                subp = [[subp[j][1], subp[j][1], subp[j][2]]] + subp[j + 1:] + subp[:j] + [[subp[j][0], subp[j][1], subp[j][1]]]
                            else:
                                sp1, sp2, sp3 = csp_split(subp[j - 1], subp[j], min_t)
                                subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
                                subp = [[sp2[1], sp2[1], sp2[2]]] + [sp3] + subp[j + 1:] + subp[:j - 1] + [sp1] + [[sp2[0], sp2[1], sp2[1]]]
                            csp = [subp] + csp
                            # reverse path if needed
                            if csp_subpath_ccw(csp[0]):
                                for i in range(len(csp)):
                                    n = []
                                    for j in csp[i]:
                                        n = [[j[2][:], j[1][:], j[0][:]]] + n
                                    csp[i] = n[:]

                    # What the absolute fudge is this doing? Closing paths? Ugh.
                    # Not sure but it most be at this level and not in the if statement, or it will not work with dynamic offsets
                    d = str(CubicSuperPath(csp))
                    print_(("original  d=", d))
                    d = re.sub(r'(?i)(m[^mz]+)', r'\1 Z ', d)
                    d = re.sub(r'(?i)\s*z\s*z\s*', r' Z ', d)
                    d = re.sub(r'(?i)\s*([A-Za-z])\s*', r' \1 ', d)
                    print_(("formatted d=", d))
                    p0 = self.transform([0, 0], layer)
                    p1 = self.transform([0, 1], layer)
                    scale = (P(p0) - P(p1)).mag()
                    if scale == 0:
                        scale = 1.
                    else:
                        scale = 1. / scale
                    print_(scale)
                    tool_d = self.tools[layer][0]['diameter'] * scale
                    r = self.options.area_inkscape_radius * scale
                    sign = 1 if r > 0 else -1
                    print_("Tool diameter = {}, r = {}".format(tool_d, r))

                    # avoiding infinite loops
                    if self.options.area_tool_overlap > 0.9:
                        self.options.area_tool_overlap = .9

                    for i in range(self.options.max_area_curves):
                        radius = - tool_d * (i * (1 - self.options.area_tool_overlap) + 0.5) * sign
                        if abs(radius) > abs(r):
                            radius = -r

                        elem = area_group.add(PathElement(style=str(MARKER_STYLE["biarc_style_i"]['area'])))
                        elem.set('sodipodi:type', 'inkscape:offset')
                        elem.set('inkscape:radius', radius)
                        elem.set('inkscape:original', d)
                        print_(("adding curve", area_group, d, str(MARKER_STYLE["biarc_style_i"]['area'])))
                        if radius == -r:
                            break

    def tab_area_fill(self):
        """Fills area with lines"""
        self.get_info_plus()
        # convert degrees into rad
        self.options.area_fill_angle = self.options.area_fill_angle * math.pi / 180
        if len(self.selected_paths) <= 0:
            self.error("This extension requires at least one selected path.")
            return
        for layer in self.layers:
            if layer in self.selected_paths:
                self.set_tool(layer)
                if self.tools[layer][0]['diameter'] <= 0:
                    self.error(f"Tool diameter must be > 0 but tool's diameter on '{layer.label}' layer is not!", "error")
                tool = self.tools[layer][0]
                for path in self.selected_paths[layer]:
                    lines = []
                    print_(("doing path", path.get("style"), path.get("d")))
                    area_group = path.getparent().add(Group())
                    csp = path.path.to_superpath()
                    if not csp:
                        print_("omitting non-path")
                        self.error("Warning: omitting non-path")
                        continue
                    csp = self.apply_transforms(path, csp)
                    csp = csp_close_all_subpaths(csp)
                    csp = self.transform_csp(csp, layer)

                    # rotate the path to get bounds in defined direction.
                    a = - self.options.area_fill_angle
                    rotated_path = [[[[point[0] * math.cos(a) - point[1] * math.sin(a), point[0] * math.sin(a) + point[1] * math.cos(a)] for point in sp] for sp in subpath] for subpath in csp]
                    bounds = csp_true_bounds(rotated_path)

                    # Draw the lines
                    # Get path's bounds
                    b = [0.0, 0.0, 0.0, 0.0]  # [minx,miny,maxx,maxy]
                    for k in range(4):
                        i = bounds[k][2]
                        j = bounds[k][3]
                        t = bounds[k][4]

                        b[k] = csp_at_t(rotated_path[i][j - 1], rotated_path[i][j], t)[k % 2]

                    # Zig-zag
                    r = tool['diameter'] * (1 - self.options.area_tool_overlap)
                    if r <= 0:
                        self.error('Tools diameter must be greater than 0!', 'error')
                        return

                    lines += [[]]

                    if self.options.area_fill_method == 'zig-zag':
                        i = b[0] - self.options.area_fill_shift * r
                        top = True
                        last_one = True
                        while i < b[2] or last_one:
                            if i >= b[2]:
                                last_one = False
                            if not lines[-1]:
                                lines[-1] += [[i, b[3]]]

                            if top:
                                lines[-1] += [[i, b[1]], [i + r, b[1]]]

                            else:
                                lines[-1] += [[i, b[3]], [i + r, b[3]]]

                            top = not top
                            i += r
                    else:

                        w = b[2] - b[0] + self.options.area_fill_shift * r
                        h = b[3] - b[1] + self.options.area_fill_shift * r
                        x = b[0] - self.options.area_fill_shift * r
                        y = b[1] - self.options.area_fill_shift * r
                        lines[-1] += [[x, y]]
                        stage = 0
                        start = True
                        while w > 0 and h > 0:
                            stage = (stage + 1) % 4
                            if stage == 0:
                                y -= h
                                h -= r
                            elif stage == 1:
                                x += w
                                if not start:
                                    w -= r
                                start = False
                            elif stage == 2:
                                y += h
                                h -= r
                            elif stage == 3:
                                x -= w
                                w -= r

                            lines[-1] += [[x, y]]

                        stage = (stage + 1) % 4
                        if w <= 0 and h > 0:
                            y = y - h if stage == 0 else y + h
                        if h <= 0 and w > 0:
                            x = x - w if stage == 3 else x + w
                        lines[-1] += [[x, y]]
                    # Rotate created paths back
                    a = self.options.area_fill_angle
                    lines = [[[point[0] * math.cos(a) - point[1] * math.sin(a), point[0] * math.sin(a) + point[1] * math.cos(a)] for point in subpath] for subpath in lines]

                    # get the intersection points

                    splitted_line = [[lines[0][0]]]
                    intersections = {}
                    for l1, l2, in zip(lines[0], lines[0][1:]):
                        ints = []

                        if l1[0] == l2[0] and l1[1] == l2[1]:
                            continue
                        for i in range(len(csp)):
                            for j in range(1, len(csp[i])):
                                sp1 = csp[i][j - 1]
                                sp2 = csp[i][j]
                                roots = csp_line_intersection(l1, l2, sp1, sp2)
                                for t in roots:
                                    p = tuple(csp_at_t(sp1, sp2, t))
                                    if l1[0] == l2[0]:
                                        t1 = (p[1] - l1[1]) / (l2[1] - l1[1])
                                    else:
                                        t1 = (p[0] - l1[0]) / (l2[0] - l1[0])
                                    if 0 <= t1 <= 1:
                                        ints += [[t1, p[0], p[1], i, j, t]]
                                        if p in intersections:
                                            intersections[p] += [[i, j, t]]
                                        else:
                                            intersections[p] = [[i, j, t]]

                        ints.sort()
                        for i in ints:
                            splitted_line[-1] += [[i[1], i[2]]]
                            splitted_line += [[[i[1], i[2]]]]
                        splitted_line[-1] += [l2]
                        i = 0
                    print_(splitted_line)
                    while i < len(splitted_line):
                        # check if the middle point of the first lines segment is inside the path.
                        # and remove the subline if not.
                        l1 = splitted_line[i][0]
                        l2 = splitted_line[i][1]
                        p = [(l1[0] + l2[0]) / 2, (l1[1] + l2[1]) / 2]
                        if not point_inside_csp(p, csp):
                            del splitted_line[i]
                        else:
                            i += 1

                    # and apply back transrormations to draw them
                    csp_line = csp_from_polyline(splitted_line)
                    csp_line = self.transform_csp(csp_line, layer, True)

                    self.draw_csp(csp_line, group=area_group)

    ################################################################################
    #
    # Engraving
    #
    # LT Notes to self: See wiki.inkscape.org/wiki/index.php/PythonEffectTutorial
    # To create anything in the Inkscape document, look at the XML editor for
    # details of how such an element looks in XML, then follow this model.
    # layer number n appears in XML as <svg:g id="layern" inkscape:label="layername">
    #
    # to create it, use
    # Mylayer = self.svg.add(Layer.new('layername'))
    #
    # group appears in XML as <svg:g id="gnnnnn"> where nnnnn is a number
    #
    # to create it, use
    # Mygroup = parent.add(Group(gcodetools="My group label")
    # where parent may be the layer or a parent group. To get the parent group, you can use
    # parent = self.selected_paths[layer][0].getparent()
    ################################################################################
    def tab_engraving(self):
        self.get_info_plus()
        global cspm
        global wl
        global nlLT
        global i
        global j
        global gcode_3Dleft
        global gcode_3Dright
        global max_dist  # minimum of tool radius and user's requested maximum distance
        global eye_dist
        eye_dist = 100  # 3D constant. Try varying it for your eyes

        def bisect(nxy1, nxy2):
            """LT Find angle bisecting the normals n1 and n2

            Parameters: Normalised normals
            Returns: nx - Normal of bisector, normalised to 1/cos(a)
                   ny -
                   sinBis2 - sin(angle turned/2): positive if turning in
            Note that bisect(n1,n2) and bisect(n2,n1) give opposite sinBis2 results
            If sinturn is less than the user's requested angle tolerance, I return 0
            """
            (nx1, ny1) = nxy1
            (nx2, ny2) = nxy2
            cosBis = math.sqrt(max(0, (1.0 + nx1 * nx2 - ny1 * ny2) / 2.0))
            # We can get correct sign of the sin, assuming cos is positive
            if (abs(ny1 - ny2) < ENGRAVING_TOLERANCE) or (abs(cosBis) < ENGRAVING_TOLERANCE):
                if abs(nx1 - nx2) < ENGRAVING_TOLERANCE:
                    return nx1, ny1, 0.0
                sinBis = math.copysign(1, ny1)
            else:
                sinBis = cosBis * (nx2 - nx1) / (ny1 - ny2)
            # We can correct signs by noting that the dot product
            # of bisector and either normal must be >0
            costurn = cosBis * nx1 + sinBis * ny1
            if costurn == 0:
                return ny1 * 100, -nx1 * 100, 1  # Path doubles back on itself
            sinturn = sinBis * nx1 - cosBis * ny1
            if costurn < 0:
                sinturn = -sinturn
            if 0 < sinturn * 114.6 < (180 - self.options.engraving_sharp_angle_tollerance):
                sinturn = 0  # set to zero if less than the user wants to see.
            return cosBis / costurn, sinBis / costurn, sinturn
            # end bisect

        def get_radius_to_line(xy1, n_xy1, n_xy2, xy2, n_xy23, xy3, n_xy3):
            """LT find biggest circle we can engrave here, if constrained by line 2-3

            Parameters:
                x1,y1,nx1,ny1 coordinates and normal of the line we're currently engraving
                nx2,ny2 angle bisector at point 2
                x2,y2 coordinates of first point of line 2-3
                nx23,ny23 normal to the line 2-3
                x3,y3 coordinates of the other end
                nx3,ny3 angle bisector at point 3
            Returns:
                radius or self.options.engraving_max_dist if line doesn't limit radius
            This function can be used in three ways:
            - With nx1=ny1=0 it finds circle centred at x1,y1
            - with nx1,ny1 normalised, it finds circle tangential at x1,y1
            - with nx1,ny1 scaled by 1/cos(a) it finds circle centred on an angle bisector
                 where a is the angle between the bisector and the previous/next normals

            If the centre of the circle tangential to the line 2-3 is outside the
            angle bisectors at its ends, ignore this line.

            Note that it handles corners in the conventional manner of letter cutting
            by mitering, not rounding.
            Algorithm uses dot products of normals to find radius
            and hence coordinates of centre
            """
            (x1, y1) = xy1
            (nx1, ny1) = n_xy1
            (nx2, ny2) = n_xy2
            (x2, y2) = xy2
            (nx23, ny23) = n_xy23
            (x3, y3) = xy3
            (nx3, ny3) = n_xy3
            global max_dist

            # Start by converting coordinates to be relative to x1,y1
            x2, y2 = x2 - x1, y2 - y1
            x3, y3 = x3 - x1, y3 - y1

            # The logic uses vector arithmetic.
            # The dot product of two vectors gives the product of their lengths
            # multiplied by the cos of the angle between them.
            # So, the perpendicular distance from x1y1 to the line 2-3
            # is equal to the dot product of its normal and x2y2 or x3y3
            # It is also equal to the projection of x1y1-xcyc on the line's normal
            # plus the radius. But, as the normal faces inside the path we must negate it.

            # Make sure the line in question is facing x1,y1 and vice versa
            dist = -x2 * nx23 - y2 * ny23
            if dist < 0:
                return max_dist
            denom = 1. - nx23 * nx1 - ny23 * ny1
            if denom < ENGRAVING_TOLERANCE:
                return max_dist

            # radius and centre are:
            r = dist / denom
            cx = r * nx1
            cy = r * ny1
            # if c is not between the angle bisectors at the ends of the line, ignore
            # Use vector cross products. Not sure if I need the .0001 safety margins:
            if (x2 - cx) * ny2 > (y2 - cy) * nx2 + 0.0001:
                return max_dist
            if (x3 - cx) * ny3 < (y3 - cy) * nx3 - 0.0001:
                return max_dist
            return min(r, max_dist)
            # end of get_radius_to_line

        def get_radius_to_point(xy1, n_xy, xy2):
            """LT find biggest circle we can engrave here, constrained by point x2,y2

            This function can be used in three ways:
            - With nx=ny=0 it finds circle centred at x1,y1
            - with nx,ny normalised, it finds circle tangential at x1,y1
            - with nx,ny scaled by 1/cos(a) it finds circle centred on an angle bisector
                 where a is the angle between the bisector and the previous/next normals

            Note that I wrote this to replace find_cutter_centre. It is far less
            sophisticated but, I hope, far faster.
            It turns out that finding a circle touching a point is harder than a circle
            touching a line.
            """
            (x1, y1) = xy1
            (nx, ny) = n_xy
            (x2, y2) = xy2
            global max_dist

            # Start by converting coordinates to be relative to x1,y1
            x2 = x2 - x1
            y2 = y2 - y1
            denom = nx ** 2 + ny ** 2 - 1
            if denom <= ENGRAVING_TOLERANCE:  # Not a corner bisector
                if denom == -1:  # Find circle centre x1,y1
                    return math.sqrt(x2 ** 2 + y2 ** 2)
                # if x2,y2 not in front of the normal...
                if x2 * nx + y2 * ny <= 0:
                    return max_dist
                return (x2 ** 2 + y2 ** 2) / (2 * (x2 * nx + y2 * ny))
            # It is a corner bisector, so..
            discriminator = (x2 * nx + y2 * ny) ** 2 - denom * (x2 ** 2 + y2 ** 2)
            if discriminator < 0:
                return max_dist  # this part irrelevant
            r = (x2 * nx + y2 * ny - math.sqrt(discriminator)) / denom
            return min(r, max_dist)
            # end of get_radius_to_point

        def bez_divide(a, b, c, d):
            """LT recursively divide a Bezier.

            Divides until difference between each
            part and a straight line is less than some limit
            Note that, as simple as this code is, it is mathematically correct.
            Parameters:
                a,b,c and d are each a list of x,y real values
                Bezier end points a and d, control points b and c
            Returns:
                a list of Beziers.
                    Each Bezier is a list with four members,
                        each a list holding a coordinate pair
                Note that the final point of one member is the same as
                the first point of the next, and the control points
                there are smooth and symmetrical. I use this fact later.
            """
            bx = b[0] - a[0]
            by = b[1] - a[1]
            cx = c[0] - a[0]
            cy = c[1] - a[1]
            dx = d[0] - a[0]
            dy = d[1] - a[1]
            limit = 8 * math.hypot(dx, dy) / self.options.engraving_newton_iterations
            # LT This is the only limit we get from the user currently
            if abs(dx * by - bx * dy) < limit and abs(dx * cy - cx * dy) < limit:
                return [[a, b, c, d]]
            abx = (a[0] + b[0]) / 2.0
            aby = (a[1] + b[1]) / 2.0
            bcx = (b[0] + c[0]) / 2.0
            bcy = (b[1] + c[1]) / 2.0
            cdx = (c[0] + d[0]) / 2.0
            cdy = (c[1] + d[1]) / 2.0
            abcx = (abx + bcx) / 2.0
            abcy = (aby + bcy) / 2.0
            bcdx = (bcx + cdx) / 2.0
            bcdy = (bcy + cdy) / 2.0
            m = [(abcx + bcdx) / 2.0, (abcy + bcdy) / 2.0]
            return bez_divide(a, [abx, aby], [abcx, abcy], m) + bez_divide(m, [bcdx, bcdy], [cdx, cdy], d)
            # end of bez_divide

        def get_biggest(nxy1, nxy2):
            """LT Find biggest circle we can draw inside path at point x1,y1 normal nx,ny

            Parameters:
                point - either on a line or at a reflex corner
                normal - normalised to 1 if on a line, to 1/cos(a) at a corner
            Returns:
                tuple (j,i,r)
                ..where j and i are indices of limiting segment, r is radius
            """
            (x1, y1) = nxy1
            (nx, ny) = nxy2
            global max_dist
            global nlLT
            global i
            global j

            n1 = nlLT[j][i - 1]  # current node
            jjmin = -1
            iimin = -1
            r = max_dist
            # set limits within which to look for lines
            xmin = x1 + r * nx - r
            xmax = x1 + r * nx + r
            ymin = y1 + r * ny - r
            ymax = y1 + r * ny + r
            for jj in xrange(0, len(nlLT)):  # for every subpath of this object
                for ii in xrange(0, len(nlLT[jj])):  # for every point and line
                    if nlLT[jj][ii - 1][2]:  # if a point
                        if jj == j:  # except this one
                            if abs(ii - i) < 3 or abs(ii - i) > len(nlLT[j]) - 3:
                                continue
                        t1 = get_radius_to_point((x1, y1), (nx, ny), nlLT[jj][ii - 1][0])
                    else:  # doing a line
                        if jj == j:  # except this one
                            if abs(ii - i) < 2 or abs(ii - i) == len(nlLT[j]) - 1:
                                continue
                            if abs(ii - i) == 2 and nlLT[j][(ii + i) / 2 - 1][3] <= 0:
                                continue
                            if (abs(ii - i) == len(nlLT[j]) - 2) and nlLT[j][-1][3] <= 0:
                                continue
                        nx2, ny2 = nlLT[jj][ii - 2][1]
                        x2, y2 = nlLT[jj][ii - 1][0]
                        nx23, ny23 = nlLT[jj][ii - 1][1]
                        x3, y3 = nlLT[jj][ii][0]
                        nx3, ny3 = nlLT[jj][ii][1]
                        if nlLT[jj][ii - 2][3] > 0:  # acute, so use normal, not bisector
                            nx2 = nx23
                            ny2 = ny23
                        if nlLT[jj][ii][3] > 0:  # acute, so use normal, not bisector
                            nx3 = nx23
                            ny3 = ny23
                        x23min = min(x2, x3)
                        x23max = max(x2, x3)
                        y23min = min(y2, y3)
                        y23max = max(y2, y3)
                        # see if line in range
                        if n1[2] == False and (x23max < xmin or x23min > xmax or y23max < ymin or y23min > ymax):
                            continue
                        t1 = get_radius_to_line((x1, y1), (nx, ny), (nx2, ny2), (x2, y2), (nx23, ny23), (x3, y3), (nx3, ny3))
                    if 0 <= t1 < r:
                        r = t1
                        iimin = ii
                        jjmin = jj
                        xmin = x1 + r * nx - r
                        xmax = x1 + r * nx + r
                        ymin = y1 + r * ny - r
                        ymax = y1 + r * ny + r
                # next ii
            # next jj
            return jjmin, iimin, r
            # end of get_biggest

        def line_divide(xy0, j0, i0, xy1, j1, i1, n_xy, length):
            """LT recursively divide a line as much as necessary

            NOTE: This function is not currently used
            By noting which other path segment is touched by the circles at each end,
            we can see if anything is to be gained by a further subdivision, since
            if they touch the same bit of path we can move linearly between them.
            Also, we can handle points correctly.
            Parameters:
                end points and indices of limiting path, normal, length
            Returns:
                list of toolpath points
                    each a list of 3 reals: x, y coordinates, radius

            """
            (x0, y0) = xy0
            (x1, y1) = xy1
            (nx, ny) = n_xy
            global nlLT
            global i
            global j
            global lmin
            x2 = (x0 + x1) / 2
            y2 = (y0 + y1) / 2
            j2, i2, r2 = get_biggest((x2, y2), (nx, ny))
            if length < lmin:
                return [[x2, y2, r2]]
            if j2 == j0 and i2 == i0:  # Same as left end. Don't subdivide this part any more
                return [[x2, y2, r2], line_divide((x2, y2), j2, i2, (x1, y1), j1, i1, (nx, ny), length / 2)]
            if j2 == j1 and i2 == i1:  # Same as right end. Don't subdivide this part any more
                return [line_divide((x0, y0), j0, i0, (x2, y2), j2, i2, (nx, ny), length / 2), [x2, y2, r2]]
            return [line_divide((x0, y0), j0, i0, (x2, y2), j2, i2, (nx, ny), length / 2), line_divide((x2, y2), j2, i2, (x1, y1), j1, i1, (nx, ny), length / 2)]
            # end of line_divide()

        def save_point(xy, w, i, j, ii, jj):
            """LT Save this point and delete previous one if linear

            The point is, we generate tons of points but many may be in a straight 3D line.
            There is no benefit in saving the intermediate points.
            """
            (x, y) = xy
            global wl
            global cspm
            x = round(x, 4)  # round to 4 decimals
            y = round(y, 4)  # round to 4 decimals
            w = round(w, 4)  # round to 4 decimals
            if len(cspm) > 1:
                xy1a, xy1, xy1b, i1, j1, ii1, jj1 = cspm[-1]
                w1 = wl[-1]
                if i == i1 and j == j1 and ii == ii1 and jj == jj1:  # one match
                    xy1a, xy2, xy1b, i1, j1, ii1, jj1 = cspm[-2]
                    w2 = wl[-2]
                    if i == i1 and j == j1 and ii == ii1 and jj == jj1:  # two matches. Now test linearity
                        length1 = math.hypot(xy1[0] - x, xy1[1] - y)
                        length2 = math.hypot(xy2[0] - x, xy2[1] - y)
                        length12 = math.hypot(xy2[0] - xy1[0], xy2[1] - xy1[1])
                        # get the xy distance of point 1 from the line 0-2
                        if length2 > length1 and length2 > length12:  # point 1 between them
                            xydist = abs((xy2[0] - x) * (xy1[1] - y) - (xy1[0] - x) * (xy2[1] - y)) / length2
                            if xydist < ENGRAVING_TOLERANCE:  # so far so good
                                wdist = w2 + (w - w2) * length1 / length2 - w1
                                if abs(wdist) < ENGRAVING_TOLERANCE:
                                    cspm.pop()
                                    wl.pop()
            cspm += [[[x, y], [x, y], [x, y], i, j, ii, jj]]
            wl += [w]
            # end of save_point

        def draw_point(xy0, xy, w, t):
            """LT Draw this point as a circle with a 1px dot in the middle (x,y)
            and a 3D line from (x0,y0) down to x,y. 3D line thickness should be t/2

            Note that points that are subsequently erased as being unneeded do get
            displayed, but this helps the user see the total area covered.
            """
            (x0, y0) = xy0
            (x, y) = xy
            global gcode_3Dleft
            global gcode_3Dright
            if self.options.engraving_draw_calculation_paths:
                elem = engraving_group.add(PathElement.arc((x, y), 1))
                elem.set('gcodetools', "Engraving calculation toolpath")
                elem.style = "fill:#ff00ff; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;"

                # Don't draw zero radius circles
                if w:
                    elem = engraving_group.add(PathElement.arc((x, y), w))
                    elem.set('gcodetools', "Engraving calculation paths")
                    elem.style = "fill:none; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;"

                    # Find slope direction for shading
                    s = math.atan2(y - y0, x - x0)  # -pi to pi
                    # convert to 2 hex digits as a shade of red
                    s2 = "#{0:x}0000".format(int(101 * (1.5 - math.sin(s + 0.5))))
                    style = "stroke:{}; stroke-opacity:1;stroke-width:{};fill:none".format(s2, t/2)
                    right = gcode_3Dleft.add(PathElement(style=style, gcodetools="Gcode G1R"))
                    right.path = "M {:f},{:f} L {:f},{:f}".format(
                        x0 - eye_dist, y0, x - eye_dist - 0.14 * w, y)
                    left = gcode_3Dright.add(PathElement(style=style, gcodetools="Gcode G1L"))
                    left.path = "M {:f},{:f} L {:f},{:f}".format(
                        x0 + eye_dist, y0, x + eye_dist + 0.14 * r, y)

        # end of subfunction definitions. engraving() starts here:
        gcode = ''
        r = 0  # theoretical and tool-radius-limited radii in pixels
        w = 0
        wmax = 0
        cspe = []
        we = []
        if not self.selected_paths:
            self.error("Please select at least one path to engrave and run again.")
            return
        if not self.check_dir():
            return
        # Find what units the user uses
        unit = " mm"
        if self.options.unit == "G20 (All units in inches)":
            unit = " inches"
        elif self.options.unit != "G21 (All units in mm)":
            self.error("Unknown unit selected. mm assumed")
        print_("engraving_max_dist mm/inch", self.options.engraving_max_dist)

        # LT See if we can use this parameter for line and Bezier subdivision:
        bitlen = 20 / self.options.engraving_newton_iterations

        for layer in self.layers:
            if layer in self.selected_paths and layer in self.orientation_points:
                # Calculate scale in pixels per user unit (mm or inch)
                p1 = self.orientation_points[layer][0][0]
                p2 = self.orientation_points[layer][0][1]
                ol = math.hypot(p1[0][0] - p2[0][0], p1[0][1] - p2[0][1])
                oluu = math.hypot(p1[1][0] - p2[1][0], p1[1][1] - p2[1][1])
                print_("Orientation2 p1 p2 ol oluu", p1, p2, ol, oluu)
                orientation_scale = ol / oluu

                self.set_tool(layer)
                shape = self.tools[layer][0]['shape']
                if re.search('w', shape):
                    toolshape = eval('lambda w: ' + shape.strip('"'))
                else:
                    self.error("Tool '{}' has no shape. 45 degree cone assumed!".format(self.tools[layer][0]['name']))
                    toolshape = lambda w: w
                # Get tool radius in pixels
                toolr = self.tools[layer][0]['diameter'] * orientation_scale / 2
                print_("tool radius in pixels=", toolr)
                # max dist from path to engrave in user's units
                max_distuu = min(self.tools[layer][0]['diameter'] / 2, self.options.engraving_max_dist)
                max_dist = max_distuu * orientation_scale
                print_("max_dist pixels", max_dist)

                engraving_group = self.selected_paths[layer][0].getparent().add(Group())
                if self.options.engraving_draw_calculation_paths and (self.my3Dlayer is None):
                    self.svg.add(Layer.new("3D"))
                # Create groups for left and right eyes
                if self.options.engraving_draw_calculation_paths:
                    gcode_3Dleft = self.my3Dlayer.add(Group(gcodetools="Gcode 3D L"))
                    gcode_3Dright = self.my3Dlayer.add(Group(gcodetools="Gcode 3D R"))

                for node in self.selected_paths[layer]:
                    if isinstance(node, inkex.PathElement):
                        cspi = node.path.to_superpath()
                        # LT: Create my own list. n1LT[j] is for subpath j
                        nlLT = []
                        for j in xrange(len(cspi)):  # LT For each subpath...
                            # Remove zero length segments, assume closed path
                            i = 0  # LT was from i=1
                            while i < len(cspi[j]):
                                if abs(cspi[j][i - 1][1][0] - cspi[j][i][1][0]) < ENGRAVING_TOLERANCE and abs(cspi[j][i - 1][1][1] - cspi[j][i][1][1]) < ENGRAVING_TOLERANCE:
                                    cspi[j][i - 1][2] = cspi[j][i][2]
                                    del cspi[j][i]
                                else:
                                    i += 1
                        for csp in cspi:  # LT6a For each subpath...
                            # Create copies in 3D layer
                            print_("csp is zz ", csp)
                            cspl = []
                            cspr = []
                            # create list containing lines and points, starting with a point
                            # line members: [x,y],[nx,ny],False,i
                            # x,y is start of line. Normal on engraved side.
                            # Normal is normalised (unit length)
                            # Note that Y axis increases down the page
                            # corner members: [x,y],[nx,ny],True,sin(halfangle)
                            # if halfangle>0: radius 0 here. normal is bisector
                            # if halfangle<0. reflex angle. normal is bisector
                            # corner normals are divided by cos(halfangle)
                            # so that they will engrave correctly
                            print_("csp is", csp)
                            nlLT.append([])
                            for i in range(0, len(csp)):  # LT for each point
                                sp0 = csp[i - 2]
                                sp1 = csp[i - 1]
                                sp2 = csp[i]
                                if self.options.engraving_draw_calculation_paths:
                                    # Copy it to 3D layer objects
                                    spl = []
                                    spr = []
                                    for j in range(0, 3):
                                        pl = [sp2[j][0] - eye_dist, sp2[j][1]]
                                        pr = [sp2[j][0] + eye_dist, sp2[j][1]]
                                        spl += [pl]
                                        spr += [pr]
                                    cspl += [spl]
                                    cspr += [spr]
                                # LT find angle between this and previous segment
                                x0, y0 = sp1[1]
                                nx1, ny1 = csp_normalized_normal(sp1, sp2, 0)
                                # I don't trust this function, so test result
                                if abs(1 - math.hypot(nx1, ny1)) > 0.00001:
                                    print_("csp_normalised_normal error t=0", nx1, ny1, sp1, sp2)
                                    self.error("csp_normalised_normal error. See log.")

                                nx0, ny0 = csp_normalized_normal(sp0, sp1, 1)
                                if abs(1 - math.hypot(nx0, ny0)) > 0.00001:
                                    print_("csp_normalised_normal error t=1", nx0, ny0, sp1, sp2)
                                    self.error("csp_normalised_normal error. See log.")
                                bx, by, s = bisect((nx0, ny0), (nx1, ny1))
                                # record x,y,normal,ifCorner, sin(angle-turned/2)
                                nlLT[-1] += [[[x0, y0], [bx, by], True, s]]

                                # LT now do the line
                                if sp1[1] == sp1[2] and sp2[0] == sp2[1]:  # straightline
                                    nlLT[-1] += [[sp1[1], [nx1, ny1], False, i]]
                                else:  # Bezier. First, recursively cut it up:
                                    nn = bez_divide(sp1[1], sp1[2], sp2[0], sp2[1])
                                    first = True  # Flag entry to divided Bezier
                                    for bLT in nn:  # save as two line segments
                                        for seg in range(3):
                                            if seg > 0 or first:
                                                nx1 = bLT[seg][1] - bLT[seg + 1][1]
                                                ny1 = bLT[seg + 1][0] - bLT[seg][0]
                                                l1 = math.hypot(nx1, ny1)
                                                if l1 < ENGRAVING_TOLERANCE:
                                                    continue
                                                nx1 = nx1 / l1  # normalise them
                                                ny1 = ny1 / l1
                                                nlLT[-1] += [[bLT[seg], [nx1, ny1], False, i]]
                                                first = False
                                            if seg < 2:  # get outgoing bisector
                                                nx0 = nx1
                                                ny0 = ny1
                                                nx1 = bLT[seg + 1][1] - bLT[seg + 2][1]
                                                ny1 = bLT[seg + 2][0] - bLT[seg + 1][0]
                                                l1 = math.hypot(nx1, ny1)
                                                if l1 < ENGRAVING_TOLERANCE:
                                                    continue
                                                nx1 = nx1 / l1  # normalise them
                                                ny1 = ny1 / l1
                                                # bisect
                                                bx, by, s = bisect((nx0, ny0), (nx1, ny1))
                                                nlLT[-1] += [[bLT[seg + 1], [bx, by], True, 0.]]
                            # LT for each segment - ends here.
                            print_(("engraving_draw_calculation_paths=", self.options.engraving_draw_calculation_paths))
                            if self.options.engraving_draw_calculation_paths:
                                # Copy complete paths to 3D layer
                                cspl += [cspl[0]]  # Close paths
                                cspr += [cspr[0]]  # Close paths
                                style = "stroke:#808080; stroke-opacity:1; stroke-width:0.6; fill:none"
                                elem = gcode_3Dleft.add(PathElement(style=style, gcodetools="G1L outline"))
                                elem.path = CubicSuperPath([cspl])
                                elem = gcode_3Dright.add(Pathelement(style=style, gcodetools="G1R outline"))
                                elem.path = CubicSuperPath([cspr])

                                for p in nlLT[-1]:  # For last sub-path
                                    if p[2]:
                                        elem = engraving_group.add(PathElement(gcodetools="Engraving normals"))
                                        elem.path = "M {:f},{:f} L {:f},{:f}".format(p[0][0], p[0][1],
                                            p[0][0] + p[1][0] * 10, p[0][1] + p[1][1] * 10)
                                        elem.style = "stroke:#f000af; stroke-opacity:0.46; stroke-width:0.1; fill:none"
                                    else:
                                        elem = engraving_group.add(PathElement(gcodetools="Engraving bisectors"))
                                        elem.path = "M {:f},{:f} L {:f},{:f}".format(p[0][0], p[0][1],
                                            p[0][0] + p[1][0] * 10, p[0][1] + p[1][1] * 10)
                                        elem.style = "stroke:#0000ff; stroke-opacity:0.46; stroke-width:0.1; fill:none"

                        # LT6a build nlLT[j] for each subpath - ends here
                        # Calculate offset points
                        reflex = False
                        for j in xrange(len(nlLT)):  # LT6b for each subpath
                            cspm = []  # Will be my output. List of csps.
                            wl = []  # Will be my w output list
                            w = r = 0  # LT initial, as first point is an angle
                            for i in xrange(len(nlLT[j])):  # LT for each node
                                # LT Note: Python enables wrapping of array indices
                                # backwards to -1, -2, but not forwards. Hence:
                                n0 = nlLT[j][i - 2]  # previous node
                                n1 = nlLT[j][i - 1]  # current node
                                n2 = nlLT[j][i]  # next node
                                # if n1[2] == True and n1[3]==0 : # A straight angle
                                # continue
                                x1a, y1a = n1[0]  # this point/start of this line
                                nx, ny = n1[1]
                                x1b, y1b = n2[0]  # next point/end of this line
                                if n1[2]:  # We're at a corner
                                    bits = 1
                                    bit0 = 0
                                    # lastr=r #Remember r from last line
                                    lastw = w  # Remember w from last line
                                    w = max_dist
                                    if n1[3] > 0:  # acute. Limit radius
                                        len1 = math.hypot((n0[0][0] - n1[0][0]), (n0[0][1] - n1[0][1]))
                                        if i < (len(nlLT[j]) - 1):
                                            len2 = math.hypot((nlLT[j][i + 1][0][0] - n1[0][0]), (nlLT[j][i + 1][0][1] - n1[0][1]))
                                        else:
                                            len2 = math.hypot((nlLT[j][0][0][0] - n1[0][0]), (nlLT[j][0][0][1] - n1[0][1]))
                                        # set initial r value, not to be exceeded
                                        w = math.sqrt(min(len1, len2)) / n1[3]
                                else:  # line. Cut it up if long.
                                    if n0[3] > 0 and not self.options.engraving_draw_calculation_paths:
                                        bit0 = r * n0[3]  # after acute corner
                                    else:
                                        bit0 = 0.0
                                    length = math.hypot((x1b - x1a), (y1a - y1b))
                                    bit0 = (min(length, bit0))
                                    bits = int((length - bit0) / bitlen)
                                    # split excess evenly at both ends
                                    bit0 += (length - bit0 - bitlen * bits) / 2
                                for b in xrange(bits):  # divide line into bits
                                    x1 = x1a + ny * (b * bitlen + bit0)
                                    y1 = y1a - nx * (b * bitlen + bit0)
                                    jjmin, iimin, w = get_biggest((x1, y1), (nx, ny))
                                    print_("i,j,jjmin,iimin,w", i, j, jjmin, iimin, w)
                                    wmax = max(wmax, w)
                                    if reflex:  # just after a reflex corner
                                        reflex = False
                                        if w < lastw:  # need to adjust it
                                            draw_point((x1, y1), (n0[0][0] + n0[1][0] * w, n0[0][1] + n0[1][1] * w), w, (lastw - w) / 2)
                                            save_point((n0[0][0] + n0[1][0] * w, n0[0][1] + n0[1][1] * w), w, i, j, iimin, jjmin)
                                    if n1[2]:  # We're at a corner
                                        if n1[3] > 0:  # acute
                                            save_point((x1 + nx * w, y1 + ny * w), w, i, j, iimin, jjmin)
                                            draw_point((x1, y1), (x1, y1), 0, 0)
                                            save_point((x1, y1), 0, i, j, iimin, jjmin)
                                        elif n1[3] < 0:  # reflex
                                            if w > lastw:
                                                draw_point((x1, y1), (x1 + nx * lastw, y1 + ny * lastw), w, (w - lastw) / 2)
                                                wmax = max(wmax, w)
                                                save_point((x1 + nx * w, y1 + ny * w), w, i, j, iimin, jjmin)
                                    elif b > 0 and n2[3] > 0 and not self.options.engraving_draw_calculation_paths:  # acute corner coming up
                                        if jjmin == j and iimin == i + 2:
                                            break
                                    draw_point((x1, y1), (x1 + nx * w, y1 + ny * w), w, bitlen)
                                    save_point((x1 + nx * w, y1 + ny * w), w, i, j, iimin, jjmin)

                                # LT end of for each bit of this line
                                if n1[2] == True and n1[3] < 0:  # reflex angle
                                    reflex = True
                                lastw = w  # remember this w
                            # LT next i
                            cspm += [cspm[0]]
                            print_("cspm", cspm)
                            wl += [wl[0]]
                            print_("wl", wl)
                            # Note: Original csp_points was a list, each element
                            # being 4 points, with the first being the same as the
                            # last of the previous set.
                            # Each point is a list of [cx,cy,r,w]
                            # I have flattened it to a flat list of points.

                            if self.options.engraving_draw_calculation_paths:
                                node = engraving_group.add(PathElement(
                                    gcodetools="Engraving calculation paths",
                                    style=MARKER_STYLE["biarc_style_i"]['biarc1']))
                                node.path = CubicSuperPath([cspm])
                                for i in xrange(len(cspm)):
                                    elem = engraving_group.add(PathElement.arc(cspm[i][1], wl[i]))
                                    elem.set('gcodetools', "Engraving calculation paths")
                                    elem.style = "fill:none;fill-opacity:0.46;stroke:#000000;stroke-width:0.1;"
                            cspe += [cspm]
                            wluu = []  # width list in user units: mm/inches
                            for w in wl:
                                wluu += [w / orientation_scale]
                            print_("wl in pixels", wl)
                            print_("wl in user units", wluu)
                            # LT previously, we was in pixels so gave wrong depth
                            we += [wluu]
                        # LT6b For each subpath - ends here
                    # LT5 if it is a path - ends here
                # LT4 for each selected object in this layer - ends here

                if cspe:
                    curve = self.parse_curve(cspe, layer, we, toolshape)  # convert to lines
                    self.draw_curve(curve, layer, engraving_group)
                    gcode += self.generate_gcode(curve, layer, self.options.Zsurface)

            # LT3 for layers loop ends here
        if gcode != '':
            self.header += "(Tool diameter should be at least " + str(2 * wmax / orientation_scale) + unit + ")\n"
            self.header += "(Depth, as a function of radius w, must be " + self.tools[layer][0]['shape'] + ")\n"
            self.header += "(Rapid feeds use safe Z=" + str(self.options.Zsafe) + unit + ")\n"
            self.header += "(Material surface at Z=" + str(self.options.Zsurface) + unit + ")\n"
            self.export_gcode(gcode)
        else:
            self.error("No need to engrave sharp angles.")

    ################################################################################
    #
    # Orientation
    #
    ################################################################################
    def tab_orientation(self, layer=None):
        self.get_info()
        Zsurface = f"{self.options.Zsurface:.5f}"
        Zdepth = f"{self.options.Zdepth:.5f}"
        if layer is None:
            layer = self.svg.get_current_layer() if self.svg.get_current_layer() is not None else self.document.getroot()

        transform = self.get_transforms(layer)
        if transform:
            transform = self.reverse_transform(transform)
            transform = str(Transform(transform))

        if self.options.orientation_points_count == "graffiti":
            print_(self.graffiti_reference_points)
            print_("Inserting graffiti points")
            if layer in self.graffiti_reference_points:
                graffiti_reference_points_count = len(self.graffiti_reference_points[layer])
            else:
                graffiti_reference_points_count = 0
            axis = ["X", "Y", "Z", "A"][graffiti_reference_points_count % 4]
            attr = {'gcodetools': "Gcodetools graffiti reference point"}
            if transform:
                attr["transform"] = transform
            group = layer.add(Group(**attr))
            elem = group.add(PathElement(style="stroke:none;fill:#00ff00;"))
            elem.set('gcodetools', "Gcodetools graffiti reference point arrow")
            elem.path = 'm {},{} 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,'\
                '-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.8125000000'\
                '01 z z'.format(graffiti_reference_points_count * 100, 0)

            draw_text(axis, graffiti_reference_points_count * 100 + 10, -10, group=group, gcodetools_tag="Gcodetools graffiti reference point text")

        elif self.options.orientation_points_count == "in-out reference point":
            draw_pointer(group=self.svg.get_current_layer(), x=self.svg.namedview.center, figure="arrow", pointer_type="In-out reference point", text="In-out point")

        else:
            print_("Inserting orientation points")

            if layer in self.orientation_points:
                self.error("Active layer already has orientation points! Remove them or select another layer!", "error")

            attr = {"gcodetools": "Gcodetools orientation group"}
            if transform:
                attr["transform"] = transform

            orientation_group = layer.add(Group(**attr))
            doc_height = self.svg.unittouu(self.document.getroot().get('height'))
            if self.document.getroot().get('height') == "100%":
                doc_height = 1052.3622047
                print_("Overriding height from 100 percents to {}".format(doc_height))
            if self.options.unit == "G21 (All units in mm)":
                points = [[0., 0., Zsurface], [100., 0., Zdepth], [0., 100., 0.]]
            elif self.options.unit == "G20 (All units in inches)":
                points = [[0., 0., Zsurface], [5., 0., Zdepth], [0., 5., 0.]]
            if self.options.orientation_points_count == "2":
                points = points[:2]
            for i in points:
                name = "Gcodetools orientation point ({} points)".format(
                    self.options.orientation_points_count)
                grp = orientation_group.add(Group(gcodetools=name))
                elem = grp.add(PathElement(style="stroke:none;fill:#000000;"))
                elem.set('gcodetools', "Gcodetools orientation point arrow")
                elem.path = 'm {},{} 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,'\
                    '-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000'\
                    '001 z'.format(i[0], -i[1] + doc_height)

                draw_text("({}; {}; {})".format(i[0], i[1], i[2]), (i[0] + 10), (-i[1] - 10 + doc_height), group=grp, gcodetools_tag="Gcodetools orientation point text")

    ################################################################################
    #
    # Tools library
    #
    ################################################################################
    def tab_tools_library(self, layer=None):
        self.get_info()

        if self.options.tools_library_type == "check":
            return self.check_tools_and_op()

        # Add a tool to the drawing
        if layer is None:
            layer = self.svg.get_current_layer() if self.svg.get_current_layer() is not None else self.document.getroot()
        if layer in self.tools:
            self.error("Active layer already has a tool! Remove it or select another layer!", "error")

        if self.options.tools_library_type == "cylinder cutter":
            tool = {
                "name": "Cylindrical cutter",
                "id": "Cylindrical cutter 0001",
                "diameter": 10,
                "penetration angle": 90,
                "feed": "400",
                "penetration feed": "100",
                "depth step": "1",
                "tool change gcode": " "
            }
        elif self.options.tools_library_type == "lathe cutter":
            tool = {
                "name": "Lathe cutter",
                "id": "Lathe cutter 0001",
                "diameter": 10,
                "penetration angle": 90,
                "feed": "400",
                "passing feed": "800",
                "fine feed": "100",
                "penetration feed": "100",
                "depth step": "1",
                "tool change gcode": " "
            }
        elif self.options.tools_library_type == "cone cutter":
            tool = {
                "name": "Cone cutter",
                "id": "Cone cutter 0001",
                "diameter": 10,
                "shape": "w",
                "feed": "400",
                "penetration feed": "100",
                "depth step": "1",
                "tool change gcode": " "
            }
        elif self.options.tools_library_type == "tangent knife":
            tool = {
                "name": "Tangent knife",
                "id": "Tangent knife 0001",
                "feed": "400",
                "penetration feed": "100",
                "depth step": "100",
                "4th axis meaning": "tangent knife",
                "4th axis scale": 1.,
                "4th axis offset": 0,
                "tool change gcode": " "
            }

        elif self.options.tools_library_type == "plasma cutter":
            tool = {
                "name": "Plasma cutter",
                "id": "Plasma cutter 0001",
                "diameter": 10,
                "penetration feed": 100,
                "feed": 400,
                "gcode before path": """G31 Z-100 F500 (find metal)
G92 Z0 (zero z)
G00 Z10 F500 (going up)
M03 (turn on plasma)
G04 P0.2 (pause)
G01 Z1 (going to cutting z)\n""",
                "gcode after path": "M05 (turn off plasma)\n",
            }
        elif self.options.tools_library_type == "graffiti":
            tool = {
                "name": "Graffiti",
                "id": "Graffiti 0001",
                "diameter": 10,
                "penetration feed": 100,
                "feed": 400,
                "gcode before path": """M03 S1(Turn spray on)\n """,
                "gcode after path": "M05 (Turn spray off)\n ",
                "tool change gcode": "(Add G00 here to change sprayer if needed)\n",

            }

        else:
            tool = self.default_tool

        tool_num = sum([len(self.tools[i]) for i in self.tools])
        colors = ["00ff00", "0000ff", "ff0000", "fefe00", "00fefe", "fe00fe", "fe7e00", "7efe00", "00fe7e", "007efe", "7e00fe", "fe007e"]

        tools_group = layer.add(Group(gcodetools="Gcodetools tool definition"))
        bg = tools_group.add(PathElement(gcodetools="Gcodetools tool background"))
        bg.style = "fill-opacity:0.5;stroke:#444444;"
        bg.style['fill'] = "#" + colors[tool_num % len(colors)]

        y = 0
        keys = []
        for key in self.tools_field_order:
            if key in tool:
                keys += [key]
        for key in tool:
            if key not in keys:
                keys += [key]
        for key in keys:
            g = tools_group.add(Group(gcodetools="Gcodetools tool parameter"))
            draw_text(key, 0, y, group=g, gcodetools_tag="Gcodetools tool definition field name", font_size=10 if key != 'name' else 20)
            param = tool[key]
            if type(param) == str and re.match("^\\s*$", param):
                param = "(None)"
            draw_text(param, 150, y, group=g, gcodetools_tag="Gcodetools tool definition field value", font_size=10 if key != 'name' else 20)
            v = str(param).split("\n")
            y += 15 * len(v) if key != 'name' else 20 * len(v)

        bg.set('d', "m -20,-20 l 400,0 0,{:f} -400,0 z ".format(y + 50))
        tools_group.transform.add_translate(*self.svg.namedview.center)
        tools_group.transform.add_translate(-150, 0)

    ################################################################################
    #
    # Check tools and OP assignment
    #
    ################################################################################
    def check_tools_and_op(self):
        if len(self.svg.selected) <= 0:
            self.error("Selection is empty! Will compute whole drawing.")
            paths = self.paths
        else:
            paths = self.selected_paths
        #    Set group
        parent = self.selected_paths.keys()[0] if len(self.selected_paths.keys()) > 0 else self.layers[0]
        group = parent.add(Group())
        trans_ = [[1, 0.3, 0], [0, 0.5, 0]]

        self.set_markers()

        bounds = [float('inf'), float('inf'), float('-inf'), float('-inf')]
        tools_bounds = {}
        for layer in self.layers:
            if layer in paths:
                self.set_tool(layer)
                tool = self.tools[layer][0]
                tools_bounds[layer] = tools_bounds[layer] if layer in tools_bounds else [float("inf"), float("-inf")]
                for path in paths[layer]:
                    group.insert(0, PathElement(**path.attrib))
                    new = group.getchildren()[0]
                    new.style = Style(
                        stroke='#000044', stroke_width=1,
                        marker_mid='url(#CheckToolsAndOPMarker)',
                        fill=tool["style"].get('fill', '#00ff00'),
                        fill_opacity=tool["style"].get('fill-opacity', 0.5))

                    trans = trans_ * self.get_transforms(path)
                    csp = path.path.transform(trans).to_superpath()

                    path_bounds = csp_simple_bound(csp)
                    trans = str(Transform(trans))
                    bounds = [min(bounds[0], path_bounds[0]), min(bounds[1], path_bounds[1]), max(bounds[2], path_bounds[2]), max(bounds[3], path_bounds[3])]
                    tools_bounds[layer] = [min(tools_bounds[layer][0], path_bounds[1]), max(tools_bounds[layer][1], path_bounds[3])]

                    new.set("transform", trans)
                    trans_[1][2] += 20
                trans_[1][2] += 100

        for layer in self.layers:
            if layer in self.tools:
                if layer in tools_bounds:
                    tool = self.tools[layer][0]
                    g = copy.deepcopy(tool["self_group"])
                    g.attrib["gcodetools"] = "Check tools and OP assignment"
                    trans = [[1, 0.3, bounds[2]], [0, 0.5, tools_bounds[layer][0]]]
                    g.set("transform", str(Transform(trans)))
                    group.insert(0, g)

    ################################################################################
    # TODO Launch browser on help tab
    ################################################################################
    def tab_help(self):
        self.error("Switch to another tab to run the extensions.\n"
                   "No changes are made if the preferences or help tabs are active.\n\n"
                   "Tutorials, manuals and support can be found at\n"
                   " English support forum:\n"
                   "    http://www.cnc-club.ru/gcodetools\n"
                   "and Russian support forum:\n"
                   "    http://www.cnc-club.ru/gcodetoolsru")
        return

    def tab_about(self):
        return self.tab_help()

    def tab_preferences(self):
        return self.tab_help()

    def tab_options(self):
        return self.tab_help()


    ################################################################################
    # Lathe
    ################################################################################
    def generate_lathe_gcode(self, subpath, layer, feed_type):
        if len(subpath) < 2:
            return ""
        feed = " F {:f}".format(self.tool[feed_type])
        x = self.options.lathe_x_axis_remap
        z = self.options.lathe_z_axis_remap
        flip_angle = -1 if x.lower() + z.lower() in ["xz", "yx", "zy"] else 1
        alias = {"X": "I", "Y": "J", "Z": "K", "x": "i", "y": "j", "z": "k"}
        i_ = alias[x]
        k_ = alias[z]
        c = [[subpath[0][1], "move", 0, 0, 0]]
        for sp1, sp2 in zip(subpath, subpath[1:]):
            c += biarc(sp1, sp2, 0, 0)
        for i in range(1, len(c)):  # Just in case check end point of each segment
            c[i - 1][4] = c[i][0][:]
        c += [[subpath[-1][1], "end", 0, 0, 0]]
        self.draw_curve(c, layer, style=MARKER_STYLE["biarc_style_lathe_{}".format(feed_type)])

        gcode = ("G01 {} {:f} {} {:f}".format(x, c[0][4][0], z, c[0][4][1])) + feed + "\n"  # Just in case move to the start...
        for s in c:
            if s[1] == 'line':
                gcode += ("G01 {} {:f} {} {:f}".format(x, s[4][0], z, s[4][1])) + feed + "\n"
            elif s[1] == 'arc':
                r = [(s[2][0] - s[0][0]), (s[2][1] - s[0][1])]
                if (r[0] ** 2 + r[1] ** 2) > self.options.min_arc_radius ** 2:
                    r1 = (P(s[0]) - P(s[2]))
                    r2 = (P(s[4]) - P(s[2]))
                    if abs(r1.mag() - r2.mag()) < 0.001:
                        gcode += ("G02" if s[3] * flip_angle < 0 else "G03") + (" {} {:f} {} {:f} {} {:f} {} {:f}".format(x, s[4][0], z, s[4][1], i_, (s[2][0] - s[0][0]), k_, (s[2][1] - s[0][1]))) + feed + "\n"
                    else:
                        r = (r1.mag() + r2.mag()) / 2
                        gcode += ("G02" if s[3] * flip_angle < 0 else "G03") + (" {} {:f} {} {:f}".format(x, s[4][0], z, s[4][1])) + " R{:f}".format(r) + feed + "\n"
        return gcode

    def tab_lathe(self):
        self.get_info_plus()
        if not self.check_dir():
            return
        x = self.options.lathe_x_axis_remap
        z = self.options.lathe_z_axis_remap
        x = re.sub("^\\s*([XYZxyz])\\s*$", r"\1", x)
        z = re.sub("^\\s*([XYZxyz])\\s*$", r"\1", z)
        if x not in ["X", "Y", "Z", "x", "y", "z"] or z not in ["X", "Y", "Z", "x", "y", "z"]:
            self.error("Lathe X and Z axis remap should be 'X', 'Y' or 'Z'. Exiting...")
            return
        if x.lower() == z.lower():
            self.error("Lathe X and Z axis remap should be the same. Exiting...")
            return
        if x.lower() + z.lower() in ["xy", "yx"]:
            gcode_plane_selection = "G17 (Using XY plane)\n"
        if x.lower() + z.lower() in ["xz", "zx"]:
            gcode_plane_selection = "G18 (Using XZ plane)\n"
        if x.lower() + z.lower() in ["zy", "yz"]:
            gcode_plane_selection = "G19 (Using YZ plane)\n"
        self.options.lathe_x_axis_remap = x
        self.options.lathe_z_axis_remap = z

        paths = self.selected_paths
        self.tool = []
        gcode = ""
        for layer in self.layers:
            if layer in paths:
                self.set_tool(layer)
                if self.tool != self.tools[layer][0]:
                    self.tool = self.tools[layer][0]
                    self.tool["passing feed"] = float(self.tool["passing feed"] if "passing feed" in self.tool else self.tool["feed"])
                    self.tool["feed"] = float(self.tool["feed"])
                    self.tool["fine feed"] = float(self.tool["fine feed"] if "fine feed" in self.tool else self.tool["feed"])
                    gcode += ("(Change tool to {})\n".format(re.sub("\"'\\(\\)\\\\", " ", self.tool["name"]))) + self.tool["tool change gcode"] + "\n"

                for path in paths[layer]:
                    csp = self.transform_csp(path.path.to_superpath(), layer)

                    for subpath in csp:
                        # Offset the path if fine cut is defined.
                        fine_cut = subpath[:]
                        if self.options.lathe_fine_cut_width > 0:
                            r = self.options.lathe_fine_cut_width
                            if self.options.lathe_create_fine_cut_using == "Move path":
                                subpath = [[[i2[0], i2[1] + r] for i2 in i1] for i1 in subpath]
                            else:
                                # Close the path to make offset correct
                                bound = csp_simple_bound([subpath])
                                minx, miny, maxx, maxy = csp_true_bounds([subpath])
                                offsetted_subpath = csp_subpath_line_to(subpath[:], [[subpath[-1][1][0], miny[1] - r * 10], [subpath[0][1][0], miny[1] - r * 10], [subpath[0][1][0], subpath[0][1][1]]])
                                left = subpath[-1][1][0]
                                right = subpath[0][1][0]
                                if left > right:
                                    left, right = right, left
                                offsetted_subpath = csp_offset([offsetted_subpath], r if not csp_subpath_ccw(offsetted_subpath) else -r)
                                offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left, 10], [left, 0])
                                offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right, 0], [right, 10])
                                offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1] - r], [10, miny[1] - r])
                                # Join offsetted_subpath together
                                # Hope there won't be any circles
                                subpath = csp_join_subpaths(offsetted_subpath)[0]

                        # Create solid object from path and lathe_width
                        bound = csp_simple_bound([subpath])
                        top_start = [subpath[0][1][0], self.options.lathe_width + self.options.Zsafe + self.options.lathe_fine_cut_width]
                        top_end = [subpath[-1][1][0], self.options.lathe_width + self.options.Zsafe + self.options.lathe_fine_cut_width]

                        gcode += ("G01 {} {:f} F {:f} \n".format(z, top_start[1], self.tool["passing feed"]))
                        gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, top_start[0], z, top_start[1], self.tool["passing feed"]))

                        subpath = csp_concat_subpaths(csp_subpath_line_to([], [top_start, subpath[0][1]]), subpath)
                        subpath = csp_subpath_line_to(subpath, [top_end, top_start])

                        width = max(0, self.options.lathe_width - max(0, bound[1]))
                        step = self.tool['depth step']
                        steps = int(math.ceil(width / step))
                        for i in range(steps + 1):
                            current_width = self.options.lathe_width - step * i
                            intersections = []
                            for j in range(1, len(subpath)):
                                sp1 = subpath[j - 1]
                                sp2 = subpath[j]
                                intersections += [[j, k] for k in csp_line_intersection([bound[0] - 10, current_width], [bound[2] + 10, current_width], sp1, sp2)]
                                intersections += [[j, k] for k in csp_line_intersection([bound[0] - 10, current_width + step], [bound[2] + 10, current_width + step], sp1, sp2)]
                            parts = csp_subpath_split_by_points(subpath, intersections)
                            for part in parts:
                                minx, miny, maxx, maxy = csp_true_bounds([part])
                                y = (maxy[1] + miny[1]) / 2
                                if y > current_width + step:
                                    gcode += self.generate_lathe_gcode(part, layer, "passing feed")
                                elif current_width <= y <= current_width + step:
                                    gcode += self.generate_lathe_gcode(part, layer, "feed")
                                else:
                                    # full step cut
                                    part = csp_subpath_line_to([], [part[0][1], part[-1][1]])
                                    gcode += self.generate_lathe_gcode(part, layer, "feed")

                        top_start = [fine_cut[0][1][0], self.options.lathe_width + self.options.Zsafe + self.options.lathe_fine_cut_width]
                        top_end = [fine_cut[-1][1][0], self.options.lathe_width + self.options.Zsafe + self.options.lathe_fine_cut_width]
                        gcode += "\n(Fine cutting start)\n(Calculating fine cut using {})\n".format(self.options.lathe_create_fine_cut_using)
                        for i in range(int(self.options.lathe_fine_cut_count)):
                            width = self.options.lathe_fine_cut_width * (1 - float(i + 1) / self.options.lathe_fine_cut_count)
                            if width == 0:
                                current_pass = fine_cut
                            else:
                                if self.options.lathe_create_fine_cut_using == "Move path":
                                    current_pass = [[[i2[0], i2[1] + width] for i2 in i1] for i1 in fine_cut]
                                else:
                                    minx, miny, maxx, maxy = csp_true_bounds([fine_cut])
                                    offsetted_subpath = csp_subpath_line_to(fine_cut[:], [[fine_cut[-1][1][0], miny[1] - r * 10], [fine_cut[0][1][0], miny[1] - r * 10], [fine_cut[0][1][0], fine_cut[0][1][1]]])
                                    left = fine_cut[-1][1][0]
                                    right = fine_cut[0][1][0]
                                    if left > right:
                                        left, right = right, left
                                    offsetted_subpath = csp_offset([offsetted_subpath], width if not csp_subpath_ccw(offsetted_subpath) else -width)
                                    offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left, 10], [left, 0])
                                    offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right, 0], [right, 10])
                                    offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1] - r], [10, miny[1] - r])
                                    current_pass = csp_join_subpaths(offsetted_subpath)[0]

                            gcode += "\n(Fine cut {:d}-th cicle start)\n".format(i + 1)
                            gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, top_start[0], z, top_start[1], self.tool["passing feed"]))
                            gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, current_pass[0][1][0], z, current_pass[0][1][1] + self.options.lathe_fine_cut_width, self.tool["passing feed"]))
                            gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, current_pass[0][1][0], z, current_pass[0][1][1], self.tool["fine feed"]))

                            gcode += self.generate_lathe_gcode(current_pass, layer, "fine feed")
                            gcode += ("G01 {} {:f} F {:f} \n".format(z, top_start[1], self.tool["passing feed"]))
                            gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, top_start[0], z, top_start[1], self.tool["passing feed"]))

        self.export_gcode(gcode)

    ################################################################################
    #
    # Lathe modify path
    # Modifies path to fit current cutter. As for now straight rect cutter.
    #
    ################################################################################

    def tab_lathe_modify_path(self):
        self.get_info()
        if self.selected_paths == {} and self.options.auto_select_paths:
            paths = self.paths
            self.error("No paths are selected! Trying to work on all available paths.")
        else:
            paths = self.selected_paths

        for layer in self.layers:
            if layer in paths:
                width = self.options.lathe_rectangular_cutter_width
                for path in paths[layer]:
                    csp = self.transform_csp(path.path.to_superpath(), layer)
                    new_csp = []
                    for subpath in csp:
                        orientation = subpath[-1][1][0] > subpath[0][1][0]
                        new_subpath = []

                        # Split segment at x' and y' == 0
                        for sp1, sp2 in zip(subpath[:], subpath[1:]):
                            ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2)
                            roots = cubic_solver_real(0, 3 * ax, 2 * bx, cx)
                            roots += cubic_solver_real(0, 3 * ay, 2 * by, cy)
                            new_subpath = csp_concat_subpaths(new_subpath, csp_seg_split(sp1, sp2, roots))
                        subpath = new_subpath
                        new_subpath = []
                        first_seg = True
                        for sp1, sp2 in zip(subpath[:], subpath[1:]):
                            n = csp_normalized_normal(sp1, sp2, 0)
                            a = math.atan2(n[0], n[1])
                            if a == 0 or a == math.pi:
                                n = csp_normalized_normal(sp1, sp2, 1)
                            a = math.atan2(n[0], n[1])
                            if a != 0 and a != math.pi:
                                o = 0 if 0 < a <= math.pi / 2 or -math.pi < a < -math.pi / 2 else 1
                                if not orientation:
                                    o = 1 - o

                                # Add first horizontal straight line if needed
                                if not first_seg and new_subpath == []:
                                    new_subpath = [[[subpath[0][i][0] - width * o, subpath[0][i][1]] for i in range(3)]]

                                new_subpath = csp_concat_subpaths(
                                        new_subpath,
                                        [
                                            [[sp1[i][0] - width * o, sp1[i][1]] for i in range(3)],
                                            [[sp2[i][0] - width * o, sp2[i][1]] for i in range(3)]
                                        ]
                                )
                            first_seg = False

                        # Add last horizontal straight line if needed
                        if a == 0 or a == math.pi:
                            new_subpath += [[[subpath[-1][i][0] - width * o, subpath[-1][i][1]] for i in range(3)]]

                    new_csp += [new_subpath]
                    self.draw_csp(new_csp, layer)

    ################################################################################
    # Graffiti function generates Gcode for graffiti drawer
    ################################################################################
    def tab_graffiti(self):
        self.get_info_plus()
        # Get reference points.

        def get_gcode_coordinates(point, layer):
            gcode = ''
            pos = []
            for ref_point in self.graffiti_reference_points[layer]:
                c = math.sqrt((point[0] - ref_point[0][0]) ** 2 + (point[1] - ref_point[0][1]) ** 2)
                gcode += " {} {:f}".format(ref_point[1], c)
                pos += [c]
            return pos, gcode

        def graffiti_preview_draw_point(x1, y1, color, radius=.5):
            self.graffiti_preview = self.graffiti_preview
            r, g, b, a_ = color
            for x in range(int(x1 - 1 - math.ceil(radius)), int(x1 + 1 + math.ceil(radius) + 1)):
                for y in range(int(y1 - 1 - math.ceil(radius)), int(y1 + 1 + math.ceil(radius) + 1)):
                    if x >= 0 and y >= 0 and y < len(self.graffiti_preview) and x * 4 < len(self.graffiti_preview[0]):
                        d = math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2)
                        a = float(a_) * (max(0, (1 - (d - radius))) if d > radius else 1) / 256
                        self.graffiti_preview[y][x * 4] = int(r * a + (1 - a) * self.graffiti_preview[y][x * 4])
                        self.graffiti_preview[y][x * 4 + 1] = int(g * a + (1 - a) * self.graffiti_preview[y][x * 4 + 1])
                        self.graffiti_preview[y][x * 4 + 2] = int(g * b + (1 - a) * self.graffiti_preview[y][x * 4 + 2])
                        self.graffiti_preview[y][x * 4 + 3] = min(255, int(self.graffiti_preview[y][x * 4 + 3] + a * 256))

        def graffiti_preview_transform(x, y):
            tr = self.graffiti_preview_transform
            d = max(tr[2] - tr[0] + 2, tr[3] - tr[1] + 2)
            return [(x - tr[0] + 1) * self.options.graffiti_preview_size / d, self.options.graffiti_preview_size - (y - tr[1] + 1) * self.options.graffiti_preview_size / d]

        def draw_graffiti_segment(layer, start, end, feed, color=(0, 255, 0, 40), emmit=1000):
            # Emit = dots per second
            l = math.sqrt(sum([(start[i] - end[i]) ** 2 for i in range(len(start))]))
            time_ = l / feed
            c1 = self.graffiti_reference_points[layer][0][0]
            c2 = self.graffiti_reference_points[layer][1][0]
            d = math.sqrt((c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2)
            if d == 0:
                raise ValueError("Error! Reference points should not be the same!")
            for i in range(int(time_ * emmit + 1)):
                t = i / (time_ * emmit)
                r1 = start[0] * (1 - t) + end[0] * t
                r2 = start[1] * (1 - t) + end[1] * t
                a = (r1 ** 2 - r2 ** 2 + d ** 2) / (2 * d)
                h = math.sqrt(r1 ** 2 - a ** 2)
                xa = c1[0] + a * (c2[0] - c1[0]) / d
                ya = c1[1] + a * (c2[1] - c1[1]) / d

                x1 = xa + h * (c2[1] - c1[1]) / d
                x2 = xa - h * (c2[1] - c1[1]) / d
                y1 = ya - h * (c2[0] - c1[0]) / d
                y2 = ya + h * (c2[0] - c1[0]) / d

                x = x1 if y1 < y2 else x2
                y = min(y1, y2)
                x, y = graffiti_preview_transform(x, y)
                graffiti_preview_draw_point(x, y, color)

        def create_connector(p1, p2, t1, t2):
            P1 = P(p1)
            P2 = P(p2)
            N1 = P(rotate_ccw(t1))
            N2 = P(rotate_ccw(t2))
            r = self.options.graffiti_min_radius
            C1 = P1 + N1 * r
            C2 = P2 + N2 * r
            # Get closest possible centers of arcs, also we define that arcs are both ccw or both not.
            dc, N1, N2, m = (
                (
                    (((P2 - N1 * r) - (P1 - N2 * r)).l2(), -N1, -N2, 1)
                    if vectors_ccw(t1, t2) else
                    (((P2 + N1 * r) - (P1 + N2 * r)).l2(), N1, N2, -1)
                )
                if vectors_ccw((P1 - C1).to_list(), t1) == vectors_ccw((P2 - C2).to_list(), t2) else
                (
                    (((P2 + N1 * r) - (P1 - N2 * r)).l2(), N1, -N2, 1)
                    if vectors_ccw(t1, t2) else
                    (((P2 - N1 * r) - (P1 + N2 * r)).l2(), -N1, N2, 1)
                )
            )
            dc = math.sqrt(dc)
            C1 = P1 + N1 * r
            C2 = P2 + N2 * r
            Dc = C2 - C1

            if dc == 0:
                # can be joined by one arc
                return csp_from_arc(p1, p2, C1.to_list(), r, t1)

            cos = Dc.x / dc
            sin = Dc.y / dc

            p1_end = [C1.x - r * sin * m, C1.y + r * cos * m]
            p2_st = [C2.x - r * sin * m, C2.y + r * cos * m]
            if point_to_point_d2(p1, p1_end) < 0.0001 and point_to_point_d2(p2, p2_st) < 0.0001:
                return [[p1, p1, p1], [p2, p2, p2]]

            arc1 = csp_from_arc(p1, p1_end, C1.to_list(), r, t1)
            arc2 = csp_from_arc(p2_st, p2, C2.to_list(), r, [cos, sin])
            return csp_concat_subpaths(arc1, arc2)

        if not self.check_dir():
            return
        if self.selected_paths == {} and self.options.auto_select_paths:
            paths = self.paths
            self.error("No paths are selected! Trying to work on all available paths.")
        else:
            paths = self.selected_paths
        self.tool = []
        gcode = """(Header)
(Generated by gcodetools from Inkscape.)
(Using graffiti extension.)
(Header end.)"""

        minx = float("inf")
        miny = float("inf")
        maxx = float("-inf")
        maxy = float("-inf")
        # Get all reference points and path's bounds to make preview

        for layer in self.layers:
            if layer in paths:
                # Set reference points
                if layer not in self.graffiti_reference_points:
                    reference_points = None
                    for i in range(self.layers.index(layer), -1, -1):
                        if self.layers[i] in self.graffiti_reference_points:
                            reference_points = self.graffiti_reference_points[self.layers[i]]
                            self.graffiti_reference_points[layer] = self.graffiti_reference_points[self.layers[i]]
                            break
                    if reference_points is None:
                        self.error('There are no graffiti reference points for layer {}'.format(layer), "error")

                # Transform reference points
                for i in range(len(self.graffiti_reference_points[layer])):
                    self.graffiti_reference_points[layer][i][0] = self.transform(self.graffiti_reference_points[layer][i][0], layer)
                    point = self.graffiti_reference_points[layer][i]
                    gcode += "(Reference point {:f};{:f} for {} axis)\n".format(point[0][0], point[0][1], point[1])

                if self.options.graffiti_create_preview:
                    for point in self.graffiti_reference_points[layer]:
                        minx = min(minx, point[0][0])
                        miny = min(miny, point[0][1])
                        maxx = max(maxx, point[0][0])
                        maxy = max(maxy, point[0][1])
                    for path in paths[layer]:
                        csp = path.path.to_superpath()
                        csp = self.apply_transforms(path, csp)
                        csp = self.transform_csp(csp, layer)
                        bounds = csp_simple_bound(csp)
                        minx = min(minx, bounds[0])
                        miny = min(miny, bounds[1])
                        maxx = max(maxx, bounds[2])
                        maxy = max(maxy, bounds[3])

        if self.options.graffiti_create_preview:
            self.graffiti_preview = list([[255] * (4 * self.options.graffiti_preview_size) for _ in range(self.options.graffiti_preview_size)])
            self.graffiti_preview_transform = [minx, miny, maxx, maxy]

        for layer in self.layers:
            if layer in paths:

                r = re.match("\\s*\\(\\s*([0-9\\-,.]+)\\s*;\\s*([0-9\\-,.]+)\\s*\\)\\s*", self.options.graffiti_start_pos)
                if r:
                    start_point = [float(r.group(1)), float(r.group(2))]
                else:
                    start_point = [0., 0.]
                last_sp1 = [[start_point[0], start_point[1] - 10] for _ in range(3)]
                last_sp2 = [start_point for _ in range(3)]

                self.set_tool(layer)
                self.tool = self.tools[layer][0]
                # Change tool every layer. (Probably layer = color so it'll be
                # better to change it even if the tool has not been changed)
                gcode += ("(Change tool to {})\n".format(re.sub("\"'\\(\\)\\\\", " ", self.tool["name"]))) + self.tool["tool change gcode"] + "\n"

                subpaths = []
                for path in paths[layer]:
                    # Rebuild the paths to polyline.
                    csp = path.path.to_superpath()
                    csp = self.apply_transforms(path, csp)
                    csp = self.transform_csp(csp, layer)
                    subpaths += csp
                polylines = []
                while len(subpaths) > 0:
                    i = min([(point_to_point_d2(last_sp2[1], subpaths[i][0][1]), i) for i in range(len(subpaths))])[1]
                    subpath = subpaths[i][:]
                    del subpaths[i]
                    polylines += [
                        ['connector', create_connector(
                                last_sp2[1],
                                subpath[0][1],
                                csp_normalized_slope(last_sp1, last_sp2, 1.),
                                csp_normalized_slope(subpath[0], subpath[1], 0.),
                        )]
                    ]
                    polyline = []
                    spl = None

                    #  remove zerro length segments
                    i = 0
                    while i < len(subpath) - 1:
                        if cspseglength(subpath[i], subpath[i + 1]) < 0.00000001:
                            subpath[i][2] = subpath[i + 1][2]
                            del subpath[i + 1]
                        else:
                            i += 1

                    for sp1, sp2 in zip(subpath, subpath[1:]):
                        if spl is not None and abs(cross(csp_normalized_slope(spl, sp1, 1.), csp_normalized_slope(sp1, sp2, 0.))) > 0.1:  # TODO add coefficient into inx
                            # We've got sharp angle at sp1.
                            polyline += [sp1]
                            polylines += [['draw', polyline[:]]]
                            polylines += [
                                ['connector', create_connector(
                                        sp1[1],
                                        sp1[1],
                                        csp_normalized_slope(spl, sp1, 1.),
                                        csp_normalized_slope(sp1, sp2, 0.),
                                )]
                            ]
                            polyline = []
                        # max_segment_length
                        polyline += [sp1]
                        print_(polyline)
                        print_(sp1)

                        spl = sp1
                    polyline += [sp2]
                    polylines += [['draw', polyline[:]]]

                    last_sp1 = sp1
                    last_sp2 = sp2

                # Add return to start_point
                if not polylines:
                    continue
                polylines += [["connect1", [[polylines[-1][1][-1][1] for _ in range(3)], [start_point for _ in range(3)]]]]

                # Make polylines from polylines. They are still csp.
                for i in range(len(polylines)):
                    polyline = []
                    l = 0
                    print_("polylines", polylines)
                    print_(polylines[i])
                    for sp1, sp2 in zip(polylines[i][1], polylines[i][1][1:]):
                        print_(sp1, sp2)
                        l = cspseglength(sp1, sp2)
                        if l > 0.00000001:
                            polyline += [sp1[1]]
                            parts = int(math.ceil(l / self.options.graffiti_max_seg_length))
                            for j in range(1, parts):
                                polyline += [csp_at_length(sp1, sp2, float(j) / parts)]
                    if l > 0.00000001:
                        polyline += [sp2[1]]
                    print_(i)
                    polylines[i][1] = polyline

                t = 0
                last_state = None
                for polyline_ in polylines:
                    polyline = polyline_[1]
                    # Draw linearization
                    if self.options.graffiti_create_linearization_preview:
                        t += 1
                        csp = [[polyline[i], polyline[i], polyline[i]] for i in range(len(polyline))]
                        draw_csp(self.transform_csp([csp], layer, reverse=True))

                    # Export polyline to gcode
                    # we are making transform from XYZA coordinates to R1...Rn
                    # where R1...Rn are radius vectors from graffiti reference points
                    # to current (x,y) point. Also we need to assign custom feed rate
                    # for each segment. And we'll use only G01 gcode.
                    last_real_pos, g = get_gcode_coordinates(polyline[0], layer)
                    last_pos = polyline[0]
                    if polyline_[0] == "draw" and last_state != "draw":
                        gcode += self.tool['gcode before path'] + "\n"
                    for point in polyline:
                        real_pos, g = get_gcode_coordinates(point, layer)
                        real_l = sum([(real_pos[i] - last_real_pos[i]) ** 2 for i in range(len(last_real_pos))])
                        l = (last_pos[0] - point[0]) ** 2 + (last_pos[1] - point[1]) ** 2
                        if l != 0:
                            feed = self.tool['feed'] * math.sqrt(real_l / l)
                            gcode += "G01 " + g + " F {:f}\n".format(feed)
                            if self.options.graffiti_create_preview:
                                draw_graffiti_segment(layer, real_pos, last_real_pos, feed, color=(0, 0, 255, 200) if polyline_[0] == "draw" else (255, 0, 0, 200), emmit=self.options.graffiti_preview_emmit)
                            last_real_pos = real_pos
                            last_pos = point[:]
                    if polyline_[0] == "draw" and last_state != "draw":
                        gcode += self.tool['gcode after path'] + "\n"
                    last_state = polyline_[0]
        self.export_gcode(gcode, no_headers=True)
        if self.options.graffiti_create_preview:
            try:
                # Draw reference points
                for layer in self.graffiti_reference_points:
                    for point in self.graffiti_reference_points[layer]:
                        x, y = graffiti_preview_transform(point[0][0], point[0][1])
                        graffiti_preview_draw_point(x, y, (0, 255, 0, 255), radius=5)

                import png
                writer = png.Writer(width=self.options.graffiti_preview_size, height=self.options.graffiti_preview_size, size=None, greyscale=False, alpha=True, bitdepth=8, palette=None, transparent=None, background=None, gamma=None, compression=None, interlace=False, bytes_per_sample=None, planes=None, colormap=None, maxval=None, chunk_limit=1048576)
                with open(os.path.join(self.options.directory, self.options.file + ".png"), 'wb') as f:
                    writer.write(f, self.graffiti_preview)

            except:
                self.error("Png module have not been found!")

    def get_info_plus(self):
        """Like get_info(), but checks some of the values"""
        self.get_info()
        if self.orientation_points == {}:
            self.error("Orientation points have not been defined! A default set of orientation points has been automatically added.")
            self.tab_orientation(self.layers[min(1, len(self.layers) - 1)])
            self.get_info()
        if self.tools == {}:
            self.error("Cutting tool has not been defined! A default tool has been automatically added.")
            self.options.tools_library_type = "default"
            self.tab_tools_library(self.layers[min(1, len(self.layers) - 1)])
            self.get_info()

    ################################################################################
    #
    # Effect
    #
    # Main function of Gcodetools class
    #
    ################################################################################
    def effect(self):
        start_time = time.time()
        global options
        options = self.options
        options.self = self
        options.doc_root = self.document.getroot()

        # define print_ function
        global print_
        if self.options.log_create_log:
            try:
                if os.path.isfile(self.options.log_filename):
                    os.remove(self.options.log_filename)
                with open(self.options.log_filename, "a") as fhl:
                    fhl.write("""Gcodetools log file.
Started at {}.
{}
""".format(time.strftime("%d.%m.%Y %H:%M:%S"), options.log_filename))
            except:
                print_ = lambda *x: None
        else:
            print_ = lambda *x: None

        # This automatically calls any `tab_{tab_name_in_inx}` which in this
        # extension is A LOT of different functions. So see all method prefixed
        # with tab_ to find out what's supported here.
        self.options.active_tab()

        print_("------------------------------------------")
        print_("Done in {:f} seconds".format(time.time() - start_time))
        print_("End at {}.".format(time.strftime("%d.%m.%Y %H:%M:%S")))


    def tab_offset(self):
        self.get_info()
        if self.options.offset_just_get_distance:
            for layer in self.selected_paths:
                if len(self.selected_paths[layer]) == 2:
                    csp1 = self.selected_paths[layer][0].path.to_superpath()
                    csp2 = self.selected_paths[layer][1].path.to_superpath()
                    dist = csp_to_csp_distance(csp1, csp2)
                    print_(dist)
                    draw_pointer(list(csp_at_t(csp1[dist[1]][dist[2] - 1], csp1[dist[1]][dist[2]], dist[3]))
                                 + list(csp_at_t(csp2[dist[4]][dist[5] - 1], csp2[dist[4]][dist[5]], dist[6])), "red", "line", comment=math.sqrt(dist[0]))
            return
        if self.options.offset_step == 0:
            self.options.offset_step = self.options.offset_radius
        if self.options.offset_step * self.options.offset_radius < 0:
            self.options.offset_step *= -1
        time_ = time.time()
        offsets_count = 0
        for layer in self.selected_paths:
            for path in self.selected_paths[layer]:

                offset = self.options.offset_step / 2
                while abs(offset) <= abs(self.options.offset_radius):
                    offset_ = csp_offset(path.path.to_superpath(), offset)
                    offsets_count += 1
                    if offset_:
                        for iii in offset_:
                            draw_csp([iii], width=1)
                    else:
                        print_("------------Reached empty offset at radius {}".format(offset))
                        break
                    offset += self.options.offset_step
        print_()
        print_("-----------------------------------------------------------------------------------")
        print_("-----------------------------------------------------------------------------------")
        print_("-----------------------------------------------------------------------------------")
        print_()
        print_("Done in {}".format(time.time() - time_))
        print_("Total offsets count {}".format(offsets_count))


if __name__ == '__main__':
    Gcodetools().run()
