# 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)