#!/usr/bin/env python3
"""
Modified by dapperfu, 2020.
Modified by Jay Johnson 2015, J Tech Photonics, Inc., jtechphotonics.com
modified by Adam Polak 2014, polakiumengineering.org

based on Copyright (C) 2009 Nick Drobchenko, nick@cnc-club.ru
based on gcode.py (C) 2007 hugomatic...
based on addnodes.py (C) 2005,2007 Aaron Spike, aaron@ekips.org
based on dots.py (C) 2005 Aaron Spike, aaron@ekips.org
based on interp.py (C) 2005 Aaron Spike, aaron@ekips.org
based on bezmisc.py (C) 2005 Aaron Spike, aaron@ekips.org
based on cubicsuperpath.py (C) 2005 Aaron Spike, aaron@ekips.org

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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""
import cmath
import copy
import gettext
import math
import os
import re
import sys
import time

import bezmisc
import cubicsuperpath
import inkex
import numpy
import numpy as np
import simpletransform
from lxml import etree

from Point import Point
from Polygon import Polygon

_ = gettext.gettext


def print_(*args, **kwargs):
    pass


def bezierslopeatt(xxx_todo_changeme, t):
    ((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3)) = xxx_todo_changeme
    ax, ay, bx, by, cx, cy, x0, y0 = bezmisc.bezierparameterize(
        ((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3))
    )
    dx = 3 * ax * (t ** 2) + 2 * bx * t + cx
    dy = 3 * ay * (t ** 2) + 2 * by * t + cy
    if dx == dy == 0:
        dx = 6 * ax * t + 2 * bx
        dy = 6 * ay * t + 2 * by
        if dx == dy == 0:
            dx = 6 * ax
            dy = 6 * ay
            if dx == dy == 0:
                print_(
                    "Slope error x = %s*t^3+%s*t^2+%s*t+%s, y = %s*t^3+%s*t^2+%s*t+%s,  t = %s, dx==dy==0"
                    % (ax, bx, cx, dx, ay, by, cy, dy, t)
                )
                print_(((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3)))
                dx, dy = 1, 1

    return dx, dy


bezmisc.bezierslopeatt = bezierslopeatt


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
###
##########################################################################

straight_tolerance = 0.0001
straight_distance_tolerance = 0.0001
options = {}

intersection_recursion_depth = 10
intersection_tolerance = 0.00001

styles = {
    "loft_style": {
        "main curve": inkex.Style(
            {
                "stroke": "#88f",
                "fill": "none",
                "stroke-width": "1",
                "marker-end": "url(#Arrow2Mend)",
            }
        ),
    },
    "biarc_style": {
        "biarc0": inkex.Style(
            {
                "stroke": "#88f",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "biarc1": inkex.Style(
            {
                "stroke": "#8f8",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "line": inkex.Style(
            {
                "stroke": "#f88",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "area": inkex.Style(
            {
                "stroke": "#777",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.1",
            }
        ),
    },
    "biarc_style_dark": {
        "biarc0": inkex.Style(
            {
                "stroke": "#33a",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "biarc1": inkex.Style(
            {
                "stroke": "#3a3",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "line": inkex.Style(
            {
                "stroke": "#a33",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "area": inkex.Style(
            {
                "stroke": "#222",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.3",
            }
        ),
    },
    "biarc_style_dark_area": {
        "biarc0": inkex.Style(
            {
                "stroke": "#33a",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.1",
            }
        ),
        "biarc1": inkex.Style(
            {
                "stroke": "#3a3",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.1",
            }
        ),
        "line": inkex.Style(
            {
                "stroke": "#a33",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.1",
            }
        ),
        "area": inkex.Style(
            {
                "stroke": "#222",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.3",
            }
        ),
    },
    "biarc_style_i": {
        "biarc0": inkex.Style(
            {
                "stroke": "#880",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "biarc1": inkex.Style(
            {
                "stroke": "#808",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "line": inkex.Style(
            {
                "stroke": "#088",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "area": inkex.Style(
            {
                "stroke": "#999",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.3",
            }
        ),
    },
    "biarc_style_dark_i": {
        "biarc0": inkex.Style(
            {
                "stroke": "#dd5",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "biarc1": inkex.Style(
            {
                "stroke": "#d5d",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "line": inkex.Style(
            {
                "stroke": "#5dd",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "1",
            }
        ),
        "area": inkex.Style(
            {
                "stroke": "#aaa",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.3",
            }
        ),
    },
    "biarc_style_lathe_feed": {
        "biarc0": inkex.Style(
            {
                "stroke": "#07f",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": ".4",
            }
        ),
        "biarc1": inkex.Style(
            {
                "stroke": "#0f7",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": ".4",
            }
        ),
        "line": inkex.Style(
            {
                "stroke": "#f44",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": ".4",
            }
        ),
        "area": inkex.Style(
            {
                "stroke": "#aaa",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.3",
            }
        ),
    },
    "biarc_style_lathe_passing feed": {
        "biarc0": inkex.Style(
            {
                "stroke": "#07f",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": ".4",
            }
        ),
        "biarc1": inkex.Style(
            {
                "stroke": "#0f7",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": ".4",
            }
        ),
        "line": inkex.Style(
            {
                "stroke": "#f44",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": ".4",
            }
        ),
        "area": inkex.Style(
            {
                "stroke": "#aaa",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.3",
            }
        ),
    },
    "biarc_style_lathe_fine feed": {
        "biarc0": inkex.Style(
            {
                "stroke": "#7f0",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": ".4",
            }
        ),
        "biarc1": inkex.Style(
            {
                "stroke": "#f70",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": ".4",
            }
        ),
        "line": inkex.Style(
            {
                "stroke": "#744",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": ".4",
            }
        ),
        "area": inkex.Style(
            {
                "stroke": "#aaa",
                "fill": "none",
                "marker-end": "url(#DrawCurveMarker)",
                "stroke-width": "0.3",
            }
        ),
    },
    "area artefact": inkex.Style(
        {"stroke": "#ff0000", "fill": "#ffff00", "stroke-width": "1"}
    ),
    "area artefact arrow": inkex.Style(
        {"stroke": "#ff0000", "fill": "#ffff00", "stroke-width": "1"}
    ),
    "dxf_points": inkex.Style({"stroke": "#ff0000", "fill": "#ff0000"}),
}


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


def csp_split(sp1, sp2, t=0.5):
    [x1, y1], [x2, y2], [x3, y3], [x4, y4] = sp1[1], sp1[2], sp2[0], 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]],
    )


############################################################################
# csp_segments_intersection(sp1,sp2,sp3,sp4)
###
# Returns array containig all intersections between two segmets 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_at_t(sp1, sp2, t):
    ax, bx, cx, dx = sp1[1][0], sp1[2][0], sp2[0][0], sp2[1][0]
    ay, by, cy, dy = sp1[1][1], sp1[2][1], sp2[0][1], sp2[1][1]

    x1, y1 = ax + (bx - ax) * t, ay + (by - ay) * t
    x2, y2 = bx + (cx - bx) * t, by + (cy - by) * t
    x3, y3 = cx + (dx - cx) * t, cy + (dy - cy) * t

    x4, y4 = x1 + (x2 - x1) * t, y1 + (y2 - y1) * t
    x5, y5 = x2 + (x3 - x2) * t, y2 + (y3 - y2) * t

    x, y = x4 + (x5 - x4) * t, y4 + (y5 - y4) * t
    return [x, y]


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


def point_to_arc_distance(p, arc):
    # Distance calculattion from point to arc
    P0, P2, c, a = arc
    dist = None
    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 - (2 * np.pi)
            else:
                alpha = (2 * np.pi) + 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, d2 = (p - P0).mag(), (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):
    n, i = 10, 0
    d, d1, dl = (0, [0, 0]), (0, [0, 0]), 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]


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


def atan2(*arg):
    if len(arg) == 1 and (
        isinstance(arg[0], type([0.0, 0.0])) or isinstance(arg[0], type((0.0, 0.0)))
    ):
        return (math.pi / 2 - math.atan2(arg[0][0], arg[0][1])) % (2 * np.pi)
    elif len(arg) == 2:

        return (math.pi / 2 - math.atan2(arg[0], arg[1])) % (2 * np.pi)
    else:
        raise ValueError("Bad argumets for atan! (%s)" % arg)


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


##########################################################################
###
# 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, l2 = cspseglength(sp1, sp2), 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, P4 = P(sp1[1]), P(sp2[1])
    TS, TE, v = (P(sp1[2]) - P0), -(P(sp2[0]) - P4), P0 - P4
    tsa, tea, va = TS.angle(), TE.angle(), v.angle()
    if (
        TE.mag() < straight_distance_tolerance
        and TS.mag() < straight_distance_tolerance
    ):
        # Both tangents are zerro - 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, TE = TS.unit(), 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 tollerance

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

    c, b, a = v * v, 2 * v * (r * TS + TE), 2 * r * (TS * TE - 1)
    if v.mag() == 0:
        return biarc_split(sp1, sp2, z1, z2, depth)
    asmall, bsmall, csmall = abs(a) < 10 ** -10, abs(b) < 10 ** -10, 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 ** 0.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, p1a, p2a = (
            (P0 - R).angle() % (2 * math.pi),
            (P1 - R).angle() % (2 * math.pi),
            (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() < 0.1:
            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 > 1 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)
            )
        return [
            [sp1[1], "arc", [R1.x, R1.y], a1, [P2.x, P2.y], [z1, zm]],
            [[P2.x, P2.y], "arc", [R2.x, R2.y], a2, [P4.x, P4.y], [zm, z2]],
        ]


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


class laser_gcode(inkex.Effect):
    def export_gcode(self, gcode):
        gcode_pass = gcode
        for _ in range(1, self.options.passes):
            gcode += f"G91\nG1Z-{self.options.pass_depth:.4f}\nG90\n{gcode_pass}"
        with open(self.out_file, "w") as f:
            f.write(f"{self.options.laser_off_command}\n")
            f.write(f"G0F{self.options.travel_speed}\n")
            f.write(f"G1F{self.options.laser_speed}\n")
            f.write(f"{gcode}\n")

    def __init__(self):
        inkex.Effect.__init__(self)
        self.arg_parser.add_argument(
            "--output_path", type=str, dest="output_path", help="File name",
        )
        self.arg_parser.add_argument(
            "--tab", type=str, dest="tab", default="", help="Tab",
        )
        self.arg_parser.add_argument(
            "--add_numeric_suffix_to_filename",
            type=inkex.Boolean,
            dest="add_numeric_suffix_to_filename",
            default=False,
            help="Add numeric suffix to file name",
        )
        self.arg_parser.add_argument(
            "--laser_command",
            type=str,
            dest="laser_command",
            default="M03",
            help="Laser gcode command",
        )
        self.arg_parser.add_argument(
            "--laser_off_command",
            type=str,
            dest="laser_off_command",
            default="M05",
            help="Laser gcode end command",
        )
        self.arg_parser.add_argument(
            "--laser_speed",
            type=int,
            dest="laser_speed",
            default="100",
            help="Laser speed (mm/min)",
        )
        self.arg_parser.add_argument(
            "--travel_speed",
            type=float,
            dest="travel_speed",
            default="3000",
            help="Travel speed (mm/min)",
        )
        self.arg_parser.add_argument(
            "--laser_power",
            type=int,
            dest="laser_power",
            default="255",
            help="S# [255 for full power]",
        )
        self.arg_parser.add_argument(
            "--passes", type=int, dest="passes", default="1", help="Quantity of passes",
        )
        self.arg_parser.add_argument(
            "--pass_depth",
            type=str,
            dest="pass_depth",
            default="1",
            help="Depth of laser cut",
        )
        self.arg_parser.add_argument(
            "--suppress_all_messages",
            type=inkex.Boolean,
            dest="suppress_all_messages",
            default=True,
            help="Hide messages during g-code generation",
        )
        self.arg_parser.add_argument(
            "--create_log",
            type=inkex.Boolean,
            dest="log_create_log",
            default=True,
            help="Create log files",
        )
        self.arg_parser.add_argument(
            "--log_filename",
            type=str,
            dest="log_filename",
            default="laser2.log",
            help="Create log files",
        )
        self.arg_parser.add_argument(
            "--engraving-draw-calculation-paths",
            type=inkex.Boolean,
            dest="engraving_draw_calculation_paths",
            default=False,
            help="Draw additional graphics to debug engraving path",
        )
        self.arg_parser.add_argument(
            "--unit",
            type=str,
            dest="unit",
            default="G21 (All units in mm)",
            help="Units either mm or inches",
        )
        self.arg_parser.add_argument(
            "--active_tab",
            type=str,
            dest="active_tab",
            default="",
            help="Defines which tab is active",
        )
        self.arg_parser.add_argument(
            "--biarc_max_split_depth",
            type=int,
            dest="biarc_max_split_depth",
            default="4",
            help="Defines maximum depth of splitting while approximating using biarcs.",
        )

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

        # Sort to reduce Rapid distance
        """
        k = 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), dist)
            keys += [k[dist[1]]]
            del k[dist[1]]
        """
        for subpath in p:
            curve.append([[[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)]
                if w is None:
                    curve.append(biarc(sp1, sp2, 0, 0))
                else:
                   curve.append(biarc(sp1, sp2, -f(w[k][i - 1]), -f(w[k][i])))

            curve.append([[[subpath[-1][1][0], subpath[-1][1][1]], "end", 0, 0]])
        return curve

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

        self.get_defs()
        # Add marker to defs if it doesnot exists
        if "DrawCurveMarker" not in self.defs:
            defs = etree.SubElement(self.document.getroot(), inkex.addNS("defs", "svg"))
            marker = etree.SubElement(
                defs,
                inkex.addNS("marker", "svg"),
                {
                    "id": "DrawCurveMarker",
                    "orient": "auto",
                    "refX": "-8",
                    "refY": "-2.41063",
                    "style": "overflow:visible",
                },
            )
            etree.SubElement(
                marker,
                inkex.addNS("path", "svg"),
                {
                    "d": "m -6.55552,-2.41063 0,0 L -13.11104,0 c 1.0473,-1.42323 1.04126,-3.37047 0,-4.82126",
                    "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;",
                },
            )
        if "DrawCurveMarker_r" not in self.defs:
            defs = etree.SubElement(self.document.getroot(), inkex.addNS("defs", "svg"))
            marker = etree.SubElement(
                defs,
                inkex.addNS("marker", "svg"),
                {
                    "id": "DrawCurveMarker_r",
                    "orient": "auto",
                    "refX": "8",
                    "refY": "-2.41063",
                    "style": "overflow:visible",
                },
            )
            etree.SubElement(
                marker,
                inkex.addNS("path", "svg"),
                {
                    "d": "m 6.55552,-2.41063 0,0 L 13.11104,0 c -1.0473,-1.42323 -1.04126,-3.37047 0,-4.82126",
                    "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;",
                },
            )
        for i in [0, 1]:
            style[f"biarc{i:d}_r"] = dict(inkex.Style.parse_str(f"biarc{i:d}_r"))
            style[f"biarc{i:d}_r"]["marker-start"] = "url(#DrawCurveMarker_r)"
            try:
                del style[f"biarc{i:d}_r"]["marker-end"]
            except KeyError:
                pass
            except:
                raise
            style[f"biarc{i:d}_r"] = inkex.Style(style[f"biarc{i:d}_r"])

        if group is None:
            group = etree.SubElement(
                self.layers[min(1, len(self.layers) - 1)],
                inkex.addNS("g", "svg"),
                {"gcodetools": "Preview group"},
            )
        s, arcn = "", 0

        a, b, c = [0.0, 0.0], [1.0, 0.0], [0.0, 1.0]
        k = (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])
        a, b, c = (
            self.transform(a, layer, True),
            self.transform(b, layer, True),
            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], si[2] = (
                self.transform(si[0], layer, True),
                (
                    self.transform(si[2], layer, True)
                    if isinstance(si[2], type([])) and len(si[2]) == 2
                    else si[2]
                ),
            )

            if s != "":
                if s[1] == "line":
                    etree.SubElement(
                        group,
                        inkex.addNS("path", "svg"),
                        {
                            "style": style["line"],
                            "d": "M {},{} L {},{}".format(
                                s[0][0], s[0][1], si[0][0], si[0][1]
                            ),
                            "gcodetools": "Preview",
                        },
                    )
                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()) % (
                        2 * np.pi
                    )  # s[3]
                    if s[3] * a < 0:
                        if a > 0:
                            a = a - (2 * np.pi)
                        else:
                            a = (2 * np.pi) + 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
                    )
                    st = style["biarc%s" % (arcn % 2)][:]
                    if a > 0:
                        a_end = a_st + a
                        st = style["biarc%s" % (arcn % 2)]
                    else:
                        a_end = a_st * 1
                        a_st = a_st + a
                        st = style["biarc%s_r" % (arcn % 2)]
                    etree.SubElement(
                        group,
                        inkex.addNS("path", "svg"),
                        {
                            "style": st,
                            inkex.addNS("cx", "sodipodi"): str(c[0]),
                            inkex.addNS("cy", "sodipodi"): str(c[1]),
                            inkex.addNS("rx", "sodipodi"): str(r),
                            inkex.addNS("ry", "sodipodi"): str(r),
                            inkex.addNS("start", "sodipodi"): str(a_st),
                            inkex.addNS("end", "sodipodi"): str(a_end),
                            inkex.addNS("open", "sodipodi"): "true",
                            inkex.addNS("type", "sodipodi"): "arc",
                            "gcodetools": "Preview",
                        },
                    )
            s = si

    def check_dir(self):
        self.options.directory = os.path.dirname(self.options.output_path)
        # Error out if the output directory does not
        if not os.path.isdir(self.options.directory):
            self.error(
                _(
                    "Directory does not exist! Please specify existing directory at options tab!"
                ),
                "error",
            )
            return False

        if self.options.add_numeric_suffix_to_filename:
            self.options.file, self.options.extension = os.path.splitext(
                os.path.basename(self.options.output_path)
            )
            import glob

            existing_files = glob.glob(
                os.path.join(
                    self.options.directory,
                    self.options.file + "*" + self.options.extension,
                )
            )
            max_n = 0
            for existing_file in existing_files:
                existing_base, _ = os.path.splitext(os.path.basename(existing_file))
                existing_file_count = int(existing_base.split("_")[1])
                max_n = max(max_n, existing_file_count)
            self.options.file = (
                f"{self.options.file}_0{max_n+1:04d}{self.options.extension}"
            )
            self.out_file = os.path.join(self.options.directory, self.options.file)

        else:
            self.out_file = os.path.dirname(self.options.output_path)

        print_("Testing writing rights on '%s'" % (self.out_file))
        try:
            with open(self.out_file, "w") as f:
                pass
        except BaseException:
            self.error(
                _("Can not write to specified file!\n\t'%s'" % (self.out_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):
        tool = self.tools
        print_("Tool in g-code generator: " + str(tool))

        def cmd_arguments(input_points):
            arguments = ["X", "Y", "Z", "I", "J", "K"]
            # Pad input_points array with "None"
            input_points = [
                input_points[i] if i < len(input_points) else None
                for i in range(len(arguments))
            ]
            if input_points[5] is not None and np.isclose(input_points[5], 0):
                input_points[5] = None
            gcode = ""
            for input_point, argument in zip(input_points, arguments):
                if input_point is not None:
                    gcode += f"{argument}{input_point:.4f}"
            return gcode

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

        try:
            self.last_used_tool is None
        except BaseException:
            self.last_used_tool = None
        print_("working on curve")
        print_("Curve: " + str(curve))
        gcode_cmds = list()
        gcode_cmds.append(tool["gcode before path"])
        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, si = curve[i - 1], curve[i]
            if s[1] == "move":
                gcode_cmds.append(
                    f"G0{cmd_arguments(si[0])}F{self.options.travel_speed:.2f}"
                )
            elif s[1] == "end":
                pass
            elif s[1] == "line":
                gcode_cmds.append(
                    f"G1{cmd_arguments(si[0])}F{self.options.laser_speed:.2f}"
                )
            elif s[1] == "arc":
                r = [(s[2][0] - s[0][0]), (s[2][1] - s[0][1])]
                if (r[0] ** 2 + r[1] ** 2) > 0.1:
                    r1, r2 = (Point(s[0]) - Point(s[2])), (Point(si[0]) - Point(s[2]))
                    if s[3] < 3:
                        arc_cmd = "G2"
                    else:
                        arc_cmd = "G3"
                    if abs(r1.mag() - r2.mag()) < 0.001:
                        arc_args = cmd_arguments(
                            si[0] + [None, (s[2][0] - s[0][0]), (s[2][1] - s[0][1])]
                        )
                        gcode_cmds.append(
                            f"{arc_cmd}{arc_args}F{self.options.laser_speed:.2f}"
                        )
                    else:
                        r = (r1.mag() + r2.mag()) / 2
                        arc_args = cmd_arguments(si[0])
                        gcode_cmds.append(
                            f"{arc_cmd}{arc_args}R{r:.4f}F{self.options.laser_speed:.2f}"
                        )
                else:
                    gcode_cmds.append(
                        f"G1{cmd_arguments(si[0])}F{self.options.laser_speed:.2f}"
                    )

        if si[1] == "end":
            gcode_cmds.append(tool["gcode after path"])
        g = "\n".join(gcode_cmds)
        return g

    def get_transforms(self, g):
        root = self.document.getroot()
        # Start with the identity transform.
        trans = inkex.Transform(None)
        while g != root:
            if "transform" in g.keys():
                t = inkex.Transform(g.get("transform"))
                trans *= t
            g = g.getparent()
        return trans

    def apply_transforms(self, g, csp):
        trans = self.get_transforms(g)
        return csp.transform(trans)

    def transform(self, source_point, layer, reverse=False):
        if layer is None:
            layer = (
                self.current_layer
                if self.current_layer is not None
                else self.document.getroot()
            )
        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

            print_(str(self.layers))
            print_(str("I: " + str(i)))
            print_("Transform: " + str(self.layers[i]))
            if self.layers[i] not in self.orientation_points:
                self.error(
                    _(
                        "Orientation points for '%s' layer have not been found! Please add orientation points using Orientation tab!"
                    )
                    % layer.get(inkex.addNS("label", "inkscape")),
                    "no_orientation_points",
                )
            elif self.layers[i] in self.transform_matrix:
                self.transform_matrix[layer] = self.transform_matrix[self.layers[i]]
            else:
                orientation_layer = self.layers[i]
                if len(self.orientation_points[orientation_layer]) > 1:
                    self.error(
                        _(
                            "There are more than one orientation point groups in '%s' layer"
                        )
                        % orientation_layer.get(inkex.addNS("label", "inkscape")),
                        "more_than_one_orientation_point_groups",
                    )
                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 '%s' Orientation points: "
                        % orientation_layer.get(inkex.addNS("label", "inkscape"))
                    )
                    for point in points:
                        print_(point)
                    # Zcoordinates definition taken from Orientatnion point 1
                    # and 2
                    self.Zcoordinates[layer] = [0, 0]
                    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 sould not be the same. If there are three orientation points they should not be in a straight line.)"
                            ),
                            "wrong_orientation_points",
                        )
                else:
                    self.error(
                        _(
                            "Orientation points are wrong! (if there are two orientation points they sould not be the same. If there are three orientation points they should not be in a straight line.)"
                        ),
                        "wrong_orientation_points",
                    )

            self.transform_matrix_reverse[layer] = numpy.linalg.inv(
                self.transform_matrix[layer]
            ).tolist()
            print_(
                "\n Layer '%s' transformation matrixes:"
                % layer.get(inkex.addNS("label", "inkscape"))
            )
            print_(self.transform_matrix)
            print_(self.transform_matrix_reverse)

            ###self.Zauto_scale[layer]  = math.sqrt( (self.transform_matrix[layer][0][0]**2 + self.transform_matrix[layer][1][1]**2)/2 )
            ### Zautoscale is absolete
            self.Zauto_scale[layer] = 1
            print_(
                "Z automatic scale = %s (computed according orientation points)"
                % self.Zauto_scale[layer]
            )

        x, y = source_point[0], 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 range(len(csp)):
            for j in range(len(csp[i])):
                for k in range(len(csp[i][j])):
                    csp[i][j][k] = self.transform(csp[i][j][k], layer, reverse)
        return csp

    ##########################################################################
    # Errors handling function, notes are just printed into Logfile,
    # warnings are printed into log file and warning message is displayed but
    # extension continues working, errors causes log and execution is halted
    # Notes, warnings adn errors could be assigned to space or comma or dot
    # sepparated strings (case is ignoreg).
    ##########################################################################
    def error(self, s, type_="Warning"):
        notes = "Note "
        warnings = """
                        Warning tools_warning
                        bad_orientation_points_in_some_layers
                        more_than_one_orientation_point_groups
                        more_than_one_tool
                        orientation_have_not_been_defined
                        tool_have_not_been_defined
                        selection_does_not_contain_paths
                        selection_does_not_contain_paths_will_take_all
                        selection_is_empty_will_comupe_drawing
                        selection_contains_objects_that_are_not_paths
                        """
        errors = """
                        Error
                        wrong_orientation_points
                        area_tools_diameter_error
                        no_tool_error
                        active_layer_already_has_tool
                        active_layer_already_has_orientation_points
                    """
        if type_.lower() in re.split("[\\s\n,\\.]+", errors.lower()):
            print_(s)
            inkex.errormsg(s + "\n")
            sys.exit()
        elif type_.lower() in re.split("[\\s\n,\\.]+", warnings.lower()):
            print_(s)
            if not self.options.suppress_all_messages:
                inkex.errormsg(s + "\n")
        elif type_.lower() in re.split("[\\s\n,\\.]+", notes.lower()):
            print_(s)
        else:
            print_(s)
            inkex.errormsg(s)
            sys.exit()

    ##########################################################################
    # Get defs from svg
    ##########################################################################
    def get_defs(self):
        self.defs = {}

        def recursive(g):
            for i in g:
                if i.tag == inkex.addNS("defs", "svg"):
                    for j in i:
                        self.defs[j.get("id")] = i
                if i.tag == inkex.addNS("g", "svg"):
                    recursive(i)

        recursive(self.document.getroot())

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

        def recursive_search(g, layer, selected=False):
            items = g.getchildren()
            items.reverse()
            for item in items:
                # If selected is checked
                if selected:
                    # Add to selected dictionary.
                    self.svg.selected[item.get("id")] = item

                #
                if (
                    item.tag == inkex.addNS("g", "svg")
                    and item.get(inkex.addNS("groupmode", "inkscape")) == "layer"
                ):
                    self.layers.append(item)
                    recursive_search(item, item)
                elif item.get("gcodetools") == "Laser GCode Orientation Group":
                    points = self.get_orientation_points(item)
                    if points is not None:
                        self.orientation_points[layer] = (
                            self.orientation_points[layer] + [points[:]]
                            if layer in self.orientation_points
                            else [points[:]]
                        )
                        print_(
                            "Found orientation points in '{}' layer: {}".format(
                                layer.get(inkex.addNS("label", "inkscape")), points
                            )
                        )
                    else:
                        self.error(
                            _(
                                "Warning! Found bad orientation points in '%s' layer. Resulting Gcode could be corrupt!"
                            )
                            % layer.get(inkex.addNS("label", "inkscape")),
                            "bad_orientation_points_in_some_layers",
                        )
                elif item.tag == inkex.addNS("path", "svg"):
                    if "gcodetools" not in item.keys():
                        self.paths[layer] = (
                            self.paths[layer] + [item]
                            if layer in self.paths
                            else [item]
                        )
                        if item.get("id") in self.svg.selected:
                            self.selected_paths[layer] = (
                                self.selected_paths[layer] + [item]
                                if layer in self.selected_paths
                                else [item]
                            )
                elif item.tag == inkex.addNS("g", "svg"):
                    recursive_search(item, layer, (item.get("id") in self.svg.selected))
                elif item.get("id") in self.svg.selected:
                    self.error(
                        _(
                            "This extension works with Paths and Dynamic Offsets and groups of them only! All other objects will be ignored!\nSolution 1: press Path->Object to path or Shift+Ctrl+C.\nSolution 2: Path->Dynamic offset or Ctrl+J.\nSolution 3: export all contours to PostScript level 2 (File->Save As->.ps) and File->Import this file."
                        ),
                        "selection_contains_objects_that_are_not_paths",
                    )

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

    def get_orientation_points(self, g):
        """ Get the orientation points from the svg (if they exist.)
        """

        # Get the container children.
        items = g.getchildren()
        # Reverse the list.
        items.reverse()
        #
        p2, p3 = [], []
        p = None
        # Loop through each of the child items
        for i in items:
            # If the item is also a container and has the given settings:
            if (
                i.tag == inkex.addNS("g", "svg")
                and i.get("gcodetools") == "Gcodetools orientation point (2 points)"
            ):
                p2 += [i]
            if (
                i.tag == inkex.addNS("g", "svg")
                and 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":
                    point[0] = self.apply_transforms(
                        node, inkex.paths.CubicSuperPath(node.get("d"))
                    )[0][0][1]
                if node.get("gcodetools") == "Gcodetools orientation point text":
                    r = re.match(
                        r"(?i)\s*\(\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*\)\s*",
                        node.text,
                    )
                    point[1] = [float(r.group(1)), float(r.group(2))]
            if point[0] != [] and point[1] != []:
                points.append(point)
        if len(points) == len(p2) == 2 or len(points) == len(p3) == 3:
            return points
        else:
            return None

    ##########################################################################
    ###
    # dxfpoints
    ###
    ##########################################################################
    def dxfpoints(self):
        if self.selected_paths == {}:
            self.error(
                _(
                    "Noting is selected. Please select something to convert to drill point (dxfpoint) or clear point sign."
                ),
                "warning",
            )
        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(r"^\s*.\s*(\S+)", path.get("d"))
                        if r is not None:
                            print_(("got path=", r.group(1)))
                            path.set(
                                "d",
                                "m %s 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"
                                % r.group(1),
                            )
                            path.set("style", styles["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")

    ##########################################################################
    ###
    # Laser
    ###
    ##########################################################################
    def laser(self):
        def get_boundaries(points):
            minx, miny, maxx, maxy = None, None, None, 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 range(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 range(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
            minimal_way_type = None
            for w in ways:
                tpoints = points[:]
                cw = []
                for j in range(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
                    minimal_way_type = w

            return minimal_way

        if self.selected_paths == {}:
            paths = self.paths
            self.error(
                _("No paths are selected! Trying to work on all available paths."),
                "warning",
            )
        else:
            paths = self.selected_paths

        self.check_dir()
        gcode = ""

        """biarc_group = etree.SubElement(
            list(self.selected_paths.keys())[0]
            if len(self.selected_paths.keys()) > 0
            else self.layers[0],
            inkex.addNS("g", "svg"),
        )
        """
        print_(("self.layers=", self.layers))
        print_(("paths=", paths))

        for layer in self.layers:
            if layer in paths:
                print_(("layer", layer))
                p = []
                dxfpoints = []
                for path in paths[layer]:
                    print_(str(layer))
                    if "d" not in path.keys():
                        self.error(
                            _(
                                "Warning: One or more paths dont have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"
                            ),
                            "selection_contains_objects_that_are_not_paths",
                        )
                        continue
                    csp = inkex.paths.CubicSuperPath(path.get("d"))
                    csp = self.apply_transforms(path, csp)
                    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_(f"got dxfpoint (scaled) at ({x:f},{y:f})")
                        dxfpoints += [[x, y]]
                    else:
                        p += csp
                dxfpoints = sort_dxfpoints(dxfpoints)
                layer_curve = self.parse_curve(p, layer)
                gcode += self.generate_gcode(layer_curve, layer, 0)

        self.export_gcode(gcode)

    ##########################################################################
    ###
    # Orientation
    ###
    ##########################################################################
    def orientation(self, layer=None):
        print_("entering orientations")
        if layer is None:
            layer = (
                self.current_layer
                if self.current_layer is not None
                else self.document.getroot()
            )
        if layer in self.orientation_points:
            self.error(
                _(
                    "Active layer already has orientation points! Remove them or select another layer!"
                ),
                "active_layer_already_has_orientation_points",
            )

        # Create orientation group on the current layer.
        orientation_group = etree.SubElement(
            layer,
            inkex.addNS("g", "svg"),
            {"gcodetools": "Laser GCode Orientation Group"},
        )

        if layer.get("transform") is not None:
            translate = (
                layer.get("transform")
                .replace("translate(", "")
                .replace(")", "")
                .split(",")
            )
        else:
            translate = [0, 0]

        # doc height in pixels (38 mm == 143.62204724px)
        doc_height = self.svg.unittouu(
            self.document.getroot().xpath("@height", namespaces=inkex.NSS)[0]
        )

        if self.document.getroot().get("height") == "100%":
            doc_height = 1052.3622047
            print_(f"Overruding height from 100% to {doc_height}")

        print_("Document height: " + str(doc_height))

        if self.options.unit == "G21 (All units in mm)":
            orientation_points = [[0.0, 0.0], [100.0, 0.0]]
            orientation_scale = 1
            print_(
                "orientation_scale < 0 ===> switching to mm units=%0.10f"
                % orientation_scale
            )
        elif self.options.unit == "G20 (All units in inches)":
            self.error(
                _("Use metric."), "error",
            )
            raise Exception("Metric.")

        print_(f"using orientation scale: {orientation_scale}, i={orientation_points}")
        for i in orientation_points:
            # X == Correct!
            # si == x,y coordinate in px
            # si have correct coordinates
            # if layer have any tranform it will be in translate so lets add
            # that
            si = [
                i[0] * orientation_scale,
                (i[1] * orientation_scale) + float(translate[1]),
            ]
            g = etree.SubElement(
                orientation_group,
                inkex.addNS("g", "svg"),
                {"gcodetools": "Gcodetools orientation point (2 points)"},
            )
            etree.SubElement(
                g,
                inkex.addNS("path", "svg"),
                {
                    "style": "stroke:none;fill:#000000;",
                    "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 z".format(
                        si[0], -si[1] + doc_height
                    ),
                    "gcodetools": "Gcodetools orientation point arrow",
                },
            )
            t = etree.SubElement(
                g,
                inkex.addNS("text", "svg"),
                {
                    "style": "font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;",
                    inkex.addNS("space", "xml"): "preserve",
                    "x": str(si[0] + 10),
                    "y": str(-si[1] - 10 + doc_height),
                    "gcodetools": "Gcodetools orientation point text",
                },
            )
            t.text = "({}; {})".format(i[0], i[1])

    ##########################################################################
    ###
    # Effect
    ###
    # Main function of Gcodetools class
    ###
    ##########################################################################
    def effect(self):
        global options
        options = self.options
        options.self = self
        options.doc_root = self.document.getroot()
        if self.options.log_create_log:
            try:
                if os.path.isfile(self.options.log_filename):
                    os.remove(self.options.log_filename)
                f = open(self.options.log_filename, "a")
                f.write(
                    "Gcodetools log file.\nStarted at %s.\n%s\n"
                    % (time.strftime("%d.%m.%Y %H:%M:%S"), options.log_filename)
                )
                f.write("%s tab is active.\n" % self.options.active_tab)
                f.close()
            except BaseException:
                print_ = lambda *x: None
        else:
            print_ = lambda *x: None
        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."
                ),
                "warning",
            )
            self.orientation(self.layers[min(0, len(self.layers) - 1)])
            self.get_info()

        self.tools = {
            "name": "Laser Engraver",
            "id": "Laser Engraver",
            "laser_feed": self.options.laser_speed,
            "feed": self.options.laser_speed,
            "gcode before path": f"{self.options.laser_command}S{int(self.options.laser_power):d}",
            "gcode after path": self.options.laser_off_command,
            # ...
        }

        self.get_info()
        self.laser()


if __name__ == "__main__":
    import inkscape_ExtensionDevTools

    inkscape_ExtensionDevTools.inkscape_run_debug()
    laser_gcode().run()
