diff --git a/README.md b/README.md index 50e7e05..8859025 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Slick uses convention over configuration wherever Slick can. Logical, pretty co | **()** parenthesis | (ref art) | Identifies a layer that will always be hidden. Must be the first and last character. | | **!** exclamation point | Background! | Identifies a layer that will always be shown. Can appear anywhere in the layer name: `Here!`, `h!r!`, `!! Here`, `--here!--`, etc. But, is meaningless inside parenthesis like `(Here!)`. | | **-- --** double dashes | --Fred's dialog-- | Identifies a parent layer to option layers. Think of it as a layer containing a list of versions of the art. **Do not use special characters (such as slashes /\\) in the names of the child option layers that would not be valid in a filename.** | -| **,** comma | Red, Blue | When appearing in the name of an option layer, identifies an option layer that will show for more than one option. The list can be as long as what will fit in a layer name. | +| **,** comma | Red, Blue | When appearing in the name of an option layer, identifies an option layer that will show for more than one option. The list can be as long as what will fit in a layer name. | | | Hello | Any layer not named as above--and not an option layer under a parent layer--will keep it's visibility as is. | Pretty simple... er... slick, eh? @@ -71,17 +71,13 @@ Slick output: - Cartoon_French.svg - Cartoon_German.svg -### Python warning! - -Eeek! A snake! Due to a funky nuance of the Python language (that I won't bore you with the details of) you cannot end the output directory string with a backslash like so: `C:\GRITS Racing\Art\Car\` That really messes with the python's brain. (Probably the fault of the writer of the OptionParser package, really.) So, leave it off (preferred), use a forward slash (platform doesn't matter with Slick), or use a double backslash (weirdly enough). No worries, Slick is slick enough to add a slash if Slick can't find one. So, enter your path like so: `C:\GRITS Racing\Art\Car` - ### Bonus feature (sort of) If your filename ends with `__master__` or `__MASTER__` or any case form such as `__MAsTer__`, Slick will strip this from the output filename. For example, a source filename of `Cartoon__master__.svg` will still output the same filenames exampled above. (But, who names a file like this other than me???) ## Installation -1. Download the latest release from the [Releases page](https://github.com/juanitogan/slick/releases). (Or, any other way you like to git yer GitHub files.) +1. Download the latest release from the [Releases page](https://github.com/juanitogan/slick/releases). (Or, any other way you like to git yer GitHub files.) Be sure to choose the Slick release appropriate for your Inkscape version: v0.92.x, or v1.0 and later. 2. Unzip the INX and PY files to your Inkscape "User extensions" folder. You can find that location in Inkscape here: Edit > Preferences > System. @@ -107,11 +103,13 @@ Slick output: - Car_270.svg - Car_315.svg -## Pipline automation +## Pipeline automation -I didn't find any help on the net for command-line control of Inkscape extension scripts (the one's that require parameters like Slick does)... (why?) but this is how I figured it can be done. Here's an example DOS batch file: +I didn't find any help on the net for command-line control of Inkscape extension scripts (the one's that require parameters like Slick does)... (why?) but, after reasoning it out, I eventually got it. Here's an example DOS batch file: -``` +### Inkscape 0.92 + +```batch @echo off pushd "C:\Program Files\Inkscape\share\extensions" "C:\Program Files\Inkscape\python.exe" ^ @@ -122,7 +120,25 @@ pushd "C:\Program Files\Inkscape\share\extensions" popd ``` -Basically, what the above does is run Inkscape's copy of Python from Inkscape's `extensions` folder. It is required to run from here so that Slick's script can pick up the Inkscape packages needed to do Slick's thing. Then, because this is running from a far-away folder in a far-away land, you must fully qualify the output directory and the master SVG file. +Basically, what the above does is run Inkscape's copy of Python from Inkscape's `extensions` folder. Python is required to run from here so that Slick's script can pick up the Inkscape packages needed to do Slick's thing. Then, because this is running from a far-away folder in a far-away land, you must fully qualify the output directory and the master SVG file. + +### Inkscape 1.0 + +```batch +@echo off +pushd "C:\Program Files\Inkscape\bin" +setlocal +set PYTHONPATH=C:\Program Files\Inkscape\share\inkscape\extensions +python ^ + "%USERPROFILE%\AppData\Roaming\inkscape\extensions\slick_layer_combinator.py" ^ + -a true ^ + -d "C:\GRITS Racing\Art\Car" ^ + "C:\GRITS Racing\Art\Car\Car__master__.svg" +endlocal +popd +``` + +Inkscape appears to be dead set on making this as hard to figure out as possible. Now, (after hacking 1.0 for intel) it seems we have to run from the `bin` folder to find the DLLs and add Inkscape's `extensions` folder to the Python path. Let's hope they don't change it up again. **slick_layer_combinator.py** parameters: @@ -134,6 +150,10 @@ Basically, what the above does is run Inkscape's copy of Python from Inkscape's All parameters are optional but you really need to specify `-d` if you don't want to go hunting for your files in some far-away folder. Use either `-a` or `-l` if want a meaningful file output... otherwise, you'll get a `*_none.svg` file with all option layers hidden. +### Python warning! + +Eeek! A snake! Due to a funky nuance of the Python language (that I won't bore you with the details of) you cannot end the output directory string with a backslash like so: `C:\GRITS Racing\Art\Car\`. That really messes with the python's brain. (Probably the fault of the writer of the OptionParser and arg_parser packages, really.) So, leave off the final backslash like so: `C:\GRITS Racing\Art\Car`. No worries, Slick is slick enough to add a slash if Slick can't find one. If you must, you could end with a forward slash (slash direction doesn't matter with Slick) or a double backslash (weirdly enough). + ## FAQ **Q:** Can I use other layer hierarchies in my drawing? @@ -161,7 +181,12 @@ All parameters are optional but you really need to specify `-d` if you don't wan ### To-not-do list -- Add options for PNG or other format exports. - - Inkscape SVGs only. Producing PNGs and such are what your pipeline scripts are for, of which, Slick is only just another slick tool in your slick toolset. +- Add options for PNG export (and/or other formats). + - Nope: Inkscape SVGs only. + - Producing PNGs and such are what your pipeline scripts are for. Of which, Slick is only just another slick tool in your slick toolset. + - For example, after using Slick to generate a bunch of SVGs, you may then want to run a script that uses the [Inkscape command-line](https://wiki.inkscape.org/wiki/index.php?title=Using_the_Command_Line) to export a PNG for each. + - It is also worth noting that [ImageMagick](https://imagemagick.org/) and [GIMP](https://www.gimp.org/) use the same rendering library for SVGs as Inkscape. I use ImageMagick in some of my scripts. (ImageMagick also has a GhostScript rendering option, if really wanting something different and less nice.) + - Most of my SVGs, however, I can simply import into Unity via [SVG Importer](http://svgimporter.com/), which converts them to meshes. Other tools can do similar mesh conversions. + - Note to self: Blog about how to squeeze a bit more image quality out of PNGs with ImageMagick and oversampling. Note also that Inkscape 1.0 PNG export now has a much-wanted antialiasing setting with a few quality options. -- Well, maybe add plain SVG export... okay, no! +- Well, maybe add plain SVG export... um... nope. diff --git a/extensions/slick_layer_combinator.inx b/inkscape-0.92/slick_layer_combinator.inx similarity index 100% rename from extensions/slick_layer_combinator.inx rename to inkscape-0.92/slick_layer_combinator.inx diff --git a/extensions/slick_layer_combinator.py b/inkscape-0.92/slick_layer_combinator.py similarity index 100% rename from extensions/slick_layer_combinator.py rename to inkscape-0.92/slick_layer_combinator.py diff --git a/inkscape-1.0/slick_layer_combinator.inx b/inkscape-1.0/slick_layer_combinator.inx new file mode 100644 index 0000000..279e6fd --- /dev/null +++ b/inkscape-1.0/slick_layer_combinator.inx @@ -0,0 +1,32 @@ + + + + SLiCk Layer Combinator + juanitogan.inkscape.slick_layer_combinator + + slick_layer_combinator.py + + + + + + + + + + + + + + + Auto-sets layer visibility by naming convention and exports an Inkscape SVG for each option layer. + all + + + + + diff --git a/inkscape-1.0/slick_layer_combinator.py b/inkscape-1.0/slick_layer_combinator.py new file mode 100644 index 0000000..caa5fc0 --- /dev/null +++ b/inkscape-1.0/slick_layer_combinator.py @@ -0,0 +1,201 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +""" +------------------------------------------------------------------------------- +SLiCk : the Slick Layer Combinator +------------------------------------------------------------------------------- + +Auto-sets layer visibility (by naming convention) on a copy of the drawing +and exports an Inkscape SVG for each option layer processed. + +Copyright (c) 2019-2020 Matt Jernigan + +------------------------------------------------------------------------------- +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 inkex # Required +from copy import deepcopy +import os + +__version__ = '0.1.1' + +inkex.localization.localize() + +### Helper functions ### + +def getLayers (element): + layers = element.xpath(".//svg:g[@inkscape:groupmode='layer']", namespaces=inkex.NSS) + return layers + +def displayLayer(layer, display): + if "style" in layer.attrib: + style = dict(inkex.Style.parse_str(layer.attrib["style"])) + else: + style = {} + + if display: + style["display"] = "inline" + else: + style["display"] = "none" + + layer.attrib["style"] = str(inkex.Style(style)) + + +### Main function ### + +class Combinator (inkex.Effect): + + def __init__(self): + """ define how the options are mapped from the inx file """ + inkex.Effect.__init__(self) # initialize the super class + + # Two ways to get debug info: + # OR just use inkex.debug(string) instead... + + # Define your list of parameters defined in the .inx file + self.arg_parser.add_argument("-a", "--all" + , type = inkex.Boolean + , dest = "all" + , default = False + , help = "Find and export all option layers" + ) + self.arg_parser.add_argument("-l", "--layers" + , type = str + , dest = "layers" + , default = "" + , help = "Comma-separated list of option layers to export" + ) + self.arg_parser.add_argument("-d", "--directory" + , type = str + , dest = "directory" + , default = None + , help = "Path to save files to (supports ~ on Windows too)" + ) + # Support for ~ is still true on the command line even though + # Inkscape's new path type parameter in the .inx file doesn't like it. + + + ### ------------------------------------------------------------------- + ### This is your main function and is called when the extension is run. + def effect(self): + """ + Turn layer visibility on/off according to naming conventions + and export a new drawing for each option layer sent or found. + """ + + # Make a copy of the doc to operate on, to leave original undisturbed. + tempdoc = deepcopy(self.document) + svg = tempdoc.getroot() + + # Determine the base filename. + docname = svg.xpath("@sodipodi:docname", namespaces=inkex.NSS)[0] + filename = docname.rsplit(".", 1)[0] + if filename.upper().endswith("__MASTER__"): + filename = filename[:-10] + if filename == "": + filename = "slick" # just overly cautious; "default" is the name until save + inkex.errormsg(_("Warning: no filename found, saving as \"{}_*.svg\".".format(filename))) + + # Cleanup path string. + #inkex.debug(os.getcwd()) + dirname = self.options.directory + if dirname == '' or dirname == None: + dirname = './' + dirname = os.path.expanduser(dirname) + dirname = os.path.expandvars(dirname) + dirname = os.path.abspath(dirname) + if dirname[-1] != os.path.sep: + dirname += os.path.sep + if not os.path.isdir(dirname): + os.makedirs(dirname) + + # Begin layer work. + allLayers = getLayers(svg) + optionLayers = [] + + # Get the names of the option layers to cycle through. + if self.options.all: + # Search the whole tree for unique layer names in option layers. + for l in allLayers: + label = l.xpath("@inkscape:label", namespaces=inkex.NSS)[0] + #inkex.debug(label) + # Is this a parent layer of option layers? eg: --my options parent-- + if label.startswith("--") and label.endswith("--"): + childLayers = getLayers(l) + for cl in childLayers: + # Layer names can have comma-delimited option names; parse them. + names = cl.xpath("@inkscape:label", namespaces=inkex.NSS)[0].split(",") + for n in names: + n = n.strip() + if n not in optionLayers: + optionLayers.append(n) + elif len(self.options.layers.strip()) > 0: + optionLayers = self.options.layers.split(",") + #map(str.strip, optionLayers) + optionLayers = [i.strip() for i in optionLayers] + #TODO handle empty strings between commas + optionLayers.sort() + + # Make sure we have work to do. + #if len(optionLayers) == 0: + # inkex.errormsg(_("No option layers found to create exports with.")) + # exit() + # + # Nevermind, we can always process the other layer types (not sure the use case). + if len(optionLayers) == 0: + optionLayers = ["none"] + + # Do it. + # First, manipulate the non-option layers. + optionParents = [] + for l in allLayers: + label = l.xpath("@inkscape:label", namespaces=inkex.NSS)[0] + + # Is this an always-hidden layer? eg: (hide me) + if label.startswith("(") and label.endswith(")"): + displayLayer(l, False) + + # Is this an always-shown layer? eg: Show Me! + elif label.find("!") >= 0: + displayLayer(l, True) + + # Is this a parent layer of option layers? eg: --my options parent-- + if label.startswith("--") and label.endswith("--"): + optionParents.append(l) + + # Second, cycle through the option layers and save each result. + for option in optionLayers: + for p in optionParents: + childLayers = getLayers(p) + for cl in childLayers: + # Layer names can have comma-delimited option names; parse them. + names = cl.xpath("@inkscape:label", namespaces=inkex.NSS)[0].split(",") + names = [n.strip() for n in names] # strip any spaces before search + displayLayer(cl, option in names) + + totalname = filename + "_" + option + ".svg" + tempdoc.write(dirname + totalname + , xml_declaration=True + , encoding=self.document.docinfo.encoding + , standalone=self.document.docinfo.standalone + , pretty_print=True + ) + inkex.utils.debug("Created file: " + totalname) + +if __name__ == '__main__': + e = Combinator() + e.run()