Beispiel #1
0
    def __init__(self,
                 d: str,
                 canvas_height: float,
                 transform_origin=True,
                 transformation=None):
        self.canvas_height = canvas_height
        self.transform_origin = transform_origin

        self.curves = []
        self.initial_point = Vector(0, 0)  # type: Vector
        self.current_point = Vector(0, 0)
        self.last_control = None  # type: Vector

        self.transformation = Transformation()

        if self.transform_origin:
            self.transformation.add_translation(0, canvas_height)
            self.transformation.add_scale(1, -1)

        if transformation is not None:
            self.transformation.extend(transformation)

        try:
            self._parse_commands(d)
        except Exception as generic_exception:
            warnings.warn(
                f"Terminating path. The following unforeseen exception occurred: {generic_exception}"
            )
Beispiel #2
0
    def draw_debug_traces(self, curves):
        """Traces arrows over all parsed paths"""

        root = self.document.getroot()
        origin = self.options.machine_origin
        bed_width = self.options.bed_width
        bed_height = self.options.bed_height

        height_str = root.get("height")
        canvas_height = float(height_str) if height_str.isnumeric() else float(
            height_str[:-2])

        group = etree.Element("{%s}g" % svg_name_space)
        group.set("id", "debug_traces")
        group.set("{%s}groupmode" % inkscape_name_space, "layer")
        group.set("{%s}label" % inkscape_name_space, "debug traces")

        group.append(
            etree.fromstring(
                xml_tree.tostring(
                    debug_methods.arrow_defs(
                        arrow_scale=self.options.debug_arrow_scale))))

        for curve in curves:
            approximation = LineSegmentChain.line_segment_approximation(curve)

            change_origin = Transformation()

            if origin != "top-left":
                change_origin.add_scale(1, -1)
                change_origin.add_translation(0, -canvas_height)

            if origin == "center":
                change_origin.add_translation(bed_width / 2, bed_height / 2)

            path_string = xml_tree.tostring(
                debug_methods.to_svg_path(
                    approximation,
                    color="red",
                    stroke_width=f"{self.options.debug_line_width}px",
                    transformation=change_origin,
                    draw_arrows=True))

            group.append(etree.fromstring(path_string))

        root.append(group)
Beispiel #3
0
def generate_debug(approximations, svg_file_name, debug_file_name):
    tree = ElementTree()
    tree.parse(svg_file_name)

    root = tree.getroot()

    height_str = root.get("height")
    canvas_height = float(height_str) if height_str.isnumeric() else float(
        height_str[:-2])

    for path in root.iter("{%s}path" % name_space):
        path.set("fill", "none")
        path.set("stroke", "black")
        path.set("stroke-width", f"{TOLERANCES['approximation']}mm")

        style = path.get("style")
        if style and "display:none" in style:
            path.set("style", "display:none")
        elif style and ("visibility:hidden" in style
                        or "visibility:collapse" in style):
            path.set("style", "visibility:hidden")
        else:
            path.set("style", "")

    group = Element("{%s}g" % name_space)

    change_origin = Transformation()
    change_origin.add_scale(1, -1)
    change_origin.add_translation(0, -canvas_height)

    defs = debug_methods.arrow_defs()
    group.append(defs)
    for approximation in approximations:
        path = debug_methods.to_svg_path(
            approximation,
            color="red",
            stroke_width=f"{TOLERANCES['approximation']/2}mm",
            transformation=change_origin,
            draw_arrows=True)
        """
        path = Element("{%s}path" % name_space)
        path.set("d", )
        add_def = False
        path.set("fill", "none")
        """

        group.append(path)

    root.append(group)

    tree.write(debug_file_name)
Beispiel #4
0
def parse_root(root: ElementTree.Element,
               transform_origin=True,
               canvas_height=None,
               draw_hidden=False,
               visible_root=True,
               root_transformation=None) -> List[Curve]:
    """
    Recursively parse an etree root's children into geometric curves.

    :param root: The etree element who's children should be recursively parsed. The root will not be drawn.
    :param canvas_height: The height of the canvas. By default the height attribute of the root is used. If the root
    does not contain the height attribute, it must be either manually specified or transform must be False.
    :param transform_origin: Whether or not to transform input coordinates from the svg coordinate system to standard
    cartesian system. Depends on canvas_height for calculations.
    :param draw_hidden: Whether or not to draw hidden elements based on their display, visibility and opacity attributes.
    :param visible_root: Specifies whether or the root is visible. (Inheritance can be overridden)
    :param root_transformation: Specifies whether the root's transformation. (Transformations are inheritable)
    :return: A list of geometric curves describing the svg. Use the Compiler sub-module to compile them to gcode.
    """

    if canvas_height is None:
        height_str = root.get("height")
        canvas_height = float(height_str) if height_str.isnumeric() else float(
            height_str[:-2])

    curves = []

    # Draw visible elements (Depth-first search)
    for element in list(root):

        # display cannot be overridden by inheritance. Just skip the element
        display = _has_style(element, "display", "none")

        if display or element.tag == "{%s}defs" % NAMESPACES["svg"]:
            continue

        transformation = deepcopy(
            root_transformation) if root_transformation else None

        transform = element.get('transform')
        if transform:
            transformation = Transformation(
            ) if transformation is None else transformation
            transformation.add_transform(transform)

        # Is the element and it's root not hidden?
        visible = visible_root and not (
            _has_style(element, "visibility", "hidden")
            or _has_style(element, "visibility", "collapse"))
        # Override inherited visibility
        visible = visible or (_has_style(element, "visibility", "visible"))

        transparent = _has_style(element, "opacity", "0")

        # If the current element is opaque and visible, draw it
        if draw_hidden or (visible and not transparent):
            if element.tag == "{%s}path" % NAMESPACES["svg"]:
                path = Path(element.attrib['d'], canvas_height,
                            transform_origin, transformation)
                curves.extend(path.curves)

        # Continue the recursion
        curves.extend(
            parse_root(element, transform_origin, canvas_height, draw_hidden,
                       visible, transformation))

    # ToDo implement shapes class
    return curves
Beispiel #5
0
class Path:
    """The Path class represents a generic svg path."""

    command_lengths = {
        'M': 2,
        'm': 2,
        'L': 2,
        'l': 2,
        'H': 1,
        'h': 1,
        'V': 1,
        'v': 1,
        'Z': 0,
        'z': 0,
        'C': 6,
        'c': 6,
        'Q': 4,
        'q': 4,
        'S': 4,
        's': 4,
        'T': 2,
        't': 2,
        'A': 7,
        'a': 7
    }

    __slots__ = "curves", "initial_point", "current_point", "last_control", "canvas_height", "draw_move", \
                "transform_origin", "transformation"

    def __init__(self,
                 d: str,
                 canvas_height: float,
                 transform_origin=True,
                 transformation=None):
        self.canvas_height = canvas_height
        self.transform_origin = transform_origin

        self.curves = []
        self.initial_point = Vector(0, 0)  # type: Vector
        self.current_point = Vector(0, 0)
        self.last_control = None  # type: Vector

        self.transformation = Transformation()

        if self.transform_origin:
            self.transformation.add_translation(0, canvas_height)
            self.transformation.add_scale(1, -1)

        if transformation is not None:
            self.transformation.extend(transformation)

        try:
            self._parse_commands(d)
        except Exception as generic_exception:
            warnings.warn(
                f"Terminating path. The following unforeseen exception occurred: {generic_exception}"
            )

    def __repr__(self):
        return f"Path({self.curves})"

    def _parse_commands(self, d: str):
        """Parse svg commands (stored in value of the d key) into geometric curves."""

        command_key = ''  # A character representing a specific command based on the svg standard
        command_arguments = [
        ]  # A list containing the arguments for the current command_key

        number_str = ''  # A buffer used to store numeric characters before conferring them to a number

        # Parse each character in d
        i = 0
        while i < len(d):
            character = d[i]

            is_numeric = character.isnumeric() or character in [
                '-', '.', 'e'
            ]  # Yes, "-6.2e-4" is a valid float.
            is_delimiter = character.isspace() or character in [',']
            is_command_key = character in self.command_lengths.keys()
            is_final = i == len(d) - 1

            # If the current command is complete, however the next command does not specify a new key, assume the next
            # command has the same key. This is implemented by inserting the current key before the next command and
            # restarting the loop without incrementing i
            try:
                if command_key and len(
                        command_arguments
                ) == self.command_lengths[command_key] and is_numeric:
                    duplicate = command_key
                    # If a moveto is followed by multiple pairs of coordinates, the subsequent pairs are treated as
                    # implicit lineto commands. https://www.w3.org/TR/SVG2/paths.html#PathDataMovetoCommands
                    if command_key == 'm':
                        duplicate = 'l'

                    if command_key == 'M':
                        duplicate = 'L'

                    d = d[:i] + duplicate + d[i:]
                    continue
            except KeyError as key_error:
                warnings.warn(
                    f"Unknown command key {command_key}. Skipping curve.")

            # If the character is part of a number, keep on composing it
            if is_numeric:
                number_str += character

                # if a negative number follows another number, no delimiter is required.
                # implicitly stated decimals like .6 don't require a delimiter. In either case we add a delimiter.
                negatives = not is_final and character != 'e' and d[i +
                                                                    1] == '-'
                implicit_decimals = not is_final and d[
                    i + 1] == '.' and '.' in number_str
                if negatives or implicit_decimals:
                    d = d[:i + 1] + ',' + d[i + 1:]

            # If the character is a delimiter or a command key or the last character, complete the number and save it
            # as an argument
            if is_delimiter or is_command_key or is_final:
                if number_str:
                    # In svg form '-.5' can be written as '-.5'. Python doesn't like that notation.
                    if number_str[0] == '.':
                        number_str = '0' + number_str
                    if number_str[0] == '-' and number_str[1] == '.':
                        number_str = '-0' + number_str[1:]

                    command_arguments.append(float(number_str))
                    number_str = ''

            # If it's a command key or the last character, parse the previous (now complete) command and save the letter
            # as the new command key
            if is_command_key or is_final:
                if command_key:
                    self._add_svg_curve(command_key, command_arguments)

                command_key = character
                command_arguments.clear()

            # If the last character is a command key (only useful for Z), save
            if is_command_key and is_final:
                self._add_svg_curve(command_key, command_arguments)

            i += 1

    def _add_svg_curve(self, command_key: str, command_arguments: List[float]):
        """
        Offer a representation of a curve using the geometry sub-module.
        Based on Mozilla Docs: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

        Each sub-method must be implemented with the following structure:
        def descriptive_name(*command_arguments):
            execute calculations and transformations, **do not modify or create any instance variables**
            generate curve
            modify instance variables
            return curve

        Alternatively a sub-method may simply call a base command.

        :param command_key: a character representing a specific command based on the svg standard
        :param command_arguments: A list containing the arguments for the current command_key
        """

        # Establish a new initial point and a new current point. (multiple coordinates are parsed as lineto commands)
        def absolute_move(x, y):
            self.initial_point = Vector(x, y)
            self.current_point = Vector(x, y)
            return None

        def relative_move(dx, dy):
            return absolute_move(*(self.current_point + Vector(dx, dy)))

        # Draw straight line
        def absolute_line(x, y):
            start = self.current_point
            end = Vector(x, y)

            line = Line(self.transformation.apply_affine_transformation(start),
                        self.transformation.apply_affine_transformation(end))

            self.current_point = end

            return line

        def relative_line(dx, dy):
            return absolute_line(*(self.current_point + Vector(dx, dy)))

        def absolute_horizontal_line(x):
            return absolute_line(x, self.current_point.y)

        def relative_horizontal_line(dx):
            return absolute_horizontal_line(self.current_point.x + dx)

        def absolute_vertical_line(y):
            return absolute_line(self.current_point.x, y)

        def relative_vertical_line(dy):
            return absolute_vertical_line(self.current_point.y + dy)

        def close_path():
            return absolute_line(*self.initial_point)

        # Draw curvy curves
        def absolute_cubic_bazier(control1_x, control1_y, control2_x,
                                  control2_y, x, y):

            trans_start = self.transformation.apply_affine_transformation(
                self.current_point)
            trans_end = self.transformation.apply_affine_transformation(
                Vector(x, y))
            trans_control1 = self.transformation.apply_affine_transformation(
                Vector(control1_x, control1_y))
            trans_control2 = self.transformation.apply_affine_transformation(
                Vector(control2_x, control2_y))

            cubic_bezier = CubicBazier(trans_start, trans_end, trans_control1,
                                       trans_control2)

            self.last_control = Vector(control2_x, control2_y)
            self.current_point = Vector(x, y)

            return cubic_bezier

        def relative_cubic_bazier(dx1, dy1, dx2, dy2, dx, dy):
            return absolute_cubic_bazier(self.current_point.x + dx1,
                                         self.current_point.y + dy1,
                                         self.current_point.x + dx2,
                                         self.current_point.y + dy2,
                                         self.current_point.x + dx,
                                         self.current_point.y + dy)

        def absolute_cubic_bezier_extension(x2, y2, x, y):
            start = self.current_point
            control2 = Vector(x2, y2)
            end = Vector(x, y)

            if self.last_control:
                control1 = 2 * start - self.last_control
                bazier = absolute_cubic_bazier(*control1, *control2, *end)
            else:
                bazier = absolute_quadratic_bazier(*control2, *end)

            self.current_point = start

            return bazier

        def relative_cubic_bazier_extension(dx2, dy2, dx, dy):
            return absolute_cubic_bezier_extension(self.current_point.x + dx2,
                                                   self.current_point.y + dy2,
                                                   self.current_point.x + dx,
                                                   self.current_point.y + dy)

        def absolute_quadratic_bazier(control1_x, control1_y, x, y):

            trans_end = self.transformation.apply_affine_transformation(
                self.current_point)
            trans_new_end = self.transformation.apply_affine_transformation(
                Vector(x, y))
            trans_control1 = self.transformation.apply_affine_transformation(
                Vector(control1_x, control1_y))

            quadratic_bezier = QuadraticBezier(trans_end, trans_new_end,
                                               trans_control1)

            self.last_control = Vector(control1_x, control1_y)
            self.current_point = Vector(x, y)

            return quadratic_bezier

        def relative_quadratic_bazier(dx1, dy1, dx, dy):
            return absolute_quadratic_bazier(self.current_point.x + dx1,
                                             self.current_point.y + dy1,
                                             self.current_point.x + dx,
                                             self.current_point.y + dy)

        def absolute_quadratic_bazier_extension(x, y):
            start = self.current_point
            end = Vector(x, y)

            if self.last_control:
                control = 2 * start - self.last_control
                bazier = absolute_quadratic_bazier(*control, *end)
            else:
                bazier = absolute_quadratic_bazier(*start, *end)

            self.current_point = end
            return bazier

        def relative_quadratic_bazier_extension(dx, dy):
            return absolute_quadratic_bazier_extension(
                self.current_point.x + dx, self.current_point.y + dy)

        # Generate EllipticalArc with center notation from svg endpoint notation.
        # Based on w3.org implementation notes. https://www.w3.org/TR/SVG2/implnote.html
        # Todo transformations aren't applied correctly to elliptical arcs
        def absolute_arc(rx, ry, deg_from_horizontal, large_arc_flag,
                         sweep_flag, x, y):
            end = Vector(x, y)
            start = self.current_point

            radii = Vector(rx, ry)

            rotation_rad = math.radians(deg_from_horizontal)

            if abs(start - end) == 0:
                raise ValueError("start and end points can't be equal")

            radii, center, start_angle, sweep_angle = formulas.endpoint_to_center_parameterization(
                start, end, radii, rotation_rad, large_arc_flag, sweep_flag)

            arc = EllipticalArc(center,
                                radii,
                                rotation_rad,
                                start_angle,
                                sweep_angle,
                                transformation=self.transformation)

            self.current_point = end
            return arc

        def relative_arc(rx, ry, deg_from_horizontal, large_arc_flag,
                         sweep_flag, dx, dy):
            return absolute_arc(rx, ry, deg_from_horizontal, large_arc_flag,
                                sweep_flag, self.current_point.x + dx,
                                self.current_point.y + dy)

        command_methods = {
            # Only move end point
            'M': absolute_move,
            'm': relative_move,

            # Draw straight line
            'L': absolute_line,
            'l': relative_line,
            'H': absolute_horizontal_line,
            'h': relative_horizontal_line,
            'V': absolute_vertical_line,
            'v': relative_vertical_line,
            'Z': close_path,
            'z': close_path,

            # Draw bazier curves
            'C': absolute_cubic_bazier,
            'c': relative_cubic_bazier,
            'S': absolute_cubic_bezier_extension,
            's': relative_cubic_bazier_extension,
            'Q': absolute_quadratic_bazier,
            'q': relative_quadratic_bazier,
            'T': absolute_quadratic_bazier_extension,
            't': relative_quadratic_bazier_extension,

            # Draw elliptical arcs
            'A': absolute_arc,
            'a': relative_arc
        }

        try:
            curve = command_methods[command_key](*command_arguments)
        except TypeError as type_error:
            warnings.warn(
                f"Mis-formed input. Skipping command {command_key, command_arguments} because it caused the "
                f"following error: \n{type_error}")
        except ValueError as value_error:
            warnings.warn(
                f"Impossible geometry. Skipping curve {command_key, command_arguments} because it caused the "
                f"following value error:\n{value_error}")
        else:
            if curve is not None:
                self.curves.append(curve)

            if verbose:
                print(f"{command_key}{tuple(command_arguments)} -> {curve}")
Beispiel #6
0
def parse_root(root: ElementTree.Element, transform_origin=True, canvas_height=None, draw_hidden=False,
               visible_root=True, root_transformation=None) -> List[Curve]:

    """
    Recursively parse an etree root's children into geometric curves.

    :param root: The etree element who's children should be recursively parsed. The root will not be drawn.
    :param canvas_height: The height of the canvas. By default the height attribute of the root is used. If the root
    does not contain the height attribute, it must be either manually specified or transform must be False.
    :param transform_origin: Whether or not to transform input coordinates from the svg coordinate system to standard
    cartesian system. Depends on canvas_height for calculations.
    :param draw_hidden: Whether or not to draw hidden elements based on their display, visibility and opacity attributes.
    :param visible_root: Specifies whether or the root is visible. (Inheritance can be overridden)
    :param root_transformation: Specifies whether the root's transformation. (Transformations are inheritable)
    :return: A list of geometric curves describing the svg. Use the Compiler sub-module to compile them to gcode.
    """

    if canvas_height is None:
        height_str = root.get("height")
        viewBox_str = root.get("viewBox")
        if height_str is None and viewBox_str:
            # "viewBox" attribute: <min-x, min-y, width, height>
            height_str = viewBox_str.split()[3]
        (number, scale_factor) = _parse_length(height_str)
        canvas_height = number * scale_factor

    if root.get("viewBox") is None:
        height_str = root.get("height")
        if height_str is not None:
            scale = 25.4/96.0
            root_transformation = root_transformation if root_transformation else Transformation()
            root_transformation.add_scale(scale, scale)
    else:
        #print("We have a viewBox of >>%s<<" % (root.get("viewBox")))
        # Calulate the transform, as described in https://www.w3.org/TR/SVG/coords.html#ComputingAViewportsTransform
        p = re.compile(r'([\d\.\-e]+)[,\s]+([\d\.\-e]+)[,\s]+([\d\.\-e]+)[,\s]+([\d\.\-e]+)')
        if p.search(root.get("viewBox")):
            parts = p.search(root.get("viewBox"))
            # TODO Can these values be anything other than numbers?  "123mm" maybe?
            #      The spec says they're "number"s, so no units, but possibly + or - or e-notation
            vb_x = float(parts[1])
            vb_y = float(parts[2])
            vb_width = float(parts[3])
            vb_height = float(parts[4])
            # TODO handle the preserveAspectRatio attribute
            # Defaults if not otherwise specified
            align = "xMidYMid"
            meet_or_slice = "meet"

            e_x = 0.0
            e_y = 0.0
            width_str = root.get("width")
            (e_number, e_multiply) = _parse_length(width_str)
            e_width = e_number * e_multiply
            e_height = canvas_height
            scale_x = e_width/vb_width
            scale_y = e_height/vb_height
            #print("vb_x: %f, vb_y: %f, vb_width: %f, vb_height: %f" % (vb_x, vb_y, vb_width, vb_height))
            #print("e_x: %f, e_y: %f, e_width: %f, e_height: %f, scale_x: %f, scale_y: %f" % (e_x, e_y, e_width, e_height, scale_x, scale_y))
            if align != "none" and meet_or_slice == "meet":
                if scale_x > scale_y:
                    scale_x = scale_y
                else:
                    scale_y = scale_x
            if align != "none" and meet_or_slice == "slice":
                if scale_x < scale_y:
                    scale_x = scale_y
                else:
                    scale_y = scale_x
            translate_x = e_x - (vb_x * scale_x)
            translate_y = e_y - (vb_y * scale_y)
            # Now apply the viewBox transformations
            root_transformation = root_transformation if root_transformation else Transformation()
            if translate_x != 0 or translate_y != 0:
                root_transformation.add_translation(translate_x, translate_y)
            if scale_x != 1.0 or scale_y != 1.0:
                root_transformation.add_scale(scale_x, scale_y)

    curves = []

    # Draw visible elements (Depth-first search)
    for element in list(root):

        # display cannot be overridden by inheritance. Just skip the element
        display = _has_style(element, "display", "none")

        if display or element.tag == "{%s}defs" % NAMESPACES["svg"]:
            continue

        transformation = deepcopy(root_transformation) if root_transformation else None

        transform = element.get('transform')
        if transform:
            transformation = Transformation() if transformation is None else transformation
            transformation.add_transform(transform)

        # Is the element and it's root not hidden?
        visible = visible_root and not (_has_style(element, "visibility", "hidden")
                                        or _has_style(element, "visibility", "collapse"))
        # Override inherited visibility
        visible = visible or (_has_style(element, "visibility", "visible"))

        # If the current element is opaque and visible, draw it
        if draw_hidden or visible:
            if element.tag == "{%s}path" % NAMESPACES["svg"]:
                path = Path(element.attrib['d'], canvas_height, transform_origin, transformation)
                curves.extend(path.curves)

        # Continue the recursion
        curves.extend(parse_root(element, transform_origin, canvas_height, draw_hidden, visible, transformation))

    # ToDo implement shapes class
    return curves
