kopia lustrzana https://github.com/inkstitch/inkstitch
Letters to font extension (#1312)
rodzic
0224794a08
commit
5a1ad7e4e8
|
@ -24,6 +24,7 @@ from .lettering import Lettering
|
||||||
from .lettering_custom_font_dir import LetteringCustomFontDir
|
from .lettering_custom_font_dir import LetteringCustomFontDir
|
||||||
from .lettering_generate_json import LetteringGenerateJson
|
from .lettering_generate_json import LetteringGenerateJson
|
||||||
from .lettering_remove_kerning import LetteringRemoveKerning
|
from .lettering_remove_kerning import LetteringRemoveKerning
|
||||||
|
from .letters_to_font import LettersToFont
|
||||||
from .object_commands import ObjectCommands
|
from .object_commands import ObjectCommands
|
||||||
from .output import Output
|
from .output import Output
|
||||||
from .params import Params
|
from .params import Params
|
||||||
|
@ -55,6 +56,7 @@ __all__ = extensions = [StitchPlanPreview,
|
||||||
LetteringGenerateJson,
|
LetteringGenerateJson,
|
||||||
LetteringRemoveKerning,
|
LetteringRemoveKerning,
|
||||||
LetteringCustomFontDir,
|
LetteringCustomFontDir,
|
||||||
|
LettersToFont,
|
||||||
Troubleshoot,
|
Troubleshoot,
|
||||||
RemoveEmbroiderySettings,
|
RemoveEmbroiderySettings,
|
||||||
Cleanup,
|
Cleanup,
|
||||||
|
|
|
@ -3,70 +3,13 @@
|
||||||
# Copyright (c) 2010 Authors
|
# Copyright (c) 2010 Authors
|
||||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import inkex
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
import pyembroidery
|
from ..stitch_plan import generate_stitch_plan
|
||||||
|
|
||||||
from ..i18n import _
|
|
||||||
from ..stitch_plan import StitchPlan
|
|
||||||
from ..svg import PIXELS_PER_MM, render_stitch_plan
|
|
||||||
from ..svg.tags import INKSCAPE_LABEL
|
|
||||||
|
|
||||||
|
|
||||||
class Input(object):
|
class Input(object):
|
||||||
def run(self, args):
|
def run(self, args):
|
||||||
embroidery_file = args[0]
|
embroidery_file = args[0]
|
||||||
self.validate_file_path(embroidery_file)
|
stitch_plan = generate_stitch_plan(embroidery_file)
|
||||||
|
print(etree.tostring(stitch_plan).decode('utf-8'))
|
||||||
pattern = pyembroidery.read(embroidery_file)
|
|
||||||
stitch_plan = StitchPlan()
|
|
||||||
color_block = None
|
|
||||||
|
|
||||||
for raw_stitches, thread in pattern.get_as_colorblocks():
|
|
||||||
color_block = stitch_plan.new_color_block(thread)
|
|
||||||
for x, y, command in raw_stitches:
|
|
||||||
if command == pyembroidery.STITCH:
|
|
||||||
color_block.add_stitch(x * PIXELS_PER_MM / 10.0, y * PIXELS_PER_MM / 10.0)
|
|
||||||
if len(color_block) > 0:
|
|
||||||
if command == pyembroidery.TRIM:
|
|
||||||
color_block.add_stitch(trim=True)
|
|
||||||
elif command == pyembroidery.STOP:
|
|
||||||
color_block.add_stitch(stop=True)
|
|
||||||
color_block = stitch_plan.new_color_block(thread)
|
|
||||||
|
|
||||||
stitch_plan.delete_empty_color_blocks()
|
|
||||||
|
|
||||||
if stitch_plan.last_color_block:
|
|
||||||
if stitch_plan.last_color_block.last_stitch:
|
|
||||||
if stitch_plan.last_color_block.last_stitch.stop:
|
|
||||||
# ending with a STOP command is redundant, so remove it
|
|
||||||
del stitch_plan.last_color_block[-1]
|
|
||||||
|
|
||||||
extents = stitch_plan.extents
|
|
||||||
svg = inkex.SvgDocumentElement("svg", nsmap=inkex.NSS, attrib={
|
|
||||||
"width": str(extents[0] * 2),
|
|
||||||
"height": str(extents[1] * 2),
|
|
||||||
"viewBox": "0 0 %s %s" % (extents[0] * 2, extents[1] * 2),
|
|
||||||
})
|
|
||||||
render_stitch_plan(svg, stitch_plan)
|
|
||||||
|
|
||||||
# rename the Stitch Plan layer so that it doesn't get overwritten by Embroider
|
|
||||||
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
|
|
||||||
layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file))
|
|
||||||
layer.attrib.pop('id')
|
|
||||||
|
|
||||||
# Shift the design so that its origin is at the center of the canvas
|
|
||||||
# Note: this is NOT the same as centering the design in the canvas!
|
|
||||||
layer.set('transform', 'translate(%s,%s)' % (extents[0], extents[1]))
|
|
||||||
|
|
||||||
print(etree.tostring(svg).decode('utf-8'))
|
|
||||||
|
|
||||||
def validate_file_path(self, path):
|
|
||||||
# Check if the file exists
|
|
||||||
if not os.path.isfile(path):
|
|
||||||
inkex.errormsg(_('File does not exist and cannot be opened. Please correct the file path and try again.\r%s') % path)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ class LetteringFrame(wx.Frame):
|
||||||
image.Rescale(300, 20, quality=wx.IMAGE_QUALITY_HIGH)
|
image.Rescale(300, 20, quality=wx.IMAGE_QUALITY_HIGH)
|
||||||
self.font_chooser.Append(font.marked_custom_font_name, wx.Bitmap(image))
|
self.font_chooser.Append(font.marked_custom_font_name, wx.Bitmap(image))
|
||||||
else:
|
else:
|
||||||
self.font_chooser.Append(font.name)
|
self.font_chooser.Append(font.marked_custom_font_name)
|
||||||
|
|
||||||
def get_font_descriptions(self):
|
def get_font_descriptions(self):
|
||||||
return {font.name: font.description for font in self.fonts.values()}
|
return {font.name: font.description for font in self.fonts.values()}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Authors: see git history
|
||||||
|
#
|
||||||
|
# Copyright (c) 2021 Authors
|
||||||
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from inkex import errormsg
|
||||||
|
|
||||||
|
from ..commands import ensure_symbol
|
||||||
|
from ..i18n import _
|
||||||
|
from ..stitch_plan import generate_stitch_plan
|
||||||
|
from ..svg import get_correction_transform
|
||||||
|
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SVG_PATH_TAG
|
||||||
|
from .base import InkstitchExtension
|
||||||
|
|
||||||
|
|
||||||
|
class LettersToFont(InkstitchExtension):
|
||||||
|
'''
|
||||||
|
This extension will create a json file to store a custom directory path for additional user fonts
|
||||||
|
'''
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
InkstitchExtension.__init__(self, *args, **kwargs)
|
||||||
|
self.arg_parser.add_argument("-d", "--font-dir", type=str, default="", dest="font_dir")
|
||||||
|
self.arg_parser.add_argument("-f", "--file-format", type=str, default="", dest="file_format")
|
||||||
|
self.arg_parser.add_argument("-c", "--import-commands", type=inkex.Boolean, default=False, dest="import_commands")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
font_dir = self.options.font_dir
|
||||||
|
file_format = self.options.file_format
|
||||||
|
|
||||||
|
if not os.path.isdir(font_dir):
|
||||||
|
errormsg(_("Font directory not found. Please specify an existing directory."))
|
||||||
|
|
||||||
|
glyphs = list(Path(font_dir).rglob(file_format))
|
||||||
|
if not glyphs:
|
||||||
|
glyphs = list(Path(font_dir).rglob(file_format.lower()))
|
||||||
|
|
||||||
|
document = self.document.getroot()
|
||||||
|
for glyph in glyphs:
|
||||||
|
letter = self.get_glyph_element(glyph)
|
||||||
|
label = "GlyphLayer-%s" % letter.get(INKSCAPE_LABEL, ' ').split('.')[0][-1]
|
||||||
|
group = inkex.Group(attrib={
|
||||||
|
INKSCAPE_LABEL: label,
|
||||||
|
INKSCAPE_GROUPMODE: "layer",
|
||||||
|
"transform": get_correction_transform(document, child=True)
|
||||||
|
})
|
||||||
|
|
||||||
|
# remove color block groups if we import without commands
|
||||||
|
# there will only be one object per color block anyway
|
||||||
|
if not self.options.import_commands:
|
||||||
|
for element in letter.iter(SVG_PATH_TAG):
|
||||||
|
group.insert(0, element)
|
||||||
|
else:
|
||||||
|
group.insert(0, letter)
|
||||||
|
|
||||||
|
document.insert(0, group)
|
||||||
|
group.set('style', 'display:none')
|
||||||
|
|
||||||
|
# users may be confused if they get an empty document
|
||||||
|
# make last letter visible again
|
||||||
|
group.set('style', None)
|
||||||
|
|
||||||
|
# In most cases trims are inserted with the imported letters.
|
||||||
|
# Let's make sure the trim symbol exists in the defs section
|
||||||
|
ensure_symbol(document, 'trim')
|
||||||
|
|
||||||
|
self.insert_baseline(document)
|
||||||
|
|
||||||
|
def get_glyph_element(self, glyph):
|
||||||
|
stitch_plan = generate_stitch_plan(str(glyph), self.options.import_commands)
|
||||||
|
# we received a stitch plan wrapped in an svg document, we only need the stitch_plan group
|
||||||
|
# this group carries the name of the file, so we can search for it.
|
||||||
|
stitch_plan = stitch_plan.xpath('.//*[@inkscape:label="%s"]' % os.path.basename(glyph), namespaces=inkex.NSS)[0]
|
||||||
|
stitch_plan.attrib.pop(INKSCAPE_GROUPMODE)
|
||||||
|
return stitch_plan
|
||||||
|
|
||||||
|
def insert_baseline(self, document):
|
||||||
|
document.namedview.new_guide(position=0.0, name="baseline")
|
|
@ -6,15 +6,18 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from random import randint
|
||||||
|
|
||||||
import inkex
|
import inkex
|
||||||
|
|
||||||
|
from ..commands import ensure_symbol
|
||||||
from ..elements import nodes_to_elements
|
from ..elements import nodes_to_elements
|
||||||
from ..exceptions import InkstitchException
|
from ..exceptions import InkstitchException
|
||||||
from ..extensions.lettering_custom_font_dir import get_custom_font_dir
|
from ..extensions.lettering_custom_font_dir import get_custom_font_dir
|
||||||
from ..i18n import _, get_languages
|
from ..i18n import _, get_languages
|
||||||
from ..stitches.auto_satin import auto_satin
|
from ..stitches.auto_satin import auto_satin
|
||||||
from ..svg.tags import INKSCAPE_LABEL, SVG_PATH_TAG
|
from ..svg.tags import (CONNECTION_END, CONNECTION_START, INKSCAPE_LABEL,
|
||||||
|
SVG_PATH_TAG, SVG_USE_TAG, XLINK_HREF)
|
||||||
from ..utils import Point
|
from ..utils import Point
|
||||||
from .font_variant import FontVariant
|
from .font_variant import FontVariant
|
||||||
|
|
||||||
|
@ -220,6 +223,8 @@ class Font(object):
|
||||||
|
|
||||||
element.set('style', '%s%s%s' % (style.to_str(), stroke_width, dash_array))
|
element.set('style', '%s%s%s' % (style.to_str(), stroke_width, dash_array))
|
||||||
|
|
||||||
|
self._ensure_command_symbols(destination_group)
|
||||||
|
|
||||||
return destination_group
|
return destination_group
|
||||||
|
|
||||||
def get_variant(self, variant):
|
def get_variant(self, variant):
|
||||||
|
@ -303,8 +308,39 @@ class Font(object):
|
||||||
|
|
||||||
position.x += self.horiz_adv_x.get(character, horiz_adv_x_default) - glyph.min_x
|
position.x += self.horiz_adv_x.get(character, horiz_adv_x_default) - glyph.min_x
|
||||||
|
|
||||||
|
self._update_commands(node, glyph)
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
def _update_commands(self, node, glyph):
|
||||||
|
for element, connectors in glyph.commands.items():
|
||||||
|
# update element
|
||||||
|
el = node.find(".//*[@id='%s']" % element)
|
||||||
|
# we cannot get a unique id from the document at this point
|
||||||
|
# so let's create a random id which will most probably work as well
|
||||||
|
new_element_id = "%s_%s" % (element, randint(0, 9999))
|
||||||
|
el.set_id(new_element_id)
|
||||||
|
for connector, symbol in connectors:
|
||||||
|
# update symbol
|
||||||
|
new_symbol_id = "%s_%s" % (symbol, randint(0, 9999))
|
||||||
|
s = node.find(".//*[@id='%s']" % symbol)
|
||||||
|
s.set_id(new_symbol_id)
|
||||||
|
# update connector
|
||||||
|
c = node.find(".//*[@id='%s']" % connector)
|
||||||
|
c.set(CONNECTION_END, "#%s" % new_element_id)
|
||||||
|
c.set(CONNECTION_START, "#%s" % new_symbol_id)
|
||||||
|
|
||||||
|
def _ensure_command_symbols(self, group):
|
||||||
|
# collect commands
|
||||||
|
commands = set()
|
||||||
|
for element in group.iterdescendants(SVG_USE_TAG):
|
||||||
|
xlink = element.get(XLINK_HREF, ' ')
|
||||||
|
if xlink.startswith('#inkstitch_'):
|
||||||
|
commands.add(xlink[11:])
|
||||||
|
# make sure all necessary command symbols are in the document
|
||||||
|
for command in commands:
|
||||||
|
ensure_symbol(group.getroottree().getroot(), command)
|
||||||
|
|
||||||
def _apply_auto_satin(self, group, trim):
|
def _apply_auto_satin(self, group, trim):
|
||||||
"""Apply Auto-Satin to an SVG XML node tree with an svg:g at its root.
|
"""Apply Auto-Satin to an SVG XML node tree with an svg:g at its root.
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ import os
|
||||||
|
|
||||||
import inkex
|
import inkex
|
||||||
|
|
||||||
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
|
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SVG_GROUP_TAG,
|
||||||
|
SVG_PATH_TAG, SVG_USE_TAG)
|
||||||
from .glyph import Glyph
|
from .glyph import Glyph
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,7 +61,8 @@ class FontVariant(object):
|
||||||
|
|
||||||
def _load_glyphs(self):
|
def _load_glyphs(self):
|
||||||
svg_path = os.path.join(self.path, "%s.svg" % self.variant)
|
svg_path = os.path.join(self.path, "%s.svg" % self.variant)
|
||||||
svg = inkex.load_svg(svg_path)
|
svg = inkex.load_svg(svg_path).getroot()
|
||||||
|
svg = self._apply_transforms(svg)
|
||||||
|
|
||||||
glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS)
|
glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS)
|
||||||
for layer in glyph_layers:
|
for layer in glyph_layers:
|
||||||
|
@ -79,6 +81,29 @@ class FontVariant(object):
|
||||||
group.style.pop('display', None)
|
group.style.pop('display', None)
|
||||||
group.attrib.pop('display', None)
|
group.attrib.pop('display', None)
|
||||||
|
|
||||||
|
def _apply_transforms(self, svg):
|
||||||
|
# apply transforms to paths and use tags
|
||||||
|
for element in svg.iterdescendants((SVG_PATH_TAG, SVG_USE_TAG)):
|
||||||
|
transform = element.composed_transform()
|
||||||
|
if element.tag == SVG_PATH_TAG:
|
||||||
|
path = element.path.transform(transform)
|
||||||
|
element.set_path(path)
|
||||||
|
element.attrib.pop("transform", None)
|
||||||
|
|
||||||
|
if element.tag == SVG_USE_TAG:
|
||||||
|
oldx = element.get('x', 0)
|
||||||
|
oldy = element.get('y', 0)
|
||||||
|
newx, newy = transform.apply_to_point((oldx, oldy))
|
||||||
|
element.set('x', newx)
|
||||||
|
element.set('y', newy)
|
||||||
|
element.attrib.pop("transform", None)
|
||||||
|
|
||||||
|
# remove transforms after they have been applied
|
||||||
|
for group in svg.iterdescendants(SVG_GROUP_TAG):
|
||||||
|
group.attrib.pop('transform', None)
|
||||||
|
|
||||||
|
return svg
|
||||||
|
|
||||||
def __getitem__(self, character):
|
def __getitem__(self, character):
|
||||||
if character in self.glyphs:
|
if character in self.glyphs:
|
||||||
return self.glyphs[character]
|
return self.glyphs[character]
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
from inkex import paths, transforms
|
from inkex import paths, transforms, units
|
||||||
|
|
||||||
from ..svg import get_guides
|
from ..svg import get_correction_transform, get_guides
|
||||||
from ..svg.tags import SVG_GROUP_TAG, SVG_PATH_TAG
|
from ..svg.tags import (CONNECTION_END, SVG_GROUP_TAG, SVG_PATH_TAG,
|
||||||
|
SVG_USE_TAG, XLINK_HREF)
|
||||||
|
|
||||||
|
|
||||||
class Glyph(object):
|
class Glyph(object):
|
||||||
|
@ -38,6 +39,7 @@ class Glyph(object):
|
||||||
self.node = self._process_group(group)
|
self.node = self._process_group(group)
|
||||||
self._process_bbox()
|
self._process_bbox()
|
||||||
self._move_to_origin()
|
self._move_to_origin()
|
||||||
|
self._process_commands()
|
||||||
|
|
||||||
def _process_group(self, group):
|
def _process_group(self, group):
|
||||||
new_group = copy(group)
|
new_group = copy(group)
|
||||||
|
@ -50,13 +52,21 @@ class Glyph(object):
|
||||||
new_group.append(self._process_group(node))
|
new_group.append(self._process_group(node))
|
||||||
else:
|
else:
|
||||||
node_copy = copy(node)
|
node_copy = copy(node)
|
||||||
|
transform = -transforms.Transform(get_correction_transform(node, True))
|
||||||
|
|
||||||
if "d" in node.attrib:
|
if "d" in node.attrib:
|
||||||
node_copy.path = node.path.transform(node.composed_transform()).to_absolute()
|
node_copy.path = node.path.transform(transform).to_absolute()
|
||||||
|
|
||||||
# Delete transforms from paths and groups, since we applied
|
if not node.tag == SVG_USE_TAG:
|
||||||
# them to the paths already.
|
# Delete transforms from paths and groups, since we applied
|
||||||
node_copy.attrib.pop('transform', None)
|
# them to the paths already.
|
||||||
|
node_copy.attrib.pop('transform', None)
|
||||||
|
else:
|
||||||
|
oldx = node.get('x', 0)
|
||||||
|
oldy = node.get('y', 0)
|
||||||
|
x, y = transform.apply_to_point((oldx, oldy))
|
||||||
|
node_copy.set('x', x)
|
||||||
|
node_copy.set('y', y)
|
||||||
|
|
||||||
new_group.append(node_copy)
|
new_group.append(node_copy)
|
||||||
|
|
||||||
|
@ -72,11 +82,30 @@ class Glyph(object):
|
||||||
self.baseline = 0
|
self.baseline = 0
|
||||||
|
|
||||||
def _process_bbox(self):
|
def _process_bbox(self):
|
||||||
bbox = [paths.Path(node.get("d")).bounding_box() for node in self.node.iterdescendants(SVG_PATH_TAG)]
|
bbox = [paths.Path(node.get("d")).bounding_box() for node in self.node.iterdescendants(SVG_PATH_TAG) if not node.get(CONNECTION_END, None)]
|
||||||
left, right = min([box.left for box in bbox]), max([box.right for box in bbox])
|
left, right = min([box.left for box in bbox]), max([box.right for box in bbox])
|
||||||
self.width = right - left
|
self.width = right - left
|
||||||
self.min_x = left
|
self.min_x = left
|
||||||
|
|
||||||
|
def _process_commands(self):
|
||||||
|
# Save object ids with commmands in a dictionary: {object_id: [connector_id, symbol_id]}
|
||||||
|
self.commands = {}
|
||||||
|
|
||||||
|
for node in self.node.iter(SVG_USE_TAG):
|
||||||
|
xlink = node.get(XLINK_HREF, ' ')
|
||||||
|
if not xlink.startswith('#inkstitch_'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
connector = self.node.xpath(".//*[@inkscape:connection-start='#%s']" % node.get('id', ' '))[0]
|
||||||
|
command_object = connector.get(CONNECTION_END)[1:]
|
||||||
|
try:
|
||||||
|
self.commands[command_object].append([connector.get_id(), node.get_id()])
|
||||||
|
except KeyError:
|
||||||
|
self.commands[command_object] = [[connector.get_id(), node.get_id()]]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
def _move_to_origin(self):
|
def _move_to_origin(self):
|
||||||
translate_x = -self.min_x
|
translate_x = -self.min_x
|
||||||
translate_y = -self.baseline
|
translate_y = -self.baseline
|
||||||
|
@ -87,3 +116,11 @@ class Glyph(object):
|
||||||
path = path.transform(transform)
|
path = path.transform(transform)
|
||||||
node.set('d', str(path))
|
node.set('d', str(path))
|
||||||
node.attrib.pop('transform', None)
|
node.attrib.pop('transform', None)
|
||||||
|
|
||||||
|
# Move commands as well
|
||||||
|
for node in self.node.iter(SVG_USE_TAG):
|
||||||
|
oldx = units.convert_unit(node.get("x", 0), 'px', node.unit)
|
||||||
|
oldy = units.convert_unit(node.get("y", 0), 'px', node.unit)
|
||||||
|
x, y = transform.apply_to_point((oldx, oldy))
|
||||||
|
node.set('x', x)
|
||||||
|
node.set('y', y)
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
# Copyright (c) 2010 Authors
|
# Copyright (c) 2010 Authors
|
||||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
from .stitch_plan import stitch_groups_to_stitch_plan, StitchPlan
|
|
||||||
from .color_block import ColorBlock
|
from .color_block import ColorBlock
|
||||||
from .stitch_group import StitchGroup
|
from .generate_stitch_plan import generate_stitch_plan
|
||||||
from .stitch import Stitch
|
|
||||||
from .read_file import stitch_plan_from_file
|
from .read_file import stitch_plan_from_file
|
||||||
|
from .stitch import Stitch
|
||||||
|
from .stitch_group import StitchGroup
|
||||||
|
from .stitch_plan import StitchPlan, stitch_groups_to_stitch_plan
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
# Authors: see git history
|
||||||
|
#
|
||||||
|
# Copyright (c) 2010 Authors
|
||||||
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
import pyembroidery
|
||||||
|
|
||||||
|
from ..i18n import _
|
||||||
|
from ..svg import PIXELS_PER_MM, render_stitch_plan
|
||||||
|
from ..svg.tags import INKSCAPE_LABEL
|
||||||
|
from .stitch import Stitch
|
||||||
|
from .stitch_plan import StitchPlan
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stitch_plan(embroidery_file, import_commands=True): # noqa: C901
|
||||||
|
validate_file_path(embroidery_file)
|
||||||
|
pattern = pyembroidery.read(embroidery_file)
|
||||||
|
stitch_plan = StitchPlan()
|
||||||
|
color_block = None
|
||||||
|
|
||||||
|
for raw_stitches, thread in pattern.get_as_colorblocks():
|
||||||
|
color_block = stitch_plan.new_color_block(thread)
|
||||||
|
for x, y, command in raw_stitches:
|
||||||
|
if command == pyembroidery.STITCH:
|
||||||
|
color_block.add_stitch(Stitch(x * PIXELS_PER_MM / 10.0, y * PIXELS_PER_MM / 10.0))
|
||||||
|
if len(color_block) > 0:
|
||||||
|
if not import_commands and command in [pyembroidery.TRIM, pyembroidery.STOP]:
|
||||||
|
# Importing commands is not wanted:
|
||||||
|
# start a new color block without inserting the command
|
||||||
|
color_block = stitch_plan.new_color_block(thread)
|
||||||
|
elif command == pyembroidery.TRIM:
|
||||||
|
color_block.add_stitch(trim=True)
|
||||||
|
elif command == pyembroidery.STOP:
|
||||||
|
color_block.add_stitch(stop=True)
|
||||||
|
color_block = stitch_plan.new_color_block(thread)
|
||||||
|
|
||||||
|
stitch_plan.delete_empty_color_blocks()
|
||||||
|
|
||||||
|
if stitch_plan.last_color_block:
|
||||||
|
if stitch_plan.last_color_block.last_stitch:
|
||||||
|
if stitch_plan.last_color_block.last_stitch.stop:
|
||||||
|
# ending with a STOP command is redundant, so remove it
|
||||||
|
del stitch_plan.last_color_block[-1]
|
||||||
|
|
||||||
|
extents = stitch_plan.extents
|
||||||
|
svg = inkex.SvgDocumentElement("svg", nsmap=inkex.NSS, attrib={
|
||||||
|
"width": str(extents[0] * 2),
|
||||||
|
"height": str(extents[1] * 2),
|
||||||
|
"viewBox": "0 0 %s %s" % (extents[0] * 2, extents[1] * 2),
|
||||||
|
})
|
||||||
|
render_stitch_plan(svg, stitch_plan)
|
||||||
|
|
||||||
|
# rename the Stitch Plan layer so that it doesn't get overwritten by Embroider
|
||||||
|
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
|
||||||
|
layer.set(INKSCAPE_LABEL, os.path.basename(embroidery_file))
|
||||||
|
layer.attrib.pop('id')
|
||||||
|
|
||||||
|
# Shift the design so that its origin is at the center of the canvas
|
||||||
|
# Note: this is NOT the same as centering the design in the canvas!
|
||||||
|
layer.set('transform', 'translate(%s,%s)' % (extents[0], extents[1]))
|
||||||
|
|
||||||
|
return svg
|
||||||
|
|
||||||
|
|
||||||
|
def validate_file_path(path):
|
||||||
|
# Check if the file exists
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
inkex.errormsg(_('File does not exist and cannot be opened. Please correct the file path and try again.\r%s') % path)
|
||||||
|
sys.exit(1)
|
|
@ -3,11 +3,10 @@
|
||||||
# Copyright (c) 2010 Authors
|
# Copyright (c) 2010 Authors
|
||||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
from inkex import transforms
|
from inkex.units import convert_unit
|
||||||
|
|
||||||
from ..utils import Point, cache, string_to_floats
|
from ..utils import Point, cache, string_to_floats
|
||||||
from .tags import INKSCAPE_LABEL, SODIPODI_GUIDE, SODIPODI_NAMEDVIEW
|
from .tags import INKSCAPE_LABEL, SODIPODI_GUIDE, SODIPODI_NAMEDVIEW
|
||||||
from .units import get_doc_size, get_viewbox_transform
|
|
||||||
|
|
||||||
|
|
||||||
class InkscapeGuide(object):
|
class InkscapeGuide(object):
|
||||||
|
@ -20,16 +19,15 @@ class InkscapeGuide(object):
|
||||||
def _parse(self):
|
def _parse(self):
|
||||||
self.label = self.node.get(INKSCAPE_LABEL, "")
|
self.label = self.node.get(INKSCAPE_LABEL, "")
|
||||||
|
|
||||||
doc_size = list(get_doc_size(self.svg))
|
doc_size = self.svg.get_page_bbox()
|
||||||
|
|
||||||
# convert the size from viewbox-relative to real-world pixels
|
|
||||||
viewbox_transform = get_viewbox_transform(self.svg)
|
|
||||||
viewbox_transform = transforms.Transform(-transforms.Transform(viewbox_transform)).apply_to_point(doc_size)
|
|
||||||
|
|
||||||
self.position = Point(*string_to_floats(self.node.get('position')))
|
|
||||||
|
|
||||||
# inkscape's Y axis is reversed from SVG's, and the guide is in inkscape coordinates
|
# inkscape's Y axis is reversed from SVG's, and the guide is in inkscape coordinates
|
||||||
self.position.y = doc_size[1] - self.position.y
|
self.position = Point(*string_to_floats(self.node.get('position')))
|
||||||
|
self.position.y = doc_size.y.size - self.position.y
|
||||||
|
|
||||||
|
# convert units to px
|
||||||
|
unit = self.svg.unit
|
||||||
|
self.position.y = convert_unit(self.position.y, 'px', unit)
|
||||||
|
|
||||||
# This one baffles me. I think inkscape might have gotten the order of
|
# This one baffles me. I think inkscape might have gotten the order of
|
||||||
# their vector wrong?
|
# their vector wrong?
|
||||||
|
|
|
@ -28,6 +28,7 @@ INKSCAPE_GROUPMODE = inkex.addNS('groupmode', 'inkscape')
|
||||||
CONNECTION_START = inkex.addNS('connection-start', 'inkscape')
|
CONNECTION_START = inkex.addNS('connection-start', 'inkscape')
|
||||||
CONNECTION_END = inkex.addNS('connection-end', 'inkscape')
|
CONNECTION_END = inkex.addNS('connection-end', 'inkscape')
|
||||||
CONNECTOR_TYPE = inkex.addNS('connector-type', 'inkscape')
|
CONNECTOR_TYPE = inkex.addNS('connector-type', 'inkscape')
|
||||||
|
INKSCAPE_DOCUMENT_UNITS = inkex.addNS('document-units', 'inkscape')
|
||||||
|
|
||||||
XLINK_HREF = inkex.addNS('href', 'xlink')
|
XLINK_HREF = inkex.addNS('href', 'xlink')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Letters to font</name>
|
||||||
|
<id>org.inkstitch.letters_to_font</id>
|
||||||
|
<param name="extension" type="string" gui-hidden="true">letters_to_font</param>
|
||||||
|
<effect needs-live-preview="false">
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="Ink/Stitch">
|
||||||
|
<submenu name="Font Management" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<param name="header" type="description" appearance="header" indent="1" >
|
||||||
|
Includes all letters of a predigitized embroidery font (one file for each letter) into the document in order to make it available for the Ink/Stitch lettering system.
|
||||||
|
</param>
|
||||||
|
<param name="file-description" type="description" indent="1" >
|
||||||
|
Embroidery files need to have the name of the letter right before the file extension. E.g. A.dst or Example_Font_A.dst will be recognized as the letter A.
|
||||||
|
</param>
|
||||||
|
<separator />
|
||||||
|
<spacer />
|
||||||
|
<param name="file-format" type="optiongroup" appearance="combo" gui-text="File format" indent="1">
|
||||||
|
{% for format, description, mimetype, category in formats %}
|
||||||
|
<option value="*.{{ format | upper }}">{{ format | upper }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</param>
|
||||||
|
<param type="path" name="font-dir" gui-text="Font directory" indent="1" mode="folder" filetypes="svg"/>
|
||||||
|
<spacer />
|
||||||
|
<param type="boolean" name="import-commands" gui-text="Import commands" indent="1">false</param>
|
||||||
|
<spacer />
|
||||||
|
<separator />
|
||||||
|
<param name="file-description" type="description" indent="1" >
|
||||||
|
⚠ After running this function, drag the baseline into the desired position and place the letters accordingly.
|
||||||
|
Save your font in a separate folder. Then generate the json file (with "Autoroute Satin" unchecked).
|
||||||
|
</param>
|
||||||
|
<separator />
|
||||||
|
<script>
|
||||||
|
{{ command_tag | safe }}
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
Ładowanie…
Reference in New Issue