From b76146aa91824817f297e9463094ec5231950e3f Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 22 Sep 2022 19:54:42 -0400 Subject: add tiles --- lib/tiles.py | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 lib/tiles.py (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py new file mode 100644 index 00000000..34097f67 --- /dev/null +++ b/lib/tiles.py @@ -0,0 +1,136 @@ +import inkex +from math import ceil, floor +from networkx import Graph +import os +from shapely.geometry import LineString +from shapely.prepared import prep + +from .svg import apply_transforms +from .utils import get_bundled_dir, guess_inkscape_config_path, Point +from random import random + + +class Tile: + def __init__(self, path): + self._load_tile(path) + + def _load_tile(self, tile_path): + tile_svg = inkex.load_svg(tile_path) + self.name = self._get_name(tile_path) + self._load_paths(tile_svg) + self._load_dimensions(tile_svg) + self._load_buffer_size(tile_svg) + self._load_parallelogram(tile_svg) + + def __repr__(self): + return f"Tile({self.name}, {self.shift0}, {self.shift1})" + + __str__ = __repr__ + + def _get_name(self, tile_path): + return os.path.splitext(os.path.basename(tile_path))[0] + + def _load_paths(self, tile_svg): + path_elements = tile_svg.findall('.//svg:path', namespaces=inkex.NSS) + self.tile = self._path_elements_to_line_strings(path_elements) + # self.center, ignore, ignore = self._get_center_and_dimensions(self.tile) + + def _load_dimensions(self, tile_svg): + svg_element = tile_svg.getroot() + self.width = svg_element.viewport_width + self.height = svg_element.viewport_height + + def _load_buffer_size(self, tile_svg): + circle_elements = tile_svg.findall('.//svg:circle', namespaces=inkex.NSS) + if circle_elements: + self.buffer_size = circle_elements[0].radius + else: + self.buffer_size = 0 + + def _load_parallelogram(self, tile_svg): + parallelogram_elements = tile_svg.findall(".//svg:*[@class='para']", namespaces=inkex.NSS) + if parallelogram_elements: + path_element = parallelogram_elements[0] + path = apply_transforms(path_element.get_path(), path_element) + subpaths = path.to_superpath() + subpath = subpaths[0] + points = [Point.from_tuple(p[1]) for p in subpath] + self.shift0 = points[1] - points[0] + self.shift1 = points[2] - points[1] + else: + self.shift0 = Point(self.width, 0) + self.shift1 = Point(0, self.height) + + def _path_elements_to_line_strings(self, path_elements): + lines = [] + for path_element in path_elements: + path = apply_transforms(path_element.get_path(), path_element) + for subpath in path.to_superpath(): + # We only care about the endpoints of each subpath. They're + # supposed to be simple line segments. + lines.append([Point.from_tuple(subpath[0][1]), Point.from_tuple(subpath[-1][1])]) + + return lines + + def _get_center_and_dimensions(self, shape): + min_x, min_y, max_x, max_y = shape.bounds + center = Point((max_x + min_x) / 2, (max_y + min_y) / 2) + width = max_x - min_x + height = max_y - min_y + + return center, width, height + + def translate_tile(self, shift): + translated_tile = [] + + for start, end in self.tile: + start += shift + end += shift + translated_tile.append((start.as_int().as_tuple(), end.as_int().as_tuple())) + + return translated_tile + + def to_graph(self, shape, only_inside=True, pad=True): + """Apply this tile to a shape, repeating as necessary. + + Return value: + networkx.Graph with edges corresponding to lines in the pattern. + Each edge has an attribute 'line_string' with the LineString + representation of this edge. + """ + shape_center, shape_width, shape_height = self._get_center_and_dimensions(shape) + shape_diagonal = (shape_width ** 2 + shape_height ** 2) ** 0.5 + graph = Graph() + + if pad: + shape = shape.buffer(-self.buffer_size) + + prepared_shape = prep(shape) + + tiles0 = ceil(shape_diagonal / self.shift0.length()) + 2 + tiles1 = ceil(shape_diagonal / self.shift1.length()) + 2 + for repeat0 in range(floor(-tiles0 / 2), ceil(tiles0 / 2)): + for repeat1 in range(floor(-tiles1 / 2), ceil(tiles1 / 2)): + shift0 = repeat0 * self.shift0 + shape_center + shift1 = repeat1 * self.shift1 + shape_center + this_tile = self.translate_tile(shift0 + shift1) + for line in this_tile: + line_string = LineString(line) + if not only_inside or prepared_shape.contains(line_string): + graph.add_edge(line[0], line[1], line_string=line_string, weight=random() + 0.1) + + return graph + + +def all_tile_paths(): + return [os.path.join(guess_inkscape_config_path(), 'tiles'), + get_bundled_dir('tiles')] + + +def all_tiles(): + for tile_dir in all_tile_paths(): + try: + for tile_file in sorted(os.listdir(tile_dir)): + yield Tile(os.path.join(tile_dir, tile_file)) + except FileNotFoundError: + pass -- cgit v1.2.3 From ba835b4f5e33f404b7bed9369a1b425a67b312c5 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 16 Jan 2023 14:27:06 -0500 Subject: meander fill: initial version --- lib/tiles.py | 96 ++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 35 deletions(-) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index 34097f67..e9f0305a 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -5,8 +5,10 @@ import os from shapely.geometry import LineString from shapely.prepared import prep +from .debug import debug from .svg import apply_transforms -from .utils import get_bundled_dir, guess_inkscape_config_path, Point +from .svg.tags import SODIPODI_NAMEDVIEW +from .utils import cache, get_bundled_dir, guess_inkscape_config_path, Point from random import random @@ -15,51 +17,68 @@ class Tile: self._load_tile(path) def _load_tile(self, tile_path): - tile_svg = inkex.load_svg(tile_path) - self.name = self._get_name(tile_path) - self._load_paths(tile_svg) - self._load_dimensions(tile_svg) - self._load_buffer_size(tile_svg) - self._load_parallelogram(tile_svg) + self.tile_svg = inkex.load_svg(tile_path) + self.tile_path = tile_path + self.name = self._get_name(self.tile_svg, tile_path) + self.tile = None + self.width = None + self.height = None + self.buffer_size = None + self.shift0 = None + self.shift1 = None def __repr__(self): return f"Tile({self.name}, {self.shift0}, {self.shift1})" __str__ = __repr__ - def _get_name(self, tile_path): - return os.path.splitext(os.path.basename(tile_path))[0] + def _get_name(self, tile_svg, tile_path): + name = tile_svg.get(SODIPODI_NAMEDVIEW) + if name: + return name + else: + return os.path.splitext(os.path.basename(tile_path))[0] + + def _load(self): + self._load_paths(self.tile_svg) + self._load_dimensions(self.tile_svg) + self._load_buffer_size(self.tile_svg) + self._load_parallelogram(self.tile_svg) def _load_paths(self, tile_svg): - path_elements = tile_svg.findall('.//svg:path', namespaces=inkex.NSS) - self.tile = self._path_elements_to_line_strings(path_elements) - # self.center, ignore, ignore = self._get_center_and_dimensions(self.tile) + if self.tile is None: + path_elements = tile_svg.findall('.//svg:path', namespaces=inkex.NSS) + self.tile = self._path_elements_to_line_strings(path_elements) + # self.center, ignore, ignore = self._get_center_and_dimensions(self.tile) def _load_dimensions(self, tile_svg): - svg_element = tile_svg.getroot() - self.width = svg_element.viewport_width - self.height = svg_element.viewport_height + if self.width is None: + svg_element = tile_svg.getroot() + self.width = svg_element.viewport_width + self.height = svg_element.viewport_height def _load_buffer_size(self, tile_svg): - circle_elements = tile_svg.findall('.//svg:circle', namespaces=inkex.NSS) - if circle_elements: - self.buffer_size = circle_elements[0].radius - else: - self.buffer_size = 0 + if self.buffer_size is None: + circle_elements = tile_svg.findall('.//svg:circle', namespaces=inkex.NSS) + if circle_elements: + self.buffer_size = circle_elements[0].radius + else: + self.buffer_size = 0 def _load_parallelogram(self, tile_svg): - parallelogram_elements = tile_svg.findall(".//svg:*[@class='para']", namespaces=inkex.NSS) - if parallelogram_elements: - path_element = parallelogram_elements[0] - path = apply_transforms(path_element.get_path(), path_element) - subpaths = path.to_superpath() - subpath = subpaths[0] - points = [Point.from_tuple(p[1]) for p in subpath] - self.shift0 = points[1] - points[0] - self.shift1 = points[2] - points[1] - else: - self.shift0 = Point(self.width, 0) - self.shift1 = Point(0, self.height) + if self.shift0 is None: + parallelogram_elements = tile_svg.findall(".//svg:*[@class='para']", namespaces=inkex.NSS) + if parallelogram_elements: + path_element = parallelogram_elements[0] + path = apply_transforms(path_element.get_path(), path_element) + subpaths = path.to_superpath() + subpath = subpaths[0] + points = [Point.from_tuple(p[1]) for p in subpath] + self.shift0 = points[1] - points[0] + self.shift1 = points[2] - points[1] + else: + self.shift0 = Point(self.width, 0) + self.shift1 = Point(0, self.height) def _path_elements_to_line_strings(self, path_elements): lines = [] @@ -80,7 +99,7 @@ class Tile: return center, width, height - def translate_tile(self, shift): + def _translate_tile(self, shift): translated_tile = [] for start, end in self.tile: @@ -90,6 +109,7 @@ class Tile: return translated_tile + @debug.time def to_graph(self, shape, only_inside=True, pad=True): """Apply this tile to a shape, repeating as necessary. @@ -98,6 +118,8 @@ class Tile: Each edge has an attribute 'line_string' with the LineString representation of this edge. """ + self._load() + shape_center, shape_width, shape_height = self._get_center_and_dimensions(shape) shape_diagonal = (shape_width ** 2 + shape_height ** 2) ** 0.5 graph = Graph() @@ -113,7 +135,7 @@ class Tile: for repeat1 in range(floor(-tiles1 / 2), ceil(tiles1 / 2)): shift0 = repeat0 * self.shift0 + shape_center shift1 = repeat1 * self.shift1 + shape_center - this_tile = self.translate_tile(shift0 + shift1) + this_tile = self._translate_tile(shift0 + shift1) for line in this_tile: line_string = LineString(line) if not only_inside or prepared_shape.contains(line_string): @@ -127,10 +149,14 @@ def all_tile_paths(): get_bundled_dir('tiles')] +@cache def all_tiles(): + tiles = [] for tile_dir in all_tile_paths(): try: for tile_file in sorted(os.listdir(tile_dir)): - yield Tile(os.path.join(tile_dir, tile_file)) + tiles.append(Tile(os.path.join(tile_dir, tile_file))) except FileNotFoundError: pass + + return tiles -- cgit v1.2.3 From 847e133f97d570e2967dfa7dcfc16a212dc2bbbc Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Tue, 17 Jan 2023 21:44:23 -0500 Subject: meander fill: more work --- lib/tiles.py | 113 +++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 64 insertions(+), 49 deletions(-) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index e9f0305a..2bef7a19 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -1,15 +1,15 @@ -import inkex +import os from math import ceil, floor + +import inkex +import lxml from networkx import Graph -import os from shapely.geometry import LineString from shapely.prepared import prep from .debug import debug from .svg import apply_transforms -from .svg.tags import SODIPODI_NAMEDVIEW -from .utils import cache, get_bundled_dir, guess_inkscape_config_path, Point -from random import random +from .utils import Point, cache, get_bundled_dir, guess_inkscape_config_path class Tile: @@ -33,11 +33,7 @@ class Tile: __str__ = __repr__ def _get_name(self, tile_svg, tile_path): - name = tile_svg.get(SODIPODI_NAMEDVIEW) - if name: - return name - else: - return os.path.splitext(os.path.basename(tile_path))[0] + return os.path.splitext(os.path.basename(tile_path)[0]) def _load(self): self._load_paths(self.tile_svg) @@ -46,39 +42,35 @@ class Tile: self._load_parallelogram(self.tile_svg) def _load_paths(self, tile_svg): - if self.tile is None: - path_elements = tile_svg.findall('.//svg:path', namespaces=inkex.NSS) - self.tile = self._path_elements_to_line_strings(path_elements) - # self.center, ignore, ignore = self._get_center_and_dimensions(self.tile) + path_elements = tile_svg.findall('.//svg:path', namespaces=inkex.NSS) + self.tile = self._path_elements_to_line_strings(path_elements) + # self.center, ignore, ignore = self._get_center_and_dimensions(self.tile) def _load_dimensions(self, tile_svg): - if self.width is None: - svg_element = tile_svg.getroot() - self.width = svg_element.viewport_width - self.height = svg_element.viewport_height + svg_element = tile_svg.getroot() + self.width = svg_element.viewport_width + self.height = svg_element.viewport_height def _load_buffer_size(self, tile_svg): - if self.buffer_size is None: - circle_elements = tile_svg.findall('.//svg:circle', namespaces=inkex.NSS) - if circle_elements: - self.buffer_size = circle_elements[0].radius - else: - self.buffer_size = 0 + circle_elements = tile_svg.findall('.//svg:circle', namespaces=inkex.NSS) + if circle_elements: + self.buffer_size = circle_elements[0].radius + else: + self.buffer_size = 0 def _load_parallelogram(self, tile_svg): - if self.shift0 is None: - parallelogram_elements = tile_svg.findall(".//svg:*[@class='para']", namespaces=inkex.NSS) - if parallelogram_elements: - path_element = parallelogram_elements[0] - path = apply_transforms(path_element.get_path(), path_element) - subpaths = path.to_superpath() - subpath = subpaths[0] - points = [Point.from_tuple(p[1]) for p in subpath] - self.shift0 = points[1] - points[0] - self.shift1 = points[2] - points[1] - else: - self.shift0 = Point(self.width, 0) - self.shift1 = Point(0, self.height) + parallelogram_elements = tile_svg.findall(".//svg:*[@class='para']", namespaces=inkex.NSS) + if parallelogram_elements: + path_element = parallelogram_elements[0] + path = apply_transforms(path_element.get_path(), path_element) + subpaths = path.to_superpath() + subpath = subpaths[0] + points = [Point.from_tuple(p[1]) for p in subpath] + self.shift0 = points[1] - points[0] + self.shift1 = points[2] - points[1] + else: + self.shift0 = Point(self.width, 0) + self.shift1 = Point(0, self.height) def _path_elements_to_line_strings(self, path_elements): lines = [] @@ -109,8 +101,19 @@ class Tile: return translated_tile + def _scale(self, x_scale, y_scale): + self.shift0 = self.shift0.scale(x_scale, y_scale) + self.shift1 = self.shift1.scale(x_scale, y_scale) + + scaled_tile = [] + for start, end in self.tile: + start = start.scale(x_scale, y_scale) + end = end.scale(x_scale, y_scale) + scaled_tile.append((start, end)) + self.tile = scaled_tile + @debug.time - def to_graph(self, shape, only_inside=True, pad=True): + def to_graph(self, shape, scale, buffer=None): """Apply this tile to a shape, repeating as necessary. Return value: @@ -119,27 +122,36 @@ class Tile: representation of this edge. """ self._load() + x_scale, y_scale = scale + self._scale(x_scale, y_scale) shape_center, shape_width, shape_height = self._get_center_and_dimensions(shape) - shape_diagonal = (shape_width ** 2 + shape_height ** 2) ** 0.5 - graph = Graph() + shape_diagonal = Point(shape_width, shape_height).length() + + if not buffer: + average_scale = (x_scale + y_scale) / 2 + buffer = self.buffer_size * average_scale - if pad: - shape = shape.buffer(-self.buffer_size) + contracted_shape = shape.buffer(-buffer) + prepared_shape = prep(contracted_shape) - prepared_shape = prep(shape) + # debug.log_line_string(contracted_shape.exterior, "contracted shape") + return self._generate_graph(prepared_shape, shape_center, shape_diagonal) + + def _generate_graph(self, shape, shape_center, shape_diagonal): + graph = Graph() tiles0 = ceil(shape_diagonal / self.shift0.length()) + 2 tiles1 = ceil(shape_diagonal / self.shift1.length()) + 2 for repeat0 in range(floor(-tiles0 / 2), ceil(tiles0 / 2)): for repeat1 in range(floor(-tiles1 / 2), ceil(tiles1 / 2)): - shift0 = repeat0 * self.shift0 + shape_center - shift1 = repeat1 * self.shift1 + shape_center - this_tile = self._translate_tile(shift0 + shift1) + shift0 = repeat0 * self.shift0 + shift1 = repeat1 * self.shift1 + this_tile = self._translate_tile(shift0 + shift1 + shape_center) for line in this_tile: line_string = LineString(line) - if not only_inside or prepared_shape.contains(line_string): - graph.add_edge(line[0], line[1], line_string=line_string, weight=random() + 0.1) + if shape.contains(line_string): + graph.add_edge(line[0], line[1]) return graph @@ -155,7 +167,10 @@ def all_tiles(): for tile_dir in all_tile_paths(): try: for tile_file in sorted(os.listdir(tile_dir)): - tiles.append(Tile(os.path.join(tile_dir, tile_file))) + try: + tiles.append(Tile(os.path.join(tile_dir, tile_file))) + except (OSError, lxml.etree.XMLSyntaxError): + pass except FileNotFoundError: pass -- cgit v1.2.3 From 338c1c7bbc893afdcc7ef1d1cc88c66789b9aa4c Mon Sep 17 00:00:00 2001 From: Kaalleen Date: Fri, 20 Jan 2023 17:28:13 +0100 Subject: fix tile names --- lib/tiles.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index 2bef7a19..17002ffa 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -19,7 +19,7 @@ class Tile: def _load_tile(self, tile_path): self.tile_svg = inkex.load_svg(tile_path) self.tile_path = tile_path - self.name = self._get_name(self.tile_svg, tile_path) + self.name = self._get_name(tile_path) self.tile = None self.width = None self.height = None @@ -32,8 +32,8 @@ class Tile: __str__ = __repr__ - def _get_name(self, tile_svg, tile_path): - return os.path.splitext(os.path.basename(tile_path)[0]) + def _get_name(self, tile_path): + return os.path.splitext(os.path.basename(tile_path))[0] def _load(self): self._load_paths(self.tile_svg) -- cgit v1.2.3 From 8dde33d867afce7ea4aa212dfce60e9e526bbc1c Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sun, 5 Feb 2023 22:58:17 -0500 Subject: make meander interruptible --- lib/tiles.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index 17002ffa..5d33d946 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -10,6 +10,7 @@ from shapely.prepared import prep from .debug import debug from .svg import apply_transforms from .utils import Point, cache, get_bundled_dir, guess_inkscape_config_path +from .utils.threading import check_stop_flag class Tile: @@ -145,6 +146,8 @@ class Tile: tiles1 = ceil(shape_diagonal / self.shift1.length()) + 2 for repeat0 in range(floor(-tiles0 / 2), ceil(tiles0 / 2)): for repeat1 in range(floor(-tiles1 / 2), ceil(tiles1 / 2)): + check_stop_flag() + shift0 = repeat0 * self.shift0 shift1 = repeat1 * self.shift1 this_tile = self._translate_tile(shift0 + shift1 + shape_center) -- cgit v1.2.3 From 9ea61ef3f33b76f199843fff82d1d624903aa11d Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 8 Feb 2023 14:39:25 -0500 Subject: remove buffer concept --- lib/tiles.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index 5d33d946..9986183b 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -24,7 +24,6 @@ class Tile: self.tile = None self.width = None self.height = None - self.buffer_size = None self.shift0 = None self.shift1 = None @@ -39,7 +38,6 @@ class Tile: def _load(self): self._load_paths(self.tile_svg) self._load_dimensions(self.tile_svg) - self._load_buffer_size(self.tile_svg) self._load_parallelogram(self.tile_svg) def _load_paths(self, tile_svg): @@ -52,13 +50,6 @@ class Tile: self.width = svg_element.viewport_width self.height = svg_element.viewport_height - def _load_buffer_size(self, tile_svg): - circle_elements = tile_svg.findall('.//svg:circle', namespaces=inkex.NSS) - if circle_elements: - self.buffer_size = circle_elements[0].radius - else: - self.buffer_size = 0 - def _load_parallelogram(self, tile_svg): parallelogram_elements = tile_svg.findall(".//svg:*[@class='para']", namespaces=inkex.NSS) if parallelogram_elements: @@ -114,7 +105,7 @@ class Tile: self.tile = scaled_tile @debug.time - def to_graph(self, shape, scale, buffer=None): + def to_graph(self, shape, scale): """Apply this tile to a shape, repeating as necessary. Return value: @@ -128,15 +119,7 @@ class Tile: shape_center, shape_width, shape_height = self._get_center_and_dimensions(shape) shape_diagonal = Point(shape_width, shape_height).length() - - if not buffer: - average_scale = (x_scale + y_scale) / 2 - buffer = self.buffer_size * average_scale - - contracted_shape = shape.buffer(-buffer) - prepared_shape = prep(contracted_shape) - - # debug.log_line_string(contracted_shape.exterior, "contracted shape") + prepared_shape = prep(shape) return self._generate_graph(prepared_shape, shape_center, shape_diagonal) -- cgit v1.2.3 From 3515ca399b6b01e0e293f5e62ee02ab392950183 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 8 Feb 2023 15:39:50 -0500 Subject: remove dead ends --- lib/tiles.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index 9986183b..7f46a5aa 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -139,8 +139,19 @@ class Tile: if shape.contains(line_string): graph.add_edge(line[0], line[1]) + self._remove_dead_ends(graph) + return graph + def _remove_dead_ends(self, graph): + while True: + nodes_with_degree_1 = [node for node, degree in graph.degree() if degree == 1] + + if nodes_with_degree_1: + graph.remove_nodes_from(nodes_with_degree_1) + else: + return + def all_tile_paths(): return [os.path.join(guess_inkscape_config_path(), 'tiles'), -- cgit v1.2.3 From 3b1a161532fb641f904c8697ac3597ecef0b4110 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sun, 12 Feb 2023 23:09:42 -0500 Subject: sort tiles by name --- lib/tiles.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index 7f46a5aa..e40692e8 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -27,6 +27,9 @@ class Tile: self.shift0 = None self.shift1 = None + def __lt__(self, other): + return self.name < other.name + def __repr__(self): return f"Tile({self.name}, {self.shift0}, {self.shift1})" -- cgit v1.2.3 From ae43fb96830d9f747f890d1985a9c5ed4741f893 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 18 Feb 2023 17:44:42 -0500 Subject: avoid NetworkXNoPath error by connecting graph --- lib/tiles.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index e40692e8..d1e79071 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -3,7 +3,7 @@ from math import ceil, floor import inkex import lxml -from networkx import Graph +import networkx as nx from shapely.geometry import LineString from shapely.prepared import prep @@ -127,7 +127,7 @@ class Tile: return self._generate_graph(prepared_shape, shape_center, shape_diagonal) def _generate_graph(self, shape, shape_center, shape_diagonal): - graph = Graph() + graph = nx.Graph() tiles0 = ceil(shape_diagonal / self.shift0.length()) + 2 tiles1 = ceil(shape_diagonal / self.shift1.length()) + 2 for repeat0 in range(floor(-tiles0 / 2), ceil(tiles0 / 2)): @@ -147,11 +147,12 @@ class Tile: return graph def _remove_dead_ends(self, graph): + graph.remove_edges_from(nx.selfloop_edges(graph)) while True: - nodes_with_degree_1 = [node for node, degree in graph.degree() if degree == 1] + dead_end_nodes = [node for node, degree in graph.degree() if degree <= 1] - if nodes_with_degree_1: - graph.remove_nodes_from(nodes_with_degree_1) + if dead_end_nodes: + graph.remove_nodes_from(dead_end_nodes) else: return -- cgit v1.2.3 From 315866de9ade249cab8db81d880d255f33f851f3 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 18 Feb 2023 21:51:22 -0500 Subject: use new combo param type --- lib/tiles.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index d1e79071..fce4d26f 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -20,7 +20,8 @@ class Tile: def _load_tile(self, tile_path): self.tile_svg = inkex.load_svg(tile_path) self.tile_path = tile_path - self.name = self._get_name(tile_path) + self.id = self._get_name(tile_path) + self.name = self.id self.tile = None self.width = None self.height = None -- cgit v1.2.3 From d278f6a54a2a316e70271ad04bd206e49a93fa5f Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 18 Feb 2023 22:24:58 -0500 Subject: add tiles json and internationalization --- lib/tiles.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'lib/tiles.py') diff --git a/lib/tiles.py b/lib/tiles.py index fce4d26f..683804a6 100644 --- a/lib/tiles.py +++ b/lib/tiles.py @@ -2,12 +2,14 @@ import os from math import ceil, floor import inkex +import json import lxml import networkx as nx from shapely.geometry import LineString from shapely.prepared import prep from .debug import debug +from .i18n import _ from .svg import apply_transforms from .utils import Point, cache, get_bundled_dir, guess_inkscape_config_path from .utils.threading import check_stop_flag @@ -18,10 +20,8 @@ class Tile: self._load_tile(path) def _load_tile(self, tile_path): - self.tile_svg = inkex.load_svg(tile_path) - self.tile_path = tile_path - self.id = self._get_name(tile_path) - self.name = self.id + self.tile_svg = inkex.load_svg(os.path.join(tile_path, "tile.svg")) + self._load_metadata(tile_path) self.tile = None self.width = None self.height = None @@ -32,10 +32,16 @@ class Tile: return self.name < other.name def __repr__(self): - return f"Tile({self.name}, {self.shift0}, {self.shift1})" + return f"Tile({self.name}, {self.id})" __str__ = __repr__ + def _load_metadata(self, tile_path): + with open(os.path.join(tile_path, "tile.json"), "rb") as tile_json: + tile_metadata = json.load(tile_json) + self.name = _(tile_metadata.get('name')) + self.id = tile_metadata.get('id') + def _get_name(self, tile_path): return os.path.splitext(os.path.basename(tile_path))[0] @@ -166,13 +172,16 @@ def all_tile_paths(): @cache def all_tiles(): tiles = [] - for tile_dir in all_tile_paths(): + for tiles_path in all_tile_paths(): try: - for tile_file in sorted(os.listdir(tile_dir)): + for tile_dir in sorted(os.listdir(tiles_path)): try: - tiles.append(Tile(os.path.join(tile_dir, tile_file))) - except (OSError, lxml.etree.XMLSyntaxError): - pass + tiles.append(Tile(os.path.join(tiles_path, tile_dir))) + except (OSError, lxml.etree.XMLSyntaxError, json.JSONDecodeError, KeyError) as exc: + debug.log(f"error loading tile {tiles_path}/{tile_dir}: {exc}") + except Exception as exc: + debug.log(f"unexpected error loading tile {tiles_path}/{tile_dir}: {exc}") + raise except FileNotFoundError: pass -- cgit v1.2.3