def __init__(self, strPort=""): self.port = ebb_serial.openPort(strPort) if self.port == None: print("Try to open port again") self.port = ebb_serial.openPort("") if self.port != None: print("Comport opened...") self.sleeptime_pen = 0.1 self.speedlimit = float(12000) / float(1000) # 20000 steps/500ms
def effect( self ): '''Main entry point: check to see which tab is selected, and act accordingly.''' self.svg = self.document.getroot() self.CheckSVGforEggbotData() if ((self.options.tab == '"Help"') or (self.options.tab == '"options"') or (self.options.tab == '"timing"')): pass else: self.serialPort = ebb_serial.openPort() if self.serialPort is None: inkex.errormsg( gettext.gettext( "Failed to connect to EggBot. :(" ) ) if self.options.tab == '"splash"': self.allLayers = True self.plotCurrentLayer = True self.svgNodeCount = 0 self.svgLastPath = 0 unused_button = ebb_motion.QueryPRGButton(self.serialPort) #Query if button pressed self.svgLayer = 12345; # indicate that we are plotting all layers. self.plotToEggBot() elif self.options.tab == '"resume"': unused_button = ebb_motion.QueryPRGButton(self.serialPort) #Query if button pressed self.resumePlotSetup() if self.resumeMode: self.plotToEggBot() elif ( self.options.cancelOnly ): pass else: inkex.errormsg( gettext.gettext( "Truly sorry, there does not seem to be any in-progress plot to resume." ) ) elif self.options.tab == '"layers"': self.allLayers = False self.plotCurrentLayer = False self.LayersPlotted = 0 self.svgLastPath = 0 unused_button = ebb_motion.QueryPRGButton(self.serialPort) #Query if button pressed self.svgNodeCount = 0; self.svgLayer = self.options.layernumber self.plotToEggBot() if ( self.LayersPlotted == 0 ): inkex.errormsg( gettext.gettext( "Truly sorry, but I did not find any numbered layers to plot." ) ) elif self.options.tab == '"setup"': self.setupCommand() elif self.options.tab == '"manual"': self.manualCommand() if self.serialPort is not None: ebb_motion.doTimedPause(self.serialPort, 10) #Pause a moment for underway commands to finish... ebb_serial.closePort(self.serialPort) self.svgDataRead = False self.UpdateSVGEggbotData( self.svg ) return
save_img(r, c) # c0->c99 requires only 98 movements if c != cols - 1: ebb_motion.doABMove(port, 0, STEPS_X, duration_x) time.sleep(duration_x / 1000) # move back to the first column ebb_motion.doABMove(port, 0, -1 * STEPS_X * (cols - 1), return_duration_x) time.sleep(return_duration_x / 1000) if r != rows - 1: ebb_motion.doABMove(port, STEPS_Y, 0, duration_y) time.sleep(duration_y / 1000) # move back to home position (0, 0) ebb_motion.doABMove(port, -1 * STEPS_Y * (rows - 1), 0, return_duration_y) time.sleep(return_duration_y / 1000) if __name__ == "__main__": duration = 200 connect() port = ebb_serial.openPort() ebb_motion.sendDisableMotors(port) move_over_page(port, COLUMNS, ROWS, duration) ebb_motion.sendDisableMotors(port) sock_image.close()
def __init__(self): self.port = ebb_serial.openPort() self.sleeptime_pen = 0.15 self.speedlimit = float(15000) / float(1000) # 20000 steps/500ms
def effect(self): '''Main entry point: check to see which mode/tab is selected, and act accordingly.''' self.versionString = "AxiDraw Merge - Version 2.0.0 dated 2018-07-10" self.spewDebugdata = False self.start_time = time.time() self.pageDelays = 0.0 self.rows_plotted = 0 self.serial_port = None self.svg_data_written = False self.csv_data_read = False self.csv_data_written = False self.csv_file_path = None self.csv_row_count = None self.delay_between_rows = False # Not currently delaying between copies self.b_stopped = False # Not currently stopped by button press #Values to be read from file: self.svg_rand_seed_Old = float(1.0) self.svg_row_old = float(0.0) # Last row plotted. self.svg_rand_seed = float(1.0) self.svgRow = int(1) self.row_to_plot = 1 skipSerial = False if self.options.preview: skipSerial = True # Input sanitization: self.options.mode = self.options.mode.strip("\"") self.options.single_type = self.options.single_type.strip("\"") self.options.data_action = self.options.data_action.strip("\"") self.options.fontface = self.options.fontface.strip("\"") self.options.setup_type = self.options.setup_type.strip("\"") self.options.resume_type = self.options.resume_type.strip("\"") if (self.options.page_delay < 0): self.options.page_delay = 0 if (self.options.mode == "model"): return if (self.options.mode == "options"): return if (self.options.mode == "timing"): return if (self.options.mode == "csv"): skipSerial = True if (self.options.mode == "text"): return if (self.options.mode == "version"): # inkex.errormsg( gettext.gettext(self.versionString)) # Accessible from CLI only return import axidraw # https://github.com/evil-mad/axidraw import hershey_advanced ad = axidraw.AxiDraw() hta = hershey_advanced.HersheyAdv() ad.getoptions([]) self.svg = self.document.getroot() ad.ReadWCBdata(self.svg) self.svg_row_old = ad.svg_row_old # Access params from ReadWCBdata ad.called_externally = True if skipSerial == False: self.serial_port = ebb_serial.openPort() if self.serial_port is None: inkex.errormsg( gettext.gettext("Failed to connect to AxiDraw. :(")) return if self.options.mode == "autoPlot": # Note: In preview mode, we only preview-plot the _last_ row to be plotted. pen_down_travel_inches = 0.0 # Local variable pen_up_travel_inches = 0.0 # Local variable pt_estimate = 0.0 # Local variable continue_plotting = True self.row_to_plot = int(self.options.first_row) if (self.options.last_row == 0 ): # "Continue until last row of data" self.options.last_row = 10000 # A large number; only limit by size of data. self.ReadCSV() if (self.csv_row_count is not None): if (self.row_to_plot > self.csv_row_count): inkex.errormsg( gettext.gettext( "No merge data found in specified range of rows.")) continue_plotting = False if (self.options.last_row < self.options.first_row): continue_plotting = False inkex.errormsg('Nothing to plot; No data rows selected.') if (continue_plotting): ad.backup_original = copy.deepcopy(self.original_document) while (continue_plotting): self.svg_rand_seed = round( time.time() * 100) / 100 # New random seed for new plot self.mergeAndPlot(hta, ad) if self.spewDebugdata: inkex.errormsg('Merging row number ' + str(int(self.row_to_plot)) + '.') pen_down_travel_inches = pen_down_travel_inches + ad.pen_down_travel_inches # Local copy pen_up_travel_inches = pen_up_travel_inches + ad.pen_up_travel_inches # Local copy pt_estimate = pt_estimate + ad.pt_estimate # Local copy if ( ad.b_stopped ): # A pause was triggered while plotting the previous row. inkex.errormsg( 'Paused while plotting row number ' + str(int(self.row_to_plot)) + '.') continue_plotting = False else: # Finished plotting the row without being paused self.row_to_plot = self.row_to_plot + 1 if (self.row_to_plot > self.options.last_row): continue_plotting = False # We have already finished the last row. else: # We will be plotting at least one more row. Delay first. self.next_csv_row() self.delay_between_rows = True # Indicate that we are currently delaying between copies timeCounter = 10 * self.options.page_delay # 100 ms units if self.spewDebugdata: inkex.errormsg( 'Delaying ' + str(int(self.options.page_delay)) + ' seconds.') while (timeCounter > 0): timeCounter = timeCounter - 1 if (self.b_stopped == False): if self.options.preview: pt_estimate += 100 self.pageDelays += 0.1 else: time.sleep( 0.100 ) # Use short intervals to improve responsiveness self.PauseCheck( ) #Query if button pressed self.delay_between_rows = False # Not currently delaying between copies if (self.b_stopped == True ): # if button pressed self.row_to_plot = self.row_to_plot - 1 # Backtrack; we didn't actually get to that row. inkex.errormsg( 'Sequence halted after row number ' + str(int(self.row_to_plot)) + '.') continue_plotting = False # Cancel plotting sequence ad.pen_down_travel_inches = pen_down_travel_inches # Copy local values back to ad.(values) ad.pen_up_travel_inches = pen_up_travel_inches # for printing time report. ad.pt_estimate = pt_estimate self.printTimeReport(ad) elif self.options.mode == "singlePlot": doPlot = True if (self.options.single_type == "singleFix" ): # Plot a specified row self.row_to_plot = int(self.options.single_row) elif (self.options.single_type == "singleAdv" ): # Automatically advance self.row_to_plot = int(self.svg_row_old + 1) else: # ( self.options.single_type == "queryRow" ) # No plotting; Query and report last row plotted inkex.errormsg('Last row merged: Row number ' + str(int(self.svg_row_old))) inkex.errormsg('Next row to merge: Row number ' + str(int(self.svg_row_old + 1))) doPlot = False if doPlot: self.svg_rand_seed = round( time.time() * 100) / 100 # New random seed for new plot self.options.last_row = self.row_to_plot # Last row is equal to first row, in this case. self.ReadCSV() if (self.csv_row_count is not None): if (self.row_to_plot > self.csv_row_count): inkex.errormsg( gettext.gettext( "No merge data found in row number ") + str(self.row_to_plot) + '.') else: ad.backup_original = copy.deepcopy( self.original_document) self.mergeAndPlot(hta, ad) self.printTimeReport(ad) elif self.options.mode == "resume": ad.options.mode = "resume" self.svg_rand_seed = ad.svg_rand_seed_old # Preserve random seed self.row_to_plot = self.svg_row_old # Preserve SVG Row ad.options.resume_type = self.options.resume_type if (self.options.resume_type == "home"): self.options.fontface = "none" # Disable Hershey Text substitution self.mergeAndPlot(hta, ad) elif (ad.svg_application_old != "Axidraw Merge"): inkex.errormsg( gettext.gettext( "No AxiDraw Merge resume data found in file.")) elif (ad.svg_layer_old == 12345 ): # There appears to be a paused "all layers" plot self.options.last_row = self.row_to_plot self.ReadCSV() if (self.csv_row_count is not None): ad.backup_original = copy.deepcopy(self.original_document) self.mergeAndPlot(hta, ad) self.printTimeReport(ad) else: inkex.errormsg( gettext.gettext( "No in-progress plot data found saved in file.")) elif self.options.mode == "setup": if self.options.preview: inkex.errormsg( gettext.gettext( 'Command unavailable while in preview mode.')) else: ad.options.mode = self.options.mode ad.options.setup_type = self.options.setup_type ad.options.pen_pos_up = self.options.pen_pos_up ad.options.pen_pos_down = self.options.pen_pos_down ad.document = self.document ad.options.port = self.serial_port ad.effect() elif self.options.mode == "csv": # Open file dialog if self.options.data_action == "choose": # Select and upload a CSV file try: import pygtk pygtk.require('2.0') import gtk # Use gtk to create file selection dialog box. # self.useGTK = True except: inkex.errormsg( "Unable to load GTK, a required component. Please contact technical support for assistance." ) return filename = None dialog = gtk.FileChooserDialog( title="Please choose a CSV file", action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) filter = gtk.FileFilter() filter.set_name("Text/CSV") filter.add_pattern("*.CSV") filter.add_pattern("*.csv") filter.add_pattern("*.txt") filter.add_pattern("*.TXT") filter.add_mime_type("text/csv") filter.add_mime_type("text/plain") filter.add_mime_type("application/csv") filter.add_mime_type("application/x-csv") filter.add_mime_type("text/x-csv") filter.add_mime_type("text/csv") filter.add_mime_type("text/comma-separated-values") filter.add_mime_type("text/x-comma-separated-values") filter.add_mime_type("text/tab-separated-values") dialog.add_filter(filter) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") dialog.add_filter(filter) response = dialog.run() if response == gtk.RESPONSE_OK: filename = dialog.get_filename() #inkex.errormsg( "File selected: " + filename) # Print full path # inkex.errormsg( "Selected file: " + str(os.path.basename(filename))) # Print file name elif response == gtk.RESPONSE_CANCEL: inkex.errormsg(gettext.gettext('No CSV file selected.')) filter.destroy() dialog.destroy() if filename is not None: CSVfile = open(filename, 'rU') try: dialect_read = csv.Sniffer().sniff(CSVfile.readline()) except: dialect_read = None inkex.errormsg( "Unable to determine format of selected file, " \ + str(os.path.basename(filename)) ) # Print file name if dialect_read is None: CSVfile.close() else: CSVfile.seek(0) # Rewind file to beginning reader = csv.reader(CSVfile, dialect=dialect_read) CSVrowCount = sum( 1 for row in reader) - 1 # Subtract 1 for header row CSVfile.seek(0) if (CSVrowCount > 0): CSVfile = open(filename, 'rU') reader = csv.DictReader(CSVfile, dialect=dialect_read) inkex.errormsg( "Found " + str(CSVrowCount) + " Rows of merge data in file " \ + str(os.path.basename(filename))) # Print file name key_names = "Column names: " for item in reader.fieldnames: key_names = key_names + "{{" + item + "}}, " key_names = key_names[: -2] # drop last two characters from string (", ") inkex.errormsg(key_names) # Print key list self.csv_file_path = str( filename) # Path & Name of the file self.storeCSVpath( self.svg ) # Store path & name file in our SVG file. else: inkex.errormsg( "Unable to interpret selected file" + str(os.path.basename(filename)) + ".") CSVfile.close() elif self.options.data_action == "view": self.csv_data_read = False CSVfile = None csvNode = None for node in self.svg: if node.tag == 'svg': for subNode in self.svg: if subNode.tag == inkex.addNS( 'MergeData', 'svg') or subNode.tag == 'MergeData': csvNode = subNode elif node.tag == inkex.addNS( 'MergeData', 'svg') or node.tag == 'MergeData': csvNode = node if csvNode is not None: try: CSVfile = str(csvNode.text) self.csv_data_read = True except: self.svg.remove( csvNode ) # An error before this point leaves csvDataRead as False. if CSVfile is None: inkex.errormsg( "No CSV data found in file. Please select and load a CSV file." ) return else: inkex.errormsg("The selected CSV data file is:") inkex.errormsg(CSVfile) # elif self.options.data_action == "erase": # self.eraseCSVpath(self.svg) # erase stored CSV file from our SVG file. if self.serial_port is not None: ebb_motion.doTimedPause( self.serial_port, 10) #Pause a moment for underway commands to finish... ebb_serial.closePort(self.serial_port)
def _init_axidraw(self): self._port = ebb_serial.openPort() ebb_motion.sendDisableMotors(self._port) # Ratio experiment: # @152 dpi 1 pixel right and down # ebb_motion.doABMove(port, 20, -20, 200) # ebb_motion.doABMove(port, 15, -15, 200) # Millimeter factor (should be correlated as closely as possible to the # move value needed to move one millimeter). Multiply with millimeter # value. # 15 (move points to pattern pixel ratio; estimation from ratio # experiment) # (1/152) * 25.4 = 0.16710526315789473 (mm per pattern pixel) # 1/0.16710526315789473 = 5.984.. (=> ~6 pattern pixel per mm) # 15 * 6 = 90 (~90 move points per mm) # mm = 90 # Further testing showed it should be less than 90 # mm = 80 # Manual testing with a 127dpi paper (1 pattern pixel is exactly 0.2mm) # showed that x- and y-axis differ a bit and that the values are # slightly higher than 80. # Also the first move to the other side on the y-axis is a bit skewed # (~0.2mm). There could maybe be code to compensate for the direction # change inaccuracy. # x_mm = 82 # y_mm = 85 # Manual testing over full DIN A4 papers yielded these results: self._x_mm = 80.2 self._y_mm = 80.2 # https://github.com/ertdfgcvb/Genau # This libraries README also states almost the same values: # "where 80 steps = 1mm" # From the "EBB (EiBotBoard) Command Set" documentation: # "Minimum speed is 1.31 steps/second." # "Maximum speed is 25k Steps/second." self._max_vel = 25000 self._min_vel = 1.31 # Calculate maximum and minimum millisecond divider self._max_ms = self._max_vel / 1000 self._min_ms = self._min_vel / 1000 # Manual/Sensible maximum and minimum values for millisecond divider # These are not the absolute maximum and minimum values but should be # more than enough for our purposes. # self._max_ms = 20 # self._min_ms = 0.1 # max_duration does not really matter in our context # min_duration = 4 @ 80 move points => 20 ms/step # BUT: # our min_steps are 26 => duration = math.ceil(1.3) = 2 @ 20 ms/step # TODO: Maybe use the same maximum/minimum as in the AxiDraw Inkscape # plugin # Maximum: # Would decrement self._ms by 1 until it is smaller than self._max_ms. # https://github.com/evil-mad/axidraw/blob/master/inkscape%20driver/axidraw_conf.py#L112 # Minimum (is hardcoded): # If self._ms value is smaller than 0.002 it would jump to 0. # https://github.com/evil-mad/axidraw/blob/master/inkscape%20driver/axidraw.py#L2373 # self._max_ms = 24.995 # self._min_ms = 0.002 # According to the User Guide lower percentages are better for accuracy # (p. 35). # 0% will be self._min_ms and 100% self._max_ms. That means that # even if self._vel_percent is 0 that the AxiDraw will still move at # the minimum velocity. # The 15% value should give pretty good accuracy and work pretty well # for small and big distances. self._vel_percent = 15 if self._vel_percent < 0 or self._vel_percent > 100: raise ValueError("The self._vel_percent variable " + f"({self._vel_percent}) is not between " + "0 and 100 (both ends included).") # Time divider # Divide by the max_steps value to get the move duration in # milliseconds. min_max_delta = self._max_ms - self._min_ms self._ms = (min_max_delta * (self._vel_percent / 100)) + self._min_ms # TODO/FIXME: This calculation with vel_percent value does not seem to # do the same as the AxiDraws with the drawing speed percentage. # In the AxiDraw Inkscape plugin code the ms value at high resolution # and 15% drawing speed seems to be around ~2.41 # Example: # # TODO: Not confirmed yet from AxiDraw code. Should be extremely # # close though because it works very well in tests. # steps_per_mm = 80 # # drawing speed pecentage (needs to be between 1 and 110 [for some # # reason]) Default: 25 # pen_down_speed = 15 # # Maximum XY speed allowed when in High Resolution mode, in inches # # per second. Default: 8.6979, Max: 8.6979 # SpeedLimXY_HR = 8.6979 # # Speed given as maximum inches/second in XY plane # inch_per_s = pen_down_speed * SpeedLimXY_HR / 110.0 # ms = (inch_per_s / 1000) * 25.4 * steps_per_mm # == ~2.41010 self._ms = 2.41 # Direction factor. Multiply with move value to get right sign for # direction. # Assumes the AxiDraw sits to the right of the pattern paper. self._up, self._right = -1, -1 self._down, self._left = 1, 1 # Use 5 columns and take 20 frames per column to get 100 frames total # per page. num_cols = 5 frames_per_col = 20 # Calculate column move deltas x_points = np.linspace(0, self._move_size[0], num_cols) # Calculate millimeter deltas for columns. self._x_deltas = np.diff(x_points) # self._x_delta_sum = np.sum(self._x_deltas) # print(f"self._x_deltas: {self._x_deltas}") # print(f"self._x_delta_sum: {self._x_delta_sum}") # Calculate move value deltas for columns. # Because of rounding errors there is a difference between the full # page move (move_size * mm) and the move_delta_sum. Since this error # is very small it is ignored. # TODO: Maybe handle these errors better. self._x_move_deltas = (self._x_deltas * self._x_mm).astype(int) # self._x_move_delta_sum = np.sum(self._x_move_deltas) # print(f"self._x_move_deltas: {self._x_move_deltas}") # print(f"self._x_move_delta_sum: {self._x_move_delta_sum}") # Decrease the distance between frames within a column to also get an # idea of the accuracy of the readings. # Space out values logarithmically y_points = np.geomspace(self._move_size[1] + 1, 1, frames_per_col) # Calculate millimeter deltas. self._y_deltas = np.abs(np.diff(y_points)) # self._y_delta_sum = np.sum(self._y_deltas) # print(f"self._y_deltas: {self._y_deltas}") # print(f"self._y_delta_sum: {self._y_delta_sum}") # Calculate move value deltas. # Because of rounding errors there is a difference between the full # page move (move_size * mm) and the move_delta_sum. Since this error # is very small it is ignored. # TODO: Maybe handle these errors better. self._y_move_deltas = (self._y_deltas * self._y_mm).astype(int)