Beispiel #7
0
def generate_debug(approximations, svg_file_name, debug_file_name):
    tree = ElementTree()
    tree.parse(svg_file_name)

    root = tree.getroot()

    height_str = root.get("height")
    (canvas_height_raw, scale_factor) = _parse_length(height_str)
    canvas_height = canvas_height_raw * scale_factor

    for path in root.iter("{%s}path" % name_space):
        path.set("fill", "none")
        path.set("stroke", "black")
        path.set("stroke-width", f"{TOLERANCES['approximation']}mm")

        style = path.get("style")
        if style and "display:none" in style:
            path.set("style", "display:none")
        elif style and ("visibility:hidden" in style
                        or "visibility:collapse" in style):
            path.set("style", "visibility:hidden")
        else:
            path.set("style", "")

    group = Element("{%s}g" % name_space)

    change_origin = Transformation()
    viewbox_str = root.get("viewBox")
    if viewbox_str is None:
        # Inverse scale
        scale = 25.4 / 96.0
        change_origin.add_scale(1.0 / scale, 1.0 / scale)
    else:
        # TODO Build a more resilient parser here
        parts = re.search(
            r'([\d\.\-e]+)[,\s]+([\d\.\-e]+)[,\s]+([\d\.\-e]+)[,\s]+([\d\.\-e]+)',
            viewbox_str)
        if parts is not None:
            # TODO Can these values be anything other than numbers?  "123mm" maybe?
            #      The spec says they're "number"s, so no units, but possibly + or - or e-notation
            vb_x = float(parts[1])
            vb_y = float(parts[2])
            vb_width = float(parts[3])
            vb_height = float(parts[4])
            # TODO handle the preserveAspectRatio attribute
            # Defaults if not otherwise specified
            align = "xMidYMid"
            meet_or_slice = "meet"

            e_x = 0.0
            e_y = 0.0
            width_str = root.get("width")
            (canvas_width_raw, _) = _parse_length(width_str)
            e_width = canvas_width_raw  # use raw number
            e_height = canvas_height_raw  # use raw number

            scale_x = e_width / vb_width
            scale_y = e_height / vb_height
            if align != "none" and meet_or_slice == "meet":
                if scale_x > scale_y:
                    scale_x = scale_y
                else:
                    scale_y = scale_x
            if align != "none" and meet_or_slice == "slice":
                if scale_x < scale_y:
                    scale_x = scale_y
                else:
                    scale_y = scale_x
            # Inverse scale
            if scale_x != 1.0 or scale_y != 1.0:
                change_origin.add_scale(1.0 / scale_factor / scale_x,
                                        1.0 / scale_factor / scale_y)
            else:
                change_origin.add_scale(1.0 / scale_factor, 1.0 / scale_factor)

            # Inverse translation
            translate_x = e_x + (vb_x * scale_x)
            translate_y = e_y + (vb_y * scale_y)
            if translate_x != 0 or translate_y != 0:
                change_origin.add_translation(translate_x, translate_y)
    change_origin.add_scale(1, -1)
    change_origin.add_translation(0, -canvas_height)

    group.append(debug_methods.arrow_defs())
    for approximation in approximations:
        path = debug_methods.to_svg_path(
            approximation,
            color="red",
            stroke_width=f"{TOLERANCES['approximation']/2}mm",
            transformation=change_origin,
            draw_arrows=True)
        group.append(path)

    root.append(group)

    tree.write(debug_file_name)
    def effect(self):
        """Takes the SVG from Inkscape, generates gcode, returns the SVG after adding debug lines."""

        root = self.document.getroot()

        # Change svg_to_gcode's approximation tolerance
        TOLERANCES["approximation"] = float(
            self.options.approximation_tolerance.replace(',', '.'))

        # Construct output path
        output_path = os.path.join(self.options.directory,
                                   self.options.filename)
        if self.options.filename_suffix:
            try:
                filename, extension = output_path.split('.')
            except:
                self.msg("Error in output directory!")
                exit(1)

            n = 1
            while os.path.isfile(output_path):
                output_path = filename + str(n) + '.' + extension
                n += 1

        # Load header and footer files
        header = []
        if os.path.isfile(self.options.header_path):
            with open(self.options.header_path, 'r') as header_file:
                header = header_file.read().splitlines()
        elif self.options.header_path != os.getcwd(
        ):  # The Inkscape file selector defaults to the working directory
            self.debug(
                f"Header file does not exist at {self.options.header_path}")

        footer = []
        if os.path.isfile(self.options.footer_path):
            with open(self.options.footer_path, 'r') as footer_file:
                footer = footer_file.read().splitlines()
        elif self.options.footer_path != os.getcwd():
            self.debug(
                f"Footer file does not exist at {self.options.footer_path}")

        # Customize header/footer
        custom_interface = generate_custom_interface(
            self.options.tool_off_command, self.options.tool_power_command)
        interface_instance = custom_interface()

        if self.options.do_laser_off_start:
            header.append(interface_instance.laser_off())
        if self.options.do_laser_off_end:
            footer.append(interface_instance.laser_off())

        header.append(
            interface_instance.set_movement_speed(self.options.travel_speed))
        if self.options.do_z_axis_start:
            header.append(
                interface_instance.linear_move(z=self.options.z_axis_start))
        if self.options.move_to_origin_end:
            footer.append(interface_instance.linear_move(x=0, y=0))

        # Generate gcode
        gcode_compiler = Compiler(custom_interface,
                                  self.options.travel_speed,
                                  self.options.cutting_speed,
                                  self.options.pass_depth,
                                  dwell_time=self.options.dwell_time,
                                  custom_header=header,
                                  custom_footer=footer,
                                  unit=self.options.unit)

        transformation = Transformation()

        transformation.add_translation(self.options.horizontal_offset,
                                       self.options.vertical_offset)
        transformation.add_scale(self.options.scaling_factor)

        if self.options.machine_origin == "center":
            transformation.add_translation(-self.options.bed_width / 2,
                                           self.options.bed_height / 2)
        elif self.options.machine_origin == "top-left":
            transformation.add_translation(0, self.options.bed_height)

        curves = parse_root(root,
                            transform_origin=not self.options.invert_y_axis,
                            root_transformation=transformation,
                            canvas_height=self.options.bed_height)

        gcode_compiler.append_curves(curves)
        gcode_compiler.compile_to_file(output_path, passes=self.options.passes)

        # Draw debug lines
        self.clear_debug()
        if self.options.draw_debug:
            self.draw_debug_traces(curves)
            self.draw_unit_reference()
            self.select_non_debug_layer()

        return self.document
Beispiel #9
0
    def effect(self):
        """Takes the SVG from Inkscape, generates gcode, returns the SVG after adding debug lines."""

        root = self.document.getroot()

        approximation_tolerance = float(
            self.options.approximation_tolerance.replace(',', '.'))

        output_path = os.path.join(self.options.directory,
                                   self.options.filename)
        if self.options.filename_suffix:
            filename, extension = output_path.split('.')

            n = 1
            while os.path.isfile(output_path):
                output_path = filename + str(n) + '.' + extension
                n += 1

        header = None
        if self.options.header_path:
            with open(self.options.header_path, 'r') as header_file:
                header = header_file.readlines()

        footer = None
        if self.options.footer_path:
            with open(self.options.footer_path, 'r') as footer_file:
                footer = footer_file.readlines()

        # Generate gcode
        self.clear_debug()

        TOLERANCES["approximation"] = approximation_tolerance
        custom_interface = generate_custom_interface(
            self.options.laser_off_command, self.options.laser_power_command,
            self.options.laser_power_range)

        gcode_compiler = Compiler(custom_interface,
                                  self.options.travel_speed,
                                  self.options.cutting_speed,
                                  self.options.pass_depth,
                                  dwell_time=self.options.dwell_time,
                                  custom_header=header,
                                  custom_footer=footer,
                                  unit=self.options.unit)

        transformation = Transformation()

        transformation.add_translation(self.options.horizontal_offset,
                                       self.options.vertical_offset)
        transformation.add_scale(self.options.scaling_factor)

        if self.options.machine_origin == "center":
            transformation.add_translation(-self.options.bed_width / 2,
                                           self.options.bed_height / 2)

        transform_origin = True
        if self.options.machine_origin == "top-left":
            transform_origin = False

        curves = parse_root(root,
                            transform_origin=transform_origin,
                            root_transformation=transformation)

        gcode_compiler.append_curves(curves)
        gcode_compiler.compile_to_file(output_path, passes=self.options.passes)

        # Generate debug lines
        if self.options.draw_debug:
            self.draw_debug_traces(curves)
            self.draw_unit_reference()
            self.select_non_debug_layer()

        return self.document
Beispiel #10
0
    def effect(self):
        """Takes the SVG from Inkscape, generates gcode, returns the SVG after adding debug lines."""

        root = self.document.getroot()

        approximation_tolerance = float(
            self.options.approximation_tolerance.replace(',', '.'))

        output_path = os.path.join(self.options.directory,
                                   self.options.filename)
        if self.options.filename_suffix:
            filename, extension = output_path.split('.')

            n = 1
            while os.path.isfile(output_path):
                output_path = filename + str(n) + '.' + extension
                n += 1

        header = None
        if os.path.isfile(self.options.header_path):
            logger.debug(F"going to read{self.options.header_path}")
            with open(self.options.header_path, 'r') as header_file:
                header = header_file.read().splitlines()
                logger.debug(F"This is my header: >>>{header}<<<")
        elif self.options.header_path != os.getcwd():
            self.debug(
                f"Header file does not exist at {self.options.header_path}")

        if self.options.set_z_axis_start_pos:
            unit = "G21"
            if self.options.unit == "in":
                unit = "G20"
            temp = F"{unit};\nG1 Z{self.options.z_axis_start};"
            if header is None:
                header = [temp]
            else:
                header.append(temp)

        footer = None
        if os.path.isfile(self.options.footer_path):
            with open(self.options.footer_path, 'r') as footer_file:
                footer = footer_file.read().splitlines()
        elif self.options.footer_path != os.getcwd():
            self.debug(
                f"Footer file does not exist at {self.options.footer_path}")

        if self.options.move_to_zero_at_end:
            temp = F"M5;\nG1 F{self.options.travel_speed} X0.0 Y0.0 Z0.0;"
            if footer is None:
                footer = [temp]
            else:
                footer.append(temp)

        # Generate gcode
        self.clear_debug()

        TOLERANCES["approximation"] = approximation_tolerance
        custom_interface = generate_custom_interface(
            self.options.laser_off_command, self.options.laser_power_command,
            self.options.laser_power_range)

        gcode_compiler = Compiler(custom_interface,
                                  self.options.travel_speed,
                                  self.options.cutting_speed,
                                  self.options.pass_depth,
                                  dwell_time=self.options.dwell_time,
                                  custom_header=header,
                                  custom_footer=footer,
                                  unit=self.options.unit)

        transformation = Transformation()

        transformation.add_translation(self.options.horizontal_offset,
                                       self.options.vertical_offset)
        transformation.add_scale(self.options.scaling_factor)

        if self.options.machine_origin == "center":
            transformation.add_translation(-self.options.bed_width / 2,
                                           self.options.bed_height / 2)

        curves = parse_root(root,
                            transform_origin=not self.options.invert_y_axis,
                            root_transformation=transformation,
                            canvas_height=self.options.bed_height)

        gcode_compiler.append_curves(curves)
        gcode_compiler.compile_to_file(output_path, passes=self.options.passes)

        # Generate debug lines
        if self.options.draw_debug:
            self.draw_debug_traces(curves)
            self.draw_unit_reference()
            self.select_non_debug_layer()

        return self.document