mightyscape-1.2/extensions/fablabchemnitz/cutting_optimizer/cutting_optimizer.py

202 lines
8.8 KiB
Python

#!/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
extra_param = None
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, extra_param, 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()