kopia lustrzana https://github.com/inkstitch/inkstitch
various fixes (#3372)
rodzic
870fff344f
commit
7ba51c0c96
lib
|
@ -598,7 +598,7 @@ class SatinColumn(EmbroideryElement):
|
|||
# This isn't used for satins at all, but other parts of the code
|
||||
# may need to know the general shape of a satin column.
|
||||
|
||||
return shgeo.MultiLineString(self.flattened_rails).convex_hull
|
||||
return shgeo.MultiLineString(self.flattened_rails)
|
||||
|
||||
@property
|
||||
@cache
|
||||
|
@ -1349,9 +1349,9 @@ class SatinColumn(EmbroideryElement):
|
|||
stitch_groups = []
|
||||
tags = ("satin_column", "satin_column_underlay", "satin_contour_underlay")
|
||||
first_linestring = shgeo.LineString(first_side)
|
||||
first_start, first_end = self._split_linestring_at_end_point(first_linestring)
|
||||
first_start, first_end = self._split_linestring_at_end_point(first_linestring, end_point)
|
||||
second_linestring = shgeo.LineString(second_side)
|
||||
second_end, second_start = self._split_linestring_at_end_point(second_linestring)
|
||||
second_end, second_start = self._split_linestring_at_end_point(second_linestring, end_point)
|
||||
stitch_groups.append(self._to_stitch_group(first_start, tags))
|
||||
stitch_groups.append(self._to_stitch_group(second_end, tags))
|
||||
stitch_groups.append(self._to_stitch_group(second_start, tags))
|
||||
|
@ -1378,7 +1378,7 @@ class SatinColumn(EmbroideryElement):
|
|||
if end_point:
|
||||
tags = ("satin_column", "satin_column_underlay", "satin_center_walk")
|
||||
stitches = shgeo.LineString(stitches)
|
||||
start, end = self._split_linestring_at_end_point(stitches)
|
||||
start, end = self._split_linestring_at_end_point(stitches, end_point)
|
||||
if self._center_walk_is_odd():
|
||||
end, start = start, end
|
||||
stitch_groups.append(self._to_stitch_group(start, tags))
|
||||
|
@ -1431,7 +1431,7 @@ class SatinColumn(EmbroideryElement):
|
|||
stitch_groups.append(self._generate_zigzag_stitch_group(points))
|
||||
continue
|
||||
zigzag_line = shgeo.LineString(points)
|
||||
start, end = self._split_linestring_at_end_point(zigzag_line)
|
||||
start, end = self._split_linestring_at_end_point(zigzag_line, end_point)
|
||||
start_groups.append(self._generate_zigzag_stitch_group([Stitch(*point) for point in start.coords]))
|
||||
end_groups.append(self._generate_zigzag_stitch_group([Stitch(*point) for point in end.coords]))
|
||||
if start_groups:
|
||||
|
@ -1813,7 +1813,7 @@ class SatinColumn(EmbroideryElement):
|
|||
def end_point(self, next_stitch):
|
||||
end_point = self._get_command_point('ending_point')
|
||||
if end_point is None and self.end_at_nearest_point and next_stitch is not None:
|
||||
end_point = nearest_points(next_stitch, self.center_line)[1]
|
||||
end_point = nearest_points(next_stitch, self.shape)[1]
|
||||
end_point = Point(*list(end_point.coords[0]))
|
||||
return end_point
|
||||
|
||||
|
@ -1872,6 +1872,7 @@ class SatinColumn(EmbroideryElement):
|
|||
stitch_groups = [self._connect_stitch_group_with_point(stitch_groups[0], start_point)] + stitch_groups
|
||||
if end_point:
|
||||
stitch_groups.append(self.do_end_path(end_point))
|
||||
pass
|
||||
|
||||
# assemble stitch groups
|
||||
stitch_group = StitchGroup(
|
||||
|
|
|
@ -486,7 +486,7 @@ class Stroke(EmbroideryElement):
|
|||
@property
|
||||
@cache
|
||||
def shape(self):
|
||||
return ensure_multi_line_string(self.as_multi_line_string().convex_hull)
|
||||
return self.as_multi_line_string().convex_hull
|
||||
|
||||
@cache
|
||||
def as_multi_line_string(self):
|
||||
|
|
|
@ -181,7 +181,7 @@ class JumpToStroke(InkstitchExtension):
|
|||
# add simple stroke to connect elements
|
||||
path.transform(Transform(get_correction_transform(node)), True)
|
||||
color = element.color
|
||||
style = f'stroke:{color};stroke-width:1px;stroke-dasharray:3, 1;fill:none;'
|
||||
style = f'stroke:{color};stroke-width:{self.svg.viewport_to_unit("1px")};stroke-dasharray:3, 1;fill:none;'
|
||||
|
||||
line = PathElement(d=str(path), style=style)
|
||||
line.set(INKSTITCH_ATTRIBS['running_stitch_length_mm'], self.options.running_stitch_length_mm)
|
||||
|
|
|
@ -1,348 +0,0 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2023 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from itertools import combinations_with_replacement
|
||||
from os import path
|
||||
|
||||
import wx
|
||||
import wx.adv
|
||||
from inkex import errormsg
|
||||
from wx.lib.mixins.listctrl import TextEditMixin
|
||||
|
||||
from ..elements import nodes_to_elements
|
||||
from ..i18n import _
|
||||
from ..lettering import get_font_list
|
||||
from ..stitch_plan import stitch_groups_to_stitch_plan
|
||||
from ..svg.tags import SVG_PATH_TAG
|
||||
from ..utils.threading import ExitThread, check_stop_flag
|
||||
from . import PreviewRenderer
|
||||
|
||||
|
||||
class LetteringKerningPanel(wx.Panel):
|
||||
|
||||
def __init__(self, parent, simulator, layer, metadata=None, background_color='white'):
|
||||
self.parent = parent
|
||||
self.simulator = simulator
|
||||
self.layer = layer
|
||||
self.metadata = metadata or dict()
|
||||
self.background_color = background_color
|
||||
|
||||
super().__init__(parent, wx.ID_ANY)
|
||||
|
||||
self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE)
|
||||
|
||||
self.fonts = None
|
||||
self.font = None
|
||||
self.default_variant = None
|
||||
self.glyphs = None
|
||||
self.kerning_pairs = None
|
||||
self.kerning_combinations = []
|
||||
|
||||
self.text_before = ''
|
||||
self.text_after = ''
|
||||
|
||||
# preview
|
||||
self.preview_renderer = PreviewRenderer(self.render_stitch_plan, self.on_stitch_plan_rendered)
|
||||
|
||||
notebook_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.notebook = wx.Notebook(self, wx.ID_ANY)
|
||||
notebook_sizer.Add(self.notebook, 1, wx.EXPAND, 0)
|
||||
|
||||
self.settings = wx.Panel(self.notebook, wx.ID_ANY)
|
||||
self.notebook.AddPage(self.settings, _("Settings"))
|
||||
|
||||
# settings
|
||||
settings_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
self.font_chooser = wx.adv.BitmapComboBox(self.settings, wx.ID_ANY, style=wx.CB_READONLY | wx.CB_SORT, size=(600, 40))
|
||||
self.font_chooser.Bind(wx.EVT_COMBOBOX, self.on_font_changed)
|
||||
|
||||
text_before_label = wx.StaticText(self.settings, label=_("Text before"))
|
||||
text_before = wx.TextCtrl(self.settings)
|
||||
text_before.Bind(wx.EVT_TEXT, self.on_text_before_changed)
|
||||
text_after_label = wx.StaticText(self.settings, label=_("Text after"))
|
||||
text_after = wx.TextCtrl(self.settings)
|
||||
text_after.Bind(wx.EVT_TEXT, self.on_text_after_changed)
|
||||
grid_text_sizer = wx.FlexGridSizer(2, 2, 10, 10)
|
||||
grid_text_sizer.AddGrowableCol(1)
|
||||
grid_text_sizer.AddMany([
|
||||
(text_before_label, 1, wx.ALL, 0),
|
||||
(text_before, 1, wx.EXPAND, 0),
|
||||
(text_after_label, 1, wx.ALL, 0),
|
||||
(text_after, 1, wx.EXPAND, 0)
|
||||
])
|
||||
|
||||
self.kerning_list = EditableListCtrl(self.settings, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
|
||||
self.kerning_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_kerning_list_select)
|
||||
self.kerning_list.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.on_kerning_update)
|
||||
|
||||
apply_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.cancel_button = wx.Button(self.settings, label=_("Cancel"))
|
||||
self.cancel_button.Bind(wx.EVT_BUTTON, self.cancel)
|
||||
self.apply_button = wx.Button(self.settings, label=_("Apply"))
|
||||
self.apply_button.Bind(wx.EVT_BUTTON, self.apply)
|
||||
apply_sizer.Add(self.cancel_button, 0, wx.RIGHT | wx.BOTTOM, 5)
|
||||
apply_sizer.Add(self.apply_button, 0, wx.RIGHT | wx.BOTTOM, 10)
|
||||
|
||||
settings_sizer.Add(self.font_chooser, 0, wx.ALL | wx.EXPAND, 10)
|
||||
settings_sizer.Add(grid_text_sizer, 0, wx.ALL | wx.EXPAND, 10)
|
||||
settings_sizer.Add(self.kerning_list, 2, wx.ALL | wx.EXPAND, 10)
|
||||
settings_sizer.Add(apply_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 10)
|
||||
|
||||
# help
|
||||
self.help = wx.Panel(self.notebook, wx.ID_ANY)
|
||||
self.notebook.AddPage(self.help, _("Help"))
|
||||
|
||||
help_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
help_text = wx.StaticText(
|
||||
self.help,
|
||||
wx.ID_ANY,
|
||||
_("Feature to verify or update kerning information for an existing font."),
|
||||
style=wx.ALIGN_LEFT
|
||||
)
|
||||
help_text.Wrap(500)
|
||||
help_sizer.Add(help_text, 0, wx.ALL, 20)
|
||||
|
||||
help_sizer.Add((20, 20), 0, 0, 0)
|
||||
|
||||
website_info = wx.StaticText(self.help, wx.ID_ANY, _("More information on our website:"))
|
||||
help_sizer.Add(website_info, 0, wx.ALL, 8)
|
||||
|
||||
self.website_link = wx.adv.HyperlinkCtrl(
|
||||
self.help,
|
||||
wx.ID_ANY,
|
||||
_("https://inkstitch.org/docs/font-tools/#kerning-tool"),
|
||||
_("https://inkstitch.org/docs/font-tools/#kerning-tool")
|
||||
)
|
||||
help_sizer.Add(self.website_link, 0, wx.ALL, 8)
|
||||
|
||||
self.help.SetSizer(help_sizer)
|
||||
self.settings.SetSizer(settings_sizer)
|
||||
self.SetSizer(notebook_sizer)
|
||||
|
||||
self.set_font_list()
|
||||
self.font_chooser.SetValue(list(self.fonts.values())[0].marked_custom_font_name)
|
||||
self.on_font_changed()
|
||||
|
||||
self.SetSizeHints(notebook_sizer.CalcMin())
|
||||
self.Layout()
|
||||
|
||||
def on_text_before_changed(self, event):
|
||||
self.text_before = event.GetEventObject().GetValue()
|
||||
self.update_preview()
|
||||
|
||||
def on_text_after_changed(self, event):
|
||||
self.text_after = event.GetEventObject().GetValue()
|
||||
self.update_preview()
|
||||
|
||||
def on_kerning_update(self, event=None):
|
||||
self.update_preview()
|
||||
|
||||
def on_kerning_list_select(self, event=None):
|
||||
self.update_preview()
|
||||
event.Skip()
|
||||
|
||||
def set_font_list(self):
|
||||
self.fonts = {}
|
||||
font_list = get_font_list()
|
||||
for font in font_list:
|
||||
self.fonts[font.marked_custom_font_name] = font
|
||||
image = font.preview_image
|
||||
if image is not None:
|
||||
image = wx.Image(image)
|
||||
# Windows requires all images to have the exact same size
|
||||
image.Rescale(300, 20, quality=wx.IMAGE_QUALITY_HIGH)
|
||||
self.font_chooser.Append(font.marked_custom_font_name, wx.Bitmap(image))
|
||||
else:
|
||||
self.font_chooser.Append(font.marked_custom_font_name)
|
||||
|
||||
def get_active_kerning_pair(self):
|
||||
selection = self.kerning_list.GetFirstSelected()
|
||||
if selection == -1:
|
||||
return ''
|
||||
kerning_pair = self.kerning_list.GetItem(selection, 0).Text
|
||||
kerning = float(self.kerning_list.GetItem(selection, 1).Text)
|
||||
if self.kerning_list.GetItem(selection, 2).Text:
|
||||
try:
|
||||
kerning = float(self.kerning_list.GetItem(selection, 2).Text)
|
||||
self.kerning_pairs[kerning_pair] = float(kerning)
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
return kerning_pair
|
||||
|
||||
def on_font_changed(self, event=None):
|
||||
self.font = self.fonts.get(self.font_chooser.GetValue(), list(self.fonts.values())[0].marked_custom_font_name)
|
||||
self.kerning_pairs = self.font.kerning_pairs
|
||||
self.font._load_variants()
|
||||
self.default_variant = self.font.variants[self.font.default_variant]
|
||||
self.glyphs = list(self.default_variant.glyphs.keys())
|
||||
|
||||
kerning_combinations = combinations_with_replacement(self.glyphs, 2)
|
||||
self.kerning_combinations = [''.join(combination) for combination in kerning_combinations]
|
||||
self.kerning_combinations.extend([combination[1] + combination[0] for combination in self.kerning_combinations])
|
||||
self.kerning_combinations = list(set(self.kerning_combinations))
|
||||
self.kerning_combinations.sort()
|
||||
|
||||
# Add the rows
|
||||
self.kerning_list.ClearAll()
|
||||
# Add some columns
|
||||
self.kerning_list.InsertColumn(0, "Kerning pair")
|
||||
self.kerning_list.InsertColumn(1, "Current kerning")
|
||||
self.kerning_list.InsertColumn(2, "New kerning")
|
||||
# Set the width of the columns
|
||||
self.kerning_list.SetColumnWidth(0, 120)
|
||||
self.kerning_list.SetColumnWidth(1, 120)
|
||||
self.kerning_list.SetColumnWidth(2, 120)
|
||||
for kerning_pair in self.kerning_combinations:
|
||||
index = self.kerning_list.InsertItem(self.kerning_list.GetItemCount(), kerning_pair)
|
||||
self.kerning_list.SetItem(index, 0, kerning_pair)
|
||||
self.kerning_list.SetItem(index, 1, str(self.kerning_pairs.get(kerning_pair, 0.0)))
|
||||
if self.kerning_list.GetItemCount() != 0:
|
||||
self.kerning_list.Select(0)
|
||||
self.kerning_list.Focus(0)
|
||||
|
||||
self.update_preview()
|
||||
|
||||
def apply(self, event):
|
||||
json_file = path.join(self.font.path, 'font.json')
|
||||
|
||||
if not path.isfile(json_file) or not path.isfile(json_file):
|
||||
errormsg(_("Could not read json file."))
|
||||
return
|
||||
|
||||
with open(json_file, 'r') as font_data:
|
||||
data = json.load(font_data)
|
||||
|
||||
kerning_pairs = {key: val for key, val in self.kerning_pairs.items() if val != 0}
|
||||
data['kerning_pairs'] = kerning_pairs
|
||||
data['glyphs'] = self.glyphs
|
||||
|
||||
# write data to font.json into the same directory as the font file
|
||||
with open(json_file, 'w', encoding="utf8") as font_data:
|
||||
json.dump(data, font_data, indent=4, ensure_ascii=False)
|
||||
|
||||
self.GetTopLevelParent().Close()
|
||||
|
||||
def cancel(self, event):
|
||||
self.GetTopLevelParent().Close()
|
||||
|
||||
def update_preview(self, event=None):
|
||||
self.preview_renderer.update()
|
||||
|
||||
def update_lettering(self):
|
||||
del self.layer[:]
|
||||
|
||||
text = self.get_active_kerning_pair()
|
||||
if not text:
|
||||
return
|
||||
|
||||
text = self.text_before + text + self.text_after
|
||||
|
||||
last_character = None
|
||||
position_x = 0
|
||||
for character in text:
|
||||
glyph = self.default_variant[character]
|
||||
if character == " " or (glyph is None and self.font.default_glyph == " "):
|
||||
position_x += self.font.word_spacing
|
||||
last_character = None
|
||||
else:
|
||||
if glyph is None:
|
||||
glyph = self.default_variant[self.font.default_glyph]
|
||||
|
||||
if glyph is not None:
|
||||
node = deepcopy(glyph.node)
|
||||
if last_character is not None:
|
||||
position_x += glyph.min_x - self.kerning_pairs.get(last_character + character, 0)
|
||||
|
||||
transform = f"translate({position_x}, 0)"
|
||||
node.set('transform', transform)
|
||||
|
||||
horiz_adv_x_default = self.font.horiz_adv_x_default
|
||||
if horiz_adv_x_default is None:
|
||||
horiz_adv_x_default = glyph.width + glyph.min_x
|
||||
|
||||
position_x += self.font.horiz_adv_x.get(character, horiz_adv_x_default) - glyph.min_x
|
||||
|
||||
self.font._update_commands(node, glyph)
|
||||
self.font._update_clips(self.layer, node, glyph)
|
||||
|
||||
# this is used to recognize a glyph layer later in the process
|
||||
# because this is not unique it will be overwritten by inkscape when inserted into the document
|
||||
node.set("id", "glyph")
|
||||
self.layer.add(node)
|
||||
last_character = character
|
||||
|
||||
def render_stitch_plan(self):
|
||||
stitch_groups = []
|
||||
try:
|
||||
self.update_lettering()
|
||||
elements = nodes_to_elements(self.layer.iterdescendants(SVG_PATH_TAG))
|
||||
last_stitch_group = None
|
||||
for element in elements:
|
||||
check_stop_flag()
|
||||
stitch_groups.extend(element.embroider(last_stitch_group))
|
||||
if stitch_groups:
|
||||
last_stitch_group = stitch_groups[-1]
|
||||
|
||||
if stitch_groups:
|
||||
return stitch_groups_to_stitch_plan(
|
||||
stitch_groups,
|
||||
collapse_len=self.metadata['collapse_len_mm'],
|
||||
min_stitch_len=self.metadata['min_stitch_len_mm']
|
||||
)
|
||||
except SystemExit:
|
||||
raise
|
||||
except ExitThread:
|
||||
raise
|
||||
except Exception:
|
||||
raise
|
||||
# Ignore errors. This can be things like incorrect paths for
|
||||
# satins or division by zero caused by incorrect param values.
|
||||
pass
|
||||
|
||||
def on_stitch_plan_rendered(self, stitch_plan):
|
||||
self.simulator.stop()
|
||||
self.simulator.load(stitch_plan)
|
||||
self.simulator.go()
|
||||
|
||||
|
||||
class EditableListCtrl(wx.ListCtrl, TextEditMixin):
|
||||
|
||||
def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
|
||||
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
|
||||
TextEditMixin.__init__(self)
|
||||
|
||||
def OpenEditor(self, column, row):
|
||||
self.original_data = self.GetItemText(row, column)
|
||||
if column == 2:
|
||||
TextEditMixin.OpenEditor(self, column, row)
|
||||
self.editor.Bind(wx.EVT_KEY_DOWN, self.on_escape)
|
||||
|
||||
def on_escape(self, event=None):
|
||||
keycode = event.GetKeyCode()
|
||||
if keycode == wx.WXK_ESCAPE:
|
||||
self.CloseEditor(event=None, swap=True)
|
||||
event.Skip()
|
||||
|
||||
def CloseEditor(self, event=None, swap=False):
|
||||
text = self.editor.GetValue()
|
||||
if swap:
|
||||
self.editor.Hide()
|
||||
TextEditMixin.CloseEditor(self, event)
|
||||
return
|
||||
|
||||
if text:
|
||||
try:
|
||||
float(text)
|
||||
except ValueError:
|
||||
swap = True
|
||||
|
||||
if swap:
|
||||
self.editor.SetValue(self.original_data)
|
||||
|
||||
TextEditMixin.CloseEditor(self, event)
|
Ładowanie…
Reference in New Issue