# Tools and types for representing seams (sets of contacts) between blocks. # Last edited on 2021-05-31 17:12:25 by jstolfi import seam_IMP; from seam_IMP import Seam_IMP import contact import block import move import move_parms import path import pyx class Seam(Seam_IMP): # An object of the {Seam} class represents a /seam/ between two # {Block} ojects: a set of zero or more {Contact} objects between # traces of that occur in those blocks. # # These blocks are the /sides/ of the seam, indexed 0 and 1. The # indexing of sides of the seam must be consistent with the indexing # of sides of its contacts. Namely, for each contact {ct} in a seam # {sm}, the trace that is side {i} of {ct} must be in the block that # is side {i} of {sm} # # A seam may be /directed/, in which case it specifies that some # choice of the block that is side 0 must be executed before the any # choice of the block that is side 1. Unless stated otherwise, the # procedures below do not care whether the {Seam} argument {sm} is # directed or not. # # While an undirected seam with zero contacts has no effect, a # directed one still provides non-trivial information, namely # the ordering constraint on the two blocks. # # A path {ph} is said to /cover/ side {i} of a seam {sm} if {ph} has a # subpath that is a choice of the block that is a side {i} of {sm}. # The path /closes/ the seam if it covers both sides of {sm}. # # A {Seam} object also has a mutable /name/ attribute, that is used # for debugging and documentation. It must be either a string or {None}. # pass def make(bc0, bc1, cts, drc): # Creates and returns a {Seam} object {sm} whose sides 0 and 1 are # the {Block} objects {bc0} and {bc1} (which must be distinct). # # The parameter {cts} must be a list of {Contact} objects. For every # element {ct} of the list, side {i} (0 or 1) of {ct} must be a trace # {mvi} that occurs in at least one choice of the corresponding block # ({bc0} or {bc1}, respectively). # # The {drc} parameter is a bool; if {True}, it specifies that the # seam is drected (meaning that any choice of{bc0} must be executed # before any choice of {bc1}). return seam_IMP.make(bc0, bc1, cts, drc) def get_contacts(sm): # Returns the contacts of {sm}, as a tuple of {Contact} objects. return seam_IMP.get_contacts(sm) def side(sm, i): # The parameter {i} must be 0 or 1. Returns the {Block} # object {bc} that is the trace on side {i} of the seam. return seam_IMP.side(sm, i) def which_side(bc, sm): # The parameter {bc} must be a {Block} object, and {sm} must be a # {Seam} object. If {bc} is one of the sides of {sm}, returns the # index (0 or 1) of that side. Otherwise returns {None}. return seam_IMP.which_side(bc, sm) def is_directed(sm): # Returns {True} if {sm} is a directed seam, {False} otherwise. return seam_IMP.is_directed(sm) def bbox(SMS): # The parameter {CTS} must be a list of {Seam} objects. Returns a # bounding box for all the contacts in all those seams, as a pair of # points {(plo, phi)} that its lower left and upper right corners. If # the list is empty, or all seams have zero contacts, returns {None}. # # The bounding box is the smallest axis-aligned rectangle that # contains endpoints (only) of all those contacts. Note that # decorations such as tics and arrowheads, as well as bits of the # contact lines, may extend outside the box. Moreover, the traces that # are the sides of those contacts are NOT included in the result. return seam_IMP.bbox(SMS) # TIMING ESTIMATORS def min_max_tcool(sm, mp_jump): # The parameter {sm} must be a {Seam} object. Let its sides # be the {block} objects {bc0} and {bc1}. # # The procedure considers every pair of choices {oph0} of {bc0} and # {oph1} of {bc1}. # # For each of those pairs of paths, and each contact {ct} in the seam that is relevant for # them (whose sides are in those two paths), the procedure computes the cooling time # {tc(ct,oph0,oph1)} assuming that {oph0} and {oph1} are added in # sequence to the tool-path. If the # contact is directed, considers only {oph0} before {oph1}, else # considers both possibilities, {oph0} before or after {oph1}. # # The procedure then returns the minimum value of {tc} over all pairs # of choices {oph0,oph1}. In particular, if there is some pair # {oph0,oph1} that fails to close every contact of {sm}, the # procedure returns {-inf}. # # The procedure assumes that the connector between {oph0} and {oph1}, # if necessary, would be either a jump with parameters {mp_jump}, or # a link with same parameters as the connected moves, as determined by # {move.connector} with {use_jumps=use_links=False}. return seam_IMP.min_max_tcool(sm, mp_jump) def min_max_tcool_list(SMS, mp_jump): # Given a list of contacts {SMS}, returns the maximum of {min_max_tcool(sm, mp_jump)} # for all seams in {SMS}. return seam_IMP.min_max_tcool_list(SMS, mp_jump) def print_contact_table(wr, BCS, SMS, MVS): # Given a list {BCS} of {Block} objects and a list {SMS} of {Seam} # objects, prints a table that shows which choice of which block contains # each side of each contact in each seam. # # Each row of the table starts with "S{i}:{k}:{j} {d}" to mean side {j} (0 # or 1) of contact {ct} with index {k} in the seam {sm = SMS[i]). The field {d} is 0 if that seam is # undirected, 1 if directed. Each column is headed with "B{r}:{s}" to # mean choice with index {s} of block {bc = BCS[r]}. The entry in that row and # column is the index of the trace {contact.side(ct,j)} (or its # reverse) among the moves of the path; or "---" if the trace does not # occur in that path. # # If {MVS} is not {None}, it must be a list of {Move} objects. In that case, # for each contact side, the index {t} of the move in that list is printed # as "M{t}" at the start of each row. return seam_IMP.print_contact_table(wr, BCS, SMS, MVS) # PLOTTING def plot_to_files(fname, SMS, clr_ct, wd_ct, BCS, CLRS, rwd, wd_axes, matter): # The arguments {SMS}, {BCS}, and {CLRS} must be lists or tuples of # {Seam} objects, {Block} objects, and {pyx.color.rgb} objects, # respectively. # # Say that a seam from the given list {SMS} is /relevant/ if at least # one of its sides is a block from the given list {BCS}. # # The procedure plots every contact of every relevant seam, with # block choices that include its sides, if they are in {BCS}. # # The plot will be a vertical stack of sub-plots. Each sub-plot will # show one or more contacts from {SMS}, and one or more blocks of {BCS} # each represented by one specific choice. There will be a measurement # grid in the background. # # Each sub-plot will show at most one choice of each block # of {BCS}. If a contact {ct} from a seam {sm} appears in # a sub-plot, then, for each block {bc} of # {BCS} that is a side of {sm}, a choice of {bc} that includes # the trace that is the corresponding side of {ct} will be plotted. # # The decision of which block choices to show in each sub-plot is # complex and partially arbitrary. However, there will be enough # sub-plots for all contacts of all seams of {SMS} to be drawn. Block # choices that are not necessary for this goal will not be plotted; in # particular, if the choices of a block include a path and its # reversal, only one of them will appear. Also, if a block {bc} of # {BCS} is not a side of any seam, no choice of {bc} will appear in # any sub-plot. A block choice may be omitted from also if the traces # that involve contacts of {SMS} are shared with other choices that # have been plotted and used to show those contacts. Conversely, the # same choice of a block {BCS[i]} may be plotted several times, if # necessary to show all contacts. # # ??? Change to show also all choices of all blocks at least once? ??? # # Every sub-plot will show the matter shadow of all choices of all # blocks of {BCS}. # # Each contact {ct} of every relevant seam {sm} is plotted in some # sub-plot with {contact.plot_single}, using the color {clr_ct} and # line of width {wd_ct}. If the seam {sm} is directed, there will be # a transversal arrowhead over the midpoint of {ct} with size # proportional to {wd_ct}, pointing from its side 0 towards its side 1, # # The parameter {CLRS} is {None} or a list of {Pyx} colors. If the # list has a single color, all trace sausages of all block choices # will be drawn with that color. If the list has more than one # element, it must have the same length as {BCS}, and the choices of # block {BCS[ibc]} will be showsn with color {CLRS[ibc]}. If {CLRS} is # {None}, the procedure chooses a default single color. The fat # sausage of each trace will be drawn with width {rwd*wd} where {wd} is # the trace's nominal width. # # The procedure writes the whole plot to files "{fname}.{ext}" # where {ext} is "eps", "png", and "jpg". # Trace axes and jump lines will be drawn with line width {wd_axes}. # If {grid} is true, a millimeter grid and a frame will be drawn on # the background of each sub-plot. # # If {dp} is not {None}, it must be a 2-vector (pair of floats), and # all elements of the bottom-most sub-plot will be displaced by {dp}. # In any case, successive sub-plots will be displaced vertically by # a fixed distance sufficient to avoid overlaps. # seam_IMP.plot_to_files(fname, SMS, clr_ct, wd_ct, BCS, CLRS, rwd, wd_axes, matter) def plot_single(c, sm, dp, clr, wd, oph0, oph1, tics): # Plots on the {pyx} context {c} every contact {ct} of the seam {sm}. # # If {oph0} is not {None}, only plots {ct} if the trace that is its # side 0 appears in the oriente path {oph0}. Similarly, if {oph1} is # not {None}, only plots {ct} if its side 1 appears in the {oph1}. # These paths are NOT plotted. # # Beware that, if {oph1} and/or {oph2} are {None}, some contacts may # be plotted on top of each other. # # The plot will all be displaced by the vector {dp} (a 2-tuple of floats). # # Each contact {ct} is plotted with {contact.plot_single}, using the # color {clr} and line of width {wd}. If the seam {sm} is directed, # there will be a transversal arrowhead over the midpoint of {ct} # pointing from its side 0 towards its side 1, Otherwise, if {tics} is # true, a tic of that length will be drawn instead. seam_IMP.plot_single(c, sm, dp, clr, wd, oph0, oph1, tics) # PRINTING AND DEBUGGING def get_name(sm): # Given a {Seam} object {sm}, returns its name attribute, if not {None}. # If that attribute is {None}, returns "C?" instead. return seam_IMP.get_name(sm) def set_name(sm, name): # Saves the string {name} as the name attrbute of the {Seam} object {sm}. seam_IMP.set_name(sm, name) def tag_names(SMS, tag): # Prepends the string {tag} (if not {None}) to the names of all {Seam} # objects in {SMS}. The objects had better be all distinct. seam_IMP.tag_names(SMS, tag) def show(wr, sm, contacts, ind, wna, wnc): # Writes the data of {sm} to {wr} in a human-readable format. # # The description has: # # * The name of the seam, as returned by {get_name}. # * The directed flag, 0 or 1 # * The names of the two blocks wich are the sides of the seam # * The number of contacts in the seam # # If {contacts} is true, the description also includes the list of the # names of the contacts that comprise the seam {sm}. # # The names of the seam and of the two blocks are padded to {wna} # columns and left-aligned. The number of contacts is padded to {wnc} # columns. The whole description is printed in a single line, is # indented by {2*ind} blanks, and is NOT ended with a newline. seam_IMP.show(wr, sm, contacts, ind, wna, wnc) def show_list(wr, SMS, contacts, ind): # Writes to file {wr} a readable description of the {Seam} objects in # the list {SMS}. The output consists of a header and then # the description of each seam, one per line, as produced with # {show} with the given parameter {contacts}; prefixed by # {2*ind} spaces and its index in {SMS}. seam_IMP.show_list(wr, SMS, contacts, ind)