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
Kaalleen 2021-03-04 18:40:53 +01:00 zatwierdzone przez GitHub
rodzic b39575a501
commit e84a86d4ac
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
135 zmienionych plików z 1435 dodań i 1003 usunięć

Wyświetl plik

@ -10,156 +10,149 @@ jobs:
linux:
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- uses: actions/setup-node@v1
with:
node-version: '11.x'
- name: download dependencies
shell: bash
run: |
curl -sOL https://inkscape.org/en/gallery/item/12187/inkscape-0.92.3.tar.bz2
node-version: '15.x'
- uses: actions/cache@v2
id: pip-cache
with:
path: ~/.cache/pip
key: ${{ runner.os }}-16.04-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-16.04-pip-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: install dependencies
shell: bash
run: |
# I'd love to use a setup-python action but it seems to give a
# python that doesn't support unicode. See:
# https://github.com/actions/setup-python/issues/23
sudo apt-get update
sudo apt-get install python2.7
python -m pip install --upgrade pip
python -m pip install wheel
sudo apt-get install gettext
# for wxPython
sudo apt-get install glib-networking libsdl1.2-dev
sudo apt install glib-networking libsdl1.2-dev
# for PyGObject
sudo apt install libgirepository1.0-dev
sudo apt install libgirepository1.0-dev libcairo2-dev
# for shapely
sudo apt install libgeos-dev
sudo apt install libgeos-dev build-essential libgtk-3-dev
uname -a
python --version
python -m pip --version
python -m pip debug
# wxPython doen't publish linux wheels in pypi
wget -q https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04/wxPython-4.0.6-cp27-cp27mu-linux_x86_64.whl
python -m pip install wxPython*.whl
python -m pip install pycairo==1.11.1
python -m pip install PyGObject==3.30.5
python -m pip install PyGObject
# colormath - last official release: 3.0.0
# we need already submitted fixes - so let's grab them from the github repository
python -m pip install git+https://github.com/gtaylor/python-colormath
python -m pip install -r requirements.txt
python -m pip install pyinstaller==3.3.1
python -m pip install pyinstaller
tar -jxf inkscape-0.92.3.tar.bz2
rm inkscape-0.92.3.tar.bz2
mv inkscape-0.92.3 inkscape
echo "${{ env.pythonLocation }}\bin" >> $GITHUB_PATH
- shell: bash
run: |
make dist
env:
BUILD: linux
- uses: actions/upload-artifact@master
- uses: actions/upload-artifact@v2
with:
name: inkstitch-linux
path: artifacts
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-node@v1
with:
node-version: '11.x'
- uses: actions/setup-python@v1
node-version: '15.x'
- uses: actions/setup-python@v2
with:
python-version: '2.7.x'
python-version: '3.9'
architecture: 'x86'
- uses: microsoft/setup-msbuild@v1.0.2
- name: download dependencies
shell: bash
run: |
curl -sOL https://github.com/lexelby/inkstitch-build-objects/releases/download/v1.0.0/Shapely-1.6.3-cp27-cp27m-win32.whl
curl -sOL https://inkscape.org/en/gallery/item/12187/inkscape-0.92.3.tar.bz2
- name: install dependencies
shell: bash
run: |
pip install Shapely-1.6.3-cp27-cp27m-win32.whl
pip install -r requirements.txt
pip install pyinstaller==3.3.1
python -m pip install --upgrade pip
python -m pip install wheel
# Just using tar -j freezes forever with no output. Heck if I know why. This seems to work.
bzcat inkscape-0.92.3.tar.bz2 | tar -vxf -
rm inkscape-0.92.3.tar.bz2
mv inkscape-0.92.3 inkscape
python -m pip install git+https://github.com/gtaylor/python-colormath
python -m pip install -r requirements.txt
python -m pip install pyinstaller
echo "${{ env.pythonLocation }}\bin" >> $GITHUB_PATH
- name: fix geos
shell: bash
run: |
cd "${{ env.pythonLocation }}\Lib/site-packages/shapely/DLLs"
cp geos_c.dll geos.dll
- shell: bash
run: |
make dist
env:
BUILD: windows
- uses: actions/upload-artifact@master
- uses: actions/upload-artifact@v2
with:
name: inkstitch-windows
path: artifacts
mac:
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- uses: actions/setup-node@v1
with:
node-version: '11.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
node-version: '15.x'
- name: install dependencies
shell: bash
run: |
brew update
# this errors because it installs python3 but python2 is already installed
brew install gtk+3 pkg-config gobject-introspection libffi gettext || true
brew install gtk+3 pkg-config gobject-introspection geos libffi gettext || true
export LDFLAGS="-L/usr/local/opt/libffi/lib"
export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig"
# for msgfmt
echo "/usr/local/opt/gettext/bin" >> $GITHUB_PATH
echo "GI_TYPELIB_PATH=/usr/local/lib/girepository-1.0/" >> $GITHUB_ENV
pip install --upgrade pip
pip --version
pip install wheel
pip install PyGObject
pip install git+https://github.com/gtaylor/python-colormath
pip install -r requirements.txt
pip install pyinstaller==3.3.1
tar -jxf inkscape-0.92.3.tar.bz2
rm inkscape-0.92.3.tar.bz2
mv inkscape-0.92.3 inkscape
pip install pyinstaller
echo "${{ env.pythonLocation }}/bin" >> $GITHUB_PATH
- shell: bash
@ -167,7 +160,7 @@ jobs:
make dist
env:
BUILD: osx
- uses: actions/upload-artifact@master
- uses: actions/upload-artifact@v2
with:
name: inkstitch-mac
path: artifacts

1
.gitignore vendored
Wyświetl plik

@ -15,3 +15,4 @@ locales/
/debug.log
/debug.svg
/.idea
/VERSION

Wyświetl plik

@ -1,5 +1,5 @@
dist: locales inx
dist: version locales inx
bash bin/build-python
bash bin/build-electron
bash bin/build-distribution-archives
@ -8,7 +8,7 @@ distclean:
rm -rf build dist inx locales *.spec *.tar.gz *.zip electron/node_modules electron/dist
.PHONY: inx
inx: locales
inx: version locales
mkdir -p inx
python bin/generate-inx-files; \
@ -49,6 +49,10 @@ locales:
mkdir -p locales; \
fi
.PHONY: version
version:
bash bin/generate-version-file
.PHONY: style
style:
flake8 . --count --max-complexity=10 --max-line-length=150 --statistics --exclude=pyembroidery,__init__.py,electron,build

Wyświetl plik

@ -4,12 +4,14 @@ VERSION="$(echo ${GITHUB_REF} | sed -e 's|refs/heads/||' -e 's|refs/tags/||' -e
OS="${BUILD:-$(uname)}"
ARCH="$(uname -m)"
cp -a images/examples palettes symbols fonts dist/inkstitch
cp -a icons locales print dist/inkstitch/bin
if [ "$BUILD" = "osx" ]; then
cp -a electron/build/mac dist/inkstitch/electron
cp -a images/examples palettes symbols fonts LICENSE VERSION dist/inkstitch.app/Contents
cp -a icons locales print dist/inkstitch.app/Contents/MacOS
cp -a electron/build/mac dist/inkstitch.app/Contents/electron
rm -rf dist/inkstitch/
else
cp -a images/examples palettes symbols fonts LICENSE VERSION dist/inkstitch
cp -a icons locales print dist/inkstitch/bin
cp -a electron/build/*-unpacked dist/inkstitch/electron
fi

Wyświetl plik

@ -52,8 +52,3 @@ shopt -s dotglob
mkdir dist/bin
mv dist/inkstitch/* dist/bin
mv dist/bin dist/inkstitch
# on Mac, pyinstaller creates a .app version as well, but we don't need that
if [ "$BUILD" = "osx" ]; then
rm -rf dist/inkstitch.app/
fi

Wyświetl plik

@ -0,0 +1,10 @@
#!/bin/bash
VERSION="${GITHUB_REF##*/}"
OS="${BUILD:-$(uname)}"
if [[ "$VERSION" == "" ]]; then
VERSION="Manual Install"
fi
echo "${VERSION} (${OS})" > VERSION

Wyświetl plik

@ -12,8 +12,8 @@ for font in sorted(os.listdir(fonts_dir)):
with open(os.path.join(fonts_dir, font, "font.json")) as font_json:
font_metadata = json.load(font_json)
print "# L10N name of font in fonts/%s" % font
print "_(%s)" % repr(font_metadata.get("name", ""))
print("# L10N name of font in fonts/%s" % font)
print("_(%s)" % repr(font_metadata.get("name", "")))
print "# L10N description of font in fonts/%s" % font
print "_(%s)" % repr(font_metadata.get("description", ""))
print("# L10N description of font in fonts/%s" % font)
print("_(%s)" % repr(font_metadata.get("description", "")))

Wyświetl plik

@ -6,5 +6,5 @@ import pyembroidery
# generate fake python code containing the descriptions of pyembroidery formats
# as gettext calls so that pybabel will extract them into messages.po
for format in pyembroidery.supported_formats():
print "# L10N description for pyembroidery file format: %s" % format['extension']
print "_(%s)" % repr(format['description'])
print("# L10N description for pyembroidery file format: %s" % format['extension'])
print("_(%s)" % repr(format['description']))

Wyświetl plik

@ -103,12 +103,6 @@ let rendererConfig = {
'css-loader',
{
loader: 'sass-loader',
// Requires sass-loader@^7.0.0
options: {
implementation: require('sass'),
fiber: require('fibers'),
indentedSyntax: true // optional
},
// Requires sass-loader@^8.0.0
options: {
implementation: require('sass'),
@ -132,6 +126,18 @@ let rendererConfig = {
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
templateParameters(compilation, assets, options) {
return {
compilation: compilation,
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: options
},
process
}
},
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,

Wyświetl plik

@ -77,12 +77,6 @@ let webConfig = {
'css-loader',
{
loader: 'sass-loader',
// Requires sass-loader@^7.0.0
options: {
implementation: require('sass'),
fiber: require('fibers'),
indentedSyntax: true // optional
},
// Requires sass-loader@^8.0.0
options: {
implementation: require('sass'),
@ -102,6 +96,18 @@ let webConfig = {
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
templateParameters(compilation, assets, options) {
return {
compilation: compilation,
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: options
},
process
}
},
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,

Wyświetl plik

@ -1,9 +1,27 @@
module.exports.selectLanguage = function () {
['LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'].forEach(language => {
module.exports.selectLanguage = function (translations) {
// get a list of available translations
var availableTranslations = ['en_US'];
for(var k in translations) availableTranslations.push(k);
var lang = undefined;
// get system language / Inkscape language
['LANG', 'LC_MESSAGES', 'LC_ALL', 'LANGUAGE'].forEach(language => {
if (process.env[language]) {
return process.env[language].split(":")[0]
// split encoding information, we don't need it
var current_lang = process.env[language].split(".")[0];
if (current_lang.length == 2) {
// current language has only two letters (e.g. en),
// compare with available languages and if present, set to a long locale name (e.g. en_US)
lang = availableTranslations.find(elem => elem.startsWith(current_lang));
} else {
lang = current_lang;
}
}
})
return "en_US"
// set default language
if (lang === undefined) {
lang = "en_US"
}
return lang
}

Wyświetl plik

@ -61,9 +61,7 @@ function createWindow() {
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {

Wyświetl plik

@ -126,7 +126,7 @@ export default {
return ""
}
let label = "STITCH"
let label = this.$gettext("STITCH")
switch (true) {
case stitch.jump:
label = this.$gettext("JUMP")

Wyświetl plik

@ -20,7 +20,18 @@
}
button {
color: rgb(0, 51, 153)
color: rgb(0, 51, 153);
align-items: flex-start;
text-align: center;
cursor: default;
background-color: buttonface;
box-sizing: border-box;
padding: 2px 6px 3px;
border-width: 2px;
border-style: outset;
border-color: buttonface;
border-image: initial;
margin-bottom: 5px;
}
.fa-spin-fast {
@ -59,11 +70,13 @@ button {
text-align: center;
flex: 1;
white-space: nowrap;
margin: 0 5px;
}
fieldset {
border: 2px solid rgb(0, 51, 153);
position: relative;
padding: 0 5px
}
.window-controls {
@ -142,6 +155,10 @@ fieldset.show-commands {
text-align: left;
}
fieldset.show-commands legend {
text-align: center;
}
fieldset.show-commands span {
display: inline-block;
vertical-align: top;
@ -152,7 +169,7 @@ fieldset.show-commands span.npp {
}
fieldset.show-commands span:first-of-type {
padding-right: 12px;
padding: 0 5px;
}
button.pressed {
@ -164,8 +181,7 @@ button.pressed {
}
.slider-container {
margin-top: 25px;
margin-bottom: 25px;
margin: 25px 5px;
flex-grow: 0;
}
@ -244,12 +260,15 @@ button.pressed {
width: 4rem;
float: right;
font-size: 1rem;
border-style: inset;
padding: 0 3px;
}
.simulator {
display: flex;
flex-direction: column;
height: 95vh;
margin: 10px;
}
.current-command {
@ -269,6 +288,7 @@ div.simulator::v-deep svg.simulation {
div.simulator::v-deep svg.simulation-scale {
height: 50px;
order: -1;
margin-left: 5px;
}
div.simulator::v-deep .simulation-scale-label {

Wyświetl plik

@ -189,7 +189,9 @@
<span class="current-command">{{currentCommand}}</span>
</fieldset>
<fieldset class="show-commands">
<legend>Show</legend>
<legend>
<translate>Show</translate>
</legend>
<span>
<input id="trim-checkbox" type="checkbox" v-model="showTrims"/>
<label for="trim-checkbox"><font-awesome-icon icon="cut"/> <translate>trims</translate></label>
@ -211,7 +213,7 @@
<font-awesome-icon icon="circle" transform="shrink-9"/>
<font-awesome-icon icon="minus" class="fa-thin-line"/>
</font-awesome-layers>
<span v-translate>needle<br/>points</span>
<span v-translate>needle points</span>
</label>
</span>
<span>

Wyświetl plik

@ -73,7 +73,7 @@ Vue.component('font-awesome-layers', FontAwesomeLayers)
Vue.use(Transitions)
Vue.use(GetTextPlugin, {
translations: translations,
defaultLanguage: selectLanguage(),
defaultLanguage: selectLanguage(translations),
silent: true
})

Wyświetl plik

@ -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

Wyświetl plik

@ -3,12 +3,15 @@ import os
import sys
import traceback
from argparse import ArgumentParser
from cStringIO import StringIO
from io import StringIO
from inkex import errormsg
from lxml.etree import XMLSyntaxError
import lib.debug as debug
from lib import extensions
from lib.i18n import _
from lib.utils import restore_stderr, save_stderr
from lib.utils import restore_stderr, save_stderr, version
logger = logging.getLogger('shapely.geos')
logger.setLevel(logging.DEBUG)
@ -36,28 +39,35 @@ extension_class = getattr(extensions, extension_class_name)
extension = extension_class()
if hasattr(sys, 'gettrace') and sys.gettrace():
extension.affect(args=remaining_args)
extension.run(args=remaining_args)
else:
save_stderr()
exception = None
try:
extension.affect(args=remaining_args)
extension.run(args=remaining_args)
except (SystemExit, KeyboardInterrupt):
raise
except XMLSyntaxError:
msg = _("Ink/Stitch cannot read your SVG file. "
"This is often the case when you use a file which has been created with Adobe Illustrator.")
msg += "\n\n"
msg += _("Try to import the file into Inkscape through 'File > Import...' (Ctrl+I)")
errormsg(msg)
except Exception:
exception = traceback.format_exc()
finally:
restore_stderr()
if shapely_errors.tell():
print >> sys.stderr, shapely_errors.getvalue()
errormsg(shapely_errors.getvalue())
if exception:
print >> sys.stderr, _("Ink/Stitch experienced an unexpected error.").encode("UTF-8")
print >> sys.stderr, _("If you'd like to help, please file an issue at "
"https://github.com/inkstitch/inkstitch/issues "
"and include the entire error description below:").encode("UTF-8"), "\n"
print >> sys.stderr, exception
errormsg(_("Ink/Stitch experienced an unexpected error.") + "\n")
errormsg(_("If you'd like to help, please file an issue at "
"https://github.com/inkstitch/inkstitch/issues "
"and include the entire error description below:") + "\n")
errormsg(version.get_inkstitch_version() + "\n")
errormsg(exception)
sys.exit(1)
else:
sys.exit(0)

Wyświetl plik

@ -16,7 +16,7 @@ def palettes():
path = os.path.join(base_path, 'palettes')
src_dir = get_bundled_dir('palettes')
copy_files(glob(os.path.join(src_dir, "*")), path)
except Exception, exc:
except Exception as exc:
return jsonify({"error": str(exc)}), 500
return jsonify({"status": "success"})

Wyświetl plik

@ -1,16 +1,17 @@
import errno
import logging
import socket
import sys
import time
from threading import Thread
import requests
from flask import Flask, g, request
from ..utils.json import InkStitchJSONEncoder
from .install import install
from .simulator import simulator
from .stitch_plan import stitch_plan
from ..utils.json import InkStitchJSONEncoder
class APIServer(Thread):
@ -27,6 +28,10 @@ class APIServer(Thread):
self.__setup_app()
def __setup_app(self): # noqa: C901
# Disable warning about using a development server in a production environment
cli = sys.modules['flask.cli']
cli.show_server_banner = lambda *x: None
self.app = Flask(__name__)
self.app.json_encoder = InkStitchJSONEncoder
@ -89,7 +94,7 @@ class APIServer(Thread):
response = requests.get("http://%s:%s/ping" % (self.host, self.port))
if response.status_code == 200:
break
except socket.error, e:
except socket.error as e:
if e.errno == errno.ECONNREFUSED:
pass
else:

Wyświetl plik

@ -11,7 +11,9 @@ def get_stitch_plan():
if not g.extension.get_elements():
return dict(colors=[], stitch_blocks=[], commands=[])
metadata = g.extension.get_inkstitch_metadata()
collapse_len = metadata['collapse_len_mm']
patches = g.extension.elements_to_patches(g.extension.elements)
stitch_plan = patches_to_stitch_plan(patches)
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
return jsonify(stitch_plan)

Wyświetl plik

@ -3,11 +3,9 @@ import sys
from copy import deepcopy
from random import random
from shapely import geometry as shgeo
import cubicsuperpath
import inkex
import simpletransform
from lxml import etree
from shapely import geometry as shgeo
from .i18n import N_, _
from .svg import (apply_transforms, generate_unique_id,
@ -104,7 +102,7 @@ class Command(BaseCommand):
self.parse_command()
def parse_connector_path(self):
path = cubicsuperpath.parsePath(self.connector.get('d'))
path = inkex.paths.Path(self.connector.get('d')).to_superpath()
return apply_transforms(path, self.connector)
def parse_command(self):
@ -153,7 +151,7 @@ class StandaloneCommand(BaseCommand):
def point(self):
pos = [float(self.node.get("x", 0)), float(self.node.get("y", 0))]
transform = get_node_transform(self.node)
simpletransform.applyTransformToPoint(transform, pos)
pos = inkex.transforms.Transform(transform).apply_to_point(pos)
return Point(*pos)
@ -209,14 +207,14 @@ def global_command(svg, command):
if len(commands) == 1:
return commands[0]
elif len(commands) > 1:
print >> sys.stderr, _("Error: there is more than one %(command)s command in the document, but there can only be one. "
"Please remove all but one.") % dict(command=command)
print(_("Error: there is more than one %(command)s command in the document, but there can only be one. "
"Please remove all but one.") % dict(command=command), file=sys.stderr)
# L10N This is a continuation of the previous error message, letting the user know
# what command we're talking about since we don't normally expose the actual
# command name to them. Contents of %(description)s are in a separate translation
# string.
print >> sys.stderr, _("%(command)s: %(description)s") % dict(command=command, description=_(get_command_description(command)))
print(_("%(command)s: %(description)s") % dict(command=command, description=_(get_command_description(command))), file=sys.stderr)
sys.exit(1)
else:
@ -256,7 +254,7 @@ def symbols_path():
@cache
def symbols_svg():
with open(symbols_path()) as symbols_file:
return inkex.etree.parse(symbols_file)
return etree.parse(symbols_file)
@cache
@ -269,7 +267,7 @@ def get_defs(document):
defs = document.find(SVG_DEFS_TAG)
if defs is None:
defs = inkex.etree.SubElement(document, SVG_DEFS_TAG)
defs = etree.SubElement(document, SVG_DEFS_TAG)
return defs
@ -284,7 +282,7 @@ def ensure_symbol(document, command):
def add_group(document, node, command):
return inkex.etree.SubElement(
return etree.SubElement(
node.getparent(),
SVG_GROUP_TAG,
{
@ -304,35 +302,35 @@ def add_connector(document, symbol, element):
if element.node.get('id') is None:
element.node.set('id', generate_unique_id(document, "object"))
path = inkex.etree.Element(SVG_PATH_TAG,
{
"id": generate_unique_id(document, "command_connector"),
"d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y),
"style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
CONNECTION_START: "#%s" % symbol.get('id'),
CONNECTION_END: "#%s" % element.node.get('id'),
CONNECTOR_TYPE: "polyline",
path = etree.Element(SVG_PATH_TAG,
{
"id": generate_unique_id(document, "command_connector"),
"d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y),
"style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
CONNECTION_START: "#%s" % symbol.get('id'),
CONNECTION_END: "#%s" % element.node.get('id'),
CONNECTOR_TYPE: "polyline",
# l10n: the name of the line that connects a command to the object it applies to
INKSCAPE_LABEL: _("connector")
})
# l10n: the name of the line that connects a command to the object it applies to
INKSCAPE_LABEL: _("connector")
})
symbol.getparent().insert(0, path)
def add_symbol(document, group, command, pos):
return inkex.etree.SubElement(group, SVG_USE_TAG,
{
"id": generate_unique_id(document, "command_use"),
XLINK_HREF: "#inkstitch_%s" % command,
"height": "100%",
"width": "100%",
"x": str(pos.x),
"y": str(pos.y),
return etree.SubElement(group, SVG_USE_TAG,
{
"id": generate_unique_id(document, "command_use"),
XLINK_HREF: "#inkstitch_%s" % command,
"height": "100%",
"width": "100%",
"x": str(pos.x),
"y": str(pos.y),
# l10n: the name of a command symbol (example: scissors icon for trim command)
INKSCAPE_LABEL: _("command marker"),
})
# l10n: the name of a command symbol (example: scissors icon for trim command)
INKSCAPE_LABEL: _("command marker"),
})
def get_command_pos(element, index, total):
@ -397,14 +395,14 @@ def add_layer_commands(layer, commands):
for command in commands:
ensure_symbol(document, command)
inkex.etree.SubElement(layer, SVG_USE_TAG,
{
"id": generate_unique_id(document, "use"),
INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
XLINK_HREF: "#inkstitch_%s" % command,
"height": "100%",
"width": "100%",
"x": "0",
"y": "-10",
"transform": correction_transform
})
etree.SubElement(layer, SVG_USE_TAG,
{
"id": generate_unique_id(document, "use"),
INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
XLINK_HREF: "#inkstitch_%s" % command,
"height": "100%",
"width": "100%",
"x": "0",
"y": "-10",
"transform": correction_transform
})

Wyświetl plik

@ -1,17 +1,16 @@
import atexit
from contextlib import contextmanager
from datetime import datetime
import os
import socket
import sys
import time
from contextlib import contextmanager
from datetime import datetime
from inkex import etree
import inkex
from simplestyle import formatStyle
from lxml import etree
from svg import line_strings_to_path
from svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
from .svg import line_strings_to_path
from .svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
def check_enabled(func):
@ -36,7 +35,10 @@ class Debug(object):
self.init_svg()
def init_log(self):
self.log_file = open(os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.log"), "w")
self.log_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.log")
# delete old content
with open(self.log_file, "w"):
pass
self.log("Debug logging enabled.")
def init_debugger(self):
@ -60,7 +62,7 @@ class Debug(object):
try:
pydevd.settrace()
except socket.error, error:
except socket.error as error:
self.log("Debugging: connection to pydevd failed: %s", error)
self.log("Be sure to run 'Start debugging server' in PyDev to enable debugging.")
else:
@ -74,8 +76,8 @@ class Debug(object):
def save_svg(self):
tree = etree.ElementTree(self.svg)
with open(os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.svg"), "w") as debug_svg:
tree.write(debug_svg)
debug_svg = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.svg")
tree.write(debug_svg)
@check_enabled
def add_layer(self, name="Debug"):
@ -113,20 +115,21 @@ class Debug(object):
timestamp = now.isoformat()
self.last_log_time = now
print >> self.log_file, timestamp, message % args
self.log_file.flush()
with open(self.log_file, "a") as logfile:
print(timestamp, message % args, file=logfile)
logfile.flush()
def time(self, func):
def decorated(*args, **kwargs):
if self.enabled:
self.raw_log("entering %s()", func.func_name)
self.raw_log("entering %s()", func.__name__)
start = time.time()
result = func(*args, **kwargs)
if self.enabled:
end = time.time()
self.raw_log("leaving %s(), duration = %s", func.func_name, round(end - start, 6))
self.raw_log("leaving %s(), duration = %s", func.__name__, round(end - start, 6))
return result
@ -150,7 +153,7 @@ class Debug(object):
@check_enabled
def log_line_strings(self, line_strings, name=None, color=None):
path = line_strings_to_path(line_strings)
path.set('style', formatStyle({"stroke": color or "#000000", "stroke-width": "0.3"}))
path.set('style', str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3"})))
if name is not None:
path.set(INKSCAPE_LABEL, name)
@ -161,7 +164,7 @@ class Debug(object):
def log_line(self, start, end, name="line", color=None):
self.log_svg_element(etree.Element("path", {
"d": "M%s,%s %s,%s" % (start + end),
"style": formatStyle({"stroke": color or "#000000", "stroke-width": "0.3"}),
"style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3"})),
INKSCAPE_LABEL: name
}))
@ -174,7 +177,7 @@ class Debug(object):
self.log_svg_element(etree.Element("path", {
"d": d,
"style": formatStyle({"stroke": color or "#000000", "stroke-width": "0.3"}),
"style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3"})),
INKSCAPE_LABEL: name
}))

Wyświetl plik

@ -1,11 +1,11 @@
from auto_fill import AutoFill
from clone import Clone
from element import EmbroideryElement
from empty_d_object import EmptyDObject
from fill import Fill
from image import ImageObject
from polyline import Polyline
from satin_column import SatinColumn
from stroke import Stroke
from text import TextObject
from utils import node_to_elements, nodes_to_elements
from .auto_fill import AutoFill
from .clone import Clone
from .element import EmbroideryElement
from .empty_d_object import EmptyDObject
from .fill import Fill
from .image import ImageObject
from .polyline import Polyline
from .satin_column import SatinColumn
from .stroke import Stroke
from .text import TextObject
from .utils import node_to_elements, nodes_to_elements

Wyświetl plik

@ -6,10 +6,9 @@ from shapely import geometry as shgeo
from ..i18n import _
from ..stitches import auto_fill
from ..utils import cache
from ..utils import cache, version
from .element import Patch, param
from .fill import Fill
from .validation import ValidationWarning
@ -20,6 +19,18 @@ class SmallShapeWarning(ValidationWarning):
"the outline instead.")
class ExpandWarning(ValidationWarning):
name = _("Expand")
description = _("The expand parameter for this fill object cannot be applied. "
"Ink/Stitch will ignore it and will use original size instead.")
class UnderlayInsetWarning(ValidationWarning):
name = _("Inset")
description = _("The underlay inset parameter for this fill object cannot be applied. "
"Ink/Stitch will ignore it and will use the original size instead.")
class AutoFill(Fill):
element_name = _("AutoFill")
@ -157,9 +168,13 @@ class AutoFill(Fill):
def underlay_underpath(self):
return self.get_boolean_param('underlay_underpath', True)
def shrink_or_grow_shape(self, amount):
def shrink_or_grow_shape(self, amount, validate=False):
if amount:
shape = self.shape.buffer(amount)
# changing the size can empty the shape
# in this case we want to use the original shape rather than returning an error
if shape.is_empty and not validate:
return self.shape
if not isinstance(shape, shgeo.MultiPolygon):
shape = shgeo.MultiPolygon([shape])
return shape
@ -235,6 +250,7 @@ class AutoFill(Fill):
# L10N this message is followed by a URL: https://github.com/inkstitch/inkstitch/issues/new
message += _("If you'd like to help us make Ink/Stitch better, please paste this whole message into a new issue at: ")
message += "https://github.com/inkstitch/inkstitch/issues/new\n\n"
message += version.get_inkstitch_version() + "\n\n"
message += traceback.format_exc()
self.fatal(message)
@ -245,5 +261,11 @@ class AutoFill(Fill):
if self.shape.area < 20:
yield SmallShapeWarning(self.shape.centroid)
if self.shrink_or_grow_shape(self.expand, True).is_empty:
yield ExpandWarning(self.shape.centroid)
if self.shrink_or_grow_shape(-self.fill_underlay_inset, True).is_empty:
yield UnderlayInsetWarning(self.shape.centroid)
for warning in super(AutoFill, self).validation_warnings():
yield warning

Wyświetl plik

@ -1,16 +1,13 @@
from copy import copy
from math import atan, degrees
from simpletransform import (applyTransformToNode, applyTransformToPoint,
computeBBox, parseTransform)
import inkex
from ..commands import is_command, is_command_symbol
from ..i18n import _
from ..svg.path import get_node_transform
from ..svg.svg import find_elements
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS, SVG_GROUP_TAG,
SVG_LINK_TAG, SVG_POLYLINE_TAG, SVG_USE_TAG,
XLINK_HREF)
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS,
SVG_POLYLINE_TAG, SVG_USE_TAG, XLINK_HREF)
from ..utils import cache
from .auto_fill import AutoFill
from .element import EmbroideryElement, param
@ -74,16 +71,16 @@ class Clone(EmbroideryElement):
if node.tag == SVG_POLYLINE_TAG:
return [Polyline(node)]
elif element.get_boolean_param("satin_column") and element.get_style("stroke"):
elif element.get_boolean_param("satin_column") and self.get_clone_style("stroke", self.node):
return [SatinColumn(node)]
else:
elements = []
if element.get_style("fill", "black") and not element.get_style("fill-opacity", 1) == "0":
if element.get_style("fill", "black") and not element.get_style("stroke", 1) == "0":
if element.get_boolean_param("auto_fill", True):
elements.append(AutoFill(node))
else:
elements.append(Fill(node))
if element.get_style("stroke"):
if element.get_style("stroke", self.node) is not None:
if not is_command(element.node):
elements.append(Stroke(node))
if element.get_boolean_param("stroke_first", False):
@ -98,32 +95,8 @@ class Clone(EmbroideryElement):
if source_node.tag not in EMBROIDERABLE_TAGS:
return []
clone = copy(source_node)
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
# b. calculated rotation for the cloned fill element to look exactly as it's source
param = INKSTITCH_ATTRIBS['angle']
@ -131,48 +104,32 @@ class Clone(EmbroideryElement):
angle = self.clone_fill_angle
else:
# clone angle
clone_mat = parseTransform(clone.get('transform', ''))
clone_mat = self.node.composed_transform()
clone_angle = degrees(atan(-clone_mat[1][0]/clone_mat[1][1]))
# source node angle
source_mat = parseTransform(source_node.get('transform', ''))
source_mat = source_node.composed_transform()
source_angle = degrees(atan(-source_mat[1][0]/source_mat[1][1]))
# source node fill angle
source_fill_angle = source_node.get(param, 0)
angle = clone_angle + float(source_fill_angle) - source_angle
clone.set(param, str(angle))
self.node.set(param, str(angle))
elements = self.clone_to_element(clone)
elements = self.clone_to_element(self.node)
for element in elements:
patches.extend(element.to_patches(last_patch))
return patches
def _get_clone_style_raw(self, style_name, node):
style = self.parse_style()
style = style.get(style_name) or self.node.get(style_name)
parent = self.node.getparent()
# style not found, get inherited style elements
while not style and parent is not None:
if parent.tag not in [SVG_GROUP_TAG, SVG_LINK_TAG]:
parent = parent.getparent()
continue
style = self.parse_style(parent)
style = style.get(style_name) or parent.get(style_name)
parent = parent.getparent()
return style
def get_clone_style(self, style_name, node, default=None):
style = self._get_clone_style_raw(style_name, node) or default
style = inkex.styles.AttrFallbackStyle(node).get(style_name) or default
return style
def center(self, source_node):
xmin, xmax, ymin, ymax = computeBBox([source_node])
point = [(xmax-((xmax-xmin)/2)), (ymax-((ymax-ymin)/2))]
transform = get_node_transform(self.node)
applyTransformToPoint(transform, point)
return point
transform = get_node_transform(self.node.getparent())
center = self.node.bounding_box(transform).center
return center
def validation_warnings(self):
source_node = get_clone_source(self.node)

Wyświetl plik

@ -1,18 +1,16 @@
import sys
from copy import deepcopy
import cubicsuperpath
import simpletransform
import inkex
import tinycss2
from cspsubdiv import cspsubdiv
from inkex import bezier
from .svg_objects import circle_to_path, ellipse_to_path, rect_to_path
from ..commands import find_commands
from ..i18n import _
from ..svg import (PIXELS_PER_MM, apply_transforms, convert_length,
get_node_transform)
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSCAPE_LABEL, INKSTITCH_ATTRIBS, SVG_CIRCLE_TAG, SVG_ELLIPSE_TAG, SVG_GROUP_TAG, SVG_LINK_TAG,
SVG_OBJECT_TAGS, SVG_RECT_TAG)
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSCAPE_LABEL, INKSTITCH_ATTRIBS,
SVG_GROUP_TAG, SVG_LINK_TAG, SVG_USE_TAG)
from ..utils import Point, cache
@ -155,22 +153,29 @@ class EmbroideryElement(object):
def parse_style(self, node=None):
if node is None:
node = self.node
element_style = node.get("style", "")
if element_style is None:
return None
declarations = tinycss2.parse_declaration_list(node.get("style", ""))
style = {declaration.lower_name: declaration.value[0].serialize() for declaration in declarations}
return style
@cache
def _get_style_raw(self, style_name):
if self.node.tag not in [SVG_GROUP_TAG, SVG_LINK_TAG] and self.node.tag not in EMBROIDERABLE_TAGS:
if self.node is None:
return None
if self.node.tag not in [SVG_GROUP_TAG, SVG_LINK_TAG, SVG_USE_TAG] and self.node.tag not in EMBROIDERABLE_TAGS:
return None
style = self.parse_style()
style = style.get(style_name) or self.node.get(style_name)
if style:
style = style.get(style_name) or self.node.get(style_name)
parent = self.node.getparent()
# style not found, get inherited style elements
while not style and parent is not None:
style = self.parse_style(parent)
style = style.get(style_name) or parent.get(style_name)
if style:
style = style.get(style_name) or parent.get(style_name)
parent = parent.getparent()
return style
@ -196,23 +201,23 @@ class EmbroideryElement(object):
# Of course, transforms may also involve rotation, skewing, and translation.
# All except translation can affect how wide the stroke appears on the screen.
node_transform = get_node_transform(self.node)
node_transform = inkex.transforms.Transform(get_node_transform(self.node))
# First, figure out the translation component of the transform. Using a zero
# vector completely cancels out the rotation, scale, and skew components.
zero = [0, 0]
simpletransform.applyTransformToPoint(node_transform, zero)
zero = inkex.Transform.apply_to_point(node_transform, zero)
translate = Point(*zero)
# Next, see how the transform affects unit vectors in the X and Y axes. We
# need to subtract off the translation or it will affect the magnitude of
# the resulting vector, which we don't want.
unit_x = [1, 0]
simpletransform.applyTransformToPoint(node_transform, unit_x)
unit_x = inkex.Transform.apply_to_point(node_transform, unit_x)
sx = (Point(*unit_x) - translate).length()
unit_y = [0, 1]
simpletransform.applyTransformToPoint(node_transform, unit_y)
unit_y = inkex.Transform.apply_to_point(node_transform, unit_y)
sy = (Point(*unit_y) - translate).length()
# Take the average as a best guess.
@ -223,11 +228,7 @@ class EmbroideryElement(object):
@property
@cache
def stroke_width(self):
width = self.get_style("stroke-width", None)
if width is None:
return 1.0
width = self.get_style("stroke-width", "1.0")
width = convert_length(width)
return width * self.stroke_scale
@ -271,20 +272,15 @@ class EmbroideryElement(object):
# In a path, each element in the 3-tuple is itself a tuple of (x, y).
# Tuples all the way down. Hasn't anyone heard of using classes?
if self.node.tag in SVG_OBJECT_TAGS:
if self.node.tag == SVG_RECT_TAG:
d = rect_to_path(self.node)
elif self.node.tag == SVG_ELLIPSE_TAG:
d = ellipse_to_path(self.node)
elif self.node.tag == SVG_CIRCLE_TAG:
d = circle_to_path(self.node)
if getattr(self.node, "get_path", None):
d = self.node.get_path()
else:
d = self.node.get("d", "")
if not d:
self.fatal(_("Object %(id)s has an empty 'd' attribute. Please delete this object from your document.") % dict(id=self.node.get("id")))
return cubicsuperpath.parsePath(d)
return inkex.paths.Path(d).to_superpath()
@cache
def parse_path(self):
@ -315,7 +311,7 @@ class EmbroideryElement(object):
return commands[0]
elif len(commands) > 1:
raise ValueError(_("%(id)s has more than one command of type '%(command)s' linked to it") %
dict(id=self.node.get(id), command=command))
dict(id=self.node.get('id'), command=command))
else:
return None
@ -326,13 +322,13 @@ class EmbroideryElement(object):
"""approximate a path containing beziers with a series of points"""
path = deepcopy(path)
cspsubdiv(path, 0.1)
bezier.cspsubdiv(path, 0.1)
return [self.strip_control_points(subpath) for subpath in path]
def flatten_subpath(self, subpath):
path = [deepcopy(subpath)]
cspsubdiv(path, 0.1)
bezier.cspsubdiv(path, 0.1)
return self.strip_control_points(path[0])
@ -373,7 +369,7 @@ class EmbroideryElement(object):
# L10N used when showing an error message to the user such as
# "Some Path (path1234): error: satin column: One or more of the rungs doesn't intersect both rails."
error_msg = "%s: %s %s" % (name, _("error:"), message)
print >> sys.stderr, "%s" % (error_msg.encode("UTF-8"))
inkex.errormsg(error_msg)
sys.exit(1)
def validation_errors(self):

Wyświetl plik

@ -136,6 +136,12 @@ class Fill(EmbroideryElement):
# biggest path.
paths = self.paths
paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
# Very small holes will cause a shape to be rendered as an outline only
# they are too small to be rendered and only confuse the auto_fill algorithm.
# So let's ignore them
if shgeo.Polygon(paths[0]).area > 5 and shgeo.Polygon(paths[-1]).area < 5:
paths = [path for path in paths if shgeo.Polygon(path).area > 3]
polygon = shgeo.MultiPolygon([(paths[0], paths[1:])])
# There is a great number of "crossing border" errors on fill shapes

Wyświetl plik

@ -1,7 +1,5 @@
from simpletransform import applyTransformToPoint
from ..i18n import _
from ..svg import get_node_transform
from ..svg.path import get_node_transform
from .element import EmbroideryElement
from .validation import ObjectTypeWarning
@ -19,13 +17,9 @@ class ImageTypeWarning(ObjectTypeWarning):
class ImageObject(EmbroideryElement):
def center(self):
point = [float(self.node.get('x', 0)), float(self.node.get('y', 0))]
point = [(point[0]+(float(self.node.get('width', 0))/2)), (point[1]+(float(self.node.get('height', 0))/2))]
transform = get_node_transform(self.node)
applyTransformToPoint(transform, point)
return point
transform = get_node_transform(self.node.getparent())
center = self.node.bounding_box(transform).center
return center
def validation_warnings(self):
yield ImageTypeWarning(self.center())

Wyświetl plik

@ -1,3 +1,4 @@
from inkex import Path
from shapely import geometry as shgeo
from ..i18n import _
@ -36,7 +37,7 @@ class Polyline(EmbroideryElement):
@property
@param('polyline', _('Manual stitch along path'), type='toggle', inverse=True)
def satin_column(self):
def polyline(self):
return self.get_boolean_param("polyline")
@property
@ -61,8 +62,8 @@ class Polyline(EmbroideryElement):
# svg transforms that is in our superclass, we'll convert the polyline
# to a degenerate cubic superpath in which the bezier handles are on
# the segment endpoints.
path = [[[point[:], point[:], point[:]] for point in self.points]]
path = self.node.get_path()
path = Path(path).to_superpath()
return path

Wyświetl plik

@ -1,14 +1,15 @@
from copy import deepcopy
from itertools import chain, izip
from itertools import chain
import cubicsuperpath
from shapely import affinity as shaffinity, geometry as shgeo
from inkex import paths
from shapely import affinity as shaffinity
from shapely import geometry as shgeo
from .element import EmbroideryElement, Patch, param
from .validation import ValidationError
from ..i18n import _
from ..svg import line_strings_to_csp, point_lists_to_csp
from ..utils import Point, cache, collapse_duplicate_point, cut
from .element import EmbroideryElement, Patch, param
from .validation import ValidationError
class SatinHasFillError(ValidationError):
@ -234,7 +235,7 @@ class SatinColumn(EmbroideryElement):
rung_endpoints.append(points)
rungs = []
for start, end in izip(*rung_endpoints):
for start, end in zip(*rung_endpoints):
# Expand the points just a bit to ensure that shapely thinks they
# intersect with the rails even with floating point inaccuracy.
start = Point(*start)
@ -266,12 +267,12 @@ class SatinColumn(EmbroideryElement):
if num_paths <= 2:
# old-style satin column with no rungs
return range(num_paths)
return list(range(num_paths))
# This takes advantage of the fact that sum() counts True as 1
intersection_counts = [sum(paths[i].intersects(paths[j]) for j in xrange(num_paths) if i != j)
for i in xrange(num_paths)]
paths_not_intersecting_two = [i for i in xrange(num_paths) if intersection_counts[i] != 2]
intersection_counts = [sum(paths[i].intersects(paths[j]) for j in range(num_paths) if i != j)
for i in range(num_paths)]
paths_not_intersecting_two = [i for i in range(num_paths) if intersection_counts[i] != 2]
num_not_intersecting_two = len(paths_not_intersecting_two)
if num_not_intersecting_two == 2:
@ -292,7 +293,7 @@ class SatinColumn(EmbroideryElement):
# kind of weird thing. Maybe one of the rungs crosses a rail more
# than once. Treat it like the previous case and we'll sort out
# the intersection issues later.
indices_by_length = sorted(range(num_paths), key=lambda index: paths[index].length, reverse=True)
indices_by_length = sorted(list(range(num_paths)), key=lambda index: paths[index].length, reverse=True)
return indices_by_length[:2]
def _cut_rail(self, rail, rung):
@ -330,7 +331,7 @@ class SatinColumn(EmbroideryElement):
self._cut_rail(rail, rung)
for rail in rails:
for i in xrange(len(rail)):
for i in range(len(rail)):
if rail[i] is not None:
rail[i] = [Point(*coord) for coord in rail[i].coords]
@ -345,7 +346,7 @@ class SatinColumn(EmbroideryElement):
# zero-length bezier at the star. The user's goal here is to ignore the
# horizontal section of the right rail.
sections = zip(*rails)
sections = list(zip(*rails))
sections = [s for s in sections if s[0] is not None and s[1] is not None]
return sections
@ -438,13 +439,13 @@ class SatinColumn(EmbroideryElement):
"""
# like in do_satin()
points = list(chain.from_iterable(izip(*self.plot_points_on_rails(self.zigzag_spacing, 0))))
points = list(chain.from_iterable(zip(*self.plot_points_on_rails(self.zigzag_spacing, 0))))
if isinstance(split_point, float):
index_of_closest_stitch = int(round(len(points) * split_point))
else:
split_point = Point(*split_point)
index_of_closest_stitch = min(range(len(points)), key=lambda index: split_point.distance(points[index]))
index_of_closest_stitch = min(list(range(len(points))), key=lambda index: split_point.distance(points[index]))
if index_of_closest_stitch % 2 == 0:
# split point is on the first rail
@ -517,7 +518,7 @@ class SatinColumn(EmbroideryElement):
def _csp_to_satin(self, csp):
node = deepcopy(self.node)
d = cubicsuperpath.formatPath(csp)
d = paths.CubicSuperPath(csp).to_path()
node.set("d", d)
# we've already applied the transform, so get rid of it
@ -626,7 +627,9 @@ class SatinColumn(EmbroideryElement):
# Each iteration of this outer loop is one stitch. Keep going
# until we fall off the end of the section.
old_center = (pos0 + pos1) / 2.0
# TODO: is there an other way?
# old_center = (pos0 + pos1) / 2.0
old_center = shgeo.Point(x/2 for x in (pos0 + pos1))
while to_travel > 0 and index0 < last_index0 and index1 < last_index1:
# In this loop, we inch along each rail a tiny bit per
@ -653,7 +656,9 @@ class SatinColumn(EmbroideryElement):
pos0, index0 = self.walk(section0, pos0, index0, 0.05)
pos1, index1 = self.walk(section1, pos1, index1, 0.05 * ratio)
new_center = (pos0 + pos1) / 2.0
# TODO: is there a better way?
# new_center = (pos0 + pos1) / 2.0
new_center = shgeo.Point(x/2 for x in (pos0 + pos1))
to_travel -= new_center.distance(old_center)
old_center = new_center
@ -705,7 +710,7 @@ class SatinColumn(EmbroideryElement):
# This fancy bit of iterable magic just repeatedly takes a point
# from each side in turn.
for point in chain.from_iterable(izip(*sides)):
for point in chain.from_iterable(zip(*sides)):
patch.add_stitch(point)
return patch
@ -724,7 +729,7 @@ class SatinColumn(EmbroideryElement):
sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
# Like in zigzag_underlay(): take a point from each side in turn.
for point in chain.from_iterable(izip(*sides)):
for point in chain.from_iterable(zip(*sides)):
patch.add_stitch(point)
return patch
@ -743,7 +748,7 @@ class SatinColumn(EmbroideryElement):
# "left" and "right" here are kind of arbitrary designations meaning
# a point from the first and second rail repectively
for left, right in izip(*sides):
for left, right in zip(*sides):
patch.add_stitch(left)
patch.add_stitch(right)
patch.add_stitch(left)

Wyświetl plik

@ -134,9 +134,9 @@ class Stroke(EmbroideryElement):
global warned_about_legacy_running_stitch
if not warned_about_legacy_running_stitch:
warned_about_legacy_running_stitch = True
print >> sys.stderr, _("Legacy running stitch setting detected!\n\nIt looks like you're using a stroke " +
"smaller than 0.5 units to indicate a running stitch, which is deprecated. Instead, please set " +
"your stroke to be dashed to indicate running stitch. Any kind of dash will work.")
print(_("Legacy running stitch setting detected!\n\nIt looks like you're using a stroke " +
"smaller than 0.5 units to indicate a running stitch, which is deprecated. Instead, please set " +
"your stroke to be dashed to indicate running stitch. Any kind of dash will work."), file=sys.stderr)
# still allow the deprecated setting to work in order to support old files
return True
@ -157,7 +157,7 @@ class Stroke(EmbroideryElement):
offset = stroke_width / 2.0
for i in xrange(len(patch) - 1):
for i in range(len(patch) - 1):
start = patch.stitches[i]
end = patch.stitches[i + 1]
segment_direction = (end - start).unit()
@ -174,7 +174,7 @@ class Stroke(EmbroideryElement):
repeated_path = []
# go back and forth along the path as specified by self.repeats
for i in xrange(self.repeats):
for i in range(self.repeats):
if i % 2 == 1:
# reverse every other pass
this_path = path[::-1]

Wyświetl plik

@ -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

Wyświetl plik

@ -1,9 +1,7 @@
from simpletransform import applyTransformToPoint
from ..i18n import _
from ..svg import get_node_transform
from .element import EmbroideryElement
from .validation import ObjectTypeWarning
from ..svg.path import get_node_transform
class TextTypeWarning(ObjectTypeWarning):
@ -17,16 +15,14 @@ class TextTypeWarning(ObjectTypeWarning):
class TextObject(EmbroideryElement):
def center(self):
point = [float(self.node.get('x', 0)), float(self.node.get('y', 0))]
transform = get_node_transform(self.node)
applyTransformToPoint(transform, point)
def pointer(self):
transform = get_node_transform(self.node.getparent())
point = self.node.bounding_box(transform).center
return point
def validation_warnings(self):
yield TextTypeWarning(self.center())
yield TextTypeWarning(self.pointer())
def to_patches(self, last_patch):
return []

Wyświetl plik

@ -1,28 +1,32 @@
from auto_satin import AutoSatin
from break_apart import BreakApart
from cleanup import Cleanup
from convert_to_satin import ConvertToSatin
from cut_satin import CutSatin
from embroider import Embroider
from flip import Flip
from global_commands import GlobalCommands
from import_threadlist import ImportThreadlist
from input import Input
from install import Install
from layer_commands import LayerCommands
from lettering import Lettering
from lib.extensions.troubleshoot import Troubleshoot
from object_commands import ObjectCommands
from output import Output
from params import Params
from print_pdf import Print
from remove_embroidery_settings import RemoveEmbroiderySettings
from simulator import Simulator
from stitch_plan_preview import StitchPlanPreview
from zip import Zip
__all__ = extensions = [Embroider,
StitchPlanPreview,
from .auto_satin import AutoSatin
from .break_apart import BreakApart
from .cleanup import Cleanup
from .convert_to_satin import ConvertToSatin
from .cut_satin import CutSatin
from .flip import Flip
from .global_commands import GlobalCommands
from .import_threadlist import ImportThreadlist
from .input import Input
from .install import Install
from .layer_commands import LayerCommands
from .lettering import Lettering
from .object_commands import ObjectCommands
from .output import Output
from .params import Params
from .print_pdf import Print
from .remove_embroidery_settings import RemoveEmbroiderySettings
from .reorder import Reorder
from .simulator import Simulator
from .stitch_plan_preview import StitchPlanPreview
from .zip import Zip
from .lettering_generate_json import LetteringGenerateJson
from .lettering_remove_kerning import LetteringRemoveKerning
from .lettering_custom_font_dir import LetteringCustomFontDir
from .embroider_settings import EmbroiderSettings
__all__ = extensions = [StitchPlanPreview,
Install,
Params,
Print,
@ -37,9 +41,14 @@ __all__ = extensions = [Embroider,
CutSatin,
AutoSatin,
Lettering,
LetteringGenerateJson,
LetteringRemoveKerning,
LetteringCustomFontDir,
Troubleshoot,
RemoveEmbroiderySettings,
Cleanup,
BreakApart,
ImportThreadlist,
Simulator]
Simulator,
Reorder,
EmbroiderSettings]

Wyświetl plik

@ -14,7 +14,7 @@ class AutoSatin(CommandsExtension):
def __init__(self, *args, **kwargs):
CommandsExtension.__init__(self, *args, **kwargs)
self.OptionParser.add_option("-p", "--preserve_order", dest="preserve_order", type="inkbool", default=False)
self.arg_parser.add_argument("-p", "--preserve_order", dest="preserve_order", type=inkex.Boolean, default=False)
def get_starting_point(self):
return self.get_point("satin_start")
@ -39,7 +39,7 @@ class AutoSatin(CommandsExtension):
if not self.get_elements():
return
if not self.selected:
if not self.svg.selected:
# L10N auto-route satin columns extension
inkex.errormsg(_("Please select one or more satin columns."))
return False

Wyświetl plik

@ -1,21 +1,19 @@
import json
import os
import re
from collections import MutableMapping
from copy import deepcopy
from stringcase import snakecase
from collections.abc import MutableMapping
import inkex
from lxml import etree
from stringcase import snakecase
from ..commands import is_command, layer_commands
from ..elements import EmbroideryElement, nodes_to_elements
from ..elements.clone import is_clone, is_embroiderable_clone
from ..elements.clone import is_clone
from ..i18n import _
from ..svg import generate_unique_id
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG,
SVG_PATH_TAG)
NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG)
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
@ -71,7 +69,7 @@ class InkStitchMetadata(MutableMapping):
tag = inkex.addNS(name, "inkstitch")
item = self.metadata.find(tag)
if item is None and create:
item = inkex.etree.SubElement(self.metadata, tag)
item = etree.SubElement(self.metadata, tag)
return item
@ -117,7 +115,7 @@ class InkstitchExtension(inkex.Effect):
def ensure_current_layer(self):
# if no layer is selected, inkex defaults to the root, which isn't
# particularly useful
if self.current_layer is self.document.getroot():
if self.svg.get_current_layer() is self.document.getroot():
try:
self.current_layer = self.document.xpath(".//svg:g[@inkscape:groupmode='layer']", namespaces=inkex.NSS)[0]
except IndexError:
@ -125,7 +123,7 @@ class InkstitchExtension(inkex.Effect):
pass
def no_elements_error(self):
if self.selected:
if self.svg.selected:
# l10n This was previously: "No embroiderable paths selected."
inkex.errormsg(_("Ink/Stitch doesn't know how to work with any of the objects you've selected.") + "\n")
else:
@ -154,8 +152,8 @@ class InkstitchExtension(inkex.Effect):
if is_command(node) or node.get(CONNECTOR_TYPE):
return[]
if self.selected:
if node.get("id") in self.selected:
if self.svg.selected:
if node.get("id") in self.svg.selected:
selected = True
else:
# if the user didn't select anything that means we process everything
@ -165,7 +163,7 @@ class InkstitchExtension(inkex.Effect):
nodes.extend(self.descendants(child, selected, troubleshoot))
if selected:
if (node.tag in EMBROIDERABLE_TAGS or is_embroiderable_clone(node)) and not (node.tag == SVG_PATH_TAG and not node.get('d', '')):
if getattr(node, "get_path", None):
nodes.append(node)
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.tag in EMBROIDERABLE_TAGS or is_clone(node)):
nodes.append(node)
@ -206,24 +204,3 @@ class InkstitchExtension(inkex.Effect):
def uniqueId(self, prefix, make_new_id=True):
"""Override inkex.Effect.uniqueId with a nicer naming scheme."""
return generate_unique_id(self.document, prefix)
def parse(self):
"""Override inkex.Effect.parse to add Ink/Stitch xml namespace"""
# SVG parsers don't actually look for anything at this URL. They just
# care that it's unique. That defines a "namespace" of element and
# attribute names to disambiguate conflicts with element and
# attribute names other XML namespaces.
# call the superclass's method first
inkex.Effect.parse(self)
# Add the inkstitch namespace to the SVG. The inkstitch namespace is
# added to inkex.NSS in ../svg/tags.py at import time.
# The below is the only way I could find to add a namespace to an
# existing element tree at the top without getting ugly prefixes like "ns0".
inkex.etree.cleanup_namespaces(self.document,
top_nsmap=inkex.NSS,
keep_ns_prefixes=inkex.NSS.keys())
self.original_document = deepcopy(self.document)

Wyświetl plik

@ -1,11 +1,10 @@
import logging
from copy import copy
import inkex
from shapely.geometry import LineString, MultiPolygon, Polygon
from shapely.ops import polygonize, unary_union
import inkex
from ..elements import EmbroideryElement
from ..i18n import _
from ..svg import get_correction_transform
@ -19,10 +18,11 @@ class BreakApart(InkstitchExtension):
'''
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
self.OptionParser.add_option("-m", "--method", type="int", default=1, dest="method")
self.arg_parser.add_argument("-m", "--method", type=int, default=1, dest="method")
self.minimum_size = 5
def effect(self): # noqa: C901
if not self.selected:
if not self.svg.selected:
inkex.errormsg(_("Please select one or more fill areas to break apart."))
return
@ -41,13 +41,12 @@ class BreakApart(InkstitchExtension):
try:
paths.sort(key=lambda point_list: Polygon(point_list).area, reverse=True)
polygon = MultiPolygon([(paths[0], paths[1:])])
if self.geom_is_valid(polygon):
if self.geom_is_valid(polygon) and Polygon(paths[-1]).area > self.minimum_size:
continue
except ValueError:
pass
polygons = self.break_apart_paths(paths)
polygons = self.ensure_minimum_size(polygons, 5)
if self.options.method == 1:
polygons = self.combine_overlapping_polygons(polygons)
polygons = self.recombine_polygons(polygons)
@ -106,6 +105,7 @@ class BreakApart(InkstitchExtension):
polygons.sort(key=lambda polygon: polygon.area, reverse=True)
multipolygons = []
holes = []
self.ensure_minimum_size(polygons, self.minimum_size)
for polygon in polygons:
if polygon in holes:
continue

Wyświetl plik

@ -1,6 +1,4 @@
import sys
from inkex import NSS
from inkex import NSS, Boolean, errormsg
from ..elements import Fill, Stroke
from ..i18n import _
@ -10,10 +8,10 @@ from .base import InkstitchExtension
class Cleanup(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
self.OptionParser.add_option("-f", "--rm_fill", dest="rm_fill", type="inkbool", default=True)
self.OptionParser.add_option("-s", "--rm_stroke", dest="rm_stroke", type="inkbool", default=True)
self.OptionParser.add_option("-a", "--fill_threshold", dest="fill_threshold", type="int", default=20)
self.OptionParser.add_option("-l", "--stroke_threshold", dest="stroke_threshold", type="int", default=5)
self.arg_parser.add_argument("-f", "--rm_fill", dest="rm_fill", type=Boolean, default=True)
self.arg_parser.add_argument("-s", "--rm_stroke", dest="rm_stroke", type=Boolean, default=True)
self.arg_parser.add_argument("-a", "--fill_threshold", dest="fill_threshold", type=int, default=20)
self.arg_parser.add_argument("-l", "--stroke_threshold", dest="stroke_threshold", type=int, default=5)
def effect(self):
self.rm_fill = self.options.rm_fill
@ -21,8 +19,7 @@ class Cleanup(InkstitchExtension):
self.fill_threshold = self.options.fill_threshold
self.stroke_threshold = self.options.stroke_threshold
# Remove selection, we want every element in the document
self.selected = {}
self.svg.selected.clear()
count = 0
svg = self.document.getroot()
@ -32,7 +29,7 @@ class Cleanup(InkstitchExtension):
count += 1
if not self.get_elements():
print >> sys.stderr, _("%s elements removed" % count)
errormsg(_("%s elements removed" % count))
return
for element in self.elements:
@ -44,4 +41,4 @@ class Cleanup(InkstitchExtension):
element.node.getparent().remove(element.node)
count += 1
print >> sys.stderr, _("%s elements removed" % count)
errormsg(_("%s elements removed" % count))

Wyświetl plik

@ -1,3 +1,5 @@
from inkex import Boolean
from .base import InkstitchExtension
@ -7,4 +9,4 @@ class CommandsExtension(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
for command in self.COMMANDS:
self.OptionParser.add_option("--%s" % command, type="inkbool")
self.arg_parser.add_argument("--%s" % command, type=Boolean)

Wyświetl plik

@ -1,12 +1,13 @@
import math
import sys
from itertools import chain, groupby
import inkex
import numpy
from lxml import etree
from numpy import diff, setdiff1d, sign
from shapely import geometry as shgeo
import inkex
from ..elements import Stroke
from ..i18n import _
from ..svg import PIXELS_PER_MM, get_correction_transform
@ -26,7 +27,7 @@ class ConvertToSatin(InkstitchExtension):
if not self.get_elements():
return
if not self.selected:
if not self.svg.selected:
inkex.errormsg(_("Please select at least one line to convert to a satin column."))
return
@ -120,8 +121,15 @@ class ConvertToSatin(InkstitchExtension):
path = shgeo.LineString(path)
left_rail = path.parallel_offset(stroke_width / 2.0, 'left', **style_args)
right_rail = path.parallel_offset(stroke_width / 2.0, 'right', **style_args)
try:
left_rail = path.parallel_offset(stroke_width / 2.0, 'left', **style_args)
right_rail = path.parallel_offset(stroke_width / 2.0, 'right', **style_args)
except ValueError:
# TODO: fix this error automatically
# Error reference: https://github.com/inkstitch/inkstitch/issues/964
inkex.errormsg(_("Ink/Stitch cannot convert your stroke into a satin column. "
"Please break up your path and try again.") + '\n')
sys.exit(1)
if not isinstance(left_rail, shgeo.LineString) or \
not isinstance(right_rail, shgeo.LineString):
@ -304,12 +312,11 @@ class ConvertToSatin(InkstitchExtension):
d += "%s,%s " % (x, y)
d += " "
return inkex.etree.Element(SVG_PATH_TAG,
{
"id": self.uniqueId("path"),
"style": path_style,
"transform": correction_transform,
"d": d,
INKSTITCH_ATTRIBS['satin_column']: "true",
}
)
return etree.Element(SVG_PATH_TAG,
{
"id": self.uniqueId("path"),
"style": path_style,
"transform": correction_transform,
"d": d,
INKSTITCH_ATTRIBS['satin_column']: "true",
})

Wyświetl plik

@ -1,9 +1,9 @@
import inkex
from .base import InkstitchExtension
from ..i18n import _
from ..elements import SatinColumn
from ..i18n import _
from ..svg import get_correction_transform
from .base import InkstitchExtension
class CutSatin(InkstitchExtension):
@ -11,7 +11,7 @@ class CutSatin(InkstitchExtension):
if not self.get_elements():
return
if not self.selected:
if not self.svg.selected:
inkex.errormsg(_("Please select one or more satin columns to cut."))
return

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -1,9 +1,8 @@
import inkex
import cubicsuperpath
from .base import InkstitchExtension
from ..i18n import _
from ..elements import SatinColumn
from ..i18n import _
from .base import InkstitchExtension
class Flip(InkstitchExtension):
@ -14,13 +13,13 @@ class Flip(InkstitchExtension):
first, second = satin.rail_indices
csp[first], csp[second] = csp[second], csp[first]
satin.node.set("d", cubicsuperpath.formatPath(csp))
satin.node.set("d", inkex.paths.CubicSuperPath.to_path(csp))
def effect(self):
if not self.get_elements():
return
if not self.selected:
if not self.svg.selected:
inkex.errormsg(_("Please select one or more satin columns to flip."))
return

Wyświetl plik

@ -12,20 +12,23 @@ from .base import InkstitchExtension
class ImportThreadlist(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
self.OptionParser.add_option("-f", "--filepath", type="str", default="", dest="filepath")
self.OptionParser.add_option("-m", "--method", type="int", default=1, dest="method")
self.OptionParser.add_option("-t", "--palette", type="str", default=None, dest="palette")
self.arg_parser.add_argument("-f", "--filepath", type=str, default="", dest="filepath")
self.arg_parser.add_argument("-m", "--method", type=int, default=1, dest="method")
self.arg_parser.add_argument("-t", "--palette", type=str, default=None, dest="palette")
def effect(self):
# Remove selection, we want all the elements in the document
self.selected = {}
self.svg.selected.clear()
if not self.get_elements():
return
path = self.options.filepath
if not os.path.exists(path):
print >> sys.stderr, _("File not found.")
inkex.errormsg(_("File not found."))
sys.exit(1)
if os.path.isdir(path):
inkex.errormsg(_("The filepath specified is not a file but a dictionary.\nPlease choose a threadlist file to import."))
sys.exit(1)
method = self.options.method
@ -35,11 +38,11 @@ class ImportThreadlist(InkstitchExtension):
colors = self.parse_threadlist_by_catalog_number(path)
if all(c is None for c in colors):
print >>sys.stderr, _("Couldn't find any matching colors in the file.")
inkex.errormsg(_("Couldn't find any matching colors in the file."))
if method == 1:
print >>sys.stderr, _('Please try to import as "other threadlist" and specify a color palette below.')
inkex.errormsg(_('Please try to import as "other threadlist" and specify a color palette below.'))
else:
print >>sys.stderr, _("Please chose an other color palette for your design.")
inkex.errormsg(_("Please chose an other color palette for your design."))
sys.exit(1)
# Iterate through the color blocks to apply colors

Wyświetl plik

@ -1,8 +1,9 @@
import os
import pyembroidery
from inkex import etree
import inkex
from lxml import etree
import pyembroidery
from ..stitch_plan import StitchPlan
from ..svg import PIXELS_PER_MM, render_stitch_plan
@ -10,7 +11,7 @@ from ..svg.tags import INKSCAPE_LABEL
class Input(object):
def affect(self, args):
def run(self, args):
embroidery_file = args[0]
pattern = pyembroidery.read(embroidery_file)
@ -47,11 +48,11 @@ class Input(object):
# rename the Stitch Plan layer so that it doesn't get overwritten by Embroider
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file.decode("UTF-8")))
layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file))
layer.attrib.pop('id')
# Shift the design so that its origin is at the center of the canvas
# Note: this is NOT the same as centering the design in the canvas!
layer.set('transform', 'translate(%s,%s)' % (extents[0], extents[1]))
print etree.tostring(svg)
print(etree.tostring(svg).decode('utf-8'))

Wyświetl plik

@ -1,9 +1,10 @@
import inkex
from lxml import etree
from ..commands import LAYER_COMMANDS, get_command_description, ensure_symbol
from ..commands import LAYER_COMMANDS, ensure_symbol, get_command_description
from ..i18n import _
from ..svg import get_correction_transform
from ..svg.tags import SVG_USE_TAG, INKSCAPE_LABEL, XLINK_HREF
from ..svg.tags import INKSCAPE_LABEL, SVG_USE_TAG, XLINK_HREF
from .commands import CommandsExtension
@ -17,20 +18,19 @@ class LayerCommands(CommandsExtension):
inkex.errormsg(_("Please choose one or more commands to add."))
return
self.ensure_current_layer()
correction_transform = get_correction_transform(self.current_layer, child=True)
correction_transform = get_correction_transform(self.svg.get_current_layer(), child=True)
for i, command in enumerate(commands):
ensure_symbol(self.document, command)
inkex.etree.SubElement(self.current_layer, SVG_USE_TAG,
{
"id": self.uniqueId("use"),
INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
XLINK_HREF: "#inkstitch_%s" % command,
"height": "100%",
"width": "100%",
"x": str(i * 20),
"y": "-10",
"transform": correction_transform
})
etree.SubElement(self.svg.get_current_layer(), SVG_USE_TAG,
{
"id": self.uniqueId("use"),
INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
XLINK_HREF: "#inkstitch_%s" % command,
"height": "100%",
"width": "100%",
"x": str(i * 20),
"y": "-10",
"transform": correction_transform
})

Wyświetl plik

@ -1,14 +1,12 @@
# -*- coding: UTF-8 -*-
import json
import os
import sys
from base64 import b64decode, b64encode
import appdirs
import inkex
import wx
import wx.adv
from lxml import etree
from ..elements import nodes_to_elements
from ..gui import PresetsPanel, SimulatorPreview, info_dialog
@ -19,6 +17,7 @@ from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_LETTERING, SVG_GROUP_TAG,
SVG_PATH_TAG)
from ..utils import DotDict, cache, get_bundled_dir
from .commands import CommandsExtension
from .lettering_custom_font_dir import get_custom_font_dir
class LetteringFrame(wx.Frame):
@ -45,6 +44,7 @@ class LetteringFrame(wx.Frame):
# font details
self.font_description = wx.StaticText(self, wx.ID_ANY)
self.Bind(wx.EVT_SIZE, self.resize)
# options
self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("Options"))
@ -80,7 +80,7 @@ class LetteringFrame(wx.Frame):
"""Load the settings saved into the SVG group element"""
self.settings = DotDict({
"text": u"",
"text": "",
"back_and_forth": False,
"font": None,
"scale": 100
@ -88,7 +88,7 @@ class LetteringFrame(wx.Frame):
try:
if INKSTITCH_LETTERING in self.group.attrib:
self.settings.update(json.loads(b64decode(self.group.get(INKSTITCH_LETTERING))))
self.settings.update(json.loads(self.group.get(INKSTITCH_LETTERING)))
return
except (TypeError, ValueError):
pass
@ -103,22 +103,14 @@ class LetteringFrame(wx.Frame):
def save_settings(self):
"""Save the settings into the SVG group element."""
# We base64 encode the string before storing it in an XML attribute.
# In theory, lxml should properly html-encode the string, using HTML
# entities like &#10; as necessary. However, we've found that Inkscape
# incorrectly interpolates the HTML entities upon reading the
# extension's output, rather than leaving them as is.
#
# Details:
# https://bugs.launchpad.net/inkscape/+bug/1804346
self.group.set(INKSTITCH_LETTERING, b64encode(json.dumps(self.settings)))
self.group.set(INKSTITCH_LETTERING, json.dumps(self.settings))
def update_font_list(self):
font_paths = {
get_bundled_dir("fonts"),
os.path.expanduser("~/.inkstitch/fonts"),
os.path.join(appdirs.user_config_dir('inkstitch'), 'fonts'),
get_custom_font_dir()
}
self.fonts = {}
@ -130,13 +122,12 @@ class LetteringFrame(wx.Frame):
except OSError:
continue
try:
for font_dir in font_dirs:
font = Font(os.path.join(font_path, font_dir))
self.fonts[font.name] = font
self.fonts_by_id[font.id] = font
except FontError:
pass
for font_dir in font_dirs:
font = Font(os.path.join(font_path, font_dir))
if font.name == "" or font.id == "":
continue
self.fonts[font.name] = font
self.fonts_by_id[font.id] = font
if len(self.fonts) == 0:
info_dialog(self, _("Unable to find any fonts! Please try reinstalling Ink/Stitch."))
@ -165,13 +156,13 @@ class LetteringFrame(wx.Frame):
self.font_chooser.Append(font.name)
def get_font_names(self):
font_names = [font.name for font in self.fonts.itervalues()]
font_names = [font.name for font in self.fonts.values()]
font_names.sort()
return font_names
def get_font_descriptions(self):
return {font.name: font.description for font in self.fonts.itervalues()}
return {font.name: font.description for font in self.fonts.values()}
def set_initial_font(self, font_id):
if font_id:
@ -191,7 +182,7 @@ class LetteringFrame(wx.Frame):
try:
return self.fonts_by_id[self.DEFAULT_FONT]
except KeyError:
return self.fonts.values()[0]
return list(self.fonts.values())[0]
def on_change(self, attribute, event):
self.settings[attribute] = event.GetEventObject().GetValue()
@ -202,7 +193,11 @@ class LetteringFrame(wx.Frame):
self.settings.font = font.id
self.scale_spinner.SetRange(int(font.min_scale * 100), int(font.max_scale * 100))
font_variants = font.has_variants()
font_variants = []
try:
font_variants = font.has_variants()
except FontError:
pass
# Update font description
color = (0, 0, 0)
@ -212,7 +207,7 @@ class LetteringFrame(wx.Frame):
description = _('This font has no available font variant. Please update or remove the font.')
self.font_description.SetLabel(description)
self.font_description.SetForegroundColour(color)
self.font_description.Wrap(self.GetSize().width - 20)
self.font_description.Wrap(self.GetSize().width - 35)
if font.reversible:
self.back_and_forth_checkbox.Enable()
@ -230,18 +225,24 @@ class LetteringFrame(wx.Frame):
self.trim_checkbox.SetValue(False)
self.update_preview()
self.GetSizer().Layout()
self.Layout()
def resize(self, event=None):
description = self.font_description.GetLabel().replace("\n", " ")
self.font_description.SetLabel(description)
self.font_description.Wrap(self.GetSize().width - 35)
self.Layout()
def update_preview(self, event=None):
self.preview.update()
def update_lettering(self):
def update_lettering(self, raise_error=False):
del self.group[:]
if self.settings.scale == 100:
destination_group = self.group
else:
destination_group = inkex.etree.SubElement(self.group, SVG_GROUP_TAG, {
destination_group = etree.SubElement(self.group, SVG_GROUP_TAG, {
# L10N The user has chosen to scale the text by some percentage
# (50%, 200%, etc). If you need to use the percentage symbol,
# make sure to double it (%%).
@ -249,7 +250,14 @@ class LetteringFrame(wx.Frame):
})
font = self.fonts.get(self.font_chooser.GetValue(), self.default_font)
font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, trim=self.settings.trim)
try:
font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, trim=self.settings.trim)
except FontError as e:
if raise_error:
inkex.errormsg("Error: Text cannot be applied to the document.\n%s" % e)
return
else:
pass
if self.settings.scale != 100:
destination_group.attrib['transform'] = 'scale(%s)' % (self.settings.scale / 100.0)
@ -295,7 +303,7 @@ class LetteringFrame(wx.Frame):
def apply(self, event):
self.preview.disable()
self.update_lettering()
self.update_lettering(True)
self.save_settings()
self.close()
@ -369,10 +377,10 @@ class Lettering(CommandsExtension):
self.cancelled = True
def get_or_create_group(self):
if self.selected:
if self.svg.selected:
groups = set()
for node in self.selected.itervalues():
for node in self.svg.selected.values():
if node.tag == SVG_GROUP_TAG and INKSTITCH_LETTERING in node.attrib:
groups.add(node)
@ -391,9 +399,9 @@ class Lettering(CommandsExtension):
return list(groups)[0]
else:
self.ensure_current_layer()
return inkex.etree.SubElement(self.current_layer, SVG_GROUP_TAG, {
return etree.SubElement(self.svg.get_current_layer(), SVG_GROUP_TAG, {
INKSCAPE_LABEL: _("Ink/Stitch Lettering"),
"transform": get_correction_transform(self.current_layer, child=True)
"transform": get_correction_transform(self.svg.get_current_layer(), child=True)
})
def effect(self):
@ -405,7 +413,7 @@ class Lettering(CommandsExtension):
display = wx.Display(current_screen)
display_size = display.GetClientArea()
frame_size = frame.GetSize()
frame.SetPosition((display_size[0], display_size[3] / 2 - frame_size[1] / 2))
frame.SetPosition((int(display_size[0]), int(display_size[3] / 2 - frame_size[1] / 2)))
frame.Show()
app.MainLoop()

Wyświetl plik

@ -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 ""

Wyświetl plik

@ -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)

Wyświetl plik

@ -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()

Wyświetl plik

@ -12,7 +12,7 @@ class ObjectCommands(CommandsExtension):
if not self.get_elements():
return
if not self.selected:
if not self.svg.selected:
inkex.errormsg(_("Please select one or more objects to which to attach commands."))
return

Wyświetl plik

@ -11,7 +11,7 @@ class Output(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
def getoptions(self, args=sys.argv[1:]):
def parse_arguments(self, args=sys.argv[1:]):
# inkex's option parsing can't handle arbitrary command line arguments
# that may be passed for a given output format, so we'll just parse the
# args ourselves. :P
@ -39,14 +39,16 @@ class Output(InkstitchExtension):
self.file_extension = self.settings.pop('format')
del sys.argv[1:]
InkstitchExtension.getoptions(self, extra_args)
InkstitchExtension.parse_arguments(self, extra_args)
def effect(self):
if not self.get_elements():
return
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
stitch_plan = patches_to_stitch_plan(patches, disable_ties=self.settings.get('laser_mode', False))
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False))
temp_file = tempfile.NamedTemporaryFile(suffix=".%s" % self.file_extension, delete=False)
@ -62,7 +64,7 @@ class Output(InkstitchExtension):
# inkscape will read the file contents from stdout and copy
# to the destination file that the user chose
with open(temp_file.name, "rb") as output_file:
sys.stdout.write(output_file.read())
sys.stdout.buffer.write(output_file.read())
sys.stdout.flush()
# clean up the temp file

Wyświetl plik

@ -151,7 +151,7 @@ class ParamsTab(ScrolledPanel):
# because they're grayed out anyway.
return values
for name, input in self.param_inputs.iteritems():
for name, input in self.param_inputs.items():
if input in self.changed_inputs and input != self.toggle_checkbox:
values[name] = input.GetValue()
@ -161,7 +161,7 @@ class ParamsTab(ScrolledPanel):
values = self.get_values()
for node in self.nodes:
# print >> sys.stderr, "apply: ", self.name, node.id, values
for name, value in values.iteritems():
for name, value in values.items():
node.set_param(name, value)
def on_change(self, callable):
@ -181,7 +181,7 @@ class ParamsTab(ScrolledPanel):
def load_preset(self, preset):
preset_data = preset.get(self.name, {})
for name, value in preset_data.iteritems():
for name, value in preset_data.items():
if name in self.param_inputs:
self.param_inputs[name].SetValue(value)
self.changed_inputs.add(self.param_inputs[name])
@ -198,16 +198,16 @@ class ParamsTab(ScrolledPanel):
description = _("These settings will be applied to %d objects.") % len(self.nodes)
if any(len(param.values) > 1 for param in self.params):
description += u"\n" + _("Some settings had different values across objects. Select a value from the dropdown or enter a new one.")
description += "\n" + _("Some settings had different values across objects. Select a value from the dropdown or enter a new one.")
if self.dependent_tabs:
if len(self.dependent_tabs) == 1:
description += u"\n" + _("Disabling this tab will disable the following %d tabs.") % len(self.dependent_tabs)
description += "\n" + _("Disabling this tab will disable the following %d tabs.") % len(self.dependent_tabs)
else:
description += u"\n" + _("Disabling this tab will disable the following tab.")
description += "\n" + _("Disabling this tab will disable the following tab.")
if self.paired_tab:
description += u"\n" + _("Enabling this tab will disable %s and vice-versa.") % self.paired_tab.name
description += "\n" + _("Enabling this tab will disable %s and vice-versa.") % self.paired_tab.name
self.description_text = description
@ -285,7 +285,7 @@ class ParamsTab(ScrolledPanel):
self.settings_grid.Add(input, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.LEFT, border=40)
self.settings_grid.Add(wx.StaticText(self, label=param.unit or ""), proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
self.inputs_to_params = {v: k for k, v in self.param_inputs.iteritems()}
self.inputs_to_params = {v: k for k, v in self.param_inputs.items()}
box.Add(self.settings_grid, proportion=1, flag=wx.ALL, border=10)
self.SetSizer(box)
@ -443,9 +443,9 @@ class SettingsFrame(wx.Frame):
self.notebook.AddPage(tab, tab.name)
sizer_1.Add(self.notebook, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10)
sizer_1.Add(self.presets_panel, 0, flag=wx.EXPAND | wx.ALL, border=10)
sizer_3.Add(self.cancel_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 5)
sizer_3.Add(self.use_last_button, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, 5)
sizer_3.Add(self.apply_button, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, 5)
sizer_3.Add(self.cancel_button, 0, wx.RIGHT, 5)
sizer_3.Add(self.use_last_button, 0, wx.RIGHT | wx.BOTTOM, 5)
sizer_3.Add(self.apply_button, 0, wx.RIGHT | wx.BOTTOM, 5)
sizer_1.Add(sizer_3, 0, wx.ALIGN_RIGHT, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
@ -491,7 +491,7 @@ class Params(InkstitchExtension):
element.order = z
nodes_by_class[cls].append(element)
return sorted(nodes_by_class.items(), key=lambda (cls, nodes): cls.__name__)
return sorted(list(nodes_by_class.items()), key=lambda cls_nodes: cls_nodes[0].__name__)
def get_values(self, param, nodes):
getter = 'get_param'
@ -501,17 +501,16 @@ class Params(InkstitchExtension):
else:
getter = 'get_param'
values = filter(lambda item: item is not None,
(getattr(node, getter)(param.name, param.default) for node in nodes))
values = [item for item in (getattr(node, getter)(param.name, param.default) for node in nodes) if item is not None]
return values
def group_params(self, params):
def by_group_and_sort_index(param):
return param.group, param.sort_index
return param.group or "", param.sort_index
def by_group(param):
return param.group
return param.group or ""
return groupby(sorted(params, key=by_group_and_sort_index), by_group)
@ -526,7 +525,7 @@ class Params(InkstitchExtension):
# If multiple tabs are enabled, make sure dependent
# tabs are grouped with the parent.
parent,
parent and parent.name,
# Within parent/dependents, put the parent first.
tab == parent
@ -565,12 +564,13 @@ class Params(InkstitchExtension):
parent_tab = None
new_tabs = []
for group, params in self.group_params(params):
tab_name = group or cls.element_name
tab = ParamsTab(parent, id=wx.ID_ANY, name=tab_name, params=list(params), nodes=nodes)
new_tabs.append(tab)
if group is None:
if group == "":
parent_tab = tab
self.assign_parents(new_tabs, parent_tab)
@ -594,7 +594,7 @@ class Params(InkstitchExtension):
display = wx.Display(current_screen)
display_size = display.GetClientArea()
frame_size = frame.GetSize()
frame.SetPosition((display_size[0], display_size[3] / 2 - frame_size[1] / 2))
frame.SetPosition((int(display_size[0]), int(display_size[3]/2 - frame_size[1]/2)))
frame.Show()
app.MainLoop()

Wyświetl plik

@ -1,19 +1,19 @@
from copy import deepcopy
from datetime import date
import errno
import json
import logging
import os
import socket
import sys
from threading import Thread
import time
from copy import deepcopy
from datetime import date
from threading import Thread
import appdirs
from flask import Flask, request, Response, send_from_directory, jsonify
import inkex
from jinja2 import Environment, FileSystemLoader, select_autoescape
import requests
from flask import Flask, Response, jsonify, request, send_from_directory
from jinja2 import Environment, FileSystemLoader, select_autoescape
from lxml import etree
from ..gui import open_url
from ..i18n import translation as inkstitch_translation
@ -72,6 +72,11 @@ class PrintPreviewServer(Thread):
def __setup_app(self): # noqa: C901
self.__set_resources_path()
# Disable warning about using a development server in a production environment
cli = sys.modules['flask.cli']
cli.show_server_banner = lambda *x: None
self.app = Flask(__name__)
@self.app.route('/')
@ -211,13 +216,21 @@ class Print(InkstitchExtension):
layers = svg.findall("./g[@%s='layer']" % INKSCAPE_GROUPMODE)
stitch_plan_layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
# Make sure there is no leftover translation from stitch plan preview
stitch_plan_layer.pop('transform')
# objects outside of the viewbox are invisible
# TODO: if we want them to be seen, we need to redefine document size to fit the design
# this is just a quick fix and doesn't work on realistic view
svg.set('style', 'overflow:visible;')
# First, delete all of the other layers. We don't need them and they'll
# just bulk up the SVG.
for layer in layers:
if layer is not stitch_plan_layer:
svg.remove(layer)
overview_svg = inkex.etree.tostring(svg)
overview_svg = etree.tostring(svg).decode('utf-8')
color_block_groups = stitch_plan_layer.getchildren()
color_block_svgs = []
@ -229,7 +242,7 @@ class Print(InkstitchExtension):
stitch_plan_layer.append(group)
# save an SVG preview
color_block_svgs.append(inkex.etree.tostring(svg))
color_block_svgs.append(etree.tostring(svg).decode('utf-8'))
return overview_svg, color_block_svgs
@ -269,13 +282,15 @@ class Print(InkstitchExtension):
# objects. It's almost certain they meant to print the whole design.
# If they really wanted to print just a few objects, they could set
# the rest invisible temporarily.
self.selected = {}
self.svg.selected.clear()
if not self.get_elements():
return
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
stitch_plan = patches_to_stitch_plan(patches)
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
palette = ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette'])
overview_svg, color_block_svgs = self.render_svgs(stitch_plan, realistic=False)

Wyświetl plik

@ -1,4 +1,4 @@
import inkex
from inkex import NSS, Boolean
from ..commands import find_commands
from ..svg.svg import find_elements
@ -8,9 +8,9 @@ from .base import InkstitchExtension
class RemoveEmbroiderySettings(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
self.OptionParser.add_option("-p", "--del_params", dest="del_params", type="inkbool", default=True)
self.OptionParser.add_option("-c", "--del_commands", dest="del_commands", type="inkbool", default=False)
self.OptionParser.add_option("-d", "--del_print", dest="del_print", type="inkbool", default=False)
self.arg_parser.add_argument("-p", "--del_params", dest="del_params", type=Boolean, default=True)
self.arg_parser.add_argument("-c", "--del_commands", dest="del_commands", type=Boolean, default=False)
self.arg_parser.add_argument("-d", "--del_print", dest="del_print", type=Boolean, default=False)
def effect(self):
self.svg = self.document.getroot()
@ -30,24 +30,24 @@ class RemoveEmbroiderySettings(InkstitchExtension):
self.remove_element(print_setting)
def remove_params(self):
if not self.selected:
if not self.svg.selected:
xpath = ".//svg:path"
elements = find_elements(self.svg, xpath)
self.remove_inkstitch_attributes(elements)
else:
for node in self.selected:
for node in self.svg.selected:
elements = self.get_selected_elements(node)
self.remove_inkstitch_attributes(elements)
def remove_commands(self):
if not self.selected:
if not self.svg.selected:
# we are not able to grab commands by a specific id
# so let's move through every object instead and see if it has a command
xpath = ".//svg:path|.//svg:circle|.//svg:rect|.//svg:ellipse"
elements = find_elements(self.svg, xpath)
else:
elements = []
for node in self.selected:
for node in self.svg.selected:
elements.extend(self.get_selected_elements(node))
if elements:
@ -56,7 +56,7 @@ class RemoveEmbroiderySettings(InkstitchExtension):
group = command.connector.getparent()
group.getparent().remove(group)
if not self.selected:
if not self.svg.selected:
# remove standalone commands
standalone_commands = ".//svg:use[starts-with(@xlink:href, '#inkstitch_')]"
self.remove_elements(standalone_commands)
@ -84,5 +84,5 @@ class RemoveEmbroiderySettings(InkstitchExtension):
def remove_inkstitch_attributes(self, elements):
for element in elements:
for attrib in element.attrib:
if attrib.startswith(inkex.NSS['inkstitch'], 1):
if attrib.startswith(NSS['inkstitch'], 1):
del element.attrib[attrib]

Wyświetl plik

@ -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()

Wyświetl plik

@ -4,7 +4,6 @@ from .base import InkstitchExtension
class StitchPlanPreview(InkstitchExtension):
def effect(self):
# delete old stitch plan
svg = self.document.getroot()
@ -15,9 +14,13 @@ class StitchPlanPreview(InkstitchExtension):
# create new stitch plan
if not self.get_elements():
return
realistic = False
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
stitch_plan = patches_to_stitch_plan(patches)
render_stitch_plan(svg, stitch_plan)
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
render_stitch_plan(svg, stitch_plan, realistic)
# translate stitch plan to the right side of the canvas
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")

Wyświetl plik

@ -1,12 +1,13 @@
import textwrap
import inkex
from inkex import errormsg
from lxml import etree
from ..commands import add_layer_commands
from ..elements.validation import (ObjectTypeWarning, ValidationError,
ValidationWarning)
from ..i18n import _
from ..svg import get_correction_transform
from ..svg.path import get_correction_transform
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SODIPODI_ROLE,
SVG_GROUP_TAG, SVG_PATH_TAG, SVG_TEXT_TAG,
SVG_TSPAN_TAG)
@ -43,7 +44,7 @@ class Troubleshoot(InkstitchExtension):
message += "\n\n"
message += _("If you are still having trouble with a shape not being embroidered, "
"check if it is in a layer with an ignore command.")
inkex.errormsg(message)
errormsg(message)
def insert_pointer(self, problem):
correction_transform = get_correction_transform(self.troubleshoot_layer)
@ -61,7 +62,7 @@ class Troubleshoot(InkstitchExtension):
pointer_style = "stroke:#000000;stroke-width:0.2;fill:%s;" % (fill_color)
text_style = "fill:%s;stroke:#000000;stroke-width:0.2;font-size:8px;text-align:center;text-anchor:middle" % (fill_color)
path = inkex.etree.Element(
path = etree.Element(
SVG_PATH_TAG,
{
"id": self.uniqueId("inkstitch__invalid_pointer__"),
@ -73,7 +74,7 @@ class Troubleshoot(InkstitchExtension):
)
layer.insert(0, path)
text = inkex.etree.Element(
text = etree.Element(
SVG_TEXT_TAG,
{
INKSCAPE_LABEL: _('Description'),
@ -85,7 +86,7 @@ class Troubleshoot(InkstitchExtension):
)
layer.append(text)
tspan = inkex.etree.Element(SVG_TSPAN_TAG)
tspan = etree.Element(SVG_TSPAN_TAG)
tspan.text = problem.name
text.append(tspan)
@ -94,7 +95,7 @@ class Troubleshoot(InkstitchExtension):
layer = svg.find(".//*[@id='__validation_layer__']")
if layer is None:
layer = inkex.etree.Element(
layer = etree.Element(
SVG_GROUP_TAG,
{
'id': '__validation_layer__',
@ -109,7 +110,7 @@ class Troubleshoot(InkstitchExtension):
add_layer_commands(layer, ["ignore_layer"])
error_group = inkex.etree.SubElement(
error_group = etree.SubElement(
layer,
SVG_GROUP_TAG,
{
@ -118,7 +119,7 @@ class Troubleshoot(InkstitchExtension):
})
layer.append(error_group)
warning_group = inkex.etree.SubElement(
warning_group = etree.SubElement(
layer,
SVG_GROUP_TAG,
{
@ -127,7 +128,7 @@ class Troubleshoot(InkstitchExtension):
})
layer.append(warning_group)
type_warning_group = inkex.etree.SubElement(
type_warning_group = etree.SubElement(
layer,
SVG_GROUP_TAG,
{
@ -145,7 +146,7 @@ class Troubleshoot(InkstitchExtension):
svg = self.document.getroot()
text_x = str(float(svg.get('viewBox', '0 0 800 0').split(' ')[2]) + 5.0)
text_container = inkex.etree.Element(
text_container = etree.Element(
SVG_TEXT_TAG,
{
"x": text_x,
@ -160,7 +161,7 @@ class Troubleshoot(InkstitchExtension):
["", ""]
]
for problem_type, problems in problem_types.items():
for problem_type, problems in list(problem_types.items()):
if problem_type == "error":
text_color = "#ff0000"
problem_type_header = _("Errors")
@ -202,7 +203,7 @@ class Troubleshoot(InkstitchExtension):
text = self.split_text(text)
for text_line in text:
tspan = inkex.etree.Element(
tspan = etree.Element(
SVG_TSPAN_TAG,
{
SODIPODI_ROLE: "line",

Wyświetl plik

@ -1,36 +1,34 @@
import os
import sys
import tempfile
from copy import deepcopy
from zipfile import ZipFile
import inkex
from inkex import Boolean
from lxml import etree
import pyembroidery
from ..i18n import _
from ..output import write_embroidery_file
from ..stitch_plan import patches_to_stitch_plan
from ..svg import PIXELS_PER_MM
from .base import InkstitchExtension
from ..threads import ThreadCatalog
from .base import InkstitchExtension
class Zip(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self)
self.OptionParser.add_option("-c", "--collapse_len_mm",
action="store", type="float",
dest="collapse_length_mm", default=3.0,
help="max collapse length (mm)")
# it's kind of obnoxious that I have to do this...
self.formats = []
for format in pyembroidery.supported_formats():
if 'writer' in format and format['category'] == 'embroidery':
extension = format['extension']
self.OptionParser.add_option('--format-%s' % extension, type="inkbool", dest=extension)
self.arg_parser.add_argument('--format-%s' % extension, type=Boolean, dest=extension)
self.formats.append(extension)
self.OptionParser.add_option('--format-svg', type="inkbool", dest='svg')
self.OptionParser.add_option('--format-threadlist', type="inkbool", dest='threadlist')
self.arg_parser.add_argument('--format-svg', type=Boolean, dest='svg')
self.arg_parser.add_argument('--format-threadlist', type=Boolean, dest='threadlist')
self.formats.append('svg')
self.formats.append('threadlist')
@ -38,8 +36,10 @@ class Zip(InkstitchExtension):
if not self.get_elements():
return
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
stitch_plan = patches_to_stitch_plan(patches, self.options.collapse_length_mm * PIXELS_PER_MM)
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
base_file_name = self.get_base_file_name()
path = tempfile.mkdtemp()
@ -50,10 +50,10 @@ class Zip(InkstitchExtension):
if getattr(self.options, format):
output_file = os.path.join(path, "%s.%s" % (base_file_name, format))
if format == 'svg':
output = open(output_file, 'w')
output.write(inkex.etree.tostring(self.document.getroot()))
output.close()
if format == 'threadlist':
document = deepcopy(self.document.getroot())
with open(output_file, 'w', encoding='utf-8') as svg:
svg.write(etree.tostring(document).decode('utf-8'))
elif format == 'threadlist':
output_file = os.path.join(path, "%s_%s.txt" % (base_file_name, _("threadlist")))
output = open(output_file, 'w')
output.write(self.get_threadlist(stitch_plan, base_file_name))
@ -76,8 +76,8 @@ class Zip(InkstitchExtension):
# inkscape will read the file contents from stdout and copy
# to the destination file that the user chose
with open(temp_file.name) as output_file:
sys.stdout.write(output_file.read())
with open(temp_file.name, 'rb') as output_file:
sys.stdout.buffer.write(output_file.read())
os.remove(temp_file.name)
for file in files:

Wyświetl plik

@ -27,5 +27,5 @@ def open_url(url):
cwd = get_bundled_dir("electron")
# Any output on stdout will crash inkscape.
null = open(os.devnull, 'w')
return subprocess.Popen(command, cwd=cwd, stdout=null)
with open(os.devnull, 'w') as null:
return subprocess.Popen(command, cwd=cwd, stdout=null)

Wyświetl plik

@ -64,7 +64,7 @@ class PresetsPanel(wx.Panel):
presets_sizer = wx.StaticBoxSizer(self.presets_box, wx.HORIZONTAL)
self.preset_chooser.SetMinSize((200, -1))
presets_sizer.Add(self.preset_chooser, 1, wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.BOTTOM | wx.EXPAND, 10)
presets_sizer.Add(self.preset_chooser, 1, wx.LEFT | wx.BOTTOM | wx.EXPAND, 10)
presets_sizer.Add(self.load_preset_button, 0, wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 10)
presets_sizer.Add(self.add_preset_button, 0, wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 10)
presets_sizer.Add(self.overwrite_preset_button, 0, wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 10)
@ -106,7 +106,7 @@ class PresetsPanel(wx.Panel):
json.dump(presets, presets_file)
def update_preset_list(self):
preset_names = self._load_presets().keys()
preset_names = list(self._load_presets().keys())
preset_names = [preset for preset in preset_names if not self.is_hidden(preset)]
self.preset_chooser.SetItems(sorted(preset_names))

Wyświetl plik

@ -1,21 +1,16 @@
from itertools import izip
import sys
from threading import Thread, Event
import time
import traceback
from threading import Event, Thread
import wx
from wx.lib.intctrl import IntCtrl
from ..i18n import _
from ..stitch_plan import stitch_plan_from_file, patches_to_stitch_plan
from ..stitch_plan import patches_to_stitch_plan, stitch_plan_from_file
from ..svg import PIXELS_PER_MM
from .dialogs import info_dialog
# L10N command label at bottom of simulator window
COMMAND_NAMES = [_("STITCH"), _("JUMP"), _("TRIM"), _("STOP"), _("COLOR CHANGE")]
@ -132,7 +127,7 @@ class ControlPanel(wx.Panel):
self.accel_entries = []
for shortcut_key in shortcut_keys:
eventId = wx.NewId()
eventId = wx.NewIdRef()
self.accel_entries.append((shortcut_key[0], shortcut_key[1], eventId))
self.Bind(wx.EVT_MENU, shortcut_key[2], id=eventId)
@ -378,7 +373,7 @@ class DrawingPanel(wx.Panel):
last_stitch = None
start = time.time()
for pen, stitches in izip(self.pens, self.stitch_blocks):
for pen, stitches in zip(self.pens, self.stitch_blocks):
canvas.SetPen(pen)
if stitch + len(stitches) < self.current_stitch:
stitch += len(stitches)
@ -423,7 +418,7 @@ class DrawingPanel(wx.Panel):
while scale_width < 50:
scale_width += one_mm
scale_width_mm = scale_width / self.zoom / PIXELS_PER_MM
scale_width_mm = int(scale_width / self.zoom / PIXELS_PER_MM)
# The scale bar looks like this:
#
@ -431,7 +426,7 @@ class DrawingPanel(wx.Panel):
# |_____|_____|
scale_lower_left_x = 20
scale_lower_left_y = canvas_height - 20
scale_lower_left_y = canvas_height - 30
canvas.DrawLines(((scale_lower_left_x, scale_lower_left_y - 6),
(scale_lower_left_x, scale_lower_left_y),
@ -504,7 +499,7 @@ class DrawingPanel(wx.Panel):
# We draw the thread with a thickness of 0.1mm. Real thread has a
# thickness of ~0.4mm, but if we did that, we wouldn't be able to
# see the individual stitches.
return wx.Pen(color.visible_on_white.rgb, width=int(0.1 * PIXELS_PER_MM * self.PIXEL_DENSITY))
return wx.Pen(list(map(int, color.visible_on_white.rgb)), int(0.1 * PIXELS_PER_MM * self.PIXEL_DENSITY))
def parse_stitch_plan(self, stitch_plan):
self.pens = []
@ -698,12 +693,7 @@ class EmbroiderySimulator(wx.Frame):
stitches_per_second=stitches_per_second)
sizer.Add(self.simulator_panel, 1, wx.EXPAND)
# self.SetSizerAndFit() sets the minimum size so that the buttons don't
# get squished. But it then also shrinks the window down to that size.
self.SetSizerAndFit(sizer)
# Therefore we have to reapply the size that the caller asked for.
self.SetSize(size)
self.SetSizeHints(sizer.CalcMin())
self.Bind(wx.EVT_CLOSE, self.on_close)

Wyświetl plik

@ -1,7 +1,7 @@
import gettext
import os
from os.path import dirname, realpath
import sys
from os.path import dirname, realpath
from .utils import cache
@ -32,7 +32,7 @@ def localize(languages=None):
global translation, _
translation = gettext.translation("inkstitch", locale_dir, fallback=True)
_ = translation.ugettext
_ = translation.gettext
@cache

Wyświetl plik

@ -1 +1 @@
from generate import generate_inx_files
from .generate import generate_inx_files

7
lib/inx/about.py 100755
Wyświetl plik

@ -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())

Wyświetl plik

@ -1,10 +1,11 @@
import pyembroidery
from .utils import build_environment, write_inx_file
from .outputs import pyembroidery_output_formats
from ..extensions import extensions, Input, Output
from ..commands import LAYER_COMMANDS, OBJECT_COMMANDS, GLOBAL_COMMANDS, COMMANDS
from ..commands import (COMMANDS, GLOBAL_COMMANDS, LAYER_COMMANDS,
OBJECT_COMMANDS)
from ..extensions import Input, Output, extensions
from ..threads import ThreadCatalog
from .outputs import pyembroidery_output_formats
from .utils import build_environment, write_inx_file
def layer_commands():
@ -41,7 +42,7 @@ def generate_extension_inx_files():
continue
name = extension.name()
template = env.get_template('%s.inx' % name)
template = env.get_template('%s.xml' % name)
write_inx_file(name, template.render(formats=pyembroidery_output_formats(),
debug_formats=pyembroidery_debug_formats(),
threadcatalog=threadcatalog(),

Wyświetl plik

@ -1,6 +1,7 @@
from .info import generate_info_inx_files
from .extensions import generate_extension_inx_files
from .inputs import generate_input_inx_files
from .outputs import generate_output_inx_files
from .extensions import generate_extension_inx_files
from .utils import iterate_inx_locales
@ -9,3 +10,4 @@ def generate_inx_files():
generate_input_inx_files()
generate_output_inx_files()
generate_extension_inx_files()
generate_info_inx_files()

9
lib/inx/info.py 100755
Wyświetl plik

@ -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())

Wyświetl plik

@ -11,7 +11,7 @@ def pyembroidery_input_formats():
def generate_input_inx_files():
env = build_environment()
template = env.get_template('input.inx')
template = env.get_template('input.xml')
for format, description in pyembroidery_input_formats():
name = "input_%s" % format.upper()

Wyświetl plik

@ -5,14 +5,17 @@ from .utils import build_environment, write_inx_file
def pyembroidery_output_formats():
for format in pyembroidery.supported_formats():
if 'writer' in format and format['category'] == 'embroidery':
yield format['extension'], format['description']
if 'writer' in format:
description = format['description']
if format['category'] != "embroidery":
description = "%s [DEBUG]" % description
yield format['extension'], description, format['mimetype'], format['category']
def generate_output_inx_files():
env = build_environment()
template = env.get_template('output.inx')
template = env.get_template('output.xml')
for format, description in pyembroidery_output_formats():
for format, description, mimetype, category in pyembroidery_output_formats():
name = "output_%s" % format.upper()
write_inx_file(name, template.render(format=format, description=description))
write_inx_file(name, template.render(format=format, mimetype=mimetype, description=description))

Wyświetl plik

@ -6,11 +6,13 @@ from os.path import dirname
from jinja2 import Environment, FileSystemLoader
from ..i18n import N_, locale_dir, translation as default_translation
from ..i18n import N_, locale_dir
from ..i18n import translation as default_translation
_top_path = dirname(dirname(dirname(os.path.realpath(__file__))))
inx_path = os.path.join(_top_path, "inx")
template_path = os.path.join(_top_path, "templates")
version_path = _top_path
current_translation = default_translation
current_locale = "en_US"
@ -26,16 +28,29 @@ def build_environment():
env.install_gettext_translations(current_translation)
env.globals["locale"] = current_locale
with open(os.path.join(version_path, 'LICENSE'), 'r') as license:
env.globals["inkstitch_license"] = "".join(license.readlines())
if "BUILD" in os.environ:
# building a ZIP release, with inkstitch packaged as a binary
# About extension: add version information
with open(os.path.join(version_path, 'VERSION'), 'r') as version:
env.globals["inkstitch_version"] = "%s %s" % (version.readline(), current_locale)
# Command tag and icons path
if sys.platform == "win32":
env.globals["command_tag"] = '<command reldir="extensions">inkstitch/bin/inkstitch.exe</command>'
env.globals["command_tag"] = '<command location="inx">inkstitch/bin/inkstitch.exe</command>'
env.globals["image_path"] = 'inkstitch/bin/icons/'
elif sys.platform == "darwin":
env.globals["command_tag"] = '<command location="inx">inkstitch.app/Contents/MacOS/inkstitch</command>'
env.globals["image_path"] = 'inkstitch.app/Contents/MacOS/icons/'
else:
env.globals["command_tag"] = '<command reldir="extensions">inkstitch/bin/inkstitch</command>'
env.globals["command_tag"] = '<command location="inx">inkstitch/bin/inkstitch</command>'
env.globals["image_path"] = 'inkstitch/bin/icons/'
else:
# user is running inkstitch.py directly as a developer
env.globals["command_tag"] = '<command reldir="extensions" interpreter="python">inkstitch.py</command>'
env.globals["command_tag"] = '<command location="inx" interpreter="python">../../inkstitch.py</command>'
env.globals["image_path"] = '../../icons/'
env.globals["inkstitch_version"] = "Manual Install"
return env
@ -49,8 +64,8 @@ def write_inx_file(name, contents):
raise
inx_file_name = "inkstitch_%s.inx" % name
with open(os.path.join(inx_locale_dir, inx_file_name), 'w') as inx_file:
print >> inx_file, contents.encode("utf-8")
with open(os.path.join(inx_locale_dir, inx_file_name), 'w', encoding="utf-8") as inx_file:
print(contents, file=inx_file)
def iterate_inx_locales():
@ -64,7 +79,7 @@ def iterate_inx_locales():
# generate menu items for this language in Inkscape's "Extensions"
# menu.
magic_string = N_("Generate INX files")
translated_magic_string = translation.ugettext(magic_string)
translated_magic_string = translation.gettext(magic_string)
if translated_magic_string != magic_string or locale == "en_US":
current_translation = translation

Wyświetl plik

@ -1 +1 @@
from font import Font, FontError
from .font import Font, FontError

Wyświetl plik

@ -1,10 +1,9 @@
# -*- coding: UTF-8 -*-
import json
import os
from copy import deepcopy
import inkex
from inkex import styles
from lxml import etree
from ..elements import nodes_to_elements
from ..exceptions import InkstitchException
@ -80,14 +79,14 @@ class Font(object):
def _load_metadata(self):
try:
with open(os.path.join(self.path, "font.json")) as metadata_file:
with open(os.path.join(self.path, "font.json"), encoding="utf-8") as metadata_file:
self.metadata = json.load(metadata_file)
except IOError:
pass
def _load_license(self):
try:
with open(os.path.join(self.path, "LICENSE")) as license_file:
with open(os.path.join(self.path, "LICENSE"), encoding="utf-8") as license_file:
self.license = license_file.read()
except IOError:
pass
@ -101,13 +100,9 @@ class Font(object):
# we'll deal with missing variants when we apply lettering
pass
def _check_variants(self):
if self.variants.get(self.default_variant) is None:
raise FontError("font not found or has no default variant")
name = localized_font_metadata('name', '')
description = localized_font_metadata('description', '')
default_glyph = font_metadata('default_glyph', u"<EFBFBD>")
default_glyph = font_metadata('defalt_glyph', "<EFBFBD>")
leading = font_metadata('leading', 5, multiplier=PIXELS_PER_MM)
kerning_pairs = font_metadata('kerning_pairs', {})
auto_satin = font_metadata('auto_satin', True)
@ -149,10 +144,13 @@ class Font(object):
return None
def has_variants(self):
# returns available variants
font_variants = []
for variant in FontVariant.VARIANT_TYPES:
if os.path.isfile(os.path.join(self.path, "%s.svg" % variant)):
font_variants.append(variant)
if not font_variants:
raise FontError(_("The font '%s' has no variants.") % self.name)
return font_variants
def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim=False):
@ -182,12 +180,23 @@ class Font(object):
if self.auto_satin and len(destination_group) > 0:
self._apply_auto_satin(destination_group, trim)
else:
# set stroke width because it is almost invisible otherwise (why?)
for element in destination_group.iterdescendants(SVG_PATH_TAG):
style = ['stroke-width:1px' if s.startswith('stroke-width') else s for s in element.get('style').split(';')]
style = ';'.join(style)
element.set('style', '%s' % style)
# make sure font stroke styles have always a similar look
for element in destination_group.iterdescendants(SVG_PATH_TAG):
dash_array = ""
stroke_width = ""
style = styles.Style(element.get('style'))
if style.get('fill') == 'none':
stroke_width = ";stroke-width:1px"
if style.get('stroke-width'):
style.pop('stroke-width')
if style.get('stroke-dasharray') and style.get('stroke-dasharray') != 'none':
stroke_width = ";stroke-width:0.5px"
dash_array = ";stroke-dasharray:3, 1"
element.set('style', '%s%s%s' % (style.to_str(), stroke_width, dash_array))
return destination_group
@ -209,7 +218,8 @@ class Font(object):
Returns:
An svg:g element containing the rendered text.
"""
group = inkex.etree.Element(SVG_GROUP_TAG, {
group = etree.Element(SVG_GROUP_TAG, {
INKSCAPE_LABEL: line
})

Wyświetl plik

@ -1,9 +1,7 @@
# -*- coding: UTF-8 -*-
import os
import inkex
import simplestyle
from lxml import etree
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
from .glyph import Glyph
@ -26,10 +24,10 @@ class FontVariant(object):
# We use unicode characters rather than English strings for font file names
# in order to be more approachable for languages other than English.
LEFT_TO_RIGHT = u""
RIGHT_TO_LEFT = u""
TOP_TO_BOTTOM = u""
BOTTOM_TO_TOP = u""
LEFT_TO_RIGHT = ""
RIGHT_TO_LEFT = ""
TOP_TO_BOTTOM = ""
BOTTOM_TO_TOP = ""
VARIANT_TYPES = (LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP)
@classmethod
@ -57,9 +55,9 @@ class FontVariant(object):
self._load_glyphs()
def _load_glyphs(self):
svg_path = os.path.join(self.path, u"%s.svg" % self.variant)
with open(svg_path) as svg_file:
svg = inkex.etree.parse(svg_file)
svg_path = os.path.join(self.path, "%s.svg" % self.variant)
with open(svg_path, encoding="utf-8") as svg_file:
svg = etree.parse(svg_file)
glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS)
for layer in glyph_layers:
@ -78,9 +76,9 @@ class FontVariant(object):
if style_text:
# The layer may be marked invisible, so we'll clear the 'display'
# style.
style = simplestyle.parseStyle(group.get('style'))
style = dict(inkex.Style.parse_str(group.get('style')))
style.pop('display')
group.set('style', simplestyle.formatStyle(style))
group.set('style', str(inkex.Style(style)))
def __getitem__(self, character):
if character in self.glyphs:

Wyświetl plik

@ -1,9 +1,9 @@
from copy import copy
import cubicsuperpath
import simpletransform
from inkex import paths, transforms
from ..svg import apply_transforms, get_guides
from ..svg import get_guides
from ..svg.path import get_correction_transform
from ..svg.tags import SVG_GROUP_TAG, SVG_PATH_TAG
@ -37,8 +37,9 @@ class Glyph(object):
def _process_group(self, group):
new_group = copy(group)
new_group.attrib.pop('transform', None)
del new_group[:] # delete references to the original group's children
# new_group.attrib.pop('transform', None)
# delete references to the original group's children
del new_group[:]
for node in group:
if node.tag == SVG_GROUP_TAG:
@ -47,11 +48,9 @@ class Glyph(object):
node_copy = copy(node)
if "d" in node.attrib:
# Convert the path to absolute coordinates, incorporating all
# nested transforms.
path = cubicsuperpath.parsePath(node.get("d"))
apply_transforms(path, node)
node_copy.set("d", cubicsuperpath.formatPath(path))
transform = -transforms.Transform(get_correction_transform(node, True))
path = paths.Path(node.get("d")).transform(transform).to_absolute()
node_copy.set("d", str(path))
# Delete transforms from paths and groups, since we applied
# them to the paths already.
@ -71,16 +70,18 @@ class Glyph(object):
self.baseline = 0
def _process_bbox(self):
left, right, top, bottom = simpletransform.computeBBox(self.node.iterdescendants())
bbox = [paths.Path(node.get("d")).bounding_box() for node in self.node.iterdescendants(SVG_PATH_TAG)]
left, right = min([box.left for box in bbox]), max([box.right for box in bbox])
self.width = right - left
self.min_x = left
def _move_to_origin(self):
translate_x = -self.min_x
translate_y = -self.baseline
transform = "translate(%s, %s)" % (translate_x, translate_y)
transform = transforms.Transform("translate(%s, %s)" % (translate_x, translate_y))
for node in self.node.iter(SVG_PATH_TAG):
node.set('transform', transform)
simpletransform.fuseTransform(node)
path = paths.Path(node.get("d"))
path = path.transform(transform)
node.set('d', str(path))
node.attrib.pop('transform', None)

Wyświetl plik

@ -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))
"""

Wyświetl plik

@ -1,4 +1,5 @@
import sys
import inkex
import pyembroidery
@ -27,7 +28,8 @@ def _string_to_floats(string):
return [float(num) for num in floats]
def get_origin(svg, (minx, miny, maxx, maxy)):
def get_origin(svg, xxx_todo_changeme):
(minx, miny, maxx, maxy) = xxx_todo_changeme
origin_command = global_command(svg, "origin")
if origin_command:
@ -91,5 +93,6 @@ def write_embroidery_file(file_path, stitch_plan, svg, settings={}):
except IOError as e:
# L10N low-level file error. %(error)s is (hopefully?) translated by
# the user's system automatically.
print >> sys.stderr, _("Error writing to %(path)s: %(error)s") % dict(path=file_path, error=e.strerror)
msg = _("Error writing to %(path)s: %(error)s") % dict(path=file_path, error=e.strerror)
inkex.errormsg(msg)
sys.exit(1)

Wyświetl plik

@ -5,7 +5,8 @@ from .stitch import Stitch
from .ties import add_ties
def patches_to_stitch_plan(patches, collapse_len=3.0 * PIXELS_PER_MM, disable_ties=False):
def patches_to_stitch_plan(patches, collapse_len=None, disable_ties=False):
"""Convert a collection of inkstitch.element.Patch objects to a StitchPlan.
* applies instructions embedded in the Patch such as trim_after and stop_after
@ -13,6 +14,7 @@ def patches_to_stitch_plan(patches, collapse_len=3.0 * PIXELS_PER_MM, disable_ti
* adds jump-stitches between patches if necessary
"""
collapse_len = (collapse_len or 3.0) * PIXELS_PER_MM
stitch_plan = StitchPlan()
color_block = stitch_plan.new_color_block(color=patches[0].color)

Wyświetl plik

@ -1,6 +1,6 @@
from running_stitch import *
from auto_fill import auto_fill
from fill import legacy_fill
from .auto_fill import auto_fill
from .fill import legacy_fill
from .running_stitch import *
# Can't put this here because we get a circular import :(
#from auto_satin import auto_satin

Wyświetl plik

@ -8,11 +8,12 @@ from shapely import geometry as shgeo
from shapely.ops import snap
from shapely.strtree import STRtree
from .fill import intersect_region_with_grating, stitch_row
from .running_stitch import running_stitch
from ..debug import debug
from ..svg import PIXELS_PER_MM
from ..utils.geometry import Point as InkstitchPoint, line_string_to_point_list
from ..utils.geometry import Point as InkstitchPoint
from ..utils.geometry import line_string_to_point_list
from .fill import intersect_region_with_grating, stitch_row
from .running_stitch import running_stitch
class PathEdge(object):
@ -82,7 +83,7 @@ def which_outline(shape, coords):
point = shgeo.Point(*coords)
outlines = list(shape.boundary)
outline_indices = range(len(outlines))
outline_indices = list(range(len(outlines)))
closest = min(outline_indices, key=lambda index: outlines[index].distance(point))
return closest
@ -175,7 +176,7 @@ def insert_node(graph, shape, point):
if key == "outline":
edges.append(((start, end), data))
edge, data = min(edges, key=lambda (edge, data): shgeo.LineString(edge).distance(projected_point))
edge, data = min(edges, key=lambda edge_data: shgeo.LineString(edge_data[0]).distance(projected_point))
graph.remove_edge(*edge, key="outline")
graph.add_edge(edge[0], node, key="outline", **data)
@ -204,7 +205,7 @@ def add_boundary_travel_nodes(graph, shape):
if length > 1:
# Just plot a point every pixel, that should be plenty of
# resolution. A pixel is around a quarter of a millimeter.
for i in xrange(1, int(length)):
for i in range(1, int(length)):
subpoint = segment.interpolate(i)
graph.add_node((subpoint.x, subpoint.y), projection=outline.project(subpoint), outline=outline_index)
@ -480,7 +481,7 @@ def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None
graph = graph.copy()
if not starting_point:
starting_point = graph.nodes.keys()[0]
starting_point = list(graph.nodes.keys())[0]
starting_node = nearest_node(graph, starting_point)

Wyświetl plik

@ -1,14 +1,12 @@
import math
from itertools import chain, izip
from itertools import chain
import inkex
import networkx as nx
from lxml import etree
from shapely import geometry as shgeo
from shapely.geometry import Point as ShapelyPoint
import cubicsuperpath
import inkex
import simplestyle
from ..commands import add_commands
from ..elements import SatinColumn, Stroke
from ..i18n import _
@ -87,7 +85,7 @@ class SatinSegment(object):
num_segments = int(math.ceil(self.center_line.length / segment_size))
segments = []
for i in xrange(num_segments):
for i in range(num_segments):
segments.append(SatinSegment(self.satin,
float(i) / num_segments,
float(i + 1) / num_segments,
@ -216,13 +214,13 @@ class RunningStitch(object):
original_element.node.get(INKSTITCH_ATTRIBS['contour_underlay_stitch_length_mm'], '')
def to_element(self):
node = inkex.etree.Element(SVG_PATH_TAG)
node.set("d", cubicsuperpath.formatPath(
line_strings_to_csp([self.path])))
node = etree.Element(SVG_PATH_TAG)
d = str(inkex.paths.CubicSuperPath(line_strings_to_csp([self.path])))
node.set("d", d)
style = self.original_element.parse_style()
style['stroke-dasharray'] = "0.5,0.5"
style = simplestyle.formatStyle(style)
style = str(inkex.Style(style))
node.set("style", style)
node.set(INKSTITCH_ATTRIBS['running_stitch_length_mm'], self.running_stitch_length)
@ -445,8 +443,9 @@ def add_jumps(graph, elements, preserve_order):
point2 = graph.nodes[node2]['point']
potential_edges.append((point1, point2))
edge = min(potential_edges, key=lambda (p1, p2): p1.distance(p2))
graph.add_edge(str(edge[0]), str(edge[1]), jump=True)
if potential_edges:
edge = min(potential_edges, key=lambda p1_p2: p1_p2[0].distance(p1_p2[1]))
graph.add_edge(str(edge[0]), str(edge[1]), jump=True)
else:
# networkx makes this super-easy! k_edge_agumentation tells us what edges
# we need to add to ensure that the graph is fully connected. We give it a
@ -497,7 +496,7 @@ def find_path(graph, starting_node, ending_node):
# forth on each satin twice due to the satin edges being in the graph
# twice (forward and reverse).
graph = nx.Graph(graph)
graph.remove_edges_from(zip(path[:-1], path[1:]))
graph.remove_edges_from(list(zip(path[:-1], path[1:])))
final_path = []
prev = None
@ -643,14 +642,14 @@ def preserve_original_groups(elements, original_parent_nodes):
to the group that contained the original SatinColumn that spawned it.
"""
for element, parent in izip(elements, original_parent_nodes):
for element, parent in zip(elements, original_parent_nodes):
if parent is not None:
parent.append(element.node)
element.node.set('transform', get_correction_transform(parent, child=True))
def create_new_group(parent, insert_index):
group = inkex.etree.Element(SVG_GROUP_TAG, {
group = etree.Element(SVG_GROUP_TAG, {
INKSCAPE_LABEL: _("Auto-Satin"),
"transform": get_correction_transform(parent, child=True)
})

Wyświetl plik

@ -3,7 +3,8 @@ import math
import shapely
from ..svg import PIXELS_PER_MM
from ..utils import cache, Point as InkstitchPoint
from ..utils import Point as InkstitchPoint
from ..utils import cache
def legacy_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, flip, staggers, skip_last):
@ -223,7 +224,7 @@ def pull_runs(rows, shape, row_spacing):
run = []
prev = None
for row_num in xrange(len(rows)):
for row_num in range(len(rows)):
row = rows[row_num]
first, rest = row[0], row[1:]

Wyświetl plik

@ -92,7 +92,7 @@ def bean_stitch(stitches, repeats):
for stitch in stitches:
new_stitches.append(stitch)
for i in xrange(repeats):
for i in range(repeats):
new_stitches.extend(copy(new_stitches[-2:]))
return new_stitches

Wyświetl plik

@ -1,7 +1,7 @@
import simpletransform
from inkex import transforms
from ..utils import string_to_floats, Point, cache
from .tags import SODIPODI_NAMEDVIEW, SODIPODI_GUIDE, INKSCAPE_LABEL
from ..utils import Point, cache, string_to_floats
from .tags import INKSCAPE_LABEL, SODIPODI_GUIDE, SODIPODI_NAMEDVIEW
from .units import get_doc_size, get_viewbox_transform
@ -19,7 +19,7 @@ class InkscapeGuide(object):
# convert the size from viewbox-relative to real-world pixels
viewbox_transform = get_viewbox_transform(self.svg)
simpletransform.applyTransformToPoint(simpletransform.invertTransform(viewbox_transform), doc_size)
viewbox_transform = transforms.Transform(-transforms.Transform(viewbox_transform)).apply_to_point(doc_size)
self.position = Point(*string_to_floats(self.node.get('position')))

Wyświetl plik

@ -1,8 +1,7 @@
import cubicsuperpath
import inkex
import simpletransform
from tags import SVG_GROUP_TAG, SVG_LINK_TAG
from lxml import etree
from .tags import SVG_GROUP_TAG, SVG_LINK_TAG
from .units import get_viewbox_transform
@ -10,7 +9,7 @@ def apply_transforms(path, node):
transform = get_node_transform(node)
# apply the combined transform to this node's path
simpletransform.applyTransformToPath(transform, path)
path = path.transform(transform)
return path
@ -21,7 +20,7 @@ def compose_parent_transforms(node, mat):
trans = node.get('transform')
if trans:
mat = simpletransform.composeTransform(simpletransform.parseTransform(trans), mat)
mat = inkex.transforms.Transform(trans) * mat
if node.getparent() is not None:
if node.getparent().tag in [SVG_GROUP_TAG, SVG_LINK_TAG]:
mat = compose_parent_transforms(node.getparent(), mat)
@ -29,20 +28,22 @@ def compose_parent_transforms(node, mat):
def get_node_transform(node):
"""
if getattr(node, "composed_transform", None):
return node.composed_transform()
"""
# start with the identity transform
transform = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
transform = inkex.transforms.Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
# this if is because sometimes inkscape likes to create paths outside of a layer?!
if node.getparent() is not None:
# combine this node's transform with all parent groups' transforms
transform = compose_parent_transforms(node, transform)
if node.get('id', '').startswith('clone_'):
transform = simpletransform.parseTransform(node.get('transform', ''))
# add in the transform implied by the viewBox
viewbox_transform = get_viewbox_transform(node.getroottree().getroot())
transform = simpletransform.composeTransform(viewbox_transform, transform)
transform = viewbox_transform * transform
return transform
@ -63,9 +64,9 @@ def get_correction_transform(node, child=False):
# now invert it, so that we can position our objects in absolute
# coordinates
transform = simpletransform.invertTransform(transform)
transform = -transform
return simpletransform.formatTransform(transform)
return str(transform)
def line_strings_to_csp(line_strings):
@ -90,6 +91,6 @@ def point_lists_to_csp(point_lists):
def line_strings_to_path(line_strings):
csp = line_strings_to_csp(line_strings)
return inkex.etree.Element("path", {
"d": cubicsuperpath.formatPath(csp)
return etree.Element("path", {
"d": str(inkex.paths.CubicSuperPath(csp))
})

Wyświetl plik

@ -1,9 +1,8 @@
import math
import inkex
import simplepath
import simplestyle
import simpletransform
from lxml import etree
from math import pi
from ..i18n import _
from ..utils import Point, cache
@ -116,24 +115,25 @@ def realistic_stitch(start, end):
start = Point(*start)
stitch_length = (end - start).length()
stitch_center = (end + start) / 2.0
stitch_center = Point((end.x+start.x)/2.0, (end[1]+start[1])/2.0)
stitch_direction = (end - start)
stitch_angle = math.atan2(stitch_direction.y, stitch_direction.x)
stitch_angle = math.atan2(stitch_direction.y, stitch_direction.x) * (180 / pi)
stitch_length = max(0, stitch_length - 0.2 * PIXELS_PER_MM)
# create the path by filling in the length in the template
path = simplepath.parsePath(stitch_path % stitch_length)
path = inkex.Path(stitch_path % stitch_length).to_arrays()
# rotate the path to match the stitch
rotation_center_x = -stitch_length / 2.0
rotation_center_y = stitch_height / 2.0
simplepath.rotatePath(path, stitch_angle, cx=rotation_center_x, cy=rotation_center_y)
path = inkex.Path(path).rotate(stitch_angle, (rotation_center_x, rotation_center_y))
# move the path to the location of the stitch
simplepath.translatePath(path, stitch_center.x - rotation_center_x, stitch_center.y - rotation_center_y)
path = inkex.Path(path).translate(stitch_center.x - rotation_center_x, stitch_center.y - rotation_center_y)
return simplepath.formatPath(path)
return str(path)
def color_block_to_point_lists(color_block):
@ -159,10 +159,9 @@ def get_correction_transform(svg):
transform = get_viewbox_transform(svg)
# we need to correct for the viewbox
transform = simpletransform.invertTransform(transform)
transform = simpletransform.formatTransform(transform)
transform = -inkex.transforms.Transform(transform)
return transform
return str(transform)
def color_block_to_realistic_stitches(color_block, svg, destination):
@ -170,12 +169,8 @@ def color_block_to_realistic_stitches(color_block, svg, destination):
color = color_block.color.visible_on_white.darker.to_hex_str()
start = point_list[0]
for point in point_list[1:]:
destination.append(inkex.etree.Element(SVG_PATH_TAG, {
'style': simplestyle.formatStyle({
'fill': color,
'stroke': 'none',
'filter': 'url(#realistic-stitch-filter)'
}),
destination.append(etree.Element(SVG_PATH_TAG, {
'style': "fill: %s; stroke: none; filter: url(#realistic-stitch-filter);" % color,
'd': realistic_stitch(start, point),
'transform': get_correction_transform(svg)
}))
@ -200,12 +195,8 @@ def color_block_to_paths(color_block, svg, destination, visual_commands):
color = color_block.color.visible_on_white.to_hex_str()
path = inkex.etree.Element(SVG_PATH_TAG, {
'style': simplestyle.formatStyle({
'stroke': color,
'stroke-width': "0.4",
'fill': 'none'
}),
path = etree.Element(SVG_PATH_TAG, {
'style': "stroke: %s; stroke-width: 0.4; fill: none;" % color,
'd': "M" + " ".join(" ".join(str(coord) for coord in point) for point in point_list),
'transform': get_correction_transform(svg),
INKSTITCH_ATTRIBS['manual_stitch']: 'true'
@ -223,10 +214,10 @@ def color_block_to_paths(color_block, svg, destination, visual_commands):
def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
if layer is None:
layer = inkex.etree.Element(SVG_GROUP_TAG,
{'id': '__inkstitch_stitch_plan__',
INKSCAPE_LABEL: _('Stitch Plan'),
INKSCAPE_GROUPMODE: 'layer'})
layer = etree.Element(SVG_GROUP_TAG,
{'id': '__inkstitch_stitch_plan__',
INKSCAPE_LABEL: _('Stitch Plan'),
INKSCAPE_GROUPMODE: 'layer'})
else:
# delete old stitch plan
del layer[:]
@ -237,10 +228,10 @@ def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
svg.append(layer)
for i, color_block in enumerate(stitch_plan):
group = inkex.etree.SubElement(layer,
SVG_GROUP_TAG,
{'id': '__color_block_%d__' % i,
INKSCAPE_LABEL: "color block %d" % (i + 1)})
group = etree.SubElement(layer,
SVG_GROUP_TAG,
{'id': '__color_block_%d__' % i,
INKSCAPE_LABEL: "color block %d" % (i + 1)})
if realistic:
color_block_to_realistic_stitches(color_block, svg, group)
else:
@ -250,6 +241,6 @@ def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True):
defs = svg.find(SVG_DEFS_TAG)
if defs is None:
defs = inkex.etree.SubElement(svg, SVG_DEFS_TAG)
defs = etree.SubElement(svg, SVG_DEFS_TAG)
defs.append(inkex.etree.fromstring(realistic_filter))
defs.append(etree.fromstring(realistic_filter))

Wyświetl plik

@ -1,4 +1,5 @@
from inkex import NSS, etree
from inkex import NSS
from lxml import etree
from ..utils import cache

Wyświetl plik

@ -1,10 +1,9 @@
import inkex
from lxml import etree
# This is used below and added to the document in ../extensions/base.py.
etree.register_namespace("inkstitch", "http://inkstitch.org/namespace")
inkex.NSS['inkstitch'] = 'http://inkstitch.org/namespace'
SVG_PATH_TAG = inkex.addNS('path', 'svg')
SVG_POLYLINE_TAG = inkex.addNS('polyline', 'svg')
SVG_RECT_TAG = inkex.addNS('rect', 'svg')

Wyświetl plik

@ -1,4 +1,4 @@
import simpletransform
import inkex
from ..i18n import _
from ..utils import cache
@ -6,10 +6,14 @@ from ..utils import cache
# modern versions of Inkscape use 96 pixels per inch as per the CSS standard
PIXELS_PER_MM = 96 / 25.4
# cribbed from inkscape-silhouette
def parse_length_with_units(str):
value, unit = inkex.units.parse_unit(str)
if not unit:
raise ValueError(_("parseLengthWithUnits: unknown unit %s") % str)
return value, unit
"""
'''
Parse an SVG value which may or may not have units attached
This version is greatly simplified in that it only allows: no units,
@ -18,6 +22,8 @@ def parse_length_with_units(str):
generality is ever needed.
'''
# cribbed from inkscape-silhouette
u = 'px'
s = str.strip()
if s[-2:] == 'px':
@ -46,11 +52,15 @@ def parse_length_with_units(str):
raise ValueError(_("parseLengthWithUnits: unknown unit %s") % s)
return v, u
"""
def convert_length(length):
value, units = parse_length_with_units(length)
return inkex.units.convert_unit(str(value) + units, 'px')
"""
if not units or units == "px":
return value
@ -67,7 +77,7 @@ def convert_length(length):
units = 'mm'
if units == 'mm':
value = value / 25.4
value /= 25.4
units = 'in'
if units == 'in':
@ -76,6 +86,7 @@ def convert_length(length):
return value * 96
raise ValueError(_("Unknown unit: %s") % units)
"""
@cache
@ -121,7 +132,7 @@ def get_viewbox_transform(node):
dx = -float(viewbox[0])
dy = -float(viewbox[1])
transform = simpletransform.parseTransform("translate(%f, %f)" % (dx, dy))
transform = inkex.transforms.Transform("translate(%f, %f)" % (dx, dy))
try:
sx = doc_width / float(viewbox[2])
@ -132,8 +143,8 @@ def get_viewbox_transform(node):
if aspect_ratio != 'none':
sx = sy = max(sx, sy) if 'slice' in aspect_ratio else min(sx, sy)
scale_transform = simpletransform.parseTransform("scale(%f, %f)" % (sx, sy))
transform = simpletransform.composeTransform(transform, scale_transform)
scale_transform = inkex.transforms.Transform("scale(%f, %f)" % (sx, sy))
transform = transform * scale_transform
except ZeroDivisionError:
pass

Wyświetl plik

@ -1,3 +1,3 @@
from color import ThreadColor
from palette import ThreadPalette
from catalog import ThreadCatalog
from .catalog import ThreadCatalog
from .color import ThreadColor
from .palette import ThreadPalette

Wyświetl plik

@ -1,6 +1,6 @@
import os
import sys
from collections import Sequence
from collections.abc import Sequence
from glob import glob
from os.path import dirname, realpath

Wyświetl plik

@ -2,8 +2,7 @@ import colorsys
import re
import tinycss2.color3
import simplestyle
from inkex import Color
from pyembroidery.EmbThread import EmbThread
@ -19,14 +18,14 @@ class ThreadColor(object):
self.manufacturer = color.brand
self.rgb = (color.get_red(), color.get_green(), color.get_blue())
return
elif isinstance(color, unicode):
elif isinstance(color, str):
self.rgb = tinycss2.color3.parse_color(color)
# remove alpha channel and multiply with 255
self.rgb = tuple(channel * 255.0 for channel in list(self.rgb)[:-1])
elif isinstance(color, (list, tuple)):
self.rgb = tuple(color)
elif self.hex_str_re.match(color):
self.rgb = simplestyle.parseColor(color)
self.rgb = Color.parse_str(color)[1]
else:
raise ValueError("Invalid color: " + repr(color))
@ -77,7 +76,7 @@ class ThreadColor(object):
@property
def hex_digits(self):
return "%02X%02X%02X" % self.rgb
return "%02X%02X%02X" % tuple([int(x) for x in self.rgb])
@property
def rgb_normalized(self):

Wyświetl plik

@ -1,7 +1,8 @@
from collections import Set
from colormath.color_objects import sRGBColor, LabColor
from collections.abc import Set
from colormath.color_conversions import convert_color
from colormath.color_diff import delta_e_cie1994
from colormath.color_objects import LabColor, sRGBColor
from .color import ThreadColor

Wyświetl plik

@ -13,7 +13,7 @@ class DotDict(dict):
self.dotdictify()
def _dotdictify(self):
for k, v in self.iteritems():
for k, v in self.items():
if isinstance(v, dict):
self[k] = DotDict(v)

Wyświetl plik

@ -1,6 +1,7 @@
import math
from shapely.geometry import LineString, Point as ShapelyPoint
from shapely.geometry import LineString
from shapely.geometry import Point as ShapelyPoint
def cut(line, distance, normalized=False):
@ -123,9 +124,6 @@ class Point:
def as_tuple(self):
return (self.x, self.y)
def __cmp__(self, other):
return cmp(self.as_tuple(), other.as_tuple())
def __getitem__(self, item):
return self.as_tuple()[item]

Wyświetl plik

@ -1,10 +1,10 @@
from os.path import realpath, expanduser, join as path_join
import sys
from os.path import expanduser, realpath
def guess_inkscape_config_path():
if getattr(sys, 'frozen', None):
path = realpath(path_join(sys._MEIPASS, "..", "..", ".."))
path = realpath(sys._MEIPASS.split('extensions', 1)[0])
if sys.platform == "win32":
import win32api

Wyświetl plik

@ -1,15 +1,15 @@
import os
import sys
from cStringIO import StringIO
from io import StringIO
def save_stderr():
# GTK likes to spam stderr, which inkscape will show in a dialog.
null = open(os.devnull, 'w')
sys.stderr_dup = os.dup(sys.stderr.fileno())
sys.real_stderr = os.fdopen(sys.stderr_dup, 'w')
os.dup2(null.fileno(), 2)
sys.stderr = StringIO()
with open(os.devnull, 'w') as null:
sys.stderr_dup = os.dup(sys.stderr.fileno())
sys.real_stderr = os.fdopen(sys.stderr_dup, 'w', encoding='utf-8')
os.dup2(null.fileno(), 2)
sys.stderr = StringIO()
def restore_stderr():
@ -22,11 +22,11 @@ def restore_stderr():
def save_stdout():
null = open(os.devnull, 'w')
sys.stdout_dup = os.dup(sys.stdout.fileno())
sys.real_stdout = os.fdopen(sys.stdout_dup, 'w')
os.dup2(null.fileno(), 1)
sys.stdout = StringIO()
with open(os.devnull, 'w') as null:
sys.stdout_dup = os.dup(sys.stdout.fileno())
sys.real_stdout = os.fdopen(sys.stdout_dup, 'w')
os.dup2(null.fileno(), 1)
sys.stdout = StringIO()
def restore_stdout():

Wyświetl plik

@ -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