# Tools and types to handle blocks -- precomputed subpaths of a tool-path. # Last edited on 2021-05-10 13:48:32 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. # # A {Block} object also has a mutable /name/ attribute, that is used # for debugging and documentation. It must be either a string or {None}. # 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 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) # PLOTTING def plot_to_files(fname, BCS, CLRS, wd_axes, matter): # The argument {BCS} must be a list or tuple of {Block} objects. The # {CLRS} argument must be a list or tuple of {pyx.color.rgb} objects. # # Plots the blocks of {BCS} with {plot_standard} below, with the same # {CLRS}, {wd_axes}, and {matter} parameters, {nch} equal to the max # number of choices of any block in {BCS}, some default choices for # the style parameters ({axes}, {dots}, and {arrows}). A millimeter # grid will be painted in the background. # # Then the procedure writes the plot (with all sub-plots) to files # "{fname}.{ext}" where {ext} is "eps", "png", and "jpg". block_IMP.plot_to_files(fname, BCS, CLRS, wd_axes, matter) def plot_standard(c, BCS, nch, dp, ystep, CLRS, wd_axes, axes, dots, arrows, matter): # The arguments {BCS} abd {CLRS} must be lists or tuples of {Block} objexts # and {pyx.color.rgb} objects, respectively. # # Plots onto the {pyx} canvas {c} the choices {0..nch-1} of all blocks # of {BCS}. # # The plot will be a vertical stack of {nch} sub-plots. The bottom # sub-plot (index 0) will show choice 0 of every block, displaced by # the 2-vector {dp}; if {dp} is {None}, the proceedure assumes {(0,0)} # (no displacement). The sub-plot just above it (index 1) will show # choice 1 of every block, and so on. Each subsequent choice will be # displaced vertically by an additional {ystep} millimeters. # # If a block does not have a choice with index {i}, it will be omitted # from sub-plot {i}. However, if {matter} is true, the block's /matter # shadow/ -- the combined extent of material of all choices -- and # painted on all sub-plots. # # If {CLRS} has a single element, uses that color for all trace # sausages. If {CLRS} is {None}, uses some default color Otherwise # {CLRS} must have the same length as {BCS}, and all traces of block # {BCS[k]} will be painted wit color {CLRS[k]}. # block_IMP.plot_standard(c, BCS, nch, dp, ystep, CLRS, wd_axes, axes, dots, arrows, matter) def plot_choice(c, bc, ich, dp, clr, wd_axes, axes, dots, arrows): # Plots onto the {pyx} canvas {c} the choice with index {ich} of block # {bc}, if there is such a choice. # # The trace sausages are plotted with color {clr}. The whole plot is # shifted by the 2-vector {dp}; if {dp} is {None}, the proceedure # assumes {(0,0)} (no displacement). # # If {matter} is true, the procedure plots the block's matter shadow # beneath the traces and jumps -- even if the block {bc} has no choice # with index {ich}. The parameters {wd_axes,axes,dots,arrows} are as in # {path.plot_standard}. block_IMP.plot_choice(c, bc, ich, dp, clr, wd_axes, axes, dots, arrows) def plot_matter_shadow(c, bc, dp): # Plots to the {pyx} canvas {c} the matter shadow of block {bc}, namely # the union of the estimated matter extent of all traces in all choicesa. block_IMP.plot_matter_shadow(c, bc, dp) # DEBUGGING def validate(bc): # Runs some consistency checks on the {Block} object {bc}. Aborts with assert failure # if it detects any errors. block_IMP.validate(bc) def get_name(bc): # Given a {Block} object {bc}, returns its name attribute, if not {None}. # If that attribute is {None}, returns "C?" instead. return block_IMP.get_name(bc) def set_name(bc, name): # Saves the string {name} as the name attrbute of the {Block} object {bc}. block_IMP.set_name(bc, name) def show(wr, bc, paths, ind, wna, wnc): # Writes to file {wr} a readable description of the block {bc}. # # The description includes: # # * the name of the block, as returned by {get_name} # * the number of choices {nch} # # If {paths} is true, the description also includes the list of the # names of the paths that comprise the block {bc}; namely, the # sequence {path.get_name(block.choice(bc,ich))} for {ich} in # {range(nch)}. # # The name is padded to {wna} columns and left-aligned. The number of # choices is padded to {wnc} columns. The whole # description uses one line, is indented by {2*ind} blanks, and does # NOT end with a newline. block_IMP.show(wr, bc, paths, ind, wna, wnc) def show_list(wr, BCS, paths): # Writes to file {wr} a readable description of the {Block} objects in # the list {BCS}. The output consists of a header and then # the description of each block, one per line, as produced with # {show} with the given parameter {paths}; prefixed by its index in {BCS}. block_IMP.show_list(wr, BCS, paths)