independent process spawn for linux

This commit is contained in:
Mario Voigt 2021-05-08 22:22:29 +02:00
parent 4ecb9b2df9
commit f4a2260687
3 changed files with 135 additions and 127 deletions

View File

@ -6,6 +6,7 @@ import shutil
import os import os
import sys import sys
import warnings import warnings
import shlex
""" """
Extension for InkScape 1.X Extension for InkScape 1.X
@ -36,15 +37,14 @@ DETACHED_PROCESS = 0x00000008
class AnimateOrder (inkex.EffectExtension): class AnimateOrder (inkex.EffectExtension):
def spawnIndependentProcess(self, args): #function to spawn non-blocking inkscape instance. the inkscape command is available because it is added to ENVIRONMENT when Inkscape main instance is started def spawnIndependentProcess(self, args): #function to spawn non-blocking inkscape instance. the inkscape command is available because it is added to ENVIRONMENT when Inkscape main instance is started
warnings.simplefilter('ignore', ResourceWarning) #suppress "enable tracemalloc to get the object allocation traceback"
if os.name == 'nt': if os.name == 'nt':
warnings.simplefilter('ignore', ResourceWarning) #suppress "enable tracemalloc to get the object allocation traceback"
subprocess.Popen(args, close_fds=True, creationflags=DETACHED_PROCESS) subprocess.Popen(args, close_fds=True, creationflags=DETACHED_PROCESS)
warnings.simplefilter("default", ResourceWarning)
else: else:
pid = os.fork() cmd = "nohup " + " ".join(args) + " &"
if pid == 0: # new process cmds = shlex.split(cmd)
os.system("nohup " + args + " &") subprocess.Popen(cmds, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
exit() warnings.simplefilter("default", ResourceWarning)
def add_arguments(self, pars): def add_arguments(self, pars):
pars.add_argument("--tab") pars.add_argument("--tab")

View File

@ -8,6 +8,7 @@ import os
import sys import sys
import subprocess import subprocess
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
import shlex
import warnings import warnings
import inkex import inkex
import inkex.command import inkex.command
@ -22,144 +23,142 @@ logger = logging.getLogger(__name__)
DETACHED_PROCESS = 0x00000008 DETACHED_PROCESS = 0x00000008
GROUP_ID = 'export_selection_transform' GROUP_ID = 'export_selection_transform'
class ExportObject(inkex.EffectExtension): class ExportObject(inkex.EffectExtension):
def add_arguments(self, pars): def add_arguments(self, pars):
pars.add_argument("--wrap_transform", type=inkex.Boolean, default=False, help="Wrap final document in transform") pars.add_argument("--wrap_transform", type=inkex.Boolean, default=False, help="Wrap final document in transform")
pars.add_argument("--border_offset", type=float, default=1.000, help="Add border offset around selection") pars.add_argument("--border_offset", type=float, default=1.000, help="Add border offset around selection")
pars.add_argument("--export_dir", default="~/inkscape_export/", help="Location to save exported documents") pars.add_argument("--export_dir", default="~/inkscape_export/", help="Location to save exported documents")
pars.add_argument("--opendir", type=inkex.Boolean, default=False, help="Open containing output directory after export") pars.add_argument("--opendir", type=inkex.Boolean, default=False, help="Open containing output directory after export")
pars.add_argument("--dxf_exporter_path", default="/usr/share/inkscape/extensions/dxf_outlines.py", help="Location of dxf_outlines.py") pars.add_argument("--dxf_exporter_path", default="/usr/share/inkscape/extensions/dxf_outlines.py", help="Location of dxf_outlines.py")
pars.add_argument("--export_dxf", type=inkex.Boolean, default=False, help="Create a dxf file") pars.add_argument("--export_dxf", type=inkex.Boolean, default=False, help="Create a dxf file")
pars.add_argument("--export_pdf", type=inkex.Boolean, default=False, help="Create a pdf file") pars.add_argument("--export_pdf", type=inkex.Boolean, default=False, help="Create a pdf file")
pars.add_argument("--newwindow", type=inkex.Boolean, default=False, help="Open file in new Inkscape window") pars.add_argument("--newwindow", type=inkex.Boolean, default=False, help="Open file in new Inkscape window")
def openExplorer(self, dir): def openExplorer(self, dir):
if os.name == 'nt': if os.name == 'nt':
Popen(["explorer", dir], close_fds=True, creationflags=DETACHED_PROCESS).wait() Popen(["explorer", dir], close_fds=True, creationflags=DETACHED_PROCESS).wait()
else: else:
Popen(["xdg-open", dir], close_fds=True, start_new_session=True).wait() Popen(["xdg-open", dir], close_fds=True, start_new_session=True).wait()
def spawnIndependentInkscape(self, file): #function to spawn non-blocking inkscape instance. the inkscape command is available because it is added to ENVIRONMENT when Inkscape main instance is started def spawnIndependentInkscape(self, file): #function to spawn non-blocking inkscape instance. the inkscape command is available because it is added to ENVIRONMENT when Inkscape main instance is started
if os.name == 'nt': warnings.simplefilter('ignore', ResourceWarning) #suppress "enable tracemalloc to get the object allocation traceback"
warnings.simplefilter('ignore', ResourceWarning) #suppress "enable tracemalloc to get the object allocation traceback" if os.name == 'nt':
Popen(["inkscape", file], close_fds=True, creationflags=DETACHED_PROCESS) Popen(["inkscape", file], close_fds=True, creationflags=DETACHED_PROCESS)
warnings.simplefilter("default", ResourceWarning) else:
else: cmd = "nohup inkscape " + file + " &"
pid = os.fork() cmds = shlex.split(cmd)
if pid == 0: # new process subprocess.Popen(cmds, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
os.system("nohup inkscape " + file + " &") warnings.simplefilter("default", ResourceWarning)
exit()
def effect(self): def effect(self):
if not self.svg.selected: if not self.svg.selected:
inkex.errormsg("Selection is empty. Please select some objects first!") inkex.errormsg("Selection is empty. Please select some objects first!")
return return
#preflight check for DXF input dir #preflight check for DXF input dir
if not os.path.exists(self.options.dxf_exporter_path): if not os.path.exists(self.options.dxf_exporter_path):
inkex.utils.debug("Location of dxf_outlines.py does not exist. Please select a proper file and try again.") inkex.utils.debug("Location of dxf_outlines.py does not exist. Please select a proper file and try again.")
exit(1) exit(1)
export_dir = Path(self.absolute_href(self.options.export_dir)) export_dir = Path(self.absolute_href(self.options.export_dir))
os.makedirs(export_dir, exist_ok=True) os.makedirs(export_dir, exist_ok=True)
offset = self.options.border_offset offset = self.options.border_offset
bbox = inkex.BoundingBox() bbox = inkex.BoundingBox()
for elem in self.svg.selected.values(): for elem in self.svg.selected.values():
transform = inkex.Transform() transform = inkex.Transform()
parent = elem.getparent() parent = elem.getparent()
if parent is not None and isinstance(parent, inkex.ShapeElement): if parent is not None and isinstance(parent, inkex.ShapeElement):
transform = parent.composed_transform() transform = parent.composed_transform()
try: try:
bbox += elem.bounding_box(transform) bbox += elem.bounding_box(transform)
except Exception: except Exception:
logger.exception("Bounding box not computed") logger.exception("Bounding box not computed")
logger.info("Skipping bounding box") logger.info("Skipping bounding box")
transform = elem.composed_transform() transform = elem.composed_transform()
x1, y1 = transform.apply_to_point([0, 0]) x1, y1 = transform.apply_to_point([0, 0])
x2, y2 = transform.apply_to_point([1, 1]) x2, y2 = transform.apply_to_point([1, 1])
bbox += inkex.BoundingBox((x1, x2), (y1, y2)) bbox += inkex.BoundingBox((x1, x2), (y1, y2))
template = self.create_document() template = self.create_document()
filename = None filename = None
group = etree.SubElement(template, '{http://www.w3.org/2000/svg}g') group = etree.SubElement(template, '{http://www.w3.org/2000/svg}g')
group.attrib['id'] = GROUP_ID group.attrib['id'] = GROUP_ID
group.attrib['transform'] = str(inkex.Transform(((1, 0, -bbox.left), (0, 1, -bbox.top)))) group.attrib['transform'] = str(inkex.Transform(((1, 0, -bbox.left), (0, 1, -bbox.top))))
for elem in self.svg.selected.values(): for elem in self.svg.selected.values():
elem_copy = deepcopy(elem) elem_copy = deepcopy(elem)
elem_copy.attrib['transform'] = str(elem.composed_transform()) elem_copy.attrib['transform'] = str(elem.composed_transform())
elem_copy.attrib['style'] = str(elem.composed_style()) elem_copy.attrib['style'] = str(elem.composed_style())
group.append(elem_copy) group.append(elem_copy)
template.attrib['viewBox'] = f'{-offset} {-offset} {bbox.width + offset * 2} {bbox.height + offset * 2}' template.attrib['viewBox'] = f'{-offset} {-offset} {bbox.width + offset * 2} {bbox.height + offset * 2}'
template.attrib['width'] = f'{bbox.width + offset * 2}' + self.svg.unit template.attrib['width'] = f'{bbox.width + offset * 2}' + self.svg.unit
template.attrib['height'] = f'{bbox.height + offset * 2}' + self.svg.unit template.attrib['height'] = f'{bbox.height + offset * 2}' + self.svg.unit
if filename is None: if filename is None:
filename = elem.attrib.get('id', None) filename = elem.attrib.get('id', None)
if filename: if filename:
filename = filename.replace(os.sep, '_') + '.svg' filename = filename.replace(os.sep, '_') + '.svg'
if not filename: #should never be the case. Inkscape might crash if the id attribute is empty or not existent due to invalid SVG if not filename: #should never be the case. Inkscape might crash if the id attribute is empty or not existent due to invalid SVG
filename = self.svg.get_unique_id("selection") + '.svg' filename = self.svg.get_unique_id("selection") + '.svg'
template.append(group) template.append(group)
if not self.options.wrap_transform: if not self.options.wrap_transform:
self.load(inkscape_command(template.tostring(), select=GROUP_ID, verbs=['SelectionUnGroup'])) self.load(inkscape_command(template.tostring(), select=GROUP_ID, verbs=['SelectionUnGroup']))
template = self.svg template = self.svg
for child in template.getchildren(): for child in template.getchildren():
if child.tag == '{http://www.w3.org/2000/svg}metadata': if child.tag == '{http://www.w3.org/2000/svg}metadata':
template.remove(child) template.remove(child)
self.save_document(template, export_dir / filename) self.save_document(template, export_dir / filename)
if self.options.opendir is True: if self.options.opendir is True:
self.openExplorer(export_dir) self.openExplorer(export_dir)
if self.options.newwindow is True: if self.options.newwindow is True:
#inkscape(os.path.join(export_dir, filename)) #blocking #inkscape(os.path.join(export_dir, filename)) #blocking
self.spawnIndependentInkscape(os.path.join(export_dir, filename)) #non-blocking self.spawnIndependentInkscape(os.path.join(export_dir, filename)) #non-blocking
if self.options.export_dxf is True: if self.options.export_dxf is True:
#ensure that python command is available #we pass 25.4/96 which stands for unit mm. See inkex.units.UNITS and dxf_outlines.inx #ensure that python command is available #we pass 25.4/96 which stands for unit mm. See inkex.units.UNITS and dxf_outlines.inx
cmd = [ cmd = [
sys.executable, #the path of the python interpreter which is used for this script sys.executable, #the path of the python interpreter which is used for this script
self.options.dxf_exporter_path, self.options.dxf_exporter_path,
'--output=' + os.path.join(export_dir, filename + '.dxf'), '--output=' + os.path.join(export_dir, filename + '.dxf'),
r'--units=25.4/96', r'--units=25.4/96',
os.path.join(export_dir, filename) os.path.join(export_dir, filename)
] ]
proc = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE) proc = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE)
stdout, stderr = proc.communicate() stdout, stderr = proc.communicate()
if proc.returncode != 0: if proc.returncode != 0:
inkex.utils.debug("%d %s %s" % (proc.returncode, stdout, stderr)) inkex.utils.debug("%d %s %s" % (proc.returncode, stdout, stderr))
if self.options.export_pdf is True: if self.options.export_pdf is True:
cli_output = inkscape(os.path.join(export_dir, filename), actions='export-pdf-version:1.5;export-text-to-path;export-filename:{file_name};export-do;FileClose'.format(file_name=os.path.join(export_dir, filename + '.pdf'))) cli_output = inkscape(os.path.join(export_dir, filename), actions='export-pdf-version:1.5;export-text-to-path;export-filename:{file_name};export-do;FileClose'.format(file_name=os.path.join(export_dir, filename + '.pdf')))
if len(cli_output) > 0: 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("Inkscape returned the following output when trying to run the file export; the file export may still have worked:")
self.msg(cli_output) self.msg(cli_output)
def create_document(self): def create_document(self):
document = self.svg.copy() document = self.svg.copy()
for child in document.getchildren(): for child in document.getchildren():
if child.tag == '{http://www.w3.org/2000/svg}defs': if child.tag == '{http://www.w3.org/2000/svg}defs':
continue continue
document.remove(child) document.remove(child)
return document return document
def save_document(self, document, filename): def save_document(self, document, filename):
with open(filename, 'wb') as fp: with open(filename, 'wb') as fp:
document = document.tostring() document = document.tostring()
fp.write(scourString(document).encode('utf8')) fp.write(scourString(document).encode('utf8'))
if __name__ == '__main__': if __name__ == '__main__':
ExportObject().run() ExportObject().run()

View File

@ -36,6 +36,15 @@ Module licenses
possible import file types -> https://www.graphics.rwth-aachen.de/media/openmesh_static/Documentations/OpenMesh-8.0-Documentation/a04096.html possible import file types -> https://www.graphics.rwth-aachen.de/media/openmesh_static/Documentations/OpenMesh-8.0-Documentation/a04096.html
todo:
- debug coplanar color for edges for some cases
- remove empty groups (text)
- abort if 0 faces
- give hints for joinery preparations (apply transform, ungroup, ...)
- update documentation accordingly
- make angleRange global for complete unfolding (to match glue pairs between multiple unfoldings)
- add angleRange to stats
""" """
# Compute the third point of a triangle when two points and all edge lengths are given # Compute the third point of a triangle when two points and all edge lengths are given