Make PNG (simple/realistic) and threadlist available in export file formats (#3019)

pull/3021/head
Kaalleen 2024-06-26 22:51:15 +02:00 zatwierdzone przez GitHub
rodzic d2e571a3fb
commit 5f23dea1a1
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
12 zmienionych plików z 310 dodań i 57 usunięć

Wyświetl plik

@ -46,6 +46,8 @@ from .output import Output
from .palette_split_text import PaletteSplitText
from .palette_to_text import PaletteToText
from .params import Params
from .png_realistic import PngRealistic
from .png_simple import PngSimple
from .preferences import Preferences
from .print_pdf import Print
from .redwork import Redwork
@ -61,6 +63,7 @@ from .stitch_plan_preview_undo import StitchPlanPreviewUndo
from .stroke_to_lpe_satin import StrokeToLpeSatin
from .tartan import Tartan
from .test_swatches import TestSwatches
from .thread_list import ThreadList
from .troubleshoot import Troubleshoot
from .unlink_clone import UnlinkClone
from .update_svg import UpdateSvg
@ -110,6 +113,8 @@ __all__ = extensions = [About,
PaletteSplitText,
PaletteToText,
Params,
PngRealistic,
PngSimple,
Preferences,
Print,
Redwork,
@ -125,6 +130,7 @@ __all__ = extensions = [About,
StrokeToLpeSatin,
Tartan,
TestSwatches,
ThreadList,
Troubleshoot,
UnlinkClone,
UpdateSvg,

Wyświetl plik

@ -0,0 +1,32 @@
# Authors: see git history
#
# Copyright (c) 2024 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import sys
from ..stitch_plan import stitch_groups_to_stitch_plan
from ..svg import render_stitch_plan
from ..threads import ThreadCatalog
from .base import InkstitchExtension
from .png_simple import write_png_output
class PngRealistic(InkstitchExtension):
def effect(self):
if not self.get_elements():
return
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
min_stitch_len = self.metadata['min_stitch_len_mm']
stitch_groups = self.elements_to_stitch_groups(self.elements)
stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=collapse_len, min_stitch_len=min_stitch_len)
ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette'])
layer = render_stitch_plan(self.svg, stitch_plan, True, visual_commands=False, render_jumps=False)
write_png_output(self.svg, layer)
# don't let inkex output the SVG!
sys.exit(0)

Wyświetl plik

@ -0,0 +1,72 @@
# Authors: see git history
#
# Copyright (c) 2024 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import sys
from tempfile import TemporaryDirectory
from inkex.units import convert_unit
from ..stitch_plan import stitch_groups_to_stitch_plan
from ..svg import render_stitch_plan
from ..threads import ThreadCatalog
from ..utils.svg_data import get_pagecolor
from .base import InkstitchExtension
from .utils.inkex_command import inkscape
class PngSimple(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self)
self.arg_parser.add_argument('--notebook', type=str, default='')
self.arg_parser.add_argument('--line_width', type=str, default='', dest='line_width')
def effect(self):
if not self.get_elements():
return
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
min_stitch_len = self.metadata['min_stitch_len_mm']
stitch_groups = self.elements_to_stitch_groups(self.elements)
stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=collapse_len, min_stitch_len=min_stitch_len)
ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette'])
line_width = convert_unit(f"{self.options.line_width}mm", self.svg.document_unit)
layer = render_stitch_plan(self.svg, stitch_plan, False, visual_commands=False,
render_jumps=False, line_width=line_width)
write_png_output(self.svg, layer)
# don't let inkex output the SVG!
sys.exit(0)
def write_png_output(svg, layer):
with TemporaryDirectory() as tempdir:
# Inkex's command functionality also writes files to temp directories like this.
temp_svg_path = f"{tempdir}/temp.svg"
temp_png_path = f"{tempdir}/temp.png"
with open(temp_svg_path, "wb") as f:
f.write(svg.tostring())
generate_png(svg, layer, temp_svg_path, temp_png_path)
# inkscape will read the file contents from stdout and copy
# to the destination file that the user chose
with open(temp_png_path, 'rb') as output_file:
sys.stdout.buffer.write(output_file.read())
def generate_png(svg, layer, input_path, output_path):
inkscape(input_path, actions="; ".join([
f"export-id:{layer.get_id()}",
"export-id-only",
"export-type:png",
f"export-dpi:{96*8}",
f"export-filename:{output_path}",
f"export-background:{get_pagecolor(svg.namedview)}",
"export-do" # Inkscape docs say this should be implicit at the end, but it doesn't seem to be.
]))

Wyświetl plik

@ -0,0 +1,78 @@
# Authors: see git history
#
# Copyright (c) 2024 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import sys
from ..i18n import _
from ..stitch_plan import stitch_groups_to_stitch_plan
from ..threads import ThreadCatalog
from .base import InkstitchExtension
class ThreadList(InkstitchExtension):
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self)
def effect(self):
if not self.get_elements():
return
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
min_stitch_len = self.metadata['min_stitch_len_mm']
stitch_groups = self.elements_to_stitch_groups(self.elements)
stitch_plan = stitch_groups_to_stitch_plan(stitch_groups, collapse_len=collapse_len, min_stitch_len=min_stitch_len)
ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette'])
thread_list = get_threadlist(stitch_plan, self.get_base_file_name())
# inkscape will read the file contents from stdout and copy
# to the destination file that the user chose
sys.stdout.write(thread_list)
# don't let inkex output the SVG!
sys.exit(0)
def get_threadlist(stitch_plan, design_name):
width = round(stitch_plan.dimensions_mm[0], 2)
height = round(stitch_plan.dimensions_mm[1], 2)
thread_used = []
thread_output = "%s\n" % _("Design Details")
thread_output += "==============================\n\n"
thread_output += _("Title")
thread_output += f": {design_name}\n"
thread_output += _("Size")
thread_output += f" (mm): {width}, {height}"
thread_output += _("Stitches")
thread_output += f": {stitch_plan.num_stitches}\n"
thread_output += _("Colors")
thread_output += f": {stitch_plan.num_colors}\n\n"
thread_output += _("Thread Order")
thread_output += "\n===========================\n\n"
for i, color_block in enumerate(stitch_plan):
thread = color_block.color
thread_output += str(i + 1) + " "
string = f"{thread.name} #{thread.number} - {thread.manufacturer} (#{thread.hex_digits.lower()})"
thread_output += string + "\n"
thread_used.append(string)
thread_output += "\n"
thread_output += _("Thread Used") + "\n"
thread_output += "===========================" + "\n\n"
for thread in set(thread_used):
thread_output += thread + "\n"
return thread_output

Wyświetl plik

@ -10,6 +10,7 @@ from copy import deepcopy
from zipfile import ZipFile
from inkex import Boolean, errormsg
from inkex.units import convert_unit
from lxml import etree
import pyembroidery
@ -17,10 +18,12 @@ import pyembroidery
from ..i18n import _
from ..output import write_embroidery_file
from ..stitch_plan import stitch_groups_to_stitch_plan
from ..svg import PIXELS_PER_MM
from ..svg import PIXELS_PER_MM, render_stitch_plan
from ..threads import ThreadCatalog
from ..utils.geometry import Point
from .base import InkstitchExtension
from .png_simple import generate_png
from .thread_list import get_threadlist
class Zip(InkstitchExtension):
@ -41,6 +44,11 @@ class Zip(InkstitchExtension):
self.formats.append('svg')
self.arg_parser.add_argument('--format-threadlist', type=Boolean, default=False, dest='threadlist')
self.formats.append('threadlist')
self.arg_parser.add_argument('--format-png_realistic', type=Boolean, default=False, dest='png_realistic')
self.formats.append('png_realistic')
self.arg_parser.add_argument('--format-png_simple', type=Boolean, default=False, dest='png_simple')
self.arg_parser.add_argument('--png_simple_line_width', type=float, default=0.3, dest='line_width')
self.formats.append('png_simple')
self.arg_parser.add_argument('--x-repeats', type=int, default=1, dest='x_repeats', )
self.arg_parser.add_argument('--y-repeats', type=int, default=1, dest='y_repeats',)
@ -64,23 +72,7 @@ class Zip(InkstitchExtension):
base_file_name = self._get_file_name()
path = tempfile.mkdtemp()
files = []
for format in self.formats:
if getattr(self.options, format):
output_file = os.path.join(path, "%s.%s" % (base_file_name, format))
if format == 'svg':
document = deepcopy(self.document.getroot())
with open(output_file, 'w', encoding='utf-8') as svg:
svg.write(etree.tostring(document).decode('utf-8'))
elif format == 'threadlist':
output_file = os.path.join(path, "%s_%s.txt" % (base_file_name, _("threadlist")))
output = open(output_file, 'w', encoding='utf-8')
output.write(self.get_threadlist(stitch_plan, base_file_name))
output.close()
else:
write_embroidery_file(output_file, stitch_plan, self.document.getroot())
files.append(output_file)
files = self.generate_output_files(stitch_plan, path, base_file_name)
if not files:
errormsg(_("No embroidery file formats selected."))
@ -123,34 +115,37 @@ class Zip(InkstitchExtension):
offsets.append(Point(x * dx, y * dy))
return stitch_plan.make_offsets(offsets)
def get_threadlist(self, stitch_plan, design_name):
thread_used = []
def generate_output_files(self, stitch_plan, path, base_file_name):
files = []
for format in self.formats:
if getattr(self.options, format):
output_file = os.path.join(path, "%s.%s" % (base_file_name, format))
if format == 'svg':
document = deepcopy(self.document.getroot())
with open(output_file, 'w', encoding='utf-8') as svg:
svg.write(etree.tostring(document).decode('utf-8'))
elif format == 'threadlist':
output_file = os.path.join(path, "%s_%s.txt" % (base_file_name, _("threadlist")))
with open(output_file, 'w', encoding='utf-8') as output:
output.write(get_threadlist(stitch_plan, base_file_name))
elif format == 'png_realistic':
output_file = os.path.join(path, f"{base_file_name}_realistic.png")
layer = render_stitch_plan(self.svg, stitch_plan, True, visual_commands=False, render_jumps=False)
self.generate_png_output(output_file, layer)
elif format == 'png_simple':
output_file = os.path.join(path, f"{base_file_name}_simple.png")
line_width = convert_unit(f"{self.options.line_width}mm", self.svg.document_unit)
layer = render_stitch_plan(self.svg, stitch_plan, False, visual_commands=False,
render_jumps=False, line_width=line_width)
self.generate_png_output(output_file, layer)
else:
write_embroidery_file(output_file, stitch_plan, self.document.getroot())
files.append(output_file)
return files
thread_output = "%s\n" % _("Design Details")
thread_output += "==============\n\n"
thread_output += "%s: %s\n" % (_("Title"), design_name)
thread_output += "%s (mm): %.2f x %.2f\n" % (_("Size"), stitch_plan.dimensions_mm[0], stitch_plan.dimensions_mm[1])
thread_output += "%s: %s\n" % (_("Stitches"), stitch_plan.num_stitches)
thread_output += "%s: %s\n\n" % (_("Colors"), stitch_plan.num_colors)
thread_output += "%s\n" % _("Thread Order")
thread_output += "============\n\n"
for i, color_block in enumerate(stitch_plan):
thread = color_block.color
thread_output += str(i + 1) + " "
string = "%s #%s - %s (#%s)" % (thread.name, thread.number, thread.manufacturer, thread.hex_digits.lower())
thread_output += string + "\n"
thread_used.append(string)
thread_output += "\n"
thread_output += _("Thread Used") + "\n"
thread_output += "============" + "\n\n"
for thread in set(thread_used):
thread_output += thread + "\n"
return "%s" % thread_output
def generate_png_output(self, output_file, layer):
with tempfile.TemporaryDirectory() as tempdir:
temp_svg_path = f"{tempdir}/temp.svg"
with open(temp_svg_path, "wb") as f:
f.write(self.svg.tostring())
generate_png(self.svg, layer, temp_svg_path, output_file)

Wyświetl plik

@ -20,7 +20,8 @@ def pyembroidery_output_formats():
description = "%s [STITCH]" % description
elif format['category'] != "embroidery":
description = "%s [DEBUG]" % description
yield format['extension'], description, format['mimetype'], format['category']
if not format['extension'] == 'png':
yield format['extension'], description, format['mimetype'], format['category']
def generate_output_inx_files(alter_data):

Wyświetl plik

@ -104,9 +104,6 @@ def write_embroidery_file(file_path, stitch_plan, svg, settings={}):
settings['max_stitch'] = float('inf')
settings['max_jump'] = float('inf')
settings['explicit_trim'] = False
elif file_path.endswith('.png'):
settings['linewidth'] = 1
settings['background'] = 'white'
try:
pyembroidery.write(pattern, file_path, settings)

Wyświetl plik

@ -179,7 +179,7 @@ def color_block_to_realistic_stitches(color_block, svg, destination, render_jump
start = point
def color_block_to_paths(color_block, svg, destination, visual_commands, render_jumps=True):
def color_block_to_paths(color_block, svg, destination, visual_commands, line_width, render_jumps=True):
# If we try to import these above, we get into a mess of circular
# imports.
from ..commands import add_commands
@ -200,7 +200,7 @@ def color_block_to_paths(color_block, svg, destination, visual_commands, render_
color = color_block.color.visible_on_white.to_hex_str()
path = inkex.PathElement(attrib={
'id': svg.get_unique_id("object"),
'style': "stroke: %s; stroke-width: 0.4; fill: none;" % color,
'style': f"stroke: {color}; stroke-width: {line_width}; fill: none;stroke-linejoin: round;stroke-linecap: round;",
'd': "M" + " ".join(" ".join(str(coord) for coord in point) for point in point_list),
'transform': get_correction_transform(svg),
INKSTITCH_ATTRIBS['stroke_method']: 'manual_stitch'
@ -220,7 +220,7 @@ def color_block_to_paths(color_block, svg, destination, visual_commands, render_
path.set(INKSTITCH_ATTRIBS['stop_after'], 'true')
def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True, render_jumps=True) -> inkex.Group:
def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True, render_jumps=True, line_width=0.4) -> inkex.Group:
layer_or_image = svg.findone(".//*[@id='__inkstitch_stitch_plan__']")
if layer_or_image is not None:
layer_or_image.getparent().remove(layer_or_image)
@ -241,7 +241,7 @@ def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True,
if realistic:
color_block_to_realistic_stitches(color_block, svg, group, render_jumps)
else:
color_block_to_paths(color_block, svg, group, visual_commands, render_jumps)
color_block_to_paths(color_block, svg, group, visual_commands, line_width, render_jumps)
if realistic:
# Remove filter from defs, if any

Wyświetl plik

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>PNG file output (realistic)</name>
<id>org.{{ id_inkstitch }}.png_realistic</id>
<output is_exported="true">
<extension>.png</extension>
<mimetype>image/png</mimetype>
<filetypename>{{ menu_inkstitch }}: Portable Network Graphics (Realistic) [IMAGE] (.png)</filetypename>
<filetypetooltip>Create a PNG file with a realistic embroidery representation using Ink/Stitch</filetypetooltip>
<dataloss>true</dataloss>
</output>
<param name="extension" type="string" gui-hidden="true">png_realistic</param>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>

Wyświetl plik

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>embroidery PNG file output</name>
<id>org.{{ id_inkstitch }}.png_simple</id>
<output is_exported="true">
<extension>.png</extension>
<mimetype>image/png</mimetype>
<filetypename>{{ menu_inkstitch }}: Portable Network Graphics (Simple) [IMAGE] (.png)</filetypename>
<filetypetooltip>Create a PNG file with a simple line embroidery representation using Ink/Stitch</filetypetooltip>
<dataloss>true</dataloss>
</output>
<param name="extension" type="string" gui-hidden="true">png_simple</param>
<param name="notebook" type="notebook">
<page name="settings" gui-text="Settings">
<param name="line_width" type="float" precision="2" min="0.01" max="5" gui-text="Line width (mm)">0.3</param>
</page>
<page name="info" gui-text="Help">
<label appearance="header">PNG file export</label>
<label>Export embroidery design to PNG</label>
<spacer />
<separator />
<spacer />
<label>Read more on our webiste</label>
<label appearance="url">https://inkstitch.org/docs/import-export/</label>
</page>
</param>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>

Wyświetl plik

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>embroidery TXT file output</name>
<id>org.{{ id_inkstitch }}.thread_list</id>
<output>
<extension>.txt</extension>
<mimetype>text/plain</mimetype>
<filetypename>{{ menu_inkstitch }}: Threadlist [COLOR] (.txt)</filetypename>
<filetypetooltip>A list of thread colors</filetypetooltip>
<dataloss>true</dataloss>
</output>
<param name="extension" type="string" gui-hidden="true">thread_list</param>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>

Wyświetl plik

@ -17,10 +17,13 @@
<label>Output formats:</label>
{%- for format, description, mimetype, category in formats %}
{%- if category != "vector" and category != "debug" %}
<param name="format-{{ format }}" type="boolean" _gui-text=".{{ format | upper }}: {{ description }}">false</param>
<param name="format-{{ format }}" type="boolean" gui-text=".{{ format | upper }}: {{ description }}">false</param>
{%- endif %}
{%- endfor %}
<param name="format-threadlist" type="boolean" gui-text=".TXT: Threadlist [COLOR]">false</param>
<param name="format-png_realistic" type="boolean" gui-text=".PNG: Portable Network Graphics (Realistic) [COLOR]">false</param>
<param name="format-png_simple" type="boolean" gui-text=".PNG: Portable Network Graphics (Simple) [COLOR]">false</param>
<param name="png_simple_line_width" type="float" precision="2" min="0.01" max="5" gui-text="Line width (mm)" indent="4">0.3</param>
<param name="format-svg" type="boolean" gui-text=".SVG: Scalable Vector Graphic">false</param>
<param name="extension" type="string" gui-hidden="true">zip</param>
</page>
@ -32,6 +35,13 @@
<param name="y-repeats" type="int" min="1" max="20" gui-text="Vertical repeats">1</param>
<param name="y-spacing" type="float" min="-1000" max="1000" gui-text="Vertical spacing (mm)">100</param>
</page>
<page name="info" gui-text="Help">
<label appearance="header">Zip File Export</label>
<label>Export multiple embroidery file formats at once.</label>
<separator />
<label>Read more on our webiste:</label>
<label appearance="url">https://inkstitch.org/docs/import-export/#batch-export</label>
</page>
</param>
<script>
{{ command_tag | safe }}