# Types and tools to handle moves (traces or jumps). # Last edited on 2021-02-19 14:19:56 by jstolfi import move_IMP; from move_IMP import Move_IMP import pyx class Move(Move_IMP): # An object of the {Move} class represents a /move/, a # straight-line movement of the nozzle: either a /trace/ if the nozzle # is down and extruding material, or a /jump/ if the nozzle is raised # and not extruding. # # The /axis/ of a {Move} object {mv} is the line segment traversed by # the center of the nozzle. The /endpoints/ of the move are those of # that segment. The order of the two points is not necessarily the # direction of motion of the nozzle (which may be unknown). Each # endpoint is a list of 2 float coordinates, in millimeters. # # A {Move} object {mv} has a read-only /nominal width/ in mm, # {width(mv)}, that is mostly used when generating G-code or plotting. # For a trace of length {L} (mm), the volume of material deposited # will be {V = L*T*width(mv)} (mm^3), where {T} is the slice's # thickness (mm). (This formula is valid in the limit of large {L}, # since it does not account for the roundish "caps" of material # deposited at the ends of the move.) The nominal width is zero if and # only if the move is a jump. # # Another read-only attribute of a {Move} is its /execution time/ # {extime(mv)}, in seconds. # # An /oriented move/ is a pair {(mv,dr)} where {mv} is a {Move} object # and {dr} is 0 or 1 to indicate the direction of motion of the nozzle # along the axis. A {Move} object {mv} by itself is assumed to be the # oriented move {(mv,0)}. # # ??? Should add a {maxspeed} atribute. ??? pass def make(p0, p1, wd, parms): # Creates and returns a {Move} object with endpoints {(p0,p1)}, # representing either a trace (if {wd > 0}) or jump (if {wd = 0}. # # The {parms} argument should be a dictionary that specifies the # dynamic parameters of the printer, namely 'acceleration', # 'velocity', and 'z_time', needed to compute the execution time. return move_IMP.make(p0, p1, wd, parms) def displace(omv, ang, disp, parms): # Returns a copy of the oriented move {omv} rotated by {ang} radians # around the origin and translated by the vector {disp}, # in that order. # # The move will be a new {Move} object, even if {ang} and {disp} are # zero. The procedure preserves the width and recomputes the execution # time, in case it depends on the move's direction. return move_IMP.displace(omv, ang, disp, parms) def connector(omvprev, omvnext, parms): # Returns a {move.Move} object {mvcn} that connects the end of oriented # move {omvprev} to the start of the oriented move {omvnext}. # # The procedure decides whether to use a jump or a trace based on the # widths and directions of the two moves and the distance between the # endpoints. In particular, uses a jump if either of the two moves is a # jump, or if they have different nominal widths, or if the distance to # be spanned is large (compared to their nominal width), or if they are # too short (ditto), or the turning angles from {omvprev} to {mvcn} and # from {mvcn} to {omvnext) are both zero or have opposite signs, or # if a jump would be faster than a link. return move_IMP.connector(omvprev, omvnext, parms) def connector_must_be_jump(vpq, vprev, vnext, wd, parms): # Returns {True} if a connecting move between two traces is better be a # jump than a trace of width {wd}. Assumes that {vpq} is the displacement vector to be # covered by the connector, and {vprev,vnext} are the displacement # vectors of the previous and next traces. return move_IMP.connector_must_be_jump(vpq, vprev, vnext, wd, parms) def is_jump(omv): # Returns {True} if {omv} is an oriented or unoriented jump -- that is, # if its nominal width is zero. return move_IMP.is_jump(omv) def width(omv): # Returns the nominal width of the oriented or unoriented move {omv}. return move_IMP.width(omv) def pini(omv): # Retuns the initial endpoint of an oriented move {omv}, taking the # orientation bit into account. return move_IMP.pini(omv) def pfin(omv): # Retuns the final endpoint of an oriented move {omv}, taking the # orientation bit into account. return move_IMP.pfin(omv) def bbox(omv): # Returns a bounding box of the oriented move {omv}, as a pair of # points {(plo, phi)} that its lower left and upper right corners. # # It is the smallest axis-aligned rectangle that contains the move's # endpoints only. Note that the nominal "sausage" of a trace, as well as # decoration such as dots and arrowheads, may extend outside the box. return move_IMP.bbox(omv) def rev(omv): # Returns the reversal of the oriented move {omv}, namely the same # {Move} object with the orientation bit {dr} complemented. return move_IMP.rev(omv) def orient(omv, dr): # Applies {rev} to the oriented move {omv}, {dr} times. # Thus the result is {omv} if {dr} is even, and {rev(omv)} # if {dr} is odd. # # If the {omv} argument is an unoriented {Move} object {mv}, the # result is {(mv,dr)}. return move_IMP.orient(omv, dr) def unpack(omv): # If {omv} is an oriented move, returns the underlying # {Move} object and the direction bit as two separate results. # If {omv} is already a move object, returns {omv} and {0}. # Also checks whether the object is indeed a {Move}. return move_IMP.unpack(omv) # TIMING def extime(omv): # Returns the execution time of the oriented move {omv}. return move_IMP.extime(omv) def cover_time(omv, m, parms): # Returns the time when the nozzle passes by the point {m} while # executing the oriented move {omv} in the specified direction, # counted from the beginning of the execution. # # Specificaly, returns the time that the nozzle takes to travel from # {pini(omv)} to the point on the axis of the move that is closest to # point {m}. The result is always between 0 and {extime(omv)}. return move_IMP.cover_time(omv, m, parms) def nozzle_travel_time(dpq, jmp, dpm, parms): # Computes the time neeed to travel distance {dpq}, including # acceleration and deceleration, and (if {jmp} is true) the raising # and lowering of the nozzle. # # If {dpm} is not {None}, returns instead the time for the nozzle to # travel the initial distance {dpm} along that move (which must be # between 0 and {dpq})a. # # The {parms} parameter is as described for {extime}. return move_IMP.nozzle_travel_time(dpq, jmp, dpm, parms) # PLOTTING def plot_standard(c, omv, dp, layer, ctrace, waxis, axis, dots, arrow, matter): # Plots sthe move {omv} on the {pyx} context {c} displaced by {dp} # in some standard style. # # If {omv} is a trace, draws it as a "sausage" -- a thick rectangle # with round caps at the end, with a width slightly smaller than its # nominal width {wd=width(mv)}. The sausage is painted with} color # {ctrace}, wich must be a {pyx.color.rgb} value. If {axis} is true, # also plots the axis of the trace. If {dots} is true, also plots dots # at the endpoints. If {arrow} is true, also plots an arrowhead midway # along the move, showing its orientation. These items are drawn with # a darkened version of color {ctrace}. If {matter} is true, plots a # grayish "overflow" sausage under the main one, slightly *wider* than # {wd}, to show the estimated area covered by the extruded material. # # If {omv} is a jump, the main and overflow sausages is omitted, and # the axis always drawn drawn as a dashed black line with dots and # arrowhead. # # The width of the axis of a trace or jump will be {waxis}, which should be positive. # The sizes of dots and arrows will be fixed proportions of {waxis}. # # If {ctrace} is {None}, uses a default color. If {dp} is {None}, # assumes {(0,0)} (no displacement). # # The plot is done in 4 passes or /layers/: (0) the overflow sausage # of the estimated extruded material, if {omv} is a trace and {matter} # is true, (1) the main sausage, if {omv} is a trace, (2) the axis, # dots, and arrowheads, if {omv} is a trace and they have been # requested, and (3) the axis, dots, and arrowhead, if {omv} is a # jump. If the parameter {layer} is not {None}, it must be an integer # in {0..3}, in which case only that layer is plotted. move_IMP.plot_standard(c, omv, dp, layer, ctrace, waxis, axis, dots, arrow, matter) def plot_layer(c, omv, dp, clr, waxis, dashed, wdots, szarrow): # Plots the move (trace or jump) {omv} on the {pyx} context {c}. # # All items will be drawn with the color {clr}. If {clr} is {None}, does nothing. # # The plot will be displaced by the vector {dp} (a 2-tuple of floats). # # If {waxis} is a positive number, draws the move's axis as a # rectangle of width {waxis} with round caps centered at the move's # endpoints. # # If {dashed} is true, the axis line will be dashed. # # If {wdots} is a positive number, plots round dots of that diameter at the # endpoints # # If {szarrow} is a positive number, draws an arrowhead with that size # at the midpoint of the axis, to show the move's direction. # The arrow is drawn even if the axis is not drawn. # # If {None} is given as {waxis}, {wdots}, or {szarrow}, the value 0 is # assumed. If {dp} is {None}, assumes {(0,0)} (no displacement). move_IMP.plot_layer(c, omv, dp, clr, waxis, dashed, wdots, szarrow)