"""Module containing the Carriage Pass class."""
from __future__ import annotations
import time
import warnings
from typing import cast, Iterator
from virtual_knitting_machine.Knitting_Machine import Knitting_Machine
from virtual_knitting_machine.knitting_machine_warnings.carriage_pass_warnings import Reordered_Knitting_Pass_Warning
from virtual_knitting_machine.machine_components.carriage_system.Carriage_Pass_Direction import Carriage_Pass_Direction
from virtual_knitting_machine.machine_components.needles.Needle import Needle
from virtual_knitting_machine.machine_components.yarn_management.Yarn_Carrier_Set import Yarn_Carrier_Set
from knitout_interpreter.knitout_operations.Rack_Instruction import Rack_Instruction
from knitout_interpreter.knitout_operations.knitout_instruction import Knitout_Instruction_Type
from knitout_interpreter.knitout_operations.needle_instructions import Needle_Instruction, Xfer_Instruction
from knitout_interpreter.knitout_operations.Knitout_Line import Knitout_Line, Knitout_Comment_Line
[docs]
class Carriage_Pass:
"""Manages knitout operations that are organized in a single carriage pass."""
def __init__(self, first_instruction: Needle_Instruction, rack: int, all_needle_rack: bool):
"""Initialize a new carriage pass with the first instruction.
Args:
first_instruction: The first needle instruction in this carriage pass.
rack: The rack position for this carriage pass.
all_needle_rack: Whether this pass uses all-needle racking.
"""
self._creation_time: float = time.time()
self.all_needle_rack: bool = all_needle_rack
self.rack: int = rack
self.xfer_pass: bool = isinstance(first_instruction, Xfer_Instruction)
if self.xfer_pass:
self.carrier_set: Yarn_Carrier_Set | None = None
self._direction: Carriage_Pass_Direction | None = None
else:
self.carrier_set: Yarn_Carrier_Set | None = first_instruction.carrier_set
self._direction: Carriage_Pass_Direction | None = first_instruction.direction
self._instructions: list[Needle_Instruction] = [first_instruction]
self._needles_to_instruction: dict[Needle, Needle_Instruction] = {first_instruction.needle: first_instruction}
self._instruction_types_to_needles: dict[Knitout_Instruction_Type, dict[Needle, Needle_Instruction]] = {first_instruction.instruction_type:
{first_instruction.needle: first_instruction}}
[docs]
def instruction_set(self) -> set[Needle_Instruction]:
"""Get an unordered set of the instructions in the carriage pass.
Returns:
An unordered set of the instructions in the carriage pass.
"""
return set(self._instructions)
[docs]
def rightward_sorted_needles(self) -> list[Needle]:
"""Get needles sorted from left to right.
Returns:
List of needles in the carriage pass sorted from left to right.
"""
return cast(list[Needle], Carriage_Pass_Direction.Rightward.sort_needles(self._needles_to_instruction.keys(), self.rack))
[docs]
def leftward_sorted_needles(self) -> list[Needle]:
"""Get needles sorted from right to left.
Returns:
List of needles in the carriage pass sorted from right to left.
"""
return cast(list[Needle], Carriage_Pass_Direction.Leftward.sort_needles(self._needles_to_instruction.keys(), self.rack))
[docs]
def sorted_needles(self) -> list[Needle]:
"""Get needles sorted by carriage pass direction.
Returns:
List of needles in carriage pass sorted by direction of carriage pass
or from left to right if no direction is given.
"""
if self.direction is None:
return self.rightward_sorted_needles()
else:
return cast(list[Needle], self.direction.sort_needles(self._needles_to_instruction.keys(), self.rack))
[docs]
def instructions_by_needles(self, needles: list[Needle]) -> list[Needle_Instruction]:
"""Get instructions ordered by the given needle list.
Args:
needles: Ordered list of needles involved in the carriage pass.
Returns:
The ordered set of instructions that start from the given needles.
"""
return [self._needles_to_instruction[n] for n in needles]
[docs]
def carriage_pass_range(self) -> tuple[int, int]:
"""Get the leftmost and rightmost needle positions in the carriage pass.
Returns:
Tuple of (leftmost position, rightmost position) in the carriage pass.
"""
sorted_needles = self.rightward_sorted_needles()
return int(sorted_needles[0].racked_position_on_front(rack=self.rack)), int(sorted_needles[-1].racked_position_on_front(rack=self.rack))
[docs]
def rack_instruction(self, comment: str = "Racking for next carriage pass.") -> Rack_Instruction:
"""Create a racking instruction to set up this carriage pass.
Args:
comment: Comment to include with the racking instruction.
Returns:
Racking instruction to set up this carriage pass.
"""
return Rack_Instruction.rack_instruction_from_int_specification(self.rack, self.all_needle_rack, comment)
@property
def direction(self) -> Carriage_Pass_Direction | None:
"""Get or set the direction of the carriage pass.
Setting the direction will reorder the instructions to the given direction.
Should only be used to reorder Xfer Passes.
Returns:
The direction of the carriage pass.
"""
return self._direction
@direction.setter
def direction(self, direction: Carriage_Pass_Direction) -> None:
"""Set the direction of the carriage pass.
Args:
direction: The new direction for the carriage pass.
"""
if not self.xfer_pass:
warnings.warn(Reordered_Knitting_Pass_Warning(direction, self))
self._direction = direction
sorted_needles = self.needles
self._instructions = [self._needles_to_instruction[n] for n in sorted_needles]
@property
def needles(self) -> list[Needle]:
"""Get needles in the order given by instruction set.
Returns:
Needles in order given by instruction set.
"""
needles = [i.needle for i in self._instructions]
if self.direction is not None:
return cast(list[Needle], self.direction.sort_needles(needles, self.rack))
else:
return needles # needles in order of given instructions
@property
def first_instruction(self) -> Needle_Instruction:
"""Get the first instruction given to carriage pass.
Returns:
First instruction given to carriage pass.
"""
return self._instructions[0]
@property
def last_instruction(self) -> Needle_Instruction:
"""Get the last instruction executed in the carriage pass.
Returns:
Last instruction executed in the given carriage pass.
"""
return self._instructions[-1]
@property
def last_needle(self) -> Needle:
"""Get the needle at the end of the ordered instructions.
Returns:
Needle at the end of the ordered instructions.
"""
return self.needles[-1]
[docs]
def contains_instruction_type(self, instruction_type: Knitout_Instruction_Type) -> bool:
"""Check if the carriage pass contains a specific instruction type.
Args:
instruction_type: Instruction type to consider.
Returns:
True if the instruction type is used at least once in this carriage pass.
"""
return instruction_type in self._instruction_types_to_needles
[docs]
def add_instruction(self, instruction: Needle_Instruction, rack: int, all_needle_rack: bool) -> bool:
"""Attempt to add an instruction to the carriage pass.
Args:
instruction: The instruction to attempt to add to the carriage pass.
rack: The required racking of this instruction.
all_needle_rack: The all_needle racking requirement for this instruction.
Returns:
True if instruction was added to pass. Otherwise, False implies that
the instruction cannot be added to this carriage pass.
"""
if self.can_add_instruction(instruction, rack, all_needle_rack):
self._instructions.append(instruction)
self._needles_to_instruction[instruction.needle] = instruction
if instruction.instruction_type not in self._instruction_types_to_needles:
self._instruction_types_to_needles[instruction.instruction_type] = {}
self._instruction_types_to_needles[instruction.instruction_type][instruction.needle] = instruction
return True
else:
return False
[docs]
def compatible_with_pass_type(self, instruction: Needle_Instruction) -> bool:
"""Check if an instruction is compatible with this type of carriage pass.
Args:
instruction: The instruction to consider compatibility with.
Returns:
True if instruction is compatible with this type of carriage pass.
"""
return bool(self.first_instruction.instruction_type.compatible_pass(instruction.instruction_type))
[docs]
def can_add_instruction(self, instruction: Needle_Instruction, rack: int, all_needle_rack: bool) -> bool:
"""Check if an instruction can be added to this carriage pass.
Args:
instruction: The instruction to consider adding to the carriage pass.
rack: The required racking of this instruction.
all_needle_rack: The all_needle racking requirement for this instruction.
Returns:
True if the instruction can be added to this carriage pass. Otherwise, False.
"""
if rack != self.rack:
return False
elif all_needle_rack != self.all_needle_rack:
return False
elif instruction.direction != self._direction:
return False
elif instruction.carrier_set != self.carrier_set:
return False
elif not self.compatible_with_pass_type(instruction):
return False
elif instruction.needle in self._needles_to_instruction: # Already has an instruction at this needle.
return False
elif self._direction is None:
if instruction.needle_2 in self._needles_to_instruction:
return False
elif (self.all_needle_rack # All needle rack
and instruction.needle.is_front != self.last_needle.is_front # last and new instruction on opposite beds
and instruction.needle.racked_position_on_front(self.rack) == self.last_needle.racked_position_on_front(self.rack)): # Last and new instruction at all-needle same position
return True
elif not self._direction.needles_are_in_pass_direction(self.last_needle, instruction.needle, self.rack, self.all_needle_rack):
return False
return True
[docs]
def can_merge_pass(self, next_carriage_pass: Carriage_Pass) -> bool:
"""Check if this carriage pass can be merged with the next one.
Args:
next_carriage_pass: A carriage pass that happens immediately after this carriage pass.
Returns:
True if these can be merged into one carriage pass.
"""
if self.direction == next_carriage_pass.direction and self.compatible_with_pass_type(next_carriage_pass.first_instruction):
next_left_needle, next_right_needle = next_carriage_pass.carriage_pass_range()
if self.direction is Carriage_Pass_Direction.Rightward:
return bool(self.last_needle.position < next_left_needle)
elif self.direction is Carriage_Pass_Direction.Leftward:
return bool(self.last_needle.position > next_right_needle)
return False
[docs]
def merge_carriage_pass(self, next_carriage_pass: Carriage_Pass, check_compatibility: bool = False) -> bool:
"""Merge the next carriage pass into this carriage pass.
Args:
next_carriage_pass: A carriage pass that happens immediately after this carriage pass.
check_compatibility: If true, checks compatibility before merging.
Returns:
True if the merge was successful.
"""
if check_compatibility and not self.can_merge_pass(next_carriage_pass):
return False
for instruction in next_carriage_pass:
added = self.add_instruction(instruction, next_carriage_pass.rack, next_carriage_pass.all_needle_rack)
assert added, f'Attempted to merge {self} and {next_carriage_pass} but failed to add {instruction}.'
return True
[docs]
def execute(self, knitting_machine: Knitting_Machine) -> list[Knitout_Line]:
"""Execute carriage pass with an implied racking operation on the given knitting machine.
Will default to ordering xfers in a rightward ascending direction.
Args:
knitting_machine: The knitting machine to execute the carriage pass on.
Returns:
A list of executed instructions from the carriage pass.
"""
executed_instructions = []
rack_instruction = self.rack_instruction()
updated = rack_instruction.execute(knitting_machine)
if updated:
executed_instructions.append(rack_instruction)
if self.xfer_pass:
self.direction = Carriage_Pass_Direction.Rightward # default xfers to be in ascending order
for instruction in self:
updated = instruction.execute(knitting_machine)
if updated:
executed_instructions.append(instruction)
else:
executed_instructions.append(Knitout_Comment_Line(instruction))
return executed_instructions
[docs]
def __str__(self) -> str:
"""Return string representation of the carriage pass.
Returns:
String representation showing direction, instruction types, and details.
"""
string = ""
indent = ""
if not self.xfer_pass:
string = f"in {self._direction} direction:"
if len(self._instruction_types_to_needles) > 1:
indent = "\t"
string += "\n"
for instruction_type, needles in self._instruction_types_to_needles.items():
string += f"{indent}{instruction_type.value} {list(needles.keys())}"
if self.xfer_pass:
string += f" at {self.rack}"
if self.carrier_set is not None:
string += f" with {self.carrier_set}"
string += "\n"
return string
[docs]
def __list__(self) -> list[Knitout_Line]:
"""Convert carriage pass to list of knitout lines.
Returns:
List of knitout lines from this carriage pass.
"""
return [*self]
[docs]
def __len__(self) -> int:
"""Get the number of instructions in the carriage pass.
Returns:
Number of instructions in the carriage pass.
"""
return len(self._instructions)
[docs]
def __repr__(self) -> str:
"""Return detailed representation of the carriage pass.
Returns:
String representation of the internal instructions list.
"""
return str(self._instructions)
[docs]
def __iter__(self) -> Iterator[Needle_Instruction]:
"""Iterate over the instructions in the carriage pass.
Returns:
Iterator over the instructions.
"""
return iter(self._instructions)
[docs]
def __getitem__(self, item: int | slice) -> Knitout_Line | list[Knitout_Line]:
"""Get instruction(s) by index or slice.
Args:
item: Index or slice to retrieve.
Returns:
Instruction or list of instructions at the specified index/slice.
"""
return self._instructions[item]
[docs]
def __hash__(self) -> int:
"""Get hash of the carriage pass based on creation time.
Returns:
Hash value based on creation time.
"""
return hash(self._creation_time)