kopia lustrzana https://github.com/inkstitch/inkstitch
Applied style guide
rodzic
0fcf8bb97c
commit
125db3f83b
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue