Source code for knittingpattern.convert.Layout

"""Map ``(x, y)`` coordinates to instructions

"""
from itertools import chain
from collections import namedtuple


INSTRUCTION_HEIGHT = 1  #: the default height of an instruction in the grid

#: This is the key to the "grid-layout"
#:
#: Values for the layout can be specified in each instruction.
#:
#: "grid-layout" : {
#:     "width" : 1
#: }
#:
#: .. seealso:: :data:`width`
GRID_LAYOUT = "grid-layout"

#: the width of the instruction in the grid layout, if specified.
#: .. seealso:: :data:`GRID_LAYOUT`
WIDTH = "width"
Point = namedtuple("Point", ["x", "y"])


[docs]class InGrid(object): """Base class for things in a grid"""
[docs] def __init__(self, position): """Create a new InGrid object.""" self._position = position
@property def x(self): """:return: x coordinate in the grid :rtype: float """ return self._position.x @property def y(self): """:return: y coordinate in the grid :rtype: float """ return self._position.y @property def xy(self): """:return: ``(x, y)`` coordinate in the grid :rtype: tuple """ return self._position @property def yx(self): """:return: ``(y, x)`` coordinate in the grid :rtype: tuple """ return self._position.y, self._position.x @property def width(self): """:return: width of the object on the grid :rtype: float """ return self._width @property def height(self): """:return: height of the object on the grid :rtype: float """ return INSTRUCTION_HEIGHT @property def row(self): """:return: row of the object on the grid :rtype: knittingpattern.Row.Row """ return self._row @property def bounding_box(self): """The bounding box of this object. :return: (min x, min y, max x, max y) :rtype: tuple """ return self._bounding_box @property def id(self): """The id of this object.""" return self._id
[docs]class InstructionInGrid(InGrid): """Holder of an instruction in the GridLayout."""
[docs] def __init__(self, instruction, position): """ :param instruction: an :class:`instruction <knittingpattern.Instruction.InstructionInRow>` :param Point position: the position of the :paramref:`instruction` """ self._instruction = instruction super().__init__(position)
@property def _width(self): """For ``self.width``.""" layout = self._instruction.get(GRID_LAYOUT) if layout is not None: width = layout.get(WIDTH) if width is not None: return width return self._instruction.number_of_consumed_meshes @property def instruction(self): """The instruction. :return: instruction that is placed on the grid :rtype: knittingpattern.Instruction.InstructionInRow """ return self._instruction @property def color(self): """The color of the instruction. :return: the color of the :attr:`instruction` """ return self._instruction.color def _row(self): """For ``self.row``.""" return self._instruction.row
[docs]class RowInGrid(InGrid): """Assign x and y coordinates to rows."""
[docs] def __init__(self, row, position): """Create a new row in the grid.""" super().__init__(position) self._row = row
@property def _width(self): """:return: the number of consumed meshes""" return sum(map(lambda i: i.width, self.instructions)) @property def instructions(self): """The instructions in a grid. :return: the :class:`instructions in a grid <InstructionInGrid>` of this row :rtype: list """ x = self.x y = self.y result = [] for instruction in self._row.instructions: instruction_in_grid = InstructionInGrid(instruction, Point(x, y)) x += instruction_in_grid.width result.append(instruction_in_grid) return result @property def _bounding_box(self): min_x = self.x min_y = self.y max_x = min_x + max(self._row.number_of_consumed_meshes, self._row.number_of_produced_meshes) max_y = min_y + self.height return min_x, min_y, max_x, max_y @property def _id(self): return self._row.id
[docs]def identity(object_): """:return: the argument""" return object_
class _RecursiveWalk(object): """This class starts walking the knitting pattern and maps instructions to positions in the grid that is created.""" def __init__(self, first_instruction): """Start walking the knitting pattern starting from first_instruction. """ self._rows_in_grid = {} self._todo = [] self._expand(first_instruction.row, Point(0, 0), []) self._walk() def _expand(self, row, consumed_position, passed): """Add the arguments `(args, kw)` to `_walk` to the todo list.""" self._todo.append((row, consumed_position, passed)) def _step(self, row, position, passed): """Walk through the knitting pattern by expanding an row.""" if row in passed or not self._row_should_be_placed(row, position): return self._place_row(row, position) passed = [row] + passed # print("{}{} at\t{} {}".format(" " * len(passed), row, position, # passed)) for i, produced_mesh in enumerate(row.produced_meshes): self._expand_produced_mesh(produced_mesh, i, position, passed) for i, consumed_mesh in enumerate(row.consumed_meshes): self._expand_consumed_mesh(consumed_mesh, i, position, passed) def _expand_consumed_mesh(self, mesh, mesh_index, row_position, passed): """expand the consumed meshes""" if not mesh.is_produced(): return row = mesh.producing_row position = Point( row_position.x + mesh.index_in_producing_row - mesh_index, row_position.y - INSTRUCTION_HEIGHT ) self._expand(row, position, passed) def _expand_produced_mesh(self, mesh, mesh_index, row_position, passed): """expand the produced meshes""" if not mesh.is_consumed(): return row = mesh.consuming_row position = Point( row_position.x - mesh.index_in_consuming_row + mesh_index, row_position.y + INSTRUCTION_HEIGHT ) self._expand(row, position, passed) def _row_should_be_placed(self, row, position): """:return: whether to place this instruction""" placed_row = self._rows_in_grid.get(row) return placed_row is None or placed_row.y < position.y def _place_row(self, row, position): """place the instruction on a grid""" self._rows_in_grid[row] = RowInGrid(row, position) def _walk(self): """Loop through all the instructions that are `_todo`.""" while self._todo: args = self._todo.pop(0) self._step(*args) def instruction_in_grid(self, instruction): """Returns an `InstructionInGrid` object for the `instruction`""" row_position = self._rows_in_grid[instruction.row].xy x = instruction.index_of_first_consumed_mesh_in_row position = Point(row_position.x + x, row_position.y) return InstructionInGrid(instruction, position) def row_in_grid(self, row): """Returns an `RowInGrid` object for the `row`""" return self._rows_in_grid[row]
[docs]class Connection(object): """a connection between two :class:`InstructionInGrid` objects"""
[docs] def __init__(self, start, stop): """ :param InstructionInGrid start: the start of the connection :param InstructionInGrid stop: the end of the connection """ self._start = start self._stop = stop
@property def start(self): """:return: the start of the connection :rtype: InstructionInGrid """ return self._start @property def stop(self): """:return: the end of the connection :rtype: InstructionInGrid """ return self._stop
[docs] def is_visible(self): """:return: is this connection is visible :rtype: bool A connection is visible if it is longer that 0.""" if self._start.y + 1 < self._stop.y: return True return False
[docs]class GridLayout(object): """This class places the instructions at ``(x, y)`` positions."""
[docs] def __init__(self, pattern): """ :param knittingpattern.KnittingPattern.KnittingPattern pattern: the pattern to layout """ self._pattern = pattern self._rows = list(pattern.rows) self._walk = _RecursiveWalk(self._rows[0].instructions[0]) self._rows.sort(key=lambda row: self._walk.row_in_grid(row).yx)
[docs] def walk_instructions(self, mapping=identity): """Iterate over instructions. :return: an iterator over :class:`instructions in grid <InstructionInGrid>` :param mapping: funcion to map the result .. code:: python for pos, c in layout.walk_instructions(lambda i: (i.xy, i.color)): print("color {} at {}".format(c, pos)) """ instructions = chain(*self.walk_rows(lambda row: row.instructions)) return map(mapping, instructions)
[docs] def walk_rows(self, mapping=identity): """Iterate over rows. :return: an iterator over :class:`rows <RowsInGrid>` :param mapping: funcion to map the result, see :meth:`walk_instructions` for an example usage """ row_in_grid = self._walk.row_in_grid return map(lambda row: mapping(row_in_grid(row)), self._rows)
[docs] def walk_connections(self, mapping=identity): """Iterate over connections between instructions. :return: an iterator over :class:`connections <Connection>` between :class:`instructions in grid <InstructionInGrid>` :param mapping: funcion to map the result, see :meth:`walk_instructions` for an example usage """ for start in self.walk_instructions(): for stop_instruction in start.instruction.consuming_instructions: if stop_instruction is None: continue stop = self._walk.instruction_in_grid(stop_instruction) connection = Connection(start, stop) if connection.is_visible(): # print("connection:", # connection.start.instruction, # connection.stop.instruction) yield mapping(connection)
@property def bounding_box(self): """The minimum and maximum bounds of this layout. :return: ``(min_x, min_y, max_x, max_y)`` the bounding box of this layout :rtype: tuple """ min_x, min_y, max_x, max_y = zip(*list(self.walk_rows( lambda row: row.bounding_box))) return min(min_x), min(min_y), max(max_x), max(max_y)
[docs] def row_in_grid(self, row): """The a RowInGrid for the row with position information. :return: a row in the grid :rtype: RowInGrid """ return self._walk.row_in_grid(row)
__all__ = ["GridLayout", "InstructionInGrid", "Connection", "identity", "Point", "INSTRUCTION_HEIGHT", "InGrid", "RowInGrid"]