This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/j_tech_photonics_laser_tool/j_tech_photonics_laser_tool.py

297 lines
12 KiB
Python

import os
from lxml import etree
from xml.etree import ElementTree as xml_tree
from inkex import EffectExtension, Boolean
from svg_to_gcode.svg_parser import parse_root, Transformation, debug_methods
from svg_to_gcode.geometry import LineSegmentChain
from svg_to_gcode.compiler import Compiler, interfaces
from svg_to_gcode import TOLERANCES
svg_name_space = "http://www.w3.org/2000/svg"
inkscape_name_space = "http://www.inkscape.org/namespaces/inkscape"
sodipodi_name_space = "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
inx_filename = "j_tech_photonics_laser_tool.inx"
def generate_custom_interface(laser_off_command, laser_power_command):
"""Wrapper function for generating a Gcode interface with a custom laser power command"""
class CustomInterface(interfaces.Gcode):
"""A Gcode interface with a custom laser power command"""
def __init__(self):
super().__init__()
def laser_off(self):
return f"{laser_off_command}"
def set_laser_power(self, _):
return f"{laser_power_command}"
return CustomInterface
class GcodeExtension(EffectExtension):
"""Inkscape Effect Extension."""
def __init__(self):
EffectExtension.__init__(self)
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 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
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 not self.options.invert_y_axis:
change_origin.add_scale(1, -1)
change_origin.add_translation(0, -bed_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", opacity="0.5", 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 draw_unit_reference(self):
"""Draws reference points to mark the bed's four corners"""
root = self.document.getroot()
unit = self.options.unit
origin = self.options.machine_origin
bed_width = self.options.bed_width
bed_height = self.options.bed_height
group = etree.Element("{%s}g" % svg_name_space)
group.set("id", "debug_references")
group.set("{%s}groupmode" % inkscape_name_space, "layer")
group.set("{%s}label" % inkscape_name_space, "debug reference points")
reference_points_svg = [(0, 0), (0, bed_height), (bed_width, 0), (bed_width, bed_height)]
reference_points_gcode = {
"bottom-left": [(0, bed_height), (0, 0), (bed_width, bed_height), (bed_width, 0)],
"top-left": [(0, 0), (0, bed_height), (bed_width, 0), (bed_width, bed_height)],
"center": [(-bed_width / 2, bed_height / 2), (-bed_width / 2, -bed_height / 2),
(bed_width / 2, bed_height / 2),
(bed_width / 2, -bed_height / 2)]
}[origin]
for i, (x, y) in enumerate(reference_points_svg):
reference_point = etree.Element("{%s}g" % svg_name_space)
stroke_width = 2
size = 7
x_direction = -1 if x > 0 else 1
plus_sign = etree.Element("{%s}g" % svg_name_space)
horizontal = etree.Element("{%s}line" % svg_name_space)
horizontal.set("x1", str(x - x_direction * stroke_width / 2))
horizontal.set("y1", str(y))
horizontal.set("x2", str(x + x_direction * size))
horizontal.set("y2", str(y))
horizontal.set("style", f"stroke:black;stroke-width:{stroke_width}")
plus_sign.append(horizontal)
y_direction = -1 if y > 0 else 1
vertical = etree.Element("{%s}line" % svg_name_space)
vertical.set("x1", str(x))
vertical.set("y1", str(y + stroke_width / 2))
vertical.set("x2", str(x))
vertical.set("y2", str(y + y_direction * size))
vertical.set("style", f"stroke:black;stroke-width:{stroke_width}")
plus_sign.append(vertical)
reference_point.append(plus_sign)
text_box = etree.Element("{%s}text" % svg_name_space)
text_box.set("x", str(x - 28))
text_box.set("y", str(y - (y <= 0) * 6 + (y > 0) * 9))
text_box.set("font-size", "6")
text_box.text = f"{reference_points_gcode[i][0]}{unit}, {reference_points_gcode[i][1]}{unit}"
reference_point.append(text_box)
group.append(reference_point)
root.append(group)
def select_non_debug_layer(self):
"""
Select content_layer and create one if it doesn't exist. This helps stop the user from accidentally placing new
objects in debug layers.
"""
root = self.document.getroot()
unique_id = "layer89324"
content_layer = root.find("{%s}g[@id='%s']" % (svg_name_space, unique_id))
if content_layer is None:
content_layer = etree.Element("{%s}g" % svg_name_space)
content_layer.set("id", unique_id)
content_layer.set("{%s}groupmode" % inkscape_name_space, "layer")
content_layer.set("{%s}label" % inkscape_name_space, "content layer")
sodipodi = root.find("{%s}namedview" % sodipodi_name_space)
if sodipodi is not None:
sodipodi.set("{%s}current-layer" % inkscape_name_space, unique_id)
root.append(content_layer)
def clear_debug(self):
"""Removes debug groups. Used before parsing paths for gcode."""
root = self.document.getroot()
debug_traces = root.find("{%s}g[@id='debug_traces']" % svg_name_space)
debug_references = root.find("{%s}g[@id='debug_references']" % svg_name_space)
if debug_traces is not None:
root.remove(debug_traces)
if debug_references is not None:
root.remove(debug_references)
def add_arguments(self, arg_parser):
"""Tell inkscape what arguments to stick in self.options (behind the hood it's more complicated, see docs)"""
arguments = self.read_arguments()
for arg in arguments:
arg_parser.add_argument("--" + arg["name"], type=arg["type"], dest=arg["name"])
@staticmethod
def read_arguments():
"""
This method reads arguments off of the inx file so you don't have to explicitly declare them in self.add_arguments()
"""
root = etree.parse(inx_filename).getroot()
arguments = [] # [{name, type, ...}]
namespace = "http://www.inkscape.org/namespace/inkscape/extension"
for arg in root.iter("{%s}param" % namespace):
name = arg.attrib["name"]
arg_type = arg.attrib["type"]
if arg_type in ["description", "notebook"]:
continue
types = {"int": int, "float": float, "bool": Boolean, "string": str, "optiongroup": str, "path": str}
arguments.append({"name": name, "type": types[arg_type]})
if next(root.iter("{%s}page" % namespace)) is not None:
arguments.append({"name": "tabs", "type": str})
return arguments
if __name__ == '__main__':
effect = GcodeExtension()
effect.run()