electron simulator (#531)

pull/679/head
Lex Neva 2020-04-28 12:34:05 -04:00 zatwierdzone przez GitHub
rodzic eb526927e1
commit cb2b4e3522
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
58 zmienionych plików z 8873 dodań i 697 usunięć

Wyświetl plik

@ -12,6 +12,9 @@ jobs:
- uses: actions/checkout@v1
with:
submodules: recursive
- uses: actions/setup-node@v1
with:
node-version: '11.x'
- name: download dependencies
shell: bash
run: |
@ -70,6 +73,9 @@ jobs:
- uses: actions/checkout@v1
with:
submodules: recursive
- uses: actions/setup-node@v1
with:
node-version: '11.x'
- uses: actions/setup-python@v1
with:
python-version: '2.7.x'
@ -112,6 +118,9 @@ jobs:
- uses: actions/checkout@v1
with:
submodules: recursive
- uses: actions/setup-node@v1
with:
node-version: '11.x'
- uses: actions/setup-python@v1
with:
python-version: '2.7.x'

3
.gitignore vendored
Wyświetl plik

@ -8,9 +8,10 @@ dist/
build/
locales/
/inx/
messages.po
*.po
/DEBUG
.pydevproject
.project
/debug.log
/debug.svg
/.idea

Wyświetl plik

@ -17,8 +17,17 @@ messages.po:
rm -f messages.po
bin/pyembroidery-gettext > pyembroidery-format-descriptions.py
bin/inkstitch-fonts-gettext > inkstitch-fonts-metadata.py
pybabel extract -o messages.po -F babel.conf --add-location=full --add-comments=l10n,L10n,L10N --sort-by-file --strip-comments -k N_ .
rm pyembroidery-format-descriptions.py
pybabel extract -o messages-babel.po -F babel.conf --add-location=full --add-comments=l10n,L10n,L10N --sort-by-file --strip-comments -k N_ -k '$$gettext' .
rm pyembroidery-format-descriptions.py inkstitch-fonts-metadata.py
find electron/src -name '*.html' -o -name '*.js' -o -name '*.vue' | xargs electron/node_modules/.bin/gettext-extract --quiet --attribute v-translate --output messages-vue.po
msgcat -o messages.po messages-babel.po messages-vue.po
electron/src/renderer/assets/translations.json: $(addsuffix /LC_MESSAGES/inkstitch.po,$(wildcard locales/*))
find locales -name '*.po' -a ! -empty | \
xargs electron/node_modules/.bin/gettext-compile --output electron/src/renderer/assets/translations.json
%.po: %.mo
msgunfmt -o $@ $<
.PHONY: clean
clean:

Wyświetl plik

@ -4,3 +4,5 @@
[jinja2: print/templates/*.html]
[jinja2: templates/*]
[babelvueextractor.extract.extract_vue: **.vue]

Wyświetl plik

@ -8,9 +8,9 @@ cp -a images/examples palettes symbols fonts dist/inkstitch
cp -a icons locales print dist/inkstitch/bin
if [ "$BUILD" = "osx" ]; then
cp -a electron/dist/mac dist/inkstitch/electron
cp -a electron/build/mac dist/inkstitch/electron
else
cp -a electron/dist/*-unpacked dist/inkstitch/electron
cp -a electron/build/*-unpacked dist/inkstitch/electron
fi
mkdir artifacts

Wyświetl plik

@ -1,19 +1,16 @@
#!/bin/bash
if [ "$BUILD" = "windows" ]; then
cd electron
yarn --link-duplicates --pure-lockfile && yarn run dist -w --ia32
args="-w --ia32"
elif [ "$BUILD" = "linux" ]; then
args="-l --x64"
docker run --rm \
-e ELECTRON_CACHE=$HOME/.cache/electron \
-v ${PWD}/electron:/project \
-v ~/.cache/electron:/root/.cache/electron \
electronuserland/builder:wine \
/bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn run dist ${args}"
else
cd electron
npm install
npm run dist
elif [ "$BUILD" = "osx" ]; then
args="-m"
fi
cd electron
npm install -g yarn
yarn --link-duplicates --pure-lockfile
yarn run dist ${args}

30
electron/.babelrc 100644
Wyświetl plik

@ -0,0 +1,30 @@
{
"comments": false,
"env": {
"main": {
"presets": [
["env", {
"targets": { "node": 7 }
}],
"stage-0"
]
},
"renderer": {
"presets": [
["env", {
"modules": false
}],
"stage-0"
]
},
"web": {
"presets": [
["env", {
"modules": false
}],
"stage-0"
]
}
},
"plugins": ["transform-runtime"]
}

Wyświetl plik

@ -0,0 +1,132 @@
'use strict'
process.env.NODE_ENV = 'production'
const { say } = require('cfonts')
const chalk = require('chalk')
const del = require('del')
const { spawn } = require('child_process')
const webpack = require('webpack')
const Multispinner = require('multispinner')
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
const webConfig = require('./webpack.web.config')
const doneLog = chalk.bgGreen.white(' DONE ') + ' '
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
const isCI = process.env.CI || false
if (process.env.BUILD_TARGET === 'clean') clean()
else if (process.env.BUILD_TARGET === 'web') web()
else build()
function clean () {
del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
console.log(`\n${doneLog}\n`)
process.exit()
}
function build () {
greeting()
del.sync(['dist/electron/*', '!.gitkeep'])
const tasks = ['main', 'renderer']
const m = new Multispinner(tasks, {
preText: 'building',
postText: 'process'
})
let results = ''
m.on('success', () => {
process.stdout.write('\x1B[2J\x1B[0f')
console.log(`\n\n${results}`)
console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
process.exit()
})
pack(mainConfig).then(result => {
results += result + '\n\n'
m.success('main')
}).catch(err => {
m.error('main')
console.log(`\n ${errorLog}failed to build main process`)
console.error(`\n${err}\n`)
process.exit(1)
})
pack(rendererConfig).then(result => {
results += result + '\n\n'
m.success('renderer')
}).catch(err => {
m.error('renderer')
console.log(`\n ${errorLog}failed to build renderer process`)
console.error(`\n${err}\n`)
process.exit(1)
})
}
function pack (config) {
return new Promise((resolve, reject) => {
config.mode = 'production'
webpack(config, (err, stats) => {
if (err) reject(err.stack || err)
else if (stats.hasErrors()) {
let err = ''
stats.toString({
chunks: false,
colors: true
})
.split(/\r?\n/)
.forEach(line => {
err += ` ${line}\n`
})
reject(err)
} else {
resolve(stats.toString({
chunks: false,
colors: true
}))
}
})
})
}
function web () {
del.sync(['dist/web/*', '!.gitkeep'])
webConfig.mode = 'production'
webpack(webConfig, (err, stats) => {
if (err || stats.hasErrors()) console.log(err)
console.log(stats.toString({
chunks: false,
colors: true
}))
process.exit()
})
}
function greeting () {
const cols = process.stdout.columns
let text = ''
if (cols > 85) text = 'lets-build'
else if (cols > 60) text = 'lets-|build'
else text = false
if (text && !isCI) {
say(text, {
colors: ['yellow'],
font: 'simple3d',
space: false
})
} else console.log(chalk.yellow.bold('\n lets-build'))
console.log()
}

Wyświetl plik

@ -0,0 +1,40 @@
const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(event => {
/**
* Reload browser when HTMLWebpackPlugin emits a new index.html
*
* Currently disabled until jantimon/html-webpack-plugin#680 is resolved.
* https://github.com/SimulatedGREG/electron-vue/issues/437
* https://github.com/jantimon/html-webpack-plugin/issues/680
*/
// if (event.action === 'reload') {
// window.location.reload()
// }
/**
* Notify `mainWindow` when `main` process is compiling,
* giving notice for an expected reload of the `electron` process
*/
if (event.action === 'compiling') {
document.body.innerHTML += `
<style>
#dev-client {
background: #4fc08d;
border-radius: 4px;
bottom: 20px;
box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
color: #fff;
font-family: 'Source Sans Pro', sans-serif;
left: 20px;
padding: 8px 12px;
position: absolute;
}
</style>
<div id="dev-client">
Compiling Main Process...
</div>
`
}
})

Wyświetl plik

@ -0,0 +1,192 @@
'use strict'
const chalk = require('chalk')
const electron = require('electron')
const path = require('path')
const { say } = require('cfonts')
const { spawn } = require('child_process')
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const webpackHotMiddleware = require('webpack-hot-middleware')
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
let electronProcess = null
let manualRestart = false
let hotMiddleware
function logStats (proc, data) {
let log = ''
log += chalk.yellow.bold(`${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)
log += '\n\n'
if (typeof data === 'object') {
data.toString({
colors: true,
chunks: false
}).split(/\r?\n/).forEach(line => {
log += ' ' + line + '\n'
})
} else {
log += ` ${data}\n`
}
log += '\n' + chalk.yellow.bold(`${new Array(28 + 1).join('-')}`) + '\n'
console.log(log)
}
function startRenderer () {
return new Promise((resolve, reject) => {
rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
rendererConfig.mode = 'development'
const compiler = webpack(rendererConfig)
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500
})
compiler.hooks.compilation.tap('compilation', compilation => {
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
compiler.hooks.done.tap('done', stats => {
logStats('Renderer', stats)
})
const server = new WebpackDevServer(
compiler,
{
contentBase: path.join(__dirname, '../'),
quiet: true,
before (app, ctx) {
app.use(hotMiddleware)
ctx.middleware.waitUntilValid(() => {
resolve()
})
}
}
)
server.listen(9080)
})
}
function startMain () {
return new Promise((resolve, reject) => {
mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
mainConfig.mode = 'development'
const compiler = webpack(mainConfig)
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
logStats('Main', chalk.white.bold('compiling...'))
hotMiddleware.publish({ action: 'compiling' })
done()
})
compiler.watch({}, (err, stats) => {
if (err) {
console.log(err)
return
}
logStats('Main', stats)
if (electronProcess && electronProcess.kill) {
manualRestart = true
process.kill(electronProcess.pid)
electronProcess = null
startElectron()
setTimeout(() => {
manualRestart = false
}, 5000)
}
resolve()
})
})
}
function startElectron () {
process.argv.shift();
process.argv.shift();
var args = [
'--inspect=5858',
path.join(__dirname, '../dist/electron/main.js')
].concat(process.argv)
// detect yarn or npm and process commandline args accordingly
if (process.env.npm_execpath.endsWith('yarn.js')) {
args = args.concat(process.argv.slice(3))
} else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
args = args.concat(process.argv.slice(2))
}
electronProcess = spawn(electron, args)
electronProcess.stdout.on('data', data => {
electronLog(data, 'blue')
})
electronProcess.stderr.on('data', data => {
electronLog(data, 'red')
})
electronProcess.on('close', () => {
if (!manualRestart) process.exit()
})
}
function electronLog (data, color) {
let log = ''
data = data.toString().split(/\r?\n/)
data.forEach(line => {
log += ` ${line}\n`
})
if (/[0-9A-z]+/.test(log)) {
console.log(
chalk[color].bold('┏ Electron -------------------') +
'\n\n' +
log +
chalk[color].bold('┗ ----------------------------') +
'\n'
)
}
}
function greeting () {
const cols = process.stdout.columns
let text = ''
if (cols > 104) text = 'electron-vue'
else if (cols > 76) text = 'electron-|vue'
else text = false
if (text) {
say(text, {
colors: ['yellow'],
font: 'simple3d',
space: false
})
} else console.log(chalk.yellow.bold('\n electron-vue'))
console.log(chalk.blue(' getting ready...') + '\n')
}
function init () {
greeting()
Promise.all([startRenderer(), startMain()])
.then(() => {
startElectron()
})
.catch(err => {
console.error(err)
})
}
init()

Wyświetl plik

@ -0,0 +1,72 @@
'use strict'
process.env.BABEL_ENV = 'main'
const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
let mainConfig = {
entry: {
main: path.join(__dirname, '../src/main/index.js')
},
externals: [
...Object.keys(dependencies || {})
],
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.node$/,
use: 'node-loader'
}
]
},
node: {
__dirname: process.env.NODE_ENV !== 'production',
__filename: process.env.NODE_ENV !== 'production'
},
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../dist/electron')
},
plugins: [
new webpack.NoEmitOnErrorsPlugin()
],
resolve: {
extensions: ['.js', '.json', '.node']
},
target: 'electron-main'
}
/**
* Adjust mainConfig for development settings
*/
if (process.env.NODE_ENV !== 'production') {
mainConfig.plugins.push(
new webpack.DefinePlugin({
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
})
)
}
/**
* Adjust mainConfig for production settings
*/
if (process.env.NODE_ENV === 'production') {
mainConfig.plugins.push(
new BabiliWebpackPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
})
)
}
module.exports = mainConfig

Wyświetl plik

@ -0,0 +1,173 @@
'use strict'
process.env.BABEL_ENV = 'renderer'
const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
/**
* List of node_modules to include in webpack bundle
*
* Required for specific packages like Vue UI libraries
* that provide pure *.vue files that need compiling
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
*/
// 'vue-slider-component' needed here due to this issue:
// https://github.com/SimulatedGREG/electron-vue/issues/725
let whiteListedModules = ['vue', 'vue-slider-component']
let rendererConfig = {
devtool: '#cheap-module-eval-source-map',
entry: {
renderer: path.join(__dirname, '../src/renderer/main.js')
},
externals: [
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
],
module: {
rules: [
{
test: /\.less$/,
use: ['vue-style-loader', 'css-loader', 'less-loader']
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.html$/,
use: 'vue-html-loader'
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.node$/,
use: 'node-loader'
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
extractCSS: process.env.NODE_ENV === 'production',
loaders: {
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
scss: 'vue-style-loader!css-loader!sass-loader',
less: 'vue-style-loader!css-loader!less-loader'
}
}
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'imgs/[name]--[folder].[ext]'
}
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name]--[folder].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'fonts/[name]--[folder].[ext]'
}
}
}
]
},
node: {
__dirname: process.env.NODE_ENV !== 'production',
__filename: process.env.NODE_ENV !== 'production'
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({filename: 'styles.css'}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
nodeModules: process.env.NODE_ENV !== 'production'
? path.resolve(__dirname, '../node_modules')
: false
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../dist/electron')
},
resolve: {
alias: {
'@': path.join(__dirname, '../src/renderer'),
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['.js', '.vue', '.json', '.css', '.node']
},
target: 'electron-renderer'
}
/**
* Adjust rendererConfig for development settings
*/
if (process.env.NODE_ENV !== 'production') {
rendererConfig.plugins.push(
new webpack.DefinePlugin({
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
})
)
}
/**
* Adjust rendererConfig for production settings
*/
if (process.env.NODE_ENV === 'production') {
rendererConfig.devtool = ''
rendererConfig.plugins.push(
new BabiliWebpackPlugin(),
new CopyWebpackPlugin([
{
from: path.join(__dirname, '../static'),
to: path.join(__dirname, '../dist/electron/static'),
ignore: ['.*']
}
]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
)
}
module.exports = rendererConfig

Wyświetl plik

@ -0,0 +1,132 @@
'use strict'
process.env.BABEL_ENV = 'web'
const path = require('path')
const webpack = require('webpack')
const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
let webConfig = {
devtool: '#cheap-module-eval-source-map',
entry: {
web: path.join(__dirname, '../src/renderer/main.js')
},
module: {
rules: [
{
test: /\.less$/,
use: ['vue-style-loader', 'css-loader', 'less-loader']
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.html$/,
use: 'vue-html-loader'
},
{
test: /\.js$/,
use: 'babel-loader',
include: [ path.resolve(__dirname, '../src/renderer') ],
exclude: /node_modules/
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
extractCSS: true,
loaders: {
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
scss: 'vue-style-loader!css-loader!sass-loader',
less: 'vue-style-loader!css-loader!less-loader'
}
}
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'imgs/[name].[ext]'
}
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'fonts/[name].[ext]'
}
}
}
]
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({filename: 'styles.css'}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
nodeModules: false
}),
new webpack.DefinePlugin({
'process.env.IS_WEB': 'true'
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
output: {
filename: '[name].js',
path: path.join(__dirname, '../dist/web')
},
resolve: {
alias: {
'@': path.join(__dirname, '../src/renderer'),
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['.js', '.vue', '.json', '.css']
},
target: 'web'
}
/**
* Adjust webConfig for production settings
*/
if (process.env.NODE_ENV === 'production') {
webConfig.devtool = ''
webConfig.plugins.push(
new BabiliWebpackPlugin(),
new CopyWebpackPlugin([
{
from: path.join(__dirname, '../static'),
to: path.join(__dirname, '../dist/web/static'),
ignore: ['.*']
}
]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
)
}
module.exports = webConfig

Wyświetl plik

Wyświetl plik

@ -0,0 +1,26 @@
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
node: true
},
extends: 'standard',
globals: {
__static: true
},
plugins: [
'html'
],
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}

12
electron/.gitignore vendored
Wyświetl plik

@ -1,2 +1,10 @@
node_modules
out
.DS_Store
dist/electron/*
dist/web/*
build/*
!build/icons
node_modules/
npm-debug.log
npm-debug.log.*
thumbs.db
!.gitkeep

Wyświetl plik

@ -0,0 +1,6 @@
Electron UI
===========
This is the home of the future Electron-based UI. Currently only the Print extension uses Electron.
For local development, run `yarn` in this directory to install dependencies and build Electron.

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 39 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 39 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 8.0 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 8.0 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 938 B

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 938 B

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 1.5 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.5 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 19 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 19 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 2.0 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.0 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 3.0 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 3.0 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 38 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 38 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 4.0 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 4.0 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 6.0 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 6.0 KiB

Wyświetl plik

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 39 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 39 KiB

Wyświetl plik

@ -3,23 +3,31 @@
"productName": "inkstitch-gui",
"version": "1.0.0",
"description": "Ink/Stitch GUI",
"main": "src/main.js",
"main": "./dist/electron/main.js",
"scripts": {
"pack": "electron-builder --dir",
"dist": "electron-builder",
"dev": "electron ."
"dist": "node .electron-vue/build.js && electron-builder",
"dev": "node .electron-vue/dev-runner.js"
},
"build": {
"productName": "inkstitch-gui",
"appId": "org.inkstitch.gui",
"directories": {
"output": "build"
},
"files": [
"dist/electron/**/*"
],
"linux": {
"icon": "src/assets/icons/png/512x512.png",
"icon": "build/icons",
"target": "dir"
},
"win": {
"icon": "src/assets/icons/win/inkstitch.ico",
"icon": "build/icons/win/inkstitch.ico",
"target": "dir"
},
"mac": {
"icon": "src/assets/icons/mac/inkstitch.icns",
"icon": "build/icons/mac/inkstitch.icns",
"target": "dir"
}
},
@ -27,13 +35,73 @@
"author": "lex",
"license": "GPL-3.0+",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.22",
"@fortawesome/free-solid-svg-icons": "^5.10.2",
"@fortawesome/vue-fontawesome": "^0.1.6",
"axios": "^0.19.0",
"electron-compile": "^6.4.4",
"tmp": "0.1.0"
"lodash.throttle": "^4.1.1",
"mousetrap": "^1.6.3",
"query-string": "^6.8.2",
"@svgdotjs/svg.js": "^3.0.16",
"@svgdotjs/svg.panzoom.js": "https://github.com/inkstitch/svg.panzoom.js",
"@svgdotjs/svg.filter.js": "^3.0.7",
"svgpath": "^2.2.3",
"tmp": "0.1.0",
"vue": "^2.5.16",
"vue-gettext": "^2.1.5",
"vue-loading-overlay": "^3.2.0",
"vue-router": "^3.0.1",
"vue-slider-component": "^3.0.38",
"vue2-transitions": "^0.3.0"
},
"devDependencies": {
"ajv": "^6.5.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.26.0",
"babili-webpack-plugin": "^0.1.2",
"cfonts": "^2.1.2",
"chalk": "^2.4.1",
"copy-webpack-plugin": "^4.5.1",
"cross-env": "^5.1.6",
"css-loader": "^0.28.11",
"del": "^3.0.0",
"devtron": "^1.4.0",
"easygettext": "^2.7.0",
"electron": "4.1.3",
"electron-builder": "^20.39.0",
"electron-packager": "13.1.1",
"electron-prebuilt-compile": "4.0.0"
"electron-debug": "^1.5.0",
"electron-devtools-installer": "^2.2.4",
"electron-prebuilt-compile": "4.0.0",
"eslint": "^4.19.1",
"eslint-config-standard": "^11.0.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^2.0.0",
"eslint-plugin-html": "^4.0.3",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "0.4.0",
"multispinner": "^0.2.1",
"node-loader": "^0.6.0",
"style-loader": "^0.21.0",
"url-loader": "^1.0.1",
"vue-html-loader": "^1.2.4",
"vue-loader": "^15.2.4",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.15.1",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4",
"webpack-hot-middleware": "^2.22.2",
"webpack-merge": "^4.1.3"
}
}

Wyświetl plik

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>inkstitch-gui</title>
<% if (htmlWebpackPlugin.options.nodeModules) { %>
<!-- Add `node_modules/` to global paths so `require` works properly in development -->
<script>
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
</script>
<% } %>
</head>
<body>
<div id="app"></div>
<!-- Set `__static` path to static files in production -->
<% if (!process.browser) { %>
<script>
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
</script>
<% } %>
<!-- webpack builds are automatically injected -->
</body>
</html>

Wyświetl plik

@ -0,0 +1,8 @@
const axios = require('axios')
const queryString = require('query-string')
var port = queryString.parse(global.location.search).port
module.exports = axios.create({
baseURL: `http://127.0.0.1:${port}/`
})

Wyświetl plik

@ -0,0 +1,9 @@
module.exports.selectLanguage = function () {
['LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'].forEach(language => {
if (process.env[language]) {
return process.env[language].split(":")[0]
}
})
return "en_US"
}

Wyświetl plik

@ -1,62 +0,0 @@
import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron';
var fs = require('fs');
var path = require('path')
var tmp = require('tmp')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({icon: path.join(__dirname, 'assets/icons/png/512x512.png')});
mainWindow.maximize();
// and load the index.html of the app.
if (process.argv[1] == ".") {
// run in development mode with `electron . <url>`
var url = process.argv[2];
} else {
var url = process.argv[1];
}
mainWindow.loadURL(url);
//mainWindow.webContents.openDevTools();
// Emitted when the window is closed.
mainWindow.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
app.quit();
});
ipcMain.on('save-pdf', function (event, pageSize) {
mainWindow.webContents.printToPDF({"pageSize": pageSize}, function(error, data) {
dialog.showSaveDialog(mainWindow, {"defaultPath": "inkstitch.pdf"}, function(filename, bookmark) {
if (typeof filename !== 'undefined')
fs.writeFileSync(filename, data, 'utf-8');
})
})
})
ipcMain.on('open-pdf', function (event, pageSize) {
mainWindow.webContents.printToPDF({"pageSize": pageSize}, function(error, data) {
tmp.file({keep: true, discardDescriptor: true}, function(err, path, fd, cleanupCallback) {
fs.writeFileSync(path, data, 'utf-8');
shell.openItem(path);
})
})
})

Wyświetl plik

@ -0,0 +1,24 @@
/**
* This file is used specifically and only for development. It installs
* `electron-debug` & `vue-devtools`. There shouldn't be any need to
* modify this file, but it can be used to extend your development
* environment.
*/
/* eslint-disable */
// Install `electron-debug` with `devtron`
require('electron-debug')({ showDevTools: true })
// Install `vue-devtools`
require('electron').app.on('ready', () => {
let installExtension = require('electron-devtools-installer')
installExtension.default(installExtension.VUEJS_DEVTOOLS)
.then(() => {})
.catch(err => {
console.log('Unable to install `vue-devtools`: \n', err)
})
})
// Require `main` process to boot app
require('./index')

Wyświetl plik

@ -0,0 +1,90 @@
'use strict'
import {app, BrowserWindow} from 'electron'
const url = require('url')
/**
* Set `__static` path to static files in production
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
*/
if (process.env.NODE_ENV === 'development') {
// we were run as electron --inspect=5858 path/to/main.js <args>
// so get rid of the first two args
console.log("args " + process.argv)
process.argv.shift()
process.argv.shift()
} else {
global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
}
let mainWindow
var target = process.argv[1] || "";
var targetURL = url.parse(target)
var winURL = null;
// Print PDF will give us a full URL to a flask server, bypassing Vue entirely.
// Eventually this will be migrated to Vue.
if (targetURL.protocol) {
winURL = target
} else {
if (process.env.NODE_ENV === 'development') {
winURL = `http://localhost:9080/?${targetURL.query || ""}#${targetURL.pathname || ""}`
} else {
winURL = `file://${__dirname}/index.html?${targetURL.query || ""}#${targetURL.pathname || ""}`;
}
}
function createWindow() {
/**
* Initial window options
*/
mainWindow = new BrowserWindow({
height: 563,
useContentSize: true,
width: 1000,
webPreferences: {nodeIntegration: true}
})
mainWindow.loadURL(winURL)
mainWindow.maximize()
mainWindow.on('closed', () => {
mainWindow = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (mainWindow === null) {
createWindow()
}
})
/**
* Auto Updater
*
* Uncomment the following code below and install `electron-updater` to
* support auto updating. Code Signing with a valid certificate is required.
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
*/
/*
import { autoUpdater } from 'electron-updater'
autoUpdater.on('update-downloaded', () => {
autoUpdater.quitAndInstall()
})
app.on('ready', () => {
if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates()
})
*/

Wyświetl plik

@ -0,0 +1,15 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'my-project'
}
</script>
<style>
/* CSS */
</style>

Wyświetl plik

@ -0,0 +1,583 @@
const inkStitch = require("../../../lib/api")
const Mousetrap = require("mousetrap")
import { SVG } from '@svgdotjs/svg.js'
require('@svgdotjs/svg.panzoom.js/src/svg.panzoom.js')
require('@svgdotjs/svg.filter.js')
const svgpath = require('svgpath')
import Loading from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/vue-loading.css';
import VueSlider from 'vue-slider-component'
import 'vue-slider-component/theme/default.css'
const throttle = require('lodash.throttle')
function SliderMark(command, icon) {
this.label = ""
this.command = command
this.icon = icon
}
export default {
name: 'simulator',
components: {
Loading,
VueSlider
},
data: function () {
return {
loading: false,
controlsExpanded: true,
infoExpanded: false,
infoMaxHeight: 0,
speed: 16,
currentStitch: 1,
currentStitchDisplay: 1,
direction: 1,
numStitches: 1,
animating: false,
sliderProcess: dotPos => this.sliderColorSections,
showTrims: false,
showJumps: false,
showColorChanges: false,
showStops: false,
showNeedlePenetrationPoints: false,
showRealisticPreview: false,
showCursor: true
}
},
watch: {
currentStitch: throttle(function () {
this.currentStitchDisplay = Math.floor(this.currentStitch)
}, 100, {leading: true, trailing: true}),
showNeedlePenetrationPoints: function () {
if (this.needlePenetrationPoints === null) {
return;
}
this.needlePenetrationPoints.forEach(npp => {
if (this.showNeedlePenetrationPoints) {
npp.show()
} else {
npp.hide()
}
})
},
showRealisticPreview() {
let animating = this.animating
this.stop()
if (this.showRealisticPreview) {
if (this.realisticPreview === null) {
// This workflow should be improved and might be a bit unconventional.
// We don't want to make the user wait for it too long.
// It would be best, if the realistic preview could load before it is actually requested.
this.$nextTick(() => {this.loading=true})
setImmediate(()=> {this.generateRealisticPaths()})
setImmediate(()=> {this.loading = false})
}
setImmediate(()=> {
for (let i = 1; i < this.stitches.length; i++) {
if (i < this.currentStitch) {
this.realisticPaths[i].show()
} else {
this.realisticPaths[i].hide()
}
}
this.simulation.hide()
this.realisticPreview.show()
})
} else {
for (let i = 1; i < this.stitches.length; i++) {
if (i < this.currentStitch) {
this.stitchPaths[i].show()
} else {
this.stitchPaths[i].hide()
}
}
this.simulation.show()
this.realisticPreview.hide()
}
if (animating) {
this.start()
}
},
showCursor: function () {
if (this.showCursor) {
this.cursor.show()
} else {
this.cursor.hide()
}
}
},
computed: {
speedDisplay() {
return this.speed * this.direction
},
currentCommand() {
let stitch = this.stitches[Math.floor(this.currentStitch)]
if (stitch === undefined || stitch === null) {
return ""
}
let label = "STITCH"
switch (true) {
case stitch.jump:
label = this.$gettext("JUMP")
break
case stitch.trim:
label = this.$gettext("TRIM")
break
case stitch.stop:
label = this.$gettext("STOP")
break
case stitch.color_change:
label = this.$gettext("COLOR CHANGE")
break
}
return label
},
paused() {
return !this.animating
},
forward() {
return this.direction > 0
},
reverse() {
return this.direction < 0
},
sliderMarks() {
var marks = {}
if (this.showTrims)
Object.assign(marks, this.trimMarks);
if (this.showJumps)
Object.assign(marks, this.jumpMarks);
if (this.showColorChanges)
Object.assign(marks, this.colorChangeMarks);
if (this.showStops)
Object.assign(marks, this.stopMarks);
return marks
}
},
methods: {
toggleInfo() {
this.infoExpanded = !this.infoExpanded;
this.infoMaxHeight = this.$refs.controlInfoButton.getBoundingClientRect().top;
},
toggleControls() {
this.controlsExpanded = !this.controlsExpanded;
},
animationSpeedUp() {
this.speed *= 2.0
},
animationSlowDown() {
this.speed = Math.max(this.speed / 2.0, 1)
},
animationReverse() {
this.direction = -1
this.start()
},
animationForward() {
this.direction = 1
this.start()
},
toggleAnimation(e) {
if (this.animating) {
this.stop()
} else {
this.start()
}
e.preventDefault();
},
animationForwardOneStitch() {
this.setCurrentStitch(this.currentStitch + 1)
},
animationBackwardOneStitch() {
this.setCurrentStitch(this.currentStitch - 1)
},
animationNextCommand() {
let nextCommandIndex = this.getNextCommandIndex()
if (nextCommandIndex === -1) {
this.setCurrentStitch(this.stitches.length)
} else {
this.setCurrentStitch(this.commandList[nextCommandIndex])
}
},
animationPreviousCommand() {
let nextCommandIndex = this.getNextCommandIndex()
let prevCommandIndex = 0
if (nextCommandIndex === -1) {
prevCommandIndex = this.commandList.length - 2
} else {
prevCommandIndex = nextCommandIndex - 2
}
let previousCommand = this.commandList[prevCommandIndex]
if (previousCommand === undefined) {
previousCommand = 1
}
this.setCurrentStitch(previousCommand)
},
getNextCommandIndex() {
let currentStitch = this.currentStitchDisplay
let nextCommand = this.commandList.findIndex(function (command) {
return command > currentStitch
})
return nextCommand
},
onCurrentStitchEntered() {
let newCurrentStitch = parseInt(this.$refs.currentStitchInput.value)
if (isNaN(newCurrentStitch)) {
this.$refs.currentStitchInput.value = Math.floor(this.currentStitch)
} else {
this.setCurrentStitch(parseInt(newCurrentStitch))
}
},
setCurrentStitch(newCurrentStitch) {
this.stop()
this.currentStitch = newCurrentStitch
this.clampCurrentStitch()
this.renderFrame()
},
clampCurrentStitch() {
this.currentStitch = Math.max(Math.min(this.currentStitch, this.numStitches), 0)
},
animate() {
let frameStart = performance.now()
let frameTime = null
if (this.lastFrameStart !== null) {
frameTime = frameStart - this.lastFrameStart
} else {
frameTime = this.targetFramePeriod
}
this.lastFrameStart = frameStart
let numStitches = this.speed * Math.max(frameTime, this.targetFramePeriod) / 1000.0;
this.currentStitch = this.currentStitch + numStitches * this.direction
this.clampCurrentStitch()
this.renderFrame()
if (this.animating && this.shouldAnimate()) {
this.timer = setTimeout(this.animate, Math.max(0, this.targetFramePeriod - frameTime))
} else {
this.timer = null;
this.stop()
}
},
renderFrame() {
while (this.renderedStitch < this.currentStitch) {
this.renderedStitch += 1
if (this.showRealisticPreview) {
this.realisticPaths[this.renderedStitch].show()
} else {
this.stitchPaths[this.renderedStitch].show();
}
}
while (this.renderedStitch > this.currentStitch) {
if (this.showRealisticPreview) {
this.realisticPaths[this.renderedStitch].hide()
} else {
this.stitchPaths[this.renderedStitch].hide();
}
this.renderedStitch -= 1
}
this.moveCursor()
},
shouldAnimate() {
if (this.direction == 1 && this.currentStitch < this.numStitches) {
return true;
} else if (this.direction == -1 && this.currentStitch > 0) {
return true;
} else {
return false;
}
},
start() {
if (!this.animating && this.shouldAnimate()) {
this.animating = true
this.timer = setTimeout(this.animate, 0);
}
},
stop() {
if (this.animating) {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
this.animating = false
this.lastFrameStart = null
}
},
resizeCursor() {
// This makes the cursor stay the same size when zooming in or out.
// I'm not exactly sure how it works, but it does.
this.cursor.size(25 / this.svg.zoom())
this.cursor.stroke({width: 2 / this.svg.zoom()})
// SVG.js seems to move the cursor when we resize it, so we need to put
// it back where it goes.
this.moveCursor()
this.adjustScale()
},
moveCursor() {
let stitch = this.stitches[Math.floor(this.currentStitch)]
if (stitch === null || stitch === undefined) {
this.cursor.hide()
} else if (this.showCursor) {
this.cursor.show()
this.cursor.center(stitch.x, stitch.y)
}
},
adjustScale: throttle(function () {
let one_mm = 96 / 25.4 * this.svg.zoom();
let scaleWidth = one_mm
let simulatorWidth = this.$refs.simulator.getBoundingClientRect().width
let maxWidth = Math.min(simulatorWidth / 2, 300)
while (scaleWidth > maxWidth) {
scaleWidth /= 2.0
}
while (scaleWidth < 100) {
scaleWidth += one_mm
}
let scaleMM = scaleWidth / one_mm
this.scale.plot(`M0,0 v10 h${scaleWidth / 2} v-5 v5 h${scaleWidth / 2} v-10`)
// round and strip trailing zeros, source: https://stackoverflow.com/a/53397618
let mm = scaleMM.toFixed(8).replace(/([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1')
this.scaleLabel.text(`${mm} mm`)
}, 100, {leading: true, trailing: true}
),
generateMarks() {
this.commandList = Array()
for (let i = 1; i < this.stitches.length; i++) {
if (this.stitches[i].trim) {
this.trimMarks[i] = new SliderMark("trim", "cut")
this.commandList.push(i)
} else if (this.stitches[i].stop) {
this.stopMarks[i] = new SliderMark("stop", "pause")
this.commandList.push(i)
} else if (this.stitches[i].jump) {
this.jumpMarks[i] = new SliderMark("jump", "frog")
this.commandList.push(i)
} else if (this.stitches[i].color_change) {
this.colorChangeMarks[i] = new SliderMark("color-change", "exchange-alt")
this.commandList.push(i)
}
}
},
generateColorSections() {
var currentStitch = 0
this.stitchPlan.color_blocks.forEach(color_block => {
this.sliderColorSections.push([
(currentStitch + 1) / this.numStitches * 100,
(currentStitch + color_block.stitches.length) / this.numStitches * 100,
{backgroundColor: color_block.color.visible_on_white.hex}
])
currentStitch += color_block.stitches.length
})
},
generateMarker(color) {
return this.svg.marker(3, 3, add => {
let needlePenetrationPoint = add.circle(3).fill(color).hide()
this.needlePenetrationPoints.push(needlePenetrationPoint)
})
},
generateScale() {
let svg = SVG().addTo(this.$refs.simulator)
svg.node.classList.add("simulation-scale")
this.scale = svg.path("M0,0").stroke({color: "black", width: "1px"}).fill("none")
this.scaleLabel = svg.text("0 mm").move(0, 12)
this.scaleLabel.node.classList.add("simulation-scale-label")
},
generateCursor() {
this.cursor =
this.svg.path("M0,0 v2.8 h1.2 v-2.8 h2.8 v-1.2 h-2.8 v-2.8 h-1.2 v2.8 h-2.8 v1.2 h2.8")
.stroke({
width: 0.1,
color: '#FFFFFF',
})
.fill('#000000')
this.cursor.node.classList.add("cursor")
},
generateRealisticPaths() {
// Create Realistic Filter
this.filter = this.svg.defs().filter()
this.filter.attr({id: "realistic-stitch-filter", x: "-0.1", y: "-0.1", height: "1.2", width: "1.2", style: "color-interpolation-filters:sRGB"})
this.filter.gaussianBlur({id: "gaussianBlur1", stdDeviation: "1.5", in: "SourceAlpha"})
this.filter.componentTransfer(function (add) {
add.funcR({ type: "identity" }),
add.funcG({ type: "identity" }),
add.funcB({ type: "identity", slope: "4.53" }),
add.funcA({ type: "gamma", slope: "0.149", intercept: "0", amplitude: "3.13", offset: "-0.33" })
}).attr({id: "componentTransfer1", in: "gaussianBlur1"})
this.filter.composite({id: "composite1", in: "componentTransfer1", in2: "SourceAlpha", operator: "in"})
this.filter.gaussianBlur({id: "gaussianBlur2", in: "composite1", stdDeviation: 0.09})
this.filter.morphology({id: "morphology1", in: "gaussianBlur2", operator: "dilate", radius: 0.1})
this.filter.specularLighting({id: "specularLighting1", in: "morphology1", specularConstant: 0.709, surfaceScale: 30}).pointLight({z: 10})
this.filter.gaussianBlur({id: "gaussianBlur3", in: "specularLighting1", stdDeviation: 0.04})
this.filter.composite({id: "composite2", in: "gaussianBlur3", in2: "SourceGraphic", operator: "arithmetic", k2: 1, k3: 1, k1: 0, k4: 0})
this.filter.composite({in: "composite2", in2: "SourceAlpha", operator: "in"})
// Create realistic paths in it's own group and move it behind the cursor
this.realisticPreview = this.svg.group({id: 'realistic'}).backward()
this.stitchPlan.color_blocks.forEach(color_block => {
let color = `${color_block.color.visible_on_white.hex}`
let realistic_path_attrs = {fill: color, stroke: "none", filter: this.filter}
let stitching = false
let prevStitch = null
color_block.stitches.forEach(stitch => {
let realisticPath = null
if (stitching && prevStitch) {
// Position
let stitch_center = []
stitch_center.x = (prevStitch.x + stitch.x) / 2.0
stitch_center.y = (prevStitch.y + stitch.y) / 2.0
// Angle
var stitch_angle = Math.atan2(stitch.y - prevStitch.y, stitch.x - prevStitch.x) * (180 / Math.PI)
// Length
let path_length = Math.hypot(stitch.x - prevStitch.x, stitch.y - prevStitch.y)
var path = `M0,0 c 0.4,0,0.4,0.3,0.4,0.6 c 0,0.3,-0.1,0.6,-0.4,0.6 v 0.2,-0.2 h -${path_length} c -0.4,0,-0.4,-0.3,-0.4,-0.6 c 0,-0.3,0.1,-0.6,0.4,-0.6 v -0.2,0.2 z`
path = svgpath(path).rotate(stitch_angle).toString()
realisticPath = this.realisticPreview.path(path).attr(realistic_path_attrs).center(stitch_center.x, stitch_center.y).hide()
} else {
realisticPath = this.realisticPreview.rect(0, 1).attr(realistic_path_attrs).center(stitch.x, stitch.y).hide()
}
this.realisticPaths.push(realisticPath)
if (stitch.trim || stitch.color_change) {
stitching = false
} else if (!stitch.jump) {
stitching = true
}
prevStitch = stitch
})
})
}
},
created: function () {
// non-reactive properties
this.targetFPS = 30
this.targetFramePeriod = 1000.0 / this.targetFPS
this.renderedStitch = 0
this.lastFrameStart = null
this.stitchPaths = [null] // 1-indexed to match up with stitch number display
this.realisticPaths = [null]
this.stitches = [null]
this.svg = null
this.simulation = null
this.realisticPreview = null
this.timer = null
this.sliderColorSections = []
this.trimMarks = {}
this.stopMarks = {}
this.colorChangeMarks = {}
this.jumpMarks = {}
this.needlePenetrationPoints = []
this.cursor = null
},
mounted: function () {
this.svg = SVG().addTo(this.$refs.simulator).size('100%', '100%').panZoom({zoomMin: 0.1})
this.svg.node.classList.add('simulation')
this.simulation = this.svg.group({id: 'line'})
this.loading = true
inkStitch.get('stitch_plan').then(response => {
this.stitchPlan = response.data
let [minx, miny, maxx, maxy] = this.stitchPlan.bounding_box
let width = maxx - minx
let height = maxy - miny
this.svg.viewbox(0, 0, width, height);
this.stitchPlan.color_blocks.forEach(color_block => {
let color = `${color_block.color.visible_on_white.hex}`
let path_attrs = {fill: "none", stroke: color, "stroke-width": 0.3}
let marker = this.generateMarker(color)
let stitching = false
let prevStitch = null
color_block.stitches.forEach(stitch => {
stitch.x -= minx
stitch.y -= miny
let path = null
if (stitching && prevStitch) {
path = this.simulation.path(`M${prevStitch.x},${prevStitch.y} ${stitch.x},${stitch.y}`).attr(path_attrs).hide()
} else {
path = this.simulation.path(`M${stitch.x},${stitch.y} ${stitch.x},${stitch.y}`).attr(path_attrs).hide()
}
path.marker('end', marker)
this.stitchPaths.push(path)
this.stitches.push(stitch)
if (stitch.trim || stitch.color_change) {
stitching = false
} else if (!stitch.jump) {
stitching = true
}
prevStitch = stitch
})
})
this.numStitches = this.stitches.length - 1
this.generateMarks()
this.generateColorSections()
this.generateScale()
this.generateCursor()
this.loading = false
// v-on:keydown doesn't seem to work, maybe an Electron issue?
Mousetrap.bind("up", this.animationSpeedUp)
Mousetrap.bind("down", this.animationSlowDown)
Mousetrap.bind("left", this.animationReverse)
Mousetrap.bind("right", this.animationForward)
Mousetrap.bind("pagedown", this.animationPreviousCommand)
Mousetrap.bind("pageup", this.animationNextCommand)
Mousetrap.bind("space", this.toggleAnimation)
Mousetrap.bind("+", this.animationForwardOneStitch)
Mousetrap.bind("-", this.animationBackwardOneStitch)
this.svg.on('zoom', this.resizeCursor)
this.resizeCursor()
this.start()
})
}
}

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 60 KiB

Wyświetl plik

@ -0,0 +1,280 @@
* {
box-sizing: border-box;
}
.loading-icon {
text-align: center;
margin-bottom: 1rem;
color: rgb(0, 51, 153);
}
.loading-text {
font-family: sans-serif;
}
.loading {
border-radius: 1rem;
border: 3px solid rgb(0, 51, 153);
background-color: rgba(0, 51, 153, 0.1);
padding: 1rem;
}
button {
color: rgb(0, 51, 153)
}
.fa-spin-fast {
animation: fa-spin 0.4s infinite linear;
}
.fa-button {
margin: 3px;
}
.fa-fast {
transform: skew(-15deg, -15deg) rotate(15deg) scale(1.25, 0.90);
}
.fa-motion-lines {
transform: scale(1.0, 1.6) translate(0, -18%) skew(-15deg, -15deg) rotate(15deg);
}
.fa-thin-line {
transform: scale(1.4, 0.4);
}
.panel {
display: flex;
justify-content: space-between;
flex-flow: wrap;
}
.panel > * {
display: inline-block;
vertical-align: middle;
text-align: center;
}
.panel fieldset {
text-align: center;
flex: 1;
white-space: nowrap;
}
fieldset {
border: 2px solid rgb(0, 51, 153);
position: relative;
}
.window-controls {
position: absolute;
top: -0.2rem;
right: -2px;
font-size: 0.8rem;
color: white;
background: rgb(0, 51, 153);
transform: scale(1, 0.8) translate(0, -100%);
}
.window-controls > div {
display: inline-block;
padding: 5px;
}
.control-info {
position: absolute;
top: 0;
left: 0;
overflow-y: auto;
transform: translate(-100%, -100%);
padding: 0 1rem 1rem;
background: white;
color: black;
border: 1px solid rgb(0, 51, 153);
border-radius: 5px;
font-size: 1rem;
}
.info-icon {
float: right;
}
.control-info h1 {
background: rgb(0, 51, 153);
color: white;
margin: 0 -1rem 1rem;
padding: 1rem;
}
.control-info div {
display: table-row;
}
.control-info > div {
display: table;
}
.control-info div:first-child p {
font-weight: bold;
}
.control-info p {
display: table-cell;
white-space: nowrap;
border-bottom: 1px solid #ccc;
padding: 0.5rem;
}
fieldset button {
display: inline-block;
}
fieldset.command span.current-command {
display: block;
width: 18rem;
margin: 0 auto;
font-family: sans-serif;
font-size: 2rem;
vertical-align: middle;
}
fieldset.show-commands {
text-align: left;
}
fieldset.show-commands span {
display: inline-block;
vertical-align: top;
}
fieldset.show-commands span.npp {
text-align: right;
}
fieldset.show-commands span:first-of-type {
padding-right: 12px;
}
button.pressed {
border-style: inset;
}
.vue-slider {
height: 25px !important;
}
.slider-container {
margin-top: 25px;
margin-bottom: 25px;
flex-grow: 0;
}
.slider-container > * {
display: inline-block;
vertical-align: middle;
}
.slider-box {
width: calc(100% - 11rem);
margin-left: 10px;
margin-right: 10px;
}
.slider-container::v-deep .vue-slider-mark-step {
width: 2px;
height: 20px;
border-radius: 2px;
background: #545454;
transform: translate(1px, -25%);
box-shadow: inset 1px 0 1px #ffffffbd, inset -1px 0 1px black;
}
.slider-container::v-deep .vue-slider-mark:first-child .vue-slider-mark-step,
.slider-container::v-deep .vue-slider-mark:last-child .vue-slider-mark-step {
display: block;
}
.slider-container::v-deep .vue-slider-rail {
border: 1px solid #dedede;
}
.slider-container::v-deep .vue-slider-process {
border-radius: 0;
box-shadow: inset 0px 2px 2px #ffffff80, inset 0px -2px 2px #0000002e;
}
.slider-container::v-deep .vue-slider-process:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
.slider-container::v-deep .vue-slider-process:nth-last-child(3) {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.vue-slider-mark-label {
font-size: 1.4rem;
color: #545454;
}
.vue-slider-mark-label svg path {
stroke: white;
stroke-width: 10px;
}
.slider-label-trim {
transform: scale(1, 0.75) translate(-50%, -9px);
}
.slider-label-stop {
font-size: 1.2rem;
transform: translate(-50%, 12px);
}
.slider-label-jump {
transform: translate(-50%, -50px);
}
.slider-label-color-change {
transform: translate(-50%, 10px);
}
.current-stitch-input {
width: 4rem;
float: right;
font-size: 1rem;
}
.simulator {
display: flex;
flex-direction: column;
height: 95vh;
}
.current-command {
color: rgb(0, 51, 153);
font-weight: bold;
}
/* we need ::v-deep here because the svg tag is added by svg.js and so Vue
can't add the data-v-* attribute
*/
div.simulator::v-deep svg.simulation {
flex-grow: 1;
flex-shrink: 1;
order: -2;
}
div.simulator::v-deep svg.simulation-scale {
height: 50px;
order: -1;
}
div.simulator::v-deep .simulation-scale-label {
font-size: 14px;
}
div.simulator::v-deep .cursor {
/* not sure what to add here to make it more visible */
}

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -0,0 +1,268 @@
<template>
<div ref="simulator" class="simulator vld-parent">
<fieldset>
<div class="window-controls">
<div ref="controlInfoButton" class="control-info-button" v-on:click="toggleInfo">
<font-awesome-icon icon="info"/>
<collapse-transition>
<div class="control-info" v-show="infoExpanded" v-bind:style="{'max-height': infoMaxHeight + 'px'}">
<h1>
<font-awesome-icon icon="info" class="info-icon"/>
<translate>Simulator Shortcut Keys</translate>
</h1>
<div>
<div>
<p>
<translate>Button</translate>
</p>
<p>
<translate>Function</translate>
</p>
<p>
<translate>Shortcut Key</translate>
</p>
</div>
<div>
<p>
<font-awesome-icon icon="pause" class="fa-button"/>
</p>
<p>
<translate>Pause</translate>
</p>
<p>
<translate>Space</translate>
</p>
</div>
<div>
<p>
<font-awesome-icon icon="play" class="fa-button"/>
</p>
<p>
<translate>Play</translate>
</p>
<p>P</p>
</div>
<div>
<p>
<font-awesome-icon icon="angle-double-left" class="fa-button"/>
</p>
<p>
<translate>Play backward</translate>
</p>
<p>
<translate translate-comment="name for left arrow keyboard key"> Arrow left</translate>
</p>
</div>
<div>
<p>
<font-awesome-icon icon="angle-double-right" class="fa-button"/>
</p>
<p>
<translate>Play forward</translate>
</p>
<p>
<translate translate-comment="name for right arrow keyboard key"> Arrow right</translate>
</p>
</div>
<div>
<p>
<font-awesome-icon icon="shoe-prints" class="fa-button fa-flip-horizontal"/>
</p>
<p>
<translate translate-comment="description of keyboard shortcut that moves one stitch backward in simulator">
One step backward
</translate>
</p>
<p>-
<translate translate-comment="name for this keyboard key: -">Minus</translate>
</p>
</div>
<div>
<p>
<font-awesome-icon icon="shoe-prints" class="fa-button"/>
</p>
<p>
<translate translate-comment="description of keyboard shortcut that moves one stitch forward in simulator">
One step forward
</translate>
</p>
<p>
<translate translate-comment="name for this keyboard key: +">+ Plus</translate>
</p>
</div>
<div>
<p>
<font-awesome-icon icon="step-backward" class="fa-button"/>
</p>
<p>
<translate>Jump to previous command</translate>
</p>
<p><translate translate-comment="name for page down keyboard key">Page down (PgDn)</translate></p>
</div>
<div>
<p>
<font-awesome-icon icon="step-forward" class="fa-button"/>
</p>
<p>
<translate>Jump to next command</translate>
</p>
<p><translate translate-comment="name for page up keyboard key">Page up (PgUp)</translate></p>
</div>
<div>
<p>
<font-awesome-icon icon="hippo" class="fa-button"/>
</p>
<p>
<translate>Slow down</translate>
</p>
<p>
<translate translate-comment="name for down arrow keyboard key"> Arrow down</translate>
</p>
</div>
<div>
<p>
<font-awesome-icon icon="horse" class="fa-button"/>
</p>
<p>
<translate>Speed up</translate>
</p>
<p>
<translate translate-comment="name for up arrow keyboard key"> Arrow up</translate>
</p>
</div>
</div>
</div>
</collapse-transition>
</div>
<div class="toggle-controls" v-on:click="toggleControls">
<font-awesome-icon v-if="controlsExpanded" icon="minus"/>
<font-awesome-icon v-else icon="plus"/>
</div>
</div>
<collapse-transition>
<div class="panel" v-show="controlsExpanded">
<fieldset class="controls">
<legend>
<translate>Controls</translate>
</legend>
<button v-on:click="stop" :class="{pressed: paused}" :title="$gettext('Pause (space)')">
<font-awesome-icon icon="pause" size="2x" class="fa-button"/>
</button>
<button v-on:click="start" :class="{pressed: animating}" :title="$gettext('Play (arrow left | arrow right)')">
<font-awesome-icon icon="play" size="2x" class="fa-button"/>
</button>
<button v-on:click="animationReverse" :class="{pressed: reverse}" :title="$gettext('Play backward (arrow left)')">
<font-awesome-icon icon="angle-double-left" size="2x" class="fa-button" :mask="['fas', 'stop']"/>
</button>
<button v-on:click="animationForward" :class="{pressed: forward}" :title="$gettext('Play forward (arrow right)')">
<font-awesome-icon icon="angle-double-right" size="2x" class="fa-button" :mask="['fas', 'stop']"/>
</button>
<button v-on:click="animationBackwardOneStitch" :title="$gettext('One step backward (-)')">
<font-awesome-icon icon="shoe-prints" size="2x" class="fa-button fa-flip-horizontal"/>
</button>
<button v-on:click="animationForwardOneStitch" :title="$gettext('One step forward (+)')">
<font-awesome-icon icon="shoe-prints" size="2x" class="fa-button"/>
</button>
<button v-on:click="animationPreviousCommand" :title="$gettext('Jump to previous command (Page down)')">
<font-awesome-icon icon="step-backward" size="2x" class="fa-button"/>
</button>
<button v-on:click="animationNextCommand" :title="$gettext('Jump to next command (Page up)')">
<font-awesome-icon icon="step-forward" size="2x" class="fa-button"/>
</button>
</fieldset>
<fieldset class="speed">
<legend>
<translate :translate-n="speed" translate-plural="Speed: %{speed} stitches/sec">Speed: %{speed} stitch/sec</translate>
</legend>
<button v-on:click="animationSlowDown" :title="$gettext('Slow down (arrow down)')">
<font-awesome-icon icon="hippo" size="2x" class="fa-button"/>
</button>
<button v-on:click="animationSpeedUp" :title="$gettext('Speed up (arrow up)')">
<font-awesome-icon icon="align-right" class="fa-motion-lines"/>
<font-awesome-icon icon="horse" size="2x" class="fa-button fa-fast"/>
</button>
</fieldset>
<fieldset class="command">
<legend>
<translate>Command</translate>
</legend>
<span class="current-command">{{currentCommand}}</span>
</fieldset>
<fieldset class="show-commands">
<legend>Show</legend>
<span>
<input id="trim-checkbox" type="checkbox" v-model="showTrims"/>
<label for="trim-checkbox"><font-awesome-icon icon="cut"/> <translate>trims</translate></label>
<br/>
<input id="jump-checkbox" type="checkbox" v-model="showJumps"/>
<label for="jump-checkbox"><font-awesome-icon icon="frog"/> <translate>jumps</translate></label>
</span>
<span>
<input id="color-change-checkbox" type="checkbox" v-model="showColorChanges"/>
<label for="color-change-checkbox"><font-awesome-icon icon="exchange-alt"/> <translate>color changes</translate></label>
<br/>
<input id="stop-checkbox" type="checkbox" v-model="showStops"/>
<label for="stop-checkbox"><font-awesome-icon icon="pause"/> <translate>stops</translate></label>
</span>
<span class="npp">
<input id="npp-checkbox" type="checkbox" v-model="showNeedlePenetrationPoints"/>
<label for="npp-checkbox">
<font-awesome-layers>
<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>
</label>
</span>
<span>
<input id="realistic-checkbox" type="checkbox" v-model="showRealisticPreview"/>
<label for="realistic-checkbox"><font-awesome-icon icon="eye"/> <span v-translate>realistic</span></label>
<br/>
<input id="cursor-checkbox" type="checkbox" v-model="showCursor"/>
<label for="cursor-checkbox"><font-awesome-icon icon="plus"/> <span v-translate>cursor</span></label>
</span>
</fieldset>
</div>
</collapse-transition>
<div class="slider-container">
<span>1</span>
<span class="slider-box">
<vue-slider
:value="currentStitchDisplay"
@change="setCurrentStitch"
:min="0"
:max="numStitches"
:duration="0"
:marks="sliderMarks"
:process="sliderProcess">
<template v-slot:label="mark">
<div :class="['vue-slider-mark-label', `slider-label-${mark.command}`, { active: mark.active }]">
<font-awesome-icon :icon="mark.icon"/>
</div>
</template>
</vue-slider>
</span>
<span>{{numStitches}}</span>
<input ref="currentStitchInput"
class="current-stitch-input"
:value="currentStitchDisplay"
@change="onCurrentStitchEntered"
@focus="stop"/>
</div>
</fieldset>
<loading :active.sync="loading" :is-full-page="false">
<div class="loading">
<div class="loading-icon">
<font-awesome-icon icon="spinner" size="4x" pulse/>
</div>
<div class="loading-text">
<translate>Rendering stitch-plan...</translate>
</div>
</div>
</loading>
</div>
</template>
<script src="../assets/js/simulator.js"></script>
<style src="../assets/style/simulator.css" scoped></style>

Wyświetl plik

@ -0,0 +1,83 @@
// ES6
import Vue from 'vue'
import axios from 'axios'
import App from './App'
import router from './router'
import {library} from '@fortawesome/fontawesome-svg-core'
import {
faAlignRight,
faAngleDoubleLeft,
faAngleDoubleRight,
faAngleRight,
faCircle,
faCut,
faExchangeAlt,
faEye,
faFrog,
faHippo,
faHorse,
faInfo,
faMinus,
faPause,
faPlay,
faPlus,
faShoePrints,
faSpinner,
faStepBackward,
faStepForward,
faStop
} from '@fortawesome/free-solid-svg-icons'
import {FontAwesomeIcon, FontAwesomeLayers} from '@fortawesome/vue-fontawesome'
import Transitions from 'vue2-transitions'
import GetTextPlugin from 'vue-gettext'
import translations from './assets/translations.json'
import {selectLanguage} from '../lib/i18n'
// We have to add to the library every icon we use anywhere in the UI.
// This avoids the need to bundle the entire font-awesome icon set with
// Ink/Stitch.
library.add(
faAlignRight,
faAngleDoubleLeft,
faAngleDoubleRight,
faAngleRight,
faCircle,
faCut,
faExchangeAlt,
faEye,
faFrog,
faHippo,
faHorse,
faInfo,
faMinus,
faPause,
faPlay,
faPlus,
faShoePrints,
faSpinner,
faStepBackward,
faStepForward,
faStop
)
Vue.component('font-awesome-icon', FontAwesomeIcon)
Vue.component('font-awesome-layers', FontAwesomeLayers)
Vue.use(Transitions)
Vue.use(GetTextPlugin, {
translations: translations,
defaultLanguage: selectLanguage(),
silent: true
})
Vue.http = Vue.prototype.$http = axios
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
components: {App},
router,
template: '<App/>'
}).$mount('#app')

Wyświetl plik

@ -0,0 +1,18 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/simulator',
name: 'simulator',
component: require('@/components/Simulator').default
},
{
path: '*',
redirect: '/'
}
]
})

Wyświetl plik

Plik diff jest za duży Load Diff

Wyświetl plik

@ -0,0 +1 @@
from .server import APIServer

110
lib/api/server.py 100644
Wyświetl plik

@ -0,0 +1,110 @@
import errno
import logging
import socket
from threading import Thread
import time
from flask import Flask, request, g
import requests
from ..utils.json import InkStitchJSONEncoder
from .simulator import simulator
from .stitch_plan import stitch_plan
class APIServer(Thread):
def __init__(self, *args, **kwargs):
self.extension = args[0]
Thread.__init__(self, *args[1:], **kwargs)
self.daemon = True
self.shutting_down = False
self.app = None
self.host = None
self.port = None
self.ready = False
self.__setup_app()
def __setup_app(self): # noqa: C901
self.app = Flask(__name__)
self.app.json_encoder = InkStitchJSONEncoder
self.app.register_blueprint(simulator, url_prefix="/simulator")
self.app.register_blueprint(stitch_plan, url_prefix="/stitch_plan")
@self.app.before_request
def store_extension():
# make the InkstitchExtension object available to the view handling
# this request
g.extension = self.extension
@self.app.route('/shutdown', methods=['POST'])
def shutdown():
self.shutting_down = True
request.environ.get('werkzeug.server.shutdown')()
return "shutting down"
@self.app.route('/ping')
def ping():
return "pong"
def stop(self):
# for whatever reason, shutting down only seems possible in
# the context of a flask request, so we'll just make one
requests.post("http://%s:%s/shutdown" % (self.host, self.port))
def disable_logging(self):
logging.getLogger('werkzeug').setLevel(logging.ERROR)
def run(self):
self.disable_logging()
self.host = "127.0.0.1"
self.port = 5000
while True:
try:
self.app.run(self.host, self.port, threaded=True)
except socket.error as e:
if e.errno == errno.EADDRINUSE:
self.port += 1
continue
else:
raise
else:
break
def ready_checker(self):
"""Wait until the server is started.
Annoyingly, there's no way to get a callback to be run when the Flask
server starts. Instead, we'll have to poll.
"""
while True:
if self.port:
try:
response = requests.get("http://%s:%s/ping" % (self.host, self.port))
if response.status_code == 200:
break
except socket.error, e:
if e.errno == errno.ECONNREFUSED:
pass
else:
raise
time.sleep(0.1)
def start_server(self):
"""Start the API server.
returns: port (int) -- the port that the server is listening on
(on localhost)
"""
checker = Thread(target=self.ready_checker)
checker.start()
self.start()
checker.join()
return self.port

Wyświetl plik

@ -0,0 +1,3 @@
from flask import Blueprint
simulator = Blueprint('simulator', __name__)

Wyświetl plik

@ -0,0 +1,17 @@
from flask import Blueprint, g, jsonify
from ..stitch_plan import patches_to_stitch_plan
stitch_plan = Blueprint('stitch_plan', __name__)
@stitch_plan.route('')
def get_stitch_plan():
if not g.extension.get_elements():
return dict(colors=[], stitch_blocks=[], commands=[])
patches = g.extension.elements_to_patches(g.extension.elements)
stitch_plan = patches_to_stitch_plan(patches)
return jsonify(stitch_plan)

Wyświetl plik

@ -16,7 +16,7 @@ from output import Output
from params import Params
from print_pdf import Print
from remove_embroidery_settings import RemoveEmbroiderySettings
from simulate import Simulate
from simulator import Simulator
from stitch_plan_preview import StitchPlanPreview
from zip import Zip
@ -25,7 +25,6 @@ __all__ = extensions = [Embroider,
Install,
Params,
Print,
Simulate,
Input,
Output,
Zip,
@ -40,4 +39,5 @@ __all__ = extensions = [Embroider,
Troubleshoot,
RemoveEmbroiderySettings,
BreakApart,
ImportThreadlist]
ImportThreadlist,
Simulator]

Wyświetl plik

@ -0,0 +1,17 @@
from ..api import APIServer
from ..gui import open_url
from .base import InkstitchExtension
class Simulator(InkstitchExtension):
def __init__(self):
InkstitchExtension.__init__(self)
def effect(self):
api_server = APIServer(self)
port = api_server.start_server()
electron = open_url("/simulator?port=%d" % port)
electron.wait()
api_server.stop()
api_server.join()

Wyświetl plik

@ -31,3 +31,6 @@ class Stitch(Point):
def copy(self):
return Stitch(self.x, self.y, self.color, self.jump, self.stop, self.trim, self.color_change, self.no_ties)
def __json__(self):
return vars(self)

Wyświetl plik

@ -98,6 +98,14 @@ class StitchPlan(object):
def __repr__(self):
return "StitchPlan(%s)" % ", ".join(repr(cb) for cb in self.color_blocks)
def __json__(self):
return dict(color_blocks=self.color_blocks,
num_stops=self.num_stops,
num_trims=self.num_trims,
num_stitches=self.num_stitches,
bounding_box=self.bounding_box
)
@property
def num_colors(self):
"""Number of unique colors in the stitch plan."""
@ -175,6 +183,9 @@ class ColorBlock(object):
def __delitem__(self, item):
del self.stitches[item]
def __json__(self):
return dict(color=self.color, stitches=self.stitches)
def has_color(self):
return self._color is not None

Wyświetl plik

@ -1,7 +1,8 @@
import simplestyle
import re
import colorsys
from pyembroidery.EmbThread import EmbThread
import re
import simplestyle
class ThreadColor(object):
@ -27,6 +28,20 @@ class ThreadColor(object):
self.number = number
self.manufacturer = manufacturer
def __json__(self):
jsonified = self._as_dict()
jsonified["visible_on_white"] = self.visible_on_white._as_dict()
return jsonified
def _as_dict(self):
return dict(name=self.name,
number=self.number,
manufacturer=self.manufacturer,
rgb=self.rgb,
hex=self.to_hex_str(),
)
def __eq__(self, other):
if isinstance(other, ThreadColor):
return self.rgb == other.rgb

Wyświetl plik

@ -63,6 +63,9 @@ class Point:
self.x = x
self.y = y
def __json__(self):
return vars(self)
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)

15
lib/utils/json.py 100644
Wyświetl plik

@ -0,0 +1,15 @@
from flask.json import JSONEncoder
class InkStitchJSONEncoder(JSONEncoder):
"""JSON encoder class that runs __json__ on an object if available.
The __json__ method should return a JSON-compatible representation of the
object.
"""
def default(self, obj):
try:
return obj.__json__()
except AttributeError:
return JSONEncoder.default(self, obj)

Wyświetl plik

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>{% trans %}Simulate{% endtrans %}</name>
<id>org.inkstitch.simulate.{{ locale }}</id>
<param name="extension" type="string" gui-hidden="true">simulate</param>
<name>{% trans %}Simulator{% endtrans %}</name>
<id>org.inkstitch.simulator.{{ locale }}</id>
<dependency type="executable" location="extensions">inkstitch.py</dependency>
<dependency type="executable" location="extensions">inkex.py</dependency>
<param name="extension" type="string" gui-hidden="true">simulator</param>
<effect>
<object-type>all</object-type>
<effects-menu>
@ -10,6 +12,6 @@
</effects-menu>
</effect>
<script>
{{ command_tag | safe }}
<command reldir="extensions" interpreter="python">inkstitch.py</command>
</script>
</inkscape-extension>