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/visicut/visicut_export.py

315 lines
12 KiB
Python

#!/usr/bin/env python3
'''
This extension strips everything which is not selected from
the current svg, saves it and
calls VisiCut on it.
Copyright (C) 2012 Thomas Oster, thomas.oster@rwth-aachen.de
Copyright (C) 2014-2018 Max Gaukler, development@maxgaukler.de
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
import sys
import os
import re
import subprocess
from subprocess import Popen
import traceback
import tempfile
import unicodedata
import codecs
DEVNULL = open(os.devnull, 'w')
def get_single_instance_port():
"""
get the single instance port used by VisiCut.
CAUTION: This code must behave exactly the same as ApplicationInstanceManager.getSingleInstancePort() in Visicut.
"""
port = 6543
if (sys.platform == "linux"):
d = os.environ.get("DISPLAY")
if (d != None):
d = d.split(':')[1].split('.')[0]
port += int(d)
if (sys.platform == "win32"):
d = os.environ.get("SESSIONNAME")
if d == None:
# no Terminal Services installed
pass
else:
# get session ID
try:
CREATE_NO_WINDOW = 0x08000000
id = subprocess.check_output("powershell -Command (get-process -pid $pid).sessionid",creationflags=CREATE_NO_WINDOW)
id = int(id.strip())
port += 2 + id
except:
sys.stderr.write("Warning: Cannot determine session ID. please report this.\n")
return port
# if Visicut or Inkscape cannot be found, change these lines here to VISICUTDIR="C:/Programs/Visicut" or wherever you installed it.
# please use forward slashes (/), not backslashes (\).
#
# example:
# VISICUTDIR="C:/Program Files (x86)/VisiCut/"
# INKSCAPEDIR="C:/Program Files (x86)/Inkscape/"
VISICUTDIR = ""
INKSCAPEDIR = ""
# wether to add (true) or replace (false) current visicut's content
IMPORT = True
# Store the IDs of selected Elements
elements = []
arguments=[]
for arg in sys.argv[1:]:
if arg[0] == "-":
if len(arg) >= 5 and arg[0:5] == "--id=":
elements += [arg[5:]]
elif len(arg) >= 13 and arg[0:13] == "--visicutbin=":
# unused
pass
# VISICUTBIN=arg[13:]
elif len(arg) >= 9 and arg[0:9] == "--import=":
IMPORT = "true" in arg[9:]
else:
arguments += [arg]
else:
filename = arg
if IMPORT:
# do not replace old file
arguments += ["--add"]
# find executable in the PATH
def which(program, extraPaths=[]):
pathlist = extraPaths + os.environ["PATH"].split(os.pathsep) + [""]
if "nt" in os.name: # Windows
if not program.lower().endswith(".exe"):
program += ".exe"
programfiles = os.environ.get("ProgramFiles", "C:\\Program Files\\")
programfiles86 = os.environ.get("ProgramFiles(x86)", "C:\\Program Files (x86)\\")
# also look in %ProgramFiles%/yourProgram/yourProgram.exe
pathlist += [programfiles + "\\" + program + "\\", programfiles86 + "\\" + program + "\\"]
def is_exe(fpath):
return os.path.isfile(fpath) and (os.access(fpath, os.X_OK) or fpath.endswith(".exe"))
for path in pathlist:
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
raise Exception("Cannot find executable {0} in PATH={1}.\n\n"
"Please report this bug on https://github.com/t-oster/VisiCut/issues\n\n"
"For a quick fix: Set VISICUTDIR and INKSCAPEDIR in "
"{2}"
.format(repr(program), repr(pathlist), os.path.realpath(__file__)))
def inkscape_version():
"""determine if Inkscape is version 0 or 1"""
version = subprocess.check_output([INKSCAPEBIN, "--version"], stderr=DEVNULL).decode('ASCII', 'ignore')
assert version.startswith("Inkscape ")
if version.startswith("Inkscape 0"):
return 0
else:
return 1
# Strip SVG to only contain selected elements, convert objects to paths, unlink clones
# Inkscape version: takes care of special cases where the selected objects depend on non-selected ones.
# Examples are linked clones, flowtext limited to a shape and linked flowtext boxes (overflow into the next box).
#
# Inkscape is called with certain "verbs" (gui actions) to do the required cleanup
# The idea is similar to http://bazaar.launchpad.net/~nikitakit/inkscape/svg2sif/view/head:/share/extensions/synfig_prepare.py#L181 , but more primitive - there is no need for more complicated preprocessing here
def stripSVG_inkscape(src, dest, elements):
version = inkscape_version()
# create temporary file for opening with inkscape.
# delete this file later so that it will disappear from the "recently opened" list.
tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix='temp-visicut-', suffix='.svg')
tmpfile.close()
tmpfile = tmpfile.name
import shutil
shutil.copyfile(src, tmpfile)
if version == 0:
# inkscape 0.92 long-term-support release. Will be in Linux distributions until 2025 or so
# Selection commands: select items, invert selection, delete
selection = []
for el in elements:
selection += ["--select=" + el]
if len(elements) > 0:
# selection += ["--verb=FitCanvasToSelection"] # TODO add a user configuration option whether to keep the page size (and by this the position relative to the page)
selection += ["--verb=EditInvertInAllLayers", "--verb=EditDelete"]
hidegui = ["--without-gui"]
# currently this only works with gui because of a bug in inkscape: https://bugs.launchpad.net/inkscape/+bug/843260
hidegui = []
command = [INKSCAPEBIN] + hidegui + [tmpfile, "--verb=UnlockAllInAllLayers", "--verb=UnhideAllInAllLayers"] + selection + ["--verb=EditSelectAllInAllLayers", "--verb=EditUnlinkClone", "--verb=ObjectToPath", "--verb=FileSave", "--verb=FileQuit"]
else:
# Inkscape 1.0, to be released ca 2020
# inkscape --select=... --verbs=...
# (see inkscape --help, inkscape --verb-list)
command = [INKSCAPEBIN, tmpfile, "--batch-process"]
verbs = ["ObjectToPath", "UnlockAllInAllLayers"]
if elements: # something is selected
# --select=object1,object2,object3,...
command += ["--select=" + ",".join(elements)]
else:
verbs += ["EditSelectAllInAllLayers"]
verbs += ["UnhideAllInAllLayers", "EditInvertInAllLayers", "EditDelete", "EditSelectAllInAllLayers", "EditUnlinkClone", "ObjectToPath", "FileSave"]
# --verb=action1;action2;...
command += ["--verb=" + ";".join(verbs)]
DEBUG = False
if DEBUG:
# Inkscape sometimes silently ignores wrong verbs, so we need to double-check that everything's right
for verb in verbs:
verb_list = [line.split(":")[0] for line in subprocess.check_output([INKSCAPEBIN, "--verb-list"], stderr=DEVNULL).split("\n")]
if verb not in verb_list:
sys.stderr.write("Inkscape does not have the verb '{}'. Please report this as a VisiCut bug.".format(verb))
inkscape_output = "(not yet run)"
try:
#sys.stderr.write(" ".join(command))
# run inkscape, buffer output
inkscape = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
inkscape_output = inkscape.communicate()[0]
if inkscape.returncode != 0:
sys.stderr.write("Error: cleaning the document with inkscape failed. Something might still be shown in visicut, but it could be incorrect.\nInkscape's output was:\n" + inkscape_output)
except:
sys.stderr.write("Error: cleaning the document with inkscape failed. Something might still be shown in visicut, but it could be incorrect. Exception information: \n" + str(sys.exc_info()[0]) + "Inkscape's output was:\n" + inkscape_output)
# move output to the intended destination filename
os.rename(tmpfile, dest)
"""
Get document name (original filename) from Inkscape SVG
Inkscape saves the file to a random temporary name.
However, the original filename is stored inside the SVG.
"""
def get_original_filename(filename):
docname = None
# parse SVG for docname tag
with codecs.open(filename, "r", encoding='utf-8') as f:
for line in f:
if 'sodipodi:docname="' in line:
matches = re.search('sodipodi:docname="(.*).svg"', line)
if not matches:
break
try:
docname = matches.group(1)
except IndexError:
# something is wrong with this line
break
# unescape XML string
docname = docname.replace('&lt;', '<')
docname = docname.replace('&gt;', '>')
docname = docname.replace('&quot;', '"')
docname = docname.replace('&amp;', '&')
# normalize accented characters (äöü -> aou)
docname = unicodedata.normalize('NFKD', docname).encode('ASCII', 'ignore').decode('ASCII')
break
if not docname:
# failed to read filename from SVG, return original one
docname = os.path.basename(filename)
if str.endswith(docname, ".svg"):
docname = docname[:-4]
if str.startswith(docname, "ink_ext_"):
# inkscape temporary file, the name is useless
docname = "new"
# sanitize the filename:
# filter out special characters (@/\& ...)
docname = "".join(x for x in docname if (x.isalnum() or x in "._- "))
docname = docname + ".svg"
return docname
# find executable paths
import platform
if platform.system() == 'Darwin':
VISICUTBIN = which("VisiCut.MacOS", [VISICUTDIR])
elif "nt" in os.name: # Windows
VISICUTBIN = which("VisiCut.exe", [VISICUTDIR])
else:
VISICUTBIN = which("VisiCut.Linux", [VISICUTDIR, "/usr/share/visicut"])
INKSCAPEBIN = which("inkscape", [INKSCAPEDIR])
tmpdir = tempfile.mkdtemp(prefix='temp-visicut-')
dest_filename = os.path.join(tmpdir, get_original_filename(filename))
# remove all non-selected elements and convert inkscape-specific elements (text-to-path etc.)
stripSVG_inkscape(src=filename, dest=dest_filename, elements=elements)
# Try to connect to running VisiCut instance
# Note: this step may be omitted, as VisiCut will do the same.
# However, doing it here saves 2-3 seconds of waiting time on Windows.
try:
import socket
s = socket.socket()
s.connect(("localhost", get_single_instance_port()))
if IMPORT:
s.send("@" + dest_filename + "\n")
else:
s.send(dest_filename + "\n")
s.close()
sys.exit(0)
except SystemExit as e:
sys.exit(e)
except:
pass
# Try to start own VisiCut instance
try:
daemonize = True
creationflags = 0
close_fds = False
if os.name == "nt":
DETACHED_PROCESS = 8 # start as "daemon"
creationflags = DETACHED_PROCESS
close_fds = True
else:
try:
import daemonize
daemonize.createDaemon()
except:
daemonize = False
cmd = [VISICUTBIN] + arguments + [dest_filename]
if daemonize is True:
Popen(cmd, creationflags=creationflags, close_fds=close_fds)
else:
Popen(cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=creationflags, close_fds=close_fds)
except:
sys.stderr.write("Can not start VisiCut (" + str(sys.exc_info()[0]) + "). Please start manually or change the VISICUTDIR variable in the Inkscape-Extension script\n")
# TODO (complicated, probably WONTFIX): cleanup temporary directories -- this is really difficult because we need to make sure that visicut no longer needs the file, even for reloading!