# Tools and types to handle blocks -- precomputed subpaths of a tool-path.
# Last edited on 2021-03-21 15:33:24 by jstolfi

import block_IMP; from block_IMP import Block_IMP

class Block(Block_IMP):
  # An object {bc} of class {Block} represents a /block/, a collection
  # of /alternative paths/ or /choices/ that can be used to create some
  # specific of part the slice. If the block {bc} has {n} alternatives,
  # the function {choice(bc,ich)} below returns the alternative path with
  # index {ich} in {0..n-1}.
  #
  # In a simple example, let {P1,...,Pm} be a set {m >= 2} raster lines
  # lying on consecutive scan lines, each making significant contact only
  # with the previous and following ones; except that {P1} may have
  # contacts with zero or more than one rasters below it, and {Pm} may
  # have contacts with zero or more than one above it. We may decide that
  # those rasters are to be executed consecutively, in alternating
  # directions. There are four ways to do that: one can start from {P1} or
  # from {Pm}, and the starting raster line may be executed from left to
  # right or from right to left.  
  #
  # These four alternative paths are actually two distinct paths {Q0}
  # and {Q1} and their reversals. While all these four paths share the
  # same {Move} objects that represent the raster traces, {Q0} and {Q1}
  # also have different /connectors/ -- extra jumps, traces, or paths
  # that connect one end of one raster line to the startof the next one.
  # Therefore {Q0} and {Q1} will usually have different execution times
  # and different relative covering times for external contacts.
  #
  # By joining those rasters into a block, the user is specifying that
  # exactly one of these four choices -- {Q0}, {Q1}, {rev(Q0)}, or
  # {rev(Q1)} -- should be inserted into the final tool-path, as a a
  # single stretch of consecutive moves.
  #
  # As another example, a component of the slice's contour -- such as a
  # hole or an ``island'' -- can be represented as a block of {m} paths.
  # Each path covers the same trajectory in the same direction, but starts
  # and ends at a different point.
  #
  # Joining several elementary paths of the input data set into blocks
  # greatly simplifies the path optimization problem, by reducing the
  # number of choices from {2^N*N!} for {N} paths to {K^M} for {M} blocks,
  # each with a geometric average of {K} choices (including reversals when
  # applicable). 
  #
  # Joining paths into blocks saves computing time also by making it
  # unnecessary to recompute the cover and cooling times of internal
  # contacts (between traces of the same alternative of a block) since
  # their cooling times are fixed and can be checked against the cooling
  # limit {Delta} as the block is created.
  #
  # On the other hand, by limiting the choices of the heuristic, this
  # optimization may prevent it from finding the optimal path. It may even
  # make the problem unsolvable, if a block is so large that doing it all
  # in one go would inevitably violate some cooling constraints elsewhere.
  # Still, if the blocks are chosen judiciously, this negative aspect may
  # be minimized. In fact, this optimization may result in a better final
  # path, because it would prevent the heuristic from finding some expensive
  # paths and stopping there.
  #
  # ASSEMBLING THE BLOCKS
  #
  # In general, one should consider joining several elements of the
  # original problem into a block only if they are close to each other
  # and mostly far from the rest of the slice; so that a good path is
  # very likely to execute them consecutively, once it starts with any
  # of them.
  #
  # For simple blocks -- like sequences of adjacent raster lines, or the
  # traces that make up a closed contour line -- the choices that are
  # worth considering can be identified and constructed by ad-hoc
  # procedures. In more complex situations, one may have to use the
  # heuristic itself to choose the alternative paths.
  #
  # For instance, if the block is to consist of the contours of six
  # small islands arranged in a circle, somewhat distant from the rest
  # of the slice, it may be desirable to precompute some or all of the
  # {6*5/2 = 15} optimal tool-paths that trace the six contours in one
  # go, begining and ending at the outermost point of each pair of
  # contours. That pre-processing may still be better than letting each
  # island be a separate input block, and letting the heuristic
  # discover by trial and error that those contours are indeed better
  # executed consecutively and *repeadly* try to find the optimal order
  # among all the {6! = 120} possibilities.
  #
  pass

def from_paths(OPHS):
  # Creates and returns a {Block} object {bc} whose alternatives are the 
  # oriented paths in the list or tuple {OPHS}.  Specifically, {choice(bc,i)}
  # will be {OPHS[i]} for {i=0,1,...,n-1} where {n=len(OPHS)}.
  #
  # The list must have at least one path. It does not make sense for a
  # choice to be an empty path (with zero moves), or to begin or end
  # with a jump.
  #
  # Note that if a path {oph} its to be considered in its two possible
  # directions, both {oph} and {path.rev(oph)} must be included in
  # the list {OPHS}.  This only makes sense if the path's initial
  # and final point are well-spaced, or there will be cooling constraints
  # between some of its traces and other blocks.
  return block_IMP.from_paths(OPHS)

def nchoices(bc):
  # Returns the number of choices in the {Block} object {bc}.
  return block_IMP.nchoices(bc)
  
def choice(bc, ich):
  # Returns the oriented path which is the alternative of index {ich} 
  # of the block {bc}. The index {ich} must be in
  # {0..nch-1} where {nch=nchoices(bc)}.
  return block_IMP.choice(bc, ich) 


def add_ct(bc, ct):
  block_IMP.add_ct(bc, ct)

def get_cts(bc):
  return block_IMP.get_cts(bc)


def set_used(bc, us):
  # Sets the {used(bc)} attribute to the bool value {us}.
  block_IMP.set_used(bc, us)

def used(bc):
  # A mutable bool that tells whether {bc} has been used -- that is,
  # whether one of its choices has been included in the current prefix path {Q}.
  return block_IMP.used(bc)
  

def bbox(BCS):
  # The parameter {BCS} should be a list or tuple of {Block} objects.
  # Returns the bounding box of all choices of all those blocks,
  # as a pair of points {(plo, phi)} that its lower left and upper right
  # corners. If the list {BCS} is empty, return {None}.
  #
  # The bounding box is the smallest axis-aligned rectangle that
  # contains all the endpoints of all moves of all choices of those
  # blocks. Note that decoration such as move axes, dots, and
  # arrowheads, as well as the sausages of traces, may extend outside
  # the box.
  return block_IMP.bbox(BCS)


def moves(bc):
  # Returns a list of all the {Move} objects that occur in all the choices
  # of the {Block} {bc}, without orientation bits and without repetitions.
  return block_IMP.moves(bc)


def find_choice_with_move(bc, omv):
  # Given a {Block} object {bc} and an oriented move {omv}, returns the
  # index of the first choice of {bc} contains that move or its reverse.
  # If {bc} has no such choice, returns {None}.
  return block_IMP.find_choice_with_move(bc, omv)

def find_block_with_move(BCS, omv):
  # Given a list {BCS} of {Block} objects and an oriented move {omv},
  # returns the index in {BCS} of the first block that has some choice
  # that contains that move or its reverse. If there is no such block in
  # {BCS}, returns {None}.
  return block_IMP.find_block_with_move(BCS, omv)

def min_extime(bc):
  # Returns the minimum of {path.extime(oph)} for all choices {oph} of 
  # block {bc}.
  return block_IMP.min_extime(bc)