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

1031 statements  

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 

103 

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 

117 

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 

140 

141MODE_DIGI=0 

142MODE_P1=1 

143MODE_P2=2 

144MODE_NONE=3 

145 

146def eprint(*args, **kwargs): 

147 print(*args, file=sys.stderr, **kwargs) 

148# 

149# plotter widget ---------------------------------------------------------- 

150# 

151class cls_tabplotter(cls_tabgeneric): 

152 

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): 

235 

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) 

242 

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) 

248 

249 def setColor(self,color): 

250 self.ledColor=color 

251 self.repaint() 

252 

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# 

260 

261class cls_DigitizedMark(QtWidgets.QGraphicsItem): 

262 

263 def __init__(self): 

264 super().__init__() 

265 self.rect = QtCore.QRectF(0,0, 10, 10) 

266 

267 def boundingRect(self): 

268 return self.rect 

269 

270 def setPos(self,x,y): 

271 super().setPos(x-5,y-5) 

272 

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) 

280 

281# 

282# custom class mark for Scaling points ---------------------------------------- 

283# 

284class cls_P1P2Mark(QtWidgets.QGraphicsTextItem): 

285 

286 def __init__(self,string): 

287 super().__init__(string) 

288 self.font=QtGui.QFont(FONT) 

289 self.font.setPixelSize(10) 

290 

291 def setPos(self,x,y): 

292 super().setPos(x-3,y-5) 

293 

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) 

302 

303# 

304# custom class graphics scene with digitizing capabilities --------------------- 

305# 

306class cls_mygraphicsscene(QtWidgets.QGraphicsScene): 

307 

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 = [] 

375 

376 def __del__(self): 

377 item = self.takeAt(0) 

378 while item: 

379 item = self.takeAt(0) 

380 

381 def addItem(self, item): 

382 self.itemList.append(item) 

383 

384 def count(self): 

385 return len(self.itemList) 

386 

387 def itemAt(self, index): 

388 if index >= 0 and index < len(self.itemList): 

389 return self.itemList[index] 

390 

391 return None 

392 

393 def takeAt(self, index): 

394 if index >= 0 and index < len(self.itemList): 

395 return self.itemList.pop(index) 

396 

397 return None 

398 

399 def expandingDirections(self): 

400 return QtCore.Qt.Orientations(QtCore.Qt.Orientation(3)) 

401 

402 def hasHeightForWidth(self): 

403 return True 

404 

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 

409 

410 def setGeometry(self, rect): 

411 super(cls_AspectLayout, self).setGeometry(rect) 

412 self.doLayout(rect, False) 

413 

414 def sizeHint(self): 

415 return self.minimumSize() 

416 

417 def minimumSize(self): 

418 size = QtCore.QSize() 

419 

420 for item in self.itemList: 

421 size = size.expandedTo(item.minimumSize()) 

422 

423 return size 

424 

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) 

436 

437 

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): 

445 

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) 

489 

490 def resizeEvent(self,event): 

491 self.fitInView(self.parent.plotscene.sceneRect(),QtCore.Qt.KeepAspectRatio) 

492 

493 

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): 

513 

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) 

585 

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) 

659 

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) 

672 

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) 

684 

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 

748 

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) 

795 

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) 

939 

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])) 

994 

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] 

1038 

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) 

1083 

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() 

1123 

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): 

1131 

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) 

1138 

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) 

1159 

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() 

1169 

1170 def hideEvent(self,event): 

1171 self.__timer__.stop() 

1172 

1173 def showEvent(self,event): 

1174 self.__timer__.start(500) 

1175 

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) 

1186 

1187# 

1188# Plotter configuration window class ------------------------------------------------ 

1189# 

1190class cls_PlotterConfigWindow(QtWidgets.QDialog): 

1191 

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) 

1201 

1202 

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) 

1221 

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) 

1234 

1235 self.setLayout(self.vbox) 

1236 

1237 

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() 

1242 

1243 

1244 def do_cancel(self): 

1245 super().reject() 

1246 

1247 

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 

1256 

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): 

1271 

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") 

1317 

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# 

1613 

1614class cls_pilplotter(cls_pildevbase): 

1615 

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() 

1675 

1676# 

1677# process frames  

1678# 

1679 def process(self,frame): 

1680 

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() 

1723 

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) 

1743 

1744# 

1745# private (overloaded) -------- 

1746# 

1747# 

1748# forward data coming from HP-IL to the plotter processor 

1749# 

1750 def __indata__(self,frame): 

1751 

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)