Skip to content

Commit

Permalink
Added support for Inkscape 1.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
juanitogan committed Jun 1, 2020
1 parent f93e969 commit 9695962
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 13 deletions.
51 changes: 38 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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.

Expand All @@ -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" ^
Expand All @@ -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:

Expand All @@ -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?
Expand Down Expand Up @@ -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.
File renamed without changes.
File renamed without changes.
32 changes: 32 additions & 0 deletions inkscape-1.0/slick_layer_combinator.inx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">

<name>SLiCk Layer Combinator</name>
<id>juanitogan.inkscape.slick_layer_combinator</id>

<dependency type="executable" location="extensions">slick_layer_combinator.py</dependency>

<label appearance="header">Option layers to cycle through:</label>
<param name="all" type="optiongroup" gui-text="">
<option value="true">Find and create exports for all option layers.</option>
<option value="false">Create exports for only the following option layer(s) . . . . . . . . . . . .</option>
</param>
<param name="layers" type="string" indent="3" gui-text="Layer names:" gui-description="Layer 1, Layer 2, etc"></param>
<label indent="4">Enter one or more layer names separated by commas.</label>
<separator />
<label appearance="header">File export path:</label>
<param name="directory" type="path" mode="folder" gui-text=""></param>

<effect needs-document="true" needs-live-preview="false">
<effects-menu hidden="false">
<submenu name="Export"/>
</effects-menu>
<menu-tip>Auto-sets layer visibility by naming convention and exports an Inkscape SVG for each option layer.</menu-tip>
<object-type>all</object-type>
</effect>

<script>
<command location="inx" interpreter="python">slick_layer_combinator.py</command>
</script>

</inkscape-extension>
201 changes: 201 additions & 0 deletions inkscape-1.0/slick_layer_combinator.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 9695962

Please sign in to comment.