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 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)
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)
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
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}")
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
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
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
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