kopia lustrzana https://github.com/inkstitch/inkstitch
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>pull/1031/head
rodzic
b39575a501
commit
e84a86d4ac
|
@ -10,156 +10,149 @@ jobs:
|
||||||
linux:
|
linux:
|
||||||
runs-on: ubuntu-16.04
|
runs-on: ubuntu-16.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: '11.x'
|
node-version: '15.x'
|
||||||
- name: download dependencies
|
|
||||||
shell: bash
|
- uses: actions/cache@v2
|
||||||
run: |
|
id: pip-cache
|
||||||
curl -sOL https://inkscape.org/en/gallery/item/12187/inkscape-0.92.3.tar.bz2
|
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
|
- name: install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
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 update
|
||||||
|
|
||||||
sudo apt-get install python2.7
|
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install wheel
|
||||||
|
|
||||||
sudo apt-get install gettext
|
sudo apt-get install gettext
|
||||||
|
|
||||||
# for wxPython
|
# for wxPython
|
||||||
sudo apt-get install glib-networking libsdl1.2-dev
|
sudo apt install glib-networking libsdl1.2-dev
|
||||||
|
|
||||||
# for PyGObject
|
# for PyGObject
|
||||||
sudo apt install libgirepository1.0-dev
|
sudo apt install libgirepository1.0-dev libcairo2-dev
|
||||||
|
|
||||||
# for shapely
|
# for shapely
|
||||||
sudo apt install libgeos-dev
|
sudo apt install libgeos-dev build-essential libgtk-3-dev
|
||||||
|
|
||||||
uname -a
|
uname -a
|
||||||
python --version
|
python --version
|
||||||
python -m pip --version
|
python -m pip --version
|
||||||
python -m pip debug
|
python -m pip debug
|
||||||
|
|
||||||
# wxPython doen't publish linux wheels in pypi
|
python -m pip install pycairo==1.11.1
|
||||||
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 PyGObject==3.30.5
|
||||||
python -m pip install wxPython*.whl
|
|
||||||
|
|
||||||
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 -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
|
echo "${{ env.pythonLocation }}\bin" >> $GITHUB_PATH
|
||||||
- shell: bash
|
- shell: bash
|
||||||
run: |
|
run: |
|
||||||
make dist
|
make dist
|
||||||
env:
|
env:
|
||||||
BUILD: linux
|
BUILD: linux
|
||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: inkstitch-linux
|
name: inkstitch-linux
|
||||||
path: artifacts
|
path: artifacts
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: '11.x'
|
node-version: '15.x'
|
||||||
- uses: actions/setup-python@v1
|
- uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '2.7.x'
|
python-version: '3.9'
|
||||||
architecture: 'x86'
|
architecture: 'x86'
|
||||||
- uses: microsoft/setup-msbuild@v1.0.2
|
- 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
|
- name: install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
pip install Shapely-1.6.3-cp27-cp27m-win32.whl
|
python -m pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
python -m pip install wheel
|
||||||
pip install pyinstaller==3.3.1
|
|
||||||
|
|
||||||
# Just using tar -j freezes forever with no output. Heck if I know why. This seems to work.
|
python -m pip install git+https://github.com/gtaylor/python-colormath
|
||||||
bzcat inkscape-0.92.3.tar.bz2 | tar -vxf -
|
|
||||||
rm inkscape-0.92.3.tar.bz2
|
python -m pip install -r requirements.txt
|
||||||
mv inkscape-0.92.3 inkscape
|
python -m pip install pyinstaller
|
||||||
|
|
||||||
echo "${{ env.pythonLocation }}\bin" >> $GITHUB_PATH
|
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
|
- shell: bash
|
||||||
run: |
|
run: |
|
||||||
make dist
|
make dist
|
||||||
env:
|
env:
|
||||||
BUILD: windows
|
BUILD: windows
|
||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: inkstitch-windows
|
name: inkstitch-windows
|
||||||
path: artifacts
|
path: artifacts
|
||||||
mac:
|
mac:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: '11.x'
|
node-version: '15.x'
|
||||||
- uses: actions/setup-python@v1
|
|
||||||
with:
|
|
||||||
python-version: '2.7.x'
|
|
||||||
- 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
|
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
brew update
|
brew update
|
||||||
|
|
||||||
# this errors because it installs python3 but python2 is already installed
|
brew install gtk+3 pkg-config gobject-introspection geos libffi gettext || true
|
||||||
brew install gtk+3 pkg-config gobject-introspection libffi gettext || true
|
|
||||||
|
|
||||||
export LDFLAGS="-L/usr/local/opt/libffi/lib"
|
export LDFLAGS="-L/usr/local/opt/libffi/lib"
|
||||||
export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig"
|
export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig"
|
||||||
|
|
||||||
# for msgfmt
|
# for msgfmt
|
||||||
echo "/usr/local/opt/gettext/bin" >> $GITHUB_PATH
|
echo "/usr/local/opt/gettext/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
echo "GI_TYPELIB_PATH=/usr/local/lib/girepository-1.0/" >> $GITHUB_ENV
|
echo "GI_TYPELIB_PATH=/usr/local/lib/girepository-1.0/" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
pip install --upgrade pip
|
||||||
pip --version
|
pip --version
|
||||||
|
pip install wheel
|
||||||
pip install PyGObject
|
pip install PyGObject
|
||||||
|
pip install git+https://github.com/gtaylor/python-colormath
|
||||||
|
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
pip install pyinstaller==3.3.1
|
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
|
echo "${{ env.pythonLocation }}/bin" >> $GITHUB_PATH
|
||||||
- shell: bash
|
- shell: bash
|
||||||
|
@ -167,7 +160,7 @@ jobs:
|
||||||
make dist
|
make dist
|
||||||
env:
|
env:
|
||||||
BUILD: osx
|
BUILD: osx
|
||||||
- uses: actions/upload-artifact@master
|
- uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: inkstitch-mac
|
name: inkstitch-mac
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
|
@ -15,3 +15,4 @@ locales/
|
||||||
/debug.log
|
/debug.log
|
||||||
/debug.svg
|
/debug.svg
|
||||||
/.idea
|
/.idea
|
||||||
|
/VERSION
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
dist: locales inx
|
dist: version locales inx
|
||||||
bash bin/build-python
|
bash bin/build-python
|
||||||
bash bin/build-electron
|
bash bin/build-electron
|
||||||
bash bin/build-distribution-archives
|
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
|
rm -rf build dist inx locales *.spec *.tar.gz *.zip electron/node_modules electron/dist
|
||||||
|
|
||||||
.PHONY: inx
|
.PHONY: inx
|
||||||
inx: locales
|
inx: version locales
|
||||||
mkdir -p inx
|
mkdir -p inx
|
||||||
python bin/generate-inx-files; \
|
python bin/generate-inx-files; \
|
||||||
|
|
||||||
|
@ -49,6 +49,10 @@ locales:
|
||||||
mkdir -p locales; \
|
mkdir -p locales; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
.PHONY: version
|
||||||
|
version:
|
||||||
|
bash bin/generate-version-file
|
||||||
|
|
||||||
.PHONY: style
|
.PHONY: style
|
||||||
style:
|
style:
|
||||||
flake8 . --count --max-complexity=10 --max-line-length=150 --statistics --exclude=pyembroidery,__init__.py,electron,build
|
flake8 . --count --max-complexity=10 --max-line-length=150 --statistics --exclude=pyembroidery,__init__.py,electron,build
|
||||||
|
|
|
@ -4,12 +4,14 @@ VERSION="$(echo ${GITHUB_REF} | sed -e 's|refs/heads/||' -e 's|refs/tags/||' -e
|
||||||
OS="${BUILD:-$(uname)}"
|
OS="${BUILD:-$(uname)}"
|
||||||
ARCH="$(uname -m)"
|
ARCH="$(uname -m)"
|
||||||
|
|
||||||
cp -a images/examples palettes symbols fonts dist/inkstitch
|
|
||||||
cp -a icons locales print dist/inkstitch/bin
|
|
||||||
|
|
||||||
if [ "$BUILD" = "osx" ]; then
|
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
|
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
|
cp -a electron/build/*-unpacked dist/inkstitch/electron
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -52,8 +52,3 @@ shopt -s dotglob
|
||||||
mkdir dist/bin
|
mkdir dist/bin
|
||||||
mv dist/inkstitch/* dist/bin
|
mv dist/inkstitch/* dist/bin
|
||||||
mv dist/bin dist/inkstitch
|
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
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
VERSION="${GITHUB_REF##*/}"
|
||||||
|
OS="${BUILD:-$(uname)}"
|
||||||
|
|
||||||
|
if [[ "$VERSION" == "" ]]; then
|
||||||
|
VERSION="Manual Install"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${VERSION} (${OS})" > VERSION
|
|
@ -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:
|
with open(os.path.join(fonts_dir, font, "font.json")) as font_json:
|
||||||
font_metadata = json.load(font_json)
|
font_metadata = json.load(font_json)
|
||||||
|
|
||||||
print "# L10N name of font in fonts/%s" % font
|
print("# L10N name of font in fonts/%s" % font)
|
||||||
print "_(%s)" % repr(font_metadata.get("name", ""))
|
print("_(%s)" % repr(font_metadata.get("name", "")))
|
||||||
|
|
||||||
print "# L10N description of font in fonts/%s" % font
|
print("# L10N description of font in fonts/%s" % font)
|
||||||
print "_(%s)" % repr(font_metadata.get("description", ""))
|
print("_(%s)" % repr(font_metadata.get("description", "")))
|
||||||
|
|
|
@ -6,5 +6,5 @@ import pyembroidery
|
||||||
# generate fake python code containing the descriptions of pyembroidery formats
|
# generate fake python code containing the descriptions of pyembroidery formats
|
||||||
# as gettext calls so that pybabel will extract them into messages.po
|
# as gettext calls so that pybabel will extract them into messages.po
|
||||||
for format in pyembroidery.supported_formats():
|
for format in pyembroidery.supported_formats():
|
||||||
print "# L10N description for pyembroidery file format: %s" % format['extension']
|
print("# L10N description for pyembroidery file format: %s" % format['extension'])
|
||||||
print "_(%s)" % repr(format['description'])
|
print("_(%s)" % repr(format['description']))
|
||||||
|
|
|
@ -103,12 +103,6 @@ let rendererConfig = {
|
||||||
'css-loader',
|
'css-loader',
|
||||||
{
|
{
|
||||||
loader: 'sass-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
|
// Requires sass-loader@^8.0.0
|
||||||
options: {
|
options: {
|
||||||
implementation: require('sass'),
|
implementation: require('sass'),
|
||||||
|
@ -132,6 +126,18 @@ let rendererConfig = {
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: 'index.html',
|
filename: 'index.html',
|
||||||
template: path.resolve(__dirname, '../src/index.ejs'),
|
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: {
|
minify: {
|
||||||
collapseWhitespace: true,
|
collapseWhitespace: true,
|
||||||
removeAttributeQuotes: true,
|
removeAttributeQuotes: true,
|
||||||
|
|
|
@ -77,12 +77,6 @@ let webConfig = {
|
||||||
'css-loader',
|
'css-loader',
|
||||||
{
|
{
|
||||||
loader: 'sass-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
|
// Requires sass-loader@^8.0.0
|
||||||
options: {
|
options: {
|
||||||
implementation: require('sass'),
|
implementation: require('sass'),
|
||||||
|
@ -102,6 +96,18 @@ let webConfig = {
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: 'index.html',
|
filename: 'index.html',
|
||||||
template: path.resolve(__dirname, '../src/index.ejs'),
|
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: {
|
minify: {
|
||||||
collapseWhitespace: true,
|
collapseWhitespace: true,
|
||||||
removeAttributeQuotes: true,
|
removeAttributeQuotes: true,
|
||||||
|
|
|
@ -1,9 +1,27 @@
|
||||||
module.exports.selectLanguage = function () {
|
module.exports.selectLanguage = function (translations) {
|
||||||
['LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'].forEach(language => {
|
// 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]) {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// set default language
|
||||||
return "en_US"
|
if (lang === undefined) {
|
||||||
|
lang = "en_US"
|
||||||
|
}
|
||||||
|
return lang
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,7 @@ function createWindow() {
|
||||||
app.on('ready', createWindow)
|
app.on('ready', createWindow)
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
app.quit()
|
app.quit()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
|
|
|
@ -126,7 +126,7 @@ export default {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
let label = "STITCH"
|
let label = this.$gettext("STITCH")
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case stitch.jump:
|
case stitch.jump:
|
||||||
label = this.$gettext("JUMP")
|
label = this.$gettext("JUMP")
|
||||||
|
|
|
@ -20,7 +20,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
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 {
|
.fa-spin-fast {
|
||||||
|
@ -59,11 +70,13 @@ button {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
margin: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
border: 2px solid rgb(0, 51, 153);
|
border: 2px solid rgb(0, 51, 153);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
padding: 0 5px
|
||||||
}
|
}
|
||||||
|
|
||||||
.window-controls {
|
.window-controls {
|
||||||
|
@ -142,6 +155,10 @@ fieldset.show-commands {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fieldset.show-commands legend {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
fieldset.show-commands span {
|
fieldset.show-commands span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
@ -152,7 +169,7 @@ fieldset.show-commands span.npp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset.show-commands span:first-of-type {
|
fieldset.show-commands span:first-of-type {
|
||||||
padding-right: 12px;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.pressed {
|
button.pressed {
|
||||||
|
@ -164,8 +181,7 @@ button.pressed {
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-container {
|
.slider-container {
|
||||||
margin-top: 25px;
|
margin: 25px 5px;
|
||||||
margin-bottom: 25px;
|
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,12 +260,15 @@ button.pressed {
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
border-style: inset;
|
||||||
|
padding: 0 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.simulator {
|
.simulator {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 95vh;
|
height: 95vh;
|
||||||
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-command {
|
.current-command {
|
||||||
|
@ -269,6 +288,7 @@ div.simulator::v-deep svg.simulation {
|
||||||
div.simulator::v-deep svg.simulation-scale {
|
div.simulator::v-deep svg.simulation-scale {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
order: -1;
|
order: -1;
|
||||||
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.simulator::v-deep .simulation-scale-label {
|
div.simulator::v-deep .simulation-scale-label {
|
||||||
|
|
|
@ -189,7 +189,9 @@
|
||||||
<span class="current-command">{{currentCommand}}</span>
|
<span class="current-command">{{currentCommand}}</span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="show-commands">
|
<fieldset class="show-commands">
|
||||||
<legend>Show</legend>
|
<legend>
|
||||||
|
<translate>Show</translate>
|
||||||
|
</legend>
|
||||||
<span>
|
<span>
|
||||||
<input id="trim-checkbox" type="checkbox" v-model="showTrims"/>
|
<input id="trim-checkbox" type="checkbox" v-model="showTrims"/>
|
||||||
<label for="trim-checkbox"><font-awesome-icon icon="cut"/> <translate>trims</translate></label>
|
<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="circle" transform="shrink-9"/>
|
||||||
<font-awesome-icon icon="minus" class="fa-thin-line"/>
|
<font-awesome-icon icon="minus" class="fa-thin-line"/>
|
||||||
</font-awesome-layers>
|
</font-awesome-layers>
|
||||||
<span v-translate>needle<br/>points</span>
|
<span v-translate>needle points</span>
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -73,7 +73,7 @@ Vue.component('font-awesome-layers', FontAwesomeLayers)
|
||||||
Vue.use(Transitions)
|
Vue.use(Transitions)
|
||||||
Vue.use(GetTextPlugin, {
|
Vue.use(GetTextPlugin, {
|
||||||
translations: translations,
|
translations: translations,
|
||||||
defaultLanguage: selectLanguage(),
|
defaultLanguage: selectLanguage(translations),
|
||||||
silent: true
|
silent: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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>
|
Po Szerokość: | Wysokość: | Rozmiar: 5.8 KiB |
30
inkstitch.py
30
inkstitch.py
|
@ -3,12 +3,15 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from argparse import ArgumentParser
|
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
|
import lib.debug as debug
|
||||||
from lib import extensions
|
from lib import extensions
|
||||||
from lib.i18n import _
|
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 = logging.getLogger('shapely.geos')
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
|
@ -36,28 +39,35 @@ extension_class = getattr(extensions, extension_class_name)
|
||||||
extension = extension_class()
|
extension = extension_class()
|
||||||
|
|
||||||
if hasattr(sys, 'gettrace') and sys.gettrace():
|
if hasattr(sys, 'gettrace') and sys.gettrace():
|
||||||
extension.affect(args=remaining_args)
|
extension.run(args=remaining_args)
|
||||||
else:
|
else:
|
||||||
save_stderr()
|
save_stderr()
|
||||||
exception = None
|
exception = None
|
||||||
try:
|
try:
|
||||||
extension.affect(args=remaining_args)
|
extension.run(args=remaining_args)
|
||||||
except (SystemExit, KeyboardInterrupt):
|
except (SystemExit, KeyboardInterrupt):
|
||||||
raise
|
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:
|
except Exception:
|
||||||
exception = traceback.format_exc()
|
exception = traceback.format_exc()
|
||||||
finally:
|
finally:
|
||||||
restore_stderr()
|
restore_stderr()
|
||||||
|
|
||||||
if shapely_errors.tell():
|
if shapely_errors.tell():
|
||||||
print >> sys.stderr, shapely_errors.getvalue()
|
errormsg(shapely_errors.getvalue())
|
||||||
|
|
||||||
if exception:
|
if exception:
|
||||||
print >> sys.stderr, _("Ink/Stitch experienced an unexpected error.").encode("UTF-8")
|
errormsg(_("Ink/Stitch experienced an unexpected error.") + "\n")
|
||||||
print >> sys.stderr, _("If you'd like to help, please file an issue at "
|
errormsg(_("If you'd like to help, please file an issue at "
|
||||||
"https://github.com/inkstitch/inkstitch/issues "
|
"https://github.com/inkstitch/inkstitch/issues "
|
||||||
"and include the entire error description below:").encode("UTF-8"), "\n"
|
"and include the entire error description below:") + "\n")
|
||||||
print >> sys.stderr, exception
|
errormsg(version.get_inkstitch_version() + "\n")
|
||||||
|
errormsg(exception)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
@ -16,7 +16,7 @@ def palettes():
|
||||||
path = os.path.join(base_path, 'palettes')
|
path = os.path.join(base_path, 'palettes')
|
||||||
src_dir = get_bundled_dir('palettes')
|
src_dir = get_bundled_dir('palettes')
|
||||||
copy_files(glob(os.path.join(src_dir, "*")), path)
|
copy_files(glob(os.path.join(src_dir, "*")), path)
|
||||||
except Exception, exc:
|
except Exception as exc:
|
||||||
return jsonify({"error": str(exc)}), 500
|
return jsonify({"error": str(exc)}), 500
|
||||||
|
|
||||||
return jsonify({"status": "success"})
|
return jsonify({"status": "success"})
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import errno
|
import errno
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from flask import Flask, g, request
|
from flask import Flask, g, request
|
||||||
|
|
||||||
|
from ..utils.json import InkStitchJSONEncoder
|
||||||
from .install import install
|
from .install import install
|
||||||
from .simulator import simulator
|
from .simulator import simulator
|
||||||
from .stitch_plan import stitch_plan
|
from .stitch_plan import stitch_plan
|
||||||
from ..utils.json import InkStitchJSONEncoder
|
|
||||||
|
|
||||||
|
|
||||||
class APIServer(Thread):
|
class APIServer(Thread):
|
||||||
|
@ -27,6 +28,10 @@ class APIServer(Thread):
|
||||||
self.__setup_app()
|
self.__setup_app()
|
||||||
|
|
||||||
def __setup_app(self): # noqa: C901
|
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 = Flask(__name__)
|
||||||
self.app.json_encoder = InkStitchJSONEncoder
|
self.app.json_encoder = InkStitchJSONEncoder
|
||||||
|
|
||||||
|
@ -89,7 +94,7 @@ class APIServer(Thread):
|
||||||
response = requests.get("http://%s:%s/ping" % (self.host, self.port))
|
response = requests.get("http://%s:%s/ping" % (self.host, self.port))
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
break
|
break
|
||||||
except socket.error, e:
|
except socket.error as e:
|
||||||
if e.errno == errno.ECONNREFUSED:
|
if e.errno == errno.ECONNREFUSED:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -11,7 +11,9 @@ def get_stitch_plan():
|
||||||
if not g.extension.get_elements():
|
if not g.extension.get_elements():
|
||||||
return dict(colors=[], stitch_blocks=[], commands=[])
|
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)
|
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)
|
return jsonify(stitch_plan)
|
||||||
|
|
|
@ -3,11 +3,9 @@ import sys
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from random import random
|
from random import random
|
||||||
|
|
||||||
from shapely import geometry as shgeo
|
|
||||||
|
|
||||||
import cubicsuperpath
|
|
||||||
import inkex
|
import inkex
|
||||||
import simpletransform
|
from lxml import etree
|
||||||
|
from shapely import geometry as shgeo
|
||||||
|
|
||||||
from .i18n import N_, _
|
from .i18n import N_, _
|
||||||
from .svg import (apply_transforms, generate_unique_id,
|
from .svg import (apply_transforms, generate_unique_id,
|
||||||
|
@ -104,7 +102,7 @@ class Command(BaseCommand):
|
||||||
self.parse_command()
|
self.parse_command()
|
||||||
|
|
||||||
def parse_connector_path(self):
|
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)
|
return apply_transforms(path, self.connector)
|
||||||
|
|
||||||
def parse_command(self):
|
def parse_command(self):
|
||||||
|
@ -153,7 +151,7 @@ class StandaloneCommand(BaseCommand):
|
||||||
def point(self):
|
def point(self):
|
||||||
pos = [float(self.node.get("x", 0)), float(self.node.get("y", 0))]
|
pos = [float(self.node.get("x", 0)), float(self.node.get("y", 0))]
|
||||||
transform = get_node_transform(self.node)
|
transform = get_node_transform(self.node)
|
||||||
simpletransform.applyTransformToPoint(transform, pos)
|
pos = inkex.transforms.Transform(transform).apply_to_point(pos)
|
||||||
|
|
||||||
return Point(*pos)
|
return Point(*pos)
|
||||||
|
|
||||||
|
@ -209,14 +207,14 @@ def global_command(svg, command):
|
||||||
if len(commands) == 1:
|
if len(commands) == 1:
|
||||||
return commands[0]
|
return commands[0]
|
||||||
elif len(commands) > 1:
|
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. "
|
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)
|
"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
|
# 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
|
# 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
|
# command name to them. Contents of %(description)s are in a separate translation
|
||||||
# string.
|
# 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)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
|
@ -256,7 +254,7 @@ def symbols_path():
|
||||||
@cache
|
@cache
|
||||||
def symbols_svg():
|
def symbols_svg():
|
||||||
with open(symbols_path()) as symbols_file:
|
with open(symbols_path()) as symbols_file:
|
||||||
return inkex.etree.parse(symbols_file)
|
return etree.parse(symbols_file)
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
|
@ -269,7 +267,7 @@ def get_defs(document):
|
||||||
defs = document.find(SVG_DEFS_TAG)
|
defs = document.find(SVG_DEFS_TAG)
|
||||||
|
|
||||||
if defs is None:
|
if defs is None:
|
||||||
defs = inkex.etree.SubElement(document, SVG_DEFS_TAG)
|
defs = etree.SubElement(document, SVG_DEFS_TAG)
|
||||||
|
|
||||||
return defs
|
return defs
|
||||||
|
|
||||||
|
@ -284,7 +282,7 @@ def ensure_symbol(document, command):
|
||||||
|
|
||||||
|
|
||||||
def add_group(document, node, command):
|
def add_group(document, node, command):
|
||||||
return inkex.etree.SubElement(
|
return etree.SubElement(
|
||||||
node.getparent(),
|
node.getparent(),
|
||||||
SVG_GROUP_TAG,
|
SVG_GROUP_TAG,
|
||||||
{
|
{
|
||||||
|
@ -304,35 +302,35 @@ def add_connector(document, symbol, element):
|
||||||
if element.node.get('id') is None:
|
if element.node.get('id') is None:
|
||||||
element.node.set('id', generate_unique_id(document, "object"))
|
element.node.set('id', generate_unique_id(document, "object"))
|
||||||
|
|
||||||
path = inkex.etree.Element(SVG_PATH_TAG,
|
path = etree.Element(SVG_PATH_TAG,
|
||||||
{
|
{
|
||||||
"id": generate_unique_id(document, "command_connector"),
|
"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),
|
"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;",
|
"style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
|
||||||
CONNECTION_START: "#%s" % symbol.get('id'),
|
CONNECTION_START: "#%s" % symbol.get('id'),
|
||||||
CONNECTION_END: "#%s" % element.node.get('id'),
|
CONNECTION_END: "#%s" % element.node.get('id'),
|
||||||
CONNECTOR_TYPE: "polyline",
|
CONNECTOR_TYPE: "polyline",
|
||||||
|
|
||||||
# l10n: the name of the line that connects a command to the object it applies to
|
# l10n: the name of the line that connects a command to the object it applies to
|
||||||
INKSCAPE_LABEL: _("connector")
|
INKSCAPE_LABEL: _("connector")
|
||||||
})
|
})
|
||||||
|
|
||||||
symbol.getparent().insert(0, path)
|
symbol.getparent().insert(0, path)
|
||||||
|
|
||||||
|
|
||||||
def add_symbol(document, group, command, pos):
|
def add_symbol(document, group, command, pos):
|
||||||
return inkex.etree.SubElement(group, SVG_USE_TAG,
|
return etree.SubElement(group, SVG_USE_TAG,
|
||||||
{
|
{
|
||||||
"id": generate_unique_id(document, "command_use"),
|
"id": generate_unique_id(document, "command_use"),
|
||||||
XLINK_HREF: "#inkstitch_%s" % command,
|
XLINK_HREF: "#inkstitch_%s" % command,
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"x": str(pos.x),
|
"x": str(pos.x),
|
||||||
"y": str(pos.y),
|
"y": str(pos.y),
|
||||||
|
|
||||||
# l10n: the name of a command symbol (example: scissors icon for trim command)
|
# l10n: the name of a command symbol (example: scissors icon for trim command)
|
||||||
INKSCAPE_LABEL: _("command marker"),
|
INKSCAPE_LABEL: _("command marker"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def get_command_pos(element, index, total):
|
def get_command_pos(element, index, total):
|
||||||
|
@ -397,14 +395,14 @@ def add_layer_commands(layer, commands):
|
||||||
|
|
||||||
for command in commands:
|
for command in commands:
|
||||||
ensure_symbol(document, command)
|
ensure_symbol(document, command)
|
||||||
inkex.etree.SubElement(layer, SVG_USE_TAG,
|
etree.SubElement(layer, SVG_USE_TAG,
|
||||||
{
|
{
|
||||||
"id": generate_unique_id(document, "use"),
|
"id": generate_unique_id(document, "use"),
|
||||||
INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
|
INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
|
||||||
XLINK_HREF: "#inkstitch_%s" % command,
|
XLINK_HREF: "#inkstitch_%s" % command,
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"x": "0",
|
"x": "0",
|
||||||
"y": "-10",
|
"y": "-10",
|
||||||
"transform": correction_transform
|
"transform": correction_transform
|
||||||
})
|
})
|
||||||
|
|
37
lib/debug.py
37
lib/debug.py
|
@ -1,17 +1,16 @@
|
||||||
import atexit
|
import atexit
|
||||||
from contextlib import contextmanager
|
|
||||||
from datetime import datetime
|
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from inkex import etree
|
|
||||||
import inkex
|
import inkex
|
||||||
from simplestyle import formatStyle
|
from lxml import etree
|
||||||
|
|
||||||
from svg import line_strings_to_path
|
from .svg import line_strings_to_path
|
||||||
from svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
|
from .svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
|
||||||
|
|
||||||
|
|
||||||
def check_enabled(func):
|
def check_enabled(func):
|
||||||
|
@ -36,7 +35,10 @@ class Debug(object):
|
||||||
self.init_svg()
|
self.init_svg()
|
||||||
|
|
||||||
def init_log(self):
|
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.")
|
self.log("Debug logging enabled.")
|
||||||
|
|
||||||
def init_debugger(self):
|
def init_debugger(self):
|
||||||
|
@ -60,7 +62,7 @@ class Debug(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pydevd.settrace()
|
pydevd.settrace()
|
||||||
except socket.error, error:
|
except socket.error as error:
|
||||||
self.log("Debugging: connection to pydevd failed: %s", error)
|
self.log("Debugging: connection to pydevd failed: %s", error)
|
||||||
self.log("Be sure to run 'Start debugging server' in PyDev to enable debugging.")
|
self.log("Be sure to run 'Start debugging server' in PyDev to enable debugging.")
|
||||||
else:
|
else:
|
||||||
|
@ -74,8 +76,8 @@ class Debug(object):
|
||||||
|
|
||||||
def save_svg(self):
|
def save_svg(self):
|
||||||
tree = etree.ElementTree(self.svg)
|
tree = etree.ElementTree(self.svg)
|
||||||
with open(os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.svg"), "w") as debug_svg:
|
debug_svg = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.svg")
|
||||||
tree.write(debug_svg)
|
tree.write(debug_svg)
|
||||||
|
|
||||||
@check_enabled
|
@check_enabled
|
||||||
def add_layer(self, name="Debug"):
|
def add_layer(self, name="Debug"):
|
||||||
|
@ -113,20 +115,21 @@ class Debug(object):
|
||||||
timestamp = now.isoformat()
|
timestamp = now.isoformat()
|
||||||
self.last_log_time = now
|
self.last_log_time = now
|
||||||
|
|
||||||
print >> self.log_file, timestamp, message % args
|
with open(self.log_file, "a") as logfile:
|
||||||
self.log_file.flush()
|
print(timestamp, message % args, file=logfile)
|
||||||
|
logfile.flush()
|
||||||
|
|
||||||
def time(self, func):
|
def time(self, func):
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
if self.enabled:
|
if self.enabled:
|
||||||
self.raw_log("entering %s()", func.func_name)
|
self.raw_log("entering %s()", func.__name__)
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
result = func(*args, **kwargs)
|
result = func(*args, **kwargs)
|
||||||
|
|
||||||
if self.enabled:
|
if self.enabled:
|
||||||
end = time.time()
|
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
|
return result
|
||||||
|
|
||||||
|
@ -150,7 +153,7 @@ class Debug(object):
|
||||||
@check_enabled
|
@check_enabled
|
||||||
def log_line_strings(self, line_strings, name=None, color=None):
|
def log_line_strings(self, line_strings, name=None, color=None):
|
||||||
path = line_strings_to_path(line_strings)
|
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:
|
if name is not None:
|
||||||
path.set(INKSCAPE_LABEL, name)
|
path.set(INKSCAPE_LABEL, name)
|
||||||
|
@ -161,7 +164,7 @@ class Debug(object):
|
||||||
def log_line(self, start, end, name="line", color=None):
|
def log_line(self, start, end, name="line", color=None):
|
||||||
self.log_svg_element(etree.Element("path", {
|
self.log_svg_element(etree.Element("path", {
|
||||||
"d": "M%s,%s %s,%s" % (start + end),
|
"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
|
INKSCAPE_LABEL: name
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -174,7 +177,7 @@ class Debug(object):
|
||||||
|
|
||||||
self.log_svg_element(etree.Element("path", {
|
self.log_svg_element(etree.Element("path", {
|
||||||
"d": d,
|
"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
|
INKSCAPE_LABEL: name
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from auto_fill import AutoFill
|
from .auto_fill import AutoFill
|
||||||
from clone import Clone
|
from .clone import Clone
|
||||||
from element import EmbroideryElement
|
from .element import EmbroideryElement
|
||||||
from empty_d_object import EmptyDObject
|
from .empty_d_object import EmptyDObject
|
||||||
from fill import Fill
|
from .fill import Fill
|
||||||
from image import ImageObject
|
from .image import ImageObject
|
||||||
from polyline import Polyline
|
from .polyline import Polyline
|
||||||
from satin_column import SatinColumn
|
from .satin_column import SatinColumn
|
||||||
from stroke import Stroke
|
from .stroke import Stroke
|
||||||
from text import TextObject
|
from .text import TextObject
|
||||||
from utils import node_to_elements, nodes_to_elements
|
from .utils import node_to_elements, nodes_to_elements
|
||||||
|
|
|
@ -6,10 +6,9 @@ from shapely import geometry as shgeo
|
||||||
|
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..stitches import auto_fill
|
from ..stitches import auto_fill
|
||||||
from ..utils import cache
|
from ..utils import cache, version
|
||||||
from .element import Patch, param
|
from .element import Patch, param
|
||||||
from .fill import Fill
|
from .fill import Fill
|
||||||
|
|
||||||
from .validation import ValidationWarning
|
from .validation import ValidationWarning
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,6 +19,18 @@ class SmallShapeWarning(ValidationWarning):
|
||||||
"the outline instead.")
|
"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):
|
class AutoFill(Fill):
|
||||||
element_name = _("AutoFill")
|
element_name = _("AutoFill")
|
||||||
|
|
||||||
|
@ -157,9 +168,13 @@ class AutoFill(Fill):
|
||||||
def underlay_underpath(self):
|
def underlay_underpath(self):
|
||||||
return self.get_boolean_param('underlay_underpath', True)
|
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:
|
if amount:
|
||||||
shape = self.shape.buffer(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):
|
if not isinstance(shape, shgeo.MultiPolygon):
|
||||||
shape = shgeo.MultiPolygon([shape])
|
shape = shgeo.MultiPolygon([shape])
|
||||||
return 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
|
# 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 += _("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 += "https://github.com/inkstitch/inkstitch/issues/new\n\n"
|
||||||
|
message += version.get_inkstitch_version() + "\n\n"
|
||||||
message += traceback.format_exc()
|
message += traceback.format_exc()
|
||||||
|
|
||||||
self.fatal(message)
|
self.fatal(message)
|
||||||
|
@ -245,5 +261,11 @@ class AutoFill(Fill):
|
||||||
if self.shape.area < 20:
|
if self.shape.area < 20:
|
||||||
yield SmallShapeWarning(self.shape.centroid)
|
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():
|
for warning in super(AutoFill, self).validation_warnings():
|
||||||
yield warning
|
yield warning
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
from copy import copy
|
|
||||||
from math import atan, degrees
|
from math import atan, degrees
|
||||||
|
|
||||||
from simpletransform import (applyTransformToNode, applyTransformToPoint,
|
import inkex
|
||||||
computeBBox, parseTransform)
|
|
||||||
|
|
||||||
from ..commands import is_command, is_command_symbol
|
from ..commands import is_command, is_command_symbol
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..svg.path import get_node_transform
|
from ..svg.path import get_node_transform
|
||||||
from ..svg.svg import find_elements
|
from ..svg.svg import find_elements
|
||||||
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, SVG_GROUP_TAG,
|
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS,
|
||||||
SVG_LINK_TAG, SVG_POLYLINE_TAG, SVG_USE_TAG,
|
SVG_POLYLINE_TAG, SVG_USE_TAG, XLINK_HREF)
|
||||||
XLINK_HREF)
|
|
||||||
from ..utils import cache
|
from ..utils import cache
|
||||||
from .auto_fill import AutoFill
|
from .auto_fill import AutoFill
|
||||||
from .element import EmbroideryElement, param
|
from .element import EmbroideryElement, param
|
||||||
|
@ -74,16 +71,16 @@ class Clone(EmbroideryElement):
|
||||||
if node.tag == SVG_POLYLINE_TAG:
|
if node.tag == SVG_POLYLINE_TAG:
|
||||||
return [Polyline(node)]
|
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)]
|
return [SatinColumn(node)]
|
||||||
else:
|
else:
|
||||||
elements = []
|
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):
|
if element.get_boolean_param("auto_fill", True):
|
||||||
elements.append(AutoFill(node))
|
elements.append(AutoFill(node))
|
||||||
else:
|
else:
|
||||||
elements.append(Fill(node))
|
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):
|
if not is_command(element.node):
|
||||||
elements.append(Stroke(node))
|
elements.append(Stroke(node))
|
||||||
if element.get_boolean_param("stroke_first", False):
|
if element.get_boolean_param("stroke_first", False):
|
||||||
|
@ -98,32 +95,8 @@ class Clone(EmbroideryElement):
|
||||||
if source_node.tag not in EMBROIDERABLE_TAGS:
|
if source_node.tag not in EMBROIDERABLE_TAGS:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
clone = copy(source_node)
|
self.node.style = source_node.composed_style()
|
||||||
|
|
||||||
# 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
|
|
||||||
# a. a custom set fill angle
|
# a. a custom set fill angle
|
||||||
# b. calculated rotation for the cloned fill element to look exactly as it's source
|
# b. calculated rotation for the cloned fill element to look exactly as it's source
|
||||||
param = INKSTITCH_ATTRIBS['angle']
|
param = INKSTITCH_ATTRIBS['angle']
|
||||||
|
@ -131,48 +104,32 @@ class Clone(EmbroideryElement):
|
||||||
angle = self.clone_fill_angle
|
angle = self.clone_fill_angle
|
||||||
else:
|
else:
|
||||||
# clone angle
|
# 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]))
|
clone_angle = degrees(atan(-clone_mat[1][0]/clone_mat[1][1]))
|
||||||
# source node angle
|
# 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_angle = degrees(atan(-source_mat[1][0]/source_mat[1][1]))
|
||||||
# source node fill angle
|
# source node fill angle
|
||||||
source_fill_angle = source_node.get(param, 0)
|
source_fill_angle = source_node.get(param, 0)
|
||||||
|
|
||||||
angle = clone_angle + float(source_fill_angle) - source_angle
|
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:
|
for element in elements:
|
||||||
patches.extend(element.to_patches(last_patch))
|
patches.extend(element.to_patches(last_patch))
|
||||||
|
|
||||||
return patches
|
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):
|
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
|
return style
|
||||||
|
|
||||||
def center(self, source_node):
|
def center(self, source_node):
|
||||||
xmin, xmax, ymin, ymax = computeBBox([source_node])
|
transform = get_node_transform(self.node.getparent())
|
||||||
point = [(xmax-((xmax-xmin)/2)), (ymax-((ymax-ymin)/2))]
|
center = self.node.bounding_box(transform).center
|
||||||
transform = get_node_transform(self.node)
|
return center
|
||||||
applyTransformToPoint(transform, point)
|
|
||||||
return point
|
|
||||||
|
|
||||||
def validation_warnings(self):
|
def validation_warnings(self):
|
||||||
source_node = get_clone_source(self.node)
|
source_node = get_clone_source(self.node)
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import sys
|
import sys
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
import cubicsuperpath
|
import inkex
|
||||||
import simpletransform
|
|
||||||
import tinycss2
|
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 ..commands import find_commands
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..svg import (PIXELS_PER_MM, apply_transforms, convert_length,
|
from ..svg import (PIXELS_PER_MM, apply_transforms, convert_length,
|
||||||
get_node_transform)
|
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,
|
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSCAPE_LABEL, INKSTITCH_ATTRIBS,
|
||||||
SVG_OBJECT_TAGS, SVG_RECT_TAG)
|
SVG_GROUP_TAG, SVG_LINK_TAG, SVG_USE_TAG)
|
||||||
from ..utils import Point, cache
|
from ..utils import Point, cache
|
||||||
|
|
||||||
|
|
||||||
|
@ -155,22 +153,29 @@ class EmbroideryElement(object):
|
||||||
def parse_style(self, node=None):
|
def parse_style(self, node=None):
|
||||||
if node is None:
|
if node is None:
|
||||||
node = self.node
|
node = self.node
|
||||||
|
element_style = node.get("style", "")
|
||||||
|
if element_style is None:
|
||||||
|
return None
|
||||||
declarations = tinycss2.parse_declaration_list(node.get("style", ""))
|
declarations = tinycss2.parse_declaration_list(node.get("style", ""))
|
||||||
style = {declaration.lower_name: declaration.value[0].serialize() for declaration in declarations}
|
style = {declaration.lower_name: declaration.value[0].serialize() for declaration in declarations}
|
||||||
return style
|
return style
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def _get_style_raw(self, style_name):
|
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
|
return None
|
||||||
|
|
||||||
style = self.parse_style()
|
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()
|
parent = self.node.getparent()
|
||||||
# style not found, get inherited style elements
|
# style not found, get inherited style elements
|
||||||
while not style and parent is not None:
|
while not style and parent is not None:
|
||||||
style = self.parse_style(parent)
|
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()
|
parent = parent.getparent()
|
||||||
return style
|
return style
|
||||||
|
|
||||||
|
@ -196,23 +201,23 @@ class EmbroideryElement(object):
|
||||||
# Of course, transforms may also involve rotation, skewing, and translation.
|
# Of course, transforms may also involve rotation, skewing, and translation.
|
||||||
# All except translation can affect how wide the stroke appears on the screen.
|
# 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
|
# First, figure out the translation component of the transform. Using a zero
|
||||||
# vector completely cancels out the rotation, scale, and skew components.
|
# vector completely cancels out the rotation, scale, and skew components.
|
||||||
zero = [0, 0]
|
zero = [0, 0]
|
||||||
simpletransform.applyTransformToPoint(node_transform, zero)
|
zero = inkex.Transform.apply_to_point(node_transform, zero)
|
||||||
translate = Point(*zero)
|
translate = Point(*zero)
|
||||||
|
|
||||||
# Next, see how the transform affects unit vectors in the X and Y axes. We
|
# 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
|
# need to subtract off the translation or it will affect the magnitude of
|
||||||
# the resulting vector, which we don't want.
|
# the resulting vector, which we don't want.
|
||||||
unit_x = [1, 0]
|
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()
|
sx = (Point(*unit_x) - translate).length()
|
||||||
|
|
||||||
unit_y = [0, 1]
|
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()
|
sy = (Point(*unit_y) - translate).length()
|
||||||
|
|
||||||
# Take the average as a best guess.
|
# Take the average as a best guess.
|
||||||
|
@ -223,11 +228,7 @@ class EmbroideryElement(object):
|
||||||
@property
|
@property
|
||||||
@cache
|
@cache
|
||||||
def stroke_width(self):
|
def stroke_width(self):
|
||||||
width = self.get_style("stroke-width", None)
|
width = self.get_style("stroke-width", "1.0")
|
||||||
|
|
||||||
if width is None:
|
|
||||||
return 1.0
|
|
||||||
|
|
||||||
width = convert_length(width)
|
width = convert_length(width)
|
||||||
return width * self.stroke_scale
|
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).
|
# 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?
|
# Tuples all the way down. Hasn't anyone heard of using classes?
|
||||||
|
|
||||||
if self.node.tag in SVG_OBJECT_TAGS:
|
if getattr(self.node, "get_path", None):
|
||||||
if self.node.tag == SVG_RECT_TAG:
|
d = self.node.get_path()
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
d = self.node.get("d", "")
|
d = self.node.get("d", "")
|
||||||
|
|
||||||
if not 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")))
|
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
|
@cache
|
||||||
def parse_path(self):
|
def parse_path(self):
|
||||||
|
@ -315,7 +311,7 @@ class EmbroideryElement(object):
|
||||||
return commands[0]
|
return commands[0]
|
||||||
elif len(commands) > 1:
|
elif len(commands) > 1:
|
||||||
raise ValueError(_("%(id)s has more than one command of type '%(command)s' linked to it") %
|
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:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -326,13 +322,13 @@ class EmbroideryElement(object):
|
||||||
"""approximate a path containing beziers with a series of points"""
|
"""approximate a path containing beziers with a series of points"""
|
||||||
|
|
||||||
path = deepcopy(path)
|
path = deepcopy(path)
|
||||||
cspsubdiv(path, 0.1)
|
bezier.cspsubdiv(path, 0.1)
|
||||||
|
|
||||||
return [self.strip_control_points(subpath) for subpath in path]
|
return [self.strip_control_points(subpath) for subpath in path]
|
||||||
|
|
||||||
def flatten_subpath(self, subpath):
|
def flatten_subpath(self, subpath):
|
||||||
path = [deepcopy(subpath)]
|
path = [deepcopy(subpath)]
|
||||||
cspsubdiv(path, 0.1)
|
bezier.cspsubdiv(path, 0.1)
|
||||||
|
|
||||||
return self.strip_control_points(path[0])
|
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
|
# 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."
|
# "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)
|
error_msg = "%s: %s %s" % (name, _("error:"), message)
|
||||||
print >> sys.stderr, "%s" % (error_msg.encode("UTF-8"))
|
inkex.errormsg(error_msg)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def validation_errors(self):
|
def validation_errors(self):
|
||||||
|
|
|
@ -136,6 +136,12 @@ class Fill(EmbroideryElement):
|
||||||
# biggest path.
|
# biggest path.
|
||||||
paths = self.paths
|
paths = self.paths
|
||||||
paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
|
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:])])
|
polygon = shgeo.MultiPolygon([(paths[0], paths[1:])])
|
||||||
|
|
||||||
# There is a great number of "crossing border" errors on fill shapes
|
# There is a great number of "crossing border" errors on fill shapes
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from simpletransform import applyTransformToPoint
|
|
||||||
|
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..svg import get_node_transform
|
from ..svg.path import get_node_transform
|
||||||
from .element import EmbroideryElement
|
from .element import EmbroideryElement
|
||||||
from .validation import ObjectTypeWarning
|
from .validation import ObjectTypeWarning
|
||||||
|
|
||||||
|
@ -19,13 +17,9 @@ class ImageTypeWarning(ObjectTypeWarning):
|
||||||
class ImageObject(EmbroideryElement):
|
class ImageObject(EmbroideryElement):
|
||||||
|
|
||||||
def center(self):
|
def center(self):
|
||||||
point = [float(self.node.get('x', 0)), float(self.node.get('y', 0))]
|
transform = get_node_transform(self.node.getparent())
|
||||||
point = [(point[0]+(float(self.node.get('width', 0))/2)), (point[1]+(float(self.node.get('height', 0))/2))]
|
center = self.node.bounding_box(transform).center
|
||||||
|
return center
|
||||||
transform = get_node_transform(self.node)
|
|
||||||
applyTransformToPoint(transform, point)
|
|
||||||
|
|
||||||
return point
|
|
||||||
|
|
||||||
def validation_warnings(self):
|
def validation_warnings(self):
|
||||||
yield ImageTypeWarning(self.center())
|
yield ImageTypeWarning(self.center())
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from inkex import Path
|
||||||
from shapely import geometry as shgeo
|
from shapely import geometry as shgeo
|
||||||
|
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
|
@ -36,7 +37,7 @@ class Polyline(EmbroideryElement):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@param('polyline', _('Manual stitch along path'), type='toggle', inverse=True)
|
@param('polyline', _('Manual stitch along path'), type='toggle', inverse=True)
|
||||||
def satin_column(self):
|
def polyline(self):
|
||||||
return self.get_boolean_param("polyline")
|
return self.get_boolean_param("polyline")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -61,8 +62,8 @@ class Polyline(EmbroideryElement):
|
||||||
# svg transforms that is in our superclass, we'll convert the polyline
|
# svg transforms that is in our superclass, we'll convert the polyline
|
||||||
# to a degenerate cubic superpath in which the bezier handles are on
|
# to a degenerate cubic superpath in which the bezier handles are on
|
||||||
# the segment endpoints.
|
# the segment endpoints.
|
||||||
|
path = self.node.get_path()
|
||||||
path = [[[point[:], point[:], point[:]] for point in self.points]]
|
path = Path(path).to_superpath()
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from itertools import chain, izip
|
from itertools import chain
|
||||||
|
|
||||||
import cubicsuperpath
|
from inkex import paths
|
||||||
from shapely import affinity as shaffinity, geometry as shgeo
|
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 ..i18n import _
|
||||||
from ..svg import line_strings_to_csp, point_lists_to_csp
|
from ..svg import line_strings_to_csp, point_lists_to_csp
|
||||||
from ..utils import Point, cache, collapse_duplicate_point, cut
|
from ..utils import Point, cache, collapse_duplicate_point, cut
|
||||||
|
from .element import EmbroideryElement, Patch, param
|
||||||
|
from .validation import ValidationError
|
||||||
|
|
||||||
|
|
||||||
class SatinHasFillError(ValidationError):
|
class SatinHasFillError(ValidationError):
|
||||||
|
@ -234,7 +235,7 @@ class SatinColumn(EmbroideryElement):
|
||||||
rung_endpoints.append(points)
|
rung_endpoints.append(points)
|
||||||
|
|
||||||
rungs = []
|
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
|
# Expand the points just a bit to ensure that shapely thinks they
|
||||||
# intersect with the rails even with floating point inaccuracy.
|
# intersect with the rails even with floating point inaccuracy.
|
||||||
start = Point(*start)
|
start = Point(*start)
|
||||||
|
@ -266,12 +267,12 @@ class SatinColumn(EmbroideryElement):
|
||||||
|
|
||||||
if num_paths <= 2:
|
if num_paths <= 2:
|
||||||
# old-style satin column with no rungs
|
# 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
|
# 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)
|
intersection_counts = [sum(paths[i].intersects(paths[j]) for j in range(num_paths) if i != j)
|
||||||
for i in xrange(num_paths)]
|
for i in range(num_paths)]
|
||||||
paths_not_intersecting_two = [i for i in xrange(num_paths) if intersection_counts[i] != 2]
|
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)
|
num_not_intersecting_two = len(paths_not_intersecting_two)
|
||||||
|
|
||||||
if num_not_intersecting_two == 2:
|
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
|
# 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
|
# than once. Treat it like the previous case and we'll sort out
|
||||||
# the intersection issues later.
|
# 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]
|
return indices_by_length[:2]
|
||||||
|
|
||||||
def _cut_rail(self, rail, rung):
|
def _cut_rail(self, rail, rung):
|
||||||
|
@ -330,7 +331,7 @@ class SatinColumn(EmbroideryElement):
|
||||||
self._cut_rail(rail, rung)
|
self._cut_rail(rail, rung)
|
||||||
|
|
||||||
for rail in rails:
|
for rail in rails:
|
||||||
for i in xrange(len(rail)):
|
for i in range(len(rail)):
|
||||||
if rail[i] is not None:
|
if rail[i] is not None:
|
||||||
rail[i] = [Point(*coord) for coord in rail[i].coords]
|
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
|
# zero-length bezier at the star. The user's goal here is to ignore the
|
||||||
# horizontal section of the right rail.
|
# 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]
|
sections = [s for s in sections if s[0] is not None and s[1] is not None]
|
||||||
|
|
||||||
return sections
|
return sections
|
||||||
|
@ -438,13 +439,13 @@ class SatinColumn(EmbroideryElement):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# like in do_satin()
|
# 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):
|
if isinstance(split_point, float):
|
||||||
index_of_closest_stitch = int(round(len(points) * split_point))
|
index_of_closest_stitch = int(round(len(points) * split_point))
|
||||||
else:
|
else:
|
||||||
split_point = Point(*split_point)
|
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:
|
if index_of_closest_stitch % 2 == 0:
|
||||||
# split point is on the first rail
|
# split point is on the first rail
|
||||||
|
@ -517,7 +518,7 @@ class SatinColumn(EmbroideryElement):
|
||||||
|
|
||||||
def _csp_to_satin(self, csp):
|
def _csp_to_satin(self, csp):
|
||||||
node = deepcopy(self.node)
|
node = deepcopy(self.node)
|
||||||
d = cubicsuperpath.formatPath(csp)
|
d = paths.CubicSuperPath(csp).to_path()
|
||||||
node.set("d", d)
|
node.set("d", d)
|
||||||
|
|
||||||
# we've already applied the transform, so get rid of it
|
# 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
|
# Each iteration of this outer loop is one stitch. Keep going
|
||||||
# until we fall off the end of the section.
|
# 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:
|
while to_travel > 0 and index0 < last_index0 and index1 < last_index1:
|
||||||
# In this loop, we inch along each rail a tiny bit per
|
# 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)
|
pos0, index0 = self.walk(section0, pos0, index0, 0.05)
|
||||||
pos1, index1 = self.walk(section1, pos1, index1, 0.05 * ratio)
|
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)
|
to_travel -= new_center.distance(old_center)
|
||||||
old_center = new_center
|
old_center = new_center
|
||||||
|
|
||||||
|
@ -705,7 +710,7 @@ class SatinColumn(EmbroideryElement):
|
||||||
|
|
||||||
# This fancy bit of iterable magic just repeatedly takes a point
|
# This fancy bit of iterable magic just repeatedly takes a point
|
||||||
# from each side in turn.
|
# 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)
|
patch.add_stitch(point)
|
||||||
|
|
||||||
return patch
|
return patch
|
||||||
|
@ -724,7 +729,7 @@ class SatinColumn(EmbroideryElement):
|
||||||
sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
|
sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
|
||||||
|
|
||||||
# Like in zigzag_underlay(): take a point from each side in turn.
|
# 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)
|
patch.add_stitch(point)
|
||||||
|
|
||||||
return patch
|
return patch
|
||||||
|
@ -743,7 +748,7 @@ class SatinColumn(EmbroideryElement):
|
||||||
|
|
||||||
# "left" and "right" here are kind of arbitrary designations meaning
|
# "left" and "right" here are kind of arbitrary designations meaning
|
||||||
# a point from the first and second rail repectively
|
# 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(left)
|
||||||
patch.add_stitch(right)
|
patch.add_stitch(right)
|
||||||
patch.add_stitch(left)
|
patch.add_stitch(left)
|
||||||
|
|
|
@ -134,9 +134,9 @@ class Stroke(EmbroideryElement):
|
||||||
global warned_about_legacy_running_stitch
|
global warned_about_legacy_running_stitch
|
||||||
if not warned_about_legacy_running_stitch:
|
if not warned_about_legacy_running_stitch:
|
||||||
warned_about_legacy_running_stitch = True
|
warned_about_legacy_running_stitch = True
|
||||||
print >> sys.stderr, _("Legacy running stitch setting detected!\n\nIt looks like you're using a stroke " +
|
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 " +
|
"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.")
|
"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
|
# still allow the deprecated setting to work in order to support old files
|
||||||
return True
|
return True
|
||||||
|
@ -157,7 +157,7 @@ class Stroke(EmbroideryElement):
|
||||||
|
|
||||||
offset = stroke_width / 2.0
|
offset = stroke_width / 2.0
|
||||||
|
|
||||||
for i in xrange(len(patch) - 1):
|
for i in range(len(patch) - 1):
|
||||||
start = patch.stitches[i]
|
start = patch.stitches[i]
|
||||||
end = patch.stitches[i + 1]
|
end = patch.stitches[i + 1]
|
||||||
segment_direction = (end - start).unit()
|
segment_direction = (end - start).unit()
|
||||||
|
@ -174,7 +174,7 @@ class Stroke(EmbroideryElement):
|
||||||
repeated_path = []
|
repeated_path = []
|
||||||
|
|
||||||
# go back and forth along the path as specified by self.repeats
|
# 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:
|
if i % 2 == 1:
|
||||||
# reverse every other pass
|
# reverse every other pass
|
||||||
this_path = path[::-1]
|
this_path = path[::-1]
|
||||||
|
|
|
@ -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
|
|
|
@ -1,9 +1,7 @@
|
||||||
from simpletransform import applyTransformToPoint
|
|
||||||
|
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..svg import get_node_transform
|
|
||||||
from .element import EmbroideryElement
|
from .element import EmbroideryElement
|
||||||
from .validation import ObjectTypeWarning
|
from .validation import ObjectTypeWarning
|
||||||
|
from ..svg.path import get_node_transform
|
||||||
|
|
||||||
|
|
||||||
class TextTypeWarning(ObjectTypeWarning):
|
class TextTypeWarning(ObjectTypeWarning):
|
||||||
|
@ -17,16 +15,14 @@ class TextTypeWarning(ObjectTypeWarning):
|
||||||
|
|
||||||
class TextObject(EmbroideryElement):
|
class TextObject(EmbroideryElement):
|
||||||
|
|
||||||
def center(self):
|
def pointer(self):
|
||||||
point = [float(self.node.get('x', 0)), float(self.node.get('y', 0))]
|
transform = get_node_transform(self.node.getparent())
|
||||||
|
point = self.node.bounding_box(transform).center
|
||||||
transform = get_node_transform(self.node)
|
|
||||||
applyTransformToPoint(transform, point)
|
|
||||||
|
|
||||||
return point
|
return point
|
||||||
|
|
||||||
def validation_warnings(self):
|
def validation_warnings(self):
|
||||||
yield TextTypeWarning(self.center())
|
yield TextTypeWarning(self.pointer())
|
||||||
|
|
||||||
def to_patches(self, last_patch):
|
def to_patches(self, last_patch):
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -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 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,
|
from .auto_satin import AutoSatin
|
||||||
StitchPlanPreview,
|
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,
|
Install,
|
||||||
Params,
|
Params,
|
||||||
Print,
|
Print,
|
||||||
|
@ -37,9 +41,14 @@ __all__ = extensions = [Embroider,
|
||||||
CutSatin,
|
CutSatin,
|
||||||
AutoSatin,
|
AutoSatin,
|
||||||
Lettering,
|
Lettering,
|
||||||
|
LetteringGenerateJson,
|
||||||
|
LetteringRemoveKerning,
|
||||||
|
LetteringCustomFontDir,
|
||||||
Troubleshoot,
|
Troubleshoot,
|
||||||
RemoveEmbroiderySettings,
|
RemoveEmbroiderySettings,
|
||||||
Cleanup,
|
Cleanup,
|
||||||
BreakApart,
|
BreakApart,
|
||||||
ImportThreadlist,
|
ImportThreadlist,
|
||||||
Simulator]
|
Simulator,
|
||||||
|
Reorder,
|
||||||
|
EmbroiderSettings]
|
||||||
|
|
|
@ -14,7 +14,7 @@ class AutoSatin(CommandsExtension):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
CommandsExtension.__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):
|
def get_starting_point(self):
|
||||||
return self.get_point("satin_start")
|
return self.get_point("satin_start")
|
||||||
|
@ -39,7 +39,7 @@ class AutoSatin(CommandsExtension):
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.selected:
|
if not self.svg.selected:
|
||||||
# L10N auto-route satin columns extension
|
# L10N auto-route satin columns extension
|
||||||
inkex.errormsg(_("Please select one or more satin columns."))
|
inkex.errormsg(_("Please select one or more satin columns."))
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from collections import MutableMapping
|
from collections.abc import MutableMapping
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from stringcase import snakecase
|
|
||||||
|
|
||||||
import inkex
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
from stringcase import snakecase
|
||||||
|
|
||||||
from ..commands import is_command, layer_commands
|
from ..commands import is_command, layer_commands
|
||||||
from ..elements import EmbroideryElement, nodes_to_elements
|
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 ..i18n import _
|
||||||
from ..svg import generate_unique_id
|
from ..svg import generate_unique_id
|
||||||
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
|
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
|
||||||
NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG,
|
NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG)
|
||||||
SVG_PATH_TAG)
|
|
||||||
|
|
||||||
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
|
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
|
||||||
|
|
||||||
|
@ -71,7 +69,7 @@ class InkStitchMetadata(MutableMapping):
|
||||||
tag = inkex.addNS(name, "inkstitch")
|
tag = inkex.addNS(name, "inkstitch")
|
||||||
item = self.metadata.find(tag)
|
item = self.metadata.find(tag)
|
||||||
if item is None and create:
|
if item is None and create:
|
||||||
item = inkex.etree.SubElement(self.metadata, tag)
|
item = etree.SubElement(self.metadata, tag)
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@ -117,7 +115,7 @@ class InkstitchExtension(inkex.Effect):
|
||||||
def ensure_current_layer(self):
|
def ensure_current_layer(self):
|
||||||
# if no layer is selected, inkex defaults to the root, which isn't
|
# if no layer is selected, inkex defaults to the root, which isn't
|
||||||
# particularly useful
|
# particularly useful
|
||||||
if self.current_layer is self.document.getroot():
|
if self.svg.get_current_layer() is self.document.getroot():
|
||||||
try:
|
try:
|
||||||
self.current_layer = self.document.xpath(".//svg:g[@inkscape:groupmode='layer']", namespaces=inkex.NSS)[0]
|
self.current_layer = self.document.xpath(".//svg:g[@inkscape:groupmode='layer']", namespaces=inkex.NSS)[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
@ -125,7 +123,7 @@ class InkstitchExtension(inkex.Effect):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def no_elements_error(self):
|
def no_elements_error(self):
|
||||||
if self.selected:
|
if self.svg.selected:
|
||||||
# l10n This was previously: "No embroiderable paths 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")
|
inkex.errormsg(_("Ink/Stitch doesn't know how to work with any of the objects you've selected.") + "\n")
|
||||||
else:
|
else:
|
||||||
|
@ -154,8 +152,8 @@ class InkstitchExtension(inkex.Effect):
|
||||||
if is_command(node) or node.get(CONNECTOR_TYPE):
|
if is_command(node) or node.get(CONNECTOR_TYPE):
|
||||||
return[]
|
return[]
|
||||||
|
|
||||||
if self.selected:
|
if self.svg.selected:
|
||||||
if node.get("id") in self.selected:
|
if node.get("id") in self.svg.selected:
|
||||||
selected = True
|
selected = True
|
||||||
else:
|
else:
|
||||||
# if the user didn't select anything that means we process everything
|
# 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))
|
nodes.extend(self.descendants(child, selected, troubleshoot))
|
||||||
|
|
||||||
if selected:
|
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)
|
nodes.append(node)
|
||||||
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.tag in EMBROIDERABLE_TAGS or is_clone(node)):
|
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.tag in EMBROIDERABLE_TAGS or is_clone(node)):
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
|
@ -206,24 +204,3 @@ class InkstitchExtension(inkex.Effect):
|
||||||
def uniqueId(self, prefix, make_new_id=True):
|
def uniqueId(self, prefix, make_new_id=True):
|
||||||
"""Override inkex.Effect.uniqueId with a nicer naming scheme."""
|
"""Override inkex.Effect.uniqueId with a nicer naming scheme."""
|
||||||
return generate_unique_id(self.document, prefix)
|
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)
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import logging
|
import logging
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
|
import inkex
|
||||||
from shapely.geometry import LineString, MultiPolygon, Polygon
|
from shapely.geometry import LineString, MultiPolygon, Polygon
|
||||||
from shapely.ops import polygonize, unary_union
|
from shapely.ops import polygonize, unary_union
|
||||||
|
|
||||||
import inkex
|
|
||||||
|
|
||||||
from ..elements import EmbroideryElement
|
from ..elements import EmbroideryElement
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..svg import get_correction_transform
|
from ..svg import get_correction_transform
|
||||||
|
@ -19,10 +18,11 @@ class BreakApart(InkstitchExtension):
|
||||||
'''
|
'''
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
InkstitchExtension.__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
|
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."))
|
inkex.errormsg(_("Please select one or more fill areas to break apart."))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -41,13 +41,12 @@ class BreakApart(InkstitchExtension):
|
||||||
try:
|
try:
|
||||||
paths.sort(key=lambda point_list: Polygon(point_list).area, reverse=True)
|
paths.sort(key=lambda point_list: Polygon(point_list).area, reverse=True)
|
||||||
polygon = MultiPolygon([(paths[0], paths[1:])])
|
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
|
continue
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
polygons = self.break_apart_paths(paths)
|
polygons = self.break_apart_paths(paths)
|
||||||
polygons = self.ensure_minimum_size(polygons, 5)
|
|
||||||
if self.options.method == 1:
|
if self.options.method == 1:
|
||||||
polygons = self.combine_overlapping_polygons(polygons)
|
polygons = self.combine_overlapping_polygons(polygons)
|
||||||
polygons = self.recombine_polygons(polygons)
|
polygons = self.recombine_polygons(polygons)
|
||||||
|
@ -106,6 +105,7 @@ class BreakApart(InkstitchExtension):
|
||||||
polygons.sort(key=lambda polygon: polygon.area, reverse=True)
|
polygons.sort(key=lambda polygon: polygon.area, reverse=True)
|
||||||
multipolygons = []
|
multipolygons = []
|
||||||
holes = []
|
holes = []
|
||||||
|
self.ensure_minimum_size(polygons, self.minimum_size)
|
||||||
for polygon in polygons:
|
for polygon in polygons:
|
||||||
if polygon in holes:
|
if polygon in holes:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import sys
|
from inkex import NSS, Boolean, errormsg
|
||||||
|
|
||||||
from inkex import NSS
|
|
||||||
|
|
||||||
from ..elements import Fill, Stroke
|
from ..elements import Fill, Stroke
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
|
@ -10,10 +8,10 @@ from .base import InkstitchExtension
|
||||||
class Cleanup(InkstitchExtension):
|
class Cleanup(InkstitchExtension):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||||
self.OptionParser.add_option("-f", "--rm_fill", dest="rm_fill", type="inkbool", default=True)
|
self.arg_parser.add_argument("-f", "--rm_fill", dest="rm_fill", type=Boolean, default=True)
|
||||||
self.OptionParser.add_option("-s", "--rm_stroke", dest="rm_stroke", type="inkbool", default=True)
|
self.arg_parser.add_argument("-s", "--rm_stroke", dest="rm_stroke", type=Boolean, default=True)
|
||||||
self.OptionParser.add_option("-a", "--fill_threshold", dest="fill_threshold", type="int", default=20)
|
self.arg_parser.add_argument("-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("-l", "--stroke_threshold", dest="stroke_threshold", type=int, default=5)
|
||||||
|
|
||||||
def effect(self):
|
def effect(self):
|
||||||
self.rm_fill = self.options.rm_fill
|
self.rm_fill = self.options.rm_fill
|
||||||
|
@ -21,8 +19,7 @@ class Cleanup(InkstitchExtension):
|
||||||
self.fill_threshold = self.options.fill_threshold
|
self.fill_threshold = self.options.fill_threshold
|
||||||
self.stroke_threshold = self.options.stroke_threshold
|
self.stroke_threshold = self.options.stroke_threshold
|
||||||
|
|
||||||
# Remove selection, we want every element in the document
|
self.svg.selected.clear()
|
||||||
self.selected = {}
|
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
svg = self.document.getroot()
|
svg = self.document.getroot()
|
||||||
|
@ -32,7 +29,7 @@ class Cleanup(InkstitchExtension):
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
print >> sys.stderr, _("%s elements removed" % count)
|
errormsg(_("%s elements removed" % count))
|
||||||
return
|
return
|
||||||
|
|
||||||
for element in self.elements:
|
for element in self.elements:
|
||||||
|
@ -44,4 +41,4 @@ class Cleanup(InkstitchExtension):
|
||||||
element.node.getparent().remove(element.node)
|
element.node.getparent().remove(element.node)
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
print >> sys.stderr, _("%s elements removed" % count)
|
errormsg(_("%s elements removed" % count))
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from inkex import Boolean
|
||||||
|
|
||||||
from .base import InkstitchExtension
|
from .base import InkstitchExtension
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,4 +9,4 @@ class CommandsExtension(InkstitchExtension):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||||
for command in self.COMMANDS:
|
for command in self.COMMANDS:
|
||||||
self.OptionParser.add_option("--%s" % command, type="inkbool")
|
self.arg_parser.add_argument("--%s" % command, type=Boolean)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import math
|
import math
|
||||||
|
import sys
|
||||||
from itertools import chain, groupby
|
from itertools import chain, groupby
|
||||||
|
|
||||||
|
import inkex
|
||||||
import numpy
|
import numpy
|
||||||
|
from lxml import etree
|
||||||
from numpy import diff, setdiff1d, sign
|
from numpy import diff, setdiff1d, sign
|
||||||
from shapely import geometry as shgeo
|
from shapely import geometry as shgeo
|
||||||
|
|
||||||
import inkex
|
|
||||||
|
|
||||||
from ..elements import Stroke
|
from ..elements import Stroke
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..svg import PIXELS_PER_MM, get_correction_transform
|
from ..svg import PIXELS_PER_MM, get_correction_transform
|
||||||
|
@ -26,7 +27,7 @@ class ConvertToSatin(InkstitchExtension):
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.selected:
|
if not self.svg.selected:
|
||||||
inkex.errormsg(_("Please select at least one line to convert to a satin column."))
|
inkex.errormsg(_("Please select at least one line to convert to a satin column."))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -120,8 +121,15 @@ class ConvertToSatin(InkstitchExtension):
|
||||||
|
|
||||||
path = shgeo.LineString(path)
|
path = shgeo.LineString(path)
|
||||||
|
|
||||||
left_rail = path.parallel_offset(stroke_width / 2.0, 'left', **style_args)
|
try:
|
||||||
right_rail = path.parallel_offset(stroke_width / 2.0, 'right', **style_args)
|
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 \
|
if not isinstance(left_rail, shgeo.LineString) or \
|
||||||
not isinstance(right_rail, shgeo.LineString):
|
not isinstance(right_rail, shgeo.LineString):
|
||||||
|
@ -304,12 +312,11 @@ class ConvertToSatin(InkstitchExtension):
|
||||||
d += "%s,%s " % (x, y)
|
d += "%s,%s " % (x, y)
|
||||||
d += " "
|
d += " "
|
||||||
|
|
||||||
return inkex.etree.Element(SVG_PATH_TAG,
|
return etree.Element(SVG_PATH_TAG,
|
||||||
{
|
{
|
||||||
"id": self.uniqueId("path"),
|
"id": self.uniqueId("path"),
|
||||||
"style": path_style,
|
"style": path_style,
|
||||||
"transform": correction_transform,
|
"transform": correction_transform,
|
||||||
"d": d,
|
"d": d,
|
||||||
INKSTITCH_ATTRIBS['satin_column']: "true",
|
INKSTITCH_ATTRIBS['satin_column']: "true",
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import inkex
|
import inkex
|
||||||
|
|
||||||
from .base import InkstitchExtension
|
|
||||||
from ..i18n import _
|
|
||||||
from ..elements import SatinColumn
|
from ..elements import SatinColumn
|
||||||
|
from ..i18n import _
|
||||||
from ..svg import get_correction_transform
|
from ..svg import get_correction_transform
|
||||||
|
from .base import InkstitchExtension
|
||||||
|
|
||||||
|
|
||||||
class CutSatin(InkstitchExtension):
|
class CutSatin(InkstitchExtension):
|
||||||
|
@ -11,7 +11,7 @@ class CutSatin(InkstitchExtension):
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.selected:
|
if not self.svg.selected:
|
||||||
inkex.errormsg(_("Please select one or more satin columns to cut."))
|
inkex.errormsg(_("Please select one or more satin columns to cut."))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
|
@ -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
|
|
@ -1,9 +1,8 @@
|
||||||
import inkex
|
import inkex
|
||||||
import cubicsuperpath
|
|
||||||
|
|
||||||
from .base import InkstitchExtension
|
|
||||||
from ..i18n import _
|
|
||||||
from ..elements import SatinColumn
|
from ..elements import SatinColumn
|
||||||
|
from ..i18n import _
|
||||||
|
from .base import InkstitchExtension
|
||||||
|
|
||||||
|
|
||||||
class Flip(InkstitchExtension):
|
class Flip(InkstitchExtension):
|
||||||
|
@ -14,13 +13,13 @@ class Flip(InkstitchExtension):
|
||||||
first, second = satin.rail_indices
|
first, second = satin.rail_indices
|
||||||
csp[first], csp[second] = csp[second], csp[first]
|
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):
|
def effect(self):
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.selected:
|
if not self.svg.selected:
|
||||||
inkex.errormsg(_("Please select one or more satin columns to flip."))
|
inkex.errormsg(_("Please select one or more satin columns to flip."))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -12,20 +12,23 @@ from .base import InkstitchExtension
|
||||||
class ImportThreadlist(InkstitchExtension):
|
class ImportThreadlist(InkstitchExtension):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||||
self.OptionParser.add_option("-f", "--filepath", type="str", default="", dest="filepath")
|
self.arg_parser.add_argument("-f", "--filepath", type=str, default="", dest="filepath")
|
||||||
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.OptionParser.add_option("-t", "--palette", type="str", default=None, dest="palette")
|
self.arg_parser.add_argument("-t", "--palette", type=str, default=None, dest="palette")
|
||||||
|
|
||||||
def effect(self):
|
def effect(self):
|
||||||
# Remove selection, we want all the elements in the document
|
# Remove selection, we want all the elements in the document
|
||||||
self.selected = {}
|
self.svg.selected.clear()
|
||||||
|
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
path = self.options.filepath
|
path = self.options.filepath
|
||||||
if not os.path.exists(path):
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
method = self.options.method
|
method = self.options.method
|
||||||
|
@ -35,11 +38,11 @@ class ImportThreadlist(InkstitchExtension):
|
||||||
colors = self.parse_threadlist_by_catalog_number(path)
|
colors = self.parse_threadlist_by_catalog_number(path)
|
||||||
|
|
||||||
if all(c is None for c in colors):
|
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:
|
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:
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
# Iterate through the color blocks to apply colors
|
# Iterate through the color blocks to apply colors
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import os
|
import os
|
||||||
import pyembroidery
|
|
||||||
|
|
||||||
from inkex import etree
|
|
||||||
import inkex
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
import pyembroidery
|
||||||
|
|
||||||
from ..stitch_plan import StitchPlan
|
from ..stitch_plan import StitchPlan
|
||||||
from ..svg import PIXELS_PER_MM, render_stitch_plan
|
from ..svg import PIXELS_PER_MM, render_stitch_plan
|
||||||
|
@ -10,7 +11,7 @@ from ..svg.tags import INKSCAPE_LABEL
|
||||||
|
|
||||||
|
|
||||||
class Input(object):
|
class Input(object):
|
||||||
def affect(self, args):
|
def run(self, args):
|
||||||
embroidery_file = args[0]
|
embroidery_file = args[0]
|
||||||
pattern = pyembroidery.read(embroidery_file)
|
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
|
# rename the Stitch Plan layer so that it doesn't get overwritten by Embroider
|
||||||
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
|
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')
|
layer.attrib.pop('id')
|
||||||
|
|
||||||
# Shift the design so that its origin is at the center of the canvas
|
# 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!
|
# Note: this is NOT the same as centering the design in the canvas!
|
||||||
layer.set('transform', 'translate(%s,%s)' % (extents[0], extents[1]))
|
layer.set('transform', 'translate(%s,%s)' % (extents[0], extents[1]))
|
||||||
|
|
||||||
print etree.tostring(svg)
|
print(etree.tostring(svg).decode('utf-8'))
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import inkex
|
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 ..i18n import _
|
||||||
from ..svg import get_correction_transform
|
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
|
from .commands import CommandsExtension
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,20 +18,19 @@ class LayerCommands(CommandsExtension):
|
||||||
inkex.errormsg(_("Please choose one or more commands to add."))
|
inkex.errormsg(_("Please choose one or more commands to add."))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.ensure_current_layer()
|
correction_transform = get_correction_transform(self.svg.get_current_layer(), child=True)
|
||||||
correction_transform = get_correction_transform(self.current_layer, child=True)
|
|
||||||
|
|
||||||
for i, command in enumerate(commands):
|
for i, command in enumerate(commands):
|
||||||
ensure_symbol(self.document, command)
|
ensure_symbol(self.document, command)
|
||||||
|
|
||||||
inkex.etree.SubElement(self.current_layer, SVG_USE_TAG,
|
etree.SubElement(self.svg.get_current_layer(), SVG_USE_TAG,
|
||||||
{
|
{
|
||||||
"id": self.uniqueId("use"),
|
"id": self.uniqueId("use"),
|
||||||
INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
|
INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
|
||||||
XLINK_HREF: "#inkstitch_%s" % command,
|
XLINK_HREF: "#inkstitch_%s" % command,
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"x": str(i * 20),
|
"x": str(i * 20),
|
||||||
"y": "-10",
|
"y": "-10",
|
||||||
"transform": correction_transform
|
"transform": correction_transform
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from base64 import b64decode, b64encode
|
|
||||||
|
|
||||||
import appdirs
|
import appdirs
|
||||||
import inkex
|
import inkex
|
||||||
import wx
|
import wx
|
||||||
import wx.adv
|
import wx.adv
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
from ..elements import nodes_to_elements
|
from ..elements import nodes_to_elements
|
||||||
from ..gui import PresetsPanel, SimulatorPreview, info_dialog
|
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)
|
SVG_PATH_TAG)
|
||||||
from ..utils import DotDict, cache, get_bundled_dir
|
from ..utils import DotDict, cache, get_bundled_dir
|
||||||
from .commands import CommandsExtension
|
from .commands import CommandsExtension
|
||||||
|
from .lettering_custom_font_dir import get_custom_font_dir
|
||||||
|
|
||||||
|
|
||||||
class LetteringFrame(wx.Frame):
|
class LetteringFrame(wx.Frame):
|
||||||
|
@ -45,6 +44,7 @@ class LetteringFrame(wx.Frame):
|
||||||
|
|
||||||
# font details
|
# font details
|
||||||
self.font_description = wx.StaticText(self, wx.ID_ANY)
|
self.font_description = wx.StaticText(self, wx.ID_ANY)
|
||||||
|
self.Bind(wx.EVT_SIZE, self.resize)
|
||||||
|
|
||||||
# options
|
# options
|
||||||
self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("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"""
|
"""Load the settings saved into the SVG group element"""
|
||||||
|
|
||||||
self.settings = DotDict({
|
self.settings = DotDict({
|
||||||
"text": u"",
|
"text": "",
|
||||||
"back_and_forth": False,
|
"back_and_forth": False,
|
||||||
"font": None,
|
"font": None,
|
||||||
"scale": 100
|
"scale": 100
|
||||||
|
@ -88,7 +88,7 @@ class LetteringFrame(wx.Frame):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if INKSTITCH_LETTERING in self.group.attrib:
|
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
|
return
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
@ -103,22 +103,14 @@ class LetteringFrame(wx.Frame):
|
||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self):
|
||||||
"""Save the settings into the SVG group element."""
|
"""Save the settings into the SVG group element."""
|
||||||
|
self.group.set(INKSTITCH_LETTERING, json.dumps(self.settings))
|
||||||
# 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 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)))
|
|
||||||
|
|
||||||
def update_font_list(self):
|
def update_font_list(self):
|
||||||
font_paths = {
|
font_paths = {
|
||||||
get_bundled_dir("fonts"),
|
get_bundled_dir("fonts"),
|
||||||
os.path.expanduser("~/.inkstitch/fonts"),
|
os.path.expanduser("~/.inkstitch/fonts"),
|
||||||
os.path.join(appdirs.user_config_dir('inkstitch'), 'fonts'),
|
os.path.join(appdirs.user_config_dir('inkstitch'), 'fonts'),
|
||||||
|
get_custom_font_dir()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fonts = {}
|
self.fonts = {}
|
||||||
|
@ -130,13 +122,12 @@ class LetteringFrame(wx.Frame):
|
||||||
except OSError:
|
except OSError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
for font_dir in font_dirs:
|
||||||
for font_dir in font_dirs:
|
font = Font(os.path.join(font_path, font_dir))
|
||||||
font = Font(os.path.join(font_path, font_dir))
|
if font.name == "" or font.id == "":
|
||||||
self.fonts[font.name] = font
|
continue
|
||||||
self.fonts_by_id[font.id] = font
|
self.fonts[font.name] = font
|
||||||
except FontError:
|
self.fonts_by_id[font.id] = font
|
||||||
pass
|
|
||||||
|
|
||||||
if len(self.fonts) == 0:
|
if len(self.fonts) == 0:
|
||||||
info_dialog(self, _("Unable to find any fonts! Please try reinstalling Ink/Stitch."))
|
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)
|
self.font_chooser.Append(font.name)
|
||||||
|
|
||||||
def get_font_names(self):
|
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()
|
font_names.sort()
|
||||||
|
|
||||||
return font_names
|
return font_names
|
||||||
|
|
||||||
def get_font_descriptions(self):
|
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):
|
def set_initial_font(self, font_id):
|
||||||
if font_id:
|
if font_id:
|
||||||
|
@ -191,7 +182,7 @@ class LetteringFrame(wx.Frame):
|
||||||
try:
|
try:
|
||||||
return self.fonts_by_id[self.DEFAULT_FONT]
|
return self.fonts_by_id[self.DEFAULT_FONT]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return self.fonts.values()[0]
|
return list(self.fonts.values())[0]
|
||||||
|
|
||||||
def on_change(self, attribute, event):
|
def on_change(self, attribute, event):
|
||||||
self.settings[attribute] = event.GetEventObject().GetValue()
|
self.settings[attribute] = event.GetEventObject().GetValue()
|
||||||
|
@ -202,7 +193,11 @@ class LetteringFrame(wx.Frame):
|
||||||
self.settings.font = font.id
|
self.settings.font = font.id
|
||||||
self.scale_spinner.SetRange(int(font.min_scale * 100), int(font.max_scale * 100))
|
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
|
# Update font description
|
||||||
color = (0, 0, 0)
|
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.')
|
description = _('This font has no available font variant. Please update or remove the font.')
|
||||||
self.font_description.SetLabel(description)
|
self.font_description.SetLabel(description)
|
||||||
self.font_description.SetForegroundColour(color)
|
self.font_description.SetForegroundColour(color)
|
||||||
self.font_description.Wrap(self.GetSize().width - 20)
|
self.font_description.Wrap(self.GetSize().width - 35)
|
||||||
|
|
||||||
if font.reversible:
|
if font.reversible:
|
||||||
self.back_and_forth_checkbox.Enable()
|
self.back_and_forth_checkbox.Enable()
|
||||||
|
@ -230,18 +225,24 @@ class LetteringFrame(wx.Frame):
|
||||||
self.trim_checkbox.SetValue(False)
|
self.trim_checkbox.SetValue(False)
|
||||||
|
|
||||||
self.update_preview()
|
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):
|
def update_preview(self, event=None):
|
||||||
self.preview.update()
|
self.preview.update()
|
||||||
|
|
||||||
def update_lettering(self):
|
def update_lettering(self, raise_error=False):
|
||||||
del self.group[:]
|
del self.group[:]
|
||||||
|
|
||||||
if self.settings.scale == 100:
|
if self.settings.scale == 100:
|
||||||
destination_group = self.group
|
destination_group = self.group
|
||||||
else:
|
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
|
# L10N The user has chosen to scale the text by some percentage
|
||||||
# (50%, 200%, etc). If you need to use the percentage symbol,
|
# (50%, 200%, etc). If you need to use the percentage symbol,
|
||||||
# make sure to double it (%%).
|
# 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 = 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:
|
if self.settings.scale != 100:
|
||||||
destination_group.attrib['transform'] = 'scale(%s)' % (self.settings.scale / 100.0)
|
destination_group.attrib['transform'] = 'scale(%s)' % (self.settings.scale / 100.0)
|
||||||
|
@ -295,7 +303,7 @@ class LetteringFrame(wx.Frame):
|
||||||
|
|
||||||
def apply(self, event):
|
def apply(self, event):
|
||||||
self.preview.disable()
|
self.preview.disable()
|
||||||
self.update_lettering()
|
self.update_lettering(True)
|
||||||
self.save_settings()
|
self.save_settings()
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
@ -369,10 +377,10 @@ class Lettering(CommandsExtension):
|
||||||
self.cancelled = True
|
self.cancelled = True
|
||||||
|
|
||||||
def get_or_create_group(self):
|
def get_or_create_group(self):
|
||||||
if self.selected:
|
if self.svg.selected:
|
||||||
groups = set()
|
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:
|
if node.tag == SVG_GROUP_TAG and INKSTITCH_LETTERING in node.attrib:
|
||||||
groups.add(node)
|
groups.add(node)
|
||||||
|
|
||||||
|
@ -391,9 +399,9 @@ class Lettering(CommandsExtension):
|
||||||
return list(groups)[0]
|
return list(groups)[0]
|
||||||
else:
|
else:
|
||||||
self.ensure_current_layer()
|
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"),
|
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):
|
def effect(self):
|
||||||
|
@ -405,7 +413,7 @@ class Lettering(CommandsExtension):
|
||||||
display = wx.Display(current_screen)
|
display = wx.Display(current_screen)
|
||||||
display_size = display.GetClientArea()
|
display_size = display.GetClientArea()
|
||||||
frame_size = frame.GetSize()
|
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()
|
frame.Show()
|
||||||
app.MainLoop()
|
app.MainLoop()
|
||||||
|
|
|
@ -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 ""
|
|
@ -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)
|
|
@ -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()
|
|
@ -12,7 +12,7 @@ class ObjectCommands(CommandsExtension):
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.selected:
|
if not self.svg.selected:
|
||||||
inkex.errormsg(_("Please select one or more objects to which to attach commands."))
|
inkex.errormsg(_("Please select one or more objects to which to attach commands."))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Output(InkstitchExtension):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
InkstitchExtension.__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
|
# 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
|
# that may be passed for a given output format, so we'll just parse the
|
||||||
# args ourselves. :P
|
# args ourselves. :P
|
||||||
|
@ -39,14 +39,16 @@ class Output(InkstitchExtension):
|
||||||
self.file_extension = self.settings.pop('format')
|
self.file_extension = self.settings.pop('format')
|
||||||
del sys.argv[1:]
|
del sys.argv[1:]
|
||||||
|
|
||||||
InkstitchExtension.getoptions(self, extra_args)
|
InkstitchExtension.parse_arguments(self, extra_args)
|
||||||
|
|
||||||
def effect(self):
|
def effect(self):
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.metadata = self.get_inkstitch_metadata()
|
||||||
|
collapse_len = self.metadata['collapse_len_mm']
|
||||||
patches = self.elements_to_patches(self.elements)
|
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)
|
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
|
# inkscape will read the file contents from stdout and copy
|
||||||
# to the destination file that the user chose
|
# to the destination file that the user chose
|
||||||
with open(temp_file.name, "rb") as output_file:
|
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()
|
sys.stdout.flush()
|
||||||
|
|
||||||
# clean up the temp file
|
# clean up the temp file
|
||||||
|
|
|
@ -151,7 +151,7 @@ class ParamsTab(ScrolledPanel):
|
||||||
# because they're grayed out anyway.
|
# because they're grayed out anyway.
|
||||||
return values
|
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:
|
if input in self.changed_inputs and input != self.toggle_checkbox:
|
||||||
values[name] = input.GetValue()
|
values[name] = input.GetValue()
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ class ParamsTab(ScrolledPanel):
|
||||||
values = self.get_values()
|
values = self.get_values()
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
# print >> sys.stderr, "apply: ", self.name, node.id, values
|
# 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)
|
node.set_param(name, value)
|
||||||
|
|
||||||
def on_change(self, callable):
|
def on_change(self, callable):
|
||||||
|
@ -181,7 +181,7 @@ class ParamsTab(ScrolledPanel):
|
||||||
def load_preset(self, preset):
|
def load_preset(self, preset):
|
||||||
preset_data = preset.get(self.name, {})
|
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:
|
if name in self.param_inputs:
|
||||||
self.param_inputs[name].SetValue(value)
|
self.param_inputs[name].SetValue(value)
|
||||||
self.changed_inputs.add(self.param_inputs[name])
|
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)
|
description = _("These settings will be applied to %d objects.") % len(self.nodes)
|
||||||
|
|
||||||
if any(len(param.values) > 1 for param in self.params):
|
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 self.dependent_tabs:
|
||||||
if len(self.dependent_tabs) == 1:
|
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:
|
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:
|
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
|
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(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.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)
|
box.Add(self.settings_grid, proportion=1, flag=wx.ALL, border=10)
|
||||||
self.SetSizer(box)
|
self.SetSizer(box)
|
||||||
|
@ -443,9 +443,9 @@ class SettingsFrame(wx.Frame):
|
||||||
self.notebook.AddPage(tab, tab.name)
|
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.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_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.cancel_button, 0, wx.RIGHT, 5)
|
||||||
sizer_3.Add(self.use_last_button, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, 5)
|
sizer_3.Add(self.use_last_button, 0, wx.RIGHT | wx.BOTTOM, 5)
|
||||||
sizer_3.Add(self.apply_button, 0, wx.ALIGN_RIGHT | 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)
|
sizer_1.Add(sizer_3, 0, wx.ALIGN_RIGHT, 0)
|
||||||
self.SetSizer(sizer_1)
|
self.SetSizer(sizer_1)
|
||||||
sizer_1.Fit(self)
|
sizer_1.Fit(self)
|
||||||
|
@ -491,7 +491,7 @@ class Params(InkstitchExtension):
|
||||||
element.order = z
|
element.order = z
|
||||||
nodes_by_class[cls].append(element)
|
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):
|
def get_values(self, param, nodes):
|
||||||
getter = 'get_param'
|
getter = 'get_param'
|
||||||
|
@ -501,17 +501,16 @@ class Params(InkstitchExtension):
|
||||||
else:
|
else:
|
||||||
getter = 'get_param'
|
getter = 'get_param'
|
||||||
|
|
||||||
values = filter(lambda item: item is not None,
|
values = [item for item in (getattr(node, getter)(param.name, param.default) for node in nodes) if item is not None]
|
||||||
(getattr(node, getter)(param.name, param.default) for node in nodes))
|
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def group_params(self, params):
|
def group_params(self, params):
|
||||||
def by_group_and_sort_index(param):
|
def by_group_and_sort_index(param):
|
||||||
return param.group, param.sort_index
|
return param.group or "", param.sort_index
|
||||||
|
|
||||||
def by_group(param):
|
def by_group(param):
|
||||||
return param.group
|
return param.group or ""
|
||||||
|
|
||||||
return groupby(sorted(params, key=by_group_and_sort_index), by_group)
|
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
|
# If multiple tabs are enabled, make sure dependent
|
||||||
# tabs are grouped with the parent.
|
# tabs are grouped with the parent.
|
||||||
parent,
|
parent and parent.name,
|
||||||
|
|
||||||
# Within parent/dependents, put the parent first.
|
# Within parent/dependents, put the parent first.
|
||||||
tab == parent
|
tab == parent
|
||||||
|
@ -565,12 +564,13 @@ class Params(InkstitchExtension):
|
||||||
|
|
||||||
parent_tab = None
|
parent_tab = None
|
||||||
new_tabs = []
|
new_tabs = []
|
||||||
|
|
||||||
for group, params in self.group_params(params):
|
for group, params in self.group_params(params):
|
||||||
tab_name = group or cls.element_name
|
tab_name = group or cls.element_name
|
||||||
tab = ParamsTab(parent, id=wx.ID_ANY, name=tab_name, params=list(params), nodes=nodes)
|
tab = ParamsTab(parent, id=wx.ID_ANY, name=tab_name, params=list(params), nodes=nodes)
|
||||||
new_tabs.append(tab)
|
new_tabs.append(tab)
|
||||||
|
|
||||||
if group is None:
|
if group == "":
|
||||||
parent_tab = tab
|
parent_tab = tab
|
||||||
|
|
||||||
self.assign_parents(new_tabs, parent_tab)
|
self.assign_parents(new_tabs, parent_tab)
|
||||||
|
@ -594,7 +594,7 @@ class Params(InkstitchExtension):
|
||||||
display = wx.Display(current_screen)
|
display = wx.Display(current_screen)
|
||||||
display_size = display.GetClientArea()
|
display_size = display.GetClientArea()
|
||||||
frame_size = frame.GetSize()
|
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()
|
frame.Show()
|
||||||
app.MainLoop()
|
app.MainLoop()
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
from copy import deepcopy
|
|
||||||
from datetime import date
|
|
||||||
import errno
|
import errno
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
from threading import Thread
|
|
||||||
import time
|
import time
|
||||||
|
from copy import deepcopy
|
||||||
|
from datetime import date
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
import appdirs
|
import appdirs
|
||||||
from flask import Flask, request, Response, send_from_directory, jsonify
|
|
||||||
import inkex
|
|
||||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|
||||||
import requests
|
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 ..gui import open_url
|
||||||
from ..i18n import translation as inkstitch_translation
|
from ..i18n import translation as inkstitch_translation
|
||||||
|
@ -72,6 +72,11 @@ class PrintPreviewServer(Thread):
|
||||||
|
|
||||||
def __setup_app(self): # noqa: C901
|
def __setup_app(self): # noqa: C901
|
||||||
self.__set_resources_path()
|
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 = Flask(__name__)
|
||||||
|
|
||||||
@self.app.route('/')
|
@self.app.route('/')
|
||||||
|
@ -211,13 +216,21 @@ class Print(InkstitchExtension):
|
||||||
layers = svg.findall("./g[@%s='layer']" % INKSCAPE_GROUPMODE)
|
layers = svg.findall("./g[@%s='layer']" % INKSCAPE_GROUPMODE)
|
||||||
stitch_plan_layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
|
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
|
# First, delete all of the other layers. We don't need them and they'll
|
||||||
# just bulk up the SVG.
|
# just bulk up the SVG.
|
||||||
for layer in layers:
|
for layer in layers:
|
||||||
if layer is not stitch_plan_layer:
|
if layer is not stitch_plan_layer:
|
||||||
svg.remove(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_groups = stitch_plan_layer.getchildren()
|
||||||
color_block_svgs = []
|
color_block_svgs = []
|
||||||
|
|
||||||
|
@ -229,7 +242,7 @@ class Print(InkstitchExtension):
|
||||||
stitch_plan_layer.append(group)
|
stitch_plan_layer.append(group)
|
||||||
|
|
||||||
# save an SVG preview
|
# 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
|
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.
|
# 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
|
# If they really wanted to print just a few objects, they could set
|
||||||
# the rest invisible temporarily.
|
# the rest invisible temporarily.
|
||||||
self.selected = {}
|
self.svg.selected.clear()
|
||||||
|
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.metadata = self.get_inkstitch_metadata()
|
||||||
|
collapse_len = self.metadata['collapse_len_mm']
|
||||||
patches = self.elements_to_patches(self.elements)
|
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'])
|
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)
|
overview_svg, color_block_svgs = self.render_svgs(stitch_plan, realistic=False)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import inkex
|
from inkex import NSS, Boolean
|
||||||
|
|
||||||
from ..commands import find_commands
|
from ..commands import find_commands
|
||||||
from ..svg.svg import find_elements
|
from ..svg.svg import find_elements
|
||||||
|
@ -8,9 +8,9 @@ from .base import InkstitchExtension
|
||||||
class RemoveEmbroiderySettings(InkstitchExtension):
|
class RemoveEmbroiderySettings(InkstitchExtension):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||||
self.OptionParser.add_option("-p", "--del_params", dest="del_params", type="inkbool", default=True)
|
self.arg_parser.add_argument("-p", "--del_params", dest="del_params", type=Boolean, default=True)
|
||||||
self.OptionParser.add_option("-c", "--del_commands", dest="del_commands", type="inkbool", default=False)
|
self.arg_parser.add_argument("-c", "--del_commands", dest="del_commands", type=Boolean, default=False)
|
||||||
self.OptionParser.add_option("-d", "--del_print", dest="del_print", type="inkbool", default=False)
|
self.arg_parser.add_argument("-d", "--del_print", dest="del_print", type=Boolean, default=False)
|
||||||
|
|
||||||
def effect(self):
|
def effect(self):
|
||||||
self.svg = self.document.getroot()
|
self.svg = self.document.getroot()
|
||||||
|
@ -30,24 +30,24 @@ class RemoveEmbroiderySettings(InkstitchExtension):
|
||||||
self.remove_element(print_setting)
|
self.remove_element(print_setting)
|
||||||
|
|
||||||
def remove_params(self):
|
def remove_params(self):
|
||||||
if not self.selected:
|
if not self.svg.selected:
|
||||||
xpath = ".//svg:path"
|
xpath = ".//svg:path"
|
||||||
elements = find_elements(self.svg, xpath)
|
elements = find_elements(self.svg, xpath)
|
||||||
self.remove_inkstitch_attributes(elements)
|
self.remove_inkstitch_attributes(elements)
|
||||||
else:
|
else:
|
||||||
for node in self.selected:
|
for node in self.svg.selected:
|
||||||
elements = self.get_selected_elements(node)
|
elements = self.get_selected_elements(node)
|
||||||
self.remove_inkstitch_attributes(elements)
|
self.remove_inkstitch_attributes(elements)
|
||||||
|
|
||||||
def remove_commands(self):
|
def remove_commands(self):
|
||||||
if not self.selected:
|
if not self.svg.selected:
|
||||||
# we are not able to grab commands by a specific id
|
# 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
|
# so let's move through every object instead and see if it has a command
|
||||||
xpath = ".//svg:path|.//svg:circle|.//svg:rect|.//svg:ellipse"
|
xpath = ".//svg:path|.//svg:circle|.//svg:rect|.//svg:ellipse"
|
||||||
elements = find_elements(self.svg, xpath)
|
elements = find_elements(self.svg, xpath)
|
||||||
else:
|
else:
|
||||||
elements = []
|
elements = []
|
||||||
for node in self.selected:
|
for node in self.svg.selected:
|
||||||
elements.extend(self.get_selected_elements(node))
|
elements.extend(self.get_selected_elements(node))
|
||||||
|
|
||||||
if elements:
|
if elements:
|
||||||
|
@ -56,7 +56,7 @@ class RemoveEmbroiderySettings(InkstitchExtension):
|
||||||
group = command.connector.getparent()
|
group = command.connector.getparent()
|
||||||
group.getparent().remove(group)
|
group.getparent().remove(group)
|
||||||
|
|
||||||
if not self.selected:
|
if not self.svg.selected:
|
||||||
# remove standalone commands
|
# remove standalone commands
|
||||||
standalone_commands = ".//svg:use[starts-with(@xlink:href, '#inkstitch_')]"
|
standalone_commands = ".//svg:use[starts-with(@xlink:href, '#inkstitch_')]"
|
||||||
self.remove_elements(standalone_commands)
|
self.remove_elements(standalone_commands)
|
||||||
|
@ -84,5 +84,5 @@ class RemoveEmbroiderySettings(InkstitchExtension):
|
||||||
def remove_inkstitch_attributes(self, elements):
|
def remove_inkstitch_attributes(self, elements):
|
||||||
for element in elements:
|
for element in elements:
|
||||||
for attrib in element.attrib:
|
for attrib in element.attrib:
|
||||||
if attrib.startswith(inkex.NSS['inkstitch'], 1):
|
if attrib.startswith(NSS['inkstitch'], 1):
|
||||||
del element.attrib[attrib]
|
del element.attrib[attrib]
|
||||||
|
|
|
@ -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()
|
|
@ -4,7 +4,6 @@ from .base import InkstitchExtension
|
||||||
|
|
||||||
|
|
||||||
class StitchPlanPreview(InkstitchExtension):
|
class StitchPlanPreview(InkstitchExtension):
|
||||||
|
|
||||||
def effect(self):
|
def effect(self):
|
||||||
# delete old stitch plan
|
# delete old stitch plan
|
||||||
svg = self.document.getroot()
|
svg = self.document.getroot()
|
||||||
|
@ -15,9 +14,13 @@ class StitchPlanPreview(InkstitchExtension):
|
||||||
# create new stitch plan
|
# create new stitch plan
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
realistic = False
|
||||||
|
self.metadata = self.get_inkstitch_metadata()
|
||||||
|
collapse_len = self.metadata['collapse_len_mm']
|
||||||
patches = self.elements_to_patches(self.elements)
|
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)
|
||||||
render_stitch_plan(svg, stitch_plan)
|
render_stitch_plan(svg, stitch_plan, realistic)
|
||||||
|
|
||||||
# translate stitch plan to the right side of the canvas
|
# translate stitch plan to the right side of the canvas
|
||||||
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
|
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import inkex
|
from inkex import errormsg
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
from ..commands import add_layer_commands
|
from ..commands import add_layer_commands
|
||||||
from ..elements.validation import (ObjectTypeWarning, ValidationError,
|
from ..elements.validation import (ObjectTypeWarning, ValidationError,
|
||||||
ValidationWarning)
|
ValidationWarning)
|
||||||
from ..i18n import _
|
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,
|
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SODIPODI_ROLE,
|
||||||
SVG_GROUP_TAG, SVG_PATH_TAG, SVG_TEXT_TAG,
|
SVG_GROUP_TAG, SVG_PATH_TAG, SVG_TEXT_TAG,
|
||||||
SVG_TSPAN_TAG)
|
SVG_TSPAN_TAG)
|
||||||
|
@ -43,7 +44,7 @@ class Troubleshoot(InkstitchExtension):
|
||||||
message += "\n\n"
|
message += "\n\n"
|
||||||
message += _("If you are still having trouble with a shape not being embroidered, "
|
message += _("If you are still having trouble with a shape not being embroidered, "
|
||||||
"check if it is in a layer with an ignore command.")
|
"check if it is in a layer with an ignore command.")
|
||||||
inkex.errormsg(message)
|
errormsg(message)
|
||||||
|
|
||||||
def insert_pointer(self, problem):
|
def insert_pointer(self, problem):
|
||||||
correction_transform = get_correction_transform(self.troubleshoot_layer)
|
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)
|
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)
|
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,
|
SVG_PATH_TAG,
|
||||||
{
|
{
|
||||||
"id": self.uniqueId("inkstitch__invalid_pointer__"),
|
"id": self.uniqueId("inkstitch__invalid_pointer__"),
|
||||||
|
@ -73,7 +74,7 @@ class Troubleshoot(InkstitchExtension):
|
||||||
)
|
)
|
||||||
layer.insert(0, path)
|
layer.insert(0, path)
|
||||||
|
|
||||||
text = inkex.etree.Element(
|
text = etree.Element(
|
||||||
SVG_TEXT_TAG,
|
SVG_TEXT_TAG,
|
||||||
{
|
{
|
||||||
INKSCAPE_LABEL: _('Description'),
|
INKSCAPE_LABEL: _('Description'),
|
||||||
|
@ -85,7 +86,7 @@ class Troubleshoot(InkstitchExtension):
|
||||||
)
|
)
|
||||||
layer.append(text)
|
layer.append(text)
|
||||||
|
|
||||||
tspan = inkex.etree.Element(SVG_TSPAN_TAG)
|
tspan = etree.Element(SVG_TSPAN_TAG)
|
||||||
tspan.text = problem.name
|
tspan.text = problem.name
|
||||||
text.append(tspan)
|
text.append(tspan)
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ class Troubleshoot(InkstitchExtension):
|
||||||
layer = svg.find(".//*[@id='__validation_layer__']")
|
layer = svg.find(".//*[@id='__validation_layer__']")
|
||||||
|
|
||||||
if layer is None:
|
if layer is None:
|
||||||
layer = inkex.etree.Element(
|
layer = etree.Element(
|
||||||
SVG_GROUP_TAG,
|
SVG_GROUP_TAG,
|
||||||
{
|
{
|
||||||
'id': '__validation_layer__',
|
'id': '__validation_layer__',
|
||||||
|
@ -109,7 +110,7 @@ class Troubleshoot(InkstitchExtension):
|
||||||
|
|
||||||
add_layer_commands(layer, ["ignore_layer"])
|
add_layer_commands(layer, ["ignore_layer"])
|
||||||
|
|
||||||
error_group = inkex.etree.SubElement(
|
error_group = etree.SubElement(
|
||||||
layer,
|
layer,
|
||||||
SVG_GROUP_TAG,
|
SVG_GROUP_TAG,
|
||||||
{
|
{
|
||||||
|
@ -118,7 +119,7 @@ class Troubleshoot(InkstitchExtension):
|
||||||
})
|
})
|
||||||
layer.append(error_group)
|
layer.append(error_group)
|
||||||
|
|
||||||
warning_group = inkex.etree.SubElement(
|
warning_group = etree.SubElement(
|
||||||
layer,
|
layer,
|
||||||
SVG_GROUP_TAG,
|
SVG_GROUP_TAG,
|
||||||
{
|
{
|
||||||
|
@ -127,7 +128,7 @@ class Troubleshoot(InkstitchExtension):
|
||||||
})
|
})
|
||||||
layer.append(warning_group)
|
layer.append(warning_group)
|
||||||
|
|
||||||
type_warning_group = inkex.etree.SubElement(
|
type_warning_group = etree.SubElement(
|
||||||
layer,
|
layer,
|
||||||
SVG_GROUP_TAG,
|
SVG_GROUP_TAG,
|
||||||
{
|
{
|
||||||
|
@ -145,7 +146,7 @@ class Troubleshoot(InkstitchExtension):
|
||||||
svg = self.document.getroot()
|
svg = self.document.getroot()
|
||||||
text_x = str(float(svg.get('viewBox', '0 0 800 0').split(' ')[2]) + 5.0)
|
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,
|
SVG_TEXT_TAG,
|
||||||
{
|
{
|
||||||
"x": text_x,
|
"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":
|
if problem_type == "error":
|
||||||
text_color = "#ff0000"
|
text_color = "#ff0000"
|
||||||
problem_type_header = _("Errors")
|
problem_type_header = _("Errors")
|
||||||
|
@ -202,7 +203,7 @@ class Troubleshoot(InkstitchExtension):
|
||||||
text = self.split_text(text)
|
text = self.split_text(text)
|
||||||
|
|
||||||
for text_line in text:
|
for text_line in text:
|
||||||
tspan = inkex.etree.Element(
|
tspan = etree.Element(
|
||||||
SVG_TSPAN_TAG,
|
SVG_TSPAN_TAG,
|
||||||
{
|
{
|
||||||
SODIPODI_ROLE: "line",
|
SODIPODI_ROLE: "line",
|
||||||
|
|
|
@ -1,36 +1,34 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from copy import deepcopy
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
import inkex
|
from inkex import Boolean
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
import pyembroidery
|
import pyembroidery
|
||||||
|
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..output import write_embroidery_file
|
from ..output import write_embroidery_file
|
||||||
from ..stitch_plan import patches_to_stitch_plan
|
from ..stitch_plan import patches_to_stitch_plan
|
||||||
from ..svg import PIXELS_PER_MM
|
|
||||||
from .base import InkstitchExtension
|
|
||||||
from ..threads import ThreadCatalog
|
from ..threads import ThreadCatalog
|
||||||
|
from .base import InkstitchExtension
|
||||||
|
|
||||||
|
|
||||||
class Zip(InkstitchExtension):
|
class Zip(InkstitchExtension):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
InkstitchExtension.__init__(self)
|
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...
|
# it's kind of obnoxious that I have to do this...
|
||||||
self.formats = []
|
self.formats = []
|
||||||
for format in pyembroidery.supported_formats():
|
for format in pyembroidery.supported_formats():
|
||||||
if 'writer' in format and format['category'] == 'embroidery':
|
if 'writer' in format and format['category'] == 'embroidery':
|
||||||
extension = format['extension']
|
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.formats.append(extension)
|
||||||
self.OptionParser.add_option('--format-svg', type="inkbool", dest='svg')
|
self.arg_parser.add_argument('--format-svg', type=Boolean, dest='svg')
|
||||||
self.OptionParser.add_option('--format-threadlist', type="inkbool", dest='threadlist')
|
self.arg_parser.add_argument('--format-threadlist', type=Boolean, dest='threadlist')
|
||||||
self.formats.append('svg')
|
self.formats.append('svg')
|
||||||
self.formats.append('threadlist')
|
self.formats.append('threadlist')
|
||||||
|
|
||||||
|
@ -38,8 +36,10 @@ class Zip(InkstitchExtension):
|
||||||
if not self.get_elements():
|
if not self.get_elements():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.metadata = self.get_inkstitch_metadata()
|
||||||
|
collapse_len = self.metadata['collapse_len_mm']
|
||||||
patches = self.elements_to_patches(self.elements)
|
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()
|
base_file_name = self.get_base_file_name()
|
||||||
path = tempfile.mkdtemp()
|
path = tempfile.mkdtemp()
|
||||||
|
@ -50,10 +50,10 @@ class Zip(InkstitchExtension):
|
||||||
if getattr(self.options, format):
|
if getattr(self.options, format):
|
||||||
output_file = os.path.join(path, "%s.%s" % (base_file_name, format))
|
output_file = os.path.join(path, "%s.%s" % (base_file_name, format))
|
||||||
if format == 'svg':
|
if format == 'svg':
|
||||||
output = open(output_file, 'w')
|
document = deepcopy(self.document.getroot())
|
||||||
output.write(inkex.etree.tostring(self.document.getroot()))
|
with open(output_file, 'w', encoding='utf-8') as svg:
|
||||||
output.close()
|
svg.write(etree.tostring(document).decode('utf-8'))
|
||||||
if format == 'threadlist':
|
elif format == 'threadlist':
|
||||||
output_file = os.path.join(path, "%s_%s.txt" % (base_file_name, _("threadlist")))
|
output_file = os.path.join(path, "%s_%s.txt" % (base_file_name, _("threadlist")))
|
||||||
output = open(output_file, 'w')
|
output = open(output_file, 'w')
|
||||||
output.write(self.get_threadlist(stitch_plan, base_file_name))
|
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
|
# inkscape will read the file contents from stdout and copy
|
||||||
# to the destination file that the user chose
|
# to the destination file that the user chose
|
||||||
with open(temp_file.name) as output_file:
|
with open(temp_file.name, 'rb') as output_file:
|
||||||
sys.stdout.write(output_file.read())
|
sys.stdout.buffer.write(output_file.read())
|
||||||
|
|
||||||
os.remove(temp_file.name)
|
os.remove(temp_file.name)
|
||||||
for file in files:
|
for file in files:
|
||||||
|
|
|
@ -27,5 +27,5 @@ def open_url(url):
|
||||||
cwd = get_bundled_dir("electron")
|
cwd = get_bundled_dir("electron")
|
||||||
|
|
||||||
# Any output on stdout will crash inkscape.
|
# Any output on stdout will crash inkscape.
|
||||||
null = open(os.devnull, 'w')
|
with open(os.devnull, 'w') as null:
|
||||||
return subprocess.Popen(command, cwd=cwd, stdout=null)
|
return subprocess.Popen(command, cwd=cwd, stdout=null)
|
||||||
|
|
|
@ -64,7 +64,7 @@ class PresetsPanel(wx.Panel):
|
||||||
|
|
||||||
presets_sizer = wx.StaticBoxSizer(self.presets_box, wx.HORIZONTAL)
|
presets_sizer = wx.StaticBoxSizer(self.presets_box, wx.HORIZONTAL)
|
||||||
self.preset_chooser.SetMinSize((200, -1))
|
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.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.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)
|
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)
|
json.dump(presets, presets_file)
|
||||||
|
|
||||||
def update_preset_list(self):
|
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)]
|
preset_names = [preset for preset in preset_names if not self.is_hidden(preset)]
|
||||||
self.preset_chooser.SetItems(sorted(preset_names))
|
self.preset_chooser.SetItems(sorted(preset_names))
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
from itertools import izip
|
|
||||||
import sys
|
import sys
|
||||||
from threading import Thread, Event
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
from threading import Event, Thread
|
||||||
|
|
||||||
import wx
|
import wx
|
||||||
from wx.lib.intctrl import IntCtrl
|
from wx.lib.intctrl import IntCtrl
|
||||||
|
|
||||||
from ..i18n import _
|
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 ..svg import PIXELS_PER_MM
|
||||||
|
|
||||||
|
|
||||||
from .dialogs import info_dialog
|
from .dialogs import info_dialog
|
||||||
|
|
||||||
|
|
||||||
# L10N command label at bottom of simulator window
|
# L10N command label at bottom of simulator window
|
||||||
COMMAND_NAMES = [_("STITCH"), _("JUMP"), _("TRIM"), _("STOP"), _("COLOR CHANGE")]
|
COMMAND_NAMES = [_("STITCH"), _("JUMP"), _("TRIM"), _("STOP"), _("COLOR CHANGE")]
|
||||||
|
|
||||||
|
@ -132,7 +127,7 @@ class ControlPanel(wx.Panel):
|
||||||
self.accel_entries = []
|
self.accel_entries = []
|
||||||
|
|
||||||
for shortcut_key in shortcut_keys:
|
for shortcut_key in shortcut_keys:
|
||||||
eventId = wx.NewId()
|
eventId = wx.NewIdRef()
|
||||||
self.accel_entries.append((shortcut_key[0], shortcut_key[1], eventId))
|
self.accel_entries.append((shortcut_key[0], shortcut_key[1], eventId))
|
||||||
self.Bind(wx.EVT_MENU, shortcut_key[2], id=eventId)
|
self.Bind(wx.EVT_MENU, shortcut_key[2], id=eventId)
|
||||||
|
|
||||||
|
@ -378,7 +373,7 @@ class DrawingPanel(wx.Panel):
|
||||||
last_stitch = None
|
last_stitch = None
|
||||||
|
|
||||||
start = time.time()
|
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)
|
canvas.SetPen(pen)
|
||||||
if stitch + len(stitches) < self.current_stitch:
|
if stitch + len(stitches) < self.current_stitch:
|
||||||
stitch += len(stitches)
|
stitch += len(stitches)
|
||||||
|
@ -423,7 +418,7 @@ class DrawingPanel(wx.Panel):
|
||||||
while scale_width < 50:
|
while scale_width < 50:
|
||||||
scale_width += one_mm
|
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:
|
# The scale bar looks like this:
|
||||||
#
|
#
|
||||||
|
@ -431,7 +426,7 @@ class DrawingPanel(wx.Panel):
|
||||||
# |_____|_____|
|
# |_____|_____|
|
||||||
|
|
||||||
scale_lower_left_x = 20
|
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),
|
canvas.DrawLines(((scale_lower_left_x, scale_lower_left_y - 6),
|
||||||
(scale_lower_left_x, scale_lower_left_y),
|
(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
|
# 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
|
# thickness of ~0.4mm, but if we did that, we wouldn't be able to
|
||||||
# see the individual stitches.
|
# 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):
|
def parse_stitch_plan(self, stitch_plan):
|
||||||
self.pens = []
|
self.pens = []
|
||||||
|
@ -698,12 +693,7 @@ class EmbroiderySimulator(wx.Frame):
|
||||||
stitches_per_second=stitches_per_second)
|
stitches_per_second=stitches_per_second)
|
||||||
sizer.Add(self.simulator_panel, 1, wx.EXPAND)
|
sizer.Add(self.simulator_panel, 1, wx.EXPAND)
|
||||||
|
|
||||||
# self.SetSizerAndFit() sets the minimum size so that the buttons don't
|
self.SetSizeHints(sizer.CalcMin())
|
||||||
# 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.Bind(wx.EVT_CLOSE, self.on_close)
|
self.Bind(wx.EVT_CLOSE, self.on_close)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import gettext
|
import gettext
|
||||||
import os
|
import os
|
||||||
from os.path import dirname, realpath
|
|
||||||
import sys
|
import sys
|
||||||
|
from os.path import dirname, realpath
|
||||||
|
|
||||||
from .utils import cache
|
from .utils import cache
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ def localize(languages=None):
|
||||||
global translation, _
|
global translation, _
|
||||||
|
|
||||||
translation = gettext.translation("inkstitch", locale_dir, fallback=True)
|
translation = gettext.translation("inkstitch", locale_dir, fallback=True)
|
||||||
_ = translation.ugettext
|
_ = translation.gettext
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
from generate import generate_inx_files
|
from .generate import generate_inx_files
|
||||||
|
|
|
@ -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())
|
|
@ -1,10 +1,11 @@
|
||||||
import pyembroidery
|
import pyembroidery
|
||||||
|
|
||||||
from .utils import build_environment, write_inx_file
|
from ..commands import (COMMANDS, GLOBAL_COMMANDS, LAYER_COMMANDS,
|
||||||
from .outputs import pyembroidery_output_formats
|
OBJECT_COMMANDS)
|
||||||
from ..extensions import extensions, Input, Output
|
from ..extensions import Input, Output, extensions
|
||||||
from ..commands import LAYER_COMMANDS, OBJECT_COMMANDS, GLOBAL_COMMANDS, COMMANDS
|
|
||||||
from ..threads import ThreadCatalog
|
from ..threads import ThreadCatalog
|
||||||
|
from .outputs import pyembroidery_output_formats
|
||||||
|
from .utils import build_environment, write_inx_file
|
||||||
|
|
||||||
|
|
||||||
def layer_commands():
|
def layer_commands():
|
||||||
|
@ -41,7 +42,7 @@ def generate_extension_inx_files():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
name = extension.name()
|
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(),
|
write_inx_file(name, template.render(formats=pyembroidery_output_formats(),
|
||||||
debug_formats=pyembroidery_debug_formats(),
|
debug_formats=pyembroidery_debug_formats(),
|
||||||
threadcatalog=threadcatalog(),
|
threadcatalog=threadcatalog(),
|
||||||
|
|
|
@ -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 .inputs import generate_input_inx_files
|
||||||
from .outputs import generate_output_inx_files
|
from .outputs import generate_output_inx_files
|
||||||
from .extensions import generate_extension_inx_files
|
|
||||||
from .utils import iterate_inx_locales
|
from .utils import iterate_inx_locales
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,3 +10,4 @@ def generate_inx_files():
|
||||||
generate_input_inx_files()
|
generate_input_inx_files()
|
||||||
generate_output_inx_files()
|
generate_output_inx_files()
|
||||||
generate_extension_inx_files()
|
generate_extension_inx_files()
|
||||||
|
generate_info_inx_files()
|
||||||
|
|
|
@ -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())
|
|
@ -11,7 +11,7 @@ def pyembroidery_input_formats():
|
||||||
|
|
||||||
def generate_input_inx_files():
|
def generate_input_inx_files():
|
||||||
env = build_environment()
|
env = build_environment()
|
||||||
template = env.get_template('input.inx')
|
template = env.get_template('input.xml')
|
||||||
|
|
||||||
for format, description in pyembroidery_input_formats():
|
for format, description in pyembroidery_input_formats():
|
||||||
name = "input_%s" % format.upper()
|
name = "input_%s" % format.upper()
|
||||||
|
|
|
@ -5,14 +5,17 @@ from .utils import build_environment, write_inx_file
|
||||||
|
|
||||||
def pyembroidery_output_formats():
|
def pyembroidery_output_formats():
|
||||||
for format in pyembroidery.supported_formats():
|
for format in pyembroidery.supported_formats():
|
||||||
if 'writer' in format and format['category'] == 'embroidery':
|
if 'writer' in format:
|
||||||
yield format['extension'], format['description']
|
description = format['description']
|
||||||
|
if format['category'] != "embroidery":
|
||||||
|
description = "%s [DEBUG]" % description
|
||||||
|
yield format['extension'], description, format['mimetype'], format['category']
|
||||||
|
|
||||||
|
|
||||||
def generate_output_inx_files():
|
def generate_output_inx_files():
|
||||||
env = build_environment()
|
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()
|
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))
|
||||||
|
|
|
@ -6,11 +6,13 @@ from os.path import dirname
|
||||||
|
|
||||||
from jinja2 import Environment, FileSystemLoader
|
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__))))
|
_top_path = dirname(dirname(dirname(os.path.realpath(__file__))))
|
||||||
inx_path = os.path.join(_top_path, "inx")
|
inx_path = os.path.join(_top_path, "inx")
|
||||||
template_path = os.path.join(_top_path, "templates")
|
template_path = os.path.join(_top_path, "templates")
|
||||||
|
version_path = _top_path
|
||||||
|
|
||||||
current_translation = default_translation
|
current_translation = default_translation
|
||||||
current_locale = "en_US"
|
current_locale = "en_US"
|
||||||
|
@ -26,16 +28,29 @@ def build_environment():
|
||||||
env.install_gettext_translations(current_translation)
|
env.install_gettext_translations(current_translation)
|
||||||
env.globals["locale"] = current_locale
|
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:
|
if "BUILD" in os.environ:
|
||||||
# building a ZIP release, with inkstitch packaged as a binary
|
# 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":
|
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:
|
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:
|
else:
|
||||||
# user is running inkstitch.py directly as a developer
|
# 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
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,8 +64,8 @@ def write_inx_file(name, contents):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
inx_file_name = "inkstitch_%s.inx" % name
|
inx_file_name = "inkstitch_%s.inx" % name
|
||||||
with open(os.path.join(inx_locale_dir, inx_file_name), 'w') as inx_file:
|
with open(os.path.join(inx_locale_dir, inx_file_name), 'w', encoding="utf-8") as inx_file:
|
||||||
print >> inx_file, contents.encode("utf-8")
|
print(contents, file=inx_file)
|
||||||
|
|
||||||
|
|
||||||
def iterate_inx_locales():
|
def iterate_inx_locales():
|
||||||
|
@ -64,7 +79,7 @@ def iterate_inx_locales():
|
||||||
# generate menu items for this language in Inkscape's "Extensions"
|
# generate menu items for this language in Inkscape's "Extensions"
|
||||||
# menu.
|
# menu.
|
||||||
magic_string = N_("Generate INX files")
|
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":
|
if translated_magic_string != magic_string or locale == "en_US":
|
||||||
current_translation = translation
|
current_translation = translation
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
from font import Font, FontError
|
from .font import Font, FontError
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
import inkex
|
from inkex import styles
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
from ..elements import nodes_to_elements
|
from ..elements import nodes_to_elements
|
||||||
from ..exceptions import InkstitchException
|
from ..exceptions import InkstitchException
|
||||||
|
@ -80,14 +79,14 @@ class Font(object):
|
||||||
|
|
||||||
def _load_metadata(self):
|
def _load_metadata(self):
|
||||||
try:
|
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)
|
self.metadata = json.load(metadata_file)
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _load_license(self):
|
def _load_license(self):
|
||||||
try:
|
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()
|
self.license = license_file.read()
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
@ -101,13 +100,9 @@ class Font(object):
|
||||||
# we'll deal with missing variants when we apply lettering
|
# we'll deal with missing variants when we apply lettering
|
||||||
pass
|
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', '')
|
name = localized_font_metadata('name', '')
|
||||||
description = localized_font_metadata('description', '')
|
description = localized_font_metadata('description', '')
|
||||||
default_glyph = font_metadata('default_glyph', u"<EFBFBD>")
|
default_glyph = font_metadata('defalt_glyph', "<EFBFBD>")
|
||||||
leading = font_metadata('leading', 5, multiplier=PIXELS_PER_MM)
|
leading = font_metadata('leading', 5, multiplier=PIXELS_PER_MM)
|
||||||
kerning_pairs = font_metadata('kerning_pairs', {})
|
kerning_pairs = font_metadata('kerning_pairs', {})
|
||||||
auto_satin = font_metadata('auto_satin', True)
|
auto_satin = font_metadata('auto_satin', True)
|
||||||
|
@ -149,10 +144,13 @@ class Font(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def has_variants(self):
|
def has_variants(self):
|
||||||
|
# returns available variants
|
||||||
font_variants = []
|
font_variants = []
|
||||||
for variant in FontVariant.VARIANT_TYPES:
|
for variant in FontVariant.VARIANT_TYPES:
|
||||||
if os.path.isfile(os.path.join(self.path, "%s.svg" % variant)):
|
if os.path.isfile(os.path.join(self.path, "%s.svg" % variant)):
|
||||||
font_variants.append(variant)
|
font_variants.append(variant)
|
||||||
|
if not font_variants:
|
||||||
|
raise FontError(_("The font '%s' has no variants.") % self.name)
|
||||||
return font_variants
|
return font_variants
|
||||||
|
|
||||||
def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim=False):
|
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:
|
if self.auto_satin and len(destination_group) > 0:
|
||||||
self._apply_auto_satin(destination_group, trim)
|
self._apply_auto_satin(destination_group, trim)
|
||||||
else:
|
|
||||||
# set stroke width because it is almost invisible otherwise (why?)
|
# make sure font stroke styles have always a similar look
|
||||||
for element in destination_group.iterdescendants(SVG_PATH_TAG):
|
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(';')]
|
dash_array = ""
|
||||||
style = ';'.join(style)
|
stroke_width = ""
|
||||||
element.set('style', '%s' % style)
|
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
|
return destination_group
|
||||||
|
|
||||||
|
@ -209,7 +218,8 @@ class Font(object):
|
||||||
Returns:
|
Returns:
|
||||||
An svg:g element containing the rendered text.
|
An svg:g element containing the rendered text.
|
||||||
"""
|
"""
|
||||||
group = inkex.etree.Element(SVG_GROUP_TAG, {
|
|
||||||
|
group = etree.Element(SVG_GROUP_TAG, {
|
||||||
INKSCAPE_LABEL: line
|
INKSCAPE_LABEL: line
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import inkex
|
import inkex
|
||||||
import simplestyle
|
from lxml import etree
|
||||||
|
|
||||||
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
|
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
|
||||||
from .glyph import Glyph
|
from .glyph import Glyph
|
||||||
|
@ -26,10 +24,10 @@ class FontVariant(object):
|
||||||
|
|
||||||
# We use unicode characters rather than English strings for font file names
|
# We use unicode characters rather than English strings for font file names
|
||||||
# in order to be more approachable for languages other than English.
|
# in order to be more approachable for languages other than English.
|
||||||
LEFT_TO_RIGHT = u"→"
|
LEFT_TO_RIGHT = "→"
|
||||||
RIGHT_TO_LEFT = u"←"
|
RIGHT_TO_LEFT = "←"
|
||||||
TOP_TO_BOTTOM = u"↓"
|
TOP_TO_BOTTOM = "↓"
|
||||||
BOTTOM_TO_TOP = u"↑"
|
BOTTOM_TO_TOP = "↑"
|
||||||
VARIANT_TYPES = (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
|
@classmethod
|
||||||
|
@ -57,9 +55,9 @@ class FontVariant(object):
|
||||||
self._load_glyphs()
|
self._load_glyphs()
|
||||||
|
|
||||||
def _load_glyphs(self):
|
def _load_glyphs(self):
|
||||||
svg_path = os.path.join(self.path, u"%s.svg" % self.variant)
|
svg_path = os.path.join(self.path, "%s.svg" % self.variant)
|
||||||
with open(svg_path) as svg_file:
|
with open(svg_path, encoding="utf-8") as svg_file:
|
||||||
svg = inkex.etree.parse(svg_file)
|
svg = etree.parse(svg_file)
|
||||||
|
|
||||||
glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS)
|
glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS)
|
||||||
for layer in glyph_layers:
|
for layer in glyph_layers:
|
||||||
|
@ -78,9 +76,9 @@ class FontVariant(object):
|
||||||
if style_text:
|
if style_text:
|
||||||
# The layer may be marked invisible, so we'll clear the 'display'
|
# The layer may be marked invisible, so we'll clear the 'display'
|
||||||
# style.
|
# style.
|
||||||
style = simplestyle.parseStyle(group.get('style'))
|
style = dict(inkex.Style.parse_str(group.get('style')))
|
||||||
style.pop('display')
|
style.pop('display')
|
||||||
group.set('style', simplestyle.formatStyle(style))
|
group.set('style', str(inkex.Style(style)))
|
||||||
|
|
||||||
def __getitem__(self, character):
|
def __getitem__(self, character):
|
||||||
if character in self.glyphs:
|
if character in self.glyphs:
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
import cubicsuperpath
|
from inkex import paths, transforms
|
||||||
import simpletransform
|
|
||||||
|
|
||||||
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
|
from ..svg.tags import SVG_GROUP_TAG, SVG_PATH_TAG
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,8 +37,9 @@ class Glyph(object):
|
||||||
|
|
||||||
def _process_group(self, group):
|
def _process_group(self, group):
|
||||||
new_group = copy(group)
|
new_group = copy(group)
|
||||||
new_group.attrib.pop('transform', None)
|
# new_group.attrib.pop('transform', None)
|
||||||
del new_group[:] # delete references to the original group's children
|
# delete references to the original group's children
|
||||||
|
del new_group[:]
|
||||||
|
|
||||||
for node in group:
|
for node in group:
|
||||||
if node.tag == SVG_GROUP_TAG:
|
if node.tag == SVG_GROUP_TAG:
|
||||||
|
@ -47,11 +48,9 @@ class Glyph(object):
|
||||||
node_copy = copy(node)
|
node_copy = copy(node)
|
||||||
|
|
||||||
if "d" in node.attrib:
|
if "d" in node.attrib:
|
||||||
# Convert the path to absolute coordinates, incorporating all
|
transform = -transforms.Transform(get_correction_transform(node, True))
|
||||||
# nested transforms.
|
path = paths.Path(node.get("d")).transform(transform).to_absolute()
|
||||||
path = cubicsuperpath.parsePath(node.get("d"))
|
node_copy.set("d", str(path))
|
||||||
apply_transforms(path, node)
|
|
||||||
node_copy.set("d", cubicsuperpath.formatPath(path))
|
|
||||||
|
|
||||||
# Delete transforms from paths and groups, since we applied
|
# Delete transforms from paths and groups, since we applied
|
||||||
# them to the paths already.
|
# them to the paths already.
|
||||||
|
@ -71,16 +70,18 @@ class Glyph(object):
|
||||||
self.baseline = 0
|
self.baseline = 0
|
||||||
|
|
||||||
def _process_bbox(self):
|
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.width = right - left
|
||||||
self.min_x = left
|
self.min_x = left
|
||||||
|
|
||||||
def _move_to_origin(self):
|
def _move_to_origin(self):
|
||||||
translate_x = -self.min_x
|
translate_x = -self.min_x
|
||||||
translate_y = -self.baseline
|
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):
|
for node in self.node.iter(SVG_PATH_TAG):
|
||||||
node.set('transform', transform)
|
path = paths.Path(node.get("d"))
|
||||||
simpletransform.fuseTransform(node)
|
path = path.transform(transform)
|
||||||
|
node.set('d', str(path))
|
||||||
|
node.attrib.pop('transform', None)
|
||||||
|
|
|
@ -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))
|
||||||
|
"""
|
|
@ -1,4 +1,5 @@
|
||||||
import sys
|
import sys
|
||||||
|
import inkex
|
||||||
|
|
||||||
import pyembroidery
|
import pyembroidery
|
||||||
|
|
||||||
|
@ -27,7 +28,8 @@ def _string_to_floats(string):
|
||||||
return [float(num) for num in floats]
|
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")
|
origin_command = global_command(svg, "origin")
|
||||||
|
|
||||||
if origin_command:
|
if origin_command:
|
||||||
|
@ -91,5 +93,6 @@ def write_embroidery_file(file_path, stitch_plan, svg, settings={}):
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
# L10N low-level file error. %(error)s is (hopefully?) translated by
|
# L10N low-level file error. %(error)s is (hopefully?) translated by
|
||||||
# the user's system automatically.
|
# 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)
|
sys.exit(1)
|
||||||
|
|
|
@ -5,7 +5,8 @@ from .stitch import Stitch
|
||||||
from .ties import add_ties
|
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.
|
"""Convert a collection of inkstitch.element.Patch objects to a StitchPlan.
|
||||||
|
|
||||||
* applies instructions embedded in the Patch such as trim_after and stop_after
|
* 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
|
* adds jump-stitches between patches if necessary
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
collapse_len = (collapse_len or 3.0) * PIXELS_PER_MM
|
||||||
stitch_plan = StitchPlan()
|
stitch_plan = StitchPlan()
|
||||||
color_block = stitch_plan.new_color_block(color=patches[0].color)
|
color_block = stitch_plan.new_color_block(color=patches[0].color)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from running_stitch import *
|
from .auto_fill import auto_fill
|
||||||
from auto_fill import auto_fill
|
from .fill import legacy_fill
|
||||||
from fill import legacy_fill
|
from .running_stitch import *
|
||||||
|
|
||||||
# Can't put this here because we get a circular import :(
|
# Can't put this here because we get a circular import :(
|
||||||
#from auto_satin import auto_satin
|
#from auto_satin import auto_satin
|
||||||
|
|
|
@ -8,11 +8,12 @@ from shapely import geometry as shgeo
|
||||||
from shapely.ops import snap
|
from shapely.ops import snap
|
||||||
from shapely.strtree import STRtree
|
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 ..debug import debug
|
||||||
from ..svg import PIXELS_PER_MM
|
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):
|
class PathEdge(object):
|
||||||
|
@ -82,7 +83,7 @@ def which_outline(shape, coords):
|
||||||
|
|
||||||
point = shgeo.Point(*coords)
|
point = shgeo.Point(*coords)
|
||||||
outlines = list(shape.boundary)
|
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))
|
closest = min(outline_indices, key=lambda index: outlines[index].distance(point))
|
||||||
|
|
||||||
return closest
|
return closest
|
||||||
|
@ -175,7 +176,7 @@ def insert_node(graph, shape, point):
|
||||||
if key == "outline":
|
if key == "outline":
|
||||||
edges.append(((start, end), data))
|
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.remove_edge(*edge, key="outline")
|
||||||
graph.add_edge(edge[0], node, key="outline", **data)
|
graph.add_edge(edge[0], node, key="outline", **data)
|
||||||
|
@ -204,7 +205,7 @@ def add_boundary_travel_nodes(graph, shape):
|
||||||
if length > 1:
|
if length > 1:
|
||||||
# Just plot a point every pixel, that should be plenty of
|
# Just plot a point every pixel, that should be plenty of
|
||||||
# resolution. A pixel is around a quarter of a millimeter.
|
# 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)
|
subpoint = segment.interpolate(i)
|
||||||
graph.add_node((subpoint.x, subpoint.y), projection=outline.project(subpoint), outline=outline_index)
|
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()
|
graph = graph.copy()
|
||||||
|
|
||||||
if not starting_point:
|
if not starting_point:
|
||||||
starting_point = graph.nodes.keys()[0]
|
starting_point = list(graph.nodes.keys())[0]
|
||||||
|
|
||||||
starting_node = nearest_node(graph, starting_point)
|
starting_node = nearest_node(graph, starting_point)
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import math
|
import math
|
||||||
from itertools import chain, izip
|
from itertools import chain
|
||||||
|
|
||||||
|
import inkex
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
from lxml import etree
|
||||||
from shapely import geometry as shgeo
|
from shapely import geometry as shgeo
|
||||||
from shapely.geometry import Point as ShapelyPoint
|
from shapely.geometry import Point as ShapelyPoint
|
||||||
|
|
||||||
import cubicsuperpath
|
|
||||||
import inkex
|
|
||||||
import simplestyle
|
|
||||||
|
|
||||||
from ..commands import add_commands
|
from ..commands import add_commands
|
||||||
from ..elements import SatinColumn, Stroke
|
from ..elements import SatinColumn, Stroke
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
|
@ -87,7 +85,7 @@ class SatinSegment(object):
|
||||||
|
|
||||||
num_segments = int(math.ceil(self.center_line.length / segment_size))
|
num_segments = int(math.ceil(self.center_line.length / segment_size))
|
||||||
segments = []
|
segments = []
|
||||||
for i in xrange(num_segments):
|
for i in range(num_segments):
|
||||||
segments.append(SatinSegment(self.satin,
|
segments.append(SatinSegment(self.satin,
|
||||||
float(i) / num_segments,
|
float(i) / num_segments,
|
||||||
float(i + 1) / 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'], '')
|
original_element.node.get(INKSTITCH_ATTRIBS['contour_underlay_stitch_length_mm'], '')
|
||||||
|
|
||||||
def to_element(self):
|
def to_element(self):
|
||||||
node = inkex.etree.Element(SVG_PATH_TAG)
|
node = etree.Element(SVG_PATH_TAG)
|
||||||
node.set("d", cubicsuperpath.formatPath(
|
d = str(inkex.paths.CubicSuperPath(line_strings_to_csp([self.path])))
|
||||||
line_strings_to_csp([self.path])))
|
node.set("d", d)
|
||||||
|
|
||||||
style = self.original_element.parse_style()
|
style = self.original_element.parse_style()
|
||||||
style['stroke-dasharray'] = "0.5,0.5"
|
style['stroke-dasharray'] = "0.5,0.5"
|
||||||
style = simplestyle.formatStyle(style)
|
style = str(inkex.Style(style))
|
||||||
node.set("style", style)
|
node.set("style", style)
|
||||||
node.set(INKSTITCH_ATTRIBS['running_stitch_length_mm'], self.running_stitch_length)
|
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']
|
point2 = graph.nodes[node2]['point']
|
||||||
potential_edges.append((point1, point2))
|
potential_edges.append((point1, point2))
|
||||||
|
|
||||||
edge = min(potential_edges, key=lambda (p1, p2): p1.distance(p2))
|
if potential_edges:
|
||||||
graph.add_edge(str(edge[0]), str(edge[1]), jump=True)
|
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:
|
else:
|
||||||
# networkx makes this super-easy! k_edge_agumentation tells us what edges
|
# 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
|
# 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
|
# forth on each satin twice due to the satin edges being in the graph
|
||||||
# twice (forward and reverse).
|
# twice (forward and reverse).
|
||||||
graph = nx.Graph(graph)
|
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 = []
|
final_path = []
|
||||||
prev = None
|
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.
|
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:
|
if parent is not None:
|
||||||
parent.append(element.node)
|
parent.append(element.node)
|
||||||
element.node.set('transform', get_correction_transform(parent, child=True))
|
element.node.set('transform', get_correction_transform(parent, child=True))
|
||||||
|
|
||||||
|
|
||||||
def create_new_group(parent, insert_index):
|
def create_new_group(parent, insert_index):
|
||||||
group = inkex.etree.Element(SVG_GROUP_TAG, {
|
group = etree.Element(SVG_GROUP_TAG, {
|
||||||
INKSCAPE_LABEL: _("Auto-Satin"),
|
INKSCAPE_LABEL: _("Auto-Satin"),
|
||||||
"transform": get_correction_transform(parent, child=True)
|
"transform": get_correction_transform(parent, child=True)
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,8 @@ import math
|
||||||
import shapely
|
import shapely
|
||||||
|
|
||||||
from ..svg import PIXELS_PER_MM
|
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):
|
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 = []
|
run = []
|
||||||
prev = None
|
prev = None
|
||||||
|
|
||||||
for row_num in xrange(len(rows)):
|
for row_num in range(len(rows)):
|
||||||
row = rows[row_num]
|
row = rows[row_num]
|
||||||
first, rest = row[0], row[1:]
|
first, rest = row[0], row[1:]
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ def bean_stitch(stitches, repeats):
|
||||||
for stitch in stitches:
|
for stitch in stitches:
|
||||||
new_stitches.append(stitch)
|
new_stitches.append(stitch)
|
||||||
|
|
||||||
for i in xrange(repeats):
|
for i in range(repeats):
|
||||||
new_stitches.extend(copy(new_stitches[-2:]))
|
new_stitches.extend(copy(new_stitches[-2:]))
|
||||||
|
|
||||||
return new_stitches
|
return new_stitches
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import simpletransform
|
from inkex import transforms
|
||||||
|
|
||||||
from ..utils import string_to_floats, Point, cache
|
from ..utils import Point, cache, string_to_floats
|
||||||
from .tags import SODIPODI_NAMEDVIEW, SODIPODI_GUIDE, INKSCAPE_LABEL
|
from .tags import INKSCAPE_LABEL, SODIPODI_GUIDE, SODIPODI_NAMEDVIEW
|
||||||
from .units import get_doc_size, get_viewbox_transform
|
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
|
# convert the size from viewbox-relative to real-world pixels
|
||||||
viewbox_transform = get_viewbox_transform(self.svg)
|
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')))
|
self.position = Point(*string_to_floats(self.node.get('position')))
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import cubicsuperpath
|
|
||||||
import inkex
|
import inkex
|
||||||
import simpletransform
|
from lxml import etree
|
||||||
from tags import SVG_GROUP_TAG, SVG_LINK_TAG
|
|
||||||
|
|
||||||
|
from .tags import SVG_GROUP_TAG, SVG_LINK_TAG
|
||||||
from .units import get_viewbox_transform
|
from .units import get_viewbox_transform
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +9,7 @@ def apply_transforms(path, node):
|
||||||
transform = get_node_transform(node)
|
transform = get_node_transform(node)
|
||||||
|
|
||||||
# apply the combined transform to this node's path
|
# apply the combined transform to this node's path
|
||||||
simpletransform.applyTransformToPath(transform, path)
|
path = path.transform(transform)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ def compose_parent_transforms(node, mat):
|
||||||
|
|
||||||
trans = node.get('transform')
|
trans = node.get('transform')
|
||||||
if trans:
|
if trans:
|
||||||
mat = simpletransform.composeTransform(simpletransform.parseTransform(trans), mat)
|
mat = inkex.transforms.Transform(trans) * mat
|
||||||
if node.getparent() is not None:
|
if node.getparent() is not None:
|
||||||
if node.getparent().tag in [SVG_GROUP_TAG, SVG_LINK_TAG]:
|
if node.getparent().tag in [SVG_GROUP_TAG, SVG_LINK_TAG]:
|
||||||
mat = compose_parent_transforms(node.getparent(), mat)
|
mat = compose_parent_transforms(node.getparent(), mat)
|
||||||
|
@ -29,20 +28,22 @@ def compose_parent_transforms(node, mat):
|
||||||
|
|
||||||
|
|
||||||
def get_node_transform(node):
|
def get_node_transform(node):
|
||||||
|
"""
|
||||||
|
if getattr(node, "composed_transform", None):
|
||||||
|
return node.composed_transform()
|
||||||
|
"""
|
||||||
|
|
||||||
# start with the identity 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?!
|
# this if is because sometimes inkscape likes to create paths outside of a layer?!
|
||||||
if node.getparent() is not None:
|
if node.getparent() is not None:
|
||||||
# combine this node's transform with all parent groups' transforms
|
# combine this node's transform with all parent groups' transforms
|
||||||
transform = compose_parent_transforms(node, transform)
|
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
|
# add in the transform implied by the viewBox
|
||||||
viewbox_transform = get_viewbox_transform(node.getroottree().getroot())
|
viewbox_transform = get_viewbox_transform(node.getroottree().getroot())
|
||||||
transform = simpletransform.composeTransform(viewbox_transform, transform)
|
transform = viewbox_transform * transform
|
||||||
|
|
||||||
return 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
|
# now invert it, so that we can position our objects in absolute
|
||||||
# coordinates
|
# coordinates
|
||||||
transform = simpletransform.invertTransform(transform)
|
transform = -transform
|
||||||
|
|
||||||
return simpletransform.formatTransform(transform)
|
return str(transform)
|
||||||
|
|
||||||
|
|
||||||
def line_strings_to_csp(line_strings):
|
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):
|
def line_strings_to_path(line_strings):
|
||||||
csp = line_strings_to_csp(line_strings)
|
csp = line_strings_to_csp(line_strings)
|
||||||
|
|
||||||
return inkex.etree.Element("path", {
|
return etree.Element("path", {
|
||||||
"d": cubicsuperpath.formatPath(csp)
|
"d": str(inkex.paths.CubicSuperPath(csp))
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import math
|
import math
|
||||||
|
|
||||||
import inkex
|
import inkex
|
||||||
import simplepath
|
from lxml import etree
|
||||||
import simplestyle
|
from math import pi
|
||||||
import simpletransform
|
|
||||||
|
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..utils import Point, cache
|
from ..utils import Point, cache
|
||||||
|
@ -116,24 +115,25 @@ def realistic_stitch(start, end):
|
||||||
start = Point(*start)
|
start = Point(*start)
|
||||||
|
|
||||||
stitch_length = (end - start).length()
|
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_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)
|
stitch_length = max(0, stitch_length - 0.2 * PIXELS_PER_MM)
|
||||||
|
|
||||||
# create the path by filling in the length in the template
|
# 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
|
# rotate the path to match the stitch
|
||||||
rotation_center_x = -stitch_length / 2.0
|
rotation_center_x = -stitch_length / 2.0
|
||||||
rotation_center_y = stitch_height / 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
|
# 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):
|
def color_block_to_point_lists(color_block):
|
||||||
|
@ -159,10 +159,9 @@ def get_correction_transform(svg):
|
||||||
transform = get_viewbox_transform(svg)
|
transform = get_viewbox_transform(svg)
|
||||||
|
|
||||||
# we need to correct for the viewbox
|
# we need to correct for the viewbox
|
||||||
transform = simpletransform.invertTransform(transform)
|
transform = -inkex.transforms.Transform(transform)
|
||||||
transform = simpletransform.formatTransform(transform)
|
|
||||||
|
|
||||||
return transform
|
return str(transform)
|
||||||
|
|
||||||
|
|
||||||
def color_block_to_realistic_stitches(color_block, svg, destination):
|
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()
|
color = color_block.color.visible_on_white.darker.to_hex_str()
|
||||||
start = point_list[0]
|
start = point_list[0]
|
||||||
for point in point_list[1:]:
|
for point in point_list[1:]:
|
||||||
destination.append(inkex.etree.Element(SVG_PATH_TAG, {
|
destination.append(etree.Element(SVG_PATH_TAG, {
|
||||||
'style': simplestyle.formatStyle({
|
'style': "fill: %s; stroke: none; filter: url(#realistic-stitch-filter);" % color,
|
||||||
'fill': color,
|
|
||||||
'stroke': 'none',
|
|
||||||
'filter': 'url(#realistic-stitch-filter)'
|
|
||||||
}),
|
|
||||||
'd': realistic_stitch(start, point),
|
'd': realistic_stitch(start, point),
|
||||||
'transform': get_correction_transform(svg)
|
'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()
|
color = color_block.color.visible_on_white.to_hex_str()
|
||||||
|
|
||||||
path = inkex.etree.Element(SVG_PATH_TAG, {
|
path = etree.Element(SVG_PATH_TAG, {
|
||||||
'style': simplestyle.formatStyle({
|
'style': "stroke: %s; stroke-width: 0.4; fill: none;" % color,
|
||||||
'stroke': color,
|
|
||||||
'stroke-width': "0.4",
|
|
||||||
'fill': 'none'
|
|
||||||
}),
|
|
||||||
'd': "M" + " ".join(" ".join(str(coord) for coord in point) for point in point_list),
|
'd': "M" + " ".join(" ".join(str(coord) for coord in point) for point in point_list),
|
||||||
'transform': get_correction_transform(svg),
|
'transform': get_correction_transform(svg),
|
||||||
INKSTITCH_ATTRIBS['manual_stitch']: 'true'
|
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):
|
def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
|
||||||
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
|
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
|
||||||
if layer is None:
|
if layer is None:
|
||||||
layer = inkex.etree.Element(SVG_GROUP_TAG,
|
layer = etree.Element(SVG_GROUP_TAG,
|
||||||
{'id': '__inkstitch_stitch_plan__',
|
{'id': '__inkstitch_stitch_plan__',
|
||||||
INKSCAPE_LABEL: _('Stitch Plan'),
|
INKSCAPE_LABEL: _('Stitch Plan'),
|
||||||
INKSCAPE_GROUPMODE: 'layer'})
|
INKSCAPE_GROUPMODE: 'layer'})
|
||||||
else:
|
else:
|
||||||
# delete old stitch plan
|
# delete old stitch plan
|
||||||
del layer[:]
|
del layer[:]
|
||||||
|
@ -237,10 +228,10 @@ def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
|
||||||
svg.append(layer)
|
svg.append(layer)
|
||||||
|
|
||||||
for i, color_block in enumerate(stitch_plan):
|
for i, color_block in enumerate(stitch_plan):
|
||||||
group = inkex.etree.SubElement(layer,
|
group = etree.SubElement(layer,
|
||||||
SVG_GROUP_TAG,
|
SVG_GROUP_TAG,
|
||||||
{'id': '__color_block_%d__' % i,
|
{'id': '__color_block_%d__' % i,
|
||||||
INKSCAPE_LABEL: "color block %d" % (i + 1)})
|
INKSCAPE_LABEL: "color block %d" % (i + 1)})
|
||||||
if realistic:
|
if realistic:
|
||||||
color_block_to_realistic_stitches(color_block, svg, group)
|
color_block_to_realistic_stitches(color_block, svg, group)
|
||||||
else:
|
else:
|
||||||
|
@ -250,6 +241,6 @@ def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
|
||||||
defs = svg.find(SVG_DEFS_TAG)
|
defs = svg.find(SVG_DEFS_TAG)
|
||||||
|
|
||||||
if defs is None:
|
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))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from inkex import NSS, etree
|
from inkex import NSS
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
from ..utils import cache
|
from ..utils import cache
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import inkex
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
etree.register_namespace("inkstitch", "http://inkstitch.org/namespace")
|
||||||
# This is used below and added to the document in ../extensions/base.py.
|
|
||||||
inkex.NSS['inkstitch'] = 'http://inkstitch.org/namespace'
|
inkex.NSS['inkstitch'] = 'http://inkstitch.org/namespace'
|
||||||
|
|
||||||
|
|
||||||
SVG_PATH_TAG = inkex.addNS('path', 'svg')
|
SVG_PATH_TAG = inkex.addNS('path', 'svg')
|
||||||
SVG_POLYLINE_TAG = inkex.addNS('polyline', 'svg')
|
SVG_POLYLINE_TAG = inkex.addNS('polyline', 'svg')
|
||||||
SVG_RECT_TAG = inkex.addNS('rect', 'svg')
|
SVG_RECT_TAG = inkex.addNS('rect', 'svg')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import simpletransform
|
import inkex
|
||||||
|
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from ..utils import cache
|
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
|
# modern versions of Inkscape use 96 pixels per inch as per the CSS standard
|
||||||
PIXELS_PER_MM = 96 / 25.4
|
PIXELS_PER_MM = 96 / 25.4
|
||||||
|
|
||||||
# cribbed from inkscape-silhouette
|
|
||||||
|
|
||||||
|
|
||||||
def parse_length_with_units(str):
|
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
|
Parse an SVG value which may or may not have units attached
|
||||||
This version is greatly simplified in that it only allows: no units,
|
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.
|
generality is ever needed.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# cribbed from inkscape-silhouette
|
||||||
|
|
||||||
u = 'px'
|
u = 'px'
|
||||||
s = str.strip()
|
s = str.strip()
|
||||||
if s[-2:] == 'px':
|
if s[-2:] == 'px':
|
||||||
|
@ -46,11 +52,15 @@ def parse_length_with_units(str):
|
||||||
raise ValueError(_("parseLengthWithUnits: unknown unit %s") % s)
|
raise ValueError(_("parseLengthWithUnits: unknown unit %s") % s)
|
||||||
|
|
||||||
return v, u
|
return v, u
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def convert_length(length):
|
def convert_length(length):
|
||||||
value, units = parse_length_with_units(length)
|
value, units = parse_length_with_units(length)
|
||||||
|
|
||||||
|
return inkex.units.convert_unit(str(value) + units, 'px')
|
||||||
|
|
||||||
|
"""
|
||||||
if not units or units == "px":
|
if not units or units == "px":
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -67,7 +77,7 @@ def convert_length(length):
|
||||||
units = 'mm'
|
units = 'mm'
|
||||||
|
|
||||||
if units == 'mm':
|
if units == 'mm':
|
||||||
value = value / 25.4
|
value /= 25.4
|
||||||
units = 'in'
|
units = 'in'
|
||||||
|
|
||||||
if units == 'in':
|
if units == 'in':
|
||||||
|
@ -76,6 +86,7 @@ def convert_length(length):
|
||||||
return value * 96
|
return value * 96
|
||||||
|
|
||||||
raise ValueError(_("Unknown unit: %s") % units)
|
raise ValueError(_("Unknown unit: %s") % units)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
|
@ -121,7 +132,7 @@ def get_viewbox_transform(node):
|
||||||
|
|
||||||
dx = -float(viewbox[0])
|
dx = -float(viewbox[0])
|
||||||
dy = -float(viewbox[1])
|
dy = -float(viewbox[1])
|
||||||
transform = simpletransform.parseTransform("translate(%f, %f)" % (dx, dy))
|
transform = inkex.transforms.Transform("translate(%f, %f)" % (dx, dy))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sx = doc_width / float(viewbox[2])
|
sx = doc_width / float(viewbox[2])
|
||||||
|
@ -132,8 +143,8 @@ def get_viewbox_transform(node):
|
||||||
if aspect_ratio != 'none':
|
if aspect_ratio != 'none':
|
||||||
sx = sy = max(sx, sy) if 'slice' in aspect_ratio else min(sx, sy)
|
sx = sy = max(sx, sy) if 'slice' in aspect_ratio else min(sx, sy)
|
||||||
|
|
||||||
scale_transform = simpletransform.parseTransform("scale(%f, %f)" % (sx, sy))
|
scale_transform = inkex.transforms.Transform("scale(%f, %f)" % (sx, sy))
|
||||||
transform = simpletransform.composeTransform(transform, scale_transform)
|
transform = transform * scale_transform
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
from color import ThreadColor
|
from .catalog import ThreadCatalog
|
||||||
from palette import ThreadPalette
|
from .color import ThreadColor
|
||||||
from catalog import ThreadCatalog
|
from .palette import ThreadPalette
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from collections import Sequence
|
from collections.abc import Sequence
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from os.path import dirname, realpath
|
from os.path import dirname, realpath
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,7 @@ import colorsys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import tinycss2.color3
|
import tinycss2.color3
|
||||||
|
from inkex import Color
|
||||||
import simplestyle
|
|
||||||
from pyembroidery.EmbThread import EmbThread
|
from pyembroidery.EmbThread import EmbThread
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,14 +18,14 @@ class ThreadColor(object):
|
||||||
self.manufacturer = color.brand
|
self.manufacturer = color.brand
|
||||||
self.rgb = (color.get_red(), color.get_green(), color.get_blue())
|
self.rgb = (color.get_red(), color.get_green(), color.get_blue())
|
||||||
return
|
return
|
||||||
elif isinstance(color, unicode):
|
elif isinstance(color, str):
|
||||||
self.rgb = tinycss2.color3.parse_color(color)
|
self.rgb = tinycss2.color3.parse_color(color)
|
||||||
# remove alpha channel and multiply with 255
|
# remove alpha channel and multiply with 255
|
||||||
self.rgb = tuple(channel * 255.0 for channel in list(self.rgb)[:-1])
|
self.rgb = tuple(channel * 255.0 for channel in list(self.rgb)[:-1])
|
||||||
elif isinstance(color, (list, tuple)):
|
elif isinstance(color, (list, tuple)):
|
||||||
self.rgb = tuple(color)
|
self.rgb = tuple(color)
|
||||||
elif self.hex_str_re.match(color):
|
elif self.hex_str_re.match(color):
|
||||||
self.rgb = simplestyle.parseColor(color)
|
self.rgb = Color.parse_str(color)[1]
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid color: " + repr(color))
|
raise ValueError("Invalid color: " + repr(color))
|
||||||
|
|
||||||
|
@ -77,7 +76,7 @@ class ThreadColor(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hex_digits(self):
|
def hex_digits(self):
|
||||||
return "%02X%02X%02X" % self.rgb
|
return "%02X%02X%02X" % tuple([int(x) for x in self.rgb])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rgb_normalized(self):
|
def rgb_normalized(self):
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from collections import Set
|
from collections.abc import Set
|
||||||
from colormath.color_objects import sRGBColor, LabColor
|
|
||||||
from colormath.color_conversions import convert_color
|
from colormath.color_conversions import convert_color
|
||||||
from colormath.color_diff import delta_e_cie1994
|
from colormath.color_diff import delta_e_cie1994
|
||||||
|
from colormath.color_objects import LabColor, sRGBColor
|
||||||
|
|
||||||
from .color import ThreadColor
|
from .color import ThreadColor
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ class DotDict(dict):
|
||||||
self.dotdictify()
|
self.dotdictify()
|
||||||
|
|
||||||
def _dotdictify(self):
|
def _dotdictify(self):
|
||||||
for k, v in self.iteritems():
|
for k, v in self.items():
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
self[k] = DotDict(v)
|
self[k] = DotDict(v)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import math
|
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):
|
def cut(line, distance, normalized=False):
|
||||||
|
@ -123,9 +124,6 @@ class Point:
|
||||||
def as_tuple(self):
|
def as_tuple(self):
|
||||||
return (self.x, self.y)
|
return (self.x, self.y)
|
||||||
|
|
||||||
def __cmp__(self, other):
|
|
||||||
return cmp(self.as_tuple(), other.as_tuple())
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
return self.as_tuple()[item]
|
return self.as_tuple()[item]
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from os.path import realpath, expanduser, join as path_join
|
|
||||||
import sys
|
import sys
|
||||||
|
from os.path import expanduser, realpath
|
||||||
|
|
||||||
|
|
||||||
def guess_inkscape_config_path():
|
def guess_inkscape_config_path():
|
||||||
if getattr(sys, 'frozen', None):
|
if getattr(sys, 'frozen', None):
|
||||||
path = realpath(path_join(sys._MEIPASS, "..", "..", ".."))
|
path = realpath(sys._MEIPASS.split('extensions', 1)[0])
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
import win32api
|
import win32api
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from cStringIO import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
|
|
||||||
def save_stderr():
|
def save_stderr():
|
||||||
# GTK likes to spam stderr, which inkscape will show in a dialog.
|
# GTK likes to spam stderr, which inkscape will show in a dialog.
|
||||||
null = open(os.devnull, 'w')
|
with open(os.devnull, 'w') as null:
|
||||||
sys.stderr_dup = os.dup(sys.stderr.fileno())
|
sys.stderr_dup = os.dup(sys.stderr.fileno())
|
||||||
sys.real_stderr = os.fdopen(sys.stderr_dup, 'w')
|
sys.real_stderr = os.fdopen(sys.stderr_dup, 'w', encoding='utf-8')
|
||||||
os.dup2(null.fileno(), 2)
|
os.dup2(null.fileno(), 2)
|
||||||
sys.stderr = StringIO()
|
sys.stderr = StringIO()
|
||||||
|
|
||||||
|
|
||||||
def restore_stderr():
|
def restore_stderr():
|
||||||
|
@ -22,11 +22,11 @@ def restore_stderr():
|
||||||
|
|
||||||
|
|
||||||
def save_stdout():
|
def save_stdout():
|
||||||
null = open(os.devnull, 'w')
|
with open(os.devnull, 'w') as null:
|
||||||
sys.stdout_dup = os.dup(sys.stdout.fileno())
|
sys.stdout_dup = os.dup(sys.stdout.fileno())
|
||||||
sys.real_stdout = os.fdopen(sys.stdout_dup, 'w')
|
sys.real_stdout = os.fdopen(sys.stdout_dup, 'w')
|
||||||
os.dup2(null.fileno(), 1)
|
os.dup2(null.fileno(), 1)
|
||||||
sys.stdout = StringIO()
|
sys.stdout = StringIO()
|
||||||
|
|
||||||
|
|
||||||
def restore_stdout():
|
def restore_stdout():
|
||||||
|
|
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Ładowanie…
Reference in New Issue