kopia lustrzana https://github.com/inkstitch/inkstitch
Update for Inkscape 1.0 (#880)
* update for inkscape 1.0 * add about extension * Build improvements for the inkscape1.0 branch (#985) * zip: export real svg not stitch plan * #411 and #726 * Tools for Font Creators (#1018) * ignore very small holes in fills * remove embroider (#1026) * auto_fill: ignore shrink_or_grow if result is empty (#589) * break apart: do not ignore small fills Co-authored-by: Hagen Fritsch <rumpeltux-github@irgendwo.org> Co-authored-by: Lex Neva <github.com@lexneva.name>pull/1031/head
rodzic
b39575a501
commit
e84a86d4ac
|
@ -10,156 +10,149 @@ jobs:
|
|||
linux:
|
||||
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
|
||||
|
|
|
@ -15,3 +15,4 @@ locales/
|
|||
/debug.log
|
||||
/debug.svg
|
||||
/.idea
|
||||
/VERSION
|
||||
|
|
8
Makefile
8
Makefile
|
@ -1,5 +1,5 @@
|
|||
|
||||
dist: locales inx
|
||||
dist: version locales inx
|
||||
bash bin/build-python
|
||||
bash bin/build-electron
|
||||
bash bin/build-distribution-archives
|
||||
|
@ -8,7 +8,7 @@ distclean:
|
|||
rm -rf build dist inx locales *.spec *.tar.gz *.zip electron/node_modules electron/dist
|
||||
|
||||
.PHONY: inx
|
||||
inx: locales
|
||||
inx: version locales
|
||||
mkdir -p inx
|
||||
python bin/generate-inx-files; \
|
||||
|
||||
|
@ -49,6 +49,10 @@ locales:
|
|||
mkdir -p locales; \
|
||||
fi
|
||||
|
||||
.PHONY: version
|
||||
version:
|
||||
bash bin/generate-version-file
|
||||
|
||||
.PHONY: style
|
||||
style:
|
||||
flake8 . --count --max-complexity=10 --max-line-length=150 --statistics --exclude=pyembroidery,__init__.py,electron,build
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
VERSION="${GITHUB_REF##*/}"
|
||||
OS="${BUILD:-$(uname)}"
|
||||
|
||||
if [[ "$VERSION" == "" ]]; then
|
||||
VERSION="Manual Install"
|
||||
fi
|
||||
|
||||
echo "${VERSION} (${OS})" > VERSION
|
|
@ -12,8 +12,8 @@ for font in sorted(os.listdir(fonts_dir)):
|
|||
with open(os.path.join(fonts_dir, font, "font.json")) as font_json:
|
||||
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", "")))
|
||||
|
|
|
@ -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']))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -61,9 +61,7 @@ function createWindow() {
|
|||
app.on('ready', createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
|
|
|
@ -126,7 +126,7 @@ export default {
|
|||
return ""
|
||||
}
|
||||
|
||||
let label = "STITCH"
|
||||
let label = this.$gettext("STITCH")
|
||||
switch (true) {
|
||||
case stitch.jump:
|
||||
label = this.$gettext("JUMP")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Official logo release date: 2018.03 > X3msnake -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="99.9999mm" height="96.4295mm" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 15109 14570"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil0 {fill:none}
|
||||
.fil3 {fill:black}
|
||||
.fil1 {fill:#003399}
|
||||
.fil4 {fill:gray}
|
||||
.fil2 {fill:white}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<rect class="fil0" width="15109" height="14570"/>
|
||||
<path class="fil1" d="M9857 7480l0 -51c0,-15 12,-27 27,-27l295 0c15,0 27,12 27,27l0 51c0,15 -12,28 -27,28l-295 0c-15,0 -27,-13 -27,-28zm2819 -1566c10,66 67,116 135,116l0 0c75,0 137,-61 137,-137l0 -144c0,-37 18,-68 49,-88 32,-19 69,-20 101,-2 83,43 138,125 138,232l0 2788c0,147 -121,268 -268,268l-292 0 0 443c0,1659 -1357,3016 -3016,3016l-4210 0c-1659,0 -3017,-1357 -3017,-3016l0 -402 -286 0c-151,0 -274,-123 -274,-273l0 -2778c0,-150 123,-273 274,-273l286 0 0 -484c0,-1659 1358,-3016 3017,-3016l4210 0c1659,0 3016,1357 3016,3016l0 734zm-10456 1686l107 0c43,0 81,21 106,52l0 170c-25,32 -63,52 -106,52l-107 0c-75,0 -136,-62 -136,-137l0 0c0,-75 61,-137 136,-137zm54 710c75,0 136,61 136,136 0,76 -61,137 -136,137 -76,0 -137,-61 -137,-137 0,-75 61,-136 137,-136zm10515 -710l107 0c75,0 137,62 137,137l0 0c0,75 -62,137 -137,137l-107 0c-47,0 -88,-24 -113,-61l0 -152c25,-36 66,-61 113,-61zm54 711c75,0 135,60 135,135 0,75 -60,136 -135,136 -75,0 -136,-61 -136,-136 0,-75 61,-135 136,-135zm-1957 -838c-676,154 -776,166 -1566,74l-1848 18c-374,4 -659,116 -1029,116l-2070 0 -138 -75 0 -302 138 -75 2070 0c370,0 655,111 1029,115l1848 19c790,-92 890,-80 1566,73 9,2 15,10 15,19 0,9 -6,16 -15,18zm-6324 -832l0 -2163 516 0 0 2163 -516 0zm1562 0l0 -2234 50 0 1347 1263 0 -1192 477 0 0 2246 -45 0 -1352 -1263 0 1180 -477 0zm2865 0l0 -2163 510 0 0 941 715 -941 614 0 -816 1029 881 1134 -624 0 -770 -996 0 996 -510 0zm-4809 2781c86,52 160,89 220,111 60,21 119,31 177,31 57,0 104,-13 139,-38 35,-26 53,-58 53,-98 0,-57 -63,-122 -188,-193 -20,-12 -36,-21 -46,-27l-95 -54c-92,-52 -161,-111 -205,-177 -45,-66 -67,-140 -67,-223 0,-113 43,-206 128,-278 85,-72 195,-108 330,-108 50,0 103,5 161,17 58,12 123,30 194,54l0 329c-68,-43 -132,-75 -193,-98 -61,-23 -112,-35 -155,-35 -47,0 -84,10 -111,30 -27,21 -41,48 -41,83 0,24 8,47 25,68 16,22 40,41 72,58l165 91c137,76 230,147 277,211 48,65 72,141 72,230 0,136 -47,247 -140,331 -93,84 -217,126 -371,126 -52,0 -109,-5 -171,-16 -61,-11 -128,-28 -202,-51l-28 -374zm1440 396l0 -1110 -445 0 0 -293 1224 0 0 293 -444 0 0 1110 -335 0zm986 0l0 -1403 335 0 0 1403 -335 0zm989 0l0 -1110 -446 0 0 -293 1225 0 0 293 -444 0 0 1110 -335 0zm1983 -959c-60,-63 -122,-109 -184,-139 -62,-30 -128,-45 -196,-45 -119,0 -216,41 -291,122 -76,82 -113,188 -113,316 0,128 37,232 112,312 75,79 172,119 292,119 71,0 142,-14 213,-44 70,-30 141,-74 211,-134l-22 373c-60,40 -125,71 -196,92 -72,22 -146,32 -223,32 -82,0 -161,-12 -237,-38 -76,-26 -146,-64 -209,-115 -91,-71 -160,-157 -208,-259 -48,-102 -72,-214 -72,-335 0,-104 18,-201 53,-292 36,-91 87,-171 155,-241 68,-70 147,-123 237,-160 90,-37 184,-55 283,-55 79,0 154,10 226,33 72,21 140,55 205,99l-36 359zm261 959l0 -1403 331 0 0 515 541 0 0 -515 329 0 0 1403 -329 0 0 -589 -541 0 0 589 -331 0zm-4389 -7142c-1376,0 -2504,1128 -2504,2504l0 4210c0,1376 1128,2504 2504,2504l4210 0c1376,0 2504,-1128 2504,-2504l0 -4210c0,-1376 -1128,-2504 -2504,-2504l-4210 0z"/>
|
||||
<path class="fil2" d="M5450 2676c-1376,0 -2504,1128 -2504,2504l0 4210c0,1376 1128,2504 2504,2504l4210 0c1376,0 2504,-1128 2504,-2504l0 -4210c0,-1376 -1128,-2504 -2504,-2504l-4210 0z"/>
|
||||
<path class="fil3" d="M9839 9818l0 -1403 331 0 0 515 541 0 0 -515 329 0 0 1403 -329 0 0 -589 -541 0 0 589 -331 0zm-261 -959c-60,-63 -122,-109 -184,-139 -62,-30 -128,-45 -196,-45 -119,0 -216,41 -291,122 -76,82 -113,188 -113,316 0,128 37,232 112,312 75,79 172,119 292,119 71,0 142,-14 213,-44 70,-30 141,-74 211,-134l-22 373c-60,40 -125,71 -196,92 -72,22 -146,32 -223,32 -82,0 -161,-12 -237,-38 -76,-26 -146,-64 -209,-115 -91,-71 -160,-157 -208,-259 -48,-102 -72,-214 -72,-335 0,-104 18,-201 53,-292 36,-91 87,-171 155,-241 68,-70 147,-123 237,-160 90,-37 184,-55 283,-55 79,0 154,10 226,33 72,21 140,55 205,99l-36 359zm-1983 959l0 -1110 -446 0 0 -293 1225 0 0 293 -444 0 0 1110 -335 0zm-989 0l0 -1403 335 0 0 1403 -335 0zm-986 0l0 -1110 -445 0 0 -293 1224 0 0 293 -444 0 0 1110 -335 0zm-1440 -396c86,52 160,89 220,111 60,21 119,31 177,31 57,0 104,-13 139,-38 35,-26 53,-58 53,-98 0,-57 -63,-122 -188,-193 -20,-12 -36,-21 -46,-27l-95 -54c-92,-52 -161,-111 -205,-177 -45,-66 -67,-140 -67,-223 0,-113 43,-206 128,-278 85,-72 195,-108 330,-108 50,0 103,5 161,17 58,12 123,30 194,54l0 329c-68,-43 -132,-75 -193,-98 -61,-23 -112,-35 -155,-35 -47,0 -84,10 -111,30 -27,21 -41,48 -41,83 0,24 8,47 25,68 16,22 40,41 72,58l165 91c137,76 230,147 277,211 48,65 72,141 72,230 0,136 -47,247 -140,331 -93,84 -217,126 -371,126 -52,0 -109,-5 -171,-16 -61,-11 -128,-28 -202,-51l-28 -374z"/>
|
||||
<path class="fil3" d="M8989 6641l0 -2163 510 0 0 941 715 -941 614 0 -816 1029 881 1134 -624 0 -770 -996 0 996 -510 0zm-2865 0l0 -2234 50 0 1347 1263 0 -1192 477 0 0 2246 -45 0 -1352 -1263 0 1180 -477 0zm-1562 0l0 -2163 516 0 0 2163 -516 0z"/>
|
||||
<path class="fil4" d="M10886 7473c-676,154 -776,166 -1566,74l-1848 18c-374,4 -659,116 -1029,116l-2070 0 -138 -75 0 -302 138 -75 2070 0c370,0 655,111 1029,115l1848 19c790,-92 890,-80 1566,73 9,2 15,10 15,19 0,9 -6,16 -15,18zm-1029 7l0 -51c0,-15 12,-27 27,-27l295 0c15,0 27,12 27,27l0 51c0,15 -12,28 -27,28l-295 0c-15,0 -27,-13 -27,-28z"/>
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 5.8 KiB |
30
inkstitch.py
30
inkstitch.py
|
@ -3,12 +3,15 @@ import os
|
|||
import sys
|
||||
import 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)
|
||||
|
|
|
@ -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"})
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
37
lib/debug.py
37
lib/debug.py
|
@ -1,17 +1,16 @@
|
|||
import atexit
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
|
||||
from inkex import etree
|
||||
import inkex
|
||||
from simplestyle import formatStyle
|
||||
from lxml import etree
|
||||
|
||||
from svg import line_strings_to_path
|
||||
from svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
|
||||
from .svg import line_strings_to_path
|
||||
from .svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
|
||||
|
||||
|
||||
def check_enabled(func):
|
||||
|
@ -36,7 +35,10 @@ class Debug(object):
|
|||
self.init_svg()
|
||||
|
||||
def init_log(self):
|
||||
self.log_file = open(os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.log"), "w")
|
||||
self.log_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.log")
|
||||
# delete old content
|
||||
with open(self.log_file, "w"):
|
||||
pass
|
||||
self.log("Debug logging enabled.")
|
||||
|
||||
def init_debugger(self):
|
||||
|
@ -60,7 +62,7 @@ class Debug(object):
|
|||
|
||||
try:
|
||||
pydevd.settrace()
|
||||
except socket.error, error:
|
||||
except socket.error as error:
|
||||
self.log("Debugging: connection to pydevd failed: %s", error)
|
||||
self.log("Be sure to run 'Start debugging server' in PyDev to enable debugging.")
|
||||
else:
|
||||
|
@ -74,8 +76,8 @@ class Debug(object):
|
|||
|
||||
def save_svg(self):
|
||||
tree = etree.ElementTree(self.svg)
|
||||
with open(os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.svg"), "w") as debug_svg:
|
||||
tree.write(debug_svg)
|
||||
debug_svg = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.svg")
|
||||
tree.write(debug_svg)
|
||||
|
||||
@check_enabled
|
||||
def add_layer(self, name="Debug"):
|
||||
|
@ -113,20 +115,21 @@ class Debug(object):
|
|||
timestamp = now.isoformat()
|
||||
self.last_log_time = now
|
||||
|
||||
print >> self.log_file, timestamp, message % args
|
||||
self.log_file.flush()
|
||||
with open(self.log_file, "a") as logfile:
|
||||
print(timestamp, message % args, file=logfile)
|
||||
logfile.flush()
|
||||
|
||||
def time(self, func):
|
||||
def decorated(*args, **kwargs):
|
||||
if self.enabled:
|
||||
self.raw_log("entering %s()", func.func_name)
|
||||
self.raw_log("entering %s()", func.__name__)
|
||||
start = time.time()
|
||||
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
if self.enabled:
|
||||
end = time.time()
|
||||
self.raw_log("leaving %s(), duration = %s", func.func_name, round(end - start, 6))
|
||||
self.raw_log("leaving %s(), duration = %s", func.__name__, round(end - start, 6))
|
||||
|
||||
return result
|
||||
|
||||
|
@ -150,7 +153,7 @@ class Debug(object):
|
|||
@check_enabled
|
||||
def log_line_strings(self, line_strings, name=None, color=None):
|
||||
path = line_strings_to_path(line_strings)
|
||||
path.set('style', formatStyle({"stroke": color or "#000000", "stroke-width": "0.3"}))
|
||||
path.set('style', str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3"})))
|
||||
|
||||
if name is not None:
|
||||
path.set(INKSCAPE_LABEL, name)
|
||||
|
@ -161,7 +164,7 @@ class Debug(object):
|
|||
def log_line(self, start, end, name="line", color=None):
|
||||
self.log_svg_element(etree.Element("path", {
|
||||
"d": "M%s,%s %s,%s" % (start + end),
|
||||
"style": formatStyle({"stroke": color or "#000000", "stroke-width": "0.3"}),
|
||||
"style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3"})),
|
||||
INKSCAPE_LABEL: name
|
||||
}))
|
||||
|
||||
|
@ -174,7 +177,7 @@ class Debug(object):
|
|||
|
||||
self.log_svg_element(etree.Element("path", {
|
||||
"d": d,
|
||||
"style": formatStyle({"stroke": color or "#000000", "stroke-width": "0.3"}),
|
||||
"style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3"})),
|
||||
INKSCAPE_LABEL: name
|
||||
}))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
def rect_to_path(node):
|
||||
x = float(node.get('x', '0'))
|
||||
y = float(node.get('y', '0'))
|
||||
width = float(node.get('width', '0'))
|
||||
height = float(node.get('height', '0'))
|
||||
rx = 0
|
||||
ry = 0
|
||||
|
||||
# rounded corners
|
||||
# the following rules apply for radius calculations:
|
||||
# * if rx or ry is missing it has to take the value of the other one
|
||||
# * the radius cannot be bigger than half of the corresponding side
|
||||
# (otherwise we receive an invalid path)
|
||||
if node.get('rx') or node.get('ry'):
|
||||
if node.get('rx'):
|
||||
rx = float(node.get('rx', '0'))
|
||||
ry = rx
|
||||
if node.get('ry'):
|
||||
ry = float(node.get('ry', '0'))
|
||||
if not ry:
|
||||
ry = rx
|
||||
|
||||
rx = min(width/2, rx)
|
||||
ry = min(height/2, ry)
|
||||
|
||||
path = 'M %(startx)f,%(y)f ' \
|
||||
'h %(width)f ' \
|
||||
'q %(rx)f,0 %(rx)f,%(ry)f ' \
|
||||
'v %(height)f ' \
|
||||
'q 0,%(ry)f -%(rx)f,%(ry)f ' \
|
||||
'h -%(width)f ' \
|
||||
'q -%(rx)f,0 -%(rx)f,-%(ry)f ' \
|
||||
'v -%(height)f ' \
|
||||
'q 0,-%(ry)f %(rx)f,-%(ry)f ' \
|
||||
'Z' \
|
||||
% dict(startx=x+rx, x=x, y=y, width=width-(2*rx), height=height-(2*ry), rx=rx, ry=ry)
|
||||
|
||||
else:
|
||||
path = "M %f,%f H %f V %f H %f Z" % (x, y, width+x, height+y, x)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def ellipse_to_path(node):
|
||||
rx = float(node.get('rx', "0")) or float(node.get('r', "0"))
|
||||
ry = float(node.get('ry', "0")) or float(node.get('r', "0"))
|
||||
cx = float(node.get('cx'))
|
||||
cy = float(node.get('cy'))
|
||||
|
||||
path = 'M %(cx_r)f,%(cy)f' \
|
||||
'C %(cx_r)f,%(cy_r)f %(cx)f,%(cy_r)f %(cx)f,%(cy_r)f ' \
|
||||
'%(cxr)f,%(cy_r)f %(cxr)f,%(cy)f %(cxr)f,%(cy)f ' \
|
||||
'%(cxr)f,%(cyr)f %(cx)f,%(cyr)f %(cx)f,%(cyr)f ' \
|
||||
'%(cx_r)f,%(cyr)f %(cx_r)f,%(cy)f %(cx_r)f,%(cy)f ' \
|
||||
'Z' \
|
||||
% dict(cx=cx, cx_r=cx-rx, cxr=cx+rx, cy=cy, cyr=cy+ry, cy_r=cy-ry)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def circle_to_path(node):
|
||||
cx = float(node.get('cx'))
|
||||
cy = float(node.get('cy'))
|
||||
r = float(node.get('r'))
|
||||
|
||||
path = 'M %(xstart)f, %(cy)f ' \
|
||||
'a %(r)f,%(r)f 0 1,0 %(rr)f,0 ' \
|
||||
'a %(r)f,%(r)f 0 1,0 -%(rr)f,0 ' \
|
||||
% dict(xstart=(cx-r), cy=cy, r=r, rr=(r*2))
|
||||
|
||||
return path
|
|
@ -1,9 +1,7 @@
|
|||
from simpletransform import applyTransformToPoint
|
||||
|
||||
from ..i18n import _
|
||||
from ..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 []
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
import os
|
||||
|
||||
from ..i18n import _
|
||||
from ..output import write_embroidery_file
|
||||
from ..stitch_plan import patches_to_stitch_plan
|
||||
from ..svg import render_stitch_plan, PIXELS_PER_MM
|
||||
from .base import InkstitchExtension
|
||||
|
||||
|
||||
class Embroider(InkstitchExtension):
|
||||
def __init__(self, *args, **kwargs):
|
||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||
self.OptionParser.add_option("-c", "--collapse_len_mm",
|
||||
action="store", type="float",
|
||||
dest="collapse_length_mm", default=3.0,
|
||||
help="max collapse length (mm)")
|
||||
self.OptionParser.add_option("--hide_layers",
|
||||
action="store", type="choice",
|
||||
choices=["true", "false"],
|
||||
dest="hide_layers", default="true",
|
||||
help="Hide all other layers when the embroidery layer is generated")
|
||||
self.OptionParser.add_option("-O", "--output_format",
|
||||
action="store", type="string",
|
||||
dest="output_format", default="csv",
|
||||
help="Output file extenstion (default: csv)")
|
||||
self.OptionParser.add_option("-P", "--path",
|
||||
action="store", type="string",
|
||||
dest="path", default=".",
|
||||
help="Directory in which to store output file")
|
||||
self.OptionParser.add_option("-F", "--output-file",
|
||||
action="store", type="string",
|
||||
dest="output_file",
|
||||
help="Output filename.")
|
||||
self.OptionParser.add_option("-b", "--max-backups",
|
||||
action="store", type="int",
|
||||
dest="max_backups", default=5,
|
||||
help="Max number of backups of output files to keep.")
|
||||
self.OptionParser.usage += _("\n\nSeeing a 'no such option' message? Please restart Inkscape to fix.")
|
||||
|
||||
def get_output_path(self):
|
||||
if self.options.output_file:
|
||||
# This is helpful for folks that run the embroider extension
|
||||
# manually from the command line (without Inkscape) for
|
||||
# debugging purposes.
|
||||
output_path = os.path.join(os.path.expanduser(os.path.expandvars(self.options.path.decode("UTF-8"))),
|
||||
self.options.output_file.decode("UTF-8"))
|
||||
else:
|
||||
csv_filename = '%s.%s' % (self.get_base_file_name(), self.options.output_format)
|
||||
output_path = os.path.join(self.options.path.decode("UTF-8"), csv_filename)
|
||||
|
||||
def add_suffix(path, suffix):
|
||||
if suffix > 0:
|
||||
path = "%s.%s" % (path, suffix)
|
||||
|
||||
return path
|
||||
|
||||
def move_if_exists(path, suffix=0):
|
||||
source = add_suffix(path, suffix)
|
||||
|
||||
if suffix >= self.options.max_backups:
|
||||
return
|
||||
|
||||
dest = add_suffix(path, suffix + 1)
|
||||
|
||||
if os.path.exists(source):
|
||||
move_if_exists(path, suffix + 1)
|
||||
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
|
||||
os.rename(source, dest)
|
||||
|
||||
move_if_exists(output_path)
|
||||
|
||||
return output_path
|
||||
|
||||
def effect(self):
|
||||
if not self.get_elements():
|
||||
return
|
||||
|
||||
if self.options.hide_layers:
|
||||
self.hide_all_layers()
|
||||
|
||||
patches = self.elements_to_patches(self.elements)
|
||||
stitch_plan = patches_to_stitch_plan(patches, self.options.collapse_length_mm * PIXELS_PER_MM)
|
||||
write_embroidery_file(self.get_output_path(), stitch_plan, self.document.getroot())
|
||||
render_stitch_plan(self.document.getroot(), stitch_plan)
|
|
@ -0,0 +1,17 @@
|
|||
from .base import InkstitchExtension
|
||||
|
||||
|
||||
class EmbroiderSettings(InkstitchExtension):
|
||||
'''
|
||||
This saves embroider settings into the metadata of the file
|
||||
'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||
self.arg_parser.add_argument("-c", "--collapse_len_mm",
|
||||
action="store", type=float,
|
||||
dest="collapse_length_mm", default=3.0,
|
||||
help="max collapse length (mm)")
|
||||
|
||||
def effect(self):
|
||||
self.metadata = self.get_inkstitch_metadata()
|
||||
self.metadata['collapse_len_mm'] = self.options.collapse_length_mm
|
|
@ -1,9 +1,8 @@
|
|||
import inkex
|
||||
import 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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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 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()
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
import appdirs
|
||||
from inkex import errormsg
|
||||
|
||||
from ..i18n import _
|
||||
from .base import InkstitchExtension
|
||||
|
||||
|
||||
class LetteringCustomFontDir(InkstitchExtension):
|
||||
'''
|
||||
This extension will create a json file to store a custom directory path for additional user fonts
|
||||
'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||
self.arg_parser.add_argument("-d", "--path", type=str, default="", dest="path")
|
||||
|
||||
def effect(self):
|
||||
path = self.options.path
|
||||
if not os.path.isdir(path):
|
||||
errormsg(_("Please specify the directory of your custom fonts."))
|
||||
return
|
||||
|
||||
data = {'custom_font_dir': '%s' % path}
|
||||
|
||||
try:
|
||||
config_path = appdirs.user_config_dir('inkstitch')
|
||||
except ImportError:
|
||||
config_path = os.path.expanduser('~/.inkstitch')
|
||||
config_path = os.path.join(config_path, 'custom_dirs.json')
|
||||
|
||||
with open(config_path, 'w', encoding="utf8") as font_data:
|
||||
json.dump(data, font_data, indent=4, ensure_ascii=False)
|
||||
|
||||
|
||||
def get_custom_font_dir():
|
||||
custom_font_dir_path = os.path.join(appdirs.user_config_dir('inkstitch'), 'custom_dirs.json')
|
||||
try:
|
||||
with open(custom_font_dir_path, 'r') as custom_dirs:
|
||||
custom_dir = json.load(custom_dirs)
|
||||
except (IOError, ValueError):
|
||||
return ""
|
||||
try:
|
||||
return custom_dir['custom_font_dir']
|
||||
except KeyError:
|
||||
pass
|
||||
return ""
|
|
@ -0,0 +1,76 @@
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from inkex import Boolean
|
||||
|
||||
from ..i18n import _
|
||||
from ..lettering.kerning import FontKerning
|
||||
from .base import InkstitchExtension
|
||||
|
||||
|
||||
class LetteringGenerateJson(InkstitchExtension):
|
||||
'''
|
||||
This extension helps font creators to generate the json file for the lettering tool
|
||||
'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||
self.arg_parser.add_argument("-n", "--font-name", type=str, default="Font", dest="font_name")
|
||||
self.arg_parser.add_argument("-d", "--font-description", type=str, default="Description", dest="font_description")
|
||||
self.arg_parser.add_argument("-s", "--auto-satin", type=Boolean, default="true", dest="auto_satin")
|
||||
self.arg_parser.add_argument("-r", "--reversible", type=Boolean, default="true", dest="reversible")
|
||||
self.arg_parser.add_argument("-g", "--default-glyph", type=str, default="", dest="default_glyph")
|
||||
self.arg_parser.add_argument("-i", "--min-scale", type=float, default=1, dest="min_scale")
|
||||
self.arg_parser.add_argument("-a", "--max-scale", type=float, default=1, dest="max_scale")
|
||||
self.arg_parser.add_argument("-l", "--leading", type=int, default=0, dest="leading")
|
||||
self.arg_parser.add_argument("-p", "--font-file", type=str, default="", dest="path")
|
||||
|
||||
def effect(self):
|
||||
# file paths
|
||||
path = self.options.path
|
||||
if not os.path.isfile(path):
|
||||
print(_("Please specify a font file."), file=sys.stderr)
|
||||
return
|
||||
output_path = os.path.join(os.path.dirname(path), 'font.json')
|
||||
|
||||
# kerning
|
||||
kerning = FontKerning(path)
|
||||
|
||||
horiz_adv_x = kerning.horiz_adv_x()
|
||||
hkern = kerning.hkern()
|
||||
word_spacing = kerning.word_spacing()
|
||||
letter_spacing = kerning.letter_spacing()
|
||||
units_per_em = kerning.units_per_em()
|
||||
# missing_glyph_spacing = kerning.missing_glyph_spacing()
|
||||
|
||||
# if letter spacing returns 0, it hasn't been specified in the font file
|
||||
# Ink/Stitch will calculate the width of each letter automatically
|
||||
if letter_spacing == 0:
|
||||
letter_spacing = None
|
||||
|
||||
# if leading (line height) is set to 0, the font author wants Ink/Stitch to use units_per_em
|
||||
# if units_per_em is not defined in the font file a default value will be returned
|
||||
if self.options.leading == 0:
|
||||
leading = units_per_em
|
||||
else:
|
||||
leading = self.options.leading
|
||||
|
||||
# collect data
|
||||
data = {'name': self.options.font_name,
|
||||
'description': self.options.font_description,
|
||||
'leading': leading,
|
||||
'auto_satin': self.options.auto_satin,
|
||||
'reversible': self.options.reversible,
|
||||
'default_glyph': self.options.default_glyph,
|
||||
'min_scale': self.options.min_scale,
|
||||
'max_scale': self.options.max_scale,
|
||||
'horiz_adv_x_default': letter_spacing,
|
||||
'horiz_adv_x_space': word_spacing,
|
||||
'units_per_em': units_per_em,
|
||||
'horiz_adv_x': horiz_adv_x,
|
||||
'kerning_pairs': hkern
|
||||
}
|
||||
|
||||
# write data to font.json into the same directory as the font file
|
||||
with open(output_path, 'w', encoding="utf8") as font_data:
|
||||
json.dump(data, font_data, indent=4, ensure_ascii=False)
|
|
@ -0,0 +1,30 @@
|
|||
import os
|
||||
|
||||
from inkex import NSS
|
||||
from lxml import etree
|
||||
|
||||
from .base import InkstitchExtension
|
||||
|
||||
|
||||
class LetteringRemoveKerning(InkstitchExtension):
|
||||
'''
|
||||
This extension helps font creators to generate the json file for the lettering tool
|
||||
'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||
self.arg_parser.add_argument("-p", "--font-files", type=str, default="", dest="paths")
|
||||
|
||||
def effect(self):
|
||||
# file paths
|
||||
paths = self.options.paths.split("|")
|
||||
for path in paths:
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
with open(path, 'r+', encoding="utf-8") as fontfile:
|
||||
svg = etree.parse(fontfile)
|
||||
xpath = ".//svg:font[1]"
|
||||
kerning = svg.xpath(xpath, namespaces=NSS)[0]
|
||||
kerning.getparent().remove(kerning)
|
||||
fontfile.seek(0)
|
||||
fontfile.write(etree.tostring(svg).decode('utf-8'))
|
||||
fontfile.truncate()
|
|
@ -12,7 +12,7 @@ class ObjectCommands(CommandsExtension):
|
|||
if not self.get_elements():
|
||||
return
|
||||
|
||||
if not self.selected:
|
||||
if not self.svg.selected:
|
||||
inkex.errormsg(_("Please select one or more objects to which to attach commands."))
|
||||
return
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import inkex
|
||||
|
||||
from .base import InkstitchExtension
|
||||
|
||||
|
||||
class Reorder(InkstitchExtension):
|
||||
# Remove selected objects from the document and readd them in the order they
|
||||
# were selected.
|
||||
|
||||
def get_selected_in_order(self):
|
||||
selected = []
|
||||
|
||||
for i in self.options.ids:
|
||||
path = '//*[@id="%s"]' % i
|
||||
for node in self.document.xpath(path, namespaces=inkex.NSS):
|
||||
selected.append(node)
|
||||
|
||||
return selected
|
||||
|
||||
def effect(self):
|
||||
objects = self.get_selected_in_order()
|
||||
|
||||
for obj in objects[1:]:
|
||||
obj.getparent().remove(obj)
|
||||
|
||||
insert_parent = objects[0].getparent()
|
||||
insert_pos = insert_parent.index(objects[0])
|
||||
|
||||
insert_parent.remove(objects[0])
|
||||
|
||||
insert_parent[insert_pos:insert_pos] = objects
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
e = Reorder()
|
||||
e.affect()
|
|
@ -4,7 +4,6 @@ from .base import InkstitchExtension
|
|||
|
||||
|
||||
class StitchPlanPreview(InkstitchExtension):
|
||||
|
||||
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__']")
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
from generate import generate_inx_files
|
||||
from .generate import generate_inx_files
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
from .utils import build_environment, write_inx_file
|
||||
|
||||
|
||||
def generate_about_inx_file():
|
||||
env = build_environment()
|
||||
template = env.get_template('about.xml')
|
||||
write_inx_file("about", template.render())
|
|
@ -1,10 +1,11 @@
|
|||
import pyembroidery
|
||||
|
||||
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(),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
from .utils import build_environment, write_inx_file
|
||||
|
||||
|
||||
def generate_info_inx_files():
|
||||
env = build_environment()
|
||||
info_inx_files = ['about', 'embroider']
|
||||
for info in info_inx_files:
|
||||
template = env.get_template('%s.xml' % info)
|
||||
write_inx_file(info, template.render())
|
|
@ -11,7 +11,7 @@ def pyembroidery_input_formats():
|
|||
|
||||
def generate_input_inx_files():
|
||||
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()
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
from font import Font, FontError
|
||||
from .font import Font, FontError
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
from inkex import NSS
|
||||
from lxml import etree
|
||||
|
||||
|
||||
class FontKerning(object):
|
||||
"""
|
||||
This class reads kerning information from an SVG file
|
||||
"""
|
||||
def __init__(self, path):
|
||||
with open(path) as svg:
|
||||
self.svg = etree.parse(svg)
|
||||
|
||||
# horiz_adv_x defines the wdith of specific letters (distance to next letter)
|
||||
def horiz_adv_x(self):
|
||||
# In XPath 2.0 we could use ".//svg:glyph/(@unicode|@horiz-adv-x)"
|
||||
xpath = ".//svg:glyph[@unicode and @horiz-adv-x]/@*[name()='unicode' or name()='horiz-adv-x']"
|
||||
hax = self.svg.xpath(xpath, namespaces=NSS)
|
||||
if len(hax) == 0:
|
||||
return {}
|
||||
return dict(zip(hax[0::2], [int(x) for x in hax[1::2]]))
|
||||
|
||||
# kerning (specific distances of two specified letters)
|
||||
def hkern(self):
|
||||
xpath = ".//svg:hkern[(@u1 or @g1) and (@u1 or @g1) and @k]/@*[contains(name(), '1') or contains(name(), '2') or name()='k']"
|
||||
hkern = self.svg.xpath(xpath, namespaces=NSS)
|
||||
for index, glyph in enumerate(hkern):
|
||||
# fontTools.agl will import fontTools.misc.py23 which will output a deprecation warning
|
||||
# ignore the warning for now - until the library fixed it
|
||||
if index == 0:
|
||||
import warnings
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
from fontTools.agl import toUnicode
|
||||
if len(glyph) > 1 and not (index + 1) % 3 == 0:
|
||||
glyph_names = glyph.split(",")
|
||||
# the glyph name is written in various languages, second is english. Let's look it up.
|
||||
if len(glyph_names) == 1:
|
||||
hkern[index] = toUnicode(glyph)
|
||||
else:
|
||||
hkern[index] = toUnicode(glyph_names[1])
|
||||
k = [int(x) for x in hkern[2::3]]
|
||||
u = [k + v for k, v in zip(hkern[0::3], hkern[1::3])]
|
||||
hkern = dict(zip(u, k))
|
||||
return hkern
|
||||
|
||||
# the space character
|
||||
def word_spacing(self):
|
||||
xpath = "string(.//svg:glyph[@glyph-name='space'][1]/@*[name()='horiz-adv-x'])"
|
||||
word_spacing = self.svg.xpath(xpath, namespaces=NSS) or 26
|
||||
return int(word_spacing)
|
||||
|
||||
# default letter spacing
|
||||
def letter_spacing(self):
|
||||
xpath = "string(.//svg:font[@horiz-adv-x][1]/@*[name()='horiz-adv-x'])"
|
||||
letter_spacing = self.svg.xpath(xpath, namespaces=NSS) or 0
|
||||
return int(letter_spacing)
|
||||
|
||||
# this value will be saved into the json file to preserve it for later font edits
|
||||
# additionally it serves to automatically define the line height (leading)
|
||||
def units_per_em(self, default=100):
|
||||
xpath = "string(.//svg:font-face[@units-per-em][1]/@*[name()='units-per-em'])"
|
||||
units_per_em = self.svg.xpath(xpath, namespaces=NSS) or default
|
||||
return int(units_per_em)
|
||||
|
||||
"""
|
||||
def missing_glyph_spacing(self):
|
||||
xpath = "string(.//svg:missing-glyph/@*[name()='horiz-adv-x'])"
|
||||
return float(self.svg.xpath(xpath, namespaces=NSS))
|
||||
"""
|
|
@ -1,4 +1,5 @@
|
|||
import sys
|
||||
import 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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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:]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')))
|
||||
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from inkex import NSS, etree
|
||||
from inkex import NSS
|
||||
from lxml import etree
|
||||
|
||||
from ..utils import cache
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import sys
|
||||
from os.path import isfile, join, realpath
|
||||
|
||||
from ..i18n import _
|
||||
|
||||
|
||||
def get_inkstitch_version():
|
||||
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
||||
version = realpath(join(sys._MEIPASS, "..", "VERSION"))
|
||||
else:
|
||||
version = realpath(join(realpath(__file__), "..", "..", "..", 'VERSION'))
|
||||
if isfile(version):
|
||||
with open(version, 'r') as v:
|
||||
inkstitch_version = _("Ink/Stitch Version: %s") % v.readline()
|
||||
else:
|
||||
inkstitch_version = _("Ink/Stitch Version: unkown")
|
||||
return inkstitch_version
|
Some files were not shown because too many files have changed in this diff Show More
Ładowanie…
Reference in New Issue