Code

Translations. POTFILES.in cleanup and inkscape.pot update.
[inkscape.git] / share / extensions / guillotine.py
1 #!/usr/bin/env python
2 '''
3 guillotine.py
5 Copyright (C) 2010 Craig Marshall, craig9 [at] gmail.com
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
21 -----------------------
23 This script slices an inkscape drawing along the guides, similarly to
24 the GIMP plugin called "guillotine". It can optionally export to the
25 same directory as the SVG file with the same name, but with a number
26 suffix. e.g.
28 /home/foo/drawing.svg
30 will export to:
32 /home/foo/drawing0.png
33 /home/foo/drawing1.png
34 /home/foo/drawing2.png
35 /home/foo/drawing3.png
37 etc.
39 '''
41 import os
42 import sys
43 import inkex
44 import simplestyle
45 import locale
46 import gettext
47 _ = gettext.gettext
49 locale.setlocale(locale.LC_ALL, '')
51 try:
52     from subprocess import Popen, PIPE
53     bsubprocess = True
54 except:
55     bsubprocess = False
57 def float_sort(a, b):
58     '''
59     This is used to sort the horizontal and vertical guide positions,
60     which are floating point numbers, but which are held as text.
61     '''
62     return cmp(float(a), float(b))
64 class Guillotine(inkex.Effect):
65     """Exports slices made using guides"""
66     def __init__(self):
67         inkex.Effect.__init__(self)
68         self.OptionParser.add_option("--directory", action="store",
69                                         type="string", dest="directory",
70                                         default=None, help="")
72         self.OptionParser.add_option("--image", action="store",
73                                         type="string", dest="image",
74                                         default=None, help="")
76         self.OptionParser.add_option("--ignore", action="store",
77                                         type="inkbool", dest="ignore",
78                                         default=None, help="")
80     def get_guides(self):
81         '''
82         Returns all guide elements as an iterable collection
83         '''
84         root = self.document.getroot()
85         guides = []
86         xpath = self.document.xpath("//sodipodi:guide",
87                                     namespaces=inkex.NSS)
88         for g in xpath:
89             guide = {}
90             (x, y) = g.attrib['position'].split(',')
91             if g.attrib['orientation'] == '0,1':
92                 guide['orientation'] = 'horizontal'
93                 guide['position'] = y
94                 guides.append(guide)
95             elif g.attrib['orientation'] == '1,0':
96                 guide['orientation'] = 'vertical'
97                 guide['position'] = x
98                 guides.append(guide)
99         return guides
101     def get_all_horizontal_guides(self):
102         '''
103         Returns all horizontal guides as a list of floats stored as
104         strings. Each value is the position from 0 in pixels.
105         '''
106         guides = []
107         for g in self.get_guides():
108             if g['orientation'] == 'horizontal':
109                 guides.append(g['position'])
110         return guides
112     def get_all_vertical_guides(self):
113         '''
114         Returns all vertical guides as a list of floats stored as
115         strings. Each value is the position from 0 in pixels.
116         '''
117         guides = []
118         for g in self.get_guides():
119             if g['orientation'] == 'vertical':
120                 guides.append(g['position'])
121         return guides
123     def get_horizontal_slice_positions(self):
124         '''
125         Make a sorted list of all horizontal guide positions,
126         including 0 and the document height, but not including
127         those outside of the canvas
128         '''
129         root = self.document.getroot()
130         horizontals = ['0']
131         height = inkex.unittouu(root.attrib['height'])
132         for h in self.get_all_horizontal_guides():
133             if h >= 0 and float(h) <= float(height):
134                 horizontals.append(h)
135         horizontals.append(height)
136         horizontals.sort(cmp=float_sort)
137         return horizontals
139     def get_vertical_slice_positions(self):
140         '''
141         Make a sorted list of all vertical guide positions,
142         including 0 and the document width, but not including
143         those outside of the canvas.
144         '''
145         root = self.document.getroot()
146         verticals = ['0']
147         width = inkex.unittouu(root.attrib['width'])
148         for v in self.get_all_vertical_guides():
149             if v >= 0 and float(v) <= float(width):
150                 verticals.append(v)
151         verticals.append(width)
152         verticals.sort(cmp=float_sort)
153         return verticals
155     def get_slices(self):
156         '''
157         Returns a list of all "slices" as denoted by the guides
158         on the page. Each slice is really just a 4 element list of
159         floats (stored as strings), consisting of the X and Y start
160         position and the X and Y end position.
161         '''
162         hs = self.get_horizontal_slice_positions()
163         vs = self.get_vertical_slice_positions()
164         slices = []
165         for i in range(len(hs)-1):
166             for j in range(len(vs)-1):
167                 slices.append([vs[j], hs[i], vs[j+1], hs[i+1]])
168         return slices
170     def get_filename_parts(self):
171         '''
172         Attempts to get directory and image as passed in by the inkscape
173         dialog. If the boolean ignore flag is set, then it will ignore
174         these settings and try to use the settings from the export
175         filename.
176         '''
178         if self.options.ignore == False:
179             if self.options.image == "" or self.options.image is None:
180                 inkex.errormsg("Please enter an image name")
181                 sys.exit(0)
182             return (self.options.directory, self.options.image)
183         else:
184             '''
185             First get the export-filename from the document, if the
186             document has been exported before (TODO: Will not work if it
187             hasn't been exported yet), then uses this to return a tuple
188             consisting of the directory to export to, and the filename
189             without extension.
190             '''
191             svg = self.document.getroot()
192             att = '{http://www.inkscape.org/namespaces/inkscape}export-filename'
193             try:
194                 export_file = svg.attrib[att]
195             except KeyError:
196                 inkex.errormsg("To use the export hints option, you " +
197                 "need to have previously exported the document. " +
198                 "Otherwise no export hints exist!")
199                 sys.exit(-1)
200             dirname, filename = os.path.split(export_file)
201             filename = filename.rsplit(".", 1)[0] # Without extension
202             return (dirname, filename)
204     def check_dir_exists(self, dir):
205         if not os.path.isdir(dir):
206             os.makedirs(dir)
208     def get_localised_string(self, str):
209         return locale.format("%.f", float(str), 0)
211     def export_slice(self, s, filename):
212         '''
213         Runs inkscape's command line interface and exports the image
214         slice from the 4 coordinates in s, and saves as the filename
215         given.
216         '''
217         svg_file = self.args[-1]
218         command = "inkscape -a %s:%s:%s:%s -e \"%s\" \"%s\" " % (self.get_localised_string(s[0]), self.get_localised_string(s[1]), self.get_localised_string(s[2]), self.get_localised_string(s[3]), filename, svg_file)
219         if bsubprocess:
220             p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
221             return_code = p.wait()
222             f = p.stdout
223             err = p.stderr
224         else:
225             _, f, err = os.open3(command)
226         f.close()
228     def export_slices(self, slices):
229         '''
230         Takes the slices list and passes each one with a calculated
231         filename/directory into export_slice.
232         '''
233         dirname, filename = self.get_filename_parts()
234         output_files = list()
235         if dirname == '' or dirname == None:
236             dirname = './'
238         dirname = os.path.expanduser(dirname)
239         dirname = os.path.expandvars(dirname)
240         dirname = os.path.abspath(dirname)
241         if dirname[-1] != os.path.sep:
242             dirname += os.path.sep
243         self.check_dir_exists(dirname)
244         i = 0
245         for s in slices:
246             f = dirname + filename + str(i) + ".png"
247             output_files.append(f)
248             self.export_slice(s, f)
249             i += 1
250         inkex.errormsg(_("The sliced bitmaps have been saved as:") + "\n\n" + "\n".join(output_files))
252     def effect(self):
253         slices = self.get_slices()
254         self.export_slices(slices)
256 if __name__ == "__main__":
257     e = Guillotine()
258     e.affect()