Merge pull request #1732 from inkstitch/lexelby/cache-stitch-plan

stitch plan caching
pull/2088/head
Lex Neva 2023-02-20 15:27:15 -05:00 zatwierdzone przez GitHub
commit 8b98083ac7
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
19 zmienionych plików z 632 dodań i 91 usunięć

Wyświetl plik

@ -0,0 +1,216 @@
<template>
<v-card raised rounded="lg" class="preferences-card">
<v-card-title class="text-center justify-center py-6">
<h1 class="font-weight-bold text-h2 basil--text">
Ink/Stitch Settings
</h1>
</v-card-title>
<v-card-text>
<v-tabs v-model="tab" background-color="transparent" grow>
<v-tab key="this_svg_settings">
This SVG
</v-tab>
<v-tab key="global_settings">
Global
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item key="this_svg_settings">
<v-card flat>
<v-card-text>
<table>
<tr>
<td class="label">
<v-tooltip bottom>
<template v-slot:activator="{on, attrs}">
<label for="collapse_len_mm" v-on="on" v-bind="attrs"><translate>Minimum jump stitch length</translate></label>
</template>
<label for="collapse_len_mm"><translate>Jump stitches smaller than this will be treated as normal stitches.</translate></label>
</v-tooltip>
</td>
<td class="preference">
<v-text-field hide-details id="collapse_len_mm" @change="updateSettings" v-model.number="this_svg_settings.collapse_len_mm" type="number"></v-text-field>
</td>
<td class="unit">
mm
</td>
<td class="button">
<v-btn small @click="set_default('collapse_len_mm')"><translate>set as default</translate></v-btn>
</td>
</tr>
<tr>
<td class="label">
<v-tooltip bottom>
<template v-slot:activator="{on, attrs}">
<label for="min_stitch_len_mm" v-on="on" v-bind="attrs"><translate>Minimum stitch length</translate></label>
</template>
<label for="min_stitch_len_mm"><translate>Drop stitches smaller than this value.</translate></label>
</v-tooltip>
</td>
<td class="preference">
<v-text-field hide-details id="min_stitch_len_mm" @change="updateSettings" v-model.number="this_svg_settings.min_stitch_len_mm" type="number"></v-text-field>
</td>
<td class="unit">
mm
</td>
<td class="button">
<v-btn small @click="set_default('min_stitch_len_mm')"><translate>set as default</translate></v-btn>
</td>
</tr>
</table>
</v-card-text>
</v-card>
</v-tab-item>
<v-tab-item key="global_settings">
<v-card flat>
<v-card-text>
<table>
<tr>
<td>
<v-tooltip bottom>
<template v-slot:activator="{on, attrs}">
<label for="default_collapse_len_mm" v-on="on" v-bind="attrs"><translate>Default minimum jump stitch length</translate></label>
</template>
<label for="default_collapse_len_mm"><translate>Used for new SVGs.</translate></label>
</v-tooltip>
</td>
<td class="preference">
<v-text-field hide-details id="default_min_stitch_len_mm" @change="updateSettings" v-model.number="global_settings.default_collapse_len_mm" type="number"></v-text-field>
</td>
<td class="unit">
mm
</td>
</tr>
<tr>
<td>
<v-tooltip bottom>
<template v-slot:activator="{on, attrs}">
<label for="default_min_stitch_len_mm" v-on="on" v-bind="attrs"><translate>Default minimum stitch length</translate></label>
</template>
<label for="default_min_stitch_len_mm"><translate>Used for new SVGs.</translate></label>
</v-tooltip>
</td>
<td class="preference">
<v-text-field hide-details id="default_min_stitch_len_mm" @change="updateSettings" v-model.number="global_settings.default_min_stitch_len_mm" type="number"></v-text-field>
</td>
<td class="unit">
mm
</td>
</tr>
<tr>
<td>
<v-tooltip bottom>
<template v-slot:activator="{on, attrs}">
<label for="cache_size" v-on="on" v-bind="attrs"><translate>Stitch plan cache size</translate></label>
</template>
<label for="default_min_stitch_len_mm"><translate>The greater the number, the more stitch plans can be cached, speeding up stitch plan calculation. Default: 100</translate></label>
</v-tooltip>
</td>
<td class="preference">
<v-text-field hide-details id="cache_size" @change="updateSettings" v-model.number="global_settings.cache_size" type="number"></v-text-field>
</td>
<td class="unit">
MB
</td>
<td class="button">
<v-btn small @click="clearCache"><translate>clear stitch plan cache</translate></v-btn>
</td>
</tr>
</table>
</v-card-text>
</v-card>
</v-tab-item>
</v-tabs-items>
</v-card-text>
<v-card-actions>
<v-btn text color="primary" @click="close"><translate>done</translate></v-btn>
</v-card-actions>
</v-card>
</template>
<script>
const inkStitch = require("../../lib/api")
export default {
name: "Preferences",
data: function () {
return {
tab: "this_svg_settings",
this_svg_settings: {
collapse_len_mm: null,
min_stitch_len_mm: null,
},
global_settings: {
default_min_stitch_len_mm: null,
default_collapse_len_mm: null,
cache_size: null
}
}
},
created: function () {
inkStitch.get("preferences/").then(response => {
Object.assign(this.this_svg_settings, response.data.this_svg_settings)
Object.assign(this.global_settings, response.data.global_settings)
})
},
methods: {
setDefault(preference_name) {
console.log(`set default: ${preference_name}`)
this.global_settings[`default_${preference_name}`] = this.this_svg_settings[preference_name]
this.updateSettings()
},
updateSettings() {
console.log(this.this_svg_settings)
console.log(this.global_settings)
inkStitch.post("preferences/", {
this_svg_settings: this.this_svg_settings,
global_settings: this.global_settings
}).then(response => {
console.log(response)
})
},
clearCache() {
inkStitch.post("preferences/clear_cache").then(response => {
console.log(response)
})
},
close() {
window.close()
}
}
}
</script>
<style scoped>
.preferences-card {
margin-left: 20%;
margin-right: 20%;
margin-top: 5%;
}
td.label {
vertical-align: bottom;
}
td.preference {
padding-right: 4px;
padding-left: 16px;
max-width: 100px;
}
td.preference::v-deep input {
text-align: right;
}
td.unit {
vertical-align: bottom;
}
td.button {
vertical-align: bottom;
padding-left: 12px;
}
</style>

Wyświetl plik

@ -23,6 +23,11 @@ export default new Router({
name: 'install',
component: require('@/components/InstallPalettes').default
},
{
path: '/preferences',
name: 'preferences',
component: require('@/components/Preferences').default
},
{
path: '*',
redirect: '/'

Wyświetl plik

@ -0,0 +1,41 @@
# Authors: see git history
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from flask import Blueprint, g, jsonify, request
from ..utils.cache import get_stitch_plan_cache
from ..utils.settings import global_settings
preferences = Blueprint('preferences', __name__)
@preferences.route('/', methods=["POST"])
def update_preferences():
metadata = g.extension.get_inkstitch_metadata()
metadata.update(request.json['this_svg_settings'])
global_settings.update(request.json['global_settings'])
# cache size may have changed
stitch_plan_cache = get_stitch_plan_cache()
stitch_plan_cache.size_limit = global_settings['cache_size'] * 1024 * 1024
stitch_plan_cache.cull()
return jsonify({"status": "success"})
@preferences.route('/', methods=["GET"])
def get_preferences():
metadata = g.extension.get_inkstitch_metadata()
return jsonify({"status": "success",
"this_svg_settings": metadata,
"global_settings": global_settings
})
@preferences.route('/clear_cache', methods=["POST"])
def clear_cache():
stitch_plan_cache = get_stitch_plan_cache()
stitch_plan_cache.clear(retry=True)
return jsonify({"status": "success"})

Wyświetl plik

@ -18,6 +18,7 @@ from ..utils.json import InkStitchJSONEncoder
from .install import install
from .simulator import simulator
from .stitch_plan import stitch_plan
from .preferences import preferences
class APIServer(Thread):
@ -45,6 +46,7 @@ class APIServer(Thread):
self.app.register_blueprint(simulator, url_prefix="/simulator")
self.app.register_blueprint(stitch_plan, url_prefix="/stitch_plan")
self.app.register_blueprint(install, url_prefix="/install")
self.app.register_blueprint(preferences, url_prefix="/preferences")
@self.app.before_request
def store_extension():

Wyświetl plik

@ -2,7 +2,6 @@
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import sys
from copy import deepcopy
import numpy as np
@ -11,12 +10,15 @@ import inkex
from inkex import bezier
from ..commands import find_commands
from ..debug import debug
from ..i18n import _
from ..patterns import apply_patterns
from ..marker import get_marker_elements_cache_key_data
from ..patterns import apply_patterns, get_patterns_cache_key_data
from ..svg import (PIXELS_PER_MM, apply_transforms, convert_length,
get_node_transform)
from ..svg.tags import INKSCAPE_LABEL, INKSTITCH_ATTRIBS
from ..utils import Point, cache
from ..utils.cache import get_stitch_plan_cache, CacheKeyGenerator
class Param(object):
@ -189,9 +191,6 @@ class EmbroideryElement(object):
style = None
return style
def has_style(self, style_name):
return self._get_style_raw(style_name) is not None
@property
@cache
def stroke_scale(self):
@ -392,21 +391,100 @@ class EmbroideryElement(object):
def to_stitch_groups(self, last_patch):
raise NotImplementedError("%s must implement to_stitch_groups()" % self.__class__.__name__)
def embroider(self, last_patch):
self.validate()
@debug.time
def _load_cached_stitch_groups(self, previous_stitch):
if not self.uses_previous_stitch():
# we don't care about the previous stitch
previous_stitch = None
patches = self.to_stitch_groups(last_patch)
apply_patterns(patches, self.node)
cache_key = self._get_cache_key(previous_stitch)
stitch_groups = get_stitch_plan_cache().get(cache_key)
for patch in patches:
patch.tie_modus = self.ties
patch.force_lock_stitches = self.force_lock_stitches
if stitch_groups:
debug.log(f"used cache for {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}")
else:
debug.log(f"did not use cache for {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}, key={cache_key}")
if patches:
patches[-1].trim_after = self.has_command("trim") or self.trim_after
patches[-1].stop_after = self.has_command("stop") or self.stop_after
return stitch_groups
return patches
def uses_previous_stitch(self):
"""Returns True if the previous stitch can affect this Element's stitches.
This function may be overridden in a subclass.
"""
return False
@debug.time
def _save_cached_stitch_groups(self, stitch_groups, previous_stitch):
stitch_plan_cache = get_stitch_plan_cache()
cache_key = self._get_cache_key(previous_stitch)
if cache_key not in stitch_plan_cache:
stitch_plan_cache[cache_key] = stitch_groups
if previous_stitch is not None:
# Also store it with None as the previous stitch, so that it can be used next time
# if we don't care about the previous stitch
cache_key = self._get_cache_key(None)
if cache_key not in stitch_plan_cache:
stitch_plan_cache[cache_key] = stitch_groups
def get_params_and_values(self):
params = {}
for param in self.get_params():
params[param.name] = self.get_param(param.name, param.default)
return params
@cache
def _get_patterns_cache_key_data(self):
return get_patterns_cache_key_data(self.node)
@cache
def _get_guides_cache_key_data(self):
return get_marker_elements_cache_key_data(self.node, "guide-line")
def _get_cache_key(self, previous_stitch):
cache_key_generator = CacheKeyGenerator()
cache_key_generator.update(self.__class__.__name__)
cache_key_generator.update(self.get_params_and_values())
cache_key_generator.update(self.parse_path())
cache_key_generator.update(list(self._get_specified_style().items()))
cache_key_generator.update(previous_stitch)
cache_key_generator.update([(c.command, c.target_point) for c in self.commands])
cache_key_generator.update(self._get_patterns_cache_key_data())
cache_key_generator.update(self._get_guides_cache_key_data())
cache_key = cache_key_generator.get_cache_key()
debug.log(f"cache key for {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)} {previous_stitch}: {cache_key}")
return cache_key
def embroider(self, last_stitch_group):
debug.log(f"starting {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}")
if last_stitch_group:
previous_stitch = last_stitch_group.stitches[-1]
else:
previous_stitch = None
stitch_groups = self._load_cached_stitch_groups(previous_stitch)
if not stitch_groups:
self.validate()
stitch_groups = self.to_stitch_groups(last_stitch_group)
apply_patterns(stitch_groups, self.node)
for stitch_group in stitch_groups:
stitch_group.tie_modus = self.ties
stitch_group.force_lock_stitches = self.force_lock_stitches
if stitch_groups:
stitch_groups[-1].trim_after = self.has_command("trim") or self.trim_after
stitch_groups[-1].stop_after = self.has_command("stop") or self.stop_after
self._save_cached_stitch_groups(stitch_groups, previous_stitch)
debug.log(f"ending {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}")
return stitch_groups
def fatal(self, message, point_to_troubleshoot=False):
label = self.node.get(INKSCAPE_LABEL)

Wyświetl plik

@ -530,24 +530,30 @@ class FillStitch(EmbroideryElement):
def fill_shape(self, shape):
return self.shrink_or_grow_shape(shape, self.expand)
def get_starting_point(self, last_patch):
def get_starting_point(self, previous_stitch_group):
# If there is a "fill_start" Command, then use that; otherwise pick
# the point closest to the end of the last patch.
if self.get_command('fill_start'):
return self.get_command('fill_start').target_point
elif last_patch:
return last_patch.stitches[-1]
elif previous_stitch_group:
return previous_stitch_group.stitches[-1]
else:
return None
def uses_previous_stitch(self):
if self.get_command('fill_start'):
return False
else:
return True
def get_ending_point(self):
if self.get_command('fill_end'):
return self.get_command('fill_end').target_point
else:
return None
def to_stitch_groups(self, last_patch): # noqa: C901
def to_stitch_groups(self, previous_stitch_group): # noqa: C901
# backwards compatibility: legacy_fill used to be inkstitch:auto_fill == False
if not self.auto_fill or self.fill_method == 3:
return self.do_legacy_fill()
@ -556,7 +562,7 @@ class FillStitch(EmbroideryElement):
end = self.get_ending_point()
for shape in self.shape.geoms:
start = self.get_starting_point(last_patch)
start = self.get_starting_point(previous_stitch_group)
try:
if self.fill_underlay:
underlay_shapes = self.underlay_shape(shape)
@ -567,16 +573,16 @@ class FillStitch(EmbroideryElement):
fill_shapes = self.fill_shape(shape)
for fill_shape in fill_shapes.geoms:
if self.fill_method == 0:
stitch_groups.extend(self.do_auto_fill(fill_shape, last_patch, start, end))
stitch_groups.extend(self.do_auto_fill(fill_shape, previous_stitch_group, start, end))
if self.fill_method == 1:
stitch_groups.extend(self.do_contour_fill(fill_shape, last_patch, start))
stitch_groups.extend(self.do_contour_fill(fill_shape, previous_stitch_group, start))
elif self.fill_method == 2:
stitch_groups.extend(self.do_guided_fill(fill_shape, last_patch, start, end))
stitch_groups.extend(self.do_guided_fill(fill_shape, previous_stitch_group, start, end))
except ExitThread:
raise
except Exception:
self.fatal_fill_error()
last_patch = stitch_groups[-1]
previous_stitch_group = stitch_groups[-1]
return stitch_groups

Wyświetl plik

@ -17,7 +17,7 @@ from .cut_satin import CutSatin
from .cutwork_segmentation import CutworkSegmentation
from .density_map import DensityMap
from .duplicate_params import DuplicateParams
from .embroider_settings import EmbroiderSettings
from .preferences import Preferences
from .fill_to_stroke import FillToStroke
from .flip import Flip
from .generate_palette import GeneratePalette
@ -96,5 +96,5 @@ __all__ = extensions = [StitchPlanPreview,
Simulator,
Reorder,
DuplicateParams,
EmbroiderSettings,
Preferences,
CutworkSegmentation]

Wyświetl plik

@ -23,6 +23,7 @@ from ..svg import generate_unique_id
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
NOT_EMBROIDERABLE_TAGS, SVG_CLIPPATH_TAG, SVG_DEFS_TAG,
SVG_GROUP_TAG, SVG_MASK_TAG)
from ..utils.settings import DEFAULT_METADATA, global_settings
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
@ -52,9 +53,14 @@ class InkStitchMetadata(MutableMapping):
"""
def __init__(self, document):
super().__init__()
self.document = document
self.metadata = document.metadata
for setting in DEFAULT_METADATA:
if self[setting] is None:
self[setting] = global_settings[f'default_{setting}']
# Because this class inherints from MutableMapping, all we have to do is
# implement these five methods and we get a full dict-like interface.
@ -96,6 +102,9 @@ class InkStitchMetadata(MutableMapping):
return i + 1
def __json__(self):
return dict(self)
class InkstitchExtension(inkex.Effect):
"""Base class for Inkstitch extensions. Not intended for direct use."""

Wyświetl plik

@ -4,12 +4,15 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from .base import InkstitchExtension
from ..api import APIServer
from ..gui import open_url
class EmbroiderSettings(InkstitchExtension):
class Preferences(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",
@ -22,6 +25,13 @@ class EmbroiderSettings(InkstitchExtension):
help="minimum stitch length (mm)")
def effect(self):
self.metadata = self.get_inkstitch_metadata()
self.metadata['collapse_len_mm'] = self.options.collapse_length_mm
self.metadata['min_stitch_len_mm'] = self.options.min_stitch_len_mm
api_server = APIServer(self)
port = api_server.start_server()
electron = open_url("/preferences?port=%d" % port)
electron.wait()
api_server.stop()
api_server.join()
# self.metadata = self.get_inkstitch_metadata()
# self.metadata['collapse_len_mm'] = self.options.collapse_length_mm
# self.metadata['min_stitch_len_mm'] = self.options.min_stitch_len_mm

Wyświetl plik

@ -78,6 +78,16 @@ def get_marker_elements(node, marker, get_fills=True, get_strokes=True, get_sati
return {'fill': fills, 'stroke': strokes, 'satin': satins}
def get_marker_elements_cache_key_data(node, marker):
marker_elements = get_marker_elements(node, marker, True, True, True)
marker_elements['fill'] = [shape.wkt for shape in marker_elements['fill']]
marker_elements['stroke'] = [shape.wkt for shape in marker_elements['stroke']]
marker_elements['satin'] = [satin.csp for satin in marker_elements['satin']]
return marker_elements
def has_marker(node, marker=list()):
if not marker:
marker = MARKER

Wyświetl plik

@ -10,50 +10,59 @@ from .stitch_plan import Stitch
from .utils import Point
def apply_patterns(patches, node):
def get_patterns_cache_key_data(node):
patterns = get_marker_elements(node, "pattern")
_apply_fill_patterns(patterns['fill'], patches)
_apply_stroke_patterns(patterns['stroke'], patches)
data = []
data.extend([fill.wkt for fill in patterns['fill']])
data.extend([stroke.wkt for stroke in patterns['stroke']])
return data
def _apply_stroke_patterns(patterns, patches):
def apply_patterns(stitch_groups, node):
patterns = get_marker_elements(node, "pattern")
_apply_fill_patterns(patterns['fill'], stitch_groups)
_apply_stroke_patterns(patterns['stroke'], stitch_groups)
def _apply_stroke_patterns(patterns, stitch_groups):
for pattern in patterns:
for patch in patches:
patch_points = []
for i, stitch in enumerate(patch.stitches):
patch_points.append(stitch)
if i == len(patch.stitches) - 1:
for stitch_group in stitch_groups:
stitch_group_points = []
for i, stitch in enumerate(stitch_group.stitches):
stitch_group_points.append(stitch)
if i == len(stitch_group.stitches) - 1:
continue
intersection_points = _get_pattern_points(stitch, patch.stitches[i+1], pattern)
intersection_points = _get_pattern_points(stitch, stitch_group.stitches[i + 1], pattern)
for point in intersection_points:
patch_points.append(Stitch(point, tags=('pattern_point',)))
patch.stitches = patch_points
stitch_group_points.append(Stitch(point, tags=('pattern_point',)))
stitch_group.stitches = stitch_group_points
def _apply_fill_patterns(patterns, patches):
def _apply_fill_patterns(patterns, stitch_groups):
for pattern in patterns:
for patch in patches:
patch_points = []
for i, stitch in enumerate(patch.stitches):
for stitch_group in stitch_groups:
stitch_group_points = []
for i, stitch in enumerate(stitch_group.stitches):
if not shgeo.Point(stitch).within(pattern):
# keep points outside the fill pattern
patch_points.append(stitch)
elif i - 1 < 0 or i >= len(patch.stitches) - 1:
stitch_group_points.append(stitch)
elif i - 1 < 0 or i >= len(stitch_group.stitches) - 1:
# keep start and end points
patch_points.append(stitch)
stitch_group_points.append(stitch)
elif stitch.has_tag('fill_row_start') or stitch.has_tag('fill_row_end'):
# keep points if they are the start or end of a fill stitch row
patch_points.append(stitch)
stitch_group_points.append(stitch)
elif stitch.has_tag('auto_fill') and not stitch.has_tag('auto_fill_top'):
# keep auto-fill underlay
patch_points.append(stitch)
stitch_group_points.append(stitch)
elif stitch.has_tag('auto_fill_travel'):
# keep travel stitches (underpath or travel around the border)
patch_points.append(stitch)
stitch_group_points.append(stitch)
elif stitch.has_tag('satin_column') and not stitch.has_tag('satin_split_stitch'):
# keep satin column stitches unless they are split stitches
patch_points.append(stitch)
patch.stitches = patch_points
stitch_group_points.append(stitch)
stitch_group.stitches = stitch_group_points
def _get_pattern_points(first, second, pattern):

Wyświetl plik

@ -12,6 +12,10 @@ class Stitch(Point):
def __init__(self, x, y=None, color=None, jump=False, stop=False, trim=False, color_change=False,
tie_modus=0, force_lock_stitches=False, no_ties=False, tags=None):
# DANGER: if you add new attributes, you MUST also set their default
# values in __new__() below. Otherwise, cached stitch plans can be
# loaded and create objects without those properties defined, because
# unpickling does not call __init__()!
base_stitch = None
if isinstance(x, Stitch):
@ -42,17 +46,26 @@ class Stitch(Point):
if base_stitch is not None:
self.add_tags(base_stitch.tags)
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls)
# Set default values for any new attributes here (see note in __init__() above)
# instance.foo = None
return instance
def __repr__(self):
return "Stitch(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.x,
self.y,
self.color,
"JUMP" if self.jump else " ",
"TRIM" if self.trim else " ",
"STOP" if self.stop else " ",
"TIE MODUS" if self.tie_modus else " ",
"FORCE LOCK STITCHES" if self.force_lock_stitches else " ",
"NO TIES" if self.no_ties else " ",
"COLOR CHANGE" if self.color_change else " ")
return "Stitch(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.x,
self.y,
self.color,
self.tags,
"JUMP" if self.jump else " ",
"TRIM" if self.trim else " ",
"STOP" if self.stop else " ",
self.tie_modus,
"FORCE LOCK STITCHES" if self.force_lock_stitches else " ",
"NO TIES" if self.no_ties else " ",
"COLOR CHANGE" if self.color_change else " ")
def _set(self, attribute, value, base_stitch):
# Set an attribute. If the caller passed a Stitch object, use its value, unless
@ -92,3 +105,12 @@ class Stitch(Point):
attributes = dict(vars(self))
attributes['tags'] = list(attributes['tags'])
return attributes
def __getstate__(self):
# This is used by pickle. We want to sort the tag list so that the
# pickled representation is stable, since it's used to generate cache
# keys.
state = self.__json__()
state['tags'].sort()
return state

Wyświetl plik

@ -19,6 +19,11 @@ class StitchGroup:
def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False,
tie_modus=0, force_lock_stitches=False, stitch_as_is=False, tags=None):
# DANGER: if you add new attributes, you MUST also set their default
# values in __new__() below. Otherwise, cached stitch plans can be
# loaded and create objects without those properties defined, because
# unpickling does not call __init__()!
self.color = color
self.trim_after = trim_after
self.stop_after = stop_after
@ -33,6 +38,14 @@ class StitchGroup:
if tags:
self.add_tags(tags)
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls)
# Set default values for any new attributes here (see note in __init__() above)
# instance.foo = None
return instance
def __add__(self, other):
if isinstance(other, StitchGroup):
return StitchGroup(self.color, self.stitches + other.stitches)

Wyświetl plik

@ -2,14 +2,73 @@
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import os
import atexit
import hashlib
import pickle
import appdirs
import diskcache
from lib.utils.settings import global_settings
try:
from functools import lru_cache
except ImportError:
from backports.functools_lru_cache import lru_cache
# simplify use of lru_cache decorator
def cache(*args, **kwargs):
return lru_cache(maxsize=None)(*args, **kwargs)
__stitch_plan_cache = None
def get_stitch_plan_cache():
global __stitch_plan_cache
if __stitch_plan_cache is None:
cache_dir = os.path.join(appdirs.user_config_dir('inkstitch'), 'cache', 'stitch_plan')
size_limit = global_settings['cache_size'] * 1024 * 1024
__stitch_plan_cache = diskcache.Cache(cache_dir, size=size_limit)
__stitch_plan_cache.size_limit = size_limit
atexit.register(__stitch_plan_cache.close)
return __stitch_plan_cache
class CacheKeyGenerator(object):
"""Generate cache keys given arbitrary data.
Given arbitrary data, generate short cache key that is extremely likely
to be unique.
Use example:
>>> generator = CacheKeyGenerator()
>>> generator.update(b'12345')
>>> generator.update([1, 2, 3, {4, 5, 6}])
>>> generator.get_cache_key()
"""
def __init__(self):
# SHA1 is chosen for speed. We don't need cryptography-grade hashing
# for this use case.
self._hasher = hashlib.sha1()
def update(self, data):
"""Provide data to be hashed into a cache key.
Arguments:
data -- a bytes object or any object that can be pickled
"""
if not isinstance(data, bytes):
data = pickle.dumps(data)
self._hasher.update(data)
def get_cache_key(self):
return self._hasher.hexdigest()

Wyświetl plik

@ -7,6 +7,8 @@ import sys
import os
from os.path import dirname, realpath
import appdirs
def get_bundled_dir(name):
if getattr(sys, 'frozen', None) is not None:
@ -26,3 +28,12 @@ def get_resource_dir(name):
return realpath(os.path.join(sys._MEIPASS, name))
else:
return realpath(os.path.join(dirname(realpath(__file__)), '..', '..', name))
def get_user_dir(name=None):
path = appdirs.user_config_dir("inkstitch")
if name is not None:
path = os.path.join(path, name)
return path

Wyświetl plik

@ -0,0 +1,58 @@
from collections.abc import MutableMapping
import json
import os
from .paths import get_user_dir
# These settings are the defaults for SVG metadata settings of the same name in
# lib.extensions.base.InkstitchMetadata
DEFAULT_METADATA = {
"min_stitch_len_mm": 0,
"collapse_len_mm": 3,
}
DEFAULT_SETTINGS = {
"cache_size": 100
}
class GlobalSettings(MutableMapping):
def __init__(self):
super().__init__()
self.__settings_file = os.path.join(get_user_dir(), "settings.json")
self.__settings = {}
for name, value in DEFAULT_METADATA.items():
self.__settings[f"default_{name}"] = value
self.__settings.update(DEFAULT_SETTINGS)
try:
with open(self.__settings_file, 'r') as settings_file:
self.__settings.update(json.load(settings_file))
except (OSError, json.JSONDecodeError, ValueError):
pass
def __setitem__(self, item, value):
self.__settings[item] = value
with open(self.__settings_file, 'w') as settings_file:
json.dump(self.__settings, settings_file)
def __getitem__(self, item):
return self.__settings[item]
def __delitem__(self, item):
del self.__settings[item]
def __iter__(self):
return iter(self.__settings)
def __len__(self):
return len(self.__settings)
def __json__(self):
return self.__settings
global_settings = GlobalSettings()

Wyświetl plik

@ -26,6 +26,7 @@ flask
fonttools
trimesh>=3.15.2
scipy
diskcache
pywinutils ; sys_platform == 'win32'
pywin32 ; sys_platform == 'win32'

Wyświetl plik

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Preferences</name>
<id>org.inkstitch.embroider_settings</id>
<param name="extension" type="string" gui-hidden="true">embroider_settings</param>
<effect needs-live-preview="false">
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch" translatable="no" />
</effects-menu>
</effect>
<param name="output_settings" type="description" appearance="header">
Output Settings
</param>
<param name="collapse_len_mm" type="float" precision="1" min="0" max="10"
gui-text="Collapse length (mm)"
gui-description="Jump stitches smaller than this will be treated as normal stitches.">3</param>
<param name="min_stitch_len_mm" type="float" precision="1" min="0" max="10"
gui-text="Minimal stitch length (mm)"
gui-description="Drop stitches smaller than this value.">0.1</param>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>

Wyświetl plik

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Preferences</name>
<id>org.inkstitch.preferences</id>
<param name="extension" type="string" gui-hidden="true">preferences</param>
<effect implements-custom-gui="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch" translatable="no"/>
</effects-menu>
</effect>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>