def on_vertical_scrollbar_moved(self,value): """ When the scrollbar moves, queue a refresh of the visible rows. This makes it only update the view when needed making scrolling much smoother. """ self._pending_row_refreshes +=1 timed_call(0,self._refresh_visible_row,value)
def run(self): """ Run the model synchronously. Shows a modal dialog until the run completes. Returns the dialog. """ runner = self.runner gui_handler = lambda *args: deferred_call(self.handle_error, *args) runner.error_handler = gui_handler dialog = RunDialog(parent=self.parent, title='Running model...', text='Please wait while the model runs.') dialog.observe('rejected', lambda change: runner.cancel()) dialog.show() event_loop = QtCore.QEventLoop() timer = QtCore.QTimer() def start_run(): runner.run() timer.setInterval(50) timer.timeout.connect(check_loop) timer.start() def check_loop(): dialog.output = runner.output() if not runner.running: timer.stop() event_loop.quit() timed_call(50, start_run) event_loop.exec_() dialog.accept() return dialog
def set_items(self, items): """ Defer until later so the view is only updated after all items are added. """ self._pending_view_refreshes += 1 timed_call(self._pending_timeout, self._refresh_layout)
def handle_dialog(qtbot, op='accept', handler=lambda qtbot, window: window, cls=Dialog, time=100, skip_answer=False): """Automatically close a dialog opened during the context. Parameters ---------- op : {'accept', 'reject'}, optional Whether to accept or reject the dialog. handler : callable, optional Callable taking as arguments the bot and the dialog, called before accepting or rejecting the dialog. cls : type, optional Dialog class to identify. time : float, optional Time to wait before handling the dialog in ms. skip_answer : bool, optional Skip answering to the dialog. If this is True the handler should handle the answer itself. """ sch = ScheduledClosing(qtbot, cls, handler, op, skip_answer) timed_call(time, sch) try: yield except Exception: raise else: qtbot.wait_until(sch.was_called, 10e3)
def on_horizontal_scrollbar_moved(self, value): """ When the scrollbar moves, queue a refresh of the visible columns. This makes it only update the view when needed making scrolling much smoother. """ self._pending_column_refreshes += 1 timed_call(0, self._refresh_visible_column, value)
def on_vertical_scrollbar_moved(self, value): """ When the scrollbar moves, queue a refresh of the visible rows. This makes it only update the view when needed making scrolling much smoother. """ self._pending_row_refreshes += 1 timed_call(0, self._refresh_visible_row, value)
def async_sleep(ms): """ Sleep for the given duration without blocking. Typically this is used with the inlineCallbacks decorator. """ d = Deferred() timed_call(ms, d.callback, True) return d
def _update_index(self): """ Update the reference to the index within the table """ d = self.declaration self.index = self.view.model.index(d.row, d.column) if self.delegate: self._refresh_count += 1 timed_call(self._loading_interval, self._update_delegate)
def _refresh_plot(self): """Defer drawing until all changes are done so we don't draw during initialization or when many values change at once. """ self._pending_refreshes += 1 refresh_time = self.declaration.refresh_time timed_call(refresh_time, self._redraw_plot)
def _update_index(self): """ Update the reference to the index within the table """ d = self.declaration self.index = self.view.model.index(d.row,d.column) if self.delegate: self._refresh_count +=1 timed_call(self._loading_interval,self._update_delegate)
def handle_dialog(qtbot, op='accept', handler=lambda qtbot, window: window, cls=Dialog, time=100, skip_answer=False): """Automatically close a dialog opened during the context. Parameters ---------- op : {'accept', 'reject'}, optional Whether to accept or reject the dialog. handler : callable, optional Callable taking as arguments the bot and the dialog, called before accepting or rejecting the dialog. cls : type, optional Dialog class to identify. time : float, optional Time to wait before handling the dialog in ms. skip_answer : bool, optional Skip answering to the dialog. If this is True the handler should handle the answer itself. """ sch = ScheduledClosing(qtbot, cls, handler, op, skip_answer) timed_call(time, sch) try: yield except Exception: raise else: qtbot.wait_until(sch.was_called, TIMEOUT)
def on_horizontal_scrollbar_moved(self,value): """ When the scrollbar moves, queue a refresh of the visible columns. This makes it only update the view when needed making scrolling much smoother. """ self._pending_column_refreshes +=1 timed_call(0,self._refresh_visible_column,value)
def schedule_ping(self): """ Ping perioidcally so the process stays awake """ if self.terminated: return # Ping the viewer to tell it to keep alive self.send_message("ping", _id="keep_alive", _silent=True) timed_call(self._ping_rate * 1000, self.schedule_ping)
def _observe_view(self, change): """ Move window and allow it to draw before taking the snapshot. """ if change['type'] == 'create': self.view.initial_position = (10, 10) self.view.always_on_top = True timed_call(1500, self.snapshot)
def _queue_update(self,change): """ Schedule an update to be performed in the next available cycle. This should be used for expensive operations as opposed to an immediate update with update_shape. """ self._update_count +=1 timed_call(0,self._dequeue_update,change)
def __init__(self, view, watch=False): self.view = view self.watch = watch self._watched_files = {} if watch: timed_call(1000, self.check_for_changes) self._exit_in_sec = 60 super(ViewerProtocol).__init__()
def _queue_save_state(self, change): """ Queue a save state. This calls _save_state after a given duration so that multiple state changes get batched and saved all at once instead of saving multiple times (which a become slow). """ if change['type'] in ['update', 'manual', 'container']: self._state_save_pending += 1 timed_call(350, self._save_state, change)
def _fire_plot_update(self, schedule_only=False): """Fire the plot update event and reschedule a new call. """ if not schedule_only: self.plot_update = True if not self._stop_timer: timed_call(1000 * self.plot_refresh_interval, self._fire_plot_update)
def _update_index(self): self.index = self._default_index() print 'UPDATE',self.declaration.text,self,self.index.internalPointer(),self.index.row(),self.index.column(),self.index.parent() assert self == self.view.model.itemAt(self.index) if self.delegate and self.index.isValid(): #print self.parent(),self,self.index.row(),self.index.column() self._refresh_count +=1 timed_call(self._loading_interval,self._update_delegate)
def schedule_close(self): """ A watchdog so if the parent is killed the viewer will automatically exit. Otherwise it will hang around forever. """ if self._exit_in_sec <= 0: # Timeout print("WARNING: Ping timeout expired, closing") sys.exit(1) else: timed_call(self._exit_in_sec * 1000, self.schedule_close) self._exit_in_sec = 0 # Clear timeout
def save_dock_area(self, change): """ Save the dock area """ self._area_saves_pending += 1 def do_save(): self._area_saves_pending -= 1 if self._area_saves_pending != 0: return #: Now save it ui = self.workbench.get_plugin('enaml.workbench.ui') ui.workspace.save_area() timed_call(350, do_save)
def main(**kwargs): app = Application() filename = kwargs.get('file', '-') frameless = kwargs.get('frameless', False) watch = kwargs.get('watch', False) if not frameless and not os.path.exists(filename): raise ValueError("File %s does not exist!" % filename) view = ViewerWindow(filename='-', frameless=frameless) view.protocol = ViewerProtocol(view=view, watch=watch) if watch: timed_call(1000, view.protocol.check_for_changes) view.show() app.deferred_call(create_stdio_connection, app.loop, view.protocol) app.deferred_call(view.protocol.handle_filename, filename) app.start()
def send_message(self, method, *args, **kwargs): # Defer until it's ready if not self.transport: log.debug('renderer | message not ready deferring') timed_call(0, self.send_message, method, *args, **kwargs) return _id = kwargs.pop('_id') request = { 'jsonrpc': '2.0', 'method': method, 'params': args or kwargs } if _id is not None: request['id'] = _id log.debug(f'renderer | sent | {request}') self.transport.write(jsonpickle.dumps(request).encode() + b'\r\n')
def send_message(self, method, *args, **kwargs): # Defer until it's ready if not self.transport or not self.window_id: #log.debug('renderer | message not ready deferring') timed_call(1000, self.send_message, method, *args, **kwargs) return _id = kwargs.pop('_id') _silent = kwargs.pop('_silent', False) request = { 'jsonrpc': '2.0', 'method': method, 'params': args or kwargs } if _id is not None: request['id'] = _id if not _silent: log.debug(f'renderer | sent | {request}') encoded_msg = jsonpickle.dumps(request).encode() + b'\r\n' deferred_call(self.transport.write, encoded_msg)
def handle_dialog(op='accept', custom=lambda x: x, cls=Dialog, time=100): """Automatically close a dialog opened during the context. Parameters ---------- op : {'accept', 'reject'}, optional Whether to accept or reject the dialog. custom : callable, optional Callable taking as only argument the dialog, called before accepting or rejecting the dialog. cls : type, optional Dialog class to identify. time : float, optional Time to wait before handling the dialog in ms. """ def close_dialog(): i = 0 while True: dial = get_window(cls) if dial is not None: break elif i > 10: raise Exception('Dialog timeout') sleep(0.1) i += 1 try: custom(dial) finally: process_app_events() from .fixtures import DIALOG_SLEEP sleep(DIALOG_SLEEP) getattr(dial, op)() timed_call(time, close_dialog) yield process_app_events()
def handle_dialog(op='accept', custom=lambda x: x, cls=Dialog, time=100, skip_answer=False): """Automatically close a dialog opened during the context. Parameters ---------- op : {'accept', 'reject'}, optional Whether to accept or reject the dialog. custom : callable, optional Callable taking as only argument the dialog, called before accepting or rejecting the dialog. cls : type, optional Dialog class to identify. time : float, optional Time to wait before handling the dialog in ms. skip_answer : bool, optional Skip answering to the dialog. If this is True the handler should handle the answer itself. """ sch = ScheduledClosing(cls, custom, op) timed_call(time, sch) try: yield except Exception: raise else: while not sch.called: process_app_events()
def check_for_changes(self): """ A simple poll loop to check if the file changed and if it has reload it by bumping the version. """ if self.watch: timed_call(1000, self.check_for_changes) try: filename = self.view.filename if os.path.exists(filename): try: mtime = os.stat(filename).st_mtime except: return if filename not in self._watched_files: self._watched_files[filename] = mtime elif self._watched_files[filename] != mtime: self._watched_files[filename] = mtime print("%s changed, reloading" % filename) deferred_call(self.handle_version, self.view.version + 1) except Exception as e: print(traceback.format_exc())
def _observe_running(self, change): if self.running: timed_call(0, self.tick)
def _observe_now(self, change): t = self.now self.year, self.month, self.day = t.year, t.month, t.day self.hour, self.minute, self.second = t.hour, t.minute, t.second if self.running: timed_call(1000, self.tick)
def _observe_parts(self, change): """ When changed, do a fit all """ if change['type'] == 'update': timed_call(500, self.fit_all)
def _refresh_plot(self): """ Defer drawing until all changes are done so we don't draw during initialization or when many values change at once. """ self._pending_refreshes+=1 timed_call(100,self._redraw_plot)
def submit(self, job, test=False): """ Submit the job to the device. If the device is currently running a job it will be queued and run when this is finished. This handles iteration over the path model defined by the job and sending commands to the actual device using roughly the procedure is as follows: device.connect() model = device.init(job) for cmd in device.process(model): device.handle(cmd) device.finish() device.disconnect() Subclasses provided by your own DeviceDriver may reimplement this to handle path interpolation however needed. The return value is ignored. The live plot view will update whenever the device.position object is updated. On devices with lower cpu/gpu capabilities this should be updated sparingly (ie the raspberry pi). Parameters ----------- job: Instance of `inkcut.job.models.Job` The job to execute on the device test: bool Do a test run. This specifies whether the commands should be sent to the actual device or not. If True, the connection will be replaced with a virtual connection that captures all the command output. """ log.debug("device | submit {}".format(job)) try: #: Only allow one job at a time if self.busy: queue = self.queue[:] queue.append(job) self.queue = queue #: Copy and reassign so the UI updates log.info("Job {} put in device queue".format(job)) return with self.device_busy(): #: Set the current the job self.job = job self.status = "Initializing job" #: Get the time to sleep based for each unit of movement config = self.config #: Rate px/ms if config.custom_rate >= 0: rate = config.custom_rate elif self.connection.always_spools or config.spooled: rate = 0 elif config.interpolate: if config.step_time > 0: rate = config.step_size / float(config.step_time) else: rate = 0 # Undefined else: rate = from_unit( config.speed, # in/s or cm/s config.speed_units.split("/")[0]) / 1000.0 # Device model is updated in real time model = yield defer.maybeDeferred(self.init, job) #: Local references are faster info = job.info #: Determine the length for tracking progress whole_path = QtGui.QPainterPath() #: Some versions of Qt seem to require a value in #: toSubpathPolygons m = QtGui.QTransform.fromScale(1, 1) for path in model.toSubpathPolygons(m): for i, p in enumerate(path): whole_path.lineTo(p) total_length = whole_path.length() total_moved = 0 log.debug("device | Path length: {}".format(total_length)) #: So a estimate of the duration can be determined info.length = total_length info.speed = rate * 1000 #: Convert to px/s #: Waiting for approval info.status = 'waiting' #: If marked for auto approve start now if info.auto_approve: info.status = 'approved' else: #: Check for approval before starting yield defer.maybeDeferred(info.request_approval) if info.status != 'approved': self.status = "Job cancelled" return #: Update stats info.status = 'running' info.started = datetime.now() self.status = "Connecting to device" with self.device_connection(test or config.test_mode) as connection: self.status = "Processing job" try: yield defer.maybeDeferred(self.connect) #: Write startup command if config.commands_before: yield defer.maybeDeferred(connection.write, config.commands_before) self.status = "Working..." #: For point in the path for (d, cmd, args, kwargs) in self.process(model): #: Check if we paused if info.paused: self.status = "Job paused" #: Sleep until resumed, cancelled, or the #: connection drops while (info.paused and not info.cancelled and connection.connected): yield async_sleep(300) # ms #: Check for cancel for non interpolated jobs if info.cancelled: self.status = "Job cancelled" info.status = 'cancelled' break elif not connection.connected: self.status = "connection error" info.status = 'error' break #: Invoke the command #: If you want to let the device handle more complex #: commands such as curves do it in process and handle yield defer.maybeDeferred(cmd, *args, **kwargs) total_moved += d #: d should be the device must move in px #: so wait a proportional amount of time for the device #: to catch up. This avoids buffer errors from dumping #: everything at once. #: Since sending is way faster than cutting #: we must delay (without blocking the UI) before #: sending the next command or the device's buffer #: quickly gets filled and crappy china piece cutters #: get all jacked up. If the transport sends to a spooled #: output (such as a printer) this can be set to 0 if rate > 0: # log.debug("d={}, delay={} t={}".format( # d, delay, d/delay # )) yield async_sleep(d / rate) #: TODO: Check if we need to update the ui #: Set the job progress based on how far we've gone if total_length > 0: info.progress = int( max( 0, min(100, 100 * total_moved / total_length))) if info.status != 'error': #: We're done, send any finalization commands yield defer.maybeDeferred(self.finish) #: Write finalize command if config.commands_after: yield defer.maybeDeferred(connection.write, config.commands_after) #: Update stats info.ended = datetime.now() #: If not cancelled or errored if info.status == 'running': info.done = True info.status = 'complete' except Exception as e: log.error(traceback.format_exc()) raise finally: if connection.connected: yield defer.maybeDeferred(self.disconnect) #: Set the origin if job.feed_to_end and job.info.status == 'complete': self.origin = self.position #: If the user didn't cancel, set the origin and #: Process any jobs that entered the queue while this was running if self.queue and not job.info.cancelled: queue = self.queue[:] job = queue.pop(0) #: Pull the first job off the queue log.info("Rescheduling {} from queue".format(job)) self.queue = queue #: Copy and reassign so the UI updates #: Call a minute later timed_call(60000, self.submit, job) except Exception as e: log.error(' device | Execution error {}'.format( traceback.format_exc())) raise
def _queue_update(self,change=None): change = change or {} self._update_count +=1 timed_call(0,self._dequeue_update,change)
def set_items(self, items): """ Defer until later so the view is only updated after all items are added. """ self._pending_view_refreshes +=1 timed_call(100,self._refresh_layout)
def update_display(self, change=None): self._update_count += 1 #log.debug('update_display') timed_call(0, self._do_update)
def submit(self, job, test=False): """ Submit the job to the device. If the device is currently running a job it will be queued and run when this is finished. This handles iteration over the path model defined by the job and sending commands to the actual device using roughly the procedure is as follows: device.connect() model = device.init(job) for cmd in device.process(model): device.handle(cmd) device.finish() device.disconnect() Subclasses provided by your own DeviceDriver may reimplement this to handle path interpolation however needed. The return value is ignored. The live plot view will update whenever the device.position object is updated. On devices with lower cpu/gpu capabilities this should be updated sparingly (ie the raspberry pi). Parameters ----------- job: Instance of `inkcut.job.models.Job` The job to execute on the device test: bool Do a test run. This specifies whether the commands should be sent to the actual device or not. If True, the connection will be replaced with a virtual connection that captures all the command output. """ log.debug("device | submit {}".format(job)) try: #: Only allow one job at a time if self.busy: queue = self.queue[:] queue.append(job) self.queue = queue #: Copy and reassign so the UI updates log.info("Job {} put in device queue".format(job)) return with self.device_busy(): #: Set the current the job self.job = job self.status = "Initializing job" #: Get the time to sleep based for each unit of movement config = self.config #: Rate px/ms if config.custom_rate >= 0: rate = config.custom_rate elif self.connection.always_spools or config.spooled: rate = 0 elif config.interpolate: if config.step_time > 0: rate = config.step_size/float(config.step_time) else: rate = 0 # Undefined else: rate = from_unit( config.speed, # in/s or cm/s config.speed_units.split("/")[0])/1000.0 # Device model is updated in real time model = yield defer.maybeDeferred(self.init, job) #: Local references are faster info = job.info #: Determine the length for tracking progress whole_path = QtGui.QPainterPath() #: Some versions of Qt seem to require a value in #: toSubpathPolygons m = QtGui.QTransform.fromScale(1, 1) for path in model.toSubpathPolygons(m): for i, p in enumerate(path): whole_path.lineTo(p) total_length = whole_path.length() total_moved = 0 log.debug("device | Path length: {}".format(total_length)) #: So a estimate of the duration can be determined info.length = total_length info.speed = rate*1000 #: Convert to px/s #: Waiting for approval info.status = 'waiting' #: If marked for auto approve start now if info.auto_approve: info.status = 'approved' else: #: Check for approval before starting yield defer.maybeDeferred(info.request_approval) if info.status != 'approved': self.status = "Job cancelled" return #: Update stats info.status = 'running' info.started = datetime.now() self.status = "Connecting to device" with self.device_connection( test or config.test_mode) as connection: self.status = "Processing job" try: yield defer.maybeDeferred(self.connect) #: Write startup command if config.commands_before: yield defer.maybeDeferred(connection.write, config.commands_before) self.status = "Working..." #: For point in the path for (d, cmd, args, kwargs) in self.process(model): #: Check if we paused if info.paused: self.status = "Job paused" #: Sleep until resumed, cancelled, or the #: connection drops while (info.paused and not info.cancelled and connection.connected): yield async_sleep(300) # ms #: Check for cancel for non interpolated jobs if info.cancelled: self.status = "Job cancelled" info.status = 'cancelled' break elif not connection.connected: self.status = "connection error" info.status = 'error' break #: Invoke the command #: If you want to let the device handle more complex #: commands such as curves do it in process and handle yield defer.maybeDeferred(cmd, *args, **kwargs) total_moved += d #: d should be the device must move in px #: so wait a proportional amount of time for the device #: to catch up. This avoids buffer errors from dumping #: everything at once. #: Since sending is way faster than cutting #: we must delay (without blocking the UI) before #: sending the next command or the device's buffer #: quickly gets filled and crappy china piece cutters #: get all jacked up. If the transport sends to a spooled #: output (such as a printer) this can be set to 0 if rate > 0: # log.debug("d={}, delay={} t={}".format( # d, delay, d/delay # )) yield async_sleep(d/rate) #: TODO: Check if we need to update the ui #: Set the job progress based on how far we've gone if total_length > 0: info.progress = int(max(0, min(100, 100*total_moved/total_length))) if info.status != 'error': #: We're done, send any finalization commands yield defer.maybeDeferred(self.finish) #: Write finalize command if config.commands_after: yield defer.maybeDeferred(connection.write, config.commands_after) #: Update stats info.ended = datetime.now() #: If not cancelled or errored if info.status == 'running': info.done = True info.status = 'complete' except Exception as e: log.error(traceback.format_exc()) raise finally: if connection.connected: yield defer.maybeDeferred(self.disconnect) #: Set the origin if job.feed_to_end and job.info.status == 'complete': self.origin = self.position #: If the user didn't cancel, set the origin and #: Process any jobs that entered the queue while this was running if self.queue and not job.info.cancelled: queue = self.queue[:] job = queue.pop(0) #: Pull the first job off the queue log.info("Rescheduling {} from queue".format(job)) self.queue = queue #: Copy and reassign so the UI updates #: Call a minute later timed_call(60000, self.submit, job) except Exception as e: log.error(' device | Execution error {}'.format( traceback.format_exc())) raise
def _update_index(self): self.index = self._default_index() if self.delegate: self._refresh_count +=1 timed_call(self._loading_interval,self._update_delegate)
def _update_index(self): self.index = self._default_index() if self.delegate: self._refresh_count +=1 timed_call(self._loading_interval, self._update_delegate)
def async_sleep(ms): d = Deferred() timed_call(ms, d.callback, True) return d
def update_display(self, change=None): self._update_count +=1 #log.debug('update_display') timed_call(0,self._do_update)