Coverage for pyilper/pilplotter.py: 88%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3# pyILPER 1.2.1 for Linux
4#
5# An emulator for virtual HP-IL devices for the PIL-Box
6# derived from ILPER 1.4.5 for Windows
7# Copyright (c) 2008-2013 Jean-Francois Garnier
8# C++ version (c) 2013 Christoph Gießelink
9# Python Version (c) 2015 Joachim Siebold
10#
11# This program is free software; you can redistribute it and/or
12# modify it under the terms of the GNU General Public License
13# as published by the Free Software Foundation; either version 2
14# of the License, or (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24#
25# Plotter tab object classes ---------------------------------------------------
26#
27# Changelog
28# 17.10.2016 jsi:
29# - initial version
30# 23.10.2016 jsi:
31# - check required version of emu7470
32# - terminate subprocess on ipc input/output error
33# 24.10.2016 jsi:
34# - show emu7470 version in status window
35# 03.11.2016 cg:
36# - bugfix in process HP-GL command, stdin flush was missing
37# - added workaround for creating a subprocess without console window under Windows
38# 04.11.2016 cg:
39# - changed pdf filename dialog to AcceptSave and added "pdf" as default suffix
40# 01.02.2017 jsi
41# - fixed crash in do_finishX, finishY if either of the lineEdits is already empty
42# 05.02.2017 jsi
43# - update pen configuration before each draw command
44# 07.08.2017 jsi
45# - papersize is now a global configuration parameter
46# 19.08.2017 jsi
47# - fixed refactoring bug: plotter HP-IL device was accidently disabled
48# permanently when the disable method was called
49# 22.08.2017 jsi
50# - disable gui elements if not active
51# 24.08.2017 jsi
52# - error in logging fixed
53# 28.08.2017 jsi
54# - remove alignments from GUI
55# - get papersize config parameter in constructor of tab widget
56# - full responsive design of plotter tab
57# 01.09.2017 jsi
58# - get open pdf file dialog from cls_pdfprinter
59# 03.09.2017 jsi
60# - register pildevice is now method of commobject
61# - missing classes of pen config window moved from pilwidgets
62# 08.09.2017 jsi
63# - fixed crash when letter format
64# - fixed crash when using user defined pen configurations
65# 19.09.2017 jsi
66# - fixed call if setInvalid
67# 27.09.2017 jsi
68# - renamed putOutput to putDataToHPIL
69# - code to output data to HP-IL rewritten
70# 28.09.2017 jsi
71# - block multiple calls of start_digi
72# 02.10.2017 jsi
73# - fixed crash on cursor restore if digitizing mode was stopped twice
74# 30.10.2017 jsi
75# - LIFUTILS path handling added
76# 28.12.2017 jsi
77# - fixed bug in parse utility - needless comma removed
78# 04.01.2018 jsi
79# - reconfigure log checkbox
80# - flush log buffer
81# 16.01.2018 jsi
82# - adapt to cls_tabgeneric, implemented cascading config menus
83# 20.01.2018 jsi
84# - removed the external plot view window. Since the plotter tab can be
85# undocked now it is no needed any more
86# 29.01.2018 jsi
87# - removed external view references
88# - clear digitize mode when "IN" received
89# - set pushbutton autodefault property to false
90# 10.08.2018 jsi
91# - cls_PenConfigWindow moved to penconfig.py
92# 25.02.2020 jsi
93# - cleanup status byte access
94# 26.02.2020 jsi
95# - cleanup status byte access fix
96# 02.03.2021 jsi
97# - Enter button is deactivated in digi mode until a point was digitized or
98# entered (hint by cgh)
99# 18.04.2022 jsi
100# - assure that the return value of heightForWidth is an integer
101# - assure that argument for pen.setWidth is int in update_PenDef
102# - improve shutdown of the plotter subprocess
104import sys
105import subprocess
106import queue
107import threading
108import array
109from PySide6 import QtCore, QtGui, QtPrintSupport, QtWidgets
110from .pilcore import UPDATE_TIMER, FONT, EMU7470_VERSION, decode_version, isWINDOWS
111from .pilconfig import PilConfigError, PILCONFIG
112from .penconfig import PENCONFIG
113from .pildevbase import cls_pildevbase
114from .pilwidgets import cls_tabgeneric, LogCheckboxWidget, T_STRING
115from .pilpdf import cls_pdfprinter
116from .lifcore import add_path
118#
119# constants --------------------------------------------------------------
120#
121CMD_CLEAR=0
122CMD_MOVE_TO=1
123CMD_DRAW_TO=2
124CMD_PLOT_AT=3
125CMD_SET_PEN=4
126CMD_OUTPUT=5
127CMD_STATUS=6
128CMD_ERRMSG=7
129CMD_DIGI_START=8
130CMD_DIGI_CLEAR=9
131CMD_P1P2=10
132CMD_EOF=11
133CMD_OFF_ERROR=12
134CMD_ON_ERROR_YELLOW=13
135CMD_ON_ERROR_RED=14
136CMD_EMU_VERSION=15
137CMD_EXT_ERROR=16
138CMD_SET_STATUS=17
139CMD_LOG=18
141MODE_DIGI=0
142MODE_P1=1
143MODE_P2=2
144MODE_NONE=3
146def eprint(*args, **kwargs):
147 print(*args, file=sys.stderr, **kwargs)
148#
149# plotter widget ----------------------------------------------------------
150#
151class cls_tabplotter(cls_tabgeneric):
153 def __init__(self,parent,name):
154 super().__init__(parent,name)
155 self.name=name
156#
157# this parameter is global
158#
159 self.papersize=PILCONFIG.get("pyilper","papersize")
160#
161# init config parameters
162#
163 self.loglevel= PILCONFIG.get(self.name,"loglevel",0)
164#
165# Create Plotter GUI object
166#
167 self.guiobject=cls_PlotterWidget(self,self.name,self.papersize)
168#
169# add gui object to tab
170#
171 self.add_guiobject(self.guiobject)
172#
173# add cascading config menu
174#
175 self.add_configwidget()
176#
177# add logging control widget
178#
179 self.add_logging()
180#
181# add local config options to cascading config menu
182#
183 self.cBut.add_option("Log level","loglevel",T_STRING, ["HP-GL","HP-GL+Status","HP-GL+Status+Commands"])
184#
185# create IL-Interface object, notify plotter processor object
186#
187 self.pildevice= cls_pilplotter(self.guiobject,self.papersize)
188 self.guiobject.set_pildevice(self.pildevice)
189 self.cBut.config_changed_signal.connect(self.do_tabconfig_changed)
190#
191# handle changes of tab config options
192#
193 def do_tabconfig_changed(self):
194 self.loglevel= PILCONFIG.get(self.name,"loglevel")
195 super().do_tabconfig_changed()
196#
197# enable pildevice and gui object
198#
199 def enable(self):
200 super().enable()
201 self.parent.commthread.register(self.pildevice,self.name)
202 self.pildevice.setactive(self.active)
203 self.pildevice.enable()
204 self.guiobject.enable()
205#
206# disable pildevice and gui object
207#
208 def disable(self):
209 self.pildevice.disable()
210 self.guiobject.disable()
211 super().disable()
212#
213# becomes visible, refresh content, activate update and blink
214#
215 def becomes_visible(self):
216 self.guiobject.becomes_visible()
217 return
218#
219# becomes invisible, deactivate update and blink
220#
221 def becomes_invisible(self):
222 self.guiobject.becomes_invisible()
223 return
224#
225# toggle active/inactive
226#
227 def toggle_active(self):
228 super().toggle_active()
229 self.guiobject.toggle_active()
230 return
231#
232# Custom class LED widget -------------------------------------------------
233#
234class cls_LedWidget(QtWidgets.QWidget):
236 def __init__(self):
237 super().__init__()
238 self.ledSize=20
239 self.ledColor= QtGui.QColor(0xff,0xff,0xff,0xff)
240 self.ledPattern= QtCore.Qt.SolidPattern
241 self.setFixedSize(self.ledSize, self.ledSize)
243 def paintEvent(self,event):
244 p=QtGui.QPainter(self)
245 p.setBrush(QtGui.QBrush(self.ledColor,self.ledPattern))
246 p.setPen(QtGui.QColor(0x00,0x00,0x00,0xff))
247 p.drawEllipse(0,0,self.ledSize-1,self.ledSize-1)
249 def setColor(self,color):
250 self.ledColor=color
251 self.repaint()
253 def setSize(self,size):
254 self.ledSize=size
255 self.setFixedSize(size,size)
256 self.repaint()
257#
258# custom class mark for digitized points --------------------------------------
259#
261class cls_DigitizedMark(QtWidgets.QGraphicsItem):
263 def __init__(self):
264 super().__init__()
265 self.rect = QtCore.QRectF(0,0, 10, 10)
267 def boundingRect(self):
268 return self.rect
270 def setPos(self,x,y):
271 super().setPos(x-5,y-5)
273 def paint(self, painter, option, widget):
274 pen = QtGui.QPen(QtCore.Qt.SolidLine)
275 pen.setWidth(2)
276 painter.setPen(pen)
277 pen.setColor(QtCore.Qt.blue)
278 painter.drawLine(0,0,10,10)
279 painter.drawLine(0,10,10,0)
281#
282# custom class mark for Scaling points ----------------------------------------
283#
284class cls_P1P2Mark(QtWidgets.QGraphicsTextItem):
286 def __init__(self,string):
287 super().__init__(string)
288 self.font=QtGui.QFont(FONT)
289 self.font.setPixelSize(10)
291 def setPos(self,x,y):
292 super().setPos(x-3,y-5)
294 def paint(self, painter, option, widget):
295 pen = QtGui.QPen(QtCore.Qt.SolidLine)
296 pen.setWidth(2)
297 pen.setColor(QtCore.Qt.red)
298 painter.setPen(pen)
299 painter.drawLine(-2,5,8,5)
300 painter.drawLine(3,0,3,10)
301 super().paint(painter, option, widget)
303#
304# custom class graphics scene with digitizing capabilities ---------------------
305#
306class cls_mygraphicsscene(QtWidgets.QGraphicsScene):
308 def __init__(self):
309 super().__init__()
310 self.mark_digi= None
311 self.mark_p1=None
312 self.mark_p2=None
313 self.mark_added= False
314 self.mode= MODE_NONE
315 return
316#
317# start digitizing in mode:
318# MODE_DIGI: mark is an x cross, mark appears after the first click
319# MODE_P1: mark P1, mark appears at the current position of scaling point P1
320# MODE_P2: mark P2, mark appears at the current position of scaling point P2
321#
322 def digi_mode(self,mode):
323 self.mode=mode
324 return
325#
326# set position of mark, in MODE_DIGI the mark is added to the scene at the first click
327#
328 def setMark(self,x,y):
329 if self.mode== MODE_DIGI:
330 if not self.mark_added:
331 self.mark_digi= cls_DigitizedMark()
332 self.addItem(self.mark_digi)
333 self.mark_added= True
334 self.mark_digi.setPos(x,y)
335 elif self.mode== MODE_P1:
336 self.mark_p1.setPos(x,y)
337 elif self.mode== MODE_P2:
338 self.mark_p2.setPos(x,y)
339#
340# add the marks of the scaling points P1 and P2 to the scene and place them at
341# their original position
342#
343 def setMarkp1p2(self,x1,y1,x2,y2):
344 self.mark_p1= cls_P1P2Mark("P1")
345 self.addItem(self.mark_p1)
346 self.mark_p1.setPos(x1,y1)
347 self.mark_p2= cls_P1P2Mark("P2")
348 self.addItem(self.mark_p2)
349 self.mark_p2.setPos(x2,y2)
350#
351# clear digitizing, remove marks from scene
352#
353 def digi_clear(self):
354 if self.mode==MODE_DIGI:
355 if self.mark_added:
356 self.removeItem(self.mark_digi)
357 self.mark_digi=None
358 self.mark_added= False
359 if self.mode==MODE_P1 or self.mode== MODE_P2:
360 self.removeItem(self.mark_p1)
361 self.removeItem(self.mark_p2)
362 self.mark_p1=None
363 self.mark_p2=None
364 self.mark_added=False
365#
366# custom layout class, only valid for one item. Ensures that the
367# item keeps its aspect ratio on resize
368#
369class cls_AspectLayout(QtWidgets.QLayout):
370 def __init__(self, aspect_ratio,parent=None):
371 super(cls_AspectLayout, self).__init__(parent)
372 self.aspect_ratio=aspect_ratio
373 self.setSpacing(-1)
374 self.itemList = []
376 def __del__(self):
377 item = self.takeAt(0)
378 while item:
379 item = self.takeAt(0)
381 def addItem(self, item):
382 self.itemList.append(item)
384 def count(self):
385 return len(self.itemList)
387 def itemAt(self, index):
388 if index >= 0 and index < len(self.itemList):
389 return self.itemList[index]
391 return None
393 def takeAt(self, index):
394 if index >= 0 and index < len(self.itemList):
395 return self.itemList.pop(index)
397 return None
399 def expandingDirections(self):
400 return QtCore.Qt.Orientations(QtCore.Qt.Orientation(3))
402 def hasHeightForWidth(self):
403 return True
405 def heightForWidth(self, width):
406# fixed DEPRECATED use of float argument for int parameter
407 height = int(width// self.aspect_ratio)
408 return height
410 def setGeometry(self, rect):
411 super(cls_AspectLayout, self).setGeometry(rect)
412 self.doLayout(rect, False)
414 def sizeHint(self):
415 return self.minimumSize()
417 def minimumSize(self):
418 size = QtCore.QSize()
420 for item in self.itemList:
421 size = size.expandedTo(item.minimumSize())
423 return size
425 def doLayout(self, rect, testOnly):
426 x = rect.x()
427 y = rect.y()
428 w = rect.width()
429 h = rect.height()
430 if int(w / self.aspect_ratio) > h:
431 item_w= int(h* self.aspect_ratio)
432 item_h= h
433 else:
434 item_w= w
435 item_h= int(w / self.aspect_ratio)
438 for item in self.itemList:
439 item.setGeometry(QtCore.QRect(x,y,item_w,item_h))
440 return
441#
442# custom class graphics view with digitizing capabilities ---------------------
443#
444class cls_mygraphicsview(QtWidgets.QGraphicsView):
446 def __init__(self,parent,aspect_ratio):
447 super().__init__()
448 self.parent=parent
449 self.aspect_ratio= aspect_ratio
450 self.restorecursor=None
451 self.digitize=False
452#
453# start digitizing, switch to crosshair cursor
454#
455 def digi_start(self):
456 if self.digitize:
457 return
458 if self.restorecursor is None:
459 self.restorecursor=self.viewport().cursor()
460 self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))
461 self.digitize= True
462#
463# finish digitizing, restore old cursor
464#
465 def digi_clear(self):
466 if not self.digitize:
467 return
468 self.viewport().setCursor(self.restorecursor)
469 self.restorecursor=None
470 self.digitize= False
471#
472# Mouse click event, convert coordinates first to scene coordinates and then to
473# plotter coordinates. Store the coordinates (in plotter units) in the coordinate
474# line edit of the GUI.
475#
476 def mousePressEvent(self, event):
477 if self.digitize:
478#DEPRECATED
479 x=event.pos()
480 p=self.mapToScene(x)
481 x=p.x()
482 y=p.y()
483 if x < 0 or x > self.parent.width or y < 0 or y > self.parent.height:
484 return
485 self.scene().setMark(x,y)
486 x=int(round(x/self.parent.factor))
487 y=int(round(self.parent.height -y)/self.parent.factor)
488 self.parent.setKoord(x,y)
490 def resizeEvent(self,event):
491 self.fitInView(self.parent.plotscene.sceneRect(),QtCore.Qt.KeepAspectRatio)
494#
495# Plotter widget class - GUI component of the plotter emulator ------------------
496#
497# The GUI is driven by:
498# User-Input and actions
499# Commands from the thread component. The thread components stores commands in the
500# GUI command queue (draw graphics, update status and error information switch and clear
501# digitize mode).
502#
503# The GUI component can send commands asynchronly to HP-GL command queue of the
504# thread component. These are: initialize, papersize changed, digitized coordinates,
505# digitized P1/P2.
506#
507# The GUI is also responsible for the plotter pen management. In the configuration
508# dialogue the HP7470A pens number 1 and 2 can be each assigned to one of 16
509# predefined pens in the pyILPER pen configuration file. These predefined pens
510# can be configures in the pyILPER main menu.
511#
512class cls_PlotterWidget(QtWidgets.QWidget):
514 def __init__(self,parent,name,papersize):
515 super().__init__()
516 self.name=name
517 self.parent=parent
518 self.papersize= papersize
519 self.pildevice= None
520#
521# get configuration for the virtual plotter
522# 1. pen indices
523#
524 self.pen_number=1
525 self.penconfig1=PILCONFIG.get(self.name,"penconfig1",0)
526 self.penconfig2=PILCONFIG.get(self.name,"penconfig2",1)
527#
528# create pen, do not assign color or width
529#
530 self.pen=QtGui.QPen()
531 self.pen.setCapStyle(QtCore.Qt.RoundCap)
532 self.pen.setJoinStyle(QtCore.Qt.RoundJoin)
533#
534# get papersize, set width and height of graphics scene according to papersize
535#
536 self.width= 650
537 self.lastx=-1
538 self.lasty=-1
539 if self.papersize ==0: # A4
540 self.aspect_ratio= 1.425
541 self.height= int(self.width/self.aspect_ratio)
542 else: # US
543 self.aspect_ratio= 1.346
544 self.height= int(self.width/self.aspect_ratio)
545 self.factor=self.height/ 7650
546#
547# initialize variables for plotter status information: status, error, error message
548# and the incorrect command
549#
550 self.error=0
551 self.illcmd=""
552 self.errmsg=""
553 self.status=0
554#
555# status window
556#
557 self.statuswin= None
558#
559# initialize digitize mode and digitzed coordinates (-1 means none)
560#
561 self.digi_mode= MODE_NONE
562 self.digi_x=-1
563 self.digi_y=-1
564#
565# initial scaling point position
566#
567 self.p1x=250
568 self.p1y=279
569 self.p2x=10250
570 self.p2y=7479
571#
572# emu7470 version
573#
574 self.emu_version=0
575#
576# create user interface
577#
578 self.hbox=QtWidgets.QHBoxLayout()
579#
580# plot graphics view
581#
582 self.plotview= cls_mygraphicsview(self,self.aspect_ratio)
583 self.plotlayout=cls_AspectLayout(self.aspect_ratio)
584 self.plotlayout.addWidget(self.plotview)
586 self.hbox.addLayout(self.plotlayout,1)
587 self.vbox=QtWidgets.QVBoxLayout()
588#
589# push buttons "Config" - starts configuration window
590#
591 self.configButton= QtWidgets.QPushButton("Pens")
592 self.configButton.setEnabled(False)
593 self.configButton.setAutoDefault(False)
594 self.vbox.addWidget(self.configButton)
595 self.configButton.clicked.connect(self.do_config)
596#
597# push buttons "Enter" - digitize: this button is only enabled in
598# digitizing mode
599#
600 self.digiButton= QtWidgets.QPushButton("Enter")
601 self.digiButton.setEnabled(False)
602 self.digiButton.setAutoDefault(False)
603 self.vbox.addWidget(self.digiButton)
604 self.digiButton.clicked.connect(self.do_enter)
605 self.digibutton_state= self.digiButton.isEnabled()
606#
607# push buttons "P1/P2" - show or alter P1/P2
608#
609 self.p1p2Button= QtWidgets.QPushButton("P1/P2")
610 self.p1p2Button.setEnabled(False)
611 self.p1p2Button.setAutoDefault(False)
612 self.vbox.addWidget(self.p1p2Button)
613 self.p1p2Button.clicked.connect(self.do_p1p2)
614#
615# push buttons "Clear" - in digitizing mode this clears that mode, otherwise it
616# clears the graphics scene and issues an "IN" command to the plotter emulator
617#
618 self.clearButton= QtWidgets.QPushButton("Clear")
619 self.clearButton.setEnabled(False)
620 self.clearButton.setAutoDefault(False)
621 self.vbox.addWidget(self.clearButton)
622 self.clearButton.clicked.connect(self.do_clear)
623#
624# push buttons "Generate PDF"
625#
626 self.printButton= QtWidgets.QPushButton("PDF")
627 self.printButton.setEnabled(False)
628 self.printButton.setAutoDefault(False)
629 self.vbox.addWidget(self.printButton)
630 self.printButton.clicked.connect(self.do_print)
631#
632# push buttons "Show Status": shows status window with status and error information
633#
634 self.statusButton= QtWidgets.QPushButton("Status")
635 self.statusButton.setEnabled(False)
636 self.statusButton.setAutoDefault(False)
637 self.vbox.addWidget(self.statusButton)
638 self.statusButton.clicked.connect(self.do_status)
639#
640# error LED: yellow: an error had occured, red: the emulator subprocess
641# crashed
642#
643 self.hbox2=QtWidgets.QHBoxLayout()
644 self.led=cls_LedWidget()
645 self.hbox2.addWidget(self.led)
646 self.led.setSize(15)
647 self.label=QtWidgets.QLabel("Error")
648 self.hbox2.addWidget(self.label)
649 self.hbox2.addStretch(1)
650 self.vbox.addLayout(self.hbox2)
651#
652# line edit of digitized coordinates. They are only enabled in digitizing mode. Digitizing
653# can also be performed by entering coordinates manually
654#
655 if self.papersize==0:
656 self.intvalidatorX=QtGui.QIntValidator(0,10900)
657 else:
658 self.intvalidatorX=QtGui.QIntValidator(0,10300)
660 self.intvalidatorY=QtGui.QIntValidator(0,7650)
661 self.hbox3=QtWidgets.QHBoxLayout()
662 self.labelX=QtWidgets.QLabel("X")
663 self.hbox3.addWidget(self.labelX)
664 self.lineEditX= QtWidgets.QLineEdit()
665 self.lineEditX.setValidator(self.intvalidatorX)
666 self.lineEditX.setText("")
667 self.lineEditX.setEnabled(False)
668 self.lineEditX.editingFinished.connect(self.do_finishX)
669 self.lineEditX.textChanged.connect(self.checkEnableEnter)
670 self.hbox3.addWidget(self.lineEditX)
671 self.vbox.addLayout(self.hbox3)
673 self.hbox4=QtWidgets.QHBoxLayout()
674 self.labelY=QtWidgets.QLabel("Y")
675 self.hbox4.addWidget(self.labelY)
676 self.lineEditY= QtWidgets.QLineEdit()
677 self.lineEditY.setValidator(self.intvalidatorY)
678 self.lineEditY.setText("")
679 self.lineEditY.setEnabled(False)
680 self.lineEditY.editingFinished.connect(self.do_finishY)
681 self.lineEditY.textChanged.connect(self.checkEnableEnter)
682 self.hbox4.addWidget(self.lineEditY)
683 self.vbox.addLayout(self.hbox4)
685 self.vbox.addStretch(1)
686 self.hbox.addLayout(self.vbox)
687 self.setLayout(self.hbox)
688#
689# configure plotview and scene
690#
691# app= QtWidgets.QApplication.instance()
692# scrollbar_width=app.style().pixelMetric(QtWidgets.QStyle.PM_ScrollBarExtent)
693 self.plotscene=cls_mygraphicsscene()
694 self.plotscene.setSceneRect(0,0,self.width,self.height)
695 self.plotview.setScene(self.plotscene)
696 self.plotview.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
697 self.plotview.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
698 self.plotview.setSceneRect(self.plotscene.sceneRect())
699# self.plotview.ensureVisible(0,0,self.width,self.height,0,0)
700#
701# initialize GUI command queue and lock
702#
703 self.gui_queue= queue.Queue()
704 self.gui_queue_lock= threading.Lock()
705#
706# initialize refresh timer
707#
708 self.UpdateTimer= QtCore.QTimer()
709 self.UpdateTimer.setSingleShot(True)
710 self.UpdateTimer.timeout.connect(self.process_queue)
711#
712# set HP-IL device object
713#
714 def set_pildevice(self,pildevice):
715 self.pildevice=pildevice
716#
717# enable: start timer
718#
719 def enable(self):
720 self.UpdateTimer.start(UPDATE_TIMER)
721 self.toggle_active()
722 return
723#
724# disable: reset LED, clear the GUI command queue, stop the timer
725#
726 def disable(self):
727 self.gui_queue_lock.acquire()
728 while True:
729 try:
730 self.gui_queue.get_nowait()
731 self.gui_queue.task_done()
732 except queue.Empty:
733 break
734 self.gui_queue_lock.release()
735 self.led.setColor(QtGui.QColor(0xff,0xff,0xff,0xff))
736 self.UpdateTimer.stop()
737 return
738#
739# becomes visible, do nothing
740#
741 def becomes_visible(self):
742 pass
743#
744# becomes invisible, do nothing
745#
746 def becomes_invisible(self):
747 pass
749# active/inactive: enable/disable GUI controls
750#
751 def toggle_active(self):
752 if self.parent.active:
753 self.configButton.setEnabled(True)
754 self.digiButton.setEnabled(self.digibutton_state)
755 self.p1p2Button.setEnabled(True)
756 self.clearButton.setEnabled(True)
757 self.printButton.setEnabled(True)
758 self.statusButton.setEnabled(True)
759 else:
760 self.configButton.setEnabled(False)
761 self.digibutton_state= self.digiButton.isEnabled()
762 self.digiButton.setEnabled(False)
763 self.p1p2Button.setEnabled(False)
764 self.clearButton.setEnabled(False)
765 self.printButton.setEnabled(False)
766 self.statusButton.setEnabled(False)
767#
768# action: clear button
769# if in digitize mode: leave digitize mode
770# if not in digitize mode: clear plotter view, send "IN" command to plotter, clear LED
771#
772 def do_clear(self):
773 if self.digi_mode != MODE_NONE:
774 self.digi_clear()
775 else:
776 self.plotscene.clear()
777 self.led.setColor(QtGui.QColor(0xff,0xff,0xff,0xff))
778 self.send_initialize()
779 self.p1x=250
780 self.p1y=279
781 self.p2x=10250
782 self.p2y=7479
783#
784# action: print pdf file
785#
786 def do_print(self):
787 flist= cls_pdfprinter.get_pdfFilename()
788 if flist is None:
789 return
790 printer = QtPrintSupport.QPrinter (QtPrintSupport.QPrinter.HighResolution)
791 if self.papersize==0:
792 printer.setPageSize(QtGui.QPageSize.A4)
793 else:
794 printer.setPageSize(QtGui.QPageSize.Letter)
796 printer.setPageOrientation(QtGui.QPageLayout.Landscape)
797 printer.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat)
798 printer.setOutputFileName(flist[0])
799 p = QtGui.QPainter(printer)
800 self.plotscene.render(p)
801 p.end()
802#
803# action configure plotter, show config window. A modified papersize is sent to
804# to the plotter emulator
805#
806 def do_config(self):
807 if cls_PlotterConfigWindow.getPlotterConfig(self):
808 try:
809 PILCONFIG.save()
810 except PilConfigError as e:
811 reply=QtWidgets.QMessageBox.critical(self.ui,'Error',e.msg+': '+e.add_msg,QtWidgets.QMessageBox.Ok,QtWidgets.QMessageBox.Ok)
812 return
813 self.penconfig1=PILCONFIG.get(self.name,"penconfig1")
814 self.penconfig2=PILCONFIG.get(self.name,"penconfig2")
815#
816# action: show status window
817#
818 def do_status(self):
819 if self.statuswin is None:
820 self.statuswin= cls_statusWindow(self)
821 self.statuswin.show()
822 self.statuswin.raise_()
823#
824# action: check if lineEditX and lineEditY have text so that we
825# can enable the Enter key. The textChanged signal is also emitted
826# if the text was changed programmatically. Thus we also catch a digitized
827# coordinate
828#
829 def checkEnableEnter(self):
830 if self.lineEditX.text() != "" and self.lineEditY.text()!= "":
831 self.digiButton.setEnabled(True)
832#
833# action: process digitized point
834#
835 def do_enter(self):
836#
837# get coordinates from GUI line edit. If they are empty then "Enter" was pressed
838# without digitizing a point
839 xs=self.lineEditX.text()
840 ys=self.lineEditY.text()
841 if xs !="" and ys != "":
842 x=int(xs)
843 y=int(ys)
844#
845# we have x,y in plotter coordinates, if MODE_DIGI send digitized coordinates to
846# plotter emulator and clear digitizing mode
847#
848 if self.digi_mode== MODE_DIGI:
849 self.send_digitize(x,y)
850 self.digi_clear()
851#
852# we are in MODE_P!: notice the digitized coordinate, clear digitizing mode
853# in both views, set mode to MODE_P2 and restart digitizing
854 elif self.digi_mode== MODE_P1:
855 self.p1x=x
856 self.p1y=y
857 self.plotview.digi_clear()
858 self.digi_mode= MODE_P2
859 self.plotscene.digi_mode(self.digi_mode)
860 self.plotview.digi_start()
861 self.lineEditX.setText(str(self.p2x))
862 self.lineEditY.setText(str(self.p2y))
863#
864# we are in MODE_P2: send digitized coordinates of P1 and P2 to plotter emulator
865# and clear digitizing mode
866#
867 elif self.digi_mode== MODE_P2:
868 self.p2x=x
869 self.p2y=y
870 self.send_p1p2(self.p1x,self.p1y,self.p2x,self.p2y)
871 self.digi_clear()
872 else:
873 self.digi_clear()
874#
875# start digitizing in both views and the scene, enable Enter Button and coordinate
876# line edit, disable P1/P2 button. Called by GUI command queue processing
877#
878 def digi_start(self):
879 if self.digi_mode== MODE_DIGI:
880 return
881 self.p1p2Button.setEnabled(False)
882 self.lineEditX.setEnabled(True)
883 self.lineEditY.setEnabled(True)
884 self.digi_mode= MODE_DIGI
885 self.plotscene.digi_mode(self.digi_mode)
886 self.plotview.digi_start()
887#
888# stop digitizing in both views and the scene, disable Enter button, enable P1/p2 button
889#
890 def digi_clear(self):
891 self.digiButton.setEnabled(False)
892 self.p1p2Button.setEnabled(True)
893 self.lineEditX.setText("")
894 self.lineEditY.setText("")
895 self.lineEditX.setEnabled(False)
896 self.lineEditY.setEnabled(False)
897 self.digi_mode= MODE_NONE
898 self.plotscene.digi_clear()
899 self.plotview.digi_clear()
900 return
901#
902# Action: digitize P1/P2
903#
904 def do_p1p2(self):
905#
906# enable "Enter", disable P1/P2, enable digitizing mode in scene and view,
907# enable coordinate line edit
908#
909 self.digiButton.setEnabled(True)
910 self.p1p2Button.setEnabled(False)
911 self.lineEditX.setEnabled(True)
912 self.lineEditY.setEnabled(True)
913 self.digi_mode= MODE_P1
914 self.plotscene.digi_mode(self.digi_mode)
915 self.plotview.digi_start()
916#
917# set marks according to the original postion of P1, P2
918#
919 x1=int(round(self.p1x)*self.factor)
920 y1=int(round(((self.height/self.factor) -self.p1y)*self.factor))
921 x2=int(round(self.p2x)*self.factor)
922 y2=int(round(((self.height/self.factor) -self.p2y)*self.factor))
923 self.plotscene.setMarkp1p2(x1,y1,x2,y2)
924 self.lineEditX.setText(str(self.p1x))
925 self.lineEditY.setText(str(self.p1y))
926#
927# handle editing finished of coordinate input in line edits. Transform plotter
928# coordinates to scene coordinates and move mark
929#
930 def do_finishX(self):
931 if self.lineEditX.isModified():
932 if(self.lineEditY.text()!=""):
933 x=int(self.lineEditX.text())
934 y=int(self.lineEditY.text())
935 x1=int(round(x)*self.factor)
936 y1=int(round(((self.height/self.factor) -y)*self.factor))
937 self.plotscene.setMark(x1,y1)
938 self.lineEditX.setModified(False)
940 def do_finishY(self):
941 if self.lineEditY.isModified():
942 if(self.lineEditX.text()!=""):
943 x=int(self.lineEditX.text())
944 y=int(self.lineEditY.text())
945 x1=int(round(x)*self.factor)
946 y1=int(round(((self.height/self.factor) -y)*self.factor))
947 self.plotscene.setMark(x1,y1)
948 self.lineEditY.setModified(False)
949#
950# put digitized coordinates into the line edits
951#
952 def setKoord(self,x,y):
953 self.lineEditX.setText(str(x))
954 self.lineEditY.setText(str(y))
955#
956# send digitized coordinates to plotter emulator
957#
958 def send_digitize(self,x,y):
959 self.pildevice.put_cmd("ZY %d %d" % (x,y))
960 return
961#
962# send initialize command to plotter emulator
963#
964 def send_initialize(self):
965 self.pildevice.put_cmd("IN")
966 return
967#
968# send IP command to plotter emulator
969#
970 def send_p1p2(self,xp1,yp1,xp2,yp2):
971 self.pildevice.put_cmd("IP%d,%d,%d,%d;" % (xp1,yp1,xp2,yp2))
972 return
973#
974# put command into the GUI-command queue, this is called by the thread component
975#
976 def put_cmd(self,item):
977 self.gui_queue_lock.acquire()
978 self.gui_queue.put(item)
979 self.gui_queue_lock.release()
980#
981# update pen definition
982#
983 def update_PenDef(self):
984 pendef = [0xff, 0xff, 0xff, 0x00, 0x01]
985 if self.pen_number==0:
986 pendef=[0xff,0xff,0xff,0x00,0x01]
987 elif self.pen_number==1:
988 pendef= PENCONFIG.get_pen(self.penconfig1)
989 elif self.pen_number==2:
990 pendef= PENCONFIG.get_pen(self.penconfig2)
991 self.pen.setColor(QtGui.QColor(pendef[0],pendef[1],pendef[2],pendef[3]))
992# fixed DEPRECATED use of float argument for int parameter
993 self.pen.setWidth(round(pendef[4]))
995#
996# process commands in the GUI command queue, this is called by a timer event
997#
998 def process_queue(self):
999 items=[]
1000 self.gui_queue_lock.acquire()
1001 while True:
1002 try:
1003 i=self.gui_queue.get_nowait()
1004 items.append(i)
1005 self.gui_queue.task_done()
1006 except queue.Empty:
1007 break
1008 self.gui_queue_lock.release()
1009 if len(items):
1010 for c in items:
1011 self.process(c)
1012 self.plotview.update()
1013 self.UpdateTimer.start(UPDATE_TIMER)
1014 return
1015#
1016# GUI command processing
1017#
1018 def process(self,item):
1019 cmd= item[0]
1020#
1021# clear graphhics views (issued by in IN command)
1022#
1023 if cmd== CMD_CLEAR:
1024 if self.digi_mode != MODE_NONE:
1025 self.digi_clear()
1026 self.plotscene.clear()
1027#
1028# end of commands
1029#
1030 elif cmd== CMD_EOF:
1031 pass
1032#
1033# set pen, pen 0 is transparent white. Set color and width of pen 1 and pen 2
1034# according to the entries in the pen condiguration
1035#
1036 elif cmd== CMD_SET_PEN:
1037 self.pen_number= item[1]
1039#
1040# move to new location, graphic command generated by plotter emulator
1041#
1042 elif cmd== CMD_MOVE_TO:
1043 self.lastx= item[1] * self.factor
1044 self.lasty= self.height- (item[2] * self.factor)
1045#
1046# draw to new location, graphic command generated by plotter emulator
1047#
1048 elif cmd== CMD_DRAW_TO:
1049 self.update_PenDef()
1050 x= item[1] * self.factor
1051 y= self.height- (item[2] * self.factor)
1052 self.plotscene.addLine(self.lastx,self.lasty,x,y,pen=self.pen)
1053 self.lastx=x
1054 self.lasty=y
1055#
1056# draw dot at location, graphic command generated by plotter emulator
1057#
1058 elif cmd== CMD_PLOT_AT:
1059 self.update_PenDef()
1060 x= item[1] * self.factor
1061 y= self.height- (item[2] * self.factor)
1062 rad=self.pen.width()/2.0
1063 x1=x-rad
1064 y1=y-rad
1065 self.plotscene.addEllipse(x1,y1,2*rad,2*rad,self.pen)
1066#
1067# set LED color to red
1068#
1069 elif cmd== CMD_ON_ERROR_RED:
1070 self.led.setColor(QtGui.QColor(0xff,0x00,0x00,0xff))
1071#
1072# set LED color to yellow
1073#
1074 elif cmd== CMD_ON_ERROR_YELLOW:
1075 self.led.setColor(QtGui.QColor(0xff,0xff,0x00,0xff))
1076#
1077# set LED color to transparent
1078#
1079 elif cmd== CMD_OFF_ERROR:
1080 self.led.setColor(QtGui.QColor(0xff,0xff,0xff,0xff))
1081#
1082# start digitize mode (issued by a DP command)
1084 elif cmd== CMD_DIGI_START:
1085 self.digi_start()
1086#
1087# terminate digitize mode (issued by a DC command)
1088#
1089 elif cmd== CMD_DIGI_CLEAR:
1090 self.digi_clear()
1091#
1092# P1/P2 changed (issued by an IN or IP command)
1093#
1094 elif cmd== CMD_P1P2:
1095 self.p1x=int(item[1])
1096 self.p1y=int(item[2])
1097 self.p2x=int(item[3])
1098 self.p2y=int(item[4])
1099#
1100# Get version of emu7470
1101#
1102 elif cmd== CMD_EMU_VERSION:
1103 self.emu_version=item[1]
1104#
1105# extended Error Message
1106#
1107 elif cmd== CMD_EXT_ERROR:
1108 self.error=item[1]
1109 self.illcmd=item[2]
1110 self.errmsg= item[3]
1111#
1112# set status
1113#
1114 elif cmd== CMD_SET_STATUS:
1115 self.status= item[1]
1116#
1117# logging
1118#
1119 elif cmd== CMD_LOG:
1120 if self.parent.loglevel >= item[1]:
1121 self.parent.cbLogging.logWrite(item[2])
1122 self.parent.cbLogging.logFlush()
1124#
1125# status window class --------------------------------------------------------
1126#
1127# Display status byte, error code, error message and illegal HP-GL command
1128# The window may remain open and the content of the window will be updated
1129#
1130class cls_statusWindow(QtWidgets.QDialog):
1132 def __init__(self,parent):
1133 super().__init__()
1134 self.parent=parent
1135 self.setWindowTitle("Plotter error status")
1136 self.__timer__=QtCore.QTimer()
1137 self.__timer__.timeout.connect(self.do_refresh)
1139 self.vbox=QtWidgets.QVBoxLayout()
1140 self.grid=QtWidgets.QGridLayout()
1141 self.grid.setSpacing(3)
1142 self.grid.addWidget(QtWidgets.QLabel("emu7470 version:"),1,0)
1143 self.grid.addWidget(QtWidgets.QLabel("Status:"),2,0)
1144 self.grid.addWidget(QtWidgets.QLabel("Error code:"),3,0)
1145 self.grid.addWidget(QtWidgets.QLabel("HP-GL command:"),4,0)
1146 self.grid.addWidget(QtWidgets.QLabel("Error message:"),5,0)
1147 self.lblVersion=QtWidgets.QLabel(decode_version(self.parent.emu_version))
1148 self.lblStatus=QtWidgets.QLabel("")
1149 self.lblError=QtWidgets.QLabel("")
1150 self.lblIllCmd=QtWidgets.QLabel("")
1151 self.lblErrMsg=QtWidgets.QLabel("")
1152 self.grid.addWidget(self.lblVersion,1,1)
1153 self.grid.addWidget(self.lblStatus,2,1)
1154 self.grid.addWidget(self.lblError,3,1)
1155 self.grid.addWidget(self.lblIllCmd,4,1)
1156 self.grid.addWidget(self.lblErrMsg,5,1)
1157 self.vbox.addLayout(self.grid)
1158 self.vbox.addStretch(1)
1160 self.hlayout=QtWidgets.QHBoxLayout()
1161 self.button = QtWidgets.QPushButton('OK')
1162 self.button.setFixedWidth(60)
1163 self.button.clicked.connect(self.do_exit)
1164 self.hlayout.addWidget(self.button)
1165 self.vbox.addLayout(self.hlayout)
1166 self.setLayout(self.vbox)
1167 self.resize(300,180)
1168 self.do_refresh()
1170 def hideEvent(self,event):
1171 self.__timer__.stop()
1173 def showEvent(self,event):
1174 self.__timer__.start(500)
1176 def do_exit(self):
1177 super().accept()
1178#
1179# timer event function, refresh output
1180#
1181 def do_refresh(self):
1182 self.lblStatus.setText("{0:b}".format(self.parent.parent.pildevice.getPlotterStatus()))
1183 self.lblError.setText(str(self.parent.error))
1184 self.lblIllCmd.setText(self.parent.illcmd)
1185 self.lblErrMsg.setText(self.parent.errmsg)
1187#
1188# Plotter configuration window class ------------------------------------------------
1189#
1190class cls_PlotterConfigWindow(QtWidgets.QDialog):
1192 def __init__(self,parent):
1193 super().__init__()
1194 self.__name__=parent.name
1195 self.__penconfig1__= PILCONFIG.get(self.__name__,"penconfig1")
1196 self.__penconfig2__= PILCONFIG.get(self.__name__,"penconfig2")
1197 self.setWindowTitle("Plotter configuration")
1198 self.vbox= QtWidgets.QVBoxLayout()
1199 self.grid=QtWidgets.QGridLayout()
1200 self.grid.setSpacing(3)
1203#
1204# Pen1 combo box
1205#
1206 self.grid.addWidget(QtWidgets.QLabel("Pen1:"),2,0)
1207 self.combopen1=QtWidgets.QComboBox()
1208 for pen_desc in PENCONFIG.get_penlist():
1209 self.combopen1.addItem(pen_desc)
1210 self.combopen1.setCurrentIndex(self.__penconfig1__)
1211 self.grid.addWidget(self.combopen1,2,1)
1212#
1213# Pen2 combo box
1214#
1215 self.grid.addWidget(QtWidgets.QLabel("Pen2:"),3,0)
1216 self.combopen2=QtWidgets.QComboBox()
1217 for pen_desc in PENCONFIG.get_penlist():
1218 self.combopen2.addItem(pen_desc)
1219 self.combopen2.setCurrentIndex(self.__penconfig2__)
1220 self.grid.addWidget(self.combopen2,3,1)
1222 self.vbox.addLayout(self.grid)
1223#
1224# OK, Cancel
1225#
1226 self.buttonBox = QtWidgets.QDialogButtonBox()
1227 self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
1228 self.buttonBox.setCenterButtons(True)
1229 self.buttonBox.accepted.connect(self.do_ok)
1230 self.buttonBox.rejected.connect(self.do_cancel)
1231 self.hlayout = QtWidgets.QHBoxLayout()
1232 self.hlayout.addWidget(self.buttonBox)
1233 self.vbox.addLayout(self.hlayout)
1235 self.setLayout(self.vbox)
1238 def do_ok(self):
1239 PILCONFIG.put(self.__name__,"penconfig1",self.combopen1.currentIndex())
1240 PILCONFIG.put(self.__name__,"penconfig2",self.combopen2.currentIndex())
1241 super().accept()
1244 def do_cancel(self):
1245 super().reject()
1248 @staticmethod
1249 def getPlotterConfig(parent):
1250 dialog= cls_PlotterConfigWindow(parent)
1251 result= dialog.exec()
1252 if result== QtWidgets.QDialog.Accepted:
1253 return True
1254 else:
1255 return False
1257#
1258# Plotter emulator (thrad component) -----------------------------------------------
1259#
1260# This is the thread component of the plotter emulator.
1261# The thread part is called from the __indata__ method. Incoming bytes from the
1262# HP-IL loop are preparsed until a complete HP-GL statement was received.
1263# The the commands in the HP-GL commmand queue (asynchronously sent by the GUI) part are
1264# processed first.
1265# The HP-GL commands are sent to the em7470 subprocess. The subprocess returns a number
1266# of commands and data which are prreprocessed by the thread component and put into
1267# the plotquee buffer. These commands are processed by a timer event of the GUI
1268# component.
1269#
1270class cls_HP7470(QtCore.QObject):
1272 def __init__(self,parent,guiobject,papersize):
1273 super().__init__()
1274 self.parent=parent
1275 self.guiobject= guiobject
1276 self.papersize= papersize
1277 self.cmdbuf=[]
1278 self.parse_state=0
1279 self.termchar= chr(3)
1280 self.proc=None
1281 self.pendown=False
1282 self.x=0
1283 self.y=0
1284 self.status=0
1285 self.error=0
1286 self.errmsg=""
1287 self.illcmd=""
1288 self.inparam=False
1289 self.separator=False
1290 self.numparam=0
1291 self.invalid=True
1292#
1293# handle emulator not found or crashed
1294#
1295 def setInvalid(self, errno, errmsg):
1296 self.invalid=True
1297 self.error=errno
1298 self.illcmd=""
1299 self.errmsg=errmsg
1300 self.guiobject.put_cmd([CMD_EXT_ERROR,self.error,self.illcmd,self.errmsg])
1301#
1302# send to GUI: switch LED to red
1303#
1304 self.guiobject.put_cmd([CMD_ON_ERROR_RED])
1305#
1306# disable HP-IL device permanently
1307#
1308 self.parent.disable_permanently()
1309#
1310# start the subprocess of the plotter emulator, check required version,
1311# set papeersize according to config
1312#
1313 def enable(self):
1314# progpath=os.path.join(os.path.dirname(pyilper.__file__),"emu7470","emu7470")
1315# progpath=re.sub("//","/",progpath,1)
1316 progpath=add_path("emu7470")
1318 try:
1319 if isWINDOWS():
1320 creationflags=0x08000000 # CREATE_NO_WINDOW
1321 else:
1322 creationflags=0
1323 self.proc=subprocess.Popen([progpath], bufsize=1, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, creationflags=creationflags)
1324 line=self.proc.stdout.readline()
1325 except OSError as e:
1326 self.setInvalid(100,"emu7470 not found")
1327 return
1328#
1329# close if version does not match
1330#
1331 try:
1332 version=int(line)
1333 except ValueError:
1334 version=0
1335 if version < EMU7470_VERSION:
1336 self.proc.stdin.close()
1337 self.proc.stdout.close()
1338 self.proc.kill()
1339 self.proc.wait()
1340 self.setInvalid(101,"incompatible version of emu7470")
1341 return
1342 self.cmdbuf.clear()
1343 self.guiobject.put_cmd([CMD_EMU_VERSION,line])
1344 self.parse_state=0
1345 self.invalid=False
1346 self.parent.put_cmd("ZZ%d" % self.papersize)
1347#
1348# stop the subprocess of the plotter emulator
1349#
1350 def disable(self):
1351 if not self.invalid:
1352 self.proc.stdin.close()
1353 self.proc.stdout.close()
1354 self.proc.kill()
1355 self.proc.wait()
1356#
1357# send a HP-GL command to the emu7470 subprocess and process the results:
1358# - plotter status, current termchar,
1359# - error code and errof message (if any)
1360# - output of "O." commands
1361# - clear plotter
1362# - geometry generated by emu7470 with the following commands:
1363# - select pen (pen number)
1364# - move to (coordinate)
1365# - draw to (coordinate)
1366# - plot at (coordinate)
1367#
1368# since we are not allowed to draw to the graphics window from the thread
1369# process all commands that affect the user interface are sent to the GUI
1370# command queue.
1371#
1372 def process(self,command):
1373#
1374# send HP-GL command to plotter as base16 encoded string, since the string
1375# may contain control sequences and we communicate th emu7470 with text i/o.
1376#
1377 log_line=""
1378 try:
1379#
1380# send status
1381#
1382 self.proc.stdin.write("%2.2x " % self.status )
1383#
1384# send command
1385#
1386 for c in command:
1387 i=ord(c)
1388 x="%2.2x" % i
1389 self.proc.stdin.write(x)
1390 if i == 0x0A or i== 0x0D:
1391 log_line+="("+x+")"
1392 else:
1393 log_line+=c
1394 self.proc.stdin.write("\n")
1395 self.proc.stdin.flush()
1396 log_line+="\n"
1397 self.guiobject.put_cmd([CMD_LOG,0,log_line])
1398 except OSError as e:
1399 self.proc.stdin.close()
1400 self.setInvalid(102,"ipc input/output error")
1401 return
1402 except AttributeError as e:
1403 self.proc.stdin.close()
1404 self.setInvalid(103,"ipc input/output error")
1405 return
1406#
1407# read processed results from plotter
1408#
1409 while True:
1410 try:
1411 line=self.proc.stdout.readline()
1412 except OSError as e:
1413 self.proc.stdin.close()
1414 self.setInvalid(104,"ipc input/output error")
1415 return
1416 if line =="":
1417 self.proc.stdin.close()
1418 self.proc.stdin.close()
1419 self.setInvalid(104,"ipc input/output error")
1420 return
1421 ret=line.split()
1422 cmd= int(ret[0])
1423#
1424# end of output of a command
1425#
1426 if cmd== CMD_EOF:
1427 self.guiobject.put_cmd([CMD_EOF])
1428 self.guiobject.put_cmd([CMD_EXT_ERROR,self.error,self.illcmd,self.errmsg])
1429 self.guiobject.put_cmd([CMD_SET_STATUS,self.status])
1430 break
1431#
1432# clear
1433#
1434 elif cmd== CMD_CLEAR:
1435 self.guiobject.put_cmd([CMD_CLEAR])
1436 self.parent.clear_outbuf()
1437#
1438# set pen
1439#
1440 elif cmd== CMD_SET_PEN:
1441 self.guiobject.put_cmd([CMD_SET_PEN, int(ret[1])])
1442 self.guiobject.put_cmd([CMD_LOG,2,"Set Pen %s\n" % ret[1]])
1443#
1444# move
1445#
1446 elif cmd== CMD_MOVE_TO:
1447 self.x= float(ret[1])
1448 self.y= float(ret[2])
1449 self.guiobject.put_cmd([CMD_MOVE_TO,self.x,self.y])
1450 self.guiobject.put_cmd([CMD_LOG,2,"Move To %d %d\n" % (self.x,self.y)])
1451#
1452# draw
1453#
1454 elif cmd== CMD_DRAW_TO:
1455 self.x= float(ret[1])
1456 self.y= float(ret[2])
1457 self.guiobject.put_cmd([CMD_DRAW_TO,self.x,self.y])
1458 self.guiobject.put_cmd([CMD_LOG,2,"Draw To %d %d\n" % (self.x,self.y)])
1459#
1460# draw dot
1461#
1462 elif cmd== CMD_PLOT_AT:
1463 self.x= float(ret[1])
1464 self.y= float(ret[2])
1465 self.guiobject.put_cmd([CMD_PLOT_AT, self.x,self.y])
1466 self.guiobject.put_cmd([CMD_LOG,2,"Plot At %d %d\n" % (self.x,self.y)])
1467#
1468# output from plotter to HP-IL, use the cls_pilplotter putDataToHPIL
1469# method. This puts the data to an output data buffer of pilotter
1470#
1471 elif cmd== CMD_OUTPUT:
1472 result=ret[1]+chr(0x0D)+chr(0x0A)
1473 self.parent.putDataToHPIL(result)
1474 self.guiobject.put_cmd([CMD_LOG,1,"Plotter to HP-IL: %s\n" % ret[1]])
1475#
1476# status, error, termchar
1477#
1478 elif cmd== CMD_STATUS:
1479 self.status=int(ret[1])
1480 self.error=int(ret[2])
1481 self.termchar= chr(int(ret[3]))
1482#
1483# error bit set?
1484#
1485 if self.status & 0x20:
1486 self.guiobject.put_cmd([CMD_ON_ERROR_YELLOW])
1487 else:
1488 self.errmsg=""
1489 self.illcmd=""
1490 self.guiobject.put_cmd([CMD_OFF_ERROR])
1491 self.guiobject.put_cmd([CMD_LOG,1,"Status %x, Error %d\n" % (self.status,self.error)])
1492#
1493# extended error message
1494#
1495 elif cmd== CMD_ERRMSG:
1496 self.errmsg= line[2:-1]
1497 self.illcmd="".join(self.cmdbuf)
1498 self.guiobject.put_cmd([CMD_LOG,1,"Error message %s\n" % (self.errmsg)])
1499#
1500# enter digitizing mode, status bit is handled by emu7470
1501#
1502 elif cmd== CMD_DIGI_START:
1503 self.guiobject.put_cmd([CMD_DIGI_START])
1504#
1505# clear digitizing mode, status bit is handled by emu7470
1506#
1507 elif cmd== CMD_DIGI_CLEAR:
1508 self.guiobject.put_cmd([CMD_DIGI_CLEAR])
1509#
1510# P1, P2 set
1511#
1512 elif cmd== CMD_P1P2:
1513 x1= float(ret[1])
1514 y1= float(ret[2])
1515 x2= float(ret[3])
1516 y2= float(ret[4])
1517 self.guiobject.put_cmd([CMD_P1P2,x1,y1,x2,y2])
1518 else:
1519 eprint("Unknown command %s" % ret)
1520#
1521# HP-IL device clear; clear command buffer
1522#
1523 def reset(self):
1524 self.cmdbuf.clear()
1525 self.parse_state=0
1526#
1527# process_char is called py the cls_pilplotter __indata__ method (registered)
1528# process single characters obtained from the interface loop, store
1529# complete HPGL-commands in and process them. This ugly parser ensures that
1530# emu7470 gets HPGL-Commands with a more strict HPGL-syntax
1531#
1532 def process_char(self,c):
1533 if self.parse_state==0:
1534#
1535# get the first character of command
1536#
1537 if c.isalpha():
1538 self.cmdbuf.append(c.upper())
1539 self.parse_state=1
1540 elif self.parse_state==1:
1541#
1542# get the second character of command, skip blanks first
1543#
1544 if c == " ":
1545 return
1546 if c.isalpha():
1547 self.cmdbuf.append(c.upper())
1548 self.parse_state=2
1549 if self.cmdbuf[0]== "L" and self.cmdbuf[1]=="B":
1550 self.parse_state=3
1551 if self.cmdbuf[0]== "D" and self.cmdbuf[1]=="T":
1552 self.parse_state=4
1553 if self.cmdbuf[0]== "S" and self.cmdbuf[1]=="M":
1554 self.parse_state=4
1555 else:
1556 self.cmdbuf.clear()
1557 self.parse_state=0
1558 elif self.parse_state==2:
1559#
1560# get parameters, remove all blanks, allow , as separator only
1561#
1562 if c.isdigit() or c=="." or c=="+" or c=="-":
1563 if not self.inparam:
1564 self.inparam= True
1565 if not self.separator:
1566 if self.numparam>0:
1567 self.cmdbuf.append(",")
1568 self.numparam+=1
1569 self.cmdbuf.append(c)
1570 return
1571 if c == " " :
1572 self.inparam= False
1573# self.separator=False
1574 return
1575 if c ==",":
1576 self.separator=True
1577 self.inparam= False
1578 self.cmdbuf.append(c)
1579 return
1580 self.inparam= False
1581 self.numparam=0
1582 self.separator=False
1583 self.parent.put_cmd("".join(self.cmdbuf))
1584 if c.isalpha():
1585 self.cmdbuf.clear()
1586 self.cmdbuf.append(c)
1587 self.parse_state=1
1588 else:
1589 self.cmdbuf.clear()
1590 self.parse_state=0
1591 elif self.parse_state==3:
1592#
1593# process label string
1594#
1595 if c == self.termchar:
1596 self.cmdbuf.append(self.termchar)
1597 self.parent.put_cmd("".join(self.cmdbuf))
1598 self.cmdbuf.clear()
1599 self.parse_state=0
1600 else:
1601 self.cmdbuf.append(c)
1602 elif self.parse_state==4:
1603#
1604# process single character of DT or SM command
1605#
1606 self.cmdbuf.append(c)
1607 self.parent.put_cmd("".join(self.cmdbuf))
1608 self.cmdbuf.clear()
1609 self.parse_state=0
1610#
1611# HP-IL plotter class ---------------------------------------------------------
1612#
1614class cls_pilplotter(cls_pildevbase):
1616 def __init__(self,guiobject,papersize):
1617 super().__init__()
1618 self.__guiobject__= guiobject
1619 self.__papersize__= papersize
1620#
1621# overloaded variable initialization
1622#
1623 self.__aid__ = 0x60 # accessory id
1624 self.__defaddr__ = 5 # default address alter AAU
1625 self.__did__ = "HP7470A" # device id
1626#
1627# object specific variables
1628#
1629 self.__disabled__=False # flag to disable device permanently
1630#
1631# initialize remote command queue and lock
1632#
1633 self.__plot_queue__= queue.Queue()
1634 self.__plot_queue_lock__= threading.Lock()
1635#
1636# plotter processor
1637#
1638 self.__plotter__=cls_HP7470(self,self.__guiobject__,self.__papersize__)
1639#
1640# initialize HP-IL outdata buffer
1641#
1642 self.__outbuf__= array.array('i')
1643 self.__oc__=0
1644#
1645# public (overloaded) --------
1646#
1647# enable: reset
1648#
1649 def enable(self):
1650 self.__plotter__.enable()
1651 return
1652#
1653# disable: clear the remote HP-GL command queue
1654#
1655 def disable(self):
1656 self.__plot_queue_lock__.acquire()
1657 while True:
1658 try:
1659 self.__plot_queue__.get_nowait()
1660 self.__plot_queue__.task_done()
1661 except queue.Empty:
1662 break
1663 self.__plot_queue_lock__.release()
1664 self.__plotter__.disable()
1665 self.clear_outbuf()
1666#
1667# clear output buffer
1668#
1669 def clear_outbuf(self):
1670 self.__status_lock__.acquire()
1671 self.__oc__=0
1672 self.__outbuf__= array.array('i')
1673 self.__status__= self.__status__ & 0xEF # clear ready for data
1674 self.__status_lock__.release()
1676#
1677# process frames
1678#
1679 def process(self,frame):
1681 if self.__isactive__:
1682 self.process_plot_queue()
1683 frame= super().process(frame)
1684 return frame
1685#
1686# process the remote HPGL command queue
1687#
1688 def process_plot_queue(self):
1689 items=[]
1690 self.__plot_queue_lock__.acquire()
1691 while True:
1692 try:
1693 i=self.__plot_queue__.get_nowait()
1694 items.append(i)
1695 self.__plot_queue__.task_done()
1696 except queue.Empty:
1697 break
1698 self.__plot_queue_lock__.release()
1699 if len(items):
1700 for c in items:
1701 self.__plotter__.process(c)
1702 return
1703#
1704# put remote HP-GL command into the plot-command queue
1705#
1706 def put_cmd(self,item):
1707 self.__plot_queue_lock__.acquire()
1708 self.__plot_queue__.put(item)
1709 self.__plot_queue_lock__.release()
1710#
1711# public --------
1712#
1713# put data to the HP-IL outdata buffer, called by the plotter processor
1714#
1715 def putDataToHPIL(self,s):
1716 self.__status_lock__.acquire()
1717 self.__oc__=0
1718 for c in s:
1719 self.__outbuf__.insert(0,ord(c))
1720 self.__oc__+=1
1721 self.__status__ = self.__status__ | 0x10 # set ready for data bit
1722 self.__status_lock__.release()
1724#
1725# disable permanently, if emu7470 is not available
1726#
1727 def disable_permanently(self):
1728 self.__disabled__= True
1729 self.setactive(False)
1730#
1731# get status
1732#
1733 def getPlotterStatus(self):
1734 return(self.__getstatus__())
1735#
1736# public (overloaded)
1737#
1738 def setactive(self,active):
1739 if not self.__disabled__:
1740 super().setactive(active)
1741 else:
1742 super().setactive(False)
1744#
1745# private (overloaded) --------
1746#
1747#
1748# forward data coming from HP-IL to the plotter processor
1749#
1750 def __indata__(self,frame):
1752 self.__access_lock__.acquire()
1753 locked= self.__islocked__
1754 self.__access_lock__.release()
1755 if not locked:
1756 self.__plotter__.process_char(chr(frame & 0xFF))
1757#
1758# clear device: empty HP-IL outdata buffer and reset plotter
1759#
1760 def __clear_device__(self):
1761 super().__clear_device__()
1762 self.clear_outbuf()
1763#
1764# clear plotter queue
1765#
1766 self.__plot_queue_lock__.acquire()
1767 while True:
1768 try:
1769 self.__plot_queue__.get_nowait()
1770 self.__plot_queue__.task_done()
1771 except queue.Empty:
1772 break
1773 self.__plot_queue_lock__.release()
1774#
1775# reset device
1776#
1777 self.__plotter__.reset()
1778 return
1779#
1780# send data from HP-IL outdata buffer to the loop
1781#
1782 def __outdata__(self,frame):
1783 self.__status_lock__.acquire()
1784 if self.__oc__== 0:
1785 frame= 0x540 # EOT
1786 else:
1787 frame= self.__outbuf__.pop()
1788 self.__oc__-=1
1789 if self.__oc__== 0:
1790 self.__status__= self.__status__ & 0xEF # clear ready for data bit
1791 self.__status_lock__.release()
1792 return(frame)