#!/usr/bin/env python3 """ Extension for InkScape 1.2 CutOptim OS Wrapper script to make CutOptim work on Windows and Linux systems without duplicating .inx files Author: Mario Voigt / FabLab Chemnitz Mail: mario.voigt@stadtfabrikanten.org Date: 31.08.2020 Last patch: 03.11.2022 License: GNU GPL v3 """ import inkex import sys import re import os import subprocess from lxml import etree from copy import deepcopy import tempfile from inkex.command import inkscape, inkscape_command class CuttingOptimizer(inkex.EffectExtension): def openDebugFile(self, file): DETACHED_PROCESS = 0x00000008 if os.name == 'nt': subprocess.Popen(["explorer", file], close_fds=True, creationflags=DETACHED_PROCESS, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).wait() else: subprocess.Popen(["xdg-open", file], close_fds=True, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).wait() def add_arguments(self, pars): args = sys.argv[1:] for arg in args: key=arg.split("=")[0] if len(arg.split("=")) == 2: value=arg.split("=")[1] try: if key != "--id": pars.add_argument(key, default=key) except: pass #ignore duplicate id arg def effect(self): extension_dir = os.path.dirname(os.path.realpath(__file__)) cmd = [] if os.name == "nt": cutoptim = os.path.join(extension_dir, "CutOptim.exe") else: cutoptim = os.path.join(extension_dir, "CutOptim") cmd.append(cutoptim) elements = self.svg.selected if len(elements) > 0: #if selection is existing, then we export only selected items to a new svg, which is then going to be processed. Otherwise we process the whole SVG document template = self.svg.copy() for child in template.getchildren(): if child.tag == '{http://www.w3.org/2000/svg}defs': continue template.remove(child) group = etree.SubElement(template, '{http://www.w3.org/2000/svg}g') group.attrib['id'] = 'export_selection_transform' for element in self.svg.selected.values(): elem_copy = deepcopy(element) elem_copy.attrib['transform'] = str(element.composed_transform()) elem_copy.attrib['style'] = str(element.specified_style()) group.append(elem_copy) template.attrib['viewBox'] = self.svg.attrib['viewBox'] template.attrib['width'] = self.svg.attrib['width'] template.attrib['height'] = self.svg.attrib['height'] template.append(group) svg_out = os.path.join(tempfile.gettempdir(), self.svg.get_unique_id("selection") + '.svg') with open(svg_out, 'wb') as fp: fp.write(template.tostring()) actions_list=[] actions_list.append("SelectionUnGroup") actions_list.append("export-type:svg") actions_list.append("export-filename:{}".format(svg_out)) actions_list.append("export-do") actions = ";".join(actions_list) cli_output = inkscape(svg_out, actions=actions) #process recent file if len(cli_output) > 0: self.msg("Inkscape returned the following output when trying to run the file export; the file export may still have worked:") self.msg(cli_output) layerSum = 0 layer_output_0 = False cancel_on_error = False for arg in vars(self.options): argval = str(getattr(self.options, arg)) if arg not in ("tab", "output", "ids", "selected_nodes", "print_cmd"): #inkex.utils.debug(str(arg) + " = " + str(getattr(self.options, arg))) #fix behaviour of "original" arg which does not correctly gets interpreted if set to false if arg == "original" and argval == "false": continue if arg == "input_file": cmd.append("--file") if len(elements) > 0: cmd.append(svg_out) else: cmd.append(argval) elif arg == "layer_output_0": if argval == "true": layerSum += 0 layer_output_0 = True #elif arg == "layer_output_1": # if argval == "true": layerSum += 1 elif arg == "layer_output_2": if argval == "true": layerSum += 2 elif arg == "layer_output_4": if argval == "true": layerSum += 4 elif arg == "layer_output_8": if argval == "true": layerSum += 8 elif arg == "layer_output_16": if argval == "true": layerSum += 16 elif arg == "cancel_on_error": if argval == "true": cancel_on_error = True else: cmd.append("--{}={}".format(arg, argval)) cmd.append("--layer_output") cmd.append("{}".format(layerSum)) if layerSum == 0 and layer_output_0 is False: inkex.utils.debug("You need to enable at least one type of layer to continue!") output_file = None if os.name == "nt": output_file = "cutoptim.svg" else: output_file = "/tmp/cutoptim.svg" if os.path.exists(output_file): try: os.remove(output_file) except OSError as e: pass cmd.append("--output") cmd.append(output_file) # run CutOptim with the parameters provided if self.options.print_cmd == "true": inkex.utils.debug("The following command would be executed on shell:\n") inkex.utils.debug(" ".join(cmd)) exit(0) else: with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) as cutoptim: cutoptim.wait() stdout, stderr = cutoptim.communicate() #inkex.utils.debug("stdout:\n{}".format(stdout.decode('UTF-8'))) errors = stderr.decode('UTF-8') if len(errors) > 0: inkex.utils.debug("Errors occured:\n{}".format(errors)) if len(errors) > 0 and cancel_on_error is True: inkex.utils.debug("Maybe enlarge your document size in case not all polygons could be placed and try again! Nesting was cancelled!") exit(1) # check output existence try: stream = open(output_file, 'r') except FileNotFoundError as e: inkex.utils.debug("There was no SVG output generated. Cannot continue. Command was:\n") inkex.utils.debug(" ".join(cmd)) exit(1) if self.options.original == "false": #we need to use string representation of bool for element in self.document.getroot(): if isinstance(element, inkex.ShapeElement): element.delete() if self.options.debug_file == "true": #we need to use string representation of bool self.openDebugFile("Debug_CutOptim.txt") # write the generated SVG into Inkscape's canvas doc = etree.parse(stream, parser=etree.XMLParser(huge_tree=True)) stream.close() group = inkex.Group(id="CutOptim") ''' 0 = Placed_Layer 1 = Original_Layer (Input Layer) <> see "Keep original layer" 2 = Polygon_Layer 4 = Large_Polygon_Layer 8 = Hull_Placed_Layer 16 = Placed_Polygon_Layer ''' if layer_output_0 is False: l0 = None else: l0 = doc.xpath('//svg:g[@inkscape:label="Placed_Layer"]', namespaces=inkex.NSS) #l1 = doc.xpath('//svg:g[@inkscape:label="Original_Layer"]', namespaces=inkex.NSS) l2 = doc.xpath('//svg:g[@inkscape:label="Polygon_Layer"]', namespaces=inkex.NSS) l4 = doc.xpath('//svg:g[@inkscape:label="Large_Polygon_Layer"]', namespaces=inkex.NSS) l8 = doc.xpath('//svg:g[@inkscape:label="Hull_Placed_Layer"]', namespaces=inkex.NSS) l16 = doc.xpath('//svg:g[@inkscape:label="Placed_Polygon_Layer"]', namespaces=inkex.NSS) for layer in (l0, l2, l4, l8, l16): #,l1 if layer is not None and len(layer) > 0: for element in layer:#[0].getchildren(): group.append(element) self.document.getroot().append(group) if __name__ == '__main__': CuttingOptimizer().run()