Applied style guide

pull/1548/head
Andreas 2021-10-29 16:18:22 +02:00 zatwierdzone przez Kaalleen
rodzic 0fcf8bb97c
commit 125db3f83b
15 zmienionych plików z 1471 dodań i 906 usunięć

Wyświetl plik

@ -12,7 +12,6 @@ import inkex
from shapely import geometry as shgeo
from shapely.validation import explain_validity
from ..stitches import legacy_fill
from ..i18n import _
from ..stitch_plan import StitchGroup
from ..stitches import auto_fill
@ -21,12 +20,12 @@ from ..utils import cache, version
from .element import param
from .element import EmbroideryElement
from ..patterns import get_patterns
#from .fill import Fill
from .validation import ValidationWarning
from ..utils import Point as InkstitchPoint
from ..svg import PIXELS_PER_MM
from ..svg.tags import INKSCAPE_LABEL
class SmallShapeWarning(ValidationWarning):
name = _("Small Fill")
description = _("This fill object is so small that it would probably look better as running stitch or satin column. "
@ -50,38 +49,42 @@ class AutoFill(EmbroideryElement):
element_name = _("AutoFill")
@property
@param('auto_fill', _('Automatically routed fill stitching'), type='toggle', default=True, sort_index = 1)
@param('auto_fill', _('Automatically routed fill stitching'), type='toggle', default=True, sort_index=1)
def auto_fill2(self):
return self.get_boolean_param('auto_fill', True)
return self.get_boolean_param('auto_fill', True)
@property
@param('fill_method', _('Fill method'), type='dropdown', default=0, options=[_("Auto Fill"), _("Tangential"), _("Guided Auto Fill")], sort_index = 2)
@param('fill_method', _('Fill method'), type='dropdown', default=0,
options=[_("Auto Fill"), _("Tangential"), _("Guided Auto Fill")], sort_index=2)
def fill_method(self):
return self.get_int_param('fill_method', 0)
@property
@param('tangential_strategy', _('Tangential strategy'), type='dropdown', default=1, options=[_("Closest point"), _("Inner to Outer")],select_items=[('fill_method',1)], sort_index = 2)
@param('tangential_strategy', _('Tangential strategy'), type='dropdown', default=1,
options=[_("Closest point"), _("Inner to Outer")], select_items=[('fill_method', 1)], sort_index=2)
def tangential_strategy(self):
return self.get_int_param('tangential_strategy', 1)
@property
@param('join_style', _('Join Style'), type='dropdown', default=0, options=[_("Round"), _("Mitered"), _("Beveled")],select_items=[('fill_method',1)], sort_index = 2)
@param('join_style', _('Join Style'), type='dropdown', default=0,
options=[_("Round"), _("Mitered"), _("Beveled")], select_items=[('fill_method', 1)], sort_index=2)
def join_style(self):
return self.get_int_param('join_style', 0)
@property
@param('interlaced', _('Interlaced'), type='boolean', default=True,select_items=[('fill_method',1),('fill_method',2)], sort_index = 2)
@param('interlaced', _('Interlaced'), type='boolean', default=True, select_items=[('fill_method', 1), ('fill_method', 2)], sort_index=2)
def interlaced(self):
return self.get_boolean_param('interlaced', True)
@property
@param('angle',
_('Angle of lines of stitches'),
tooltip=_('The angle increases in a counter-clockwise direction. 0 is horizontal. Negative angles are allowed.'),
tooltip=_(
'The angle increases in a counter-clockwise direction. 0 is horizontal. Negative angles are allowed.'),
unit='deg',
type='float',
sort_index = 4,
select_items=[('fill_method',0)],
sort_index=4,
select_items=[('fill_method', 0)],
default=0)
@cache
def angle(self):
@ -99,8 +102,8 @@ class AutoFill(EmbroideryElement):
tooltip=_('The last stitch in each row is quite close to the first stitch in the next row. '
'Skipping it decreases stitch count and density.'),
type='boolean',
sort_index = 4,
select_items=[('fill_method',0), ('fill_method',2)],
sort_index=4,
select_items=[('fill_method', 0), ('fill_method', 2)],
default=False)
def skip_last(self):
return self.get_boolean_param("skip_last", False)
@ -112,8 +115,8 @@ class AutoFill(EmbroideryElement):
tooltip=_('The flip option can help you with routing your stitch path. '
'When you enable flip, stitching goes from right-to-left instead of left-to-right.'),
type='boolean',
sort_index = 4,
select_items=[('fill_method',0), ('fill_method',2)],
sort_index=4,
select_items=[('fill_method', 0), ('fill_method', 2)],
default=False)
def flip(self):
return self.get_boolean_param("flip", False)
@ -123,7 +126,7 @@ class AutoFill(EmbroideryElement):
_('Spacing between rows'),
tooltip=_('Distance between rows of stitches.'),
unit='mm',
sort_index = 4,
sort_index=4,
type='float',
default=0.25)
def row_spacing(self):
@ -136,9 +139,10 @@ class AutoFill(EmbroideryElement):
@property
@param('max_stitch_length_mm',
_('Maximum fill stitch length'),
tooltip=_('The length of each stitch in a row. Shorter stitch may be used at the start or end of a row.'),
tooltip=_(
'The length of each stitch in a row. Shorter stitch may be used at the start or end of a row.'),
unit='mm',
sort_index = 4,
sort_index=4,
type='float',
default=3.0)
def max_stitch_length(self):
@ -147,10 +151,11 @@ class AutoFill(EmbroideryElement):
@property
@param('staggers',
_('Stagger rows this many times before repeating'),
tooltip=_('Setting this dictates how many rows apart the stitches will be before they fall in the same column position.'),
tooltip=_(
'Setting this dictates how many rows apart the stitches will be before they fall in the same column position.'),
type='int',
sort_index = 4,
select_items=[('fill_method',0)],
sort_index=4,
select_items=[('fill_method', 0)],
default=4)
def staggers(self):
return max(self.get_int_param("staggers", 4), 1)
@ -162,10 +167,10 @@ class AutoFill(EmbroideryElement):
# ensure path length
for i, path in enumerate(paths):
if len(path) < 3:
paths[i] = [(path[0][0], path[0][1]), (path[0][0]+1.0, path[0][1]), (path[0][0], path[0][1]+1.0)]
paths[i] = [(path[0][0], path[0][1]), (path[0][0] +
1.0, path[0][1]), (path[0][0], path[0][1]+1.0)]
return paths
@property
@cache
def outline(self):
@ -176,19 +181,16 @@ class AutoFill(EmbroideryElement):
def outline_length(self):
return self.outline.length
@property
def flip(self):
return False
@property
@param('running_stitch_length_mm',
_('Running stitch length (traversal between sections)'),
tooltip=_('Length of stitches around the outline of the fill region used when moving from section to section.'),
tooltip=_(
'Length of stitches around the outline of the fill region used when moving from section to section.'),
unit='mm',
type='float',
default=1.5,
select_items=[('fill_method',0),('fill_method',2)],
sort_index = 4)
select_items=[('fill_method', 0), ('fill_method', 2)],
sort_index=4)
def running_stitch_length(self):
return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)
@ -200,7 +202,8 @@ class AutoFill(EmbroideryElement):
@property
@param('fill_underlay_angle',
_('Fill angle'),
tooltip=_('Default: fill angle + 90 deg. Insert comma-seperated list for multiple layers.'),
tooltip=_(
'Default: fill angle + 90 deg. Insert comma-seperated list for multiple layers.'),
unit='deg',
group=_('AutoFill Underlay'),
type='float')
@ -211,7 +214,8 @@ class AutoFill(EmbroideryElement):
if underlay_angles is not None:
underlay_angles = underlay_angles.strip().split(',')
try:
underlay_angles = [math.radians(float(angle)) for angle in underlay_angles]
underlay_angles = [math.radians(
float(angle)) for angle in underlay_angles]
except (TypeError, ValueError):
return default_value
else:
@ -243,7 +247,8 @@ class AutoFill(EmbroideryElement):
@property
@param('fill_underlay_inset_mm',
_('Inset'),
tooltip=_('Shrink the shape before doing underlay, to prevent underlay from showing around the outside of the fill.'),
tooltip=_(
'Shrink the shape before doing underlay, to prevent underlay from showing around the outside of the fill.'),
unit='mm',
group=_('AutoFill Underlay'),
type='float',
@ -266,12 +271,13 @@ class AutoFill(EmbroideryElement):
@property
@param('expand_mm',
_('Expand'),
tooltip=_('Expand the shape before fill stitching, to compensate for gaps between shapes.'),
tooltip=_(
'Expand the shape before fill stitching, to compensate for gaps between shapes.'),
unit='mm',
type='float',
default=0,
sort_index = 5,
select_items=[('fill_method',0),('fill_method',2)])
sort_index=5,
select_items=[('fill_method', 0), ('fill_method', 2)])
def expand(self):
return self.get_float_param('expand_mm', 0)
@ -283,8 +289,8 @@ class AutoFill(EmbroideryElement):
'are not visible. This gives them a jagged appearance.'),
type='boolean',
default=True,
select_items=[('fill_method',0),('fill_method',2)],
sort_index = 6)
select_items=[('fill_method', 0), ('fill_method', 2)],
sort_index=6)
def underpath(self):
return self.get_boolean_param('underpath', True)
@ -308,7 +314,8 @@ class AutoFill(EmbroideryElement):
# from the first. So let's at least make sure the "first" thing is the
# biggest path.
paths = self.paths
paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
paths.sort(key=lambda point_list: shgeo.Polygon(
point_list).area, reverse=True)
# Very small holes will cause a shape to be rendered as an outline only
# they are too small to be rendered and only confuse the auto_fill algorithm.
# So let's ignore them
@ -397,7 +404,7 @@ class AutoFill(EmbroideryElement):
color=self.color,
tags=("auto_fill", "auto_fill_underlay"),
stitches=auto_fill(
self.underlay_shape,
self.underlay_shape,
None,
self.fill_underlay_angle[i],
self.fill_underlay_row_spacing,
@ -410,8 +417,8 @@ class AutoFill(EmbroideryElement):
underpath=self.underlay_underpath))
stitch_groups.append(underlay)
starting_point = underlay.stitches[-1]
if self.fill_method == 0: #Auto Fill
if self.fill_method == 0: # Auto Fill
stitch_group = StitchGroup(
color=self.color,
tags=("auto_fill", "auto_fill_top"),
@ -429,30 +436,31 @@ class AutoFill(EmbroideryElement):
ending_point,
self.underpath))
stitch_groups.append(stitch_group)
elif self.fill_method == 1: #Tangential Fill
elif self.fill_method == 1: # Tangential Fill
polygons = list(self.fill_shape)
if not starting_point:
starting_point = (0,0)
starting_point = (0, 0)
for poly in polygons:
connectedLine, connectedLineOrigin = StitchPattern.offset_poly(
poly,
-self.row_spacing,
self.join_style+1,
self.max_stitch_length,
poly,
-self.row_spacing,
self.join_style+1,
self.max_stitch_length,
self.interlaced,
self.tangential_strategy,
shgeo.Point(starting_point))
path = [InkstitchPoint(*p) for p in connectedLine]
stitch_group = StitchGroup(
color=self.color,
tags=("auto_fill", "auto_fill_top"),
stitches=path)
color=self.color,
tags=("auto_fill", "auto_fill_top"),
stitches=path)
stitch_groups.append(stitch_group)
elif self.fill_method == 2: #Guided Auto Fill
lines = get_patterns(self.node,"#inkstitch-guide-line-marker")
elif self.fill_method == 2: # Guided Auto Fill
lines = get_patterns(self.node, "#inkstitch-guide-line-marker")
lines = lines['stroke_patterns']
if not lines or lines[0].is_empty:
inkex.errormsg(_("No line marked as guide line found within the same group as patch"))
inkex.errormsg(
_("No line marked as guide line found within the same group as patch"))
else:
stitch_group = StitchGroup(
color=self.color,

Wyświetl plik

@ -14,7 +14,6 @@ from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS,
from ..utils import cache
from .auto_fill import AutoFill
from .element import EmbroideryElement, param
#from .fill import Fill
from .polyline import Polyline
from .satin_column import SatinColumn
from .stroke import Stroke
@ -79,9 +78,9 @@ class Clone(EmbroideryElement):
else:
elements = []
if element.get_style("fill", "black") and not element.get_style("stroke", 1) == "0":
#if element.get_boolean_param("auto_fill", True):
# if element.get_boolean_param("auto_fill", True):
elements.append(AutoFill(node))
#else:
# else:
# elements.append(Fill(node))
if element.get_style("stroke", self.node) is not None:
if not is_command(element.node):

Wyświetl plik

@ -33,7 +33,6 @@ class Param(object):
self.tooltip = tooltip
self.sort_index = sort_index
self.select_items = select_items
#print("IN PARAM: ", self.values)
def __repr__(self):
return "Param(%s)" % vars(self)
@ -164,7 +163,8 @@ class EmbroideryElement(object):
# Of course, transforms may also involve rotation, skewing, and translation.
# All except translation can affect how wide the stroke appears on the screen.
node_transform = inkex.transforms.Transform(get_node_transform(self.node))
node_transform = inkex.transforms.Transform(
get_node_transform(self.node))
# First, figure out the translation component of the transform. Using a zero
# vector completely cancels out the rotation, scale, and skew components.
@ -198,7 +198,8 @@ class EmbroideryElement(object):
@property
@param('ties',
_('Allow lock stitches'),
tooltip=_('Tie thread at the beginning and/or end of this object. Manual stitch will not add lock stitches.'),
tooltip=_(
'Tie thread at the beginning and/or end of this object. Manual stitch will not add lock stitches.'),
type='dropdown',
# Ties: 0 = Both | 1 = Before | 2 = After | 3 = Neither
# L10N options to allow lock stitch before and after objects
@ -256,7 +257,8 @@ class EmbroideryElement(object):
d = self.node.get("d", "")
if not d:
self.fatal(_("Object %(id)s has an empty 'd' attribute. Please delete this object from your document.") % dict(id=self.node.get("id")))
self.fatal(_("Object %(id)s has an empty 'd' attribute. Please delete this object from your document.") % dict(
id=self.node.get("id")))
return inkex.paths.Path(d).to_superpath()
@ -266,7 +268,8 @@ class EmbroideryElement(object):
@property
def shape(self):
raise NotImplementedError("INTERNAL ERROR: %s must implement shape()", self.__class__)
raise NotImplementedError(
"INTERNAL ERROR: %s must implement shape()", self.__class__)
@property
@cache
@ -316,7 +319,8 @@ class EmbroideryElement(object):
return self.get_boolean_param('stop_after', False)
def to_stitch_groups(self, last_patch):
raise NotImplementedError("%s must implement to_stitch_groups()" % self.__class__.__name__)
raise NotImplementedError(
"%s must implement to_stitch_groups()" % self.__class__.__name__)
def embroider(self, last_patch):
self.validate()
@ -329,8 +333,10 @@ class EmbroideryElement(object):
patch.force_lock_stitches = self.force_lock_stitches
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
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 patches

Wyświetl plik

@ -11,7 +11,6 @@ from .auto_fill import AutoFill
from .clone import Clone, is_clone
from .element import EmbroideryElement
from .empty_d_object import EmptyDObject
#from .fill import Fill
from .image import ImageObject
from .pattern import PatternObject
from .polyline import Polyline
@ -41,9 +40,9 @@ def node_to_elements(node): # noqa: C901
else:
elements = []
if element.get_style("fill", "black") and not element.get_style('fill-opacity', 1) == "0":
#if element.get_boolean_param("auto_fill", True):
# if element.get_boolean_param("auto_fill", True):
elements.append(AutoFill(node))
#else:
# else:
# elements.append(Fill(node))
if element.get_style("stroke"):
if not is_command(element.node):

Wyświetl plik

@ -7,9 +7,9 @@
import os
import sys
from collections import defaultdict,namedtuple
from collections import defaultdict
from copy import copy
from itertools import groupby,zip_longest
from itertools import groupby, zip_longest
import wx
from wx.lib.scrolledpanel import ScrolledPanel
@ -25,14 +25,11 @@ from ..utils import get_resource_dir
from .base import InkstitchExtension
#ChoiceWidgets = namedtuple("ChoiceWidgets", "param widget last_initialized_choice")
def grouper(iterable_obj, count, fillvalue=None):
args = [iter(iterable_obj)] * count
return zip_longest(*args, fillvalue=fillvalue)
class ParamsTab(ScrolledPanel):
def __init__(self, *args, **kwargs):
self.params = kwargs.pop('params', [])
@ -56,14 +53,16 @@ class ParamsTab(ScrolledPanel):
if toggles:
self.toggle = toggles[0]
self.params.remove(self.toggle)
self.toggle_checkbox = wx.CheckBox(self, label=self.toggle.description)
self.toggle_checkbox = wx.CheckBox(
self, label=self.toggle.description)
value = any(self.toggle.values)
if self.toggle.inverse:
value = not value
self.toggle_checkbox.SetValue(value)
self.toggle_checkbox.Bind(wx.EVT_CHECKBOX, self.update_toggle_state)
self.toggle_checkbox.Bind(
wx.EVT_CHECKBOX, self.update_toggle_state)
self.toggle_checkbox.Bind(wx.EVT_CHECKBOX, self.changed)
self.param_inputs[self.toggle.name] = self.toggle_checkbox
@ -76,7 +75,8 @@ class ParamsTab(ScrolledPanel):
self.settings_grid.AddGrowableCol(1, 2)
self.settings_grid.SetFlexibleDirection(wx.HORIZONTAL)
self.pencil_icon = wx.Image(os.path.join(get_resource_dir("icons"), "pencil_20x20.png")).ConvertToBitmap()
self.pencil_icon = wx.Image(os.path.join(get_resource_dir(
"icons"), "pencil_20x20.png")).ConvertToBitmap()
self.__set_properties()
self.__do_layout()
@ -230,19 +230,25 @@ class ParamsTab(ScrolledPanel):
if len(self.nodes) == 1:
description = _("These settings will be applied to 1 object.")
else:
description = _("These settings will be applied to %d objects.") % len(self.nodes)
description = _(
"These settings will be applied to %d objects.") % len(self.nodes)
if any(len(param.values) > 1 for param in self.params):
description += "\n" + _("Some settings had different values across objects. Select a value from the dropdown or enter a new one.")
description += "\n" + \
_("Some settings had different values across objects. Select a value from the dropdown or enter a new one.")
if self.dependent_tabs:
if len(self.dependent_tabs) == 1:
description += "\n" + _("Disabling this tab will disable the following %d tabs.") % len(self.dependent_tabs)
description += "\n" + \
_("Disabling this tab will disable the following %d tabs.") % len(
self.dependent_tabs)
else:
description += "\n" + _("Disabling this tab will disable the following tab.")
description += "\n" + \
_("Disabling this tab will disable the following tab.")
if self.paired_tab:
description += "\n" + _("Enabling this tab will disable %s and vice-versa.") % self.paired_tab.name
description += "\n" + \
_("Enabling this tab will disable %s and vice-versa.") % self.paired_tab.name
self.description_text = description
@ -268,21 +274,21 @@ class ParamsTab(ScrolledPanel):
# end wxGlade
pass
#choice tuple is None or contains ("choice widget param name", "actual selection")
def update_choice_widgets(self, choice_tuple = None):
if choice_tuple == None: #update all choices
# choice tuple is None or contains ("choice widget param name", "actual selection")
def update_choice_widgets(self, choice_tuple=None):
if choice_tuple is None: # update all choices
for choice in self.dict_of_choices.values():
self.update_choice_widgets((choice["param"].name, choice["widget"].GetSelection()))
self.update_choice_widgets(
(choice["param"].name, choice["widget"].GetSelection()))
else:
choice = self.dict_of_choices[choice_tuple[0]]
last_selection = choice["last_initialized_choice"]
last_selection = choice["last_initialized_choice"]
current_selection = choice["widget"].GetSelection()
if last_selection != -1 and last_selection != current_selection: #Hide the old widgets
if last_selection != -1 and last_selection != current_selection: # Hide the old widgets
for widget in self.choice_widgets[(choice["param"].name, last_selection)]:
widget.Hide()
#self.settings_grid.Detach(widget)
#choice_index = self.settings_grid.GetChildren().index(self.settings_grid.GetItem(choice["widget"])) #TODO: is there a better way to get the index in the sizer?
# self.settings_grid.Detach(widget)
for widgets in grouper(self.choice_widgets[choice_tuple], 4):
widgets[0].Show(True)
widgets[1].Show(True)
@ -295,20 +301,24 @@ class ParamsTab(ScrolledPanel):
# just to add space around the settings
box = wx.BoxSizer(wx.VERTICAL)
summary_box = wx.StaticBox(self, wx.ID_ANY, label=_("Inkscape objects"))
summary_box = wx.StaticBox(
self, wx.ID_ANY, label=_("Inkscape objects"))
sizer = wx.StaticBoxSizer(summary_box, wx.HORIZONTAL)
self.description = wx.StaticText(self)
self.update_description()
self.description.SetLabel(self.description_text)
self.description_container = box
self.Bind(wx.EVT_SIZE, self.resized)
sizer.Add(self.description, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
sizer.Add(self.description, proportion=0,
flag=wx.EXPAND | wx.ALL, border=5)
box.Add(sizer, proportion=0, flag=wx.ALL, border=5)
if self.toggle:
toggle_sizer = wx.BoxSizer(wx.HORIZONTAL)
toggle_sizer.Add(self.create_change_indicator(self.toggle.name), proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border=5)
toggle_sizer.Add(self.toggle_checkbox, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
toggle_sizer.Add(self.create_change_indicator(
self.toggle.name), proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border=5)
toggle_sizer.Add(self.toggle_checkbox, proportion=0,
flag=wx.ALIGN_CENTER_VERTICAL)
box.Add(toggle_sizer, proportion=0, flag=wx.BOTTOM, border=10)
for param in self.params:
@ -316,14 +326,16 @@ class ParamsTab(ScrolledPanel):
description = wx.StaticText(self, label=param.description)
description.SetToolTip(param.tooltip)
if param.select_items != None:
if param.select_items is not None:
col1.Hide()
description.Hide()
for item in param.select_items:
self.choice_widgets[item].extend([col1, description])
#else:
self.settings_grid.Add(col1, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
self.settings_grid.Add(description, proportion=1, flag=wx.EXPAND | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.TOP, border=5)
# else:
self.settings_grid.Add(
col1, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
self.settings_grid.Add(description, proportion=1, flag=wx.EXPAND |
wx.RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.TOP, border=5)
if param.type == 'boolean':
if len(param.values) > 1:
@ -340,9 +352,11 @@ class ParamsTab(ScrolledPanel):
input.SetSelection(int(param.values[0]))
input.Bind(wx.EVT_CHOICE, self.changed)
input.Bind(wx.EVT_CHOICE, self.update_choice_state)
self.dict_of_choices[param.name] = {"param": param, "widget": input, "last_initialized_choice": 1}
self.dict_of_choices[param.name] = {
"param": param, "widget": input, "last_initialized_choice": 1}
elif len(param.values) > 1:
input = wx.ComboBox(self, wx.ID_ANY, choices=sorted(str(value) for value in param.values), style=wx.CB_DROPDOWN)
input = wx.ComboBox(self, wx.ID_ANY, choices=sorted(
str(value) for value in param.values), style=wx.CB_DROPDOWN)
input.Bind(wx.EVT_COMBOBOX, self.changed)
input.Bind(wx.EVT_TEXT, self.changed)
else:
@ -354,14 +368,16 @@ class ParamsTab(ScrolledPanel):
col4 = wx.StaticText(self, label=param.unit or "")
if param.select_items != None:
if param.select_items is not None:
input.Hide()
col4.Hide()
for item in param.select_items:
self.choice_widgets[item].extend([input, col4])
#else:
self.settings_grid.Add(input, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.LEFT, border=40)
self.settings_grid.Add(col4, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
# else:
self.settings_grid.Add(
input, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.LEFT, border=40)
self.settings_grid.Add(
col4, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
self.inputs_to_params = {v: k for k, v in self.param_inputs.items()}
@ -372,16 +388,20 @@ class ParamsTab(ScrolledPanel):
self.Layout()
def create_change_indicator(self, param):
indicator = wx.Button(self, style=wx.BORDER_NONE | wx.BU_NOTEXT, size=(28, 28))
indicator.SetToolTip(_('Click to force this parameter to be saved when you click "Apply and Quit"'))
indicator.Bind(wx.EVT_BUTTON, lambda event: self.enable_change_indicator(param))
indicator = wx.Button(self, style=wx.BORDER_NONE |
wx.BU_NOTEXT, size=(28, 28))
indicator.SetToolTip(
_('Click to force this parameter to be saved when you click "Apply and Quit"'))
indicator.Bind(
wx.EVT_BUTTON, lambda event: self.enable_change_indicator(param))
self.param_change_indicators[param] = indicator
return indicator
def enable_change_indicator(self, param):
self.param_change_indicators[param].SetBitmapLabel(self.pencil_icon)
self.param_change_indicators[param].SetToolTip(_('This parameter will be saved when you click "Apply and Quit"'))
self.param_change_indicators[param].SetToolTip(
_('This parameter will be saved when you click "Apply and Quit"'))
self.changed_inputs.add(self.param_inputs[param])
@ -407,7 +427,8 @@ class SettingsFrame(wx.Frame):
_("Embroidery Params")
)
icon = wx.Icon(os.path.join(get_resource_dir("icons"), "inkstitch256x256.png"))
icon = wx.Icon(os.path.join(
get_resource_dir("icons"), "inkstitch256x256.png"))
self.SetIcon(icon)
self.notebook = wx.Notebook(self, wx.ID_ANY)
@ -425,7 +446,8 @@ class SettingsFrame(wx.Frame):
self.cancel_button.Bind(wx.EVT_BUTTON, self.cancel)
self.Bind(wx.EVT_CLOSE, self.cancel)
self.use_last_button = wx.Button(self, wx.ID_ANY, _("Use Last Settings"))
self.use_last_button = wx.Button(
self, wx.ID_ANY, _("Use Last Settings"))
self.use_last_button.Bind(wx.EVT_BUTTON, self.use_last)
self.apply_button = wx.Button(self, wx.ID_ANY, _("Apply and Quit"))
@ -544,7 +566,8 @@ class SettingsFrame(wx.Frame):
for tab in self.tabs:
self.notebook.AddPage(tab, tab.name)
sizer_1.Add(self.warning_panel, 0, flag=wx.EXPAND | wx.ALL, border=10)
sizer_1.Add(self.notebook, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10)
sizer_1.Add(self.notebook, 1, wx.EXPAND |
wx.LEFT | wx.TOP | wx.RIGHT, 10)
sizer_1.Add(self.presets_panel, 0, flag=wx.EXPAND | wx.ALL, border=10)
sizer_3.Add(self.cancel_button, 0, wx.RIGHT, 5)
sizer_3.Add(self.use_last_button, 0, wx.RIGHT | wx.BOTTOM, 5)
@ -584,7 +607,7 @@ class Params(InkstitchExtension):
else:
if element.get_style("fill", 'black') and not element.get_style("fill-opacity", 1) == "0":
classes.append(AutoFill)
#classes.append(Fill)
# classes.append(Fill)
if element.get_style("stroke") is not None:
classes.append(Stroke)
if element.get_style("stroke-dasharray") is None:
@ -611,7 +634,8 @@ class Params(InkstitchExtension):
else:
getter = 'get_param'
values = [item for item in (getattr(node, getter)(param.name, param.default) for node in nodes) if item is not None]
values = [item for item in (getattr(node, getter)(
param.name, param.default) for node in nodes) if item is not None]
return values
@ -677,7 +701,8 @@ class Params(InkstitchExtension):
for group, params in self.group_params(params):
tab_name = group or cls.element_name
tab = ParamsTab(parent, id=wx.ID_ANY, name=tab_name, params=list(params), nodes=nodes)
tab = ParamsTab(parent, id=wx.ID_ANY, name=tab_name,
params=list(params), nodes=nodes)
new_tabs.append(tab)
if group == "":
@ -697,14 +722,16 @@ class Params(InkstitchExtension):
def effect(self):
try:
app = wx.App()
frame = SettingsFrame(tabs_factory=self.create_tabs, on_cancel=self.cancel)
frame = SettingsFrame(
tabs_factory=self.create_tabs, on_cancel=self.cancel)
# position left, center
current_screen = wx.Display.GetFromPoint(wx.GetMousePosition())
display = wx.Display(current_screen)
display_size = display.GetClientArea()
frame_size = frame.GetSize()
frame.SetPosition((int(display_size[0]), int(display_size[3]/2 - frame_size[1]/2)))
frame.SetPosition((int(display_size[0]), int(
display_size[3]/2 - frame_size[1]/2)))
frame.Show()
app.MainLoop()

Wyświetl plik

@ -18,11 +18,13 @@ class SelectionToGuideLine(InkstitchExtension):
return
if not self.svg.selected:
inkex.errormsg(_("Please select one object to be marked as a guide line."))
inkex.errormsg(
_("Please select one object to be marked as a guide line."))
return
if len(self.get_nodes())!=1:
inkex.errormsg(_("Please select only one object to be marked as a guide line."))
if len(self.get_nodes()) != 1:
inkex.errormsg(
_("Please select only one object to be marked as a guide line."))
return
for guide_line in self.get_nodes():

Wyświetl plik

@ -19,7 +19,7 @@ def is_pattern(node):
def apply_patterns(patches, node):
patterns = get_patterns(node,"#inkstitch-pattern-marker")
patterns = get_patterns(node, "#inkstitch-pattern-marker")
_apply_fill_patterns(patterns['fill_patterns'], patches)
_apply_stroke_patterns(patterns['stroke_patterns'], patches)
@ -32,7 +32,8 @@ def _apply_stroke_patterns(patterns, patches):
patch_points.append(stitch)
if i == len(patch.stitches) - 1:
continue
intersection_points = _get_pattern_points(stitch, patch.stitches[i+1], pattern)
intersection_points = _get_pattern_points(
stitch, patch.stitches[i+1], pattern)
for point in intersection_points:
patch_points.append(Stitch(point, tags=('pattern_point',)))
patch.stitches = patch_points

Wyświetl plik

@ -1,14 +1,11 @@
import matplotlib.pyplot as plt
from shapely.geometry import Polygon
from shapely.ops import nearest_points, substring, polygonize
from anytree import PreOrderIter
from shapely.geometry.polygon import orient
#import LineStringSampling as Sampler
# import LineStringSampling as Sampler
import numpy as np
import matplotlib.collections as mcoll
import matplotlib.path as mpath
# def offset_polygons(polys, offset,joinstyle):
# if polys.geom_type == 'Polygon':
@ -40,7 +37,7 @@ import matplotlib.path as mpath
def plot_MultiPolygon(MultiPoly, plt, colorString):
if MultiPoly.is_empty:
return
if MultiPoly.geom_type == 'Polygon':
if MultiPoly.geom_type == "Polygon":
x2, y2 = MultiPoly.exterior.xy
plt.plot(x2, y2, colorString)
@ -56,6 +53,7 @@ def plot_MultiPolygon(MultiPoly, plt, colorString):
x2, y2 = inners.coords.xy
plt.plot(x2, y2, colorString)
# Test whether there are areas which would currently not be stitched but should be stitched
@ -65,12 +63,13 @@ def subtractResult(poly, rootPoly, offsetThresh):
poly2 = poly2.difference(node.val.buffer(offsetThresh, 5, 3, 3))
return poly2
# Used for debugging - plots all polygon exteriors within an AnyTree which is provided by the root node rootPoly.
def drawPoly(rootPoly, colorString):
fig, axs = plt.subplots(1, 1)
axs.axis('equal')
axs.axis("equal")
plt.gca().invert_yaxis()
for node in PreOrderIter(rootPoly):
# if(node.id == "hole"):
@ -84,15 +83,26 @@ def drawPoly(rootPoly, colorString):
def drawresult(resultcoords, resultcoords_Origin, colorString):
fig, axs = plt.subplots(1, 1)
axs.axis('equal')
axs.axis("equal")
plt.gca().invert_yaxis()
plt.plot(*zip(*resultcoords), colorString)
colormap = np.array(['r', 'g', 'b', 'c', 'm', 'y', 'k', 'gray', 'm'])
labelmap = np.array(['MUST_USE', 'REGULAR_SPACING', 'INITIAL_RASTERING', 'EDGE_NEEDED', 'NOT_NEEDED',
'ALREADY_TRANSFERRED', 'ADDITIONAL_TRACKING_POINT_NOT_NEEDED', 'EDGE_RASTERING_ALLOWED', 'EDGE_PREVIOUSLY_SHIFTED'])
colormap = np.array(["r", "g", "b", "c", "m", "y", "k", "gray", "m"])
labelmap = np.array(
[
"MUST_USE",
"REGULAR_SPACING",
"INITIAL_RASTERING",
"EDGE_NEEDED",
"NOT_NEEDED",
"ALREADY_TRANSFERRED",
"ADDITIONAL_TRACKING_POINT_NOT_NEEDED",
"EDGE_RASTERING_ALLOWED",
"EDGE_PREVIOUSLY_SHIFTED",
]
)
for i in range(0, 8+1):
for i in range(0, 8 + 1):
# if i != Sampler.PointSource.EDGE_NEEDED and i != Sampler.PointSource.INITIAL_RASTERING:
# continue
selection = []
@ -102,8 +112,8 @@ def drawresult(resultcoords, resultcoords_Origin, colorString):
if len(selection) > 0:
plt.scatter(*zip(*selection), c=colormap[i], label=labelmap[i])
# plt.scatter(*zip(*resultcoords),
# c=colormap[resultcoords_Origin])
# plt.scatter(*zip(*resultcoords),
# c=colormap[resultcoords_Origin])
axs.legend()
plt.show(block=True)
@ -112,8 +122,14 @@ def drawresult(resultcoords, resultcoords_Origin, colorString):
def colorline(
x, y, z=None, cmap=plt.get_cmap('copper'), norm=plt.Normalize(0.0, 1.0),
linewidth=3, alpha=1.0):
x,
y,
z=None,
cmap=plt.get_cmap("copper"),
norm=plt.Normalize(0.0, 1.0),
linewidth=3,
alpha=1.0,
):
"""
http://nbviewer.ipython.org/github/dpsanders/matplotlib-examples/blob/master/colorline.ipynb
http://matplotlib.org/examples/pylab_examples/multicolored_line.html
@ -133,14 +149,16 @@ def colorline(
z = np.asarray(z)
segments = make_segments(x, y)
lc = mcoll.LineCollection(segments, array=z, cmap=cmap, norm=norm,
linewidth=linewidth, alpha=alpha)
lc = mcoll.LineCollection(
segments, array=z, cmap=cmap, norm=norm, linewidth=linewidth, alpha=alpha
)
ax = plt.gca()
ax.add_collection(lc)
return lc
# Used by colorline

Wyświetl plik

@ -1,4 +1,3 @@
from sys import path
from shapely.geometry.polygon import LineString
from shapely.geometry import Point
from shapely.ops import substring
@ -8,33 +7,41 @@ from enum import IntEnum
from ..stitches import constants
from ..stitches import PointTransfer
#Used to tag the origin of a rastered point
# Used to tag the origin of a rastered point
class PointSource(IntEnum):
#MUST_USE = 0 # Legacy
# MUST_USE = 0 # Legacy
REGULAR_SPACING = 1 # introduced to not exceed maximal stichting distance
#INITIAL_RASTERING = 2 #Legacy
EDGE_NEEDED = 3 # point which must be stitched to avoid to large deviations to the desired path
#NOT_NEEDED = 4 #Legacy
#ALREADY_TRANSFERRED = 5 #Legacy
#ADDITIONAL_TRACKING_POINT_NOT_NEEDED = 6 #Legacy
#EDGE_RASTERING_ALLOWED = 7 #Legacy
#EDGE_PREVIOUSLY_SHIFTED = 8 #Legacy
ENTER_LEAVING_POINT = 9 #Whether this point is used to enter or leave a child
SOFT_EDGE_INTERNAL = 10 #If the angle at a point is <= constants.limiting_angle this point is marked as SOFT_EDGE
HARD_EDGE_INTERNAL = 11 #If the angle at a point is > constants.limiting_angle this point is marked as HARD_EDGE (HARD_EDGES will always be stitched)
PROJECTED_POINT = 12 #If the point was created by a projection (transferred point) of a neighbor it is marked as PROJECTED_POINT
REGULAR_SPACING_INTERNAL = 13 # introduced to not exceed maximal stichting distance
#FORBIDDEN_POINT_INTERNAL=14 #Legacy
SOFT_EDGE = 15 #If the angle at a point is <= constants.limiting_angle this point is marked as SOFT_EDGE
HARD_EDGE = 16 #If the angle at a point is > constants.limiting_angle this point is marked as HARD_EDGE (HARD_EDGES will always be stitched)
FORBIDDEN_POINT=17 #Only relevant for desired interlacing - non-shifted point positions at the next neighbor are marked as forbidden
REPLACED_FORBIDDEN_POINT=18 #If one decides to avoid forbidden points new points to the left and to the right as replacement are created
DIRECT = 19 #Calculated by next neighbor projection
OVERNEXT = 20 #Calculated by overnext neighbor projection
# INITIAL_RASTERING = 2 #Legacy
# point which must be stitched to avoid to large deviations to the desired path
EDGE_NEEDED = 3
# NOT_NEEDED = 4 #Legacy
# ALREADY_TRANSFERRED = 5 #Legacy
# ADDITIONAL_TRACKING_POINT_NOT_NEEDED = 6 #Legacy
# EDGE_RASTERING_ALLOWED = 7 #Legacy
# EDGE_PREVIOUSLY_SHIFTED = 8 #Legacy
ENTER_LEAVING_POINT = 9 # Whether this point is used to enter or leave a child
# If the angle at a point is <= constants.limiting_angle this point is marked as SOFT_EDGE
SOFT_EDGE_INTERNAL = 10
# If the angle at a point is > constants.limiting_angle this point is marked as HARD_EDGE (HARD_EDGES will always be stitched)
HARD_EDGE_INTERNAL = 11
# If the point was created by a projection (transferred point) of a neighbor it is marked as PROJECTED_POINT
PROJECTED_POINT = 12
REGULAR_SPACING_INTERNAL = 13 # introduced to not exceed maximal stichting distance
# FORBIDDEN_POINT_INTERNAL=14 #Legacy
SOFT_EDGE = 15 # If the angle at a point is <= constants.limiting_angle this point is marked as SOFT_EDGE
# If the angle at a point is > constants.limiting_angle this point is marked as HARD_EDGE (HARD_EDGES will always be stitched)
HARD_EDGE = 16
FORBIDDEN_POINT = 17 # Only relevant for desired interlacing - non-shifted point positions at the next neighbor are marked as forbidden
# If one decides to avoid forbidden points new points to the left and to the right as replacement are created
REPLACED_FORBIDDEN_POINT = 18
DIRECT = 19 # Calculated by next neighbor projection
OVERNEXT = 20 # Calculated by overnext neighbor projection
# Calculates the angles between adjacent edges at each interior point
#Note that the first and last values in the return array are zero since for the boundary points no angle calculations were possible
# Note that the first and last values in the return array are zero since for the boundary points no angle calculations were possible
def calculate_line_angles(line):
Angles = np.zeros(len(line.coords))
for i in range(1, len(line.coords)-1):
@ -42,44 +49,47 @@ def calculate_line_angles(line):
vec2 = np.array(line.coords[i+1])-np.array(line.coords[i])
vec1length = np.linalg.norm(vec1)
vec2length = np.linalg.norm(vec2)
#if vec1length <= 0:
# if vec1length <= 0:
# print("HIER FEHLER")
#if vec2length <=0:
# if vec2length <=0:
# print("HIER FEHLEr")
assert(vec1length >0)
assert(vec2length >0)
scalar_prod=np.dot(vec1, vec2)/(vec1length*vec2length)
scalar_prod = min(max(scalar_prod,-1),1)
#if scalar_prod > 1.0:
assert(vec1length > 0)
assert(vec2length > 0)
scalar_prod = np.dot(vec1, vec2)/(vec1length*vec2length)
scalar_prod = min(max(scalar_prod, -1), 1)
# if scalar_prod > 1.0:
# scalar_prod = 1.0
#elif scalar_prod < -1.0:
# elif scalar_prod < -1.0:
# scalar_prod = -1.0
Angles[i] = math.acos(scalar_prod)
return Angles
#Rasters a line between start_distance and end_distance.
#Input:
#-line: The line to be rastered
#-start_distance: The distance along the line from which the rastering should start
#-end_distance: The distance along the line until which the rastering should be done
#-maxstitch_distance: The maximum allowed stitch distance
#-stitching_direction: =1 is stitched along line direction, =-1 if stitched in reversed order. Note that
# Rasters a line between start_distance and end_distance.
# Input:
# -line: The line to be rastered
# -start_distance: The distance along the line from which the rastering should start
# -end_distance: The distance along the line until which the rastering should be done
# -maxstitch_distance: The maximum allowed stitch distance
# -stitching_direction: =1 is stitched along line direction, =-1 if stitched in reversed order. Note that
# start_distance > end_distance for stitching_direction = -1
#-must_use_points_deque: deque with projected points on line from its neighbors. An item of the deque
#is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
#index of point_origin is the index of the point in the neighboring line
#-abs_offset: used offset between to offsetted curves
#Output:
#-List of tuples with the rastered point coordinates
#-List which defines the point origin for each point according to the PointSource enum.
def raster_line_string_with_priority_points(line, start_distance, end_distance, maxstitch_distance, stitching_direction, must_use_points_deque, abs_offset):
# -must_use_points_deque: deque with projected points on line from its neighbors. An item of the deque
# is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
# index of point_origin is the index of the point in the neighboring line
# -abs_offset: used offset between to offsetted curves
# Output:
# -List of tuples with the rastered point coordinates
# -List which defines the point origin for each point according to the PointSource enum.
def raster_line_string_with_priority_points(line, start_distance, end_distance, maxstitch_distance,
stitching_direction, must_use_points_deque, abs_offset):
if (abs(end_distance-start_distance) < constants.line_lengh_seen_as_one_point):
return [line.interpolate(start_distance).coords[0]], [PointSource.HARD_EDGE]
assert (stitching_direction == -1 and start_distance >= end_distance) or (
stitching_direction == 1 and start_distance <= end_distance)
deque_points = list(must_use_points_deque)
linecoords = line.coords
@ -92,7 +102,8 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
deque_points[i] = (deque_points[i][0],
line.length-deque_points[i][1])
else:
deque_points = deque_points[::-1] #Since points with highest priority (=distance along line) are first (descending sorted)
# Since points with highest priority (=distance along line) are first (descending sorted)
deque_points = deque_points[::-1]
# Remove all points from the deque which do not fall in the segment [start_distance; end_distance]
while (len(deque_points) > 0 and deque_points[0][1] <= start_distance+min(maxstitch_distance/20, constants.point_spacing_to_be_considered_equal)):
@ -107,95 +118,109 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
path_coords = substring(aligned_line,
start_distance, end_distance)
#aligned line is a line without doubled points. I had the strange situation in which the offset "start_distance" from the line beginning resulted in a starting point which was
# already present in aligned_line causing a doubled point. A double point is not allowed in the following calculations so we need to remove it:
if abs(path_coords.coords[0][0]-path_coords.coords[1][0])<constants.eps and abs(path_coords.coords[0][1]-path_coords.coords[1][1])<constants.eps:
# aligned line is a line without doubled points.
# I had the strange situation in which the offset "start_distance" from the line beginning
# resulted in a starting point which was already present in aligned_line causing a doubled point.
# A double point is not allowed in the following calculations so we need to remove it:
if (abs(path_coords.coords[0][0]-path_coords.coords[1][0]) < constants.eps and
abs(path_coords.coords[0][1]-path_coords.coords[1][1]) < constants.eps):
path_coords.coords = path_coords.coords[1:]
if abs(path_coords.coords[-1][0]-path_coords.coords[-2][0])<constants.eps and abs(path_coords.coords[-1][1]-path_coords.coords[-2][1])<constants.eps:
if (abs(path_coords.coords[-1][0]-path_coords.coords[-2][0]) < constants.eps and
abs(path_coords.coords[-1][1]-path_coords.coords[-2][1]) < constants.eps):
path_coords.coords = path_coords.coords[:-1]
angles = calculate_line_angles(path_coords)
current_distance = start_distance
#Next we merge the line points and the projected (deque) points into one list
# Next we merge the line points and the projected (deque) points into one list
merged_point_list = []
dq_iter = 0
for point,angle in zip(path_coords.coords,angles):
#if abs(point[0]-40.4) < 0.2 and abs(point[1]-2.3)< 0.2:
for point, angle in zip(path_coords.coords, angles):
# if abs(point[0]-40.4) < 0.2 and abs(point[1]-2.3)< 0.2:
# print("GEFUNDEN")
current_distance = start_distance+path_coords.project(Point(point))
while dq_iter < len(deque_points) and deque_points[dq_iter][1] < current_distance:
#We want to avoid setting points at soft edges close to forbidden points
# We want to avoid setting points at soft edges close to forbidden points
if deque_points[dq_iter][0].point_source == PointSource.FORBIDDEN_POINT:
#Check whether a previous added point is a soft edge close to the forbidden point
if (merged_point_list[-1][0].point_source == PointSource.SOFT_EDGE_INTERNAL and
# Check whether a previous added point is a soft edge close to the forbidden point
if (merged_point_list[-1][0].point_source == PointSource.SOFT_EDGE_INTERNAL and
abs(merged_point_list[-1][1]-deque_points[dq_iter][1] < abs_offset*constants.factor_offset_forbidden_point)):
item = merged_point_list.pop()
merged_point_list.append((PointTransfer.projected_point_tuple(point=item[0].point, point_source=\
PointSource.FORBIDDEN_POINT),item[1]))
merged_point_list.append((PointTransfer.projected_point_tuple(
point=item[0].point, point_source=PointSource.FORBIDDEN_POINT), item[1]))
else:
merged_point_list.append(deque_points[dq_iter])
dq_iter+=1
#Check whether the current point is close to a forbidden point
if (dq_iter < len(deque_points) and
dq_iter += 1
# Check whether the current point is close to a forbidden point
if (dq_iter < len(deque_points) and
deque_points[dq_iter-1][0].point_source == PointSource.FORBIDDEN_POINT and
angle < constants.limiting_angle and
abs(deque_points[dq_iter-1][1]-current_distance) < abs_offset*constants.factor_offset_forbidden_point):
abs(deque_points[dq_iter-1][1]-current_distance) < abs_offset*constants.factor_offset_forbidden_point):
point_source = PointSource.FORBIDDEN_POINT
else:
if angle < constants.limiting_angle:
point_source = PointSource.SOFT_EDGE_INTERNAL
else:
point_source = PointSource.HARD_EDGE_INTERNAL
merged_point_list.append((PointTransfer.projected_point_tuple(point=Point(point), point_source=point_source),current_distance))
merged_point_list.append((PointTransfer.projected_point_tuple(
point=Point(point), point_source=point_source), current_distance))
result_list = [merged_point_list[0]]
#General idea: Take one point of merged_point_list after another into the current segment until this segment is not simplified to a straight line by shapelys simplify method.
#Then, look at the points within this segment and choose the best fitting one (HARD_EDGE > OVERNEXT projected point > DIRECT projected point) as termination of this segment
# General idea: Take one point of merged_point_list after another into the current segment until this segment is not simplified
# to a straight line by shapelys simplify method.
# Then, look at the points within this segment and choose the best fitting one
# (HARD_EDGE > OVERNEXT projected point > DIRECT projected point) as termination of this segment
# and start point for the next segment (so we do not always take the maximum possible length for a segment)
segment_start_index = 0
segment_end_index = 1
forbidden_point_list = []
while segment_end_index < len(merged_point_list):
#if abs(merged_point_list[segment_end_index-1][0].point.coords[0][0]-67.9) < 0.2 and abs(merged_point_list[segment_end_index-1][0].point.coords[0][1]-161.0)< 0.2:
while segment_end_index < len(merged_point_list):
# if abs(merged_point_list[segment_end_index-1][0].point.coords[0][0]-67.9) < 0.2 and
# abs(merged_point_list[segment_end_index-1][0].point.coords[0][1]-161.0)< 0.2:
# print("GEFUNDEN")
#Collection of points for the current segment
# Collection of points for the current segment
current_point_list = [merged_point_list[segment_start_index][0].point]
while segment_end_index < len(merged_point_list):
segment_length = merged_point_list[segment_end_index][1]-merged_point_list[segment_start_index][1]
segment_length = merged_point_list[segment_end_index][1] - \
merged_point_list[segment_start_index][1]
if segment_length > maxstitch_distance+constants.point_spacing_to_be_considered_equal:
new_distance = merged_point_list[segment_start_index][1]+maxstitch_distance
merged_point_list.insert(segment_end_index,(PointTransfer.projected_point_tuple(point=aligned_line.interpolate(new_distance), point_source=\
PointSource.REGULAR_SPACING_INTERNAL),new_distance))
if abs(merged_point_list[segment_end_index][0].point.coords[0][0]-12.2) < 0.2 and abs(merged_point_list[segment_end_index][0].point.coords[0][1]-0.9)< 0.2:
print("GEFUNDEN")
segment_end_index+=1
new_distance = merged_point_list[segment_start_index][1] + \
maxstitch_distance
merged_point_list.insert(segment_end_index, (PointTransfer.projected_point_tuple(
point=aligned_line.interpolate(new_distance), point_source=PointSource.REGULAR_SPACING_INTERNAL), new_distance))
# if (abs(merged_point_list[segment_end_index][0].point.coords[0][0]-12.2) < 0.2 and
# abs(merged_point_list[segment_end_index][0].point.coords[0][1]-0.9) < 0.2):
# print("GEFUNDEN")
segment_end_index += 1
break
#if abs(merged_point_list[segment_end_index][0].point.coords[0][0]-93.6) < 0.2 and abs(merged_point_list[segment_end_index][0].point.coords[0][1]-122.7)< 0.2:
# if abs(merged_point_list[segment_end_index][0].point.coords[0][0]-93.6) < 0.2 and
# abs(merged_point_list[segment_end_index][0].point.coords[0][1]-122.7)< 0.2:
# print("GEFUNDEN")
current_point_list.append(merged_point_list[segment_end_index][0].point)
simplified_len = len(LineString(current_point_list).simplify(constants.factor_offset_remove_dense_points*abs_offset,preserve_topology=False).coords)
if simplified_len > 2: #not all points have been simplified - so we need to add it
current_point_list.append(
merged_point_list[segment_end_index][0].point)
simplified_len = len(LineString(current_point_list).simplify(
constants.factor_offset_remove_dense_points*abs_offset, preserve_topology=False).coords)
if simplified_len > 2: # not all points have been simplified - so we need to add it
break
if merged_point_list[segment_end_index][0].point_source ==PointSource.HARD_EDGE_INTERNAL:
segment_end_index+=1
if merged_point_list[segment_end_index][0].point_source == PointSource.HARD_EDGE_INTERNAL:
segment_end_index += 1
break
segment_end_index+=1
segment_end_index += 1
segment_end_index-=1
segment_end_index -= 1
#Now we choose the best fitting point within this segment
# Now we choose the best fitting point within this segment
index_overnext = -1
index_direct = -1
index_hard_edge = -1
iter = segment_start_index+1
iter = segment_start_index+1
while (iter <= segment_end_index):
if merged_point_list[iter][0].point_source == PointSource.OVERNEXT:
index_overnext = iter
@ -208,48 +233,48 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
segment_end_index = index_hard_edge
else:
if index_overnext != -1:
if (index_direct != -1 and index_direct > index_overnext and
(merged_point_list[index_direct][1]-merged_point_list[index_overnext][1]) >=
constants.factor_segment_length_direct_preferred_over_overnext*
if (index_direct != -1 and index_direct > index_overnext and
(merged_point_list[index_direct][1]-merged_point_list[index_overnext][1]) >=
constants.factor_segment_length_direct_preferred_over_overnext *
(merged_point_list[index_overnext][1]-merged_point_list[segment_start_index][1])):
#We allow to take the direct projected point instead of the overnext projected point if it would result in a
#significant longer segment length
# We allow to take the direct projected point instead of the overnext projected point if it would result in a
# significant longer segment length
segment_end_index = index_direct
else:
segment_end_index = index_overnext
elif index_direct != -1:
segment_end_index = index_direct
#Usually OVERNEXT and DIRECT points are close to each other and in some cases both were selected as segment edges
#If they are too close (<abs_offset) we remove one of it
# Usually OVERNEXT and DIRECT points are close to each other and in some cases both were selected as segment edges
# If they are too close (<abs_offset) we remove one of it
if (((merged_point_list[segment_start_index][0].point_source == PointSource.OVERNEXT and
merged_point_list[segment_end_index][0].point_source == PointSource.DIRECT) or
(merged_point_list[segment_start_index][0].point_source == PointSource.DIRECT and
merged_point_list[segment_end_index][0].point_source == PointSource.OVERNEXT)) and
abs(merged_point_list[segment_end_index][1] - merged_point_list[segment_start_index][1]) < abs_offset):
abs(merged_point_list[segment_end_index][1] - merged_point_list[segment_start_index][1]) < abs_offset):
result_list.pop()
result_list.append(merged_point_list[segment_end_index])
#To have a chance to replace all forbidden points afterwards
# To have a chance to replace all forbidden points afterwards
if merged_point_list[segment_end_index][0].point_source == PointSource.FORBIDDEN_POINT:
forbidden_point_list.append(len(result_list)-1)
segment_start_index = segment_end_index
segment_end_index+=1
segment_end_index += 1
return_point_list = [] #[result_list[0][0].point.coords[0]]
return_point_source_list = []#[result_list[0][0].point_source]
return_point_list = [] # [result_list[0][0].point.coords[0]]
return_point_source_list = [] # [result_list[0][0].point_source]
#Currently replacement of forbidden points not satisfying
#result_list = replace_forbidden_points(aligned_line, result_list, forbidden_point_list,abs_offset)
# Currently replacement of forbidden points not satisfying
# result_list = replace_forbidden_points(aligned_line, result_list, forbidden_point_list,abs_offset)
#Finally we create the final return_point_list and return_point_source_list
# Finally we create the final return_point_list and return_point_source_list
for i in range(len(result_list)):
return_point_list.append(result_list[i][0].point.coords[0])
#if abs(result_list[i][0].point.coords[0][0]-91.7) < 0.2 and abs(result_list[i][0].point.coords[0][1]-106.15)< 0.2:
# if abs(result_list[i][0].point.coords[0][0]-91.7) < 0.2 and abs(result_list[i][0].point.coords[0][1]-106.15)< 0.2:
# print("GEFUNDEN")
if result_list[i][0].point_source == PointSource.HARD_EDGE_INTERNAL:
point_source = PointSource.HARD_EDGE
point_source = PointSource.HARD_EDGE
elif result_list[i][0].point_source == PointSource.SOFT_EDGE_INTERNAL:
point_source = PointSource.SOFT_EDGE
elif result_list[i][0].point_source == PointSource.REGULAR_SPACING_INTERNAL:
@ -261,132 +286,145 @@ def raster_line_string_with_priority_points(line, start_distance, end_distance,
return_point_source_list.append(point_source)
assert(len(return_point_list) == len(return_point_source_list))
#return remove_dense_points(returnpointlist, returnpointsourcelist, maxstitch_distance,abs_offset)
# return remove_dense_points(returnpointlist, returnpointsourcelist, maxstitch_distance,abs_offset)
return return_point_list, return_point_source_list
#Rasters a line between start_distance and end_distance.
#Input:
#-line: The line to be rastered
#-start_distance: The distance along the line from which the rastering should start
#-end_distance: The distance along the line until which the rastering should be done
#-maxstitch_distance: The maximum allowed stitch distance
#-stitching_direction: =1 is stitched along line direction, =-1 if stitched in reversed order. Note that
# Rasters a line between start_distance and end_distance.
# Input:
# -line: The line to be rastered
# -start_distance: The distance along the line from which the rastering should start
# -end_distance: The distance along the line until which the rastering should be done
# -maxstitch_distance: The maximum allowed stitch distance
# -stitching_direction: =1 is stitched along line direction, =-1 if stitched in reversed order. Note that
# start_distance > end_distance for stitching_direction = -1
#-must_use_points_deque: deque with projected points on line from its neighbors. An item of the deque
#is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
#index of point_origin is the index of the point in the neighboring line
#-abs_offset: used offset between to offsetted curves
#Output:
#-List of tuples with the rastered point coordinates
#-List which defines the point origin for each point according to the PointSource enum.
# -must_use_points_deque: deque with projected points on line from its neighbors. An item of the deque
# is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
# index of point_origin is the index of the point in the neighboring line
# -abs_offset: used offset between to offsetted curves
# Output:
# -List of tuples with the rastered point coordinates
# -List which defines the point origin for each point according to the PointSource enum.
def raster_line_string_with_priority_points_graph(line, maxstitch_distance, stitching_direction, must_use_points_deque, abs_offset, offset_by_half):
if (line.length < constants.line_lengh_seen_as_one_point):
return [line.coords[0]], [PointSource.HARD_EDGE]
deque_points = list(must_use_points_deque)
linecoords = line.coords
if stitching_direction==-1:
if stitching_direction == -1:
linecoords = linecoords[::-1]
for i in range(len(deque_points)):
deque_points[i] = (deque_points[i][0],
line.length-deque_points[i][1])
else:
deque_points = deque_points[::-1] #Since points with highest priority (=distance along line) are first (descending sorted)
# Since points with highest priority (=distance along line) are first (descending sorted)
deque_points = deque_points[::-1]
# Ordering in priority queue:
# (point, LineStringSampling.PointSource), priority)
aligned_line = LineString(linecoords) #might be different from line for stitching_direction=-1
# might be different from line for stitching_direction=-1
aligned_line = LineString(linecoords)
angles = calculate_line_angles(aligned_line)
#For the first and last point we cannot calculate an angle. Set it to above the limit to make it a hard edge
# For the first and last point we cannot calculate an angle. Set it to above the limit to make it a hard edge
angles[0] = 1.1*constants.limiting_angle
angles[-1] = 1.1*constants.limiting_angle
angles[-1] = 1.1*constants.limiting_angle
current_distance = 0.0
#Next we merge the line points and the projected (deque) points into one list
# Next we merge the line points and the projected (deque) points into one list
merged_point_list = []
dq_iter = 0
for point,angle in zip(aligned_line.coords,angles):
#if abs(point[0]-52.9) < 0.2 and abs(point[1]-183.4)< 0.2:
for point, angle in zip(aligned_line.coords, angles):
# if abs(point[0]-52.9) < 0.2 and abs(point[1]-183.4)< 0.2:
# print("GEFUNDEN")
current_distance = aligned_line.project(Point(point))
while dq_iter < len(deque_points) and deque_points[dq_iter][1] < current_distance:
#We want to avoid setting points at soft edges close to forbidden points
# We want to avoid setting points at soft edges close to forbidden points
if deque_points[dq_iter][0].point_source == PointSource.FORBIDDEN_POINT:
#Check whether a previous added point is a soft edge close to the forbidden point
if (merged_point_list[-1][0].point_source == PointSource.SOFT_EDGE_INTERNAL and
# Check whether a previous added point is a soft edge close to the forbidden point
if (merged_point_list[-1][0].point_source == PointSource.SOFT_EDGE_INTERNAL and
abs(merged_point_list[-1][1]-deque_points[dq_iter][1] < abs_offset*constants.factor_offset_forbidden_point)):
item = merged_point_list.pop()
merged_point_list.append((PointTransfer.projected_point_tuple(point=item[0].point, point_source=\
PointSource.FORBIDDEN_POINT),item[1]))
merged_point_list.append((PointTransfer.projected_point_tuple(
point=item[0].point, point_source=PointSource.FORBIDDEN_POINT), item[1]))
else:
merged_point_list.append(deque_points[dq_iter])
dq_iter+=1
#Check whether the current point is close to a forbidden point
if (dq_iter < len(deque_points) and
dq_iter += 1
# Check whether the current point is close to a forbidden point
if (dq_iter < len(deque_points) and
deque_points[dq_iter-1][0].point_source == PointSource.FORBIDDEN_POINT and
angle < constants.limiting_angle and
abs(deque_points[dq_iter-1][1]-current_distance) < abs_offset*constants.factor_offset_forbidden_point):
abs(deque_points[dq_iter-1][1]-current_distance) < abs_offset*constants.factor_offset_forbidden_point):
point_source = PointSource.FORBIDDEN_POINT
else:
if angle < constants.limiting_angle:
point_source = PointSource.SOFT_EDGE_INTERNAL
else:
point_source = PointSource.HARD_EDGE_INTERNAL
merged_point_list.append((PointTransfer.projected_point_tuple(point=Point(point), point_source=point_source),current_distance))
merged_point_list.append((PointTransfer.projected_point_tuple(
point=Point(point), point_source=point_source), current_distance))
result_list = [merged_point_list[0]]
#General idea: Take one point of merged_point_list after another into the current segment until this segment is not simplified to a straight line by shapelys simplify method.
#Then, look at the points within this segment and choose the best fitting one (HARD_EDGE > OVERNEXT projected point > DIRECT projected point) as termination of this segment
# General idea: Take one point of merged_point_list after another into the current segment until this segment is not simplified
# to a straight line by shapelys simplify method.
# Then, look at the points within this segment and choose the best fitting one
# (HARD_EDGE > OVERNEXT projected point > DIRECT projected point) as termination of this segment
# and start point for the next segment (so we do not always take the maximum possible length for a segment)
segment_start_index = 0
segment_end_index = 1
forbidden_point_list = []
while segment_end_index < len(merged_point_list):
#if abs(merged_point_list[segment_end_index-1][0].point.coords[0][0]-67.9) < 0.2 and abs(merged_point_list[segment_end_index-1][0].point.coords[0][1]-161.0)< 0.2:
while segment_end_index < len(merged_point_list):
# if abs(merged_point_list[segment_end_index-1][0].point.coords[0][0]-67.9) < 0.2 and
# abs(merged_point_list[segment_end_index-1][0].point.coords[0][1]-161.0)< 0.2:
# print("GEFUNDEN")
#Collection of points for the current segment
# Collection of points for the current segment
current_point_list = [merged_point_list[segment_start_index][0].point]
while segment_end_index < len(merged_point_list):
segment_length = merged_point_list[segment_end_index][1]-merged_point_list[segment_start_index][1]
segment_length = merged_point_list[segment_end_index][1] - \
merged_point_list[segment_start_index][1]
if segment_length > maxstitch_distance+constants.point_spacing_to_be_considered_equal:
new_distance = merged_point_list[segment_start_index][1]+maxstitch_distance
merged_point_list.insert(segment_end_index,(PointTransfer.projected_point_tuple(point=aligned_line.interpolate(new_distance), point_source=\
PointSource.REGULAR_SPACING_INTERNAL),new_distance))
#if abs(merged_point_list[segment_end_index][0].point.coords[0][0]-12.2) < 0.2 and abs(merged_point_list[segment_end_index][0].point.coords[0][1]-0.9)< 0.2:
new_distance = merged_point_list[segment_start_index][1] + \
maxstitch_distance
merged_point_list.insert(segment_end_index, (PointTransfer.projected_point_tuple(
point=aligned_line.interpolate(new_distance), point_source=PointSource.REGULAR_SPACING_INTERNAL), new_distance))
# if abs(merged_point_list[segment_end_index][0].point.coords[0][0]-12.2) < 0.2 and 7
# abs(merged_point_list[segment_end_index][0].point.coords[0][1]-0.9)< 0.2:
# print("GEFUNDEN")
segment_end_index+=1
segment_end_index += 1
break
#if abs(merged_point_list[segment_end_index][0].point.coords[0][0]-34.4) < 0.2 and abs(merged_point_list[segment_end_index][0].point.coords[0][1]-6.2)< 0.2:
# if abs(merged_point_list[segment_end_index][0].point.coords[0][0]-34.4) < 0.2 and
# abs(merged_point_list[segment_end_index][0].point.coords[0][1]-6.2)< 0.2:
# print("GEFUNDEN")
current_point_list.append(merged_point_list[segment_end_index][0].point)
simplified_len = len(LineString(current_point_list).simplify(constants.factor_offset_remove_dense_points*abs_offset,preserve_topology=False).coords)
if simplified_len > 2: #not all points have been simplified - so we need to add it
current_point_list.append(
merged_point_list[segment_end_index][0].point)
simplified_len = len(LineString(current_point_list).simplify(
constants.factor_offset_remove_dense_points*abs_offset, preserve_topology=False).coords)
if simplified_len > 2: # not all points have been simplified - so we need to add it
break
if merged_point_list[segment_end_index][0].point_source ==PointSource.HARD_EDGE_INTERNAL:
segment_end_index+=1
if merged_point_list[segment_end_index][0].point_source == PointSource.HARD_EDGE_INTERNAL:
segment_end_index += 1
break
segment_end_index+=1
segment_end_index += 1
segment_end_index-=1
segment_end_index -= 1
#Now we choose the best fitting point within this segment
# Now we choose the best fitting point within this segment
index_overnext = -1
index_direct = -1
index_hard_edge = -1
iter = segment_start_index+1
iter = segment_start_index+1
while (iter <= segment_end_index):
if merged_point_list[iter][0].point_source == PointSource.OVERNEXT:
index_overnext = iter
@ -406,48 +444,49 @@ def raster_line_string_with_priority_points_graph(line, maxstitch_distance, stit
index_less_preferred = index_overnext
if index_preferred != -1:
if (index_less_preferred != -1 and index_less_preferred > index_preferred and
(merged_point_list[index_less_preferred][1]-merged_point_list[index_preferred][1]) >=
constants.factor_segment_length_direct_preferred_over_overnext*
if (index_less_preferred != -1 and index_less_preferred > index_preferred and
(merged_point_list[index_less_preferred][1]-merged_point_list[index_preferred][1]) >=
constants.factor_segment_length_direct_preferred_over_overnext *
(merged_point_list[index_preferred][1]-merged_point_list[segment_start_index][1])):
#We allow to take the direct projected point instead of the overnext projected point if it would result in a
#significant longer segment length
# We allow to take the direct projected point instead of the overnext projected point if it would result in a
# significant longer segment length
segment_end_index = index_less_preferred
else:
segment_end_index = index_preferred
elif index_less_preferred != -1:
segment_end_index = index_less_preferred
#Usually OVERNEXT and DIRECT points are close to each other and in some cases both were selected as segment edges
#If they are too close (<abs_offset) we remove one of it
# Usually OVERNEXT and DIRECT points are close to each other and in some cases both were selected as segment edges
# If they are too close (<abs_offset) we remove one of it
if (((merged_point_list[segment_start_index][0].point_source == PointSource.OVERNEXT and
merged_point_list[segment_end_index][0].point_source == PointSource.DIRECT) or
(merged_point_list[segment_start_index][0].point_source == PointSource.DIRECT and
merged_point_list[segment_end_index][0].point_source == PointSource.OVERNEXT)) and
abs(merged_point_list[segment_end_index][1] - merged_point_list[segment_start_index][1]) < abs_offset):
abs(merged_point_list[segment_end_index][1] - merged_point_list[segment_start_index][1]) < abs_offset):
result_list.pop()
result_list.append(merged_point_list[segment_end_index])
#To have a chance to replace all forbidden points afterwards
# To have a chance to replace all forbidden points afterwards
if merged_point_list[segment_end_index][0].point_source == PointSource.FORBIDDEN_POINT:
forbidden_point_list.append(len(result_list)-1)
segment_start_index = segment_end_index
segment_end_index+=1
segment_end_index += 1
return_point_list = [] #[result_list[0][0].point.coords[0]]
return_point_source_list = []#[result_list[0][0].point_source]
return_point_list = [] # [result_list[0][0].point.coords[0]]
return_point_source_list = [] # [result_list[0][0].point_source]
#Currently replacement of forbidden points not satisfying
result_list = replace_forbidden_points(aligned_line, result_list, forbidden_point_list,abs_offset)
# Currently replacement of forbidden points not satisfying
result_list = replace_forbidden_points(
aligned_line, result_list, forbidden_point_list, abs_offset)
#Finally we create the final return_point_list and return_point_source_list
# Finally we create the final return_point_list and return_point_source_list
for i in range(len(result_list)):
return_point_list.append(result_list[i][0].point.coords[0])
#if abs(result_list[i][0].point.coords[0][0]-91.7) < 0.2 and abs(result_list[i][0].point.coords[0][1]-106.15)< 0.2:
# if abs(result_list[i][0].point.coords[0][0]-91.7) < 0.2 and abs(result_list[i][0].point.coords[0][1]-106.15)< 0.2:
# print("GEFUNDEN")
if result_list[i][0].point_source == PointSource.HARD_EDGE_INTERNAL:
point_source = PointSource.HARD_EDGE
point_source = PointSource.HARD_EDGE
elif result_list[i][0].point_source == PointSource.SOFT_EDGE_INTERNAL:
point_source = PointSource.SOFT_EDGE
elif result_list[i][0].point_source == PointSource.REGULAR_SPACING_INTERNAL:
@ -459,44 +498,49 @@ def raster_line_string_with_priority_points_graph(line, maxstitch_distance, stit
return_point_source_list.append(point_source)
assert(len(return_point_list) == len(return_point_source_list))
#return remove_dense_points(returnpointlist, returnpointsourcelist, maxstitch_distance,abs_offset)
# return remove_dense_points(returnpointlist, returnpointsourcelist, maxstitch_distance,abs_offset)
return return_point_list, return_point_source_list
def replace_forbidden_points(line, result_list, forbidden_point_list_indices, abs_offset):
current_index_shift = 0 #since we add and remove points in the result_list, we need to adjust the indices stored in forbidden_point_list_indices
# since we add and remove points in the result_list, we need to adjust the indices stored in forbidden_point_list_indices
current_index_shift = 0
for index in forbidden_point_list_indices:
#if abs(result_list[index][0].point.coords[0][0]-40.7) < 0.2 and abs(result_list[index][0].point.coords[0][1]-1.3)< 0.2:
# if abs(result_list[index][0].point.coords[0][0]-40.7) < 0.2 and abs(result_list[index][0].point.coords[0][1]-1.3)< 0.2:
# print("GEFUNDEN")
index+=current_index_shift
distance_left = result_list[index][0].point.distance(result_list[index-1][0].point)/2.0
distance_right = result_list[index][0].point.distance(result_list[(index+1)%len(result_list)][0].point)/2.0
index += current_index_shift
distance_left = result_list[index][0].point.distance(
result_list[index-1][0].point)/2.0
distance_right = result_list[index][0].point.distance(
result_list[(index+1) % len(result_list)][0].point)/2.0
while distance_left > constants.point_spacing_to_be_considered_equal and distance_right > constants.point_spacing_to_be_considered_equal:
new_point_left_proj = result_list[index][1]-distance_left
if new_point_left_proj < 0:
new_point_left_proj += line.length
new_point_right_proj = result_list[index][1]+distance_right
if new_point_right_proj > line.length:
new_point_right_proj-=line.length
new_point_right_proj -= line.length
point_left = line.interpolate(new_point_left_proj)
point_right = line.interpolate(new_point_right_proj)
forbidden_point_distance = result_list[index][0].point.distance(LineString([point_left, point_right]))
forbidden_point_distance = result_list[index][0].point.distance(
LineString([point_left, point_right]))
if forbidden_point_distance < constants.factor_offset_remove_dense_points*abs_offset:
del result_list[index]
result_list.insert(index, (PointTransfer.projected_point_tuple(point=point_right, point_source=\
PointSource.REPLACED_FORBIDDEN_POINT),new_point_right_proj))
result_list.insert(index, (PointTransfer.projected_point_tuple(point=point_left, point_source=\
PointSource.REPLACED_FORBIDDEN_POINT),new_point_left_proj))
current_index_shift+=1
result_list.insert(index, (PointTransfer.projected_point_tuple(
point=point_right, point_source=PointSource.REPLACED_FORBIDDEN_POINT), new_point_right_proj))
result_list.insert(index, (PointTransfer.projected_point_tuple(
point=point_left, point_source=PointSource.REPLACED_FORBIDDEN_POINT), new_point_left_proj))
current_index_shift += 1
break
else:
distance_left/=2.0
distance_right/=2.0
distance_left /= 2.0
distance_right /= 2.0
return result_list
if __name__ == "__main__":
line = LineString([(0,0), (1,0), (2,1),(3,0),(4,0)])
line = LineString([(0, 0), (1, 0), (2, 1), (3, 0), (4, 0)])
print(calculate_line_angles(line)*180.0/math.pi)

Wyświetl plik

@ -1,4 +1,4 @@
from shapely.geometry import Point, MultiPoint
from shapely.geometry import Point, MultiPoint
from shapely.geometry.polygon import LineString, LinearRing
from collections import namedtuple
from shapely.ops import nearest_points
@ -6,11 +6,14 @@ import math
from ..stitches import constants
from ..stitches import LineStringSampling
projected_point_tuple = namedtuple('projected_point_tuple', ['point', 'point_source'])
projected_point_tuple = namedtuple(
'projected_point_tuple', ['point', 'point_source'])
# Calculated the nearest interserction point of "bisectorline" with the coordinates of child (child.val).
# It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
# intersection was found.
#Calculated the nearest interserction point of "bisectorline" with the coordinates of child (child.val).
#It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
#intersection was found.
def calc_transferred_point(bisectorline, child):
result = bisectorline.intersection(child.val)
if result.is_empty:
@ -24,37 +27,44 @@ def calc_transferred_point(bisectorline, child):
resultlist = list(result)
desired_point = resultlist[0]
if len(resultlist) > 1:
desired_point = nearest_points(result, Point(bisectorline.coords[0]))[0]
desired_point = nearest_points(
result, Point(bisectorline.coords[0]))[0]
priority = child.val.project(desired_point)
point = desired_point
return point, priority
#Takes the current tree item and its rastered points (to_transfer_points) and transfers these points to its parent, siblings and childs
# To do so it calculates the current normal and determines its intersection with the neighbors which gives the transferred points.
#Input:
#-treenode: Tree node whose points stored in "to_transfer_points" shall be transferred to its neighbors.
#-used_offset: The used offset when the curves where offsetted
#-offset_by_half: True if the transferred points shall be interlaced with respect to the points in "to_transfer_points"
#-max_stitching_distance: The maximum allowed stitch distance between two points
#-to_transfer_points: List of points belonging to treenode which shall be transferred - it is assumed that to_transfer_points can be handled as closed ring
#-to_transfer_points_origin: The origin tag of each point in to_transfer_points
#-overnext_neighbor: Transfer the points to the overnext neighbor (gives a more stable interlacing)
#-transfer_forbidden_points: Only allowed for interlacing (offset_by_half): Might be used to transfer points unshifted as forbidden points to the neighbor to avoid a point placing there
#-transfer_to_parent: If True, points will be transferred to the parent
#-transfer_to_sibling: If True, points will be transferred to the siblings
#-transfer_to_child: If True, points will be transferred to the childs
#Output:
#-Fills the attribute "transferred_point_priority_deque" of the siblings and parent in the tree datastructure. An item of the deque
#is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
#index of point_origin is the index of the point in the neighboring line
def transfer_points_to_surrounding(treenode, used_offset, offset_by_half, max_stitching_distance, to_transfer_points, to_transfer_points_origin=[],
overnext_neighbor = False, transfer_forbidden_points = False, transfer_to_parent=True, transfer_to_sibling=True, transfer_to_child=True):
def transfer_points_to_surrounding(treenode, used_offset, offset_by_half, to_transfer_points, to_transfer_points_origin=[],
overnext_neighbor=False, transfer_forbidden_points=False,
transfer_to_parent=True, transfer_to_sibling=True, transfer_to_child=True):
"""
Takes the current tree item and its rastered points (to_transfer_points) and transfers these points to its parent, siblings and childs
To do so it calculates the current normal and determines its intersection with the neighbors which gives the transferred points.
Input:
-treenode: Tree node whose points stored in "to_transfer_points" shall be transferred to its neighbors.
-used_offset: The used offset when the curves where offsetted
-offset_by_half: True if the transferred points shall be interlaced with respect to the points in "to_transfer_points"
-to_transfer_points: List of points belonging to treenode which shall be transferred - it is assumed that to_transfer_points
can be handled as closed ring
-to_transfer_points_origin: The origin tag of each point in to_transfer_points
-overnext_neighbor: Transfer the points to the overnext neighbor (gives a more stable interlacing)
-transfer_forbidden_points: Only allowed for interlacing (offset_by_half): Might be used to transfer points unshifted as
forbidden points to the neighbor to avoid a point placing there
-transfer_to_parent: If True, points will be transferred to the parent
-transfer_to_sibling: If True, points will be transferred to the siblings
-transfer_to_child: If True, points will be transferred to the childs
Output:
-Fills the attribute "transferred_point_priority_deque" of the siblings and parent in the tree datastructure. An item of the deque
is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
index of point_origin is the index of the point in the neighboring line
"""
assert(len(to_transfer_points)==len(to_transfer_points_origin) or len(to_transfer_points_origin) == 0)
assert(len(to_transfer_points) == len(to_transfer_points_origin)
or len(to_transfer_points_origin) == 0)
assert((overnext_neighbor and not offset_by_half) or not overnext_neighbor)
assert(not transfer_forbidden_points or transfer_forbidden_points and (offset_by_half or not offset_by_half and overnext_neighbor))
assert(not transfer_forbidden_points or transfer_forbidden_points and (
offset_by_half or not offset_by_half and overnext_neighbor))
if len(to_transfer_points) == 0:
return
@ -71,37 +81,37 @@ def transfer_points_to_surrounding(treenode, used_offset, offset_by_half, max_st
if transfer_to_child:
for child in childs_tuple:
if child.already_rastered == False:
if not child.already_rastered:
if not overnext_neighbor:
child_list.append(child)
if transfer_forbidden_points:
child_list_forbidden.append(child)
if overnext_neighbor:
for subchild in child.children:
if subchild.already_rastered == False:
if not subchild.already_rastered:
child_list.append(subchild)
if transfer_to_sibling:
for sibling in siblings_tuple:
if sibling.already_rastered == False:
if not sibling.already_rastered:
if not overnext_neighbor:
neighbor_list.append(sibling)
if transfer_forbidden_points:
neighbor_list_forbidden.append(sibling)
if overnext_neighbor:
for subchild in sibling.children:
if subchild.already_rastered == False:
if not subchild.already_rastered:
neighbor_list.append(subchild)
if transfer_to_parent and treenode.parent != None:
if treenode.parent.already_rastered == False:
if transfer_to_parent and treenode.parent is not None:
if not treenode.parent.already_rastered:
if not overnext_neighbor:
neighbor_list.append(treenode.parent)
neighbor_list.append(treenode.parent)
if transfer_forbidden_points:
neighbor_list_forbidden.append(treenode.parent)
neighbor_list_forbidden.append(treenode.parent)
if overnext_neighbor:
if treenode.parent.parent != None:
if treenode.parent.parent.already_rastered == False:
if treenode.parent.parent is not None:
if not treenode.parent.parent.already_rastered:
neighbor_list.append(treenode.parent.parent)
if not neighbor_list and not child_list:
@ -126,19 +136,20 @@ def transfer_points_to_surrounding(treenode, used_offset, offset_by_half, max_st
closed_line = LinearRing(to_transfer_points)
bisectorline_length = abs(used_offset) * \
constants.transfer_point_distance_factor*(2.0 if overnext_neighbor else 1.0)
constants.transfer_point_distance_factor * \
(2.0 if overnext_neighbor else 1.0)
bisectorline_length_forbidden_points = abs(used_offset) * \
constants.transfer_point_distance_factor
linesign_child = math.copysign(1, used_offset)
i = 0
currentDistance = 0
while i < len(point_list):
assert(point_source_list[i] != LineStringSampling.PointSource.ENTER_LEAVING_POINT)
#if abs(point_list[i].coords[0][0]-47) < 0.3 and abs(point_list[i].coords[0][1]-4.5) < 0.3:
assert(point_source_list[i] !=
LineStringSampling.PointSource.ENTER_LEAVING_POINT)
# if abs(point_list[i].coords[0][0]-47) < 0.3 and abs(point_list[i].coords[0][1]-4.5) < 0.3:
# print("HIIIIIIIIIIIERRR")
# We create a bisecting line through the current point
@ -152,7 +163,6 @@ def transfer_points_to_surrounding(treenode, used_offset, offset_by_half, max_st
normalized_vector_prev_x /= prev_spacing
normalized_vector_prev_y /= prev_spacing
normalized_vector_next_x = normalized_vector_next_y = 0
next_spacing = 0
while True:
@ -187,13 +197,15 @@ def transfer_points_to_surrounding(treenode, used_offset, offset_by_half, max_st
vecy = -linesign_child*bisectorline_length*normalized_vector_next_x
if transfer_forbidden_points:
vecx_forbidden_point = linesign_child*bisectorline_length_forbidden_points*normalized_vector_next_y
vecy_forbidden_point = -linesign_child*bisectorline_length_forbidden_points*normalized_vector_next_x
vecx_forbidden_point = linesign_child * \
bisectorline_length_forbidden_points*normalized_vector_next_y
vecy_forbidden_point = -linesign_child * \
bisectorline_length_forbidden_points*normalized_vector_next_x
else:
vecx *= bisectorline_length/vec_length
vecy *= bisectorline_length/vec_length
if (vecx*normalized_vector_next_y-vecy * normalized_vector_next_x)*linesign_child < 0:
vecx = -vecx
vecy = -vecy
@ -212,55 +224,66 @@ def transfer_points_to_surrounding(treenode, used_offset, offset_by_half, max_st
originPoint = closed_line.interpolate(off)
bisectorline_child = LineString([(originPoint.coords[0][0],
originPoint.coords[0][1]),
(originPoint.coords[0][0]+vecx,
originPoint.coords[0][1]+vecy)])
originPoint.coords[0][1]),
(originPoint.coords[0][0]+vecx,
originPoint.coords[0][1]+vecy)])
bisectorline_neighbor = LineString([(originPoint.coords[0][0],
originPoint.coords[0][1]),
(originPoint.coords[0][0]-vecx,
originPoint.coords[0][1]-vecy)])
originPoint.coords[0][1]),
(originPoint.coords[0][0]-vecx,
originPoint.coords[0][1]-vecy)])
bisectorline_forbidden_point_child = LineString([(originPoint_forbidden_point.coords[0][0],
originPoint_forbidden_point.coords[0][1]),
(originPoint_forbidden_point.coords[0][0]+vecx_forbidden_point,
originPoint_forbidden_point.coords[0][1]+vecy_forbidden_point)])
originPoint_forbidden_point.coords[0][1]),
(originPoint_forbidden_point.coords[0][0]+vecx_forbidden_point,
originPoint_forbidden_point.coords[0][1]+vecy_forbidden_point)])
bisectorline_forbidden_point_neighbor = LineString([(originPoint_forbidden_point.coords[0][0],
originPoint_forbidden_point.coords[0][1]),
(originPoint_forbidden_point.coords[0][0]-vecx_forbidden_point,
originPoint_forbidden_point.coords[0][1]-vecy_forbidden_point)])
originPoint_forbidden_point.coords[0][1]),
(originPoint_forbidden_point.coords[0][0]-vecx_forbidden_point,
originPoint_forbidden_point.coords[0][1]-vecy_forbidden_point)])
for child in child_list:
point, priority = calc_transferred_point(bisectorline_child,child)
if point==None:
point, priority = calc_transferred_point(bisectorline_child, child)
if point is None:
continue
child.transferred_point_priority_deque.insert(projected_point_tuple(point = point, point_source=LineStringSampling.PointSource.OVERNEXT if overnext_neighbor else LineStringSampling.PointSource.DIRECT), priority)
child.transferred_point_priority_deque.insert(projected_point_tuple(
point=point, point_source=LineStringSampling.PointSource.OVERNEXT if overnext_neighbor
else LineStringSampling.PointSource.DIRECT), priority)
for child in child_list_forbidden:
point, priority = calc_transferred_point(bisectorline_forbidden_point_child,child)
if point == None:
point, priority = calc_transferred_point(
bisectorline_forbidden_point_child, child)
if point is None:
continue
child.transferred_point_priority_deque.insert(projected_point_tuple(point=point, point_source=LineStringSampling.PointSource.FORBIDDEN_POINT), priority)
child.transferred_point_priority_deque.insert(projected_point_tuple(
point=point, point_source=LineStringSampling.PointSource.FORBIDDEN_POINT), priority)
for neighbor in neighbor_list:
point, priority = calc_transferred_point(bisectorline_neighbor,neighbor)
if point==None:
point, priority = calc_transferred_point(
bisectorline_neighbor, neighbor)
if point is None:
continue
neighbor.transferred_point_priority_deque.insert(projected_point_tuple(point = point, point_source=LineStringSampling.PointSource.OVERNEXT if overnext_neighbor else LineStringSampling.PointSource.DIRECT), priority)
neighbor.transferred_point_priority_deque.insert(projected_point_tuple(
point=point, point_source=LineStringSampling.PointSource.OVERNEXT if overnext_neighbor
else LineStringSampling.PointSource.DIRECT), priority)
for neighbor in neighbor_list_forbidden:
point, priority = calc_transferred_point(bisectorline_forbidden_point_neighbor,neighbor)
if point == None:
point, priority = calc_transferred_point(
bisectorline_forbidden_point_neighbor, neighbor)
if point is None:
continue
neighbor.transferred_point_priority_deque.insert(projected_point_tuple(point=point, point_source=LineStringSampling.PointSource.FORBIDDEN_POINT), priority)
neighbor.transferred_point_priority_deque.insert(projected_point_tuple(
point=point, point_source=LineStringSampling.PointSource.FORBIDDEN_POINT), priority)
i += 1
currentDistance += next_spacing
assert(len(point_list) == len(point_source_list))
#Calculated the nearest interserction point of "bisectorline" with the coordinates of child.
#It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
#intersection was found.
# Calculated the nearest interserction point of "bisectorline" with the coordinates of child.
# It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
# intersection was found.
def calc_transferred_point_graph(bisectorline, edge_geometry):
result = bisectorline.intersection(edge_geometry)
if result.is_empty:
@ -274,41 +297,44 @@ def calc_transferred_point_graph(bisectorline, edge_geometry):
resultlist = list(result)
desired_point = resultlist[0]
if len(resultlist) > 1:
desired_point = nearest_points(result, Point(bisectorline.coords[0]))[0]
desired_point = nearest_points(
result, Point(bisectorline.coords[0]))[0]
priority = edge_geometry.project(desired_point)
point = desired_point
return point, priority
#Takes the current tree item and its rastered points (to_transfer_points) and transfers these points to its parent, siblings and childs
# To do so it calculates the current normal and determines its intersection with the neighbors which gives the transferred points.
#Input:
#-treenode: Tree node whose points stored in "to_transfer_points" shall be transferred to its neighbors.
#-used_offset: The used offset when the curves where offsetted
#-offset_by_half: True if the transferred points shall be interlaced with respect to the points in "to_transfer_points"
#-max_stitching_distance: The maximum allowed stitch distance between two points
#-to_transfer_points: List of points belonging to treenode which shall be transferred - it is assumed that to_transfer_points can be handled as closed ring
#-to_transfer_points_origin: The origin tag of each point in to_transfer_points
#-overnext_neighbor: Transfer the points to the overnext neighbor (gives a more stable interlacing)
#-transfer_forbidden_points: Only allowed for interlacing (offset_by_half): Might be used to transfer points unshifted as forbidden points to the neighbor to avoid a point placing there
#-transfer_to_parent: If True, points will be transferred to the parent
#-transfer_to_sibling: If True, points will be transferred to the siblings
#-transfer_to_child: If True, points will be transferred to the childs
#Output:
#-Fills the attribute "transferred_point_priority_deque" of the siblings and parent in the tree datastructure. An item of the deque
#is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
#index of point_origin is the index of the point in the neighboring line
def transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, used_offset, offset_by_half, to_transfer_points,
overnext_neighbor = False, transfer_forbidden_points = False, transfer_to_previous=True, transfer_to_next=True):
overnext_neighbor=False, transfer_forbidden_points=False, transfer_to_previous=True, transfer_to_next=True):
"""
Takes the current graph edge and its rastered points (to_transfer_points) and transfers these points to its previous and next edges (if selected)
To do so it calculates the current normal and determines its intersection with the neighbors which gives the transferred points.
Input:
-fill_stitch_graph: Graph data structure of the stitching lines
-current_edge: Current graph edge whose neighbors in fill_stitch_graph shall be considered
-used_offset: The used offset when the curves where offsetted
-offset_by_half: True if the transferred points shall be interlaced with respect to the points in "to_transfer_points"
-to_transfer_points: List of points belonging to treenode which shall be transferred - it is assumed that to_transfer_points
can be handled as closed ring
-overnext_neighbor: Transfer the points to the overnext neighbor (gives a more stable interlacing)
-transfer_forbidden_points: Only allowed for interlacing (offset_by_half): Might be used to transfer points unshifted as
forbidden points to the neighbor to avoid a point placing there
-transfer_to_previous: If True, points will be transferred to the previous edge in the graph
-transfer_to_next: If True, points will be transferred to the next edge in the graph
Output:
-Fills the attribute "transferred_point_priority_deque" of the next/previous edges. An item of the deque
is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
index of point_origin is the index of the point in the neighboring line
"""
assert((overnext_neighbor and not offset_by_half) or not overnext_neighbor)
assert(not transfer_forbidden_points or transfer_forbidden_points and (offset_by_half or not offset_by_half and overnext_neighbor))
assert(not transfer_forbidden_points or transfer_forbidden_points and (
offset_by_half or not offset_by_half and overnext_neighbor))
if len(to_transfer_points) == 0:
return
# Take only neighbors which have not rastered before
# We need to distinguish between childs (project towards inner) and parent/siblings (project towards outer)
previous_edge_list = []
@ -319,7 +345,8 @@ def transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, used_o
if transfer_to_previous:
previous_neighbors_tuples = current_edge['previous_neighbors']
for neighbor in previous_neighbors_tuples:
neighbor_edge = fill_stitch_graph[neighbor[0]][neighbor[-1]]['segment']
neighbor_edge = fill_stitch_graph[neighbor[0]
][neighbor[-1]]['segment']
if not neighbor_edge['already_rastered']:
if not overnext_neighbor:
previous_edge_list.append(neighbor_edge)
@ -328,14 +355,16 @@ def transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, used_o
if overnext_neighbor:
overnext_previous_neighbors_tuples = neighbor_edge['previous_neighbors']
for overnext_neighbor in overnext_previous_neighbors_tuples:
overnext_neighbor_edge = fill_stitch_graph[overnext_neighbor[0]][overnext_neighbor[-1]]['segment']
overnext_neighbor_edge = fill_stitch_graph[overnext_neighbor[0]
][overnext_neighbor[-1]]['segment']
if not overnext_neighbor_edge['already_rastered']:
previous_edge_list.append(overnext_neighbor_edge)
if transfer_to_next:
next_neighbors_tuples = current_edge['next_neighbors']
for neighbor in next_neighbors_tuples:
neighbor_edge = fill_stitch_graph[neighbor[0]][neighbor[-1]]['segment']
neighbor_edge = fill_stitch_graph[neighbor[0]
][neighbor[-1]]['segment']
if not neighbor_edge['already_rastered']:
if not overnext_neighbor:
next_edge_list.append(neighbor_edge)
@ -344,11 +373,11 @@ def transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, used_o
if overnext_neighbor:
overnext_next_neighbors_tuples = neighbor_edge['next_neighbors']
for overnext_neighbor in overnext_next_neighbors_tuples:
overnext_neighbor_edge = fill_stitch_graph[overnext_neighbor[0]][overnext_neighbor[-1]]['segment']
overnext_neighbor_edge = fill_stitch_graph[overnext_neighbor[0]
][overnext_neighbor[-1]]['segment']
if not overnext_neighbor_edge['already_rastered']:
next_edge_list.append(overnext_neighbor_edge)
if not previous_edge_list and not next_edge_list:
return
@ -357,19 +386,19 @@ def transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, used_o
line = LineString(to_transfer_points)
bisectorline_length = abs(used_offset) * \
constants.transfer_point_distance_factor*(2.0 if overnext_neighbor else 1.0)
constants.transfer_point_distance_factor * \
(2.0 if overnext_neighbor else 1.0)
bisectorline_length_forbidden_points = abs(used_offset) * \
constants.transfer_point_distance_factor
linesign_child = math.copysign(1, used_offset)
i = 0
currentDistance = 0
while i < len(point_list):
#if abs(point_list[i].coords[0][0]-47) < 0.3 and abs(point_list[i].coords[0][1]-4.5) < 0.3:
# if abs(point_list[i].coords[0][0]-47) < 0.3 and abs(point_list[i].coords[0][1]-4.5) < 0.3:
# print("HIIIIIIIIIIIERRR")
# We create a bisecting line through the current point
@ -383,7 +412,6 @@ def transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, used_o
normalized_vector_prev_x /= prev_spacing
normalized_vector_prev_y /= prev_spacing
normalized_vector_next_x = normalized_vector_next_y = 0
next_spacing = 0
while True:
@ -416,13 +444,15 @@ def transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, used_o
vecy = -linesign_child*bisectorline_length*normalized_vector_next_x
if transfer_forbidden_points:
vecx_forbidden_point = linesign_child*bisectorline_length_forbidden_points*normalized_vector_next_y
vecy_forbidden_point = -linesign_child*bisectorline_length_forbidden_points*normalized_vector_next_x
vecx_forbidden_point = linesign_child * \
bisectorline_length_forbidden_points*normalized_vector_next_y
vecy_forbidden_point = -linesign_child * \
bisectorline_length_forbidden_points*normalized_vector_next_x
else:
vecx *= bisectorline_length/vec_length
vecy *= bisectorline_length/vec_length
if (vecx*normalized_vector_next_y-vecy * normalized_vector_next_x)*linesign_child < 0:
vecx = -vecx
vecy = -vecy
@ -446,22 +476,25 @@ def transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, used_o
originPoint.coords[0][1]+vecy)])
bisectorline_forbidden_point = LineString([(originPoint_forbidden_point.coords[0][0]-vecx_forbidden_point,
originPoint_forbidden_point.coords[0][1]-vecy_forbidden_point),
(originPoint_forbidden_point.coords[0][0]+vecx_forbidden_point,
originPoint_forbidden_point.coords[0][1]+vecy_forbidden_point)])
originPoint_forbidden_point.coords[0][1]-vecy_forbidden_point),
(originPoint_forbidden_point.coords[0][0]+vecx_forbidden_point,
originPoint_forbidden_point.coords[0][1]+vecy_forbidden_point)])
for edge in previous_edge_list+next_edge_list:
point, priority = calc_transferred_point_graph(bisectorline,edge['geometry'])
if point==None:
point, priority = calc_transferred_point_graph(
bisectorline, edge['geometry'])
if point is None:
continue
edge['projected_points'].insert(projected_point_tuple(point = point, point_source=LineStringSampling.PointSource.OVERNEXT if overnext_neighbor else LineStringSampling.PointSource.DIRECT), priority)
edge['projected_points'].insert(projected_point_tuple(
point=point, point_source=LineStringSampling.PointSource.OVERNEXT if overnext_neighbor
else LineStringSampling.PointSource.DIRECT), priority)
for edge_forbidden in previous_edge_list_forbidden+next_edge_list_forbidden:
point, priority = calc_transferred_point_graph(bisectorline_forbidden_point,edge_forbidden['geometry'])
if point == None:
point, priority = calc_transferred_point_graph(
bisectorline_forbidden_point, edge_forbidden['geometry'])
if point is None:
continue
edge_forbidden['projected_points'].insert(projected_point_tuple(point=point, point_source=LineStringSampling.PointSource.FORBIDDEN_POINT), priority)
edge_forbidden['projected_points'].insert(projected_point_tuple(
point=point, point_source=LineStringSampling.PointSource.FORBIDDEN_POINT), priority)
i += 1
currentDistance += next_spacing

Wyświetl plik

@ -1,6 +1,6 @@
from shapely.geometry.polygon import LinearRing, LineString
from shapely.geometry import Polygon, MultiLineString
from shapely.ops import polygonize
from shapely.ops import polygonize
from shapely.geometry import MultiPolygon
from anytree import AnyNode, PreOrderIter
from shapely.geometry.polygon import orient
@ -10,68 +10,90 @@ from ..stitches import ConnectAndSamplePattern
from ..stitches import constants
# Problem: When shapely offsets a LinearRing the start/end point might be handled wrongly since they are only treated as LineString.
# (See e.g. https://i.stack.imgur.com/vVh56.png as a problematic example)
# This method checks first whether the start/end point form a problematic edge with respect to the offset side. If it is not a problematic
# edge we can use the normal offset_routine. Otherwise we need to perform two offsets:
# -offset the ring
# -offset the start/end point + its two neighbors left and right
# Finally both offsets are merged together to get the correct offset of a LinearRing
def offset_linear_ring(ring, offset, side, resolution, join_style, mitre_limit):
"""
Solves following problem: When shapely offsets a LinearRing the
start/end point might be handled wrongly since they
are only treated as LineString.
(See e.g. https://i.stack.imgur.com/vVh56.png as a problematic example)
This method checks first whether the start/end point form a problematic
edge with respect to the offset side. If it is not a problematic
edge we can use the normal offset_routine. Otherwise we need to
perform two offsets:
-offset the ring
-offset the start/end point + its two neighbors left and right
Finally both offsets are merged together to get the correct
offset of a LinearRing
"""
coords = ring.coords[:]
# check whether edge at index 0 is concave or convex. Only for concave edges we need to spend additional effort
# check whether edge at index 0 is concave or convex. Only for
# concave edges we need to spend additional effort
dx_seg1 = dy_seg1 = 0
if coords[0] != coords[-1]:
dx_seg1 = coords[0][0]-coords[-1][0]
dy_seg1 = coords[0][1]-coords[-1][1]
dx_seg1 = coords[0][0] - coords[-1][0]
dy_seg1 = coords[0][1] - coords[-1][1]
else:
dx_seg1 = coords[0][0]-coords[-2][0]
dy_seg1 = coords[0][1]-coords[-2][1]
dx_seg2 = coords[1][0]-coords[0][0]
dy_seg2 = coords[1][1]-coords[0][1]
dx_seg1 = coords[0][0] - coords[-2][0]
dy_seg1 = coords[0][1] - coords[-2][1]
dx_seg2 = coords[1][0] - coords[0][0]
dy_seg2 = coords[1][1] - coords[0][1]
# use cross product:
crossvalue = dx_seg1*dy_seg2-dy_seg1*dx_seg2
crossvalue = dx_seg1 * dy_seg2 - dy_seg1 * dx_seg2
sidesign = 1
if side == 'left':
if side == "left":
sidesign = -1
# We do not need to take care of the joint n-0 since we offset along a concave edge:
if sidesign*offset*crossvalue <= 0:
# We do not need to take care of the joint n-0 since we
# offset along a concave edge:
if sidesign * offset * crossvalue <= 0:
return ring.parallel_offset(offset, side, resolution, join_style, mitre_limit)
# We offset along a convex edge so we offset the joint n-0 separately:
if coords[0] != coords[-1]:
coords.append(coords[0])
offset_ring1 = ring.parallel_offset(
offset, side, resolution, join_style, mitre_limit)
offset, side, resolution, join_style, mitre_limit
)
offset_ring2 = LineString((coords[-2], coords[0], coords[1])).parallel_offset(
offset, side, resolution, join_style, mitre_limit)
offset, side, resolution, join_style, mitre_limit
)
# Next we need to merge the results:
if offset_ring1.geom_type == 'LineString':
return LinearRing(offset_ring2.coords[:]+offset_ring1.coords[1:-1])
if offset_ring1.geom_type == "LineString":
return LinearRing(offset_ring2.coords[:] + offset_ring1.coords[1:-1])
else:
# We have more than one resulting LineString for offset of the geometry (ring) = offset_ring1.
# Hence we need to find the LineString which belongs to the offset of element 0 in coords =offset_ring2
# We have more than one resulting LineString for offset of
# the geometry (ring) = offset_ring1.
# Hence we need to find the LineString which belongs to the
# offset of element 0 in coords =offset_ring2
# in order to add offset_ring2 geometry to it:
result_list = []
thresh = constants.offset_factor_for_adjacent_geometry*abs(offset)
thresh = constants.offset_factor_for_adjacent_geometry * abs(offset)
for offsets in offset_ring1:
if(abs(offsets.coords[0][0]-coords[0][0]) < thresh and abs(offsets.coords[0][1]-coords[0][1]) < thresh):
result_list.append(LinearRing(
offset_ring2.coords[:]+offsets.coords[1:-1]))
if (
abs(offsets.coords[0][0] - coords[0][0]) < thresh
and abs(offsets.coords[0][1] - coords[0][1]) < thresh
):
result_list.append(
LinearRing(offset_ring2.coords[:] + offsets.coords[1:-1])
)
else:
result_list.append(LinearRing(offsets))
return MultiLineString(result_list)
# Removes all geometries which do not form a "valid" LinearRing (meaning a ring which does not form a straight line)
def take_only_valid_linear_rings(rings):
if(rings.geom_type == 'MultiLineString'):
"""
Removes all geometries which do not form a "valid" LinearRing
(meaning a ring which does not form a straight line)
"""
if rings.geom_type == "MultiLineString":
new_list = []
for ring in rings:
if len(ring.coords) > 3 or (len(ring.coords) == 3 and ring.coords[0] != ring.coords[-1]):
if len(ring.coords) > 3 or (
len(ring.coords) == 3 and ring.coords[0] != ring.coords[-1]
):
new_list.append(ring)
if len(new_list) == 1:
return LinearRing(new_list[0])
@ -86,138 +108,184 @@ def take_only_valid_linear_rings(rings):
return rings
#Since naturally holes have the opposite point ordering than non-holes we make
#all lines within the tree "root" uniform (having all the same ordering direction)
def make_tree_uniform_ccw(root):
"""
Since naturally holes have the opposite point ordering than non-holes we
make all lines within the tree "root" uniform (having all the same
ordering direction)
"""
for node in PreOrderIter(root):
if(node.id == 'hole'):
if node.id == "hole":
node.val.coords = list(node.val.coords)[::-1]
#Used to define which stitching strategy shall be used
# Used to define which stitching strategy shall be used
class StitchingStrategy(IntEnum):
CLOSEST_POINT = 0
INNER_TO_OUTER = 1
# Takes a polygon (which can have holes) as input and creates offsetted versions until the polygon is filled with these smaller offsets.
# These created geometries are afterwards connected to each other and resampled with a maximum stitch_distance.
# The return value is a LineString which should cover the full polygon.
#Input:
#-poly: The shapely polygon which can have holes
#-offset: The used offset for the curves
#-join_style: Join style for the offset - can be round, mitered or bevel (https://shapely.readthedocs.io/en/stable/manual.html#shapely.geometry.JOIN_STYLE)
#For examples look at https://shapely.readthedocs.io/en/stable/_images/parallel_offset.png
#-stitch_distance maximum allowed stitch distance between two points
#-offset_by_half: True if the points shall be interlaced
#-strategy: According to StitchingStrategy you can select between different strategies for the connection between parent and childs
#Output:
#-List of point coordinate tuples
#-Tag (origin) of each point to analyze why a point was placed at this position
def offset_poly(poly, offset, join_style, stitch_distance, offset_by_half, strategy, starting_point):
def offset_poly(
poly, offset, join_style, stitch_distance, offset_by_half, strategy, starting_point
):
"""
Takes a polygon (which can have holes) as input and creates offsetted
versions until the polygon is filled with these smaller offsets.
These created geometries are afterwards connected to each other and
resampled with a maximum stitch_distance.
The return value is a LineString which should cover the full polygon.
Input:
-poly: The shapely polygon which can have holes
-offset: The used offset for the curves
-join_style: Join style for the offset - can be round, mitered or bevel
(https://shapely.readthedocs.io/en/stable/manual.html#shapely.geometry.JOIN_STYLE)
For examples look at
https://shapely.readthedocs.io/en/stable/_images/parallel_offset.png
-stitch_distance maximum allowed stitch distance between two points
-offset_by_half: True if the points shall be interlaced
-strategy: According to StitchingStrategy enum class you can select between
different strategies for the connection between parent and childs
-starting_point: Defines the starting point for the stitching
Output:
-List of point coordinate tuples
-Tag (origin) of each point to analyze why a point was placed
at this position
"""
ordered_poly = orient(poly, -1)
ordered_poly = ordered_poly.simplify(
constants.simplification_threshold, False)
root = AnyNode(id="node", val=ordered_poly.exterior, already_rastered=False, transferred_point_priority_deque=DEPQ(
iterable=None, maxlen=None))
ordered_poly = ordered_poly.simplify(constants.simplification_threshold, False)
root = AnyNode(
id="node",
val=ordered_poly.exterior,
already_rastered=False,
transferred_point_priority_deque=DEPQ(iterable=None, maxlen=None),
)
active_polys = [root]
active_holes = [[]]
for holes in ordered_poly.interiors:
#print("hole: - is ccw: ", LinearRing(holes).is_ccw)
active_holes[0].append(
AnyNode(id="hole", val=holes, already_rastered=False, transferred_point_priority_deque=DEPQ(
iterable=None, maxlen=None)))
AnyNode(
id="hole",
val=holes,
already_rastered=False,
transferred_point_priority_deque=DEPQ(iterable=None, maxlen=None),
)
)
# counter = 0
while len(active_polys) > 0: # and counter < 20:
# counter += 1
# print("New iter")
while len(active_polys) > 0:
current_poly = active_polys.pop()
current_holes = active_holes.pop()
poly_inners = []
# outer = current_poly.val.parallel_offset(offset,'left', 5, join_style, 10)
outer = offset_linear_ring(current_poly.val, offset, 'left', 5, join_style, 10)
outer = offset_linear_ring(
current_poly.val,
offset,
"left",
resolution=5,
joint_style=join_style,
mitre_limit=10,
)
outer = outer.simplify(constants.simplification_threshold, False)
outer = take_only_valid_linear_rings(outer)
for j in range(len(current_holes)):
# inner = closeLinearRing(current_holes[j].val,offset/2.0).parallel_offset(offset,'left', 5, join_style, 10)
inner = offset_linear_ring(
current_holes[j].val, offset, 'left', 5, join_style, 10)
current_holes[j].val,
offset,
"left",
resolution=5,
joint_style=join_style,
mitre_limit=10,
)
inner = inner.simplify(constants.simplification_threshold, False)
inner = take_only_valid_linear_rings(inner)
if not inner.is_empty:
poly_inners.append(Polygon(inner))
if not outer.is_empty:
if len(poly_inners) == 0:
if outer.geom_type == 'LineString':
if outer.geom_type == "LineString":
result = Polygon(outer)
else:
result = MultiPolygon(polygonize(outer))
else:
if outer.geom_type == 'LineString':
result = Polygon(outer).difference(
MultiPolygon(poly_inners))
if outer.geom_type == "LineString":
result = Polygon(outer).difference(MultiPolygon(poly_inners))
else:
result = MultiPolygon(outer).difference(
MultiPolygon(poly_inners))
result = MultiPolygon(outer).difference(MultiPolygon(poly_inners))
if not result.is_empty and result.area > offset*offset/10:
if not result.is_empty and result.area > offset * offset / 10:
result_list = []
if result.geom_type == 'Polygon':
if result.geom_type == "Polygon":
result_list = [result]
else:
result_list = list(result)
# print("New result_list: ", len(result_list))
for polygon in result_list:
polygon = orient(polygon, -1)
if polygon.area < offset*offset/10:
if polygon.area < offset * offset / 10:
continue
polygon = polygon.simplify(constants.simplification_threshold, False)
polygon = polygon.simplify(
constants.simplification_threshold, False
)
poly_coords = polygon.exterior
# if polygon.exterior.is_ccw:
# hole.coords = list(hole.coords)[::-1]
#poly_coords = polygon.exterior.simplify(constants.simplification_threshold, False)
poly_coords = take_only_valid_linear_rings(poly_coords)
if poly_coords.is_empty:
continue
#print("node: - is ccw: ", LinearRing(poly_coords).is_ccw)
# if(LinearRing(poly_coords).is_ccw):
# print("Fehler!")
node = AnyNode(id="node", parent=current_poly,
val=poly_coords, already_rastered=False, transferred_point_priority_deque=DEPQ(
iterable=None, maxlen=None))
node = AnyNode(
id="node",
parent=current_poly,
val=poly_coords,
already_rastered=False,
transferred_point_priority_deque=DEPQ(
iterable=None, maxlen=None
),
)
active_polys.append(node)
hole_node_list = []
for hole in polygon.interiors:
hole_node = AnyNode(
id="hole", val=hole, already_rastered=False, transferred_point_priority_deque=DEPQ(
iterable=None, maxlen=None))
id="hole",
val=hole,
already_rastered=False,
transferred_point_priority_deque=DEPQ(
iterable=None, maxlen=None
),
)
for previous_hole in current_holes:
if Polygon(hole).contains(Polygon(previous_hole.val)):
previous_hole.parent = hole_node
hole_node_list.append(hole_node)
active_holes.append(hole_node_list)
for previous_hole in current_holes: # if the previous holes are not contained in the new holes they have been merged with the outer polygon
if previous_hole.parent == None:
for previous_hole in current_holes:
# If the previous holes are not
# contained in the new holes they
# have been merged with the
# outer polygon
if previous_hole.parent is None:
previous_hole.parent = current_poly
#DebuggingMethods.drawPoly(root, 'r-')
# DebuggingMethods.drawPoly(root, 'r-')
make_tree_uniform_ccw(root)
# print(RenderTree(root))
if strategy == StitchingStrategy.CLOSEST_POINT:
connected_line, connected_line_origin = ConnectAndSamplePattern.connect_raster_tree_nearest_neighbor(
root, offset, stitch_distance, starting_point, offset_by_half)
(
connected_line,
connected_line_origin,
) = ConnectAndSamplePattern.connect_raster_tree_nearest_neighbor(
root, offset, stitch_distance, starting_point, offset_by_half
)
elif strategy == StitchingStrategy.INNER_TO_OUTER:
connected_line, connected_line_origin = ConnectAndSamplePattern.connect_raster_tree_from_inner_to_outer(
root, offset, stitch_distance, starting_point, offset_by_half)
(
connected_line,
connected_line_origin,
) = ConnectAndSamplePattern.connect_raster_tree_from_inner_to_outer(
root, offset, stitch_distance, starting_point, offset_by_half
)
else:
print("Invalid strategy!")
assert(0)
raise ValueError("Invalid stitching stratety!")
return connected_line, connected_line_origin

Wyświetl plik

@ -16,7 +16,6 @@ from depq import DEPQ
from ..debug import debug
from ..stitch_plan import Stitch
from ..svg import PIXELS_PER_MM
from ..utils import geometry
from ..utils.geometry import Point as InkstitchPoint
from ..utils.geometry import line_string_to_point_list
from .fill import intersect_region_with_grating, intersect_region_with_grating_line, stitch_row
@ -64,11 +63,12 @@ def auto_fill(shape,
ending_point=None,
underpath=True,
offset_by_half=True):
#offset_by_half only relevant for line != None; staggers only relevant for line == None!
# offset_by_half only relevant for line != None; staggers only relevant for line == None!
fill_stitch_graph = []
try:
fill_stitch_graph = build_fill_stitch_graph(shape, line, angle, row_spacing, end_row_spacing, starting_point, ending_point)
fill_stitch_graph = build_fill_stitch_graph(
shape, line, angle, row_spacing, end_row_spacing, starting_point, ending_point)
except ValueError:
# Small shapes will cause the graph to fail - min() arg is an empty sequence through insert node
return fallback(shape, running_stitch_length)
@ -76,10 +76,12 @@ def auto_fill(shape,
if not graph_is_valid(fill_stitch_graph, shape, max_stitch_length):
return fallback(shape, running_stitch_length)
travel_graph = build_travel_graph(fill_stitch_graph, shape, angle, underpath)
path = find_stitch_path(fill_stitch_graph, travel_graph, starting_point, ending_point)
travel_graph = build_travel_graph(
fill_stitch_graph, shape, angle, underpath)
path = find_stitch_path(
fill_stitch_graph, travel_graph, starting_point, ending_point)
result = path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing,
max_stitch_length, running_stitch_length, staggers, skip_last,line!=None,offset_by_half)
max_stitch_length, running_stitch_length, staggers, skip_last, line is not None, offset_by_half)
return result
@ -97,7 +99,8 @@ def which_outline(shape, coords):
point = shgeo.Point(*coords)
outlines = list(shape.boundary)
outline_indices = list(range(len(outlines)))
closest = min(outline_indices, key=lambda index: outlines[index].distance(point))
closest = min(outline_indices,
key=lambda index: outlines[index].distance(point))
return closest
@ -148,17 +151,18 @@ def build_fill_stitch_graph(shape, line, angle, row_spacing, end_row_spacing, st
debug.add_layer("auto-fill fill stitch")
if line == None:
if line is None:
# Convert the shape into a set of parallel line segments.
rows_of_segments = intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing)
rows_of_segments = intersect_region_with_grating(
shape, angle, row_spacing, end_row_spacing)
else:
rows_of_segments = intersect_region_with_grating_line(shape, line, row_spacing, end_row_spacing)
rows_of_segments = intersect_region_with_grating_line(
shape, line, row_spacing, end_row_spacing)
#segments = [segment for row in rows_of_segments for segment in row]
# segments = [segment for row in rows_of_segments for segment in row]
graph = networkx.MultiGraph()
for i in range(len(rows_of_segments)):
for segment in rows_of_segments[i]:
# First, add the grating segments as edges. We'll use the coordinates
@ -166,16 +170,18 @@ def build_fill_stitch_graph(shape, line, angle, row_spacing, end_row_spacing, st
# networkx allows us to label nodes with arbitrary data. We'll
# mark this one as a grating segment.
#graph.add_edge(*segment, key="segment", underpath_edges=[])
previous_neighbors_ = [(seg[0],seg[-1]) for seg in rows_of_segments[i-1] if i > 0]
next_neighbors_ = [(seg[0],seg[-1]) for seg in rows_of_segments[(i+1)% len(rows_of_segments)] if i < len(rows_of_segments)-1]
# graph.add_edge(*segment, key="segment", underpath_edges=[])
previous_neighbors_ = [(seg[0], seg[-1])
for seg in rows_of_segments[i-1] if i > 0]
next_neighbors_ = [(seg[0], seg[-1]) for seg in rows_of_segments[(i+1) %
len(rows_of_segments)] if i < len(rows_of_segments)-1]
graph.add_edge(segment[0],segment[-1], key="segment", underpath_edges=[],
geometry=shgeo.LineString(segment), previous_neighbors = previous_neighbors_, next_neighbors = next_neighbors_,
projected_points=DEPQ(iterable=None, maxlen=None), already_rastered=False)
graph.add_edge(segment[0], segment[-1], key="segment", underpath_edges=[],
geometry=shgeo.LineString(segment), previous_neighbors=previous_neighbors_, next_neighbors=next_neighbors_,
projected_points=DEPQ(iterable=None, maxlen=None), already_rastered=False)
#fill_stitch_graph[start][end]['segment']['underpath_edges'].append(edge)
# fill_stitch_graph[start][end]['segment']['underpath_edges'].append(edge)
tag_nodes_with_outline_and_projection(graph, shape, graph.nodes())
add_edges_between_outline_nodes(graph, duplicate_every_other=True)
@ -205,7 +211,8 @@ def insert_node(graph, shape, point):
if key == "outline":
edges.append(((start, end), data))
edge, data = min(edges, key=lambda edge_data: shgeo.LineString(edge_data[0]).distance(projected_point))
edge, data = min(edges, key=lambda edge_data: shgeo.LineString(
edge_data[0]).distance(projected_point))
graph.remove_edge(*edge, key="outline")
graph.add_edge(edge[0], node, key="outline", **data)
@ -218,7 +225,8 @@ def tag_nodes_with_outline_and_projection(graph, shape, nodes):
outline_index = which_outline(shape, node)
outline_projection = project(shape, node, outline_index)
graph.add_node(node, outline=outline_index, projection=outline_projection)
graph.add_node(node, outline=outline_index,
projection=outline_projection)
def add_boundary_travel_nodes(graph, shape):
@ -236,9 +244,11 @@ def add_boundary_travel_nodes(graph, shape):
# resolution. A pixel is around a quarter of a millimeter.
for i in range(1, int(length)):
subpoint = segment.interpolate(i)
graph.add_node((subpoint.x, subpoint.y), projection=outline.project(subpoint), outline=outline_index)
graph.add_node((subpoint.x, subpoint.y), projection=outline.project(
subpoint), outline=outline_index)
graph.add_node((point.x, point.y), projection=outline.project(point), outline=outline_index)
graph.add_node((point.x, point.y), projection=outline.project(
point), outline=outline_index)
prev = point
@ -253,7 +263,8 @@ def add_edges_between_outline_nodes(graph, duplicate_every_other=False):
outline.
"""
nodes = list(graph.nodes(data=True)) # returns a list of tuples: [(node, {data}), (node, {data}) ...]
# returns a list of tuples: [(node, {data}), (node, {data}) ...]
nodes = list(graph.nodes(data=True))
nodes.sort(key=lambda node: (node[1]['outline'], node[1]['projection']))
for outline_index, nodes in groupby(nodes, key=lambda node: node[1]['outline']):
@ -318,7 +329,8 @@ def build_travel_graph(fill_stitch_graph, shape, fill_stitch_angle, underpath):
graph.add_nodes_from(fill_stitch_graph.nodes(data=True))
if underpath:
boundary_points, travel_edges = build_travel_edges(shape, fill_stitch_angle)
boundary_points, travel_edges = build_travel_edges(
shape, fill_stitch_angle)
# This will ensure that a path traveling inside the shape can reach its
# target on the outline, which will be one of the points added above.
@ -349,7 +361,7 @@ def get_segments(graph):
for start, end, key, data in graph.edges(keys=True, data=True):
if key == 'segment':
segments.append(data["geometry"])
#segments.append(shgeo.LineString((start, end)))
# segments.append(shgeo.LineString((start, end)))
return segments
@ -371,7 +383,8 @@ def process_travel_edges(graph, fill_stitch_graph, shape, travel_edges):
# This makes the distance calculations below a bit faster. We're
# not looking for high precision anyway.
outline = shape.boundary.simplify(0.5 * PIXELS_PER_MM, preserve_topology=False)
outline = shape.boundary.simplify(
0.5 * PIXELS_PER_MM, preserve_topology=False)
for ls in travel_edges:
# In most cases, ls will be a simple line segment. If we're
@ -389,7 +402,8 @@ def process_travel_edges(graph, fill_stitch_graph, shape, travel_edges):
if segment.crosses(ls):
start = segment.coords[0]
end = segment.coords[-1]
fill_stitch_graph[start][end]['segment']['underpath_edges'].append(edge)
fill_stitch_graph[start][end]['segment']['underpath_edges'].append(
edge)
# The weight of a travel edge is the length of the line segment.
weight = p1.distance(p2)
@ -458,9 +472,12 @@ def build_travel_edges(shape, fill_angle):
else:
scale = 1.0
grating1 = travel_grating(shape, fill_angle + math.pi / 4, scale * 2 * PIXELS_PER_MM)
grating2 = travel_grating(shape, fill_angle - math.pi / 4, scale * 2 * PIXELS_PER_MM)
grating3 = travel_grating(shape, fill_angle - math.pi / 2, scale * math.sqrt(2) * PIXELS_PER_MM)
grating1 = travel_grating(
shape, fill_angle + math.pi / 4, scale * 2 * PIXELS_PER_MM)
grating2 = travel_grating(
shape, fill_angle - math.pi / 4, scale * 2 * PIXELS_PER_MM)
grating3 = travel_grating(
shape, fill_angle - math.pi / 2, scale * math.sqrt(2) * PIXELS_PER_MM)
debug.add_layer("auto-fill travel")
debug.log_line_strings(grating1, "grating1")
@ -471,10 +488,12 @@ def build_travel_edges(shape, fill_angle):
for ls in mls
for coord in ls.coords]
diagonal_edges = ensure_multi_line_string(grating1.symmetric_difference(grating2))
diagonal_edges = ensure_multi_line_string(
grating1.symmetric_difference(grating2))
# without this, floating point inaccuracies prevent the intersection points from lining up perfectly.
vertical_edges = ensure_multi_line_string(snap(grating3.difference(grating1), diagonal_edges, 0.005))
vertical_edges = ensure_multi_line_string(
snap(grating3.difference(grating1), diagonal_edges, 0.005))
return endpoints, chain(diagonal_edges, vertical_edges)
@ -536,7 +555,8 @@ def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None
last_vertex, last_key = current_vertex, current_key
vertex_stack.pop()
else:
ignore, next_vertex, next_key = pick_edge(graph.edges(current_vertex, keys=True))
ignore, next_vertex, next_key = pick_edge(
graph.edges(current_vertex, keys=True))
vertex_stack.append((next_vertex, next_key))
graph.remove_edge(current_vertex, next_vertex, next_key)
@ -565,7 +585,8 @@ def find_stitch_path(graph, travel_graph, starting_point=None, ending_point=None
# relevant in the case that the user specifies an underlay with an inset
# value, because the starting point (and possibly ending point) can be
# inside the shape.
outline_nodes = [node for node, outline in travel_graph.nodes(data="outline") if outline is not None]
outline_nodes = [node for node, outline in travel_graph.nodes(
data="outline") if outline is not None]
real_end = nearest_node(outline_nodes, ending_point)
path.append(PathEdge((ending_node, real_end), key="outline"))
@ -639,28 +660,31 @@ def travel(travel_graph, start, end, running_stitch_length, skip_last):
# stitch.
return stitches[1:]
def stitch_line(stitches, stitching_direction, geometry,projected_points, max_stitch_length,row_spacing,skip_last,offset_by_half):
#print(start_point)
#print(geometry[0])
#if stitching_direction == -1:
# geometry.coords = geometry.coords[::-1]
stitched_line, stitched_line_origin = raster_line_string_with_priority_points_graph(geometry,max_stitch_length,stitching_direction,projected_points,abs(row_spacing),offset_by_half)
def stitch_line(stitches, stitching_direction, geometry, projected_points, max_stitch_length, row_spacing, skip_last, offset_by_half):
# print(start_point)
# print(geometry[0])
# if stitching_direction == -1:
# geometry.coords = geometry.coords[::-1]
stitched_line, stitched_line_origin = raster_line_string_with_priority_points_graph(
geometry, max_stitch_length, stitching_direction, projected_points, abs(row_spacing), offset_by_half)
stitches.append(Stitch(*stitched_line[0], tags=('fill_row_start',)))
for i in range(1,len(stitched_line)):
for i in range(1, len(stitched_line)):
stitches.append(Stitch(*stitched_line[i], tags=('fill_row')))
if not skip_last:
if stitching_direction==1:
stitches.append(Stitch(*geometry.coords[-1], tags=('fill_row_end',)))
if stitching_direction == 1:
stitches.append(
Stitch(*geometry.coords[-1], tags=('fill_row_end',)))
else:
stitches.append(Stitch(*geometry.coords[0], tags=('fill_row_end',)))
stitches.append(
Stitch(*geometry.coords[0], tags=('fill_row_end',)))
@debug.time
def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length,
running_stitch_length, staggers, skip_last, offsetted_line, offset_by_half):
def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing, max_stitch_length,
running_stitch_length, staggers, skip_last, offsetted_line, offset_by_half):
path = collapse_sequential_outline_edges(path)
stitches = []
@ -678,18 +702,24 @@ def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing,
projected_points = current_edge['projected_points']
stitching_direction = 1
if (abs(edge[0][0]-path_geometry.coords[0][0])+abs(edge[0][1]-path_geometry.coords[0][1]) >
abs(edge[0][0]-path_geometry.coords[-1][0])+abs(edge[0][1]-path_geometry.coords[-1][1])):
abs(edge[0][0]-path_geometry.coords[-1][0])+abs(edge[0][1]-path_geometry.coords[-1][1])):
stitching_direction = -1
stitch_line(new_stitches, stitching_direction, path_geometry,projected_points, max_stitch_length,row_spacing,skip_last,offset_by_half)
stitch_line(new_stitches, stitching_direction, path_geometry, projected_points,
max_stitch_length, row_spacing, skip_last, offset_by_half)
current_edge['already_rastered'] = True
transfer_points_to_surrounding_graph(fill_stitch_graph,current_edge,row_spacing,False,new_stitches,overnext_neighbor=True)
transfer_points_to_surrounding_graph(fill_stitch_graph,current_edge,row_spacing,offset_by_half,new_stitches,overnext_neighbor=False,transfer_forbidden_points=offset_by_half)
transfer_points_to_surrounding_graph(
fill_stitch_graph, current_edge, row_spacing, False, new_stitches, overnext_neighbor=True)
transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, row_spacing, offset_by_half,
new_stitches, overnext_neighbor=False, transfer_forbidden_points=offset_by_half)
stitches.extend(new_stitches)
else:
stitch_row(stitches, edge[0], edge[1], angle, row_spacing, max_stitch_length, staggers, skip_last)
travel_graph.remove_edges_from(fill_stitch_graph[edge[0]][edge[1]]['segment'].get('underpath_edges', []))
stitch_row(stitches, edge[0], edge[1], angle,
row_spacing, max_stitch_length, staggers, skip_last)
travel_graph.remove_edges_from(
fill_stitch_graph[edge[0]][edge[1]]['segment'].get('underpath_edges', []))
else:
stitches.extend(travel(travel_graph, edge[0], edge[1], running_stitch_length, skip_last))
stitches.extend(
travel(travel_graph, edge[0], edge[1], running_stitch_length, skip_last))
return stitches

Wyświetl plik

@ -3,39 +3,60 @@ import math
# Used in the simplify routine of shapely
simplification_threshold = 0.01
# If a transferred point is closer than this value to one of its neighbors, it will be checked whether it can be removed
# If a transferred point is closer than this value to one of its neighbors,
# it will be checked whether it can be removed
distance_thresh_remove_transferred_point = 0.15
# If a line segment is shorter than this threshold it is handled as a single point
line_lengh_seen_as_one_point = 0.05
# E.g. to check whether a point is already present in a point list, the point is allowed to be this value in distance apart
# E.g. to check whether a point is already present in a point list,
# the point is allowed to be this value in distance apart
point_spacing_to_be_considered_equal = 0.05
# Adjacent geometry should have points closer than offset*offset_factor_for_adjacent_geometry to be considered adjacent
# Adjacent geometry should have points closer than
# offset*offset_factor_for_adjacent_geometry to be considered adjacent
offset_factor_for_adjacent_geometry = 1.5
# Transfer point distance is used for projecting points from already rastered geometry to adjacent geometry
# (max spacing transfer_point_distance_factor*offset) to get a more regular pattern
# Transfer point distance is used for projecting points from already
# rastered geometry to adjacent geometry
# (max spacing transfer_point_distance_factor*offset)
# to get a more regular pattern
transfer_point_distance_factor = 1.5
# Used to handle numerical inaccuracies during comparisons
eps = 1E-3
eps = 1e-3
factor_offset_starting_points=0.5 #When entering and leaving a child from a parent we introduce an offset of abs_offset*factor_offset_starting_points so
#that entering and leaving points are not lying above each other.
# When entering and leaving a child from a parent we introduce an offset of
# abs_offset*factor_offset_starting_points
# so that entering and leaving points are not lying above each other.
factor_offset_starting_points = 0.5
factor_offset_remove_points=0.5 #if points are closer than abs_offset*factor_offset_remove_points one of it is removed
# if points are closer than abs_offset*factor_offset_remove_points one of it is removed
factor_offset_remove_points = 0.5
fac_offset_edge_shift = 0.25 #if an unshifted relevant edge is closer than abs_offset*fac_offset_edge_shift to the line segment created by the shifted edge,
#the shift is allowed - otherwise the edge must not be shifted.
# if an unshifted relevant edge is closer than
# abs_offset*fac_offset_edge_shift
# to the line segment created by the shifted edge,
# the shift is allowed - otherwise the edge must not be shifted.
fac_offset_edge_shift = 0.25
limiting_angle = math.pi*15/180.0 #decides whether the point belongs to a hard edge (must use this point during sampling) or soft edge (do not necessarily need to use this point)
limiting_angle_straight = math.pi*0.5/180.0 #angles straighter (smaller) than this are considered as more or less straight (no concrete edges required for path segments having only angles <= this value)
# decides whether the point belongs to a hard edge (must use this point during sampling)
# or soft edge (do not necessarily need to use this point)
limiting_angle = math.pi * 15 / 180.0
# angles straighter (smaller) than this are considered as more or less straight
# (no concrete edges required for path segments having only angles <= this value)
limiting_angle_straight = math.pi * 0.5 / 180.0
factor_offset_remove_dense_points=0.2 #if a point distance to the connected line of its two neighbors is smaller than abs_offset times this factor, this point will be removed if the stitching distance will not be exceeded
# if a point distance to the connected line of its two neighbors is smaller than
# abs_offset times this factor, this point will be removed if the stitching distance will not be exceeded
factor_offset_remove_dense_points = 0.2
factor_offset_forbidden_point = 1.0 #if a soft edge is closer to a forbidden point than abs_offset*this factor it will be marked as forbidden.
# if a soft edge is closer to a forbidden point than abs_offset*this factor it will be marked as forbidden.
factor_offset_forbidden_point = 1.0
factor_segment_length_direct_preferred_over_overnext = 0.5 #usually overnext projected points are preferred. If an overnext projected point would create a much smaller segment than a direct projected point we might prefer the direct projected point
# usually overnext projected points are preferred.
# If an overnext projected point would create a much smaller segment than a direct
# projected point we might prefer the direct projected point
factor_segment_length_direct_preferred_over_overnext = 0.5

Wyświetl plik

@ -12,8 +12,10 @@ from ..utils import Point as InkstitchPoint
from ..utils import cache
from ..stitch_plan import Stitch
def legacy_fill(shape, angle, row_spacing, end_row_spacing, max_stitch_length, flip, staggers, skip_last):
rows_of_segments = intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing, flip)
rows_of_segments = intersect_region_with_grating(
shape, angle, row_spacing, end_row_spacing, flip)
groups_of_segments = pull_runs(rows_of_segments, shape, row_spacing)
return [section_to_stitches(group, angle, row_spacing, max_stitch_length, staggers, skip_last)
@ -73,7 +75,8 @@ def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, stagge
stitches.append(beg)
first_stitch = adjust_stagger(beg, angle, row_spacing, max_stitch_length, staggers)
first_stitch = adjust_stagger(
beg, angle, row_spacing, max_stitch_length, staggers)
# we might have chosen our first stitch just outside this row, so move back in
if (first_stitch - beg) * row_direction < 0:
@ -82,13 +85,15 @@ def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, stagge
offset = (first_stitch - beg).length()
while offset < segment_length:
stitches.append(Stitch(beg + offset * row_direction, tags=('fill_row')))
stitches.append(
Stitch(beg + offset * row_direction, tags=('fill_row')))
offset += max_stitch_length
if (end - stitches[-1]).length() > 0.1 * PIXELS_PER_MM and not skip_last:
stitches.append(end)
def extend_line(line, minx,maxx,miny,maxy):
def extend_line(line, minx, maxx, miny, maxy):
line = line.simplify(0.01, False)
upper_left = InkstitchPoint(minx, miny)
@ -103,26 +108,30 @@ def extend_line(line, minx,maxx,miny,maxy):
point4 = InkstitchPoint(*line.coords[-1])
new_ending_point = point4+(point4-point3).unit()*length
line = LineString([new_starting_point.as_tuple()]+line.coords[1:-1]+[new_ending_point.as_tuple()])
line = LineString([new_starting_point.as_tuple()] +
line.coords[1:-1]+[new_ending_point.as_tuple()])
def intersect_region_with_grating_line(shape, line, row_spacing, end_row_spacing=None, flip=False):
row_spacing = abs(row_spacing)
(minx, miny, maxx, maxy) = shape.bounds
upper_left = InkstitchPoint(minx, miny)
rows = []
extend_line(line, minx,maxx,miny,maxy) #extend the line towards the ends to increase probability that all offsetted curves cross the shape
# extend the line towards the ends to increase probability that all offsetted curves cross the shape
extend_line(line, minx, maxx, miny, maxy)
line_offsetted = line
res = line_offsetted.intersection(shape)
while isinstance(res, (shapely.geometry.GeometryCollection, shapely.geometry.MultiLineString)) or (not res.is_empty and len(res.coords) > 1):
if isinstance(res, (shapely.geometry.GeometryCollection, shapely.geometry.MultiLineString)):
runs = [line_string.coords for line_string in res.geoms if (not line_string.is_empty and len(line_string.coords) > 1)]
runs = [line_string.coords for line_string in res.geoms if (
not line_string.is_empty and len(line_string.coords) > 1)]
else:
runs = [res.coords]
runs.sort(key=lambda seg: (InkstitchPoint(*seg[0]) - upper_left).length())
runs.sort(key=lambda seg: (
InkstitchPoint(*seg[0]) - upper_left).length())
if flip:
runs.reverse()
runs = [tuple(reversed(run)) for run in runs]
@ -130,8 +139,8 @@ def intersect_region_with_grating_line(shape, line, row_spacing, end_row_spacing
if row_spacing > 0:
rows.append(runs)
else:
rows.insert(0,runs)
line_offsetted = line_offsetted.parallel_offset(row_spacing,'left',5)
rows.insert(0, runs)
line_offsetted = line_offsetted.parallel_offset(row_spacing, 'left', 5)
if row_spacing < 0:
line_offsetted.coords = line_offsetted.coords[::-1]
line_offsetted = line_offsetted.simplify(0.01, False)
@ -139,12 +148,13 @@ def intersect_region_with_grating_line(shape, line, row_spacing, end_row_spacing
if row_spacing > 0 and not isinstance(res, (shapely.geometry.GeometryCollection, shapely.geometry.MultiLineString)):
if (res.is_empty or len(res.coords) == 1):
row_spacing = -row_spacing
#print("Set to right")
line_offsetted = line.parallel_offset(row_spacing,'left',5)
line_offsetted.coords = line_offsetted.coords[::-1] #using negative row spacing leads as a side effect to reversed offsetted lines - here we undo this
# print("Set to right")
line_offsetted = line.parallel_offset(row_spacing, 'left', 5)
# using negative row spacing leads as a side effect to reversed offsetted lines - here we undo this
line_offsetted.coords = line_offsetted.coords[::-1]
line_offsetted = line_offsetted.simplify(0.01, False)
res = line_offsetted.intersection(shape)
return rows
@ -174,7 +184,8 @@ def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=Non
# angle degrees clockwise and ask for the new bounding box. The max
# and min y tell me how far to go.
_, start, _, end = shapely.affinity.rotate(shape, angle, origin='center', use_radians=True).bounds
_, start, _, end = shapely.affinity.rotate(
shape, angle, origin='center', use_radians=True).bounds
# convert start and end to be relative to center (simplifies things later)
start -= center.y
@ -211,7 +222,8 @@ def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=Non
runs = [res.coords]
if runs:
runs.sort(key=lambda seg: (InkstitchPoint(*seg[0]) - upper_left).length())
runs.sort(key=lambda seg: (
InkstitchPoint(*seg[0]) - upper_left).length())
if flip:
runs.reverse()
@ -220,7 +232,9 @@ def intersect_region_with_grating(shape, angle, row_spacing, end_row_spacing=Non
rows.append(runs)
if end_row_spacing:
current_row_y += row_spacing + (end_row_spacing - row_spacing) * ((current_row_y - start) / height)
current_row_y += row_spacing + \
(end_row_spacing - row_spacing) * \
((current_row_y - start) / height)
else:
current_row_y += row_spacing
@ -237,7 +251,8 @@ def section_to_stitches(group_of_segments, angle, row_spacing, max_stitch_length
if (swap):
(beg, end) = (end, beg)
stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, staggers, skip_last)
stitch_row(stitches, beg, end, angle, row_spacing,
max_stitch_length, staggers, skip_last)
swap = not swap