2021-04-13 20:39:22 +02:00
#!/usr/bin/env python3
2021-04-09 19:13:32 +02:00
from copy import deepcopy
from pathlib import Path
import logging
import math
import os
2021-05-07 22:24:50 +02:00
import sys
2021-04-13 20:39:22 +02:00
import subprocess
from subprocess import Popen , PIPE
2021-05-07 22:24:50 +02:00
import warnings
2021-04-09 19:13:32 +02:00
import inkex
import inkex . command
2021-04-13 20:39:22 +02:00
from inkex . command import inkscape , inkscape_command
2021-04-09 19:13:32 +02:00
from lxml import etree
from scour . scour import scourString
2021-05-21 12:22:41 +02:00
'''
ToDo : work with temporary SVG file in temp dir instead handling deletion stuff at the end / valide that the file exists when spawning new instance
'''
2021-04-09 19:13:32 +02:00
logger = logging . getLogger ( __name__ )
2021-05-07 22:24:50 +02:00
DETACHED_PROCESS = 0x00000008
2021-04-09 19:13:32 +02:00
GROUP_ID = ' export_selection_transform '
class ExportObject ( inkex . EffectExtension ) :
2021-05-08 22:22:29 +02:00
def add_arguments ( self , pars ) :
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 ( " --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 ( " --dxf_exporter_path " , default = " /usr/share/inkscape/extensions/dxf_outlines.py " , help = " Location of dxf_outlines.py " )
2021-05-20 20:36:17 +02:00
pars . add_argument ( " --export_svg " , type = inkex . Boolean , default = False , help = " Create a svg file " )
2021-05-08 22:22:29 +02:00
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 " )
2021-05-27 13:22:30 +02:00
pars . add_argument ( " --newwindow " , type = inkex . Boolean , default = False , help = " Open file in new Inkscape window " )
2021-05-08 22:22:29 +02:00
def openExplorer ( self , dir ) :
if os . name == ' nt ' :
Popen ( [ " explorer " , dir ] , close_fds = True , creationflags = DETACHED_PROCESS ) . wait ( )
else :
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
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 )
else :
2021-05-20 14:38:01 +02:00
subprocess . Popen ( [ " inkscape " , file ] , start_new_session = True , stdout = subprocess . DEVNULL , stderr = subprocess . DEVNULL )
2021-05-08 22:22:29 +02:00
warnings . simplefilter ( " default " , ResourceWarning )
def effect ( self ) :
2021-05-20 20:36:17 +02:00
svg_export = self . options . export_svg
if self . options . export_svg is False and \
self . options . export_dxf is False and \
self . options . export_pdf is False and \
self . options . newwindow is False :
inkex . utils . debug ( " You must select at least one option to continue! " )
return
else :
self . options . export_svg = True #required for all other options!
2021-05-08 22:22:29 +02:00
if not self . svg . selected :
inkex . errormsg ( " Selection is empty. Please select some objects first! " )
return
#preflight check for DXF input dir
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. " )
exit ( 1 )
export_dir = Path ( self . absolute_href ( self . options . export_dir ) )
os . makedirs ( export_dir , exist_ok = True )
offset = self . options . border_offset
bbox = inkex . BoundingBox ( )
for elem in self . svg . selected . values ( ) :
transform = inkex . Transform ( )
parent = elem . getparent ( )
if parent is not None and isinstance ( parent , inkex . ShapeElement ) :
transform = parent . composed_transform ( )
try :
bbox + = elem . bounding_box ( transform )
except Exception :
logger . exception ( " Bounding box not computed " )
logger . info ( " Skipping bounding box " )
transform = elem . composed_transform ( )
x1 , y1 = transform . apply_to_point ( [ 0 , 0 ] )
x2 , y2 = transform . apply_to_point ( [ 1 , 1 ] )
bbox + = inkex . BoundingBox ( ( x1 , x2 ) , ( y1 , y2 ) )
template = self . create_document ( )
2021-05-20 20:36:17 +02:00
svg_filename = None
2021-05-08 22:22:29 +02:00
group = etree . SubElement ( template , ' { http://www.w3.org/2000/svg}g ' )
group . attrib [ ' id ' ] = GROUP_ID
group . attrib [ ' transform ' ] = str ( inkex . Transform ( ( ( 1 , 0 , - bbox . left ) , ( 0 , 1 , - bbox . top ) ) ) )
for elem in self . svg . selected . values ( ) :
elem_copy = deepcopy ( elem )
elem_copy . attrib [ ' transform ' ] = str ( elem . composed_transform ( ) )
elem_copy . attrib [ ' style ' ] = str ( elem . composed_style ( ) )
group . append ( elem_copy )
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 [ ' height ' ] = f ' { bbox . height + offset * 2 } ' + self . svg . unit
2021-05-20 20:36:17 +02:00
if svg_filename is None :
filename_base = elem . attrib . get ( ' id ' , None ) . replace ( os . sep , ' _ ' )
if filename_base :
svg_filename = filename_base + ' .svg '
if not filename_base : #should never be the case. Inkscape might crash if the id attribute is empty or not existent due to invalid SVG
filename_base = self . svg . get_unique_id ( " selection " )
svg_filename = filename_base + ' .svg '
2021-05-08 22:22:29 +02:00
template . append ( group )
if not self . options . wrap_transform :
self . load ( inkscape_command ( template . tostring ( ) , select = GROUP_ID , verbs = [ ' SelectionUnGroup ' ] ) )
template = self . svg
for child in template . getchildren ( ) :
if child . tag == ' { http://www.w3.org/2000/svg}metadata ' :
template . remove ( child )
2021-05-20 20:36:17 +02:00
if self . options . export_svg is True :
self . save_document ( template , export_dir / svg_filename )
2021-05-08 22:22:29 +02:00
if self . options . opendir is True :
self . openExplorer ( export_dir )
if self . options . newwindow is True :
2021-05-20 20:36:17 +02:00
#inkscape(os.path.join(export_dir, svg_filename)) #blocking cmd
self . spawnIndependentInkscape ( os . path . join ( export_dir , svg_filename ) ) #non-blocking
2021-05-08 22:22:29 +02:00
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
cmd = [
sys . executable , #the path of the python interpreter which is used for this script
self . options . dxf_exporter_path ,
2021-05-20 20:36:17 +02:00
' --output= ' + os . path . join ( export_dir , filename_base + ' .dxf ' ) ,
2021-05-08 22:22:29 +02:00
r ' --units=25.4/96 ' ,
2021-05-20 20:36:17 +02:00
os . path . join ( export_dir , svg_filename )
2021-05-08 22:22:29 +02:00
]
proc = Popen ( cmd , shell = False , stdout = PIPE , stderr = PIPE )
stdout , stderr = proc . communicate ( )
if proc . returncode != 0 :
inkex . utils . debug ( " %d %s %s " % ( proc . returncode , stdout , stderr ) )
if self . options . export_pdf is True :
2021-05-20 20:36:17 +02:00
cli_output = inkscape ( os . path . join ( export_dir , svg_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_base + ' .pdf ' ) ) )
2021-05-08 22:22:29 +02:00
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 )
2021-05-21 12:22:41 +02:00
if svg_export is False and self . options . newwindow is False : #we need the SVG file in case we open in new window because we spawn a new instance
2021-05-20 20:36:17 +02:00
os . remove ( os . path . join ( export_dir , svg_filename ) ) #remove SVG if not enabled to export. Might delete existing SVG accidently!
2021-05-08 22:22:29 +02:00
def create_document ( self ) :
document = self . svg . copy ( )
for child in document . getchildren ( ) :
if child . tag == ' { http://www.w3.org/2000/svg}defs ' :
continue
document . remove ( child )
return document
def save_document ( self , document , filename ) :
with open ( filename , ' wb ' ) as fp :
document = document . tostring ( )
fp . write ( scourString ( document ) . encode ( ' utf8 ' ) )
2021-04-09 19:13:32 +02:00
if __name__ == ' __main__ ' :
2021-05-08 22:22:29 +02:00
ExportObject ( ) . run ( )