# Tools and types for representing seams (sets of contacts) between blocks. # Last edited on 2021-10-14 12:56:24 by stolfi 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. # # Those blocks are the /sides/ of the seam. The two sides {bc[isd]}, for {isd} in {0,1}, # can be obtained with {side_block(sm,isd)}; and the set {CTS} of its contacts # is {get_contacts(sm)}. # # A path {oph} is said to /cover/ side {isd} of a seam {sm} if {oph} # has a subpath that is a choice of the block that is a side {isd} of # {sm}. The path /closes/ the seam if it covers both sides of {sm}. # # VALIDITY # # To be valid, a seam must satisfy: # # * The two side blocks must be distinct and have disjoint moves. That is. # the {Move} objects used by the choices of {bc[0]} must not be used by # any choice of {bc[1]}, and vice-versa. # # * The indexing of sides of the seam and of its contacts must be # consistent; namely, for each contact {ct} in {CTS} the trace that # is side {isd} of {ct} must be used by some choice of the block # {bc[isd]}. # # * The seam must include *only* those contacts between those two blocks. That is, # for every contact {ct} in {CTS} and each {isd} in {0,1} there must # be a choice {oph} in {bc[isd]} that uses the move # {contact.side_move(ct,isd)}. # # * Conversely, the seam must include all such contacts. Namely, for # any two choices {oph0,oph1} of {bc[0]} and {bc[1]}, {CTS} must # contain the intersection of {path.contacts(oph0,0)} and # {path.contacts(oph1,1)}. # # * If the seam is non-empty, it must be impossible to pick # choices from its blocks that have no contacts between them. # Namely, for each {isd} in {0,1} and every choice {oph} # of {bc[isd]}, there must be at least one contact {ct} in {CTS} # such that {oph} is listed in {contact.get_side_paths(ct,isd)}. # # A non-empty seam imposes contraints between the choices of its two # blocks that can be included in the final tool-path. If the tool-path # includes choice {oph[isd]} of each block {bc[isd]}, there must be at # least one contact shared by those two paths. # # DIRECTED SEAMS # # A seam may be /directed/, in which case it specifies that some # choice of the block that is side 0 must be fabricated 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 {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 {isd} (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 fabricated # 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_block(sm, isd): # The parameter {isd} must be 0 or 1. Returns the {Block} # object {bc} that side side {isd} of the seam. return seam_IMP.side_block(sm, isd) def which_side_block(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_block(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_rcool(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 ratio {rc(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 {rc} 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_rcool(sm, mp_jump) def min_max_rcool_list(SMS, mp_jump): # Given a list of contacts {SMS}, returns the maximum of {min_max_rcool(sm, mp_jump)} # for all seams in {SMS}. return seam_IMP.min_max_rcool_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{ism}:{kct}:{jsd} {d}" to mean side {jsd} (0 # or 1) of contact {ct} with index {kct} in the seam {sm = SMS[ism]). 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_move(ct,jsd)} (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 {bc} of {BCS} 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 has_name(sm): # Returns {True} if and only {sm}'s name atribute is not {None}. return seam_IMP.has_name(sm) 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, pref, sm, suff, contacts, 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 preceded by {pref} (if not {None}, and # followed by {suff} (if not {None}). It is NOT terminated by a # newline, unless provided by {suff}.. seam_IMP.show(wr, pref, sm, suff, contacts, wna, wnc) def show_list(wr, pref, SMS, suff, contacts): # 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 # {pref} spaces and its index in {SMS}, and followed by {suff} and a newline. seam_IMP.show_list(wr, pref, SMS, suff, contacts)