summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaalleen <36401965+kaalleen@users.noreply.github.com>2021-03-04 18:40:53 +0100
committerGitHub <noreply@github.com>2021-03-04 18:40:53 +0100
commite84a86d4ac0caf29d6074728376ff0a594243fec (patch)
tree888c79ed0094ba2916a1d329861a85515959913c
parentb39575a50191307b3b56eab6455626398eec6397 (diff)
Update for Inkscape 1.0 (#880)
* update for inkscape 1.0 * add about extension * Build improvements for the inkscape1.0 branch (#985) * zip: export real svg not stitch plan * #411 and #726 * Tools for Font Creators (#1018) * ignore very small holes in fills * remove embroider (#1026) * auto_fill: ignore shrink_or_grow if result is empty (#589) * break apart: do not ignore small fills Co-authored-by: Hagen Fritsch <rumpeltux-github@irgendwo.org> Co-authored-by: Lex Neva <github.com@lexneva.name>
-rw-r--r--.github/workflows/build.yml127
-rw-r--r--.gitignore1
-rw-r--r--Makefile8
-rwxr-xr-xbin/build-distribution-archives10
-rwxr-xr-xbin/build-python5
-rwxr-xr-xbin/generate-version-file10
-rwxr-xr-xbin/inkstitch-fonts-gettext8
-rwxr-xr-xbin/pyembroidery-gettext4
-rw-r--r--electron/.electron-vue/webpack.renderer.config.js18
-rw-r--r--electron/.electron-vue/webpack.web.config.js18
-rw-r--r--electron/src/lib/i18n.js28
-rw-r--r--electron/src/main/index.js2
-rw-r--r--electron/src/renderer/assets/js/simulator.js2
-rw-r--r--electron/src/renderer/assets/style/simulator.css28
-rw-r--r--electron/src/renderer/components/Simulator.vue6
-rw-r--r--electron/src/renderer/main.js2
-rw-r--r--icons/inkstitch_colour_logo.svg27
-rw-r--r--inkstitch.py30
-rw-r--r--lib/api/install.py2
-rw-r--r--lib/api/server.py9
-rw-r--r--lib/api/stitch_plan.py4
-rw-r--r--lib/commands.py88
-rw-r--r--lib/debug.py37
-rw-r--r--lib/elements/__init__.py22
-rw-r--r--lib/elements/auto_fill.py28
-rw-r--r--lib/elements/clone.py75
-rw-r--r--lib/elements/element.py56
-rw-r--r--lib/elements/fill.py6
-rw-r--r--lib/elements/image.py14
-rw-r--r--lib/elements/polyline.py7
-rw-r--r--lib/elements/satin_column.py47
-rw-r--r--lib/elements/stroke.py10
-rw-r--r--lib/elements/svg_objects.py71
-rw-r--r--lib/elements/text.py14
-rw-r--r--lib/extensions/__init__.py57
-rw-r--r--lib/extensions/auto_satin.py4
-rw-r--r--lib/extensions/base.py45
-rw-r--r--lib/extensions/break_apart.py12
-rw-r--r--lib/extensions/cleanup.py19
-rw-r--r--lib/extensions/commands.py4
-rw-r--r--lib/extensions/convert_to_satin.py35
-rw-r--r--lib/extensions/cut_satin.py6
-rw-r--r--lib/extensions/embroider.py87
-rw-r--r--lib/extensions/embroider_settings.py17
-rw-r--r--lib/extensions/flip.py9
-rw-r--r--lib/extensions/import_threadlist.py19
-rw-r--r--lib/extensions/input.py11
-rw-r--r--lib/extensions/layer_commands.py30
-rw-r--r--lib/extensions/lettering.py82
-rw-r--r--lib/extensions/lettering_custom_font_dir.py48
-rw-r--r--lib/extensions/lettering_generate_json.py76
-rw-r--r--lib/extensions/lettering_remove_kerning.py30
-rw-r--r--lib/extensions/object_commands.py2
-rw-r--r--lib/extensions/output.py10
-rw-r--r--lib/extensions/params.py38
-rw-r--r--lib/extensions/print_pdf.py35
-rw-r--r--lib/extensions/remove_embroidery_settings.py20
-rw-r--r--lib/extensions/reorder.py36
-rw-r--r--lib/extensions/stitch_plan_preview.py9
-rw-r--r--lib/extensions/troubleshoot.py27
-rw-r--r--lib/extensions/zip.py34
-rw-r--r--lib/gui/electron.py4
-rw-r--r--lib/gui/presets.py4
-rw-r--r--lib/gui/simulator.py26
-rw-r--r--lib/i18n.py4
-rw-r--r--lib/inx/__init__.py2
-rwxr-xr-xlib/inx/about.py7
-rwxr-xr-xlib/inx/extensions.py11
-rw-r--r--lib/inx/generate.py4
-rwxr-xr-xlib/inx/info.py9
-rwxr-xr-xlib/inx/inputs.py2
-rw-r--r--lib/inx/outputs.py13
-rw-r--r--lib/inx/utils.py31
-rw-r--r--lib/lettering/__init__.py2
-rw-r--r--lib/lettering/font.py44
-rw-r--r--lib/lettering/font_variant.py22
-rw-r--r--lib/lettering/glyph.py31
-rw-r--r--lib/lettering/kerning.py69
-rw-r--r--lib/output.py7
-rw-r--r--lib/stitch_plan/stitch_plan.py4
-rw-r--r--lib/stitches/__init__.py6
-rw-r--r--lib/stitches/auto_fill.py15
-rw-r--r--lib/stitches/auto_satin.py29
-rw-r--r--lib/stitches/fill.py5
-rw-r--r--lib/stitches/running_stitch.py2
-rw-r--r--lib/svg/guides.py8
-rw-r--r--lib/svg/path.py29
-rw-r--r--lib/svg/rendering.py59
-rw-r--r--lib/svg/svg.py3
-rw-r--r--lib/svg/tags.py5
-rw-r--r--lib/svg/units.py25
-rw-r--r--lib/threads/__init__.py6
-rw-r--r--lib/threads/catalog.py2
-rw-r--r--lib/threads/color.py9
-rw-r--r--lib/threads/palette.py5
-rw-r--r--lib/utils/dotdict.py2
-rw-r--r--lib/utils/geometry.py6
-rw-r--r--lib/utils/inkscape.py4
-rw-r--r--lib/utils/io.py22
-rw-r--r--lib/utils/version.py17
-rw-r--r--print/resources/inkstitch.js4
-rw-r--r--print/resources/style.css3
-rw-r--r--print/templates/operator_detailedview.html2
m---------pyembroidery0
-rw-r--r--requirements.txt14
-rw-r--r--templates/about.xml28
-rw-r--r--templates/auto_satin.xml (renamed from templates/auto_satin.inx)0
-rw-r--r--templates/break_apart.xml (renamed from templates/break_apart.inx)0
-rw-r--r--templates/cleanup.xml (renamed from templates/cleanup.inx)0
-rw-r--r--templates/convert_to_satin.xml (renamed from templates/convert_to_satin.inx)0
-rw-r--r--templates/cut_satin.xml (renamed from templates/cut_satin.inx)0
-rw-r--r--templates/embroider.inx28
-rw-r--r--templates/embroider.xml18
-rw-r--r--templates/embroider_settings.xml21
-rw-r--r--templates/flip.xml (renamed from templates/flip.inx)0
-rw-r--r--templates/global_commands.xml (renamed from templates/global_commands.inx)0
-rw-r--r--templates/import_threadlist.xml (renamed from templates/import_threadlist.inx)4
-rw-r--r--templates/input.xml (renamed from templates/input.inx)0
-rw-r--r--templates/install.xml (renamed from templates/install.inx)2
-rw-r--r--templates/layer_commands.xml (renamed from templates/layer_commands.inx)0
-rw-r--r--templates/lettering.xml (renamed from templates/lettering.inx)2
-rw-r--r--templates/lettering_custom_font_dir.xml26
-rw-r--r--templates/lettering_generate_json.xml46
-rw-r--r--templates/lettering_remove_kerning.xml28
-rw-r--r--templates/object_commands.xml (renamed from templates/object_commands.inx)0
-rw-r--r--templates/output.xml (renamed from templates/output.inx)2
-rw-r--r--templates/output_params_txt.xml2
-rw-r--r--templates/params.xml (renamed from templates/params.inx)2
-rw-r--r--templates/print.xml (renamed from templates/print.inx)2
-rw-r--r--templates/remove_embroidery_settings.xml (renamed from templates/remove_embroidery_settings.inx)0
-rw-r--r--templates/reorder.xml15
-rw-r--r--templates/simulator.xml (renamed from templates/simulator.inx)2
-rw-r--r--templates/stitch_plan_preview.xml (renamed from templates/stitch_plan_preview.inx)0
-rw-r--r--templates/troubleshoot.xml (renamed from templates/troubleshoot.inx)0
-rw-r--r--templates/zip.xml (renamed from templates/zip.inx)4
135 files changed, 1434 insertions, 1002 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 76f221a9..0da74dee 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,156 +10,149 @@ jobs:
linux:
runs-on: ubuntu-16.04
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v2
with:
submodules: recursive
+ - uses: actions/setup-python@v2
+ with:
+ python-version: '3.9'
- uses: actions/setup-node@v1
with:
- node-version: '11.x'
- - name: download dependencies
- shell: bash
- run: |
- curl -sOL https://inkscape.org/en/gallery/item/12187/inkscape-0.92.3.tar.bz2
+ node-version: '15.x'
+
+ - uses: actions/cache@v2
+ id: pip-cache
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-16.04-pip-${{ hashFiles('**/requirements.txt') }}
+ restore-keys: |
+ ${{ runner.os }}-16.04-pip-
+
+ - name: Get yarn cache directory path
+ id: yarn-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn cache dir)"
+
+ - uses: actions/cache@v2
+ id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
+ with:
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
- name: install dependencies
shell: bash
run: |
- # I'd love to use a setup-python action but it seems to give a
- # python that doesn't support unicode. See:
- # https://github.com/actions/setup-python/issues/23
-
sudo apt-get update
-
- sudo apt-get install python2.7
python -m pip install --upgrade pip
+ python -m pip install wheel
sudo apt-get install gettext
# for wxPython
- sudo apt-get install glib-networking libsdl1.2-dev
+ sudo apt install glib-networking libsdl1.2-dev
# for PyGObject
- sudo apt install libgirepository1.0-dev
+ sudo apt install libgirepository1.0-dev libcairo2-dev
# for shapely
- sudo apt install libgeos-dev
+ sudo apt install libgeos-dev build-essential libgtk-3-dev
uname -a
python --version
python -m pip --version
python -m pip debug
- # wxPython doen't publish linux wheels in pypi
- wget -q https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04/wxPython-4.0.6-cp27-cp27mu-linux_x86_64.whl
- python -m pip install wxPython*.whl
+ python -m pip install pycairo==1.11.1
+ python -m pip install PyGObject==3.30.5
- python -m pip install PyGObject
+ # colormath - last official release: 3.0.0
+ # we need already submitted fixes - so let's grab them from the github repository
+ python -m pip install git+https://github.com/gtaylor/python-colormath
python -m pip install -r requirements.txt
- python -m pip install pyinstaller==3.3.1
+ python -m pip install pyinstaller
- tar -jxf inkscape-0.92.3.tar.bz2
- rm inkscape-0.92.3.tar.bz2
- mv inkscape-0.92.3 inkscape
-
echo "${{ env.pythonLocation }}\bin" >> $GITHUB_PATH
- shell: bash
run: |
make dist
env:
BUILD: linux
- - uses: actions/upload-artifact@master
+ - uses: actions/upload-artifact@v2
with:
name: inkstitch-linux
path: artifacts
windows:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-node@v1
with:
- node-version: '11.x'
- - uses: actions/setup-python@v1
+ node-version: '15.x'
+ - uses: actions/setup-python@v2
with:
- python-version: '2.7.x'
+ python-version: '3.9'
architecture: 'x86'
- uses: microsoft/setup-msbuild@v1.0.2
- - name: download dependencies
- shell: bash
- run: |
- curl -sOL https://github.com/lexelby/inkstitch-build-objects/releases/download/v1.0.0/Shapely-1.6.3-cp27-cp27m-win32.whl
- curl -sOL https://inkscape.org/en/gallery/item/12187/inkscape-0.92.3.tar.bz2
- name: install dependencies
shell: bash
run: |
- pip install Shapely-1.6.3-cp27-cp27m-win32.whl
- pip install -r requirements.txt
- pip install pyinstaller==3.3.1
+ python -m pip install --upgrade pip
+ python -m pip install wheel
- # Just using tar -j freezes forever with no output. Heck if I know why. This seems to work.
- bzcat inkscape-0.92.3.tar.bz2 | tar -vxf -
- rm inkscape-0.92.3.tar.bz2
- mv inkscape-0.92.3 inkscape
+ python -m pip install git+https://github.com/gtaylor/python-colormath
+
+ python -m pip install -r requirements.txt
+ python -m pip install pyinstaller
echo "${{ env.pythonLocation }}\bin" >> $GITHUB_PATH
- - name: fix geos
- shell: bash
- run: |
- cd "${{ env.pythonLocation }}\Lib/site-packages/shapely/DLLs"
- cp geos_c.dll geos.dll
- shell: bash
run: |
make dist
env:
BUILD: windows
- - uses: actions/upload-artifact@master
+ - uses: actions/upload-artifact@v2
with:
name: inkstitch-windows
path: artifacts
mac:
runs-on: macos-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v2
with:
submodules: recursive
- - uses: actions/setup-node@v1
+ - uses: actions/setup-python@v2
with:
- node-version: '11.x'
- - uses: actions/setup-python@v1
- with:
- python-version: '2.7.x'
+ python-version: '3.9'
- uses: actions/setup-node@v1
with:
- node-version: '10.x'
- - name: download inkscape
- shell: bash
- run: |
- curl -sOL https://inkscape.org/en/gallery/item/12187/inkscape-0.92.3.tar.bz2
+ node-version: '15.x'
- name: install dependencies
shell: bash
run: |
brew update
- # this errors because it installs python3 but python2 is already installed
- brew install gtk+3 pkg-config gobject-introspection libffi gettext || true
+ brew install gtk+3 pkg-config gobject-introspection geos libffi gettext || true
export LDFLAGS="-L/usr/local/opt/libffi/lib"
export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig"
-
+
# for msgfmt
echo "/usr/local/opt/gettext/bin" >> $GITHUB_PATH
echo "GI_TYPELIB_PATH=/usr/local/lib/girepository-1.0/" >> $GITHUB_ENV
+ pip install --upgrade pip
pip --version
+ pip install wheel
pip install PyGObject
+ pip install git+https://github.com/gtaylor/python-colormath
+
pip install -r requirements.txt
- pip install pyinstaller==3.3.1
-
- tar -jxf inkscape-0.92.3.tar.bz2
- rm inkscape-0.92.3.tar.bz2
- mv inkscape-0.92.3 inkscape
+ pip install pyinstaller
echo "${{ env.pythonLocation }}/bin" >> $GITHUB_PATH
- shell: bash
@@ -167,7 +160,7 @@ jobs:
make dist
env:
BUILD: osx
- - uses: actions/upload-artifact@master
+ - uses: actions/upload-artifact@v2
with:
name: inkstitch-mac
path: artifacts
diff --git a/.gitignore b/.gitignore
index 4ff89f90..09f99873 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ locales/
/debug.log
/debug.svg
/.idea
+/VERSION
diff --git a/Makefile b/Makefile
index b5bbd4e5..3253a570 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
-dist: locales inx
+dist: version locales inx
bash bin/build-python
bash bin/build-electron
bash bin/build-distribution-archives
@@ -8,7 +8,7 @@ distclean:
rm -rf build dist inx locales *.spec *.tar.gz *.zip electron/node_modules electron/dist
.PHONY: inx
-inx: locales
+inx: version locales
mkdir -p inx
python bin/generate-inx-files; \
@@ -49,6 +49,10 @@ locales:
mkdir -p locales; \
fi
+.PHONY: version
+version:
+ bash bin/generate-version-file
+
.PHONY: style
style:
flake8 . --count --max-complexity=10 --max-line-length=150 --statistics --exclude=pyembroidery,__init__.py,electron,build
diff --git a/bin/build-distribution-archives b/bin/build-distribution-archives
index 6e11818d..6402122b 100755
--- a/bin/build-distribution-archives
+++ b/bin/build-distribution-archives
@@ -4,12 +4,14 @@ VERSION="$(echo ${GITHUB_REF} | sed -e 's|refs/heads/||' -e 's|refs/tags/||' -e
OS="${BUILD:-$(uname)}"
ARCH="$(uname -m)"
-cp -a images/examples palettes symbols fonts dist/inkstitch
-cp -a icons locales print dist/inkstitch/bin
-
if [ "$BUILD" = "osx" ]; then
- cp -a electron/build/mac dist/inkstitch/electron
+ cp -a images/examples palettes symbols fonts LICENSE VERSION dist/inkstitch.app/Contents
+ cp -a icons locales print dist/inkstitch.app/Contents/MacOS
+ cp -a electron/build/mac dist/inkstitch.app/Contents/electron
+ rm -rf dist/inkstitch/
else
+ cp -a images/examples palettes symbols fonts LICENSE VERSION dist/inkstitch
+ cp -a icons locales print dist/inkstitch/bin
cp -a electron/build/*-unpacked dist/inkstitch/electron
fi
diff --git a/bin/build-python b/bin/build-python
index 0dd3e0a0..88e166ae 100755
--- a/bin/build-python
+++ b/bin/build-python
@@ -52,8 +52,3 @@ shopt -s dotglob
mkdir dist/bin
mv dist/inkstitch/* dist/bin
mv dist/bin dist/inkstitch
-
-# on Mac, pyinstaller creates a .app version as well, but we don't need that
-if [ "$BUILD" = "osx" ]; then
- rm -rf dist/inkstitch.app/
-fi
diff --git a/bin/generate-version-file b/bin/generate-version-file
new file mode 100755
index 00000000..36c70944
--- /dev/null
+++ b/bin/generate-version-file
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+VERSION="${GITHUB_REF##*/}"
+OS="${BUILD:-$(uname)}"
+
+if [[ "$VERSION" == "" ]]; then
+ VERSION="Manual Install"
+fi
+
+echo "${VERSION} (${OS})" > VERSION
diff --git a/bin/inkstitch-fonts-gettext b/bin/inkstitch-fonts-gettext
index 8b802fee..24fb9fdf 100755
--- a/bin/inkstitch-fonts-gettext
+++ b/bin/inkstitch-fonts-gettext
@@ -12,8 +12,8 @@ for font in sorted(os.listdir(fonts_dir)):
with open(os.path.join(fonts_dir, font, "font.json")) as font_json:
font_metadata = json.load(font_json)
- print "# L10N name of font in fonts/%s" % font
- print "_(%s)" % repr(font_metadata.get("name", ""))
+ print("# L10N name of font in fonts/%s" % font)
+ print("_(%s)" % repr(font_metadata.get("name", "")))
- print "# L10N description of font in fonts/%s" % font
- print "_(%s)" % repr(font_metadata.get("description", ""))
+ print("# L10N description of font in fonts/%s" % font)
+ print("_(%s)" % repr(font_metadata.get("description", "")))
diff --git a/bin/pyembroidery-gettext b/bin/pyembroidery-gettext
index ac9bd1ab..f4e844b9 100755
--- a/bin/pyembroidery-gettext
+++ b/bin/pyembroidery-gettext
@@ -6,5 +6,5 @@ import pyembroidery
# generate fake python code containing the descriptions of pyembroidery formats
# as gettext calls so that pybabel will extract them into messages.po
for format in pyembroidery.supported_formats():
- print "# L10N description for pyembroidery file format: %s" % format['extension']
- print "_(%s)" % repr(format['description'])
+ print("# L10N description for pyembroidery file format: %s" % format['extension'])
+ print("_(%s)" % repr(format['description']))
diff --git a/electron/.electron-vue/webpack.renderer.config.js b/electron/.electron-vue/webpack.renderer.config.js
index 89f8cd78..ac281db6 100644
--- a/electron/.electron-vue/webpack.renderer.config.js
+++ b/electron/.electron-vue/webpack.renderer.config.js
@@ -103,12 +103,6 @@ let rendererConfig = {
'css-loader',
{
loader: 'sass-loader',
- // Requires sass-loader@^7.0.0
- options: {
- implementation: require('sass'),
- fiber: require('fibers'),
- indentedSyntax: true // optional
- },
// Requires sass-loader@^8.0.0
options: {
implementation: require('sass'),
@@ -132,6 +126,18 @@ let rendererConfig = {
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
+ templateParameters(compilation, assets, options) {
+ return {
+ compilation: compilation,
+ webpack: compilation.getStats().toJson(),
+ webpackConfig: compilation.options,
+ htmlWebpackPlugin: {
+ files: assets,
+ options: options
+ },
+ process
+ }
+ },
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
diff --git a/electron/.electron-vue/webpack.web.config.js b/electron/.electron-vue/webpack.web.config.js
index 913518a2..50a73df6 100644
--- a/electron/.electron-vue/webpack.web.config.js
+++ b/electron/.electron-vue/webpack.web.config.js
@@ -77,12 +77,6 @@ let webConfig = {
'css-loader',
{
loader: 'sass-loader',
- // Requires sass-loader@^7.0.0
- options: {
- implementation: require('sass'),
- fiber: require('fibers'),
- indentedSyntax: true // optional
- },
// Requires sass-loader@^8.0.0
options: {
implementation: require('sass'),
@@ -102,6 +96,18 @@ let webConfig = {
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
+ templateParameters(compilation, assets, options) {
+ return {
+ compilation: compilation,
+ webpack: compilation.getStats().toJson(),
+ webpackConfig: compilation.options,
+ htmlWebpackPlugin: {
+ files: assets,
+ options: options
+ },
+ process
+ }
+ },
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
diff --git a/electron/src/lib/i18n.js b/electron/src/lib/i18n.js
index 886fd654..4f9395d7 100644
--- a/electron/src/lib/i18n.js
+++ b/electron/src/lib/i18n.js
@@ -1,9 +1,27 @@
-module.exports.selectLanguage = function () {
- ['LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'].forEach(language => {
+module.exports.selectLanguage = function (translations) {
+ // get a list of available translations
+ var availableTranslations = ['en_US'];
+ for(var k in translations) availableTranslations.push(k);
+
+ var lang = undefined;
+
+ // get system language / Inkscape language
+ ['LANG', 'LC_MESSAGES', 'LC_ALL', 'LANGUAGE'].forEach(language => {
if (process.env[language]) {
- return process.env[language].split(":")[0]
+ // split encoding information, we don't need it
+ var current_lang = process.env[language].split(".")[0];
+ if (current_lang.length == 2) {
+ // current language has only two letters (e.g. en),
+ // compare with available languages and if present, set to a long locale name (e.g. en_US)
+ lang = availableTranslations.find(elem => elem.startsWith(current_lang));
+ } else {
+ lang = current_lang;
+ }
}
})
-
- return "en_US"
+ // set default language
+ if (lang === undefined) {
+ lang = "en_US"
+ }
+ return lang
}
diff --git a/electron/src/main/index.js b/electron/src/main/index.js
index f7fb9437..14a3da55 100644
--- a/electron/src/main/index.js
+++ b/electron/src/main/index.js
@@ -61,9 +61,7 @@ function createWindow() {
app.on('ready', createWindow)
app.on('window-all-closed', () => {
- if (process.platform !== 'darwin') {
app.quit()
- }
})
app.on('activate', () => {
diff --git a/electron/src/renderer/assets/js/simulator.js b/electron/src/renderer/assets/js/simulator.js
index 638f0ca2..6c251a59 100644
--- a/electron/src/renderer/assets/js/simulator.js
+++ b/electron/src/renderer/assets/js/simulator.js
@@ -126,7 +126,7 @@ export default {
return ""
}
- let label = "STITCH"
+ let label = this.$gettext("STITCH")
switch (true) {
case stitch.jump:
label = this.$gettext("JUMP")
diff --git a/electron/src/renderer/assets/style/simulator.css b/electron/src/renderer/assets/style/simulator.css
index 7da1d9ef..30e601b0 100644
--- a/electron/src/renderer/assets/style/simulator.css
+++ b/electron/src/renderer/assets/style/simulator.css
@@ -20,7 +20,18 @@
}
button {
- color: rgb(0, 51, 153)
+ color: rgb(0, 51, 153);
+ align-items: flex-start;
+ text-align: center;
+ cursor: default;
+ background-color: buttonface;
+ box-sizing: border-box;
+ padding: 2px 6px 3px;
+ border-width: 2px;
+ border-style: outset;
+ border-color: buttonface;
+ border-image: initial;
+ margin-bottom: 5px;
}
.fa-spin-fast {
@@ -59,11 +70,13 @@ button {
text-align: center;
flex: 1;
white-space: nowrap;
+ margin: 0 5px;
}
fieldset {
border: 2px solid rgb(0, 51, 153);
position: relative;
+ padding: 0 5px
}
.window-controls {
@@ -142,6 +155,10 @@ fieldset.show-commands {
text-align: left;
}
+fieldset.show-commands legend {
+ text-align: center;
+}
+
fieldset.show-commands span {
display: inline-block;
vertical-align: top;
@@ -152,7 +169,7 @@ fieldset.show-commands span.npp {
}
fieldset.show-commands span:first-of-type {
- padding-right: 12px;
+ padding: 0 5px;
}
button.pressed {
@@ -164,8 +181,7 @@ button.pressed {
}
.slider-container {
- margin-top: 25px;
- margin-bottom: 25px;
+ margin: 25px 5px;
flex-grow: 0;
}
@@ -244,12 +260,15 @@ button.pressed {
width: 4rem;
float: right;
font-size: 1rem;
+ border-style: inset;
+ padding: 0 3px;
}
.simulator {
display: flex;
flex-direction: column;
height: 95vh;
+ margin: 10px;
}
.current-command {
@@ -269,6 +288,7 @@ div.simulator::v-deep svg.simulation {
div.simulator::v-deep svg.simulation-scale {
height: 50px;
order: -1;
+ margin-left: 5px;
}
div.simulator::v-deep .simulation-scale-label {
diff --git a/electron/src/renderer/components/Simulator.vue b/electron/src/renderer/components/Simulator.vue
index c6a190fb..66abadd6 100644
--- a/electron/src/renderer/components/Simulator.vue
+++ b/electron/src/renderer/components/Simulator.vue
@@ -189,7 +189,9 @@
<span class="current-command">{{currentCommand}}</span>
</fieldset>
<fieldset class="show-commands">
- <legend>Show</legend>
+ <legend>
+ <translate>Show</translate>
+ </legend>
<span>
<input id="trim-checkbox" type="checkbox" v-model="showTrims"/>
<label for="trim-checkbox"><font-awesome-icon icon="cut"/> <translate>trims</translate></label>
@@ -211,7 +213,7 @@
<font-awesome-icon icon="circle" transform="shrink-9"/>
<font-awesome-icon icon="minus" class="fa-thin-line"/>
</font-awesome-layers>
- <span v-translate>needle<br/>points</span>
+ <span v-translate>needle points</span>
</label>
</span>
<span>
diff --git a/electron/src/renderer/main.js b/electron/src/renderer/main.js
index 25a3f7f1..ee7dba02 100644
--- a/electron/src/renderer/main.js
+++ b/electron/src/renderer/main.js
@@ -73,7 +73,7 @@ Vue.component('font-awesome-layers', FontAwesomeLayers)
Vue.use(Transitions)
Vue.use(GetTextPlugin, {
translations: translations,
- defaultLanguage: selectLanguage(),
+ defaultLanguage: selectLanguage(translations),
silent: true
})
diff --git a/icons/inkstitch_colour_logo.svg b/icons/inkstitch_colour_logo.svg
new file mode 100644
index 00000000..c5214ec0
--- /dev/null
+++ b/icons/inkstitch_colour_logo.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Official logo release date: 2018.03 > X3msnake -->
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="99.9999mm" height="96.4295mm" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
+viewBox="0 0 15109 14570"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <style type="text/css">
+ <![CDATA[
+ .fil0 {fill:none}
+ .fil3 {fill:black}
+ .fil1 {fill:#003399}
+ .fil4 {fill:gray}
+ .fil2 {fill:white}
+ ]]>
+ </style>
+ </defs>
+ <g id="Layer_x0020_1">
+ <metadata id="CorelCorpID_0Corel-Layer"/>
+ <rect class="fil0" width="15109" height="14570"/>
+ <path class="fil1" d="M9857 7480l0 -51c0,-15 12,-27 27,-27l295 0c15,0 27,12 27,27l0 51c0,15 -12,28 -27,28l-295 0c-15,0 -27,-13 -27,-28zm2819 -1566c10,66 67,116 135,116l0 0c75,0 137,-61 137,-137l0 -144c0,-37 18,-68 49,-88 32,-19 69,-20 101,-2 83,43 138,125 138,232l0 2788c0,147 -121,268 -268,268l-292 0 0 443c0,1659 -1357,3016 -3016,3016l-4210 0c-1659,0 -3017,-1357 -3017,-3016l0 -402 -286 0c-151,0 -274,-123 -274,-273l0 -2778c0,-150 123,-273 274,-273l286 0 0 -484c0,-1659 1358,-3016 3017,-3016l4210 0c1659,0 3016,1357 3016,3016l0 734zm-10456 1686l107 0c43,0 81,21 106,52l0 170c-25,32 -63,52 -106,52l-107 0c-75,0 -136,-62 -136,-137l0 0c0,-75 61,-137 136,-137zm54 710c75,0 136,61 136,136 0,76 -61,137 -136,137 -76,0 -137,-61 -137,-137 0,-75 61,-136 137,-136zm10515 -710l107 0c75,0 137,62 137,137l0 0c0,75 -62,137 -137,137l-107 0c-47,0 -88,-24 -113,-61l0 -152c25,-36 66,-61 113,-61zm54 711c75,0 135,60 135,135 0,75 -60,136 -135,136 -75,0 -136,-61 -136,-136 0,-75 61,-135 136,-135zm-1957 -838c-676,154 -776,166 -1566,74l-1848 18c-374,4 -659,116 -1029,116l-2070 0 -138 -75 0 -302 138 -75 2070 0c370,0 655,111 1029,115l1848 19c790,-92 890,-80 1566,73 9,2 15,10 15,19 0,9 -6,16 -15,18zm-6324 -832l0 -2163 516 0 0 2163 -516 0zm1562 0l0 -2234 50 0 1347 1263 0 -1192 477 0 0 2246 -45 0 -1352 -1263 0 1180 -477 0zm2865 0l0 -2163 510 0 0 941 715 -941 614 0 -816 1029 881 1134 -624 0 -770 -996 0 996 -510 0zm-4809 2781c86,52 160,89 220,111 60,21 119,31 177,31 57,0 104,-13 139,-38 35,-26 53,-58 53,-98 0,-57 -63,-122 -188,-193 -20,-12 -36,-21 -46,-27l-95 -54c-92,-52 -161,-111 -205,-177 -45,-66 -67,-140 -67,-223 0,-113 43,-206 128,-278 85,-72 195,-108 330,-108 50,0 103,5 161,17 58,12 123,30 194,54l0 329c-68,-43 -132,-75 -193,-98 -61,-23 -112,-35 -155,-35 -47,0 -84,10 -111,30 -27,21 -41,48 -41,83 0,24 8,47 25,68 16,22 40,41 72,58l165 91c137,76 230,147 277,211 48,65 72,141 72,230 0,136 -47,247 -140,331 -93,84 -217,126 -371,126 -52,0 -109,-5 -171,-16 -61,-11 -128,-28 -202,-51l-28 -374zm1440 396l0 -1110 -445 0 0 -293 1224 0 0 293 -444 0 0 1110 -335 0zm986 0l0 -1403 335 0 0 1403 -335 0zm989 0l0 -1110 -446 0 0 -293 1225 0 0 293 -444 0 0 1110 -335 0zm1983 -959c-60,-63 -122,-109 -184,-139 -62,-30 -128,-45 -196,-45 -119,0 -216,41 -291,122 -76,82 -113,188 -113,316 0,128 37,232 112,312 75,79 172,119 292,119 71,0 142,-14 213,-44 70,-30 141,-74 211,-134l-22 373c-60,40 -125,71 -196,92 -72,22 -146,32 -223,32 -82,0 -161,-12 -237,-38 -76,-26 -146,-64 -209,-115 -91,-71 -160,-157 -208,-259 -48,-102 -72,-214 -72,-335 0,-104 18,-201 53,-292 36,-91 87,-171 155,-241 68,-70 147,-123 237,-160 90,-37 184,-55 283,-55 79,0 154,10 226,33 72,21 140,55 205,99l-36 359zm261 959l0 -1403 331 0 0 515 541 0 0 -515 329 0 0 1403 -329 0 0 -589 -541 0 0 589 -331 0zm-4389 -7142c-1376,0 -2504,1128 -2504,2504l0 4210c0,1376 1128,2504 2504,2504l4210 0c1376,0 2504,-1128 2504,-2504l0 -4210c0,-1376 -1128,-2504 -2504,-2504l-4210 0z"/>
+ <path class="fil2" d="M5450 2676c-1376,0 -2504,1128 -2504,2504l0 4210c0,1376 1128,2504 2504,2504l4210 0c1376,0 2504,-1128 2504,-2504l0 -4210c0,-1376 -1128,-2504 -2504,-2504l-4210 0z"/>
+ <path class="fil3" d="M9839 9818l0 -1403 331 0 0 515 541 0 0 -515 329 0 0 1403 -329 0 0 -589 -541 0 0 589 -331 0zm-261 -959c-60,-63 -122,-109 -184,-139 -62,-30 -128,-45 -196,-45 -119,0 -216,41 -291,122 -76,82 -113,188 -113,316 0,128 37,232 112,312 75,79 172,119 292,119 71,0 142,-14 213,-44 70,-30 141,-74 211,-134l-22 373c-60,40 -125,71 -196,92 -72,22 -146,32 -223,32 -82,0 -161,-12 -237,-38 -76,-26 -146,-64 -209,-115 -91,-71 -160,-157 -208,-259 -48,-102 -72,-214 -72,-335 0,-104 18,-201 53,-292 36,-91 87,-171 155,-241 68,-70 147,-123 237,-160 90,-37 184,-55 283,-55 79,0 154,10 226,33 72,21 140,55 205,99l-36 359zm-1983 959l0 -1110 -446 0 0 -293 1225 0 0 293 -444 0 0 1110 -335 0zm-989 0l0 -1403 335 0 0 1403 -335 0zm-986 0l0 -1110 -445 0 0 -293 1224 0 0 293 -444 0 0 1110 -335 0zm-1440 -396c86,52 160,89 220,111 60,21 119,31 177,31 57,0 104,-13 139,-38 35,-26 53,-58 53,-98 0,-57 -63,-122 -188,-193 -20,-12 -36,-21 -46,-27l-95 -54c-92,-52 -161,-111 -205,-177 -45,-66 -67,-140 -67,-223 0,-113 43,-206 128,-278 85,-72 195,-108 330,-108 50,0 103,5 161,17 58,12 123,30 194,54l0 329c-68,-43 -132,-75 -193,-98 -61,-23 -112,-35 -155,-35 -47,0 -84,10 -111,30 -27,21 -41,48 -41,83 0,24 8,47 25,68 16,22 40,41 72,58l165 91c137,76 230,147 277,211 48,65 72,141 72,230 0,136 -47,247 -140,331 -93,84 -217,126 -371,126 -52,0 -109,-5 -171,-16 -61,-11 -128,-28 -202,-51l-28 -374z"/>
+ <path class="fil3" d="M8989 6641l0 -2163 510 0 0 941 715 -941 614 0 -816 1029 881 1134 -624 0 -770 -996 0 996 -510 0zm-2865 0l0 -2234 50 0 1347 1263 0 -1192 477 0 0 2246 -45 0 -1352 -1263 0 1180 -477 0zm-1562 0l0 -2163 516 0 0 2163 -516 0z"/>
+ <path class="fil4" d="M10886 7473c-676,154 -776,166 -1566,74l-1848 18c-374,4 -659,116 -1029,116l-2070 0 -138 -75 0 -302 138 -75 2070 0c370,0 655,111 1029,115l1848 19c790,-92 890,-80 1566,73 9,2 15,10 15,19 0,9 -6,16 -15,18zm-1029 7l0 -51c0,-15 12,-27 27,-27l295 0c15,0 27,12 27,27l0 51c0,15 -12,28 -27,28l-295 0c-15,0 -27,-13 -27,-28z"/>
+ </g>
+</svg>
diff --git a/inkstitch.py b/inkstitch.py
index 58d0f434..13eab809 100644
--- a/inkstitch.py
+++ b/inkstitch.py
@@ -3,12 +3,15 @@ import os
import sys
import traceback
from argparse import ArgumentParser
-from cStringIO import StringIO
+from io import StringIO
+
+from inkex import errormsg
+from lxml.etree import XMLSyntaxError
import lib.debug as debug
from lib import extensions
from lib.i18n import _
-from lib.utils import restore_stderr, save_stderr
+from lib.utils import restore_stderr, save_stderr, version
logger = logging.getLogger('shapely.geos')
logger.setLevel(logging.DEBUG)
@@ -36,28 +39,35 @@ extension_class = getattr(extensions, extension_class_name)
extension = extension_class()
if hasattr(sys, 'gettrace') and sys.gettrace():
- extension.affect(args=remaining_args)
+ extension.run(args=remaining_args)
else:
save_stderr()
exception = None
try:
- extension.affect(args=remaining_args)
+ extension.run(args=remaining_args)
except (SystemExit, KeyboardInterrupt):
raise
+ except XMLSyntaxError:
+ msg = _("Ink/Stitch cannot read your SVG file. "
+ "This is often the case when you use a file which has been created with Adobe Illustrator.")
+ msg += "\n\n"
+ msg += _("Try to import the file into Inkscape through 'File > Import...' (Ctrl+I)")
+ errormsg(msg)
except Exception:
exception = traceback.format_exc()
finally:
restore_stderr()
if shapely_errors.tell():
- print >> sys.stderr, shapely_errors.getvalue()
+ errormsg(shapely_errors.getvalue())
if exception:
- print >> sys.stderr, _("Ink/Stitch experienced an unexpected error.").encode("UTF-8")
- print >> sys.stderr, _("If you'd like to help, please file an issue at "
- "https://github.com/inkstitch/inkstitch/issues "
- "and include the entire error description below:").encode("UTF-8"), "\n"
- print >> sys.stderr, exception
+ errormsg(_("Ink/Stitch experienced an unexpected error.") + "\n")
+ errormsg(_("If you'd like to help, please file an issue at "
+ "https://github.com/inkstitch/inkstitch/issues "
+ "and include the entire error description below:") + "\n")
+ errormsg(version.get_inkstitch_version() + "\n")
+ errormsg(exception)
sys.exit(1)
else:
sys.exit(0)
diff --git a/lib/api/install.py b/lib/api/install.py
index 20138973..f52233fb 100644
--- a/lib/api/install.py
+++ b/lib/api/install.py
@@ -16,7 +16,7 @@ def palettes():
path = os.path.join(base_path, 'palettes')
src_dir = get_bundled_dir('palettes')
copy_files(glob(os.path.join(src_dir, "*")), path)
- except Exception, exc:
+ except Exception as exc:
return jsonify({"error": str(exc)}), 500
return jsonify({"status": "success"})
diff --git a/lib/api/server.py b/lib/api/server.py
index bdfa4573..0db253c6 100644
--- a/lib/api/server.py
+++ b/lib/api/server.py
@@ -1,16 +1,17 @@
import errno
import logging
import socket
+import sys
import time
from threading import Thread
import requests
from flask import Flask, g, request
+from ..utils.json import InkStitchJSONEncoder
from .install import install
from .simulator import simulator
from .stitch_plan import stitch_plan
-from ..utils.json import InkStitchJSONEncoder
class APIServer(Thread):
@@ -27,6 +28,10 @@ class APIServer(Thread):
self.__setup_app()
def __setup_app(self): # noqa: C901
+ # Disable warning about using a development server in a production environment
+ cli = sys.modules['flask.cli']
+ cli.show_server_banner = lambda *x: None
+
self.app = Flask(__name__)
self.app.json_encoder = InkStitchJSONEncoder
@@ -89,7 +94,7 @@ class APIServer(Thread):
response = requests.get("http://%s:%s/ping" % (self.host, self.port))
if response.status_code == 200:
break
- except socket.error, e:
+ except socket.error as e:
if e.errno == errno.ECONNREFUSED:
pass
else:
diff --git a/lib/api/stitch_plan.py b/lib/api/stitch_plan.py
index fd6bf9c9..95cdc7d8 100644
--- a/lib/api/stitch_plan.py
+++ b/lib/api/stitch_plan.py
@@ -11,7 +11,9 @@ def get_stitch_plan():
if not g.extension.get_elements():
return dict(colors=[], stitch_blocks=[], commands=[])
+ metadata = g.extension.get_inkstitch_metadata()
+ collapse_len = metadata['collapse_len_mm']
patches = g.extension.elements_to_patches(g.extension.elements)
- stitch_plan = patches_to_stitch_plan(patches)
+ stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
return jsonify(stitch_plan)
diff --git a/lib/commands.py b/lib/commands.py
index 9d0b243c..ba307487 100644
--- a/lib/commands.py
+++ b/lib/commands.py
@@ -3,11 +3,9 @@ import sys
from copy import deepcopy
from random import random
-from shapely import geometry as shgeo
-
-import cubicsuperpath
import inkex
-import simpletransform
+from lxml import etree
+from shapely import geometry as shgeo
from .i18n import N_, _
from .svg import (apply_transforms, generate_unique_id,
@@ -104,7 +102,7 @@ class Command(BaseCommand):
self.parse_command()
def parse_connector_path(self):
- path = cubicsuperpath.parsePath(self.connector.get('d'))
+ path = inkex.paths.Path(self.connector.get('d')).to_superpath()
return apply_transforms(path, self.connector)
def parse_command(self):
@@ -153,7 +151,7 @@ class StandaloneCommand(BaseCommand):
def point(self):
pos = [float(self.node.get("x", 0)), float(self.node.get("y", 0))]
transform = get_node_transform(self.node)
- simpletransform.applyTransformToPoint(transform, pos)
+ pos = inkex.transforms.Transform(transform).apply_to_point(pos)
return Point(*pos)
@@ -209,14 +207,14 @@ def global_command(svg, command):
if len(commands) == 1:
return commands[0]
elif len(commands) > 1:
- print >> sys.stderr, _("Error: there is more than one %(command)s command in the document, but there can only be one. "
- "Please remove all but one.") % dict(command=command)
+ print(_("Error: there is more than one %(command)s command in the document, but there can only be one. "
+ "Please remove all but one.") % dict(command=command), file=sys.stderr)
# L10N This is a continuation of the previous error message, letting the user know
# what command we're talking about since we don't normally expose the actual
# command name to them. Contents of %(description)s are in a separate translation
# string.
- print >> sys.stderr, _("%(command)s: %(description)s") % dict(command=command, description=_(get_command_description(command)))
+ print(_("%(command)s: %(description)s") % dict(command=command, description=_(get_command_description(command))), file=sys.stderr)
sys.exit(1)
else:
@@ -256,7 +254,7 @@ def symbols_path():
@cache
def symbols_svg():
with open(symbols_path()) as symbols_file:
- return inkex.etree.parse(symbols_file)
+ return etree.parse(symbols_file)
@cache
@@ -269,7 +267,7 @@ def get_defs(document):
defs = document.find(SVG_DEFS_TAG)
if defs is None:
- defs = inkex.etree.SubElement(document, SVG_DEFS_TAG)
+ defs = etree.SubElement(document, SVG_DEFS_TAG)
return defs
@@ -284,7 +282,7 @@ def ensure_symbol(document, command):
def add_group(document, node, command):
- return inkex.etree.SubElement(
+ return etree.SubElement(
node.getparent(),
SVG_GROUP_TAG,
{
@@ -304,35 +302,35 @@ def add_connector(document, symbol, element):
if element.node.get('id') is None:
element.node.set('id', generate_unique_id(document, "object"))
- path = inkex.etree.Element(SVG_PATH_TAG,
- {
- "id": generate_unique_id(document, "command_connector"),
- "d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y),
- "style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
- CONNECTION_START: "#%s" % symbol.get('id'),
- CONNECTION_END: "#%s" % element.node.get('id'),
- CONNECTOR_TYPE: "polyline",
+ path = etree.Element(SVG_PATH_TAG,
+ {
+ "id": generate_unique_id(document, "command_connector"),
+ "d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y),
+ "style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
+ CONNECTION_START: "#%s" % symbol.get('id'),
+ CONNECTION_END: "#%s" % element.node.get('id'),
+ CONNECTOR_TYPE: "polyline",
- # l10n: the name of the line that connects a command to the object it applies to
- INKSCAPE_LABEL: _("connector")
- })
+ # l10n: the name of the line that connects a command to the object it applies to
+ INKSCAPE_LABEL: _("connector")
+ })
symbol.getparent().insert(0, path)
def add_symbol(document, group, command, pos):
- return inkex.etree.SubElement(group, SVG_USE_TAG,
- {
- "id": generate_unique_id(document, "command_use"),
- XLINK_HREF: "#inkstitch_%s" % command,
- "height": "100%",
- "width": "100%",
- "x": str(pos.x),
- "y": str(pos.y),
+ return etree.SubElement(group, SVG_USE_TAG,
+ {
+ "id": generate_unique_id(document, "command_use"),
+ XLINK_HREF: "#inkstitch_%s" % command,
+ "height": "100%",
+ "width": "100%",
+ "x": str(pos.x),
+ "y": str(pos.y),
- # l10n: the name of a command symbol (example: scissors icon for trim command)
- INKSCAPE_LABEL: _("command marker"),
- })
+ # l10n: the name of a command symbol (example: scissors icon for trim command)
+ INKSCAPE_LABEL: _("command marker"),
+ })
def get_command_pos(element, index, total):
@@ -397,14 +395,14 @@ def add_layer_commands(layer, commands):
for command in commands:
ensure_symbol(document, command)
- inkex.etree.SubElement(layer, SVG_USE_TAG,
- {
- "id": generate_unique_id(document, "use"),
- INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
- XLINK_HREF: "#inkstitch_%s" % command,
- "height": "100%",
- "width": "100%",
- "x": "0",
- "y": "-10",
- "transform": correction_transform
- })
+ etree.SubElement(layer, SVG_USE_TAG,
+ {
+ "id": generate_unique_id(document, "use"),
+ INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
+ XLINK_HREF: "#inkstitch_%s" % command,
+ "height": "100%",
+ "width": "100%",
+ "x": "0",
+ "y": "-10",
+ "transform": correction_transform
+ })
diff --git a/lib/debug.py b/lib/debug.py
index 6ce67697..5d022e63 100644
--- a/lib/debug.py
+++ b/lib/debug.py
@@ -1,17 +1,16 @@
import atexit
-from contextlib import contextmanager
-from datetime import datetime
import os
import socket
import sys
import time
+from contextlib import contextmanager
+from datetime import datetime
-from inkex import etree
import inkex
-from simplestyle import formatStyle
+from lxml import etree
-from svg import line_strings_to_path
-from svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
+from .svg import line_strings_to_path
+from .svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
def check_enabled(func):
@@ -36,7 +35,10 @@ class Debug(object):
self.init_svg()
def init_log(self):
- self.log_file = open(os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.log"), "w")
+ self.log_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.log")
+ # delete old content
+ with open(self.log_file, "w"):
+ pass
self.log("Debug logging enabled.")
def init_debugger(self):
@@ -60,7 +62,7 @@ class Debug(object):
try:
pydevd.settrace()
- except socket.error, error:
+ except socket.error as error:
self.log("Debugging: connection to pydevd failed: %s", error)
self.log("Be sure to run 'Start debugging server' in PyDev to enable debugging.")
else:
@@ -74,8 +76,8 @@ class Debug(object):
def save_svg(self):
tree = etree.ElementTree(self.svg)
- with open(os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.svg"), "w") as debug_svg:
- tree.write(debug_svg)
+ debug_svg = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.svg")
+ tree.write(debug_svg)
@check_enabled
def add_layer(self, name="Debug"):
@@ -113,20 +115,21 @@ class Debug(object):
timestamp = now.isoformat()
self.last_log_time = now
- print >> self.log_file, timestamp, message % args
- self.log_file.flush()
+ with open(self.log_file, "a") as logfile:
+ print(timestamp, message % args, file=logfile)
+ logfile.flush()
def time(self, func):
def decorated(*args, **kwargs):
if self.enabled:
- self.raw_log("entering %s()", func.func_name)
+ self.raw_log("entering %s()", func.__name__)
start = time.time()
result = func(*args, **kwargs)
if self.enabled:
end = time.time()
- self.raw_log("leaving %s(), duration = %s", func.func_name, round(end - start, 6))
+ self.raw_log("leaving %s(), duration = %s", func.__name__, round(end - start, 6))
return result
@@ -150,7 +153,7 @@ class Debug(object):
@check_enabled
def log_line_strings(self, line_strings, name=None, color=None):
path = line_strings_to_path(line_strings)
- path.set('style', formatStyle({"stroke": color or "#000000", "stroke-width": "0.3"}))
+ path.set('style', str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3"})))
if name is not None:
path.set(INKSCAPE_LABEL, name)
@@ -161,7 +164,7 @@ class Debug(object):
def log_line(self, start, end, name="line", color=None):
self.log_svg_element(etree.Element("path", {
"d": "M%s,%s %s,%s" % (start + end),
- "style": formatStyle({"stroke": color or "#000000", "stroke-width": "0.3"}),
+ "style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3"})),
INKSCAPE_LABEL: name
}))
@@ -174,7 +177,7 @@ class Debug(object):
self.log_svg_element(etree.Element("path", {
"d": d,
- "style": formatStyle({"stroke": color or "#000000", "stroke-width": "0.3"}),
+ "style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3"})),
INKSCAPE_LABEL: name
}))
diff --git a/lib/elements/__init__.py b/lib/elements/__init__.py
index 92ef94a3..d53b2314 100644
--- a/lib/elements/__init__.py
+++ b/lib/elements/__init__.py
@@ -1,11 +1,11 @@
-from auto_fill import AutoFill
-from clone import Clone
-from element import EmbroideryElement
-from empty_d_object import EmptyDObject
-from fill import Fill
-from image import ImageObject
-from polyline import Polyline
-from satin_column import SatinColumn
-from stroke import Stroke
-from text import TextObject
-from utils import node_to_elements, nodes_to_elements
+from .auto_fill import AutoFill
+from .clone import Clone
+from .element import EmbroideryElement
+from .empty_d_object import EmptyDObject
+from .fill import Fill
+from .image import ImageObject
+from .polyline import Polyline
+from .satin_column import SatinColumn
+from .stroke import Stroke
+from .text import TextObject
+from .utils import node_to_elements, nodes_to_elements
diff --git a/lib/elements/auto_fill.py b/lib/elements/auto_fill.py
index b574c8bf..31da7e63 100644
--- a/lib/elements/auto_fill.py
+++ b/lib/elements/auto_fill.py
@@ -6,10 +6,9 @@ from shapely import geometry as shgeo
from ..i18n import _
from ..stitches import auto_fill
-from ..utils import cache
+from ..utils import cache, version
from .element import Patch, param
from .fill import Fill
-
from .validation import ValidationWarning
@@ -20,6 +19,18 @@ class SmallShapeWarning(ValidationWarning):
"the outline instead.")
+class ExpandWarning(ValidationWarning):
+ name = _("Expand")
+ description = _("The expand parameter for this fill object cannot be applied. "
+ "Ink/Stitch will ignore it and will use original size instead.")
+
+
+class UnderlayInsetWarning(ValidationWarning):
+ name = _("Inset")
+ description = _("The underlay inset parameter for this fill object cannot be applied. "
+ "Ink/Stitch will ignore it and will use the original size instead.")
+
+
class AutoFill(Fill):
element_name = _("AutoFill")
@@ -157,9 +168,13 @@ class AutoFill(Fill):
def underlay_underpath(self):
return self.get_boolean_param('underlay_underpath', True)
- def shrink_or_grow_shape(self, amount):
+ def shrink_or_grow_shape(self, amount, validate=False):
if amount:
shape = self.shape.buffer(amount)
+ # changing the size can empty the shape
+ # in this case we want to use the original shape rather than returning an error
+ if shape.is_empty and not validate:
+ return self.shape
if not isinstance(shape, shgeo.MultiPolygon):
shape = shgeo.MultiPolygon([shape])
return shape
@@ -235,6 +250,7 @@ class AutoFill(Fill):
# L10N this message is followed by a URL: https://github.com/inkstitch/inkstitch/issues/new
message += _("If you'd like to help us make Ink/Stitch better, please paste this whole message into a new issue at: ")
message += "https://github.com/inkstitch/inkstitch/issues/new\n\n"
+ message += version.get_inkstitch_version() + "\n\n"
message += traceback.format_exc()
self.fatal(message)
@@ -245,5 +261,11 @@ class AutoFill(Fill):
if self.shape.area < 20:
yield SmallShapeWarning(self.shape.centroid)
+ if self.shrink_or_grow_shape(self.expand, True).is_empty:
+ yield ExpandWarning(self.shape.centroid)
+
+ if self.shrink_or_grow_shape(-self.fill_underlay_inset, True).is_empty:
+ yield UnderlayInsetWarning(self.shape.centroid)
+
for warning in super(AutoFill, self).validation_warnings():
yield warning
diff --git a/lib/elements/clone.py b/lib/elements/clone.py
index b8046d2d..fd770bd7 100644
--- a/lib/elements/clone.py
+++ b/lib/elements/clone.py
@@ -1,16 +1,13 @@
-from copy import copy
from math import atan, degrees
-from simpletransform import (applyTransformToNode, applyTransformToPoint,
- computeBBox, parseTransform)
+import inkex
from ..commands import is_command, is_command_symbol
from ..i18n import _
from ..svg.path import get_node_transform
from ..svg.svg import find_elements
-from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, SVG_GROUP_TAG,
- SVG_LINK_TAG, SVG_POLYLINE_TAG, SVG_USE_TAG,
- XLINK_HREF)
+from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS,
+ SVG_POLYLINE_TAG, SVG_USE_TAG, XLINK_HREF)
from ..utils import cache
from .auto_fill import AutoFill
from .element import EmbroideryElement, param
@@ -74,16 +71,16 @@ class Clone(EmbroideryElement):
if node.tag == SVG_POLYLINE_TAG:
return [Polyline(node)]
- elif element.get_boolean_param("satin_column") and element.get_style("stroke"):
+ elif element.get_boolean_param("satin_column") and self.get_clone_style("stroke", self.node):
return [SatinColumn(node)]
else:
elements = []
- if element.get_style("fill", "black") and not element.get_style("fill-opacity", 1) == "0":
+ if element.get_style("fill", "black") and not element.get_style("stroke", 1) == "0":
if element.get_boolean_param("auto_fill", True):
elements.append(AutoFill(node))
else:
elements.append(Fill(node))
- if element.get_style("stroke"):
+ if element.get_style("stroke", self.node) is not None:
if not is_command(element.node):
elements.append(Stroke(node))
if element.get_boolean_param("stroke_first", False):
@@ -98,32 +95,8 @@ class Clone(EmbroideryElement):
if source_node.tag not in EMBROIDERABLE_TAGS:
return []
- clone = copy(source_node)
-
- # set id
- clone_id = 'clone__%s__%s' % (self.node.get('id', ''), clone.get('id', ''))
- clone.set('id', clone_id)
-
- # apply transform
- transform = get_node_transform(self.node)
- applyTransformToNode(transform, clone)
-
- # apply style
- stroke_style = self.get_clone_style('stroke', self.node)
- if not stroke_style:
- stroke_style = self.get_clone_style('stroke', source_node)
- fill_style = self.node.get('fill')
- if not fill_style:
- fill_style = self.get_clone_style('fill', source_node, "#000000")
- fill_opacity = self.node.get('fill-opacity')
- if not fill_opacity:
- fill_opacity = self.get_clone_style('fill-opacity', source_node, "1")
- style = "fill:%s;fill-opacity:%s;" % (fill_style, fill_opacity)
- if stroke_style:
- style += "stroke:%s;" % stroke_style
- clone.set('style', style)
-
- # set fill angle. Use either
+ self.node.style = source_node.composed_style()
+
# a. a custom set fill angle
# b. calculated rotation for the cloned fill element to look exactly as it's source
param = INKSTITCH_ATTRIBS['angle']
@@ -131,48 +104,32 @@ class Clone(EmbroideryElement):
angle = self.clone_fill_angle
else:
# clone angle
- clone_mat = parseTransform(clone.get('transform', ''))
+ clone_mat = self.node.composed_transform()
clone_angle = degrees(atan(-clone_mat[1][0]/clone_mat[1][1]))
# source node angle
- source_mat = parseTransform(source_node.get('transform', ''))
+ source_mat = source_node.composed_transform()
source_angle = degrees(atan(-source_mat[1][0]/source_mat[1][1]))
# source node fill angle
source_fill_angle = source_node.get(param, 0)
angle = clone_angle + float(source_fill_angle) - source_angle
- clone.set(param, str(angle))
+ self.node.set(param, str(angle))
- elements = self.clone_to_element(clone)
+ elements = self.clone_to_element(self.node)
for element in elements:
patches.extend(element.to_patches(last_patch))
return patches
- def _get_clone_style_raw(self, style_name, node):
- style = self.parse_style()
- style = style.get(style_name) or self.node.get(style_name)
- parent = self.node.getparent()
- # style not found, get inherited style elements
- while not style and parent is not None:
- if parent.tag not in [SVG_GROUP_TAG, SVG_LINK_TAG]:
- parent = parent.getparent()
- continue
- style = self.parse_style(parent)
- style = style.get(style_name) or parent.get(style_name)
- parent = parent.getparent()
- return style
-
def get_clone_style(self, style_name, node, default=None):
- style = self._get_clone_style_raw(style_name, node) or default
+ style = inkex.styles.AttrFallbackStyle(node).get(style_name) or default
return style
def center(self, source_node):
- xmin, xmax, ymin, ymax = computeBBox([source_node])
- point = [(xmax-((xmax-xmin)/2)), (ymax-((ymax-ymin)/2))]
- transform = get_node_transform(self.node)
- applyTransformToPoint(transform, point)
- return point
+ transform = get_node_transform(self.node.getparent())
+ center = self.node.bounding_box(transform).center
+ return center
def validation_warnings(self):
source_node = get_clone_source(self.node)
diff --git a/lib/elements/element.py b/lib/elements/element.py
index 5d2934cd..2ced143b 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -1,18 +1,16 @@
import sys
from copy import deepcopy
-import cubicsuperpath
-import simpletransform
+import inkex
import tinycss2
-from cspsubdiv import cspsubdiv
+from inkex import bezier
-from .svg_objects import circle_to_path, ellipse_to_path, rect_to_path
from ..commands import find_commands
from ..i18n import _
from ..svg import (PIXELS_PER_MM, apply_transforms, convert_length,
get_node_transform)
-from ..svg.tags import (EMBROIDERABLE_TAGS, INKSCAPE_LABEL, INKSTITCH_ATTRIBS, SVG_CIRCLE_TAG, SVG_ELLIPSE_TAG, SVG_GROUP_TAG, SVG_LINK_TAG,
- SVG_OBJECT_TAGS, SVG_RECT_TAG)
+from ..svg.tags import (EMBROIDERABLE_TAGS, INKSCAPE_LABEL, INKSTITCH_ATTRIBS,
+ SVG_GROUP_TAG, SVG_LINK_TAG, SVG_USE_TAG)
from ..utils import Point, cache
@@ -155,22 +153,29 @@ class EmbroideryElement(object):
def parse_style(self, node=None):
if node is None:
node = self.node
+ element_style = node.get("style", "")
+ if element_style is None:
+ return None
declarations = tinycss2.parse_declaration_list(node.get("style", ""))
style = {declaration.lower_name: declaration.value[0].serialize() for declaration in declarations}
return style
@cache
def _get_style_raw(self, style_name):
- if self.node.tag not in [SVG_GROUP_TAG, SVG_LINK_TAG] and self.node.tag not in EMBROIDERABLE_TAGS:
+ if self.node is None:
+ return None
+ if self.node.tag not in [SVG_GROUP_TAG, SVG_LINK_TAG, SVG_USE_TAG] and self.node.tag not in EMBROIDERABLE_TAGS:
return None
style = self.parse_style()
- style = style.get(style_name) or self.node.get(style_name)
+ if style:
+ style = style.get(style_name) or self.node.get(style_name)
parent = self.node.getparent()
# style not found, get inherited style elements
while not style and parent is not None:
style = self.parse_style(parent)
- style = style.get(style_name) or parent.get(style_name)
+ if style:
+ style = style.get(style_name) or parent.get(style_name)
parent = parent.getparent()
return style
@@ -196,23 +201,23 @@ class EmbroideryElement(object):
# Of course, transforms may also involve rotation, skewing, and translation.
# All except translation can affect how wide the stroke appears on the screen.
- node_transform = get_node_transform(self.node)
+ node_transform = inkex.transforms.Transform(get_node_transform(self.node))
# First, figure out the translation component of the transform. Using a zero
# vector completely cancels out the rotation, scale, and skew components.
zero = [0, 0]
- simpletransform.applyTransformToPoint(node_transform, zero)
+ zero = inkex.Transform.apply_to_point(node_transform, zero)
translate = Point(*zero)
# Next, see how the transform affects unit vectors in the X and Y axes. We
# need to subtract off the translation or it will affect the magnitude of
# the resulting vector, which we don't want.
unit_x = [1, 0]
- simpletransform.applyTransformToPoint(node_transform, unit_x)
+ unit_x = inkex.Transform.apply_to_point(node_transform, unit_x)
sx = (Point(*unit_x) - translate).length()
unit_y = [0, 1]
- simpletransform.applyTransformToPoint(node_transform, unit_y)
+ unit_y = inkex.Transform.apply_to_point(node_transform, unit_y)
sy = (Point(*unit_y) - translate).length()
# Take the average as a best guess.
@@ -223,11 +228,7 @@ class EmbroideryElement(object):
@property
@cache
def stroke_width(self):
- width = self.get_style("stroke-width", None)
-
- if width is None:
- return 1.0
-
+ width = self.get_style("stroke-width", "1.0")
width = convert_length(width)
return width * self.stroke_scale
@@ -271,20 +272,15 @@ class EmbroideryElement(object):
# In a path, each element in the 3-tuple is itself a tuple of (x, y).
# Tuples all the way down. Hasn't anyone heard of using classes?
- if self.node.tag in SVG_OBJECT_TAGS:
- if self.node.tag == SVG_RECT_TAG:
- d = rect_to_path(self.node)
- elif self.node.tag == SVG_ELLIPSE_TAG:
- d = ellipse_to_path(self.node)
- elif self.node.tag == SVG_CIRCLE_TAG:
- d = circle_to_path(self.node)
+ if getattr(self.node, "get_path", None):
+ d = self.node.get_path()
else:
d = self.node.get("d", "")
if not d:
self.fatal(_("Object %(id)s has an empty 'd' attribute. Please delete this object from your document.") % dict(id=self.node.get("id")))
- return cubicsuperpath.parsePath(d)
+ return inkex.paths.Path(d).to_superpath()
@cache
def parse_path(self):
@@ -315,7 +311,7 @@ class EmbroideryElement(object):
return commands[0]
elif len(commands) > 1:
raise ValueError(_("%(id)s has more than one command of type '%(command)s' linked to it") %
- dict(id=self.node.get(id), command=command))
+ dict(id=self.node.get('id'), command=command))
else:
return None
@@ -326,13 +322,13 @@ class EmbroideryElement(object):
"""approximate a path containing beziers with a series of points"""
path = deepcopy(path)
- cspsubdiv(path, 0.1)
+ bezier.cspsubdiv(path, 0.1)
return [self.strip_control_points(subpath) for subpath in path]
def flatten_subpath(self, subpath):
path = [deepcopy(subpath)]
- cspsubdiv(path, 0.1)
+ bezier.cspsubdiv(path, 0.1)
return self.strip_control_points(path[0])
@@ -373,7 +369,7 @@ class EmbroideryElement(object):
# L10N used when showing an error message to the user such as
# "Some Path (path1234): error: satin column: One or more of the rungs doesn't intersect both rails."
error_msg = "%s: %s %s" % (name, _("error:"), message)
- print >> sys.stderr, "%s" % (error_msg.encode("UTF-8"))
+ inkex.errormsg(error_msg)
sys.exit(1)
def validation_errors(self):
diff --git a/lib/elements/fill.py b/lib/elements/fill.py
index 2e94847a..1f4c7b1e 100644
--- a/lib/elements/fill.py
+++ b/lib/elements/fill.py
@@ -136,6 +136,12 @@ class Fill(EmbroideryElement):
# biggest path.
paths = self.paths
paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
+ # Very small holes will cause a shape to be rendered as an outline only
+ # they are too small to be rendered and only confuse the auto_fill algorithm.
+ # So let's ignore them
+ if shgeo.Polygon(paths[0]).area > 5 and shgeo.Polygon(paths[-1]).area < 5:
+ paths = [path for path in paths if shgeo.Polygon(path).area > 3]
+
polygon = shgeo.MultiPolygon([(paths[0], paths[1:])])
# There is a great number of "crossing border" errors on fill shapes
diff --git a/lib/elements/image.py b/lib/elements/image.py
index ec8d1765..160898a5 100644
--- a/lib/elements/image.py
+++ b/lib/elements/image.py
@@ -1,7 +1,5 @@
-from simpletransform import applyTransformToPoint
-
from ..i18n import _
-from ..svg import get_node_transform
+from ..svg.path import get_node_transform
from .element import EmbroideryElement
from .validation import ObjectTypeWarning
@@ -19,13 +17,9 @@ class ImageTypeWarning(ObjectTypeWarning):
class ImageObject(EmbroideryElement):
def center(self):
- point = [float(self.node.get('x', 0)), float(self.node.get('y', 0))]
- point = [(point[0]+(float(self.node.get('width', 0))/2)), (point[1]+(float(self.node.get('height', 0))/2))]
-
- transform = get_node_transform(self.node)
- applyTransformToPoint(transform, point)
-
- return point
+ transform = get_node_transform(self.node.getparent())
+ center = self.node.bounding_box(transform).center
+ return center
def validation_warnings(self):
yield ImageTypeWarning(self.center())
diff --git a/lib/elements/polyline.py b/lib/elements/polyline.py
index 2d008d35..da1e807d 100644
--- a/lib/elements/polyline.py
+++ b/lib/elements/polyline.py
@@ -1,3 +1,4 @@
+from inkex import Path
from shapely import geometry as shgeo
from ..i18n import _
@@ -36,7 +37,7 @@ class Polyline(EmbroideryElement):
@property
@param('polyline', _('Manual stitch along path'), type='toggle', inverse=True)
- def satin_column(self):
+ def polyline(self):
return self.get_boolean_param("polyline")
@property
@@ -61,8 +62,8 @@ class Polyline(EmbroideryElement):
# svg transforms that is in our superclass, we'll convert the polyline
# to a degenerate cubic superpath in which the bezier handles are on
# the segment endpoints.
-
- path = [[[point[:], point[:], point[:]] for point in self.points]]
+ path = self.node.get_path()
+ path = Path(path).to_superpath()
return path
diff --git a/lib/elements/satin_column.py b/lib/elements/satin_column.py
index d0a3f8ff..fbadd92f 100644
--- a/lib/elements/satin_column.py
+++ b/lib/elements/satin_column.py
@@ -1,14 +1,15 @@
from copy import deepcopy
-from itertools import chain, izip
+from itertools import chain
-import cubicsuperpath
-from shapely import affinity as shaffinity, geometry as shgeo
+from inkex import paths
+from shapely import affinity as shaffinity
+from shapely import geometry as shgeo
-from .element import EmbroideryElement, Patch, param
-from .validation import ValidationError
from ..i18n import _
from ..svg import line_strings_to_csp, point_lists_to_csp
from ..utils import Point, cache, collapse_duplicate_point, cut
+from .element import EmbroideryElement, Patch, param
+from .validation import ValidationError
class SatinHasFillError(ValidationError):
@@ -234,7 +235,7 @@ class SatinColumn(EmbroideryElement):
rung_endpoints.append(points)
rungs = []
- for start, end in izip(*rung_endpoints):
+ for start, end in zip(*rung_endpoints):
# Expand the points just a bit to ensure that shapely thinks they
# intersect with the rails even with floating point inaccuracy.
start = Point(*start)
@@ -266,12 +267,12 @@ class SatinColumn(EmbroideryElement):
if num_paths <= 2:
# old-style satin column with no rungs
- return range(num_paths)
+ return list(range(num_paths))
# This takes advantage of the fact that sum() counts True as 1
- intersection_counts = [sum(paths[i].intersects(paths[j]) for j in xrange(num_paths) if i != j)
- for i in xrange(num_paths)]
- paths_not_intersecting_two = [i for i in xrange(num_paths) if intersection_counts[i] != 2]
+ intersection_counts = [sum(paths[i].intersects(paths[j]) for j in range(num_paths) if i != j)
+ for i in range(num_paths)]
+ paths_not_intersecting_two = [i for i in range(num_paths) if intersection_counts[i] != 2]
num_not_intersecting_two = len(paths_not_intersecting_two)
if num_not_intersecting_two == 2:
@@ -292,7 +293,7 @@ class SatinColumn(EmbroideryElement):
# kind of weird thing. Maybe one of the rungs crosses a rail more
# than once. Treat it like the previous case and we'll sort out
# the intersection issues later.
- indices_by_length = sorted(range(num_paths), key=lambda index: paths[index].length, reverse=True)
+ indices_by_length = sorted(list(range(num_paths)), key=lambda index: paths[index].length, reverse=True)
return indices_by_length[:2]
def _cut_rail(self, rail, rung):
@@ -330,7 +331,7 @@ class SatinColumn(EmbroideryElement):
self._cut_rail(rail, rung)
for rail in rails:
- for i in xrange(len(rail)):
+ for i in range(len(rail)):
if rail[i] is not None:
rail[i] = [Point(*coord) for coord in rail[i].coords]
@@ -345,7 +346,7 @@ class SatinColumn(EmbroideryElement):
# zero-length bezier at the star. The user's goal here is to ignore the
# horizontal section of the right rail.
- sections = zip(*rails)
+ sections = list(zip(*rails))
sections = [s for s in sections if s[0] is not None and s[1] is not None]
return sections
@@ -438,13 +439,13 @@ class SatinColumn(EmbroideryElement):
"""
# like in do_satin()
- points = list(chain.from_iterable(izip(*self.plot_points_on_rails(self.zigzag_spacing, 0))))
+ points = list(chain.from_iterable(zip(*self.plot_points_on_rails(self.zigzag_spacing, 0))))
if isinstance(split_point, float):
index_of_closest_stitch = int(round(len(points) * split_point))
else:
split_point = Point(*split_point)
- index_of_closest_stitch = min(range(len(points)), key=lambda index: split_point.distance(points[index]))
+ index_of_closest_stitch = min(list(range(len(points))), key=lambda index: split_point.distance(points[index]))
if index_of_closest_stitch % 2 == 0:
# split point is on the first rail
@@ -517,7 +518,7 @@ class SatinColumn(EmbroideryElement):
def _csp_to_satin(self, csp):
node = deepcopy(self.node)
- d = cubicsuperpath.formatPath(csp)
+ d = paths.CubicSuperPath(csp).to_path()
node.set("d", d)
# we've already applied the transform, so get rid of it
@@ -626,7 +627,9 @@ class SatinColumn(EmbroideryElement):
# Each iteration of this outer loop is one stitch. Keep going
# until we fall off the end of the section.
- old_center = (pos0 + pos1) / 2.0
+ # TODO: is there an other way?
+ # old_center = (pos0 + pos1) / 2.0
+ old_center = shgeo.Point(x/2 for x in (pos0 + pos1))
while to_travel > 0 and index0 < last_index0 and index1 < last_index1:
# In this loop, we inch along each rail a tiny bit per
@@ -653,7 +656,9 @@ class SatinColumn(EmbroideryElement):
pos0, index0 = self.walk(section0, pos0, index0, 0.05)
pos1, index1 = self.walk(section1, pos1, index1, 0.05 * ratio)
- new_center = (pos0 + pos1) / 2.0
+ # TODO: is there a better way?
+ # new_center = (pos0 + pos1) / 2.0
+ new_center = shgeo.Point(x/2 for x in (pos0 + pos1))
to_travel -= new_center.distance(old_center)
old_center = new_center
@@ -705,7 +710,7 @@ class SatinColumn(EmbroideryElement):
# This fancy bit of iterable magic just repeatedly takes a point
# from each side in turn.
- for point in chain.from_iterable(izip(*sides)):
+ for point in chain.from_iterable(zip(*sides)):
patch.add_stitch(point)
return patch
@@ -724,7 +729,7 @@ class SatinColumn(EmbroideryElement):
sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
# Like in zigzag_underlay(): take a point from each side in turn.
- for point in chain.from_iterable(izip(*sides)):
+ for point in chain.from_iterable(zip(*sides)):
patch.add_stitch(point)
return patch
@@ -743,7 +748,7 @@ class SatinColumn(EmbroideryElement):
# "left" and "right" here are kind of arbitrary designations meaning
# a point from the first and second rail repectively
- for left, right in izip(*sides):
+ for left, right in zip(*sides):
patch.add_stitch(left)
patch.add_stitch(right)
patch.add_stitch(left)
diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py
index 36d1048e..d63a21a9 100644
--- a/lib/elements/stroke.py
+++ b/lib/elements/stroke.py
@@ -134,9 +134,9 @@ class Stroke(EmbroideryElement):
global warned_about_legacy_running_stitch
if not warned_about_legacy_running_stitch:
warned_about_legacy_running_stitch = True
- print >> sys.stderr, _("Legacy running stitch setting detected!\n\nIt looks like you're using a stroke " +
- "smaller than 0.5 units to indicate a running stitch, which is deprecated. Instead, please set " +
- "your stroke to be dashed to indicate running stitch. Any kind of dash will work.")
+ print(_("Legacy running stitch setting detected!\n\nIt looks like you're using a stroke " +
+ "smaller than 0.5 units to indicate a running stitch, which is deprecated. Instead, please set " +
+ "your stroke to be dashed to indicate running stitch. Any kind of dash will work."), file=sys.stderr)
# still allow the deprecated setting to work in order to support old files
return True
@@ -157,7 +157,7 @@ class Stroke(EmbroideryElement):
offset = stroke_width / 2.0
- for i in xrange(len(patch) - 1):
+ for i in range(len(patch) - 1):
start = patch.stitches[i]
end = patch.stitches[i + 1]
segment_direction = (end - start).unit()
@@ -174,7 +174,7 @@ class Stroke(EmbroideryElement):
repeated_path = []
# go back and forth along the path as specified by self.repeats
- for i in xrange(self.repeats):
+ for i in range(self.repeats):
if i % 2 == 1:
# reverse every other pass
this_path = path[::-1]
diff --git a/lib/elements/svg_objects.py b/lib/elements/svg_objects.py
deleted file mode 100644
index 4760af5f..00000000
--- a/lib/elements/svg_objects.py
+++ /dev/null
@@ -1,71 +0,0 @@
-def rect_to_path(node):
- x = float(node.get('x', '0'))
- y = float(node.get('y', '0'))
- width = float(node.get('width', '0'))
- height = float(node.get('height', '0'))
- rx = 0
- ry = 0
-
- # rounded corners
- # the following rules apply for radius calculations:
- # * if rx or ry is missing it has to take the value of the other one
- # * the radius cannot be bigger than half of the corresponding side
- # (otherwise we receive an invalid path)
- if node.get('rx') or node.get('ry'):
- if node.get('rx'):
- rx = float(node.get('rx', '0'))
- ry = rx
- if node.get('ry'):
- ry = float(node.get('ry', '0'))
- if not ry:
- ry = rx
-
- rx = min(width/2, rx)
- ry = min(height/2, ry)
-
- path = 'M %(startx)f,%(y)f ' \
- 'h %(width)f ' \
- 'q %(rx)f,0 %(rx)f,%(ry)f ' \
- 'v %(height)f ' \
- 'q 0,%(ry)f -%(rx)f,%(ry)f ' \
- 'h -%(width)f ' \
- 'q -%(rx)f,0 -%(rx)f,-%(ry)f ' \
- 'v -%(height)f ' \
- 'q 0,-%(ry)f %(rx)f,-%(ry)f ' \
- 'Z' \
- % dict(startx=x+rx, x=x, y=y, width=width-(2*rx), height=height-(2*ry), rx=rx, ry=ry)
-
- else:
- path = "M %f,%f H %f V %f H %f Z" % (x, y, width+x, height+y, x)
-
- return path
-
-
-def ellipse_to_path(node):
- rx = float(node.get('rx', "0")) or float(node.get('r', "0"))
- ry = float(node.get('ry', "0")) or float(node.get('r', "0"))
- cx = float(node.get('cx'))
- cy = float(node.get('cy'))
-
- path = 'M %(cx_r)f,%(cy)f' \
- 'C %(cx_r)f,%(cy_r)f %(cx)f,%(cy_r)f %(cx)f,%(cy_r)f ' \
- '%(cxr)f,%(cy_r)f %(cxr)f,%(cy)f %(cxr)f,%(cy)f ' \
- '%(cxr)f,%(cyr)f %(cx)f,%(cyr)f %(cx)f,%(cyr)f ' \
- '%(cx_r)f,%(cyr)f %(cx_r)f,%(cy)f %(cx_r)f,%(cy)f ' \
- 'Z' \
- % dict(cx=cx, cx_r=cx-rx, cxr=cx+rx, cy=cy, cyr=cy+ry, cy_r=cy-ry)
-
- return path
-
-
-def circle_to_path(node):
- cx = float(node.get('cx'))
- cy = float(node.get('cy'))
- r = float(node.get('r'))
-
- path = 'M %(xstart)f, %(cy)f ' \
- 'a %(r)f,%(r)f 0 1,0 %(rr)f,0 ' \
- 'a %(r)f,%(r)f 0 1,0 -%(rr)f,0 ' \
- % dict(xstart=(cx-r), cy=cy, r=r, rr=(r*2))
-
- return path
diff --git a/lib/elements/text.py b/lib/elements/text.py
index 2d066bb0..838be96a 100644
--- a/lib/elements/text.py
+++ b/lib/elements/text.py
@@ -1,9 +1,7 @@
-from simpletransform import applyTransformToPoint
-
from ..i18n import _
-from ..svg import get_node_transform
from .element import EmbroideryElement
from .validation import ObjectTypeWarning
+from ..svg.path import get_node_transform
class TextTypeWarning(ObjectTypeWarning):
@@ -17,16 +15,14 @@ class TextTypeWarning(ObjectTypeWarning):
class TextObject(EmbroideryElement):
- def center(self):
- point = [float(self.node.get('x', 0)), float(self.node.get('y', 0))]
-
- transform = get_node_transform(self.node)
- applyTransformToPoint(transform, point)
+ def pointer(self):
+ transform = get_node_transform(self.node.getparent())
+ point = self.node.bounding_box(transform).center
return point
def validation_warnings(self):
- yield TextTypeWarning(self.center())
+ yield TextTypeWarning(self.pointer())
def to_patches(self, last_patch):
return []
diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py
index a5388f19..1758772e 100644
--- a/lib/extensions/__init__.py
+++ b/lib/extensions/__init__.py
@@ -1,28 +1,32 @@
-from auto_satin import AutoSatin
-from break_apart import BreakApart
-from cleanup import Cleanup
-from convert_to_satin import ConvertToSatin
-from cut_satin import CutSatin
-from embroider import Embroider
-from flip import Flip
-from global_commands import GlobalCommands
-from import_threadlist import ImportThreadlist
-from input import Input
-from install import Install
-from layer_commands import LayerCommands
-from lettering import Lettering
from lib.extensions.troubleshoot import Troubleshoot
-from object_commands import ObjectCommands
-from output import Output
-from params import Params
-from print_pdf import Print
-from remove_embroidery_settings import RemoveEmbroiderySettings
-from simulator import Simulator
-from stitch_plan_preview import StitchPlanPreview
-from zip import Zip
-__all__ = extensions = [Embroider,
- StitchPlanPreview,
+from .auto_satin import AutoSatin
+from .break_apart import BreakApart
+from .cleanup import Cleanup
+from .convert_to_satin import ConvertToSatin
+from .cut_satin import CutSatin
+from .flip import Flip
+from .global_commands import GlobalCommands
+from .import_threadlist import ImportThreadlist
+from .input import Input
+from .install import Install
+from .layer_commands import LayerCommands
+from .lettering import Lettering
+from .object_commands import ObjectCommands
+from .output import Output
+from .params import Params
+from .print_pdf import Print
+from .remove_embroidery_settings import RemoveEmbroiderySettings
+from .reorder import Reorder
+from .simulator import Simulator
+from .stitch_plan_preview import StitchPlanPreview
+from .zip import Zip
+from .lettering_generate_json import LetteringGenerateJson
+from .lettering_remove_kerning import LetteringRemoveKerning
+from .lettering_custom_font_dir import LetteringCustomFontDir
+from .embroider_settings import EmbroiderSettings
+
+__all__ = extensions = [StitchPlanPreview,
Install,
Params,
Print,
@@ -37,9 +41,14 @@ __all__ = extensions = [Embroider,
CutSatin,
AutoSatin,
Lettering,
+ LetteringGenerateJson,
+ LetteringRemoveKerning,
+ LetteringCustomFontDir,
Troubleshoot,
RemoveEmbroiderySettings,
Cleanup,
BreakApart,
ImportThreadlist,
- Simulator]
+ Simulator,
+ Reorder,
+ EmbroiderSettings]
diff --git a/lib/extensions/auto_satin.py b/lib/extensions/auto_satin.py
index a447a493..fce4a1fd 100644
--- a/lib/extensions/auto_satin.py
+++ b/lib/extensions/auto_satin.py
@@ -14,7 +14,7 @@ class AutoSatin(CommandsExtension):
def __init__(self, *args, **kwargs):
CommandsExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-p", "--preserve_order", dest="preserve_order", type="inkbool", default=False)
+ self.arg_parser.add_argument("-p", "--preserve_order", dest="preserve_order", type=inkex.Boolean, default=False)
def get_starting_point(self):
return self.get_point("satin_start")
@@ -39,7 +39,7 @@ class AutoSatin(CommandsExtension):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
# L10N auto-route satin columns extension
inkex.errormsg(_("Please select one or more satin columns."))
return False
diff --git a/lib/extensions/base.py b/lib/extensions/base.py
index 9f6dc5f6..1a38973f 100644
--- a/lib/extensions/base.py
+++ b/lib/extensions/base.py
@@ -1,21 +1,19 @@
import json
import os
import re
-from collections import MutableMapping
-from copy import deepcopy
-
-from stringcase import snakecase
+from collections.abc import MutableMapping
import inkex
+from lxml import etree
+from stringcase import snakecase
from ..commands import is_command, layer_commands
from ..elements import EmbroideryElement, nodes_to_elements
-from ..elements.clone import is_clone, is_embroiderable_clone
+from ..elements.clone import is_clone
from ..i18n import _
from ..svg import generate_unique_id
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
- NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG,
- SVG_PATH_TAG)
+ NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG)
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
@@ -71,7 +69,7 @@ class InkStitchMetadata(MutableMapping):
tag = inkex.addNS(name, "inkstitch")
item = self.metadata.find(tag)
if item is None and create:
- item = inkex.etree.SubElement(self.metadata, tag)
+ item = etree.SubElement(self.metadata, tag)
return item
@@ -117,7 +115,7 @@ class InkstitchExtension(inkex.Effect):
def ensure_current_layer(self):
# if no layer is selected, inkex defaults to the root, which isn't
# particularly useful
- if self.current_layer is self.document.getroot():
+ if self.svg.get_current_layer() is self.document.getroot():
try:
self.current_layer = self.document.xpath(".//svg:g[@inkscape:groupmode='layer']", namespaces=inkex.NSS)[0]
except IndexError:
@@ -125,7 +123,7 @@ class InkstitchExtension(inkex.Effect):
pass
def no_elements_error(self):
- if self.selected:
+ if self.svg.selected:
# l10n This was previously: "No embroiderable paths selected."
inkex.errormsg(_("Ink/Stitch doesn't know how to work with any of the objects you've selected.") + "\n")
else:
@@ -154,8 +152,8 @@ class InkstitchExtension(inkex.Effect):
if is_command(node) or node.get(CONNECTOR_TYPE):
return[]
- if self.selected:
- if node.get("id") in self.selected:
+ if self.svg.selected:
+ if node.get("id") in self.svg.selected:
selected = True
else:
# if the user didn't select anything that means we process everything
@@ -165,7 +163,7 @@ class InkstitchExtension(inkex.Effect):
nodes.extend(self.descendants(child, selected, troubleshoot))
if selected:
- if (node.tag in EMBROIDERABLE_TAGS or is_embroiderable_clone(node)) and not (node.tag == SVG_PATH_TAG and not node.get('d', '')):
+ if getattr(node, "get_path", None):
nodes.append(node)
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.tag in EMBROIDERABLE_TAGS or is_clone(node)):
nodes.append(node)
@@ -206,24 +204,3 @@ class InkstitchExtension(inkex.Effect):
def uniqueId(self, prefix, make_new_id=True):
"""Override inkex.Effect.uniqueId with a nicer naming scheme."""
return generate_unique_id(self.document, prefix)
-
- def parse(self):
- """Override inkex.Effect.parse to add Ink/Stitch xml namespace"""
-
- # SVG parsers don't actually look for anything at this URL. They just
- # care that it's unique. That defines a "namespace" of element and
- # attribute names to disambiguate conflicts with element and
- # attribute names other XML namespaces.
-
- # call the superclass's method first
- inkex.Effect.parse(self)
-
- # Add the inkstitch namespace to the SVG. The inkstitch namespace is
- # added to inkex.NSS in ../svg/tags.py at import time.
-
- # The below is the only way I could find to add a namespace to an
- # existing element tree at the top without getting ugly prefixes like "ns0".
- inkex.etree.cleanup_namespaces(self.document,
- top_nsmap=inkex.NSS,
- keep_ns_prefixes=inkex.NSS.keys())
- self.original_document = deepcopy(self.document)
diff --git a/lib/extensions/break_apart.py b/lib/extensions/break_apart.py
index 0b17d3d7..d0ab2619 100644
--- a/lib/extensions/break_apart.py
+++ b/lib/extensions/break_apart.py
@@ -1,11 +1,10 @@
import logging
from copy import copy
+import inkex
from shapely.geometry import LineString, MultiPolygon, Polygon
from shapely.ops import polygonize, unary_union
-import inkex
-
from ..elements import EmbroideryElement
from ..i18n import _
from ..svg import get_correction_transform
@@ -19,10 +18,11 @@ class BreakApart(InkstitchExtension):
'''
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-m", "--method", type="int", default=1, dest="method")
+ self.arg_parser.add_argument("-m", "--method", type=int, default=1, dest="method")
+ self.minimum_size = 5
def effect(self): # noqa: C901
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select one or more fill areas to break apart."))
return
@@ -41,13 +41,12 @@ class BreakApart(InkstitchExtension):
try:
paths.sort(key=lambda point_list: Polygon(point_list).area, reverse=True)
polygon = MultiPolygon([(paths[0], paths[1:])])
- if self.geom_is_valid(polygon):
+ if self.geom_is_valid(polygon) and Polygon(paths[-1]).area > self.minimum_size:
continue
except ValueError:
pass
polygons = self.break_apart_paths(paths)
- polygons = self.ensure_minimum_size(polygons, 5)
if self.options.method == 1:
polygons = self.combine_overlapping_polygons(polygons)
polygons = self.recombine_polygons(polygons)
@@ -106,6 +105,7 @@ class BreakApart(InkstitchExtension):
polygons.sort(key=lambda polygon: polygon.area, reverse=True)
multipolygons = []
holes = []
+ self.ensure_minimum_size(polygons, self.minimum_size)
for polygon in polygons:
if polygon in holes:
continue
diff --git a/lib/extensions/cleanup.py b/lib/extensions/cleanup.py
index e06b4bea..f1965aba 100644
--- a/lib/extensions/cleanup.py
+++ b/lib/extensions/cleanup.py
@@ -1,6 +1,4 @@
-import sys
-
-from inkex import NSS
+from inkex import NSS, Boolean, errormsg
from ..elements import Fill, Stroke
from ..i18n import _
@@ -10,10 +8,10 @@ from .base import InkstitchExtension
class Cleanup(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-f", "--rm_fill", dest="rm_fill", type="inkbool", default=True)
- self.OptionParser.add_option("-s", "--rm_stroke", dest="rm_stroke", type="inkbool", default=True)
- self.OptionParser.add_option("-a", "--fill_threshold", dest="fill_threshold", type="int", default=20)
- self.OptionParser.add_option("-l", "--stroke_threshold", dest="stroke_threshold", type="int", default=5)
+ self.arg_parser.add_argument("-f", "--rm_fill", dest="rm_fill", type=Boolean, default=True)
+ self.arg_parser.add_argument("-s", "--rm_stroke", dest="rm_stroke", type=Boolean, default=True)
+ self.arg_parser.add_argument("-a", "--fill_threshold", dest="fill_threshold", type=int, default=20)
+ self.arg_parser.add_argument("-l", "--stroke_threshold", dest="stroke_threshold", type=int, default=5)
def effect(self):
self.rm_fill = self.options.rm_fill
@@ -21,8 +19,7 @@ class Cleanup(InkstitchExtension):
self.fill_threshold = self.options.fill_threshold
self.stroke_threshold = self.options.stroke_threshold
- # Remove selection, we want every element in the document
- self.selected = {}
+ self.svg.selected.clear()
count = 0
svg = self.document.getroot()
@@ -32,7 +29,7 @@ class Cleanup(InkstitchExtension):
count += 1
if not self.get_elements():
- print >> sys.stderr, _("%s elements removed" % count)
+ errormsg(_("%s elements removed" % count))
return
for element in self.elements:
@@ -44,4 +41,4 @@ class Cleanup(InkstitchExtension):
element.node.getparent().remove(element.node)
count += 1
- print >> sys.stderr, _("%s elements removed" % count)
+ errormsg(_("%s elements removed" % count))
diff --git a/lib/extensions/commands.py b/lib/extensions/commands.py
index 86e291fd..19b85e6d 100644
--- a/lib/extensions/commands.py
+++ b/lib/extensions/commands.py
@@ -1,3 +1,5 @@
+from inkex import Boolean
+
from .base import InkstitchExtension
@@ -7,4 +9,4 @@ class CommandsExtension(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
for command in self.COMMANDS:
- self.OptionParser.add_option("--%s" % command, type="inkbool")
+ self.arg_parser.add_argument("--%s" % command, type=Boolean)
diff --git a/lib/extensions/convert_to_satin.py b/lib/extensions/convert_to_satin.py
index e2b287dd..048c08da 100644
--- a/lib/extensions/convert_to_satin.py
+++ b/lib/extensions/convert_to_satin.py
@@ -1,12 +1,13 @@
import math
+import sys
from itertools import chain, groupby
+import inkex
import numpy
+from lxml import etree
from numpy import diff, setdiff1d, sign
from shapely import geometry as shgeo
-import inkex
-
from ..elements import Stroke
from ..i18n import _
from ..svg import PIXELS_PER_MM, get_correction_transform
@@ -26,7 +27,7 @@ class ConvertToSatin(InkstitchExtension):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select at least one line to convert to a satin column."))
return
@@ -120,8 +121,15 @@ class ConvertToSatin(InkstitchExtension):
path = shgeo.LineString(path)
- left_rail = path.parallel_offset(stroke_width / 2.0, 'left', **style_args)
- right_rail = path.parallel_offset(stroke_width / 2.0, 'right', **style_args)
+ try:
+ left_rail = path.parallel_offset(stroke_width / 2.0, 'left', **style_args)
+ right_rail = path.parallel_offset(stroke_width / 2.0, 'right', **style_args)
+ except ValueError:
+ # TODO: fix this error automatically
+ # Error reference: https://github.com/inkstitch/inkstitch/issues/964
+ inkex.errormsg(_("Ink/Stitch cannot convert your stroke into a satin column. "
+ "Please break up your path and try again.") + '\n')
+ sys.exit(1)
if not isinstance(left_rail, shgeo.LineString) or \
not isinstance(right_rail, shgeo.LineString):
@@ -304,12 +312,11 @@ class ConvertToSatin(InkstitchExtension):
d += "%s,%s " % (x, y)
d += " "
- return inkex.etree.Element(SVG_PATH_TAG,
- {
- "id": self.uniqueId("path"),
- "style": path_style,
- "transform": correction_transform,
- "d": d,
- INKSTITCH_ATTRIBS['satin_column']: "true",
- }
- )
+ return etree.Element(SVG_PATH_TAG,
+ {
+ "id": self.uniqueId("path"),
+ "style": path_style,
+ "transform": correction_transform,
+ "d": d,
+ INKSTITCH_ATTRIBS['satin_column']: "true",
+ })
diff --git a/lib/extensions/cut_satin.py b/lib/extensions/cut_satin.py
index b776a68c..7cc80295 100644
--- a/lib/extensions/cut_satin.py
+++ b/lib/extensions/cut_satin.py
@@ -1,9 +1,9 @@
import inkex
-from .base import InkstitchExtension
-from ..i18n import _
from ..elements import SatinColumn
+from ..i18n import _
from ..svg import get_correction_transform
+from .base import InkstitchExtension
class CutSatin(InkstitchExtension):
@@ -11,7 +11,7 @@ class CutSatin(InkstitchExtension):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select one or more satin columns to cut."))
return
diff --git a/lib/extensions/embroider.py b/lib/extensions/embroider.py
deleted file mode 100644
index b9089612..00000000
--- a/lib/extensions/embroider.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import os
-
-from ..i18n import _
-from ..output import write_embroidery_file
-from ..stitch_plan import patches_to_stitch_plan
-from ..svg import render_stitch_plan, PIXELS_PER_MM
-from .base import InkstitchExtension
-
-
-class Embroider(InkstitchExtension):
- def __init__(self, *args, **kwargs):
- InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-c", "--collapse_len_mm",
- action="store", type="float",
- dest="collapse_length_mm", default=3.0,
- help="max collapse length (mm)")
- self.OptionParser.add_option("--hide_layers",
- action="store", type="choice",
- choices=["true", "false"],
- dest="hide_layers", default="true",
- help="Hide all other layers when the embroidery layer is generated")
- self.OptionParser.add_option("-O", "--output_format",
- action="store", type="string",
- dest="output_format", default="csv",
- help="Output file extenstion (default: csv)")
- self.OptionParser.add_option("-P", "--path",
- action="store", type="string",
- dest="path", default=".",
- help="Directory in which to store output file")
- self.OptionParser.add_option("-F", "--output-file",
- action="store", type="string",
- dest="output_file",
- help="Output filename.")
- self.OptionParser.add_option("-b", "--max-backups",
- action="store", type="int",
- dest="max_backups", default=5,
- help="Max number of backups of output files to keep.")
- self.OptionParser.usage += _("\n\nSeeing a 'no such option' message? Please restart Inkscape to fix.")
-
- def get_output_path(self):
- if self.options.output_file:
- # This is helpful for folks that run the embroider extension
- # manually from the command line (without Inkscape) for
- # debugging purposes.
- output_path = os.path.join(os.path.expanduser(os.path.expandvars(self.options.path.decode("UTF-8"))),
- self.options.output_file.decode("UTF-8"))
- else:
- csv_filename = '%s.%s' % (self.get_base_file_name(), self.options.output_format)
- output_path = os.path.join(self.options.path.decode("UTF-8"), csv_filename)
-
- def add_suffix(path, suffix):
- if suffix > 0:
- path = "%s.%s" % (path, suffix)
-
- return path
-
- def move_if_exists(path, suffix=0):
- source = add_suffix(path, suffix)
-
- if suffix >= self.options.max_backups:
- return
-
- dest = add_suffix(path, suffix + 1)
-
- if os.path.exists(source):
- move_if_exists(path, suffix + 1)
-
- if os.path.exists(dest):
- os.remove(dest)
-
- os.rename(source, dest)
-
- move_if_exists(output_path)
-
- return output_path
-
- def effect(self):
- if not self.get_elements():
- return
-
- if self.options.hide_layers:
- self.hide_all_layers()
-
- patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches, self.options.collapse_length_mm * PIXELS_PER_MM)
- write_embroidery_file(self.get_output_path(), stitch_plan, self.document.getroot())
- render_stitch_plan(self.document.getroot(), stitch_plan)
diff --git a/lib/extensions/embroider_settings.py b/lib/extensions/embroider_settings.py
new file mode 100644
index 00000000..88e2ba9b
--- /dev/null
+++ b/lib/extensions/embroider_settings.py
@@ -0,0 +1,17 @@
+from .base import InkstitchExtension
+
+
+class EmbroiderSettings(InkstitchExtension):
+ '''
+ This saves embroider settings into the metadata of the file
+ '''
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-c", "--collapse_len_mm",
+ action="store", type=float,
+ dest="collapse_length_mm", default=3.0,
+ help="max collapse length (mm)")
+
+ def effect(self):
+ self.metadata = self.get_inkstitch_metadata()
+ self.metadata['collapse_len_mm'] = self.options.collapse_length_mm
diff --git a/lib/extensions/flip.py b/lib/extensions/flip.py
index 0864da85..87b8b3f0 100644
--- a/lib/extensions/flip.py
+++ b/lib/extensions/flip.py
@@ -1,9 +1,8 @@
import inkex
-import cubicsuperpath
-from .base import InkstitchExtension
-from ..i18n import _
from ..elements import SatinColumn
+from ..i18n import _
+from .base import InkstitchExtension
class Flip(InkstitchExtension):
@@ -14,13 +13,13 @@ class Flip(InkstitchExtension):
first, second = satin.rail_indices
csp[first], csp[second] = csp[second], csp[first]
- satin.node.set("d", cubicsuperpath.formatPath(csp))
+ satin.node.set("d", inkex.paths.CubicSuperPath.to_path(csp))
def effect(self):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select one or more satin columns to flip."))
return
diff --git a/lib/extensions/import_threadlist.py b/lib/extensions/import_threadlist.py
index d31c0d69..029043c2 100644
--- a/lib/extensions/import_threadlist.py
+++ b/lib/extensions/import_threadlist.py
@@ -12,20 +12,23 @@ from .base import InkstitchExtension
class ImportThreadlist(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-f", "--filepath", type="str", default="", dest="filepath")
- self.OptionParser.add_option("-m", "--method", type="int", default=1, dest="method")
- self.OptionParser.add_option("-t", "--palette", type="str", default=None, dest="palette")
+ self.arg_parser.add_argument("-f", "--filepath", type=str, default="", dest="filepath")
+ self.arg_parser.add_argument("-m", "--method", type=int, default=1, dest="method")
+ self.arg_parser.add_argument("-t", "--palette", type=str, default=None, dest="palette")
def effect(self):
# Remove selection, we want all the elements in the document
- self.selected = {}
+ self.svg.selected.clear()
if not self.get_elements():
return
path = self.options.filepath
if not os.path.exists(path):
- print >> sys.stderr, _("File not found.")
+ inkex.errormsg(_("File not found."))
+ sys.exit(1)
+ if os.path.isdir(path):
+ inkex.errormsg(_("The filepath specified is not a file but a dictionary.\nPlease choose a threadlist file to import."))
sys.exit(1)
method = self.options.method
@@ -35,11 +38,11 @@ class ImportThreadlist(InkstitchExtension):
colors = self.parse_threadlist_by_catalog_number(path)
if all(c is None for c in colors):
- print >>sys.stderr, _("Couldn't find any matching colors in the file.")
+ inkex.errormsg(_("Couldn't find any matching colors in the file."))
if method == 1:
- print >>sys.stderr, _('Please try to import as "other threadlist" and specify a color palette below.')
+ inkex.errormsg(_('Please try to import as "other threadlist" and specify a color palette below.'))
else:
- print >>sys.stderr, _("Please chose an other color palette for your design.")
+ inkex.errormsg(_("Please chose an other color palette for your design."))
sys.exit(1)
# Iterate through the color blocks to apply colors
diff --git a/lib/extensions/input.py b/lib/extensions/input.py
index 957d355c..c6dcb698 100644
--- a/lib/extensions/input.py
+++ b/lib/extensions/input.py
@@ -1,8 +1,9 @@
import os
-import pyembroidery
-from inkex import etree
import inkex
+from lxml import etree
+
+import pyembroidery
from ..stitch_plan import StitchPlan
from ..svg import PIXELS_PER_MM, render_stitch_plan
@@ -10,7 +11,7 @@ from ..svg.tags import INKSCAPE_LABEL
class Input(object):
- def affect(self, args):
+ def run(self, args):
embroidery_file = args[0]
pattern = pyembroidery.read(embroidery_file)
@@ -47,11 +48,11 @@ class Input(object):
# rename the Stitch Plan layer so that it doesn't get overwritten by Embroider
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
- layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file.decode("UTF-8")))
+ layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file))
layer.attrib.pop('id')
# Shift the design so that its origin is at the center of the canvas
# Note: this is NOT the same as centering the design in the canvas!
layer.set('transform', 'translate(%s,%s)' % (extents[0], extents[1]))
- print etree.tostring(svg)
+ print(etree.tostring(svg).decode('utf-8'))
diff --git a/lib/extensions/layer_commands.py b/lib/extensions/layer_commands.py
index e710e351..89726510 100644
--- a/lib/extensions/layer_commands.py
+++ b/lib/extensions/layer_commands.py
@@ -1,9 +1,10 @@
import inkex
+from lxml import etree
-from ..commands import LAYER_COMMANDS, get_command_description, ensure_symbol
+from ..commands import LAYER_COMMANDS, ensure_symbol, get_command_description
from ..i18n import _
from ..svg import get_correction_transform
-from ..svg.tags import SVG_USE_TAG, INKSCAPE_LABEL, XLINK_HREF
+from ..svg.tags import INKSCAPE_LABEL, SVG_USE_TAG, XLINK_HREF
from .commands import CommandsExtension
@@ -17,20 +18,19 @@ class LayerCommands(CommandsExtension):
inkex.errormsg(_("Please choose one or more commands to add."))
return
- self.ensure_current_layer()
- correction_transform = get_correction_transform(self.current_layer, child=True)
+ correction_transform = get_correction_transform(self.svg.get_current_layer(), child=True)
for i, command in enumerate(commands):
ensure_symbol(self.document, command)
- inkex.etree.SubElement(self.current_layer, SVG_USE_TAG,
- {
- "id": self.uniqueId("use"),
- INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
- XLINK_HREF: "#inkstitch_%s" % command,
- "height": "100%",
- "width": "100%",
- "x": str(i * 20),
- "y": "-10",
- "transform": correction_transform
- })
+ etree.SubElement(self.svg.get_current_layer(), SVG_USE_TAG,
+ {
+ "id": self.uniqueId("use"),
+ INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
+ XLINK_HREF: "#inkstitch_%s" % command,
+ "height": "100%",
+ "width": "100%",
+ "x": str(i * 20),
+ "y": "-10",
+ "transform": correction_transform
+ })
diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py
index d988778d..ee0dd9a0 100644
--- a/lib/extensions/lettering.py
+++ b/lib/extensions/lettering.py
@@ -1,14 +1,12 @@
-# -*- coding: UTF-8 -*-
-
import json
import os
import sys
-from base64 import b64decode, b64encode
import appdirs
import inkex
import wx
import wx.adv
+from lxml import etree
from ..elements import nodes_to_elements
from ..gui import PresetsPanel, SimulatorPreview, info_dialog
@@ -19,6 +17,7 @@ from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_LETTERING, SVG_GROUP_TAG,
SVG_PATH_TAG)
from ..utils import DotDict, cache, get_bundled_dir
from .commands import CommandsExtension
+from .lettering_custom_font_dir import get_custom_font_dir
class LetteringFrame(wx.Frame):
@@ -45,6 +44,7 @@ class LetteringFrame(wx.Frame):
# font details
self.font_description = wx.StaticText(self, wx.ID_ANY)
+ self.Bind(wx.EVT_SIZE, self.resize)
# options
self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("Options"))
@@ -80,7 +80,7 @@ class LetteringFrame(wx.Frame):
"""Load the settings saved into the SVG group element"""
self.settings = DotDict({
- "text": u"",
+ "text": "",
"back_and_forth": False,
"font": None,
"scale": 100
@@ -88,7 +88,7 @@ class LetteringFrame(wx.Frame):
try:
if INKSTITCH_LETTERING in self.group.attrib:
- self.settings.update(json.loads(b64decode(self.group.get(INKSTITCH_LETTERING))))
+ self.settings.update(json.loads(self.group.get(INKSTITCH_LETTERING)))
return
except (TypeError, ValueError):
pass
@@ -103,22 +103,14 @@ class LetteringFrame(wx.Frame):
def save_settings(self):
"""Save the settings into the SVG group element."""
-
- # We base64 encode the string before storing it in an XML attribute.
- # In theory, lxml should properly html-encode the string, using HTML
- # entities like &#10; as necessary. However, we've found that Inkscape
- # incorrectly interpolates the HTML entities upon reading the
- # extension's output, rather than leaving them as is.
- #
- # Details:
- # https://bugs.launchpad.net/inkscape/+bug/1804346
- self.group.set(INKSTITCH_LETTERING, b64encode(json.dumps(self.settings)))
+ self.group.set(INKSTITCH_LETTERING, json.dumps(self.settings))
def update_font_list(self):
font_paths = {
get_bundled_dir("fonts"),
os.path.expanduser("~/.inkstitch/fonts"),
os.path.join(appdirs.user_config_dir('inkstitch'), 'fonts'),
+ get_custom_font_dir()
}
self.fonts = {}
@@ -130,13 +122,12 @@ class LetteringFrame(wx.Frame):
except OSError:
continue
- try:
- for font_dir in font_dirs:
- font = Font(os.path.join(font_path, font_dir))
- self.fonts[font.name] = font
- self.fonts_by_id[font.id] = font
- except FontError:
- pass
+ for font_dir in font_dirs:
+ font = Font(os.path.join(font_path, font_dir))
+ if font.name == "" or font.id == "":
+ continue
+ self.fonts[font.name] = font
+ self.fonts_by_id[font.id] = font
if len(self.fonts) == 0:
info_dialog(self, _("Unable to find any fonts! Please try reinstalling Ink/Stitch."))
@@ -165,13 +156,13 @@ class LetteringFrame(wx.Frame):
self.font_chooser.Append(font.name)
def get_font_names(self):
- font_names = [font.name for font in self.fonts.itervalues()]
+ font_names = [font.name for font in self.fonts.values()]
font_names.sort()
return font_names
def get_font_descriptions(self):
- return {font.name: font.description for font in self.fonts.itervalues()}
+ return {font.name: font.description for font in self.fonts.values()}
def set_initial_font(self, font_id):
if font_id:
@@ -191,7 +182,7 @@ class LetteringFrame(wx.Frame):
try:
return self.fonts_by_id[self.DEFAULT_FONT]
except KeyError:
- return self.fonts.values()[0]
+ return list(self.fonts.values())[0]
def on_change(self, attribute, event):
self.settings[attribute] = event.GetEventObject().GetValue()
@@ -202,7 +193,11 @@ class LetteringFrame(wx.Frame):
self.settings.font = font.id
self.scale_spinner.SetRange(int(font.min_scale * 100), int(font.max_scale * 100))
- font_variants = font.has_variants()
+ font_variants = []
+ try:
+ font_variants = font.has_variants()
+ except FontError:
+ pass
# Update font description
color = (0, 0, 0)
@@ -212,7 +207,7 @@ class LetteringFrame(wx.Frame):
description = _('This font has no available font variant. Please update or remove the font.')
self.font_description.SetLabel(description)
self.font_description.SetForegroundColour(color)
- self.font_description.Wrap(self.GetSize().width - 20)
+ self.font_description.Wrap(self.GetSize().width - 35)
if font.reversible:
self.back_and_forth_checkbox.Enable()
@@ -230,18 +225,24 @@ class LetteringFrame(wx.Frame):
self.trim_checkbox.SetValue(False)
self.update_preview()
- self.GetSizer().Layout()
+ self.Layout()
+
+ def resize(self, event=None):
+ description = self.font_description.GetLabel().replace("\n", " ")
+ self.font_description.SetLabel(description)
+ self.font_description.Wrap(self.GetSize().width - 35)
+ self.Layout()
def update_preview(self, event=None):
self.preview.update()
- def update_lettering(self):
+ def update_lettering(self, raise_error=False):
del self.group[:]
if self.settings.scale == 100:
destination_group = self.group
else:
- destination_group = inkex.etree.SubElement(self.group, SVG_GROUP_TAG, {
+ destination_group = etree.SubElement(self.group, SVG_GROUP_TAG, {
# L10N The user has chosen to scale the text by some percentage
# (50%, 200%, etc). If you need to use the percentage symbol,
# make sure to double it (%%).
@@ -249,7 +250,14 @@ class LetteringFrame(wx.Frame):
})
font = self.fonts.get(self.font_chooser.GetValue(), self.default_font)
- font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, trim=self.settings.trim)
+ try:
+ font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, trim=self.settings.trim)
+ except FontError as e:
+ if raise_error:
+ inkex.errormsg("Error: Text cannot be applied to the document.\n%s" % e)
+ return
+ else:
+ pass
if self.settings.scale != 100:
destination_group.attrib['transform'] = 'scale(%s)' % (self.settings.scale / 100.0)
@@ -295,7 +303,7 @@ class LetteringFrame(wx.Frame):
def apply(self, event):
self.preview.disable()
- self.update_lettering()
+ self.update_lettering(True)
self.save_settings()
self.close()
@@ -369,10 +377,10 @@ class Lettering(CommandsExtension):
self.cancelled = True
def get_or_create_group(self):
- if self.selected:
+ if self.svg.selected:
groups = set()
- for node in self.selected.itervalues():
+ for node in self.svg.selected.values():
if node.tag == SVG_GROUP_TAG and INKSTITCH_LETTERING in node.attrib:
groups.add(node)
@@ -391,9 +399,9 @@ class Lettering(CommandsExtension):
return list(groups)[0]
else:
self.ensure_current_layer()
- return inkex.etree.SubElement(self.current_layer, SVG_GROUP_TAG, {
+ return etree.SubElement(self.svg.get_current_layer(), SVG_GROUP_TAG, {
INKSCAPE_LABEL: _("Ink/Stitch Lettering"),
- "transform": get_correction_transform(self.current_layer, child=True)
+ "transform": get_correction_transform(self.svg.get_current_layer(), child=True)
})
def effect(self):
@@ -405,7 +413,7 @@ class Lettering(CommandsExtension):
display = wx.Display(current_screen)
display_size = display.GetClientArea()
frame_size = frame.GetSize()
- frame.SetPosition((display_size[0], display_size[3] / 2 - frame_size[1] / 2))
+ frame.SetPosition((int(display_size[0]), int(display_size[3] / 2 - frame_size[1] / 2)))
frame.Show()
app.MainLoop()
diff --git a/lib/extensions/lettering_custom_font_dir.py b/lib/extensions/lettering_custom_font_dir.py
new file mode 100644
index 00000000..0103c7d6
--- /dev/null
+++ b/lib/extensions/lettering_custom_font_dir.py
@@ -0,0 +1,48 @@
+import json
+import os
+
+import appdirs
+from inkex import errormsg
+
+from ..i18n import _
+from .base import InkstitchExtension
+
+
+class LetteringCustomFontDir(InkstitchExtension):
+ '''
+ This extension will create a json file to store a custom directory path for additional user fonts
+ '''
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-d", "--path", type=str, default="", dest="path")
+
+ def effect(self):
+ path = self.options.path
+ if not os.path.isdir(path):
+ errormsg(_("Please specify the directory of your custom fonts."))
+ return
+
+ data = {'custom_font_dir': '%s' % path}
+
+ try:
+ config_path = appdirs.user_config_dir('inkstitch')
+ except ImportError:
+ config_path = os.path.expanduser('~/.inkstitch')
+ config_path = os.path.join(config_path, 'custom_dirs.json')
+
+ with open(config_path, 'w', encoding="utf8") as font_data:
+ json.dump(data, font_data, indent=4, ensure_ascii=False)
+
+
+def get_custom_font_dir():
+ custom_font_dir_path = os.path.join(appdirs.user_config_dir('inkstitch'), 'custom_dirs.json')
+ try:
+ with open(custom_font_dir_path, 'r') as custom_dirs:
+ custom_dir = json.load(custom_dirs)
+ except (IOError, ValueError):
+ return ""
+ try:
+ return custom_dir['custom_font_dir']
+ except KeyError:
+ pass
+ return ""
diff --git a/lib/extensions/lettering_generate_json.py b/lib/extensions/lettering_generate_json.py
new file mode 100644
index 00000000..9b44c367
--- /dev/null
+++ b/lib/extensions/lettering_generate_json.py
@@ -0,0 +1,76 @@
+import json
+import os
+import sys
+
+from inkex import Boolean
+
+from ..i18n import _
+from ..lettering.kerning import FontKerning
+from .base import InkstitchExtension
+
+
+class LetteringGenerateJson(InkstitchExtension):
+ '''
+ This extension helps font creators to generate the json file for the lettering tool
+ '''
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-n", "--font-name", type=str, default="Font", dest="font_name")
+ self.arg_parser.add_argument("-d", "--font-description", type=str, default="Description", dest="font_description")
+ self.arg_parser.add_argument("-s", "--auto-satin", type=Boolean, default="true", dest="auto_satin")
+ self.arg_parser.add_argument("-r", "--reversible", type=Boolean, default="true", dest="reversible")
+ self.arg_parser.add_argument("-g", "--default-glyph", type=str, default="", dest="default_glyph")
+ self.arg_parser.add_argument("-i", "--min-scale", type=float, default=1, dest="min_scale")
+ self.arg_parser.add_argument("-a", "--max-scale", type=float, default=1, dest="max_scale")
+ self.arg_parser.add_argument("-l", "--leading", type=int, default=0, dest="leading")
+ self.arg_parser.add_argument("-p", "--font-file", type=str, default="", dest="path")
+
+ def effect(self):
+ # file paths
+ path = self.options.path
+ if not os.path.isfile(path):
+ print(_("Please specify a font file."), file=sys.stderr)
+ return
+ output_path = os.path.join(os.path.dirname(path), 'font.json')
+
+ # kerning
+ kerning = FontKerning(path)
+
+ horiz_adv_x = kerning.horiz_adv_x()
+ hkern = kerning.hkern()
+ word_spacing = kerning.word_spacing()
+ letter_spacing = kerning.letter_spacing()
+ units_per_em = kerning.units_per_em()
+ # missing_glyph_spacing = kerning.missing_glyph_spacing()
+
+ # if letter spacing returns 0, it hasn't been specified in the font file
+ # Ink/Stitch will calculate the width of each letter automatically
+ if letter_spacing == 0:
+ letter_spacing = None
+
+ # if leading (line height) is set to 0, the font author wants Ink/Stitch to use units_per_em
+ # if units_per_em is not defined in the font file a default value will be returned
+ if self.options.leading == 0:
+ leading = units_per_em
+ else:
+ leading = self.options.leading
+
+ # collect data
+ data = {'name': self.options.font_name,
+ 'description': self.options.font_description,
+ 'leading': leading,
+ 'auto_satin': self.options.auto_satin,
+ 'reversible': self.options.reversible,
+ 'default_glyph': self.options.default_glyph,
+ 'min_scale': self.options.min_scale,
+ 'max_scale': self.options.max_scale,
+ 'horiz_adv_x_default': letter_spacing,
+ 'horiz_adv_x_space': word_spacing,
+ 'units_per_em': units_per_em,
+ 'horiz_adv_x': horiz_adv_x,
+ 'kerning_pairs': hkern
+ }
+
+ # write data to font.json into the same directory as the font file
+ with open(output_path, 'w', encoding="utf8") as font_data:
+ json.dump(data, font_data, indent=4, ensure_ascii=False)
diff --git a/lib/extensions/lettering_remove_kerning.py b/lib/extensions/lettering_remove_kerning.py
new file mode 100644
index 00000000..aec8717e
--- /dev/null
+++ b/lib/extensions/lettering_remove_kerning.py
@@ -0,0 +1,30 @@
+import os
+
+from inkex import NSS
+from lxml import etree
+
+from .base import InkstitchExtension
+
+
+class LetteringRemoveKerning(InkstitchExtension):
+ '''
+ This extension helps font creators to generate the json file for the lettering tool
+ '''
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-p", "--font-files", type=str, default="", dest="paths")
+
+ def effect(self):
+ # file paths
+ paths = self.options.paths.split("|")
+ for path in paths:
+ if not os.path.isfile(path):
+ continue
+ with open(path, 'r+', encoding="utf-8") as fontfile:
+ svg = etree.parse(fontfile)
+ xpath = ".//svg:font[1]"
+ kerning = svg.xpath(xpath, namespaces=NSS)[0]
+ kerning.getparent().remove(kerning)
+ fontfile.seek(0)
+ fontfile.write(etree.tostring(svg).decode('utf-8'))
+ fontfile.truncate()
diff --git a/lib/extensions/object_commands.py b/lib/extensions/object_commands.py
index d33ab2ba..f1c2fb46 100644
--- a/lib/extensions/object_commands.py
+++ b/lib/extensions/object_commands.py
@@ -12,7 +12,7 @@ class ObjectCommands(CommandsExtension):
if not self.get_elements():
return
- if not self.selected:
+ if not self.svg.selected:
inkex.errormsg(_("Please select one or more objects to which to attach commands."))
return
diff --git a/lib/extensions/output.py b/lib/extensions/output.py
index ccf4d7cb..52e9d3a9 100644
--- a/lib/extensions/output.py
+++ b/lib/extensions/output.py
@@ -11,7 +11,7 @@ class Output(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- def getoptions(self, args=sys.argv[1:]):
+ def parse_arguments(self, args=sys.argv[1:]):
# inkex's option parsing can't handle arbitrary command line arguments
# that may be passed for a given output format, so we'll just parse the
# args ourselves. :P
@@ -39,14 +39,16 @@ class Output(InkstitchExtension):
self.file_extension = self.settings.pop('format')
del sys.argv[1:]
- InkstitchExtension.getoptions(self, extra_args)
+ InkstitchExtension.parse_arguments(self, extra_args)
def effect(self):
if not self.get_elements():
return
+ self.metadata = self.get_inkstitch_metadata()
+ collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches, disable_ties=self.settings.get('laser_mode', False))
+ stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False))
temp_file = tempfile.NamedTemporaryFile(suffix=".%s" % self.file_extension, delete=False)
@@ -62,7 +64,7 @@ class Output(InkstitchExtension):
# inkscape will read the file contents from stdout and copy
# to the destination file that the user chose
with open(temp_file.name, "rb") as output_file:
- sys.stdout.write(output_file.read())
+ sys.stdout.buffer.write(output_file.read())
sys.stdout.flush()
# clean up the temp file
diff --git a/lib/extensions/params.py b/lib/extensions/params.py
index 600a4669..910941fe 100644
--- a/lib/extensions/params.py
+++ b/lib/extensions/params.py
@@ -151,7 +151,7 @@ class ParamsTab(ScrolledPanel):
# because they're grayed out anyway.
return values
- for name, input in self.param_inputs.iteritems():
+ for name, input in self.param_inputs.items():
if input in self.changed_inputs and input != self.toggle_checkbox:
values[name] = input.GetValue()
@@ -161,7 +161,7 @@ class ParamsTab(ScrolledPanel):
values = self.get_values()
for node in self.nodes:
# print >> sys.stderr, "apply: ", self.name, node.id, values
- for name, value in values.iteritems():
+ for name, value in values.items():
node.set_param(name, value)
def on_change(self, callable):
@@ -181,7 +181,7 @@ class ParamsTab(ScrolledPanel):
def load_preset(self, preset):
preset_data = preset.get(self.name, {})
- for name, value in preset_data.iteritems():
+ for name, value in preset_data.items():
if name in self.param_inputs:
self.param_inputs[name].SetValue(value)
self.changed_inputs.add(self.param_inputs[name])
@@ -198,16 +198,16 @@ class ParamsTab(ScrolledPanel):
description = _("These settings will be applied to %d objects.") % len(self.nodes)
if any(len(param.values) > 1 for param in self.params):
- description += u"\n • " + _("Some settings had different values across objects. Select a value from the dropdown or enter a new one.")
+ description += "\n • " + _("Some settings had different values across objects. Select a value from the dropdown or enter a new one.")
if self.dependent_tabs:
if len(self.dependent_tabs) == 1:
- description += u"\n • " + _("Disabling this tab will disable the following %d tabs.") % len(self.dependent_tabs)
+ description += "\n • " + _("Disabling this tab will disable the following %d tabs.") % len(self.dependent_tabs)
else:
- description += u"\n • " + _("Disabling this tab will disable the following tab.")
+ description += "\n • " + _("Disabling this tab will disable the following tab.")
if self.paired_tab:
- description += u"\n • " + _("Enabling this tab will disable %s and vice-versa.") % self.paired_tab.name
+ description += "\n • " + _("Enabling this tab will disable %s and vice-versa.") % self.paired_tab.name
self.description_text = description
@@ -285,7 +285,7 @@ class ParamsTab(ScrolledPanel):
self.settings_grid.Add(input, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.LEFT, border=40)
self.settings_grid.Add(wx.StaticText(self, label=param.unit or ""), proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
- self.inputs_to_params = {v: k for k, v in self.param_inputs.iteritems()}
+ self.inputs_to_params = {v: k for k, v in self.param_inputs.items()}
box.Add(self.settings_grid, proportion=1, flag=wx.ALL, border=10)
self.SetSizer(box)
@@ -443,9 +443,9 @@ class SettingsFrame(wx.Frame):
self.notebook.AddPage(tab, tab.name)
sizer_1.Add(self.notebook, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10)
sizer_1.Add(self.presets_panel, 0, flag=wx.EXPAND | wx.ALL, border=10)
- sizer_3.Add(self.cancel_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 5)
- sizer_3.Add(self.use_last_button, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, 5)
- sizer_3.Add(self.apply_button, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, 5)
+ sizer_3.Add(self.cancel_button, 0, wx.RIGHT, 5)
+ sizer_3.Add(self.use_last_button, 0, wx.RIGHT | wx.BOTTOM, 5)
+ sizer_3.Add(self.apply_button, 0, wx.RIGHT | wx.BOTTOM, 5)
sizer_1.Add(sizer_3, 0, wx.ALIGN_RIGHT, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
@@ -491,7 +491,7 @@ class Params(InkstitchExtension):
element.order = z
nodes_by_class[cls].append(element)
- return sorted(nodes_by_class.items(), key=lambda (cls, nodes): cls.__name__)
+ return sorted(list(nodes_by_class.items()), key=lambda cls_nodes: cls_nodes[0].__name__)
def get_values(self, param, nodes):
getter = 'get_param'
@@ -501,17 +501,16 @@ class Params(InkstitchExtension):
else:
getter = 'get_param'
- values = filter(lambda item: item is not None,
- (getattr(node, getter)(param.name, param.default) for node in nodes))
+ values = [item for item in (getattr(node, getter)(param.name, param.default) for node in nodes) if item is not None]
return values
def group_params(self, params):
def by_group_and_sort_index(param):
- return param.group, param.sort_index
+ return param.group or "", param.sort_index
def by_group(param):
- return param.group
+ return param.group or ""
return groupby(sorted(params, key=by_group_and_sort_index), by_group)
@@ -526,7 +525,7 @@ class Params(InkstitchExtension):
# If multiple tabs are enabled, make sure dependent
# tabs are grouped with the parent.
- parent,
+ parent and parent.name,
# Within parent/dependents, put the parent first.
tab == parent
@@ -565,12 +564,13 @@ class Params(InkstitchExtension):
parent_tab = None
new_tabs = []
+
for group, params in self.group_params(params):
tab_name = group or cls.element_name
tab = ParamsTab(parent, id=wx.ID_ANY, name=tab_name, params=list(params), nodes=nodes)
new_tabs.append(tab)
- if group is None:
+ if group == "":
parent_tab = tab
self.assign_parents(new_tabs, parent_tab)
@@ -594,7 +594,7 @@ class Params(InkstitchExtension):
display = wx.Display(current_screen)
display_size = display.GetClientArea()
frame_size = frame.GetSize()
- frame.SetPosition((display_size[0], display_size[3] / 2 - frame_size[1] / 2))
+ frame.SetPosition((int(display_size[0]), int(display_size[3]/2 - frame_size[1]/2)))
frame.Show()
app.MainLoop()
diff --git a/lib/extensions/print_pdf.py b/lib/extensions/print_pdf.py
index 8af07cf7..1218b5e9 100644
--- a/lib/extensions/print_pdf.py
+++ b/lib/extensions/print_pdf.py
@@ -1,19 +1,19 @@
-from copy import deepcopy
-from datetime import date
import errno
import json
import logging
import os
import socket
import sys
-from threading import Thread
import time
+from copy import deepcopy
+from datetime import date
+from threading import Thread
import appdirs
-from flask import Flask, request, Response, send_from_directory, jsonify
-import inkex
-from jinja2 import Environment, FileSystemLoader, select_autoescape
import requests
+from flask import Flask, Response, jsonify, request, send_from_directory
+from jinja2 import Environment, FileSystemLoader, select_autoescape
+from lxml import etree
from ..gui import open_url
from ..i18n import translation as inkstitch_translation
@@ -72,6 +72,11 @@ class PrintPreviewServer(Thread):
def __setup_app(self): # noqa: C901
self.__set_resources_path()
+
+ # Disable warning about using a development server in a production environment
+ cli = sys.modules['flask.cli']
+ cli.show_server_banner = lambda *x: None
+
self.app = Flask(__name__)
@self.app.route('/')
@@ -211,13 +216,21 @@ class Print(InkstitchExtension):
layers = svg.findall("./g[@%s='layer']" % INKSCAPE_GROUPMODE)
stitch_plan_layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
+ # Make sure there is no leftover translation from stitch plan preview
+ stitch_plan_layer.pop('transform')
+
+ # objects outside of the viewbox are invisible
+ # TODO: if we want them to be seen, we need to redefine document size to fit the design
+ # this is just a quick fix and doesn't work on realistic view
+ svg.set('style', 'overflow:visible;')
+
# First, delete all of the other layers. We don't need them and they'll
# just bulk up the SVG.
for layer in layers:
if layer is not stitch_plan_layer:
svg.remove(layer)
- overview_svg = inkex.etree.tostring(svg)
+ overview_svg = etree.tostring(svg).decode('utf-8')
color_block_groups = stitch_plan_layer.getchildren()
color_block_svgs = []
@@ -229,7 +242,7 @@ class Print(InkstitchExtension):
stitch_plan_layer.append(group)
# save an SVG preview
- color_block_svgs.append(inkex.etree.tostring(svg))
+ color_block_svgs.append(etree.tostring(svg).decode('utf-8'))
return overview_svg, color_block_svgs
@@ -269,13 +282,15 @@ class Print(InkstitchExtension):
# objects. It's almost certain they meant to print the whole design.
# If they really wanted to print just a few objects, they could set
# the rest invisible temporarily.
- self.selected = {}
+ self.svg.selected.clear()
if not self.get_elements():
return
+ self.metadata = self.get_inkstitch_metadata()
+ collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches)
+ stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
palette = ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette'])
overview_svg, color_block_svgs = self.render_svgs(stitch_plan, realistic=False)
diff --git a/lib/extensions/remove_embroidery_settings.py b/lib/extensions/remove_embroidery_settings.py
index 2a4d06dd..6ccdb703 100644
--- a/lib/extensions/remove_embroidery_settings.py
+++ b/lib/extensions/remove_embroidery_settings.py
@@ -1,4 +1,4 @@
-import inkex
+from inkex import NSS, Boolean
from ..commands import find_commands
from ..svg.svg import find_elements
@@ -8,9 +8,9 @@ from .base import InkstitchExtension
class RemoveEmbroiderySettings(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
- self.OptionParser.add_option("-p", "--del_params", dest="del_params", type="inkbool", default=True)
- self.OptionParser.add_option("-c", "--del_commands", dest="del_commands", type="inkbool", default=False)
- self.OptionParser.add_option("-d", "--del_print", dest="del_print", type="inkbool", default=False)
+ self.arg_parser.add_argument("-p", "--del_params", dest="del_params", type=Boolean, default=True)
+ self.arg_parser.add_argument("-c", "--del_commands", dest="del_commands", type=Boolean, default=False)
+ self.arg_parser.add_argument("-d", "--del_print", dest="del_print", type=Boolean, default=False)
def effect(self):
self.svg = self.document.getroot()
@@ -30,24 +30,24 @@ class RemoveEmbroiderySettings(InkstitchExtension):
self.remove_element(print_setting)
def remove_params(self):
- if not self.selected:
+ if not self.svg.selected:
xpath = ".//svg:path"
elements = find_elements(self.svg, xpath)
self.remove_inkstitch_attributes(elements)
else:
- for node in self.selected:
+ for node in self.svg.selected:
elements = self.get_selected_elements(node)
self.remove_inkstitch_attributes(elements)
def remove_commands(self):
- if not self.selected:
+ if not self.svg.selected:
# we are not able to grab commands by a specific id
# so let's move through every object instead and see if it has a command
xpath = ".//svg:path|.//svg:circle|.//svg:rect|.//svg:ellipse"
elements = find_elements(self.svg, xpath)
else:
elements = []
- for node in self.selected:
+ for node in self.svg.selected:
elements.extend(self.get_selected_elements(node))
if elements:
@@ -56,7 +56,7 @@ class RemoveEmbroiderySettings(InkstitchExtension):
group = command.connector.getparent()
group.getparent().remove(group)
- if not self.selected:
+ if not self.svg.selected:
# remove standalone commands
standalone_commands = ".//svg:use[starts-with(@xlink:href, '#inkstitch_')]"
self.remove_elements(standalone_commands)
@@ -84,5 +84,5 @@ class RemoveEmbroiderySettings(InkstitchExtension):
def remove_inkstitch_attributes(self, elements):
for element in elements:
for attrib in element.attrib:
- if attrib.startswith(inkex.NSS['inkstitch'], 1):
+ if attrib.startswith(NSS['inkstitch'], 1):
del element.attrib[attrib]
diff --git a/lib/extensions/reorder.py b/lib/extensions/reorder.py
new file mode 100644
index 00000000..4db02760
--- /dev/null
+++ b/lib/extensions/reorder.py
@@ -0,0 +1,36 @@
+import inkex
+
+from .base import InkstitchExtension
+
+
+class Reorder(InkstitchExtension):
+ # Remove selected objects from the document and readd them in the order they
+ # were selected.
+
+ def get_selected_in_order(self):
+ selected = []
+
+ for i in self.options.ids:
+ path = '//*[@id="%s"]' % i
+ for node in self.document.xpath(path, namespaces=inkex.NSS):
+ selected.append(node)
+
+ return selected
+
+ def effect(self):
+ objects = self.get_selected_in_order()
+
+ for obj in objects[1:]:
+ obj.getparent().remove(obj)
+
+ insert_parent = objects[0].getparent()
+ insert_pos = insert_parent.index(objects[0])
+
+ insert_parent.remove(objects[0])
+
+ insert_parent[insert_pos:insert_pos] = objects
+
+
+if __name__ == '__main__':
+ e = Reorder()
+ e.affect()
diff --git a/lib/extensions/stitch_plan_preview.py b/lib/extensions/stitch_plan_preview.py
index b89e24a7..e1c398b5 100644
--- a/lib/extensions/stitch_plan_preview.py
+++ b/lib/extensions/stitch_plan_preview.py
@@ -4,7 +4,6 @@ from .base import InkstitchExtension
class StitchPlanPreview(InkstitchExtension):
-
def effect(self):
# delete old stitch plan
svg = self.document.getroot()
@@ -15,9 +14,13 @@ class StitchPlanPreview(InkstitchExtension):
# create new stitch plan
if not self.get_elements():
return
+
+ realistic = False
+ self.metadata = self.get_inkstitch_metadata()
+ collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches)
- render_stitch_plan(svg, stitch_plan)
+ stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
+ render_stitch_plan(svg, stitch_plan, realistic)
# translate stitch plan to the right side of the canvas
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
diff --git a/lib/extensions/troubleshoot.py b/lib/extensions/troubleshoot.py
index 6b63390a..bd352d8f 100644
--- a/lib/extensions/troubleshoot.py
+++ b/lib/extensions/troubleshoot.py
@@ -1,12 +1,13 @@
import textwrap
-import inkex
+from inkex import errormsg
+from lxml import etree
from ..commands import add_layer_commands
from ..elements.validation import (ObjectTypeWarning, ValidationError,
ValidationWarning)
from ..i18n import _
-from ..svg import get_correction_transform
+from ..svg.path import get_correction_transform
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SODIPODI_ROLE,
SVG_GROUP_TAG, SVG_PATH_TAG, SVG_TEXT_TAG,
SVG_TSPAN_TAG)
@@ -43,7 +44,7 @@ class Troubleshoot(InkstitchExtension):
message += "\n\n"
message += _("If you are still having trouble with a shape not being embroidered, "
"check if it is in a layer with an ignore command.")
- inkex.errormsg(message)
+ errormsg(message)
def insert_pointer(self, problem):
correction_transform = get_correction_transform(self.troubleshoot_layer)
@@ -61,7 +62,7 @@ class Troubleshoot(InkstitchExtension):
pointer_style = "stroke:#000000;stroke-width:0.2;fill:%s;" % (fill_color)
text_style = "fill:%s;stroke:#000000;stroke-width:0.2;font-size:8px;text-align:center;text-anchor:middle" % (fill_color)
- path = inkex.etree.Element(
+ path = etree.Element(
SVG_PATH_TAG,
{
"id": self.uniqueId("inkstitch__invalid_pointer__"),
@@ -73,7 +74,7 @@ class Troubleshoot(InkstitchExtension):
)
layer.insert(0, path)
- text = inkex.etree.Element(
+ text = etree.Element(
SVG_TEXT_TAG,
{
INKSCAPE_LABEL: _('Description'),
@@ -85,7 +86,7 @@ class Troubleshoot(InkstitchExtension):
)
layer.append(text)
- tspan = inkex.etree.Element(SVG_TSPAN_TAG)
+ tspan = etree.Element(SVG_TSPAN_TAG)
tspan.text = problem.name
text.append(tspan)
@@ -94,7 +95,7 @@ class Troubleshoot(InkstitchExtension):
layer = svg.find(".//*[@id='__validation_layer__']")
if layer is None:
- layer = inkex.etree.Element(
+ layer = etree.Element(
SVG_GROUP_TAG,
{
'id': '__validation_layer__',
@@ -109,7 +110,7 @@ class Troubleshoot(InkstitchExtension):
add_layer_commands(layer, ["ignore_layer"])
- error_group = inkex.etree.SubElement(
+ error_group = etree.SubElement(
layer,
SVG_GROUP_TAG,
{
@@ -118,7 +119,7 @@ class Troubleshoot(InkstitchExtension):
})
layer.append(error_group)
- warning_group = inkex.etree.SubElement(
+ warning_group = etree.SubElement(
layer,
SVG_GROUP_TAG,
{
@@ -127,7 +128,7 @@ class Troubleshoot(InkstitchExtension):
})
layer.append(warning_group)
- type_warning_group = inkex.etree.SubElement(
+ type_warning_group = etree.SubElement(
layer,
SVG_GROUP_TAG,
{
@@ -145,7 +146,7 @@ class Troubleshoot(InkstitchExtension):
svg = self.document.getroot()
text_x = str(float(svg.get('viewBox', '0 0 800 0').split(' ')[2]) + 5.0)
- text_container = inkex.etree.Element(
+ text_container = etree.Element(
SVG_TEXT_TAG,
{
"x": text_x,
@@ -160,7 +161,7 @@ class Troubleshoot(InkstitchExtension):
["", ""]
]
- for problem_type, problems in problem_types.items():
+ for problem_type, problems in list(problem_types.items()):
if problem_type == "error":
text_color = "#ff0000"
problem_type_header = _("Errors")
@@ -202,7 +203,7 @@ class Troubleshoot(InkstitchExtension):
text = self.split_text(text)
for text_line in text:
- tspan = inkex.etree.Element(
+ tspan = etree.Element(
SVG_TSPAN_TAG,
{
SODIPODI_ROLE: "line",
diff --git a/lib/extensions/zip.py b/lib/extensions/zip.py
index aebff331..fedf8c65 100644
--- a/lib/extensions/zip.py
+++ b/lib/extensions/zip.py
@@ -1,36 +1,34 @@
import os
import sys
import tempfile
+from copy import deepcopy
from zipfile import ZipFile
-import inkex
+from inkex import Boolean
+from lxml import etree
+
import pyembroidery
from ..i18n import _
from ..output import write_embroidery_file
from ..stitch_plan import patches_to_stitch_plan
-from ..svg import PIXELS_PER_MM
-from .base import InkstitchExtension
from ..threads import ThreadCatalog
+from .base import InkstitchExtension
class Zip(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self)
- self.OptionParser.add_option("-c", "--collapse_len_mm",
- action="store", type="float",
- dest="collapse_length_mm", default=3.0,
- help="max collapse length (mm)")
# it's kind of obnoxious that I have to do this...
self.formats = []
for format in pyembroidery.supported_formats():
if 'writer' in format and format['category'] == 'embroidery':
extension = format['extension']
- self.OptionParser.add_option('--format-%s' % extension, type="inkbool", dest=extension)
+ self.arg_parser.add_argument('--format-%s' % extension, type=Boolean, dest=extension)
self.formats.append(extension)
- self.OptionParser.add_option('--format-svg', type="inkbool", dest='svg')
- self.OptionParser.add_option('--format-threadlist', type="inkbool", dest='threadlist')
+ self.arg_parser.add_argument('--format-svg', type=Boolean, dest='svg')
+ self.arg_parser.add_argument('--format-threadlist', type=Boolean, dest='threadlist')
self.formats.append('svg')
self.formats.append('threadlist')
@@ -38,8 +36,10 @@ class Zip(InkstitchExtension):
if not self.get_elements():
return
+ self.metadata = self.get_inkstitch_metadata()
+ collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
- stitch_plan = patches_to_stitch_plan(patches, self.options.collapse_length_mm * PIXELS_PER_MM)
+ stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
base_file_name = self.get_base_file_name()
path = tempfile.mkdtemp()
@@ -50,10 +50,10 @@ class Zip(InkstitchExtension):
if getattr(self.options, format):
output_file = os.path.join(path, "%s.%s" % (base_file_name, format))
if format == 'svg':
- output = open(output_file, 'w')
- output.write(inkex.etree.tostring(self.document.getroot()))
- output.close()
- if format == 'threadlist':
+ document = deepcopy(self.document.getroot())
+ with open(output_file, 'w', encoding='utf-8') as svg:
+ svg.write(etree.tostring(document).decode('utf-8'))
+ elif format == 'threadlist':
output_file = os.path.join(path, "%s_%s.txt" % (base_file_name, _("threadlist")))
output = open(output_file, 'w')
output.write(self.get_threadlist(stitch_plan, base_file_name))
@@ -76,8 +76,8 @@ class Zip(InkstitchExtension):
# inkscape will read the file contents from stdout and copy
# to the destination file that the user chose
- with open(temp_file.name) as output_file:
- sys.stdout.write(output_file.read())
+ with open(temp_file.name, 'rb') as output_file:
+ sys.stdout.buffer.write(output_file.read())
os.remove(temp_file.name)
for file in files:
diff --git a/lib/gui/electron.py b/lib/gui/electron.py
index 83486f78..ef215fb5 100644
--- a/lib/gui/electron.py
+++ b/lib/gui/electron.py
@@ -27,5 +27,5 @@ def open_url(url):
cwd = get_bundled_dir("electron")
# Any output on stdout will crash inkscape.
- null = open(os.devnull, 'w')
- return subprocess.Popen(command, cwd=cwd, stdout=null)
+ with open(os.devnull, 'w') as null:
+ return subprocess.Popen(command, cwd=cwd, stdout=null)
diff --git a/lib/gui/presets.py b/lib/gui/presets.py
index b3312f0e..2c0d0481 100644
--- a/lib/gui/presets.py
+++ b/lib/gui/presets.py
@@ -64,7 +64,7 @@ class PresetsPanel(wx.Panel):
presets_sizer = wx.StaticBoxSizer(self.presets_box, wx.HORIZONTAL)
self.preset_chooser.SetMinSize((200, -1))
- presets_sizer.Add(self.preset_chooser, 1, wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.BOTTOM | wx.EXPAND, 10)
+ presets_sizer.Add(self.preset_chooser, 1, wx.LEFT | wx.BOTTOM | wx.EXPAND, 10)
presets_sizer.Add(self.load_preset_button, 0, wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 10)
presets_sizer.Add(self.add_preset_button, 0, wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 10)
presets_sizer.Add(self.overwrite_preset_button, 0, wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 10)
@@ -106,7 +106,7 @@ class PresetsPanel(wx.Panel):
json.dump(presets, presets_file)
def update_preset_list(self):
- preset_names = self._load_presets().keys()
+ preset_names = list(self._load_presets().keys())
preset_names = [preset for preset in preset_names if not self.is_hidden(preset)]
self.preset_chooser.SetItems(sorted(preset_names))
diff --git a/lib/gui/simulator.py b/lib/gui/simulator.py
index 48709cb8..996bc8f9 100644
--- a/lib/gui/simulator.py
+++ b/lib/gui/simulator.py
@@ -1,21 +1,16 @@
-from itertools import izip
import sys
-from threading import Thread, Event
import time
import traceback
+from threading import Event, Thread
import wx
from wx.lib.intctrl import IntCtrl
from ..i18n import _
-from ..stitch_plan import stitch_plan_from_file, patches_to_stitch_plan
-
+from ..stitch_plan import patches_to_stitch_plan, stitch_plan_from_file
from ..svg import PIXELS_PER_MM
-
-
from .dialogs import info_dialog
-
# L10N command label at bottom of simulator window
COMMAND_NAMES = [_("STITCH"), _("JUMP"), _("TRIM"), _("STOP"), _("COLOR CHANGE")]
@@ -132,7 +127,7 @@ class ControlPanel(wx.Panel):
self.accel_entries = []
for shortcut_key in shortcut_keys:
- eventId = wx.NewId()
+ eventId = wx.NewIdRef()
self.accel_entries.append((shortcut_key[0], shortcut_key[1], eventId))
self.Bind(wx.EVT_MENU, shortcut_key[2], id=eventId)
@@ -378,7 +373,7 @@ class DrawingPanel(wx.Panel):
last_stitch = None
start = time.time()
- for pen, stitches in izip(self.pens, self.stitch_blocks):
+ for pen, stitches in zip(self.pens, self.stitch_blocks):
canvas.SetPen(pen)
if stitch + len(stitches) < self.current_stitch:
stitch += len(stitches)
@@ -423,7 +418,7 @@ class DrawingPanel(wx.Panel):
while scale_width < 50:
scale_width += one_mm
- scale_width_mm = scale_width / self.zoom / PIXELS_PER_MM
+ scale_width_mm = int(scale_width / self.zoom / PIXELS_PER_MM)
# The scale bar looks like this:
#
@@ -431,7 +426,7 @@ class DrawingPanel(wx.Panel):
# |_____|_____|
scale_lower_left_x = 20
- scale_lower_left_y = canvas_height - 20
+ scale_lower_left_y = canvas_height - 30
canvas.DrawLines(((scale_lower_left_x, scale_lower_left_y - 6),
(scale_lower_left_x, scale_lower_left_y),
@@ -504,7 +499,7 @@ class DrawingPanel(wx.Panel):
# We draw the thread with a thickness of 0.1mm. Real thread has a
# thickness of ~0.4mm, but if we did that, we wouldn't be able to
# see the individual stitches.
- return wx.Pen(color.visible_on_white.rgb, width=int(0.1 * PIXELS_PER_MM * self.PIXEL_DENSITY))
+ return wx.Pen(list(map(int, color.visible_on_white.rgb)), int(0.1 * PIXELS_PER_MM * self.PIXEL_DENSITY))
def parse_stitch_plan(self, stitch_plan):
self.pens = []
@@ -698,12 +693,7 @@ class EmbroiderySimulator(wx.Frame):
stitches_per_second=stitches_per_second)
sizer.Add(self.simulator_panel, 1, wx.EXPAND)
- # self.SetSizerAndFit() sets the minimum size so that the buttons don't
- # get squished. But it then also shrinks the window down to that size.
- self.SetSizerAndFit(sizer)
-
- # Therefore we have to reapply the size that the caller asked for.
- self.SetSize(size)
+ self.SetSizeHints(sizer.CalcMin())
self.Bind(wx.EVT_CLOSE, self.on_close)
diff --git a/lib/i18n.py b/lib/i18n.py
index f57bbf9c..50e71c92 100644
--- a/lib/i18n.py
+++ b/lib/i18n.py
@@ -1,7 +1,7 @@
import gettext
import os
-from os.path import dirname, realpath
import sys
+from os.path import dirname, realpath
from .utils import cache
@@ -32,7 +32,7 @@ def localize(languages=None):
global translation, _
translation = gettext.translation("inkstitch", locale_dir, fallback=True)
- _ = translation.ugettext
+ _ = translation.gettext
@cache
diff --git a/lib/inx/__init__.py b/lib/inx/__init__.py
index 32b8bfae..cc2b039d 100644
--- a/lib/inx/__init__.py
+++ b/lib/inx/__init__.py
@@ -1 +1 @@
-from generate import generate_inx_files
+from .generate import generate_inx_files
diff --git a/lib/inx/about.py b/lib/inx/about.py
new file mode 100755
index 00000000..6db13865
--- /dev/null
+++ b/lib/inx/about.py
@@ -0,0 +1,7 @@
+from .utils import build_environment, write_inx_file
+
+
+def generate_about_inx_file():
+ env = build_environment()
+ template = env.get_template('about.xml')
+ write_inx_file("about", template.render())
diff --git a/lib/inx/extensions.py b/lib/inx/extensions.py
index 030e8aa6..379e98cd 100755
--- a/lib/inx/extensions.py
+++ b/lib/inx/extensions.py
@@ -1,10 +1,11 @@
import pyembroidery
-from .utils import build_environment, write_inx_file
-from .outputs import pyembroidery_output_formats
-from ..extensions import extensions, Input, Output
-from ..commands import LAYER_COMMANDS, OBJECT_COMMANDS, GLOBAL_COMMANDS, COMMANDS
+from ..commands import (COMMANDS, GLOBAL_COMMANDS, LAYER_COMMANDS,
+ OBJECT_COMMANDS)
+from ..extensions import Input, Output, extensions
from ..threads import ThreadCatalog
+from .outputs import pyembroidery_output_formats
+from .utils import build_environment, write_inx_file
def layer_commands():
@@ -41,7 +42,7 @@ def generate_extension_inx_files():
continue
name = extension.name()
- template = env.get_template('%s.inx' % name)
+ template = env.get_template('%s.xml' % name)
write_inx_file(name, template.render(formats=pyembroidery_output_formats(),
debug_formats=pyembroidery_debug_formats(),
threadcatalog=threadcatalog(),
diff --git a/lib/inx/generate.py b/lib/inx/generate.py
index 941596de..8a5b9569 100644
--- a/lib/inx/generate.py
+++ b/lib/inx/generate.py
@@ -1,6 +1,7 @@
+from .info import generate_info_inx_files
+from .extensions import generate_extension_inx_files
from .inputs import generate_input_inx_files
from .outputs import generate_output_inx_files
-from .extensions import generate_extension_inx_files
from .utils import iterate_inx_locales
@@ -9,3 +10,4 @@ def generate_inx_files():
generate_input_inx_files()
generate_output_inx_files()
generate_extension_inx_files()
+ generate_info_inx_files()
diff --git a/lib/inx/info.py b/lib/inx/info.py
new file mode 100755
index 00000000..f391b546
--- /dev/null
+++ b/lib/inx/info.py
@@ -0,0 +1,9 @@
+from .utils import build_environment, write_inx_file
+
+
+def generate_info_inx_files():
+ env = build_environment()
+ info_inx_files = ['about', 'embroider']
+ for info in info_inx_files:
+ template = env.get_template('%s.xml' % info)
+ write_inx_file(info, template.render())
diff --git a/lib/inx/inputs.py b/lib/inx/inputs.py
index d40ffeaf..b50ec9f9 100755
--- a/lib/inx/inputs.py
+++ b/lib/inx/inputs.py
@@ -11,7 +11,7 @@ def pyembroidery_input_formats():
def generate_input_inx_files():
env = build_environment()
- template = env.get_template('input.inx')
+ template = env.get_template('input.xml')
for format, description in pyembroidery_input_formats():
name = "input_%s" % format.upper()
diff --git a/lib/inx/outputs.py b/lib/inx/outputs.py
index aef0c8b5..ccb323c7 100644
--- a/lib/inx/outputs.py
+++ b/lib/inx/outputs.py
@@ -5,14 +5,17 @@ from .utils import build_environment, write_inx_file
def pyembroidery_output_formats():
for format in pyembroidery.supported_formats():
- if 'writer' in format and format['category'] == 'embroidery':
- yield format['extension'], format['description']
+ if 'writer' in format:
+ description = format['description']
+ if format['category'] != "embroidery":
+ description = "%s [DEBUG]" % description
+ yield format['extension'], description, format['mimetype'], format['category']
def generate_output_inx_files():
env = build_environment()
- template = env.get_template('output.inx')
+ template = env.get_template('output.xml')
- for format, description in pyembroidery_output_formats():
+ for format, description, mimetype, category in pyembroidery_output_formats():
name = "output_%s" % format.upper()
- write_inx_file(name, template.render(format=format, description=description))
+ write_inx_file(name, template.render(format=format, mimetype=mimetype, description=description))
diff --git a/lib/inx/utils.py b/lib/inx/utils.py
index a7c98a60..2fb6b21b 100644
--- a/lib/inx/utils.py
+++ b/lib/inx/utils.py
@@ -6,11 +6,13 @@ from os.path import dirname
from jinja2 import Environment, FileSystemLoader
-from ..i18n import N_, locale_dir, translation as default_translation
+from ..i18n import N_, locale_dir
+from ..i18n import translation as default_translation
_top_path = dirname(dirname(dirname(os.path.realpath(__file__))))
inx_path = os.path.join(_top_path, "inx")
template_path = os.path.join(_top_path, "templates")
+version_path = _top_path
current_translation = default_translation
current_locale = "en_US"
@@ -26,16 +28,29 @@ def build_environment():
env.install_gettext_translations(current_translation)
env.globals["locale"] = current_locale
+ with open(os.path.join(version_path, 'LICENSE'), 'r') as license:
+ env.globals["inkstitch_license"] = "".join(license.readlines())
+
if "BUILD" in os.environ:
# building a ZIP release, with inkstitch packaged as a binary
+ # About extension: add version information
+ with open(os.path.join(version_path, 'VERSION'), 'r') as version:
+ env.globals["inkstitch_version"] = "%s %s" % (version.readline(), current_locale)
+ # Command tag and icons path
if sys.platform == "win32":
- env.globals["command_tag"] = '<command reldir="extensions">inkstitch/bin/inkstitch.exe</command>'
+ env.globals["command_tag"] = '<command location="inx">inkstitch/bin/inkstitch.exe</command>'
+ env.globals["image_path"] = 'inkstitch/bin/icons/'
+ elif sys.platform == "darwin":
+ env.globals["command_tag"] = '<command location="inx">inkstitch.app/Contents/MacOS/inkstitch</command>'
+ env.globals["image_path"] = 'inkstitch.app/Contents/MacOS/icons/'
else:
- env.globals["command_tag"] = '<command reldir="extensions">inkstitch/bin/inkstitch</command>'
+ env.globals["command_tag"] = '<command location="inx">inkstitch/bin/inkstitch</command>'
+ env.globals["image_path"] = 'inkstitch/bin/icons/'
else:
# user is running inkstitch.py directly as a developer
- env.globals["command_tag"] = '<command reldir="extensions" interpreter="python">inkstitch.py</command>'
-
+ env.globals["command_tag"] = '<command location="inx" interpreter="python">../../inkstitch.py</command>'
+ env.globals["image_path"] = '../../icons/'
+ env.globals["inkstitch_version"] = "Manual Install"
return env
@@ -49,8 +64,8 @@ def write_inx_file(name, contents):
raise
inx_file_name = "inkstitch_%s.inx" % name
- with open(os.path.join(inx_locale_dir, inx_file_name), 'w') as inx_file:
- print >> inx_file, contents.encode("utf-8")
+ with open(os.path.join(inx_locale_dir, inx_file_name), 'w', encoding="utf-8") as inx_file:
+ print(contents, file=inx_file)
def iterate_inx_locales():
@@ -64,7 +79,7 @@ def iterate_inx_locales():
# generate menu items for this language in Inkscape's "Extensions"
# menu.
magic_string = N_("Generate INX files")
- translated_magic_string = translation.ugettext(magic_string)
+ translated_magic_string = translation.gettext(magic_string)
if translated_magic_string != magic_string or locale == "en_US":
current_translation = translation
diff --git a/lib/lettering/__init__.py b/lib/lettering/__init__.py
index 5d20d683..5a9d345c 100644
--- a/lib/lettering/__init__.py
+++ b/lib/lettering/__init__.py
@@ -1 +1 @@
-from font import Font, FontError \ No newline at end of file
+from .font import Font, FontError
diff --git a/lib/lettering/font.py b/lib/lettering/font.py
index 0974a1cf..3ef99d47 100644
--- a/lib/lettering/font.py
+++ b/lib/lettering/font.py
@@ -1,10 +1,9 @@
-# -*- coding: UTF-8 -*-
-
import json
import os
from copy import deepcopy
-import inkex
+from inkex import styles
+from lxml import etree
from ..elements import nodes_to_elements
from ..exceptions import InkstitchException
@@ -80,14 +79,14 @@ class Font(object):
def _load_metadata(self):
try:
- with open(os.path.join(self.path, "font.json")) as metadata_file:
+ with open(os.path.join(self.path, "font.json"), encoding="utf-8") as metadata_file:
self.metadata = json.load(metadata_file)
except IOError:
pass
def _load_license(self):
try:
- with open(os.path.join(self.path, "LICENSE")) as license_file:
+ with open(os.path.join(self.path, "LICENSE"), encoding="utf-8") as license_file:
self.license = license_file.read()
except IOError:
pass
@@ -101,13 +100,9 @@ class Font(object):
# we'll deal with missing variants when we apply lettering
pass
- def _check_variants(self):
- if self.variants.get(self.default_variant) is None:
- raise FontError("font not found or has no default variant")
-
name = localized_font_metadata('name', '')
description = localized_font_metadata('description', '')
- default_glyph = font_metadata('default_glyph', u"�")
+ default_glyph = font_metadata('defalt_glyph', "�")
leading = font_metadata('leading', 5, multiplier=PIXELS_PER_MM)
kerning_pairs = font_metadata('kerning_pairs', {})
auto_satin = font_metadata('auto_satin', True)
@@ -149,10 +144,13 @@ class Font(object):
return None
def has_variants(self):
+ # returns available variants
font_variants = []
for variant in FontVariant.VARIANT_TYPES:
if os.path.isfile(os.path.join(self.path, "%s.svg" % variant)):
font_variants.append(variant)
+ if not font_variants:
+ raise FontError(_("The font '%s' has no variants.") % self.name)
return font_variants
def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim=False):
@@ -182,12 +180,23 @@ class Font(object):
if self.auto_satin and len(destination_group) > 0:
self._apply_auto_satin(destination_group, trim)
- else:
- # set stroke width because it is almost invisible otherwise (why?)
- for element in destination_group.iterdescendants(SVG_PATH_TAG):
- style = ['stroke-width:1px' if s.startswith('stroke-width') else s for s in element.get('style').split(';')]
- style = ';'.join(style)
- element.set('style', '%s' % style)
+
+ # make sure font stroke styles have always a similar look
+ for element in destination_group.iterdescendants(SVG_PATH_TAG):
+ dash_array = ""
+ stroke_width = ""
+ style = styles.Style(element.get('style'))
+
+ if style.get('fill') == 'none':
+ stroke_width = ";stroke-width:1px"
+ if style.get('stroke-width'):
+ style.pop('stroke-width')
+
+ if style.get('stroke-dasharray') and style.get('stroke-dasharray') != 'none':
+ stroke_width = ";stroke-width:0.5px"
+ dash_array = ";stroke-dasharray:3, 1"
+
+ element.set('style', '%s%s%s' % (style.to_str(), stroke_width, dash_array))
return destination_group
@@ -209,7 +218,8 @@ class Font(object):
Returns:
An svg:g element containing the rendered text.
"""
- group = inkex.etree.Element(SVG_GROUP_TAG, {
+
+ group = etree.Element(SVG_GROUP_TAG, {
INKSCAPE_LABEL: line
})
diff --git a/lib/lettering/font_variant.py b/lib/lettering/font_variant.py
index 7c9fa1c0..2071b2cb 100644
--- a/lib/lettering/font_variant.py
+++ b/lib/lettering/font_variant.py
@@ -1,9 +1,7 @@
-# -*- coding: UTF-8 -*-
-
import os
import inkex
-import simplestyle
+from lxml import etree
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
from .glyph import Glyph
@@ -26,10 +24,10 @@ class FontVariant(object):
# We use unicode characters rather than English strings for font file names
# in order to be more approachable for languages other than English.
- LEFT_TO_RIGHT = u"→"
- RIGHT_TO_LEFT = u"←"
- TOP_TO_BOTTOM = u"↓"
- BOTTOM_TO_TOP = u"↑"
+ LEFT_TO_RIGHT = "→"
+ RIGHT_TO_LEFT = "←"
+ TOP_TO_BOTTOM = "↓"
+ BOTTOM_TO_TOP = "↑"
VARIANT_TYPES = (LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP)
@classmethod
@@ -57,9 +55,9 @@ class FontVariant(object):
self._load_glyphs()
def _load_glyphs(self):
- svg_path = os.path.join(self.path, u"%s.svg" % self.variant)
- with open(svg_path) as svg_file:
- svg = inkex.etree.parse(svg_file)
+ svg_path = os.path.join(self.path, "%s.svg" % self.variant)
+ with open(svg_path, encoding="utf-8") as svg_file:
+ svg = etree.parse(svg_file)
glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS)
for layer in glyph_layers:
@@ -78,9 +76,9 @@ class FontVariant(object):
if style_text:
# The layer may be marked invisible, so we'll clear the 'display'
# style.
- style = simplestyle.parseStyle(group.get('style'))
+ style = dict(inkex.Style.parse_str(group.get('style')))
style.pop('display')
- group.set('style', simplestyle.formatStyle(style))
+ group.set('style', str(inkex.Style(style)))
def __getitem__(self, character):
if character in self.glyphs:
diff --git a/lib/lettering/glyph.py b/lib/lettering/glyph.py
index 061a930c..84517474 100644
--- a/lib/lettering/glyph.py
+++ b/lib/lettering/glyph.py
@@ -1,9 +1,9 @@
from copy import copy
-import cubicsuperpath
-import simpletransform
+from inkex import paths, transforms
-from ..svg import apply_transforms, get_guides
+from ..svg import get_guides
+from ..svg.path import get_correction_transform
from ..svg.tags import SVG_GROUP_TAG, SVG_PATH_TAG
@@ -37,8 +37,9 @@ class Glyph(object):
def _process_group(self, group):
new_group = copy(group)
- new_group.attrib.pop('transform', None)
- del new_group[:] # delete references to the original group's children
+ # new_group.attrib.pop('transform', None)
+ # delete references to the original group's children
+ del new_group[:]
for node in group:
if node.tag == SVG_GROUP_TAG:
@@ -47,11 +48,9 @@ class Glyph(object):
node_copy = copy(node)
if "d" in node.attrib:
- # Convert the path to absolute coordinates, incorporating all
- # nested transforms.
- path = cubicsuperpath.parsePath(node.get("d"))
- apply_transforms(path, node)
- node_copy.set("d", cubicsuperpath.formatPath(path))
+ transform = -transforms.Transform(get_correction_transform(node, True))
+ path = paths.Path(node.get("d")).transform(transform).to_absolute()
+ node_copy.set("d", str(path))
# Delete transforms from paths and groups, since we applied
# them to the paths already.
@@ -71,16 +70,18 @@ class Glyph(object):
self.baseline = 0
def _process_bbox(self):
- left, right, top, bottom = simpletransform.computeBBox(self.node.iterdescendants())
-
+ bbox = [paths.Path(node.get("d")).bounding_box() for node in self.node.iterdescendants(SVG_PATH_TAG)]
+ left, right = min([box.left for box in bbox]), max([box.right for box in bbox])
self.width = right - left
self.min_x = left
def _move_to_origin(self):
translate_x = -self.min_x
translate_y = -self.baseline
- transform = "translate(%s, %s)" % (translate_x, translate_y)
+ transform = transforms.Transform("translate(%s, %s)" % (translate_x, translate_y))
for node in self.node.iter(SVG_PATH_TAG):
- node.set('transform', transform)
- simpletransform.fuseTransform(node)
+ path = paths.Path(node.get("d"))
+ path = path.transform(transform)
+ node.set('d', str(path))
+ node.attrib.pop('transform', None)
diff --git a/lib/lettering/kerning.py b/lib/lettering/kerning.py
new file mode 100644
index 00000000..920e7d59
--- /dev/null
+++ b/lib/lettering/kerning.py
@@ -0,0 +1,69 @@
+from inkex import NSS
+from lxml import etree
+
+
+class FontKerning(object):
+ """
+ This class reads kerning information from an SVG file
+ """
+ def __init__(self, path):
+ with open(path) as svg:
+ self.svg = etree.parse(svg)
+
+ # horiz_adv_x defines the wdith of specific letters (distance to next letter)
+ def horiz_adv_x(self):
+ # In XPath 2.0 we could use ".//svg:glyph/(@unicode|@horiz-adv-x)"
+ xpath = ".//svg:glyph[@unicode and @horiz-adv-x]/@*[name()='unicode' or name()='horiz-adv-x']"
+ hax = self.svg.xpath(xpath, namespaces=NSS)
+ if len(hax) == 0:
+ return {}
+ return dict(zip(hax[0::2], [int(x) for x in hax[1::2]]))
+
+ # kerning (specific distances of two specified letters)
+ def hkern(self):
+ xpath = ".//svg:hkern[(@u1 or @g1) and (@u1 or @g1) and @k]/@*[contains(name(), '1') or contains(name(), '2') or name()='k']"
+ hkern = self.svg.xpath(xpath, namespaces=NSS)
+ for index, glyph in enumerate(hkern):
+ # fontTools.agl will import fontTools.misc.py23 which will output a deprecation warning
+ # ignore the warning for now - until the library fixed it
+ if index == 0:
+ import warnings
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ from fontTools.agl import toUnicode
+ if len(glyph) > 1 and not (index + 1) % 3 == 0:
+ glyph_names = glyph.split(",")
+ # the glyph name is written in various languages, second is english. Let's look it up.
+ if len(glyph_names) == 1:
+ hkern[index] = toUnicode(glyph)
+ else:
+ hkern[index] = toUnicode(glyph_names[1])
+ k = [int(x) for x in hkern[2::3]]
+ u = [k + v for k, v in zip(hkern[0::3], hkern[1::3])]
+ hkern = dict(zip(u, k))
+ return hkern
+
+ # the space character
+ def word_spacing(self):
+ xpath = "string(.//svg:glyph[@glyph-name='space'][1]/@*[name()='horiz-adv-x'])"
+ word_spacing = self.svg.xpath(xpath, namespaces=NSS) or 26
+ return int(word_spacing)
+
+ # default letter spacing
+ def letter_spacing(self):
+ xpath = "string(.//svg:font[@horiz-adv-x][1]/@*[name()='horiz-adv-x'])"
+ letter_spacing = self.svg.xpath(xpath, namespaces=NSS) or 0
+ return int(letter_spacing)
+
+ # this value will be saved into the json file to preserve it for later font edits
+ # additionally it serves to automatically define the line height (leading)
+ def units_per_em(self, default=100):
+ xpath = "string(.//svg:font-face[@units-per-em][1]/@*[name()='units-per-em'])"
+ units_per_em = self.svg.xpath(xpath, namespaces=NSS) or default
+ return int(units_per_em)
+
+ """
+ def missing_glyph_spacing(self):
+ xpath = "string(.//svg:missing-glyph/@*[name()='horiz-adv-x'])"
+ return float(self.svg.xpath(xpath, namespaces=NSS))
+ """
diff --git a/lib/output.py b/lib/output.py
index fbcdea6c..60579801 100644
--- a/lib/output.py
+++ b/lib/output.py
@@ -1,4 +1,5 @@
import sys
+import inkex
import pyembroidery
@@ -27,7 +28,8 @@ def _string_to_floats(string):
return [float(num) for num in floats]
-def get_origin(svg, (minx, miny, maxx, maxy)):
+def get_origin(svg, xxx_todo_changeme):
+ (minx, miny, maxx, maxy) = xxx_todo_changeme
origin_command = global_command(svg, "origin")
if origin_command:
@@ -91,5 +93,6 @@ def write_embroidery_file(file_path, stitch_plan, svg, settings={}):
except IOError as e:
# L10N low-level file error. %(error)s is (hopefully?) translated by
# the user's system automatically.
- print >> sys.stderr, _("Error writing to %(path)s: %(error)s") % dict(path=file_path, error=e.strerror)
+ msg = _("Error writing to %(path)s: %(error)s") % dict(path=file_path, error=e.strerror)
+ inkex.errormsg(msg)
sys.exit(1)
diff --git a/lib/stitch_plan/stitch_plan.py b/lib/stitch_plan/stitch_plan.py
index 0b2f75cd..de66cb10 100644
--- a/lib/stitch_plan/stitch_plan.py
+++ b/lib/stitch_plan/stitch_plan.py
@@ -5,7 +5,8 @@ from .stitch import Stitch
from .ties import add_ties
-def patches_to_stitch_plan(patches, collapse_len=3.0 * PIXELS_PER_MM, disable_ties=False):
+def patches_to_stitch_plan(patches, collapse_len=None, disable_ties=False):
+
"""Convert a collection of inkstitch.element.Patch objects to a StitchPlan.
* applies instructions embedded in the Patch such as trim_after and stop_after
@@ -13,6 +14,7 @@ def patches_to_stitch_plan(patches, collapse_len=3.0 * PIXELS_PER_MM, disable_ti
* adds jump-stitches between patches if necessary
"""
+ collapse_len = (collapse_len or 3.0) * PIXELS_PER_MM
stitch_plan = StitchPlan()
color_block = stitch_plan.new_color_block(color=patches[0].color)
diff --git a/lib/stitches/__init__.py b/lib/stitches/__init__.py
index e052d144..12c636a6 100644
--- a/lib/stitches/__init__.py
+++ b/lib/stitches/__init__.py
@@ -1,6 +1,6 @@
-from running_stitch import *
-from auto_fill import auto_fill
-from fill import legacy_fill
+from .auto_fill import auto_fill
+from .fill import legacy_fill
+from .running_stitch import *
# Can't put this here because we get a circular import :(
#from auto_satin import auto_satin
diff --git a/lib/stitches/auto_fill.py b/lib/stitches/auto_fill.py
index b5157a5a..485f51e5 100644
--- a/lib/stitches/auto_fill.py
+++ b/lib/stitches/auto_fill.py
@@ -8,11 +8,12 @@ from shapely import geometry as shgeo
from shapely.ops import snap
from shapely.strtree import STRtree
-from .fill import intersect_region_with_grating, stitch_row
-from .running_stitch import running_stitch
from ..debug import debug
from ..svg import PIXELS_PER_MM
-from ..utils.geometry import Point as InkstitchPoint, line_string_to_point_list
+from ..utils.geometry import Point as InkstitchPoint
+from ..utils.geometry import line_string_to_point_list
+from .fill import intersect_region_with_grating, stitch_row
+from .running_stitch import running_stitch
class PathEdge(object):
@@ -82,7 +83,7 @@ def which_outline(shape, coords):
point = shgeo.Point(*coords)
outlines = list(shape.boundary)
- outline_indices = range(len(outlines))
+ outline_indices = list(range(len(outlines)))
closest = min(outline_indices, key=lambda index: outlines[index].distance(point))
return closest
@@ -175,7 +176,7 @@ def insert_node(graph, shape, point):
if key == "outline":
edges.append(((start, end), data))
- edge, data = min(edges, key=lambda (edge, data): shgeo.LineString(edge).distance(projected_point))
+ edge, data = min(edges, key=lambda edge_data: shgeo.LineString(edge_data[0]).distance(projected_point))
graph.remove_edge(*edge, key="outline")
graph.add_edge(edge[0], node, key="outline", **data)
@@ -204,7 +205,7 @@ def add_boundary_travel_nodes(graph, shape):
if length > 1:
# Just plot a point every pixel, that should be plenty of
# resolution. A pixel is around a quarter of a millimeter.
- for i in xrange(1, int(length)):
+ for i in range(1, int(length)):
subpoint = segment.interpolate(i)
graph.add_node((subpoint.x, subpoint.y), projection=outline.project(subpoint), outline=outline_index)
@@ -480,7 +481,7 @@ def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None
graph = graph.copy()
if not starting_point:
- starting_point = graph.nodes.keys()[0]
+ starting_point = list(graph.nodes.keys())[0]
starting_node = nearest_node(graph, starting_point)
diff --git a/lib/stitches/auto_satin.py b/lib/stitches/auto_satin.py
index 112c714b..5ce91a49 100644
--- a/lib/stitches/auto_satin.py
+++ b/lib/stitches/auto_satin.py
@@ -1,14 +1,12 @@
import math
-from itertools import chain, izip
+from itertools import chain
+import inkex
import networkx as nx
+from lxml import etree
from shapely import geometry as shgeo
from shapely.geometry import Point as ShapelyPoint
-import cubicsuperpath
-import inkex
-import simplestyle
-
from ..commands import add_commands
from ..elements import SatinColumn, Stroke
from ..i18n import _
@@ -87,7 +85,7 @@ class SatinSegment(object):
num_segments = int(math.ceil(self.center_line.length / segment_size))
segments = []
- for i in xrange(num_segments):
+ for i in range(num_segments):
segments.append(SatinSegment(self.satin,
float(i) / num_segments,
float(i + 1) / num_segments,
@@ -216,13 +214,13 @@ class RunningStitch(object):
original_element.node.get(INKSTITCH_ATTRIBS['contour_underlay_stitch_length_mm'], '')
def to_element(self):
- node = inkex.etree.Element(SVG_PATH_TAG)
- node.set("d", cubicsuperpath.formatPath(
- line_strings_to_csp([self.path])))
+ node = etree.Element(SVG_PATH_TAG)
+ d = str(inkex.paths.CubicSuperPath(line_strings_to_csp([self.path])))
+ node.set("d", d)
style = self.original_element.parse_style()
style['stroke-dasharray'] = "0.5,0.5"
- style = simplestyle.formatStyle(style)
+ style = str(inkex.Style(style))
node.set("style", style)
node.set(INKSTITCH_ATTRIBS['running_stitch_length_mm'], self.running_stitch_length)
@@ -445,8 +443,9 @@ def add_jumps(graph, elements, preserve_order):
point2 = graph.nodes[node2]['point']
potential_edges.append((point1, point2))
- edge = min(potential_edges, key=lambda (p1, p2): p1.distance(p2))
- graph.add_edge(str(edge[0]), str(edge[1]), jump=True)
+ if potential_edges:
+ edge = min(potential_edges, key=lambda p1_p2: p1_p2[0].distance(p1_p2[1]))
+ graph.add_edge(str(edge[0]), str(edge[1]), jump=True)
else:
# networkx makes this super-easy! k_edge_agumentation tells us what edges
# we need to add to ensure that the graph is fully connected. We give it a
@@ -497,7 +496,7 @@ def find_path(graph, starting_node, ending_node):
# forth on each satin twice due to the satin edges being in the graph
# twice (forward and reverse).
graph = nx.Graph(graph)
- graph.remove_edges_from(zip(path[:-1], path[1:]))
+ graph.remove_edges_from(list(zip(path[:-1], path[1:])))
final_path = []
prev = None
@@ -643,14 +642,14 @@ def preserve_original_groups(elements, original_parent_nodes):
to the group that contained the original SatinColumn that spawned it.
"""
- for element, parent in izip(elements, original_parent_nodes):
+ for element, parent in zip(elements, original_parent_nodes):
if parent is not None:
parent.append(element.node)
element.node.set('transform', get_correction_transform(parent, child=True))
def create_new_group(parent, insert_index):
- group = inkex.etree.Element(SVG_GROUP_TAG, {
+ group = etree.Element(SVG_GROUP_TAG, {
INKSCAPE_LABEL: _("Auto-Satin"),
"transform": get_correction_transform(parent, child=True)
})
diff --git a/lib/stitches/fill.py b/lib/stitches/fill.py
index 924f64f6..a19e080b 100644
--- a/lib/stitches/fill.py
+++ b/lib/stitches/fill.py
@@ -3,7 +3,8 @@ import math
import shapely
from ..svg import PIXELS_PER_MM
-from ..utils import cache, Point as InkstitchPoint
+from ..utils import Point as InkstitchPoint
+from ..utils import cache
def legacy_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, flip, staggers, skip_last):
@@ -223,7 +224,7 @@ def pull_runs(rows, shape, row_spacing):
run = []
prev = None
- for row_num in xrange(len(rows)):
+ for row_num in range(len(rows)):
row = rows[row_num]
first, rest = row[0], row[1:]
diff --git a/lib/stitches/running_stitch.py b/lib/stitches/running_stitch.py
index 0bb8fc7d..57a03865 100644
--- a/lib/stitches/running_stitch.py
+++ b/lib/stitches/running_stitch.py
@@ -92,7 +92,7 @@ def bean_stitch(stitches, repeats):
for stitch in stitches:
new_stitches.append(stitch)
- for i in xrange(repeats):
+ for i in range(repeats):
new_stitches.extend(copy(new_stitches[-2:]))
return new_stitches
diff --git a/lib/svg/guides.py b/lib/svg/guides.py
index 3e26a90d..255b3e6a 100644
--- a/lib/svg/guides.py
+++ b/lib/svg/guides.py
@@ -1,7 +1,7 @@
-import simpletransform
+from inkex import transforms
-from ..utils import string_to_floats, Point, cache
-from .tags import SODIPODI_NAMEDVIEW, SODIPODI_GUIDE, INKSCAPE_LABEL
+from ..utils import Point, cache, string_to_floats
+from .tags import INKSCAPE_LABEL, SODIPODI_GUIDE, SODIPODI_NAMEDVIEW
from .units import get_doc_size, get_viewbox_transform
@@ -19,7 +19,7 @@ class InkscapeGuide(object):
# convert the size from viewbox-relative to real-world pixels
viewbox_transform = get_viewbox_transform(self.svg)
- simpletransform.applyTransformToPoint(simpletransform.invertTransform(viewbox_transform), doc_size)
+ viewbox_transform = transforms.Transform(-transforms.Transform(viewbox_transform)).apply_to_point(doc_size)
self.position = Point(*string_to_floats(self.node.get('position')))
diff --git a/lib/svg/path.py b/lib/svg/path.py
index cc4b8cbb..baa93443 100644
--- a/lib/svg/path.py
+++ b/lib/svg/path.py
@@ -1,8 +1,7 @@
-import cubicsuperpath
import inkex
-import simpletransform
-from tags import SVG_GROUP_TAG, SVG_LINK_TAG
+from lxml import etree
+from .tags import SVG_GROUP_TAG, SVG_LINK_TAG
from .units import get_viewbox_transform
@@ -10,7 +9,7 @@ def apply_transforms(path, node):
transform = get_node_transform(node)
# apply the combined transform to this node's path
- simpletransform.applyTransformToPath(transform, path)
+ path = path.transform(transform)
return path
@@ -21,7 +20,7 @@ def compose_parent_transforms(node, mat):
trans = node.get('transform')
if trans:
- mat = simpletransform.composeTransform(simpletransform.parseTransform(trans), mat)
+ mat = inkex.transforms.Transform(trans) * mat
if node.getparent() is not None:
if node.getparent().tag in [SVG_GROUP_TAG, SVG_LINK_TAG]:
mat = compose_parent_transforms(node.getparent(), mat)
@@ -29,20 +28,22 @@ def compose_parent_transforms(node, mat):
def get_node_transform(node):
+ """
+ if getattr(node, "composed_transform", None):
+ return node.composed_transform()
+ """
+
# start with the identity transform
- transform = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
+ transform = inkex.transforms.Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
# this if is because sometimes inkscape likes to create paths outside of a layer?!
if node.getparent() is not None:
# combine this node's transform with all parent groups' transforms
transform = compose_parent_transforms(node, transform)
- if node.get('id', '').startswith('clone_'):
- transform = simpletransform.parseTransform(node.get('transform', ''))
-
# add in the transform implied by the viewBox
viewbox_transform = get_viewbox_transform(node.getroottree().getroot())
- transform = simpletransform.composeTransform(viewbox_transform, transform)
+ transform = viewbox_transform * transform
return transform
@@ -63,9 +64,9 @@ def get_correction_transform(node, child=False):
# now invert it, so that we can position our objects in absolute
# coordinates
- transform = simpletransform.invertTransform(transform)
+ transform = -transform
- return simpletransform.formatTransform(transform)
+ return str(transform)
def line_strings_to_csp(line_strings):
@@ -90,6 +91,6 @@ def point_lists_to_csp(point_lists):
def line_strings_to_path(line_strings):
csp = line_strings_to_csp(line_strings)
- return inkex.etree.Element("path", {
- "d": cubicsuperpath.formatPath(csp)
+ return etree.Element("path", {
+ "d": str(inkex.paths.CubicSuperPath(csp))
})
diff --git a/lib/svg/rendering.py b/lib/svg/rendering.py
index 5860ceef..ced7d4f1 100644
--- a/lib/svg/rendering.py
+++ b/lib/svg/rendering.py
@@ -1,9 +1,8 @@
import math
import inkex
-import simplepath
-import simplestyle
-import simpletransform
+from lxml import etree
+from math import pi
from ..i18n import _
from ..utils import Point, cache
@@ -116,24 +115,25 @@ def realistic_stitch(start, end):
start = Point(*start)
stitch_length = (end - start).length()
- stitch_center = (end + start) / 2.0
+ stitch_center = Point((end.x+start.x)/2.0, (end[1]+start[1])/2.0)
stitch_direction = (end - start)
- stitch_angle = math.atan2(stitch_direction.y, stitch_direction.x)
+ stitch_angle = math.atan2(stitch_direction.y, stitch_direction.x) * (180 / pi)
stitch_length = max(0, stitch_length - 0.2 * PIXELS_PER_MM)
# create the path by filling in the length in the template
- path = simplepath.parsePath(stitch_path % stitch_length)
+ path = inkex.Path(stitch_path % stitch_length).to_arrays()
# rotate the path to match the stitch
rotation_center_x = -stitch_length / 2.0
rotation_center_y = stitch_height / 2.0
- simplepath.rotatePath(path, stitch_angle, cx=rotation_center_x, cy=rotation_center_y)
+
+ path = inkex.Path(path).rotate(stitch_angle, (rotation_center_x, rotation_center_y))
# move the path to the location of the stitch
- simplepath.translatePath(path, stitch_center.x - rotation_center_x, stitch_center.y - rotation_center_y)
+ path = inkex.Path(path).translate(stitch_center.x - rotation_center_x, stitch_center.y - rotation_center_y)
- return simplepath.formatPath(path)
+ return str(path)
def color_block_to_point_lists(color_block):
@@ -159,10 +159,9 @@ def get_correction_transform(svg):
transform = get_viewbox_transform(svg)
# we need to correct for the viewbox
- transform = simpletransform.invertTransform(transform)
- transform = simpletransform.formatTransform(transform)
+ transform = -inkex.transforms.Transform(transform)
- return transform
+ return str(transform)
def color_block_to_realistic_stitches(color_block, svg, destination):
@@ -170,12 +169,8 @@ def color_block_to_realistic_stitches(color_block, svg, destination):
color = color_block.color.visible_on_white.darker.to_hex_str()
start = point_list[0]
for point in point_list[1:]:
- destination.append(inkex.etree.Element(SVG_PATH_TAG, {
- 'style': simplestyle.formatStyle({
- 'fill': color,
- 'stroke': 'none',
- 'filter': 'url(#realistic-stitch-filter)'
- }),
+ destination.append(etree.Element(SVG_PATH_TAG, {
+ 'style': "fill: %s; stroke: none; filter: url(#realistic-stitch-filter);" % color,
'd': realistic_stitch(start, point),
'transform': get_correction_transform(svg)
}))
@@ -200,12 +195,8 @@ def color_block_to_paths(color_block, svg, destination, visual_commands):
color = color_block.color.visible_on_white.to_hex_str()
- path = inkex.etree.Element(SVG_PATH_TAG, {
- 'style': simplestyle.formatStyle({
- 'stroke': color,
- 'stroke-width': "0.4",
- 'fill': 'none'
- }),
+ path = etree.Element(SVG_PATH_TAG, {
+ 'style': "stroke: %s; stroke-width: 0.4; fill: none;" % color,
'd': "M" + " ".join(" ".join(str(coord) for coord in point) for point in point_list),
'transform': get_correction_transform(svg),
INKSTITCH_ATTRIBS['manual_stitch']: 'true'
@@ -223,10 +214,10 @@ def color_block_to_paths(color_block, svg, destination, visual_commands):
def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
if layer is None:
- layer = inkex.etree.Element(SVG_GROUP_TAG,
- {'id': '__inkstitch_stitch_plan__',
- INKSCAPE_LABEL: _('Stitch Plan'),
- INKSCAPE_GROUPMODE: 'layer'})
+ layer = etree.Element(SVG_GROUP_TAG,
+ {'id': '__inkstitch_stitch_plan__',
+ INKSCAPE_LABEL: _('Stitch Plan'),
+ INKSCAPE_GROUPMODE: 'layer'})
else:
# delete old stitch plan
del layer[:]
@@ -237,10 +228,10 @@ def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
svg.append(layer)
for i, color_block in enumerate(stitch_plan):
- group = inkex.etree.SubElement(layer,
- SVG_GROUP_TAG,
- {'id': '__color_block_%d__' % i,
- INKSCAPE_LABEL: "color block %d" % (i + 1)})
+ group = etree.SubElement(layer,
+ SVG_GROUP_TAG,
+ {'id': '__color_block_%d__' % i,
+ INKSCAPE_LABEL: "color block %d" % (i + 1)})
if realistic:
color_block_to_realistic_stitches(color_block, svg, group)
else:
@@ -250,6 +241,6 @@ def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
defs = svg.find(SVG_DEFS_TAG)
if defs is None:
- defs = inkex.etree.SubElement(svg, SVG_DEFS_TAG)
+ defs = etree.SubElement(svg, SVG_DEFS_TAG)
- defs.append(inkex.etree.fromstring(realistic_filter))
+ defs.append(etree.fromstring(realistic_filter))
diff --git a/lib/svg/svg.py b/lib/svg/svg.py
index 3cf7f017..1a6b10e8 100644
--- a/lib/svg/svg.py
+++ b/lib/svg/svg.py
@@ -1,4 +1,5 @@
-from inkex import NSS, etree
+from inkex import NSS
+from lxml import etree
from ..utils import cache
diff --git a/lib/svg/tags.py b/lib/svg/tags.py
index 810924a6..6fa47aa0 100644
--- a/lib/svg/tags.py
+++ b/lib/svg/tags.py
@@ -1,10 +1,9 @@
import inkex
+from lxml import etree
-
-# This is used below and added to the document in ../extensions/base.py.
+etree.register_namespace("inkstitch", "http://inkstitch.org/namespace")
inkex.NSS['inkstitch'] = 'http://inkstitch.org/namespace'
-
SVG_PATH_TAG = inkex.addNS('path', 'svg')
SVG_POLYLINE_TAG = inkex.addNS('polyline', 'svg')
SVG_RECT_TAG = inkex.addNS('rect', 'svg')
diff --git a/lib/svg/units.py b/lib/svg/units.py
index 319e018b..6f16d7fb 100644
--- a/lib/svg/units.py
+++ b/lib/svg/units.py
@@ -1,4 +1,4 @@
-import simpletransform
+import inkex
from ..i18n import _
from ..utils import cache
@@ -6,10 +6,14 @@ from ..utils import cache
# modern versions of Inkscape use 96 pixels per inch as per the CSS standard
PIXELS_PER_MM = 96 / 25.4
-# cribbed from inkscape-silhouette
-
def parse_length_with_units(str):
+ value, unit = inkex.units.parse_unit(str)
+ if not unit:
+ raise ValueError(_("parseLengthWithUnits: unknown unit %s") % str)
+ return value, unit
+
+ """
'''
Parse an SVG value which may or may not have units attached
This version is greatly simplified in that it only allows: no units,
@@ -18,6 +22,8 @@ def parse_length_with_units(str):
generality is ever needed.
'''
+ # cribbed from inkscape-silhouette
+
u = 'px'
s = str.strip()
if s[-2:] == 'px':
@@ -46,11 +52,15 @@ def parse_length_with_units(str):
raise ValueError(_("parseLengthWithUnits: unknown unit %s") % s)
return v, u
+ """
def convert_length(length):
value, units = parse_length_with_units(length)
+ return inkex.units.convert_unit(str(value) + units, 'px')
+
+ """
if not units or units == "px":
return value
@@ -67,7 +77,7 @@ def convert_length(length):
units = 'mm'
if units == 'mm':
- value = value / 25.4
+ value /= 25.4
units = 'in'
if units == 'in':
@@ -76,6 +86,7 @@ def convert_length(length):
return value * 96
raise ValueError(_("Unknown unit: %s") % units)
+ """
@cache
@@ -121,7 +132,7 @@ def get_viewbox_transform(node):
dx = -float(viewbox[0])
dy = -float(viewbox[1])
- transform = simpletransform.parseTransform("translate(%f, %f)" % (dx, dy))
+ transform = inkex.transforms.Transform("translate(%f, %f)" % (dx, dy))
try:
sx = doc_width / float(viewbox[2])
@@ -132,8 +143,8 @@ def get_viewbox_transform(node):
if aspect_ratio != 'none':
sx = sy = max(sx, sy) if 'slice' in aspect_ratio else min(sx, sy)
- scale_transform = simpletransform.parseTransform("scale(%f, %f)" % (sx, sy))
- transform = simpletransform.composeTransform(transform, scale_transform)
+ scale_transform = inkex.transforms.Transform("scale(%f, %f)" % (sx, sy))
+ transform = transform * scale_transform
except ZeroDivisionError:
pass
diff --git a/lib/threads/__init__.py b/lib/threads/__init__.py
index 03cd777b..1081ea14 100644
--- a/lib/threads/__init__.py
+++ b/lib/threads/__init__.py
@@ -1,3 +1,3 @@
-from color import ThreadColor
-from palette import ThreadPalette
-from catalog import ThreadCatalog
+from .catalog import ThreadCatalog
+from .color import ThreadColor
+from .palette import ThreadPalette
diff --git a/lib/threads/catalog.py b/lib/threads/catalog.py
index aba2696d..c91ce227 100644
--- a/lib/threads/catalog.py
+++ b/lib/threads/catalog.py
@@ -1,6 +1,6 @@
import os
import sys
-from collections import Sequence
+from collections.abc import Sequence
from glob import glob
from os.path import dirname, realpath
diff --git a/lib/threads/color.py b/lib/threads/color.py
index e24856a9..32016112 100644
--- a/lib/threads/color.py
+++ b/lib/threads/color.py
@@ -2,8 +2,7 @@ import colorsys
import re
import tinycss2.color3
-
-import simplestyle
+from inkex import Color
from pyembroidery.EmbThread import EmbThread
@@ -19,14 +18,14 @@ class ThreadColor(object):
self.manufacturer = color.brand
self.rgb = (color.get_red(), color.get_green(), color.get_blue())
return
- elif isinstance(color, unicode):
+ elif isinstance(color, str):
self.rgb = tinycss2.color3.parse_color(color)
# remove alpha channel and multiply with 255
self.rgb = tuple(channel * 255.0 for channel in list(self.rgb)[:-1])
elif isinstance(color, (list, tuple)):
self.rgb = tuple(color)
elif self.hex_str_re.match(color):
- self.rgb = simplestyle.parseColor(color)
+ self.rgb = Color.parse_str(color)[1]
else:
raise ValueError("Invalid color: " + repr(color))
@@ -77,7 +76,7 @@ class ThreadColor(object):
@property
def hex_digits(self):
- return "%02X%02X%02X" % self.rgb
+ return "%02X%02X%02X" % tuple([int(x) for x in self.rgb])
@property
def rgb_normalized(self):
diff --git a/lib/threads/palette.py b/lib/threads/palette.py
index d685e5bb..c5e3002c 100644
--- a/lib/threads/palette.py
+++ b/lib/threads/palette.py
@@ -1,7 +1,8 @@
-from collections import Set
-from colormath.color_objects import sRGBColor, LabColor
+from collections.abc import Set
+
from colormath.color_conversions import convert_color
from colormath.color_diff import delta_e_cie1994
+from colormath.color_objects import LabColor, sRGBColor
from .color import ThreadColor
diff --git a/lib/utils/dotdict.py b/lib/utils/dotdict.py
index 76f23697..e946ecd4 100644
--- a/lib/utils/dotdict.py
+++ b/lib/utils/dotdict.py
@@ -13,7 +13,7 @@ class DotDict(dict):
self.dotdictify()
def _dotdictify(self):
- for k, v in self.iteritems():
+ for k, v in self.items():
if isinstance(v, dict):
self[k] = DotDict(v)
diff --git a/lib/utils/geometry.py b/lib/utils/geometry.py
index bae32c32..f7b49407 100644
--- a/lib/utils/geometry.py
+++ b/lib/utils/geometry.py
@@ -1,6 +1,7 @@
import math
-from shapely.geometry import LineString, Point as ShapelyPoint
+from shapely.geometry import LineString
+from shapely.geometry import Point as ShapelyPoint
def cut(line, distance, normalized=False):
@@ -123,9 +124,6 @@ class Point:
def as_tuple(self):
return (self.x, self.y)
- def __cmp__(self, other):
- return cmp(self.as_tuple(), other.as_tuple())
-
def __getitem__(self, item):
return self.as_tuple()[item]
diff --git a/lib/utils/inkscape.py b/lib/utils/inkscape.py
index a650da69..f89ea447 100644
--- a/lib/utils/inkscape.py
+++ b/lib/utils/inkscape.py
@@ -1,10 +1,10 @@
-from os.path import realpath, expanduser, join as path_join
import sys
+from os.path import expanduser, realpath
def guess_inkscape_config_path():
if getattr(sys, 'frozen', None):
- path = realpath(path_join(sys._MEIPASS, "..", "..", ".."))
+ path = realpath(sys._MEIPASS.split('extensions', 1)[0])
if sys.platform == "win32":
import win32api
diff --git a/lib/utils/io.py b/lib/utils/io.py
index f51f629c..239585f4 100644
--- a/lib/utils/io.py
+++ b/lib/utils/io.py
@@ -1,15 +1,15 @@
import os
import sys
-from cStringIO import StringIO
+from io import StringIO
def save_stderr():
# GTK likes to spam stderr, which inkscape will show in a dialog.
- null = open(os.devnull, 'w')
- sys.stderr_dup = os.dup(sys.stderr.fileno())
- sys.real_stderr = os.fdopen(sys.stderr_dup, 'w')
- os.dup2(null.fileno(), 2)
- sys.stderr = StringIO()
+ with open(os.devnull, 'w') as null:
+ sys.stderr_dup = os.dup(sys.stderr.fileno())
+ sys.real_stderr = os.fdopen(sys.stderr_dup, 'w', encoding='utf-8')
+ os.dup2(null.fileno(), 2)
+ sys.stderr = StringIO()
def restore_stderr():
@@ -22,11 +22,11 @@ def restore_stderr():
def save_stdout():
- null = open(os.devnull, 'w')
- sys.stdout_dup = os.dup(sys.stdout.fileno())
- sys.real_stdout = os.fdopen(sys.stdout_dup, 'w')
- os.dup2(null.fileno(), 1)
- sys.stdout = StringIO()
+ with open(os.devnull, 'w') as null:
+ sys.stdout_dup = os.dup(sys.stdout.fileno())
+ sys.real_stdout = os.fdopen(sys.stdout_dup, 'w')
+ os.dup2(null.fileno(), 1)
+ sys.stdout = StringIO()
def restore_stdout():
diff --git a/lib/utils/version.py b/lib/utils/version.py
new file mode 100644
index 00000000..02eb388b
--- /dev/null
+++ b/lib/utils/version.py
@@ -0,0 +1,17 @@
+import sys
+from os.path import isfile, join, realpath
+
+from ..i18n import _
+
+
+def get_inkstitch_version():
+ if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
+ version = realpath(join(sys._MEIPASS, "..", "VERSION"))
+ else:
+ version = realpath(join(realpath(__file__), "..", "..", "..", 'VERSION'))
+ if isfile(version):
+ with open(version, 'r') as v:
+ inkstitch_version = _("Ink/Stitch Version: %s") % v.readline()
+ else:
+ inkstitch_version = _("Ink/Stitch Version: unkown")
+ return inkstitch_version
diff --git a/print/resources/inkstitch.js b/print/resources/inkstitch.js
index a5d57e26..8bdc4ec0 100644
--- a/print/resources/inkstitch.js
+++ b/print/resources/inkstitch.js
@@ -150,7 +150,7 @@ function writeEstimatedTime( selector, estimatedTime ) {
function scaleSVG(element, scale = 'fit') {
// always center svg
- transform = "translate(-50%, -50%)";
+ var transform = "translate(-50%, -50%)";
if(scale == 'fit') {
var scale = Math.min(
@@ -278,7 +278,7 @@ $(function() {
})
});
- /* Contendeditable Fields */
+ /* Contenteditable Fields */
$('body').on('focusout', '[contenteditable="true"]:not(.info-text)', function() {
/* change svg scale */
diff --git a/print/resources/style.css b/print/resources/style.css
index e1bb857a..79e34db8 100644
--- a/print/resources/style.css
+++ b/print/resources/style.css
@@ -384,7 +384,7 @@ body {
z-index: 4;
position: fixed;
width: 50%;
- height: 25%%;
+ height: 25%;
top: 200px;
left: 25%;
background: rgb(255, 255, 255);
@@ -906,7 +906,6 @@ body {
background: #fff;
margin-left: auto;
margin-right: auto;
- white-space: wrap;
text-align: center;
padding-top: 2mm;
position: relative;
diff --git a/print/templates/operator_detailedview.html b/print/templates/operator_detailedview.html
index c74a7a72..b16691a0 100644
--- a/print/templates/operator_detailedview.html
+++ b/print/templates/operator_detailedview.html
@@ -28,7 +28,7 @@
<span>{{ _('Design box size') }}: {{ "%0.1fmm X %0.1fmm" | format(*job.dimensions) }}</span>
<!-- <span>{{ _('Total thread used') }}: {{job.estimated_thread }}</span> -->
<span class="total-num-stitches">{{ _('Total stitch count') }}: {{job.num_stitches }}</span>
- <span class="time-opd">{{ ('Estimated time') }}: <span class="total-estimated-time"></span></span>
+ <span class="time-opd">{{ _('Estimated time') }}: <span class="total-estimated-time"></span></span>
</p>
<p>
<span class="total-stops">{{ _('Total stops') }}: {{ job.num_stops }}</span>
diff --git a/pyembroidery b/pyembroidery
-Subproject c0b817d0626140221e707e88be571728c393a3c
+Subproject d23ead18908706a5db2365441d4bef6884e99ba
diff --git a/requirements.txt b/requirements.txt
index 3bf490a6..84ba47d0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,19 +1,19 @@
./pyembroidery
+inkex
backports.functools_lru_cache
-wxPython==4.0.6
-networkx==2.2
-shapely==1.6.3
+wxPython
+networkx
+shapely
lxml
appdirs
-numpy<1.16.0
+numpy<=1.17.4
jinja2>2.9
requests
colormath
stringcase
tinycss2
-
-# We're not ready for flask 1.0 yet. Logging changed, among othe things.
-flask==0.*
+flask
+fonttools
pywinutils; sys.platform == 'win32'
pywin32; sys.platform == 'win32'
diff --git a/templates/about.xml b/templates/about.xml
new file mode 100644
index 00000000..68609d95
--- /dev/null
+++ b/templates/about.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <name>{% trans %}About{% endtrans %}</name>
+ <id>org.inkstitch.about.{{ locale }}</id>
+ <param type="notebook" name="about-tabs" indent="1">
+ <page name="about" gui-text="{% trans %}About{% endtrans %}">
+ <image>{{ image_path }}inkstitch_colour_logo.svg</image>
+ <label indent="1" appearance="header">Ink/Stitch - {{ inkstitch_version }}</label>
+ <separator/>
+ <label indent="1">{% trans %}An open-source machine embroidery design platform based on Inkscape.{% endtrans %}</label>
+ <separator/>
+ <spacer/>
+ <label indent="1" appearance="url">https://inkstitch.org</label>
+ </page>
+ <page name="license" gui-text="{%trans %}License{% endtrans %}">
+ <param name="license-text" type="description" xml:space="preserve">
+{{ inkstitch_license }}
+ </param>
+ </page>
+ </param>
+ <effect needs-live-preview="false" needs-document="false">
+ <object-type>all</object-type>
+ <effects-menu>
+ <submenu name="Ink/Stitch">
+ </submenu>
+ </effects-menu>
+ </effect>
+</inkscape-extension>
diff --git a/templates/auto_satin.inx b/templates/auto_satin.xml
index 673ac51e..673ac51e 100644
--- a/templates/auto_satin.inx
+++ b/templates/auto_satin.xml
diff --git a/templates/break_apart.inx b/templates/break_apart.xml
index 83333ad1..83333ad1 100644
--- a/templates/break_apart.inx
+++ b/templates/break_apart.xml
diff --git a/templates/cleanup.inx b/templates/cleanup.xml
index 4cf11ce1..4cf11ce1 100644
--- a/templates/cleanup.inx
+++ b/templates/cleanup.xml
diff --git a/templates/convert_to_satin.inx b/templates/convert_to_satin.xml
index 80f0b678..80f0b678 100644
--- a/templates/convert_to_satin.inx
+++ b/templates/convert_to_satin.xml
diff --git a/templates/cut_satin.inx b/templates/cut_satin.xml
index b780543a..b780543a 100644
--- a/templates/cut_satin.inx
+++ b/templates/cut_satin.xml
diff --git a/templates/embroider.inx b/templates/embroider.inx
deleted file mode 100644
index b79c2f5f..00000000
--- a/templates/embroider.inx
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
- <name>{% trans %}Embroider{% endtrans %}</name>
- <id>org.inkstitch.embroider.{{ locale }}</id>
- <param name="collapse_len_mm" type="float" min="0.0" max="10.0" _gui-text="{% trans %}Collapse length (mm){% endtrans %}" _gui-description="{% trans %}Jump stitches smaller than this will be treated as normal stitches.{% endtrans %}">3.0</param>
- <param name="hide_layers" type="boolean" _gui-text="{% trans %}Hide other layers{% endtrans %}" _gui-description="{% trans %}Hide all other top-level layers when the embroidery layer is generated, in order to make stitching discernible.{% endtrans %}">true</param>
- <param name="output_format" type="optiongroup" _gui-text="{% trans %}Output file format{% endtrans %}" appearance="minimal">
- {% for format, description in formats %}
- <_option value="{{ format }}">{{ _(description) }} ({{ format | upper }})</_option>
- {% endfor %}
- {% for format, description in debug_formats %}
- <_option value="{{ format }}">{{ _(description) }} ({{ format | upper }}) [{{ _("DEBUG") }}]</_option>
- {% endfor %}
- </param>
- <param name="path" type="string" _gui-text="{% trans %}Directory{% endtrans %}" _gui-description="{% trans %}Leave empty to save the output in Inkscape's extension directory.{% endtrans %}"></param>
- <param name="extension" type="string" gui-hidden="true">embroider</param>
- <effect>
- <object-type>all</object-type>
- <effects-menu>
- <submenu name="Ink/Stitch">
- <submenu name="{% trans %}Visualise and Export{% endtrans %}" />
- </submenu>
- </effects-menu>
- </effect>
- <script>
- {{ command_tag | safe }}
- </script>
-</inkscape-extension>
diff --git a/templates/embroider.xml b/templates/embroider.xml
new file mode 100644
index 00000000..ff56ac7d
--- /dev/null
+++ b/templates/embroider.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <name>{% trans %}Embroider{% endtrans %}</name>
+ <id>org.inkstitch.embroider.{{ locale }}</id>
+ <param name="title" type="description" appearance="header">{% trans %}Create a stitch file{% endtrans %}</param>
+ <param name="howto" type="description">{% trans %}Save your embroidery file through | File > Save a Copy ... |{% endtrans %}</param>
+ <param name="howto_detail" type="description">{% trans %}Choose from listed embroidery file formats and save.{% endtrans %}</param>
+ <spacer />
+ <param name="info_zip" type="description">{% trans %}Multiple file formats can be saved by choosing the zip file format.{% endtrans %}</param>
+ <effect needs-live-preview="false" needs-document="false">
+ <object-type>all</object-type>
+ <effects-menu>
+ <submenu name="Ink/Stitch">
+ <submenu name="{% trans %}Visualise and Export{% endtrans %}" />
+ </submenu>
+ </effects-menu>
+ </effect>
+</inkscape-extension>
diff --git a/templates/embroider_settings.xml b/templates/embroider_settings.xml
new file mode 100644
index 00000000..51ce9d16
--- /dev/null
+++ b/templates/embroider_settings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <name>{% trans %}Settings{% endtrans %}</name>
+ <id>org.inkstitch.embroider_settings.{{ locale }}</id>
+ <param name="extension" type="string" gui-hidden="true">embroider_settings</param>
+ <effect>
+ <object-type>all</object-type>
+ <effects-menu>
+ <submenu name="Ink/Stitch" />
+ </effects-menu>
+ </effect>
+ <param name="output_settings" type="description" appearance="header">
+ {% trans %}Output Settings{% endtrans %}
+ </param>
+ <param name="collapse_len_mm" type="float" precision="1" min="0" max="10"
+ _gui-text="{% trans %}Collapse length (mm){% endtrans %}"
+ _gui-description="{% trans %}Jump stitches smaller than this will be treated as normal stitches.{% endtrans %}">3</param>
+ <script>
+ {{ command_tag | safe }}
+ </script>
+</inkscape-extension>
diff --git a/templates/flip.inx b/templates/flip.xml
index 29b11457..29b11457 100644
--- a/templates/flip.inx
+++ b/templates/flip.xml
diff --git a/templates/global_commands.inx b/templates/global_commands.xml
index d37e2e41..d37e2e41 100644
--- a/templates/global_commands.inx
+++ b/templates/global_commands.xml
diff --git a/templates/import_threadlist.inx b/templates/import_threadlist.xml
index 081a881a..5db963b6 100644
--- a/templates/import_threadlist.inx
+++ b/templates/import_threadlist.xml
@@ -3,9 +3,7 @@
<name>{% trans %}Import Threadlist{% endtrans %}</name>
<id>org.inkstitch.import_threadlist.{{ locale }}</id>
<param name="extension" type="string" gui-hidden="true">import_threadlist</param>
- <!-- This doesn't work at the moment. It's an Inkstitch 1.0 feature, but would be desireable.
- <param type="path" name="filepath" gui-text="{{ _('Choose file') }}" mode="file" filetypes="txt"/> -->
- <param name="filepath" type="string" _gui-text="{{ _('File Path') }}">{{ _("Enter path to file") }}</param>
+ <param name="filepath" type="path" gui-text="{{ _('Choose file') }}" mode="file" filetypes="txt"/>
<param name="method" type="optiongroup" _gui-text="Choose method">
<option value="1">Import Ink/Stitch threadlist</option>
<option value="2">Import other threadlist*</option>
diff --git a/templates/input.inx b/templates/input.xml
index 5f4a4610..5f4a4610 100644
--- a/templates/input.inx
+++ b/templates/input.xml
diff --git a/templates/install.inx b/templates/install.xml
index ffcb9b7b..764027b8 100644
--- a/templates/install.inx
+++ b/templates/install.xml
@@ -3,7 +3,7 @@
<name>{% trans %}Install thread color palettes for Inkscape{% endtrans %}</name>
<id>org.inkstitch.install.{{ locale }}</id>
<param name="extension" type="string" gui-hidden="true">install</param>
- <effect>
+ <effect implements-custom-gui="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch" />
diff --git a/templates/layer_commands.inx b/templates/layer_commands.xml
index 249d536e..249d536e 100644
--- a/templates/layer_commands.inx
+++ b/templates/layer_commands.xml
diff --git a/templates/lettering.inx b/templates/lettering.xml
index 65eee8a1..6dea9e93 100644
--- a/templates/lettering.inx
+++ b/templates/lettering.xml
@@ -3,7 +3,7 @@
<name>{% trans %}Lettering{% endtrans %}</name>
<id>org.inkstitch.lettering.{{ locale }}</id>
<param name="extension" type="string" gui-hidden="true">lettering</param>
- <effect>
+ <effect implements-custom-gui="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch" />
diff --git a/templates/lettering_custom_font_dir.xml b/templates/lettering_custom_font_dir.xml
new file mode 100644
index 00000000..b08c37d6
--- /dev/null
+++ b/templates/lettering_custom_font_dir.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <name>{% trans %}Custom font directory{% endtrans %}</name>
+ <id>org.inkstitch.lettering_custom_font_dir.{{ locale }}</id>
+ <param name="extension" type="string" gui-hidden="true">lettering_custom_font_dir</param>
+ <effect needs-live-preview="false">
+ <object-type>all</object-type>
+ <effects-menu>
+ <submenu name="Ink/Stitch">
+ <submenu name="{% trans %}Font Tools{% endtrans %}" />
+ </submenu>
+ </effects-menu>
+ </effect>
+ <param name="file-description" type="description" indent="1" >
+ {% trans %}Set a custom directory for additional fonts to be used with the lettering tool.{% endtrans %}
+ </param>
+ <spacer />
+ <param name="path" type="path" mode="folder" gui-text="{% trans %}Custom font directory{% endtrans %}" indent="1"></param>
+ <spacer />
+ <param name="file-description" type="description" indent="1" >
+ {% trans %}Usage: Create a subdirectory for each font you add.{% endtrans %}
+ </param>
+ <script>
+ {{ command_tag | safe }}
+ </script>
+</inkscape-extension>
diff --git a/templates/lettering_generate_json.xml b/templates/lettering_generate_json.xml
new file mode 100644
index 00000000..5f4907f7
--- /dev/null
+++ b/templates/lettering_generate_json.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <name>{% trans %}Generate JSON{% endtrans %}</name>
+ <id>org.inkstitch.lettering_generate_json.{{ locale }}</id>
+ <param name="extension" type="string" gui-hidden="true">lettering_generate_json</param>
+ <effect needs-live-preview="false">
+ <object-type>all</object-type>
+ <effects-menu>
+ <submenu name="Ink/Stitch">
+ <submenu name="{% trans %}Font Tools{% endtrans %}" />
+ </submenu>
+ </effects-menu>
+ </effect>
+ <param name="header" type="description" appearance="header" indent="1" >
+ {% trans %}Generates font.json which can be used by the lettering tool.{% endtrans %}
+ </param>
+ <spacer />
+ <separator indent="1"/>
+ <param type="string" name="font-name" gui-text="{% trans %}Name{% endtrans %}" indent="1" />
+ <param type="string" name="font-description" gui-text="{% trans %}Description{% endtrans %}" indent="1" />
+ <separator indent="1"/>
+ <spacer />
+ <param name="file-description" type="description" indent="1" >
+ {% trans %}Insert a font SVG file with kerning information.{% endtrans %}
+ </param>
+ <param type="path" name="font-file" gui-text="{% trans %}Font File{% endtrans %}" indent="1" mode="file" filetypes="svg"/>
+ <spacer />
+ <separator indent="1"/>
+ <param type="bool" name="auto-satin" gui-text="{% trans %}Autoroute Satin{% endtrans %}"
+ gui-description="{% trans %}Disable if you defined manual routing in your font.{% endtrans %}" indent="1">true</param>
+ <param type="bool" name="reversible" gui-text="{% trans %}Reversible{% endtrans %}"
+ gui-description="{% trans %}If disabled back and forth stitching will not be possile for this font.{% endtrans %}" indent="1">true</param>
+ <separator indent="1"/>
+ <param type="string" name="default-glyph" gui-text="{% trans %}Default Glyph{% endtrans %}" indent="1">&#65533;</param>
+ <separator indent="1"/>
+ <spacer />
+ <param name="min-scale" type="float" precision="1" min="0.1" max="1" gui-text="{% trans %}Min Scale{% endtrans %}" indent="1">1</param>
+ <param name="max-scale" type="float" precision="1" min="1" max="10" gui-text="{% trans %}Max Scale{% endtrans %}" indent="1">1</param>
+ <separator indent="1"/>
+ <param name="leading" type="int" precision="1" min="-100" max="100" gui-text="{% trans %}Leading (px){% endtrans %}" gui-description="{% trans %}If 0, the value will be calculated or defaults to 100{% endtrans %}" indent="1"></param>
+ <spacer />
+ <separator indent="1"/>
+ <script>
+ {{ command_tag | safe }}
+ </script>
+</inkscape-extension>
diff --git a/templates/lettering_remove_kerning.xml b/templates/lettering_remove_kerning.xml
new file mode 100644
index 00000000..5657e003
--- /dev/null
+++ b/templates/lettering_remove_kerning.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <name>{% trans %}Remove Kerning{% endtrans %}</name>
+ <id>org.inkstitch.lettering_remove_kerning.{{ locale }}</id>
+ <param name="extension" type="string" gui-hidden="true">lettering_remove_kerning</param>
+ <effect needs-live-preview="false">
+ <object-type>all</object-type>
+ <effects-menu>
+ <submenu name="Ink/Stitch">
+ <submenu name="{% trans %}Font Tools{% endtrans %}" />
+ </submenu>
+ </effects-menu>
+ </effect>
+ <param name="header" type="description" appearance="header" indent="1" >
+ {% trans %}Removes Kerning information from given SVG files{% endtrans %}
+ </param>
+ <separator />
+ <param name="file-description" type="description" indent="1" >
+ &#9888; {% trans %}Make sure you keep a copy of the original file. After running this extension kerning information will be lost unrevertably from these files.{% endtrans %}
+ </param>
+ <separator />
+ <spacer />
+ <param type="path" name="font-files" gui-text="{% trans %}Select Font Files{% endtrans %}" indent="1" mode="files" filetypes="svg"/>
+ <spacer />
+ <script>
+ {{ command_tag | safe }}
+ </script>
+</inkscape-extension>
diff --git a/templates/object_commands.inx b/templates/object_commands.xml
index affe7584..affe7584 100644
--- a/templates/object_commands.inx
+++ b/templates/object_commands.xml
diff --git a/templates/output.inx b/templates/output.xml
index ffff4be9..545c3d28 100644
--- a/templates/output.inx
+++ b/templates/output.xml
@@ -4,7 +4,7 @@
<id>org.inkstitch.output.{{ format }}.{{ locale }}</id>
<output>
<extension>.{{ format }}</extension>
- <mimetype>application/x-embroidery-{{ format }}</mimetype>
+ <mimetype>{{ mimetype }}</mimetype>
<_filetypename>Ink/Stitch: {{ _(description) }} (.{{ format }})</_filetypename>
<_filetypetooltip>{{ _("Save design in %(file_extension)s format using Ink/Stitch") % dict(file_extension=format.upper()) }}</_filetypetooltip>
<dataloss>true</dataloss>
diff --git a/templates/output_params_txt.xml b/templates/output_params_txt.xml
index c8ca35ee..8b7f66f5 100644
--- a/templates/output_params_txt.xml
+++ b/templates/output_params_txt.xml
@@ -36,6 +36,6 @@
gui-description="{{ _("minimum spindle speed value (grbl $31 setting)") }}" min="-1" max="1000000000">-1</param>
<param name="max_spindle_speed" type="int" gui-text="{{ _("max spindle speed") }}"
gui-description="{{ _("minimum spindle speed value (grbl $30 setting)") }}" min="-1" max="1000000000">-1</param>
- <param name="feed_rate" type="int" gui-description="{{ _("feed rate (in mm/min, set to -1 to omit)")}}" min="-1" max="1000000000">-1</param>
+ <param name="feed_rate" type="int" gui-text="{{ _("feed rate (in mm/min, set to -1 to omit)")}}" min="-1" max="1000000000">-1</param>
</page>
</param>
diff --git a/templates/params.inx b/templates/params.xml
index a2dc89a3..4ceb9020 100644
--- a/templates/params.inx
+++ b/templates/params.xml
@@ -3,7 +3,7 @@
<name>{% trans %}Params{% endtrans %}</name>
<id>org.inkstitch.params.{{ locale }}</id>
<param name="extension" type="string" gui-hidden="true">params</param>
- <effect>
+ <effect implements-custom-gui="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch" />
diff --git a/templates/print.inx b/templates/print.xml
index 33d8b25c..410f9b54 100644
--- a/templates/print.inx
+++ b/templates/print.xml
@@ -3,7 +3,7 @@
<name>{% trans %}PDF Export{% endtrans %}</name>
<id>org.inkstitch.print.{{ locale }}</id>
<param name="extension" type="string" gui-hidden="true">print</param>
- <effect>
+ <effect implements-custom-gui="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch">
diff --git a/templates/remove_embroidery_settings.inx b/templates/remove_embroidery_settings.xml
index c83cc1b8..c83cc1b8 100644
--- a/templates/remove_embroidery_settings.inx
+++ b/templates/remove_embroidery_settings.xml
diff --git a/templates/reorder.xml b/templates/reorder.xml
new file mode 100644
index 00000000..7e002616
--- /dev/null
+++ b/templates/reorder.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <name>{% trans %}Reorder{% endtrans %}</name>
+ <id>org.inkstitch.reorder.{{ locale }}</id>
+ <param name="extension" type="string" gui-hidden="true">reorder</param>
+ <effect>
+ <object-type>all</object-type>
+ <effects-menu>
+ <submenu name="Ink/Stitch" />
+ </effects-menu>
+ </effect>
+ <script>
+ {{ command_tag | safe }}
+ </script>
+</inkscape-extension>
diff --git a/templates/simulator.inx b/templates/simulator.xml
index dfa6b34a..029d8b37 100644
--- a/templates/simulator.inx
+++ b/templates/simulator.xml
@@ -3,7 +3,7 @@
<name>{% trans %}Simulator / Realistic Preview{% endtrans %}</name>
<id>org.inkstitch.simulator.{{ locale }}</id>
<param name="extension" type="string" gui-hidden="true">simulator</param>
- <effect>
+ <effect implements-custom-gui="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch">
diff --git a/templates/stitch_plan_preview.inx b/templates/stitch_plan_preview.xml
index 72ea7f04..72ea7f04 100644
--- a/templates/stitch_plan_preview.inx
+++ b/templates/stitch_plan_preview.xml
diff --git a/templates/troubleshoot.inx b/templates/troubleshoot.xml
index 02df8ddc..02df8ddc 100644
--- a/templates/troubleshoot.inx
+++ b/templates/troubleshoot.xml
diff --git a/templates/zip.inx b/templates/zip.xml
index 42623019..4d620424 100644
--- a/templates/zip.inx
+++ b/templates/zip.xml
@@ -9,8 +9,10 @@
<_filetypetooltip>{{ _("Create a ZIP with multiple embroidery file formats using Ink/Stitch") }}</_filetypetooltip>
<dataloss>true</dataloss>
</output>
- {%- for format, description in formats %}
+ {%- for format, description, mimetype, category in formats %}
+ {%- if category == "embroidery" %}
<param name="format-{{ format }}" type="boolean" _gui-text=".{{ format | upper }}: {{ _(description) }}">false</param>
+ {%- endif %}
{%- endfor %}
<param name="format-svg" type="boolean" _gui-text=".SVG: {{ _("Scalable Vector Graphic") }}">false</param>
<param name="format-threadlist" type="boolean" _gui-text=".TXT: {{ _("Threadlist") }}">false</param>