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

180 lines
6.3 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (C) 2006 Aaron Spike, aaron@ekips.org
# Copyright (C) 2010-2012 Nicolas Dufour, nicoduf@yahoo.fr
# (Windows support and various fixes)
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
"""
Export to Gimp's XCF file format including Grids and Guides.
"""
import os
from collections import OrderedDict
import inkex
from inkex.base import TempDirMixin
from inkex.command import take_snapshot, call
from inkex.localization import inkex_gettext as _
class PSDExport(TempDirMixin, inkex.OutputExtension):
"""
Provide a quick and dirty way of using gimp to output an xcf from Inkscape.
Both Inkscape and Gimp must be installed for this extension to work.
"""
dir_prefix = 'gimp-out-'
def add_arguments(self, pars):
pars.add_argument("--tab")
pars.add_argument("-d", "--guides", type=inkex.Boolean, help="Save Guides")
pars.add_argument("-r", "--grid", type=inkex.Boolean, help="Save Grid")
pars.add_argument("-b", "--background", type=inkex.Boolean, help="Save background color")
pars.add_argument("-i", "--dpi", type=float, default=96.0, help="File resolution")
def get_guides(self):
"""Generate a list of horzontal and vertical only guides"""
horz_guides = []
vert_guides = []
# Grab all guide tags in the namedview tag
for guide in self.svg.namedview.get_guides():
if guide.is_horizontal:
# GIMP doesn't like guides that are outside of the image
if 0 < guide.point.y < self.svg.height:
# The origin is at the top in GIMP land
horz_guides.append(str(guide.point.y))
elif guide.is_vertical:
# GIMP doesn't like guides that are outside of the image
if 0 < guide.point.x < self.svg.width:
vert_guides.append(str(guide.point.x))
return ('h', ' '.join(horz_guides)), ('v', ' '.join(vert_guides))
def get_grid(self):
"""Get the grid if asked for and return as gimpfu script"""
scale = (self.svg.scale) * (self.options.dpi / 96.0)
# GIMP only allows one rectangular grid
xpath = "sodipodi:namedview/inkscape:grid[@type='xygrid' and (not(@units) or @units='px')]"
if self.svg.xpath(xpath):
node = self.svg.getElement(xpath)
for attr, default, target in (('spacing', 1, 'spacing'), ('origin', 0, 'offset')):
fmt = {'target': target}
for dim in 'xy':
# These attributes could be nonexistent
unit = float(node.get(attr + dim, default))
unit = self.svg.uutounit(unit, "px") * scale
fmt[dim] = int(round(float(unit)))
yield '(gimp-image-grid-set-{target} img {x} {y})'.format(**fmt)
@property
def docname(self):
"""Get the document name suitable for export"""
return self.svg.get('sodipodi:docname') or 'document'
def save(self, stream):
pngs = OrderedDict()
valid = False
for node in self.svg.xpath("/svg:svg/*[name()='g' or @style][@id]"):
if not len(node): # pylint: disable=len-as-condition
# Ignore empty layers
continue
valid = True
node_id = node.get('id')
name = node.get("inkscape:label", node_id)
pngs[name] = take_snapshot(
self.document,
dirname=self.tempdir,
name=name,
dpi=int(self.options.dpi),
export_id=node_id,
export_id_only=True,
export_area_page=True,
export_background_opacity=int(bool(self.options.background))
)
if not valid:
inkex.errormsg(_('This extension requires at least one non empty layer.'))
return
xcf = os.path.join(self.tempdir, "{}.psd".format(self.docname))
script_fu = """
(tracing 1)
(define
(png-to-layer img png_filename layer_name)
(let*
(
(png (car (file-png-load RUN-NONINTERACTIVE png_filename png_filename)))
(png_layer (car (gimp-image-get-active-layer png)))
(xcf_layer (car (gimp-layer-new-from-drawable png_layer img)))
)
(gimp-image-add-layer img xcf_layer -1)
(gimp-drawable-set-name xcf_layer layer_name)
)
)
(let*
(
(img (car (gimp-image-new 200 200 RGB)))
)
(gimp-image-set-resolution img {dpi} {dpi})
(gimp-image-undo-disable img)
(for-each
(lambda (names)
(png-to-layer img (car names) (cdr names))
)
(map cons '("{files}") '("{names}"))
)
(gimp-image-resize-to-layers img)
""".format(
dpi=self.options.dpi,
files='" "'.join(pngs.values()),
names='" "'.join(list(pngs))
)
if self.options.guides:
for dim, guides in self.get_guides():
script_fu += """
(for-each
(lambda ({d}Guide)
(gimp-image-add-{d}guide img {d}Guide)
)
'({g})
)""".format(d=dim, g=guides)
# Grid
if self.options.grid:
for fu_let in self.get_grid():
script_fu += "\n" + fu_let + "\n"
script_fu += """
(gimp-image-undo-enable img)
(file-psd-save RUN-NONINTERACTIVE img (car (gimp-image-get-active-layer img)) "{xcf}" "{xcf}" 0 0))
(gimp-quit 0)
""".format(xcf=xcf)
#please install with apt/dnf to install gimp. Do not use snap or flatpak installations
call('gimp', "-b", "-", i=True, batch_interpreter="plug-in-script-fu-eval", stdin=script_fu)
with open(xcf, 'rb') as fhl:
stream.write(fhl.read())
if __name__ == '__main__':
PSDExport().run()