kopia lustrzana https://github.com/inkstitch/inkstitch
137 wiersze
4.9 KiB
Python
137 wiersze
4.9 KiB
Python
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
|