def save(self, path, filetype=None, title=None, description=None, svg_dpi=SVG_INKSCAPE): if path is None or not isinstance(path, str): logger.error('please provide a valide path to save the image "' + str(path) + '"') return if filetype is None: if path.lower().endswith('.svg'): filetype = 'svg' else: filetype = 'raster' dpi = 72 # 300 # inkscape 96 ? check for illustrator --> check if filetype == 'svg': generator = QSvgGenerator() generator.setFileName(path) if svg_dpi == self.SVG_ILLUSTRATOR: generator.setSize(QSize(595, 842)) generator.setViewBox(QRect(0, 0, 595, 842)) else: generator.setSize(QSize(794, 1123)) generator.setViewBox(QRect(0, 0, 794, 1123)) if title is not None and isinstance(title, str): generator.setTitle(title) if description is not None and isinstance(description, str): generator.setDescription(description) generator.setResolution( svg_dpi ) # fixes issues in inkscape of pt size --> 72 pr illustrator and 96 pr inkscape but need change size painter = QPainter(generator) print(generator.title(), generator.heightMM(), generator.height(), generator.widthMM(), generator.resolution(), generator.description(), generator.logicalDpiX()) else: scaling_factor_dpi = 1 scaling_factor_dpi = self.scaling_factor_to_achieve_DPI(300) image = QtGui.QImage( QSize( self.cm_to_inch(21) * dpi * scaling_factor_dpi, self.cm_to_inch(29.7) * dpi * scaling_factor_dpi), QtGui.QImage.Format_RGB32) painter = QPainter( image) # see what happens in case of rounding of pixels painter.scale(scaling_factor_dpi, scaling_factor_dpi) painter.setRenderHint( QPainter.HighQualityAntialiasing ) # to improve rendering #Antialiasing otherwise or nothing self.paint(painter) painter.end() if filetype != 'svg': image.save(path)
def __save(self): """If AerialWare has been used as a module, emits signal 'done'. Saves user results into SVG and makes report otherwise. """ # Variables for report self.pointsMeridian = "" self.pointsHorizontal = "" # Total lengths of paths. Used in report and methods. self.lenMeridian = 0 self.lenMeridianWithTurns = 0 self.lenHorizontal = 0 self.lenHorizontalWithTurns = 0 # Fields for methods self.pathMeridianPointsPx = [] self.pathMeridianPointsDeg = [] self.pathMeridianLinesPx = [] self.pathMeridianLinesWithTurnsPx = [] self.pathMeridianLinesDeg = [] self.pathMeridianLinesWithTurnsDeg = [] self.pathHorizontalPointsPx = [] self.pathHorizontalPointsDeg = [] self.pathHorizontalLinesPx = [] self.pathHorizontalLinesWithTurnsPx = [] self.pathHorizontalLinesDeg = [] self.pathHorizontalLinesWithTurnsDeg = [] # Process each line def processLines(lines, isMeridian=False): i = 2 # Counter for report isEven = False # If current line is even we must swap it's points. for line in lines: linePx = line.line() p1 = linePx.p1() p2 = linePx.p2() p1Deg = self.pxToDeg(p1.x(), p1.y()) p2Deg = self.pxToDeg(p2.x(), p2.y()) lineDeg = QLineF(p1Deg, p2Deg) lineLength = self.__lenMeters(p1Deg, p2Deg) if isMeridian: self.pathMeridianLinesWithTurnsPx.append(linePx) self.pathMeridianLinesWithTurnsDeg.append(lineDeg) self.lenMeridianWithTurns += lineLength else: self.pathHorizontalLinesWithTurnsPx.append(linePx) self.pathHorizontalLinesWithTurnsDeg.append(lineDeg) self.lenHorizontalWithTurns += lineLength if line.pen().style( ) == Qt.SolidLine: # Check if current line doesn't represent turn if isEven: p1, p2, p1Deg, p2Deg = p2, p1, p2Deg, p1Deg point = f'"{i - 1}","{p1Deg.x()}","{p1Deg.y()}"\n' + f'"{i}","{p2Deg.x()}","{p2Deg.y()}"\n' if isMeridian: self.pointsMeridian += point self.pathMeridianPointsPx.extend([p1, p2]) self.pathMeridianPointsDeg.extend([p1Deg, p2Deg]) self.pathMeridianLinesPx.append(linePx) self.pathMeridianLinesDeg.append(lineDeg) self.lenMeridian += lineLength else: self.pointsHorizontal += point self.pathHorizontalPointsPx.extend([p1, p2]) self.pathHorizontalPointsDeg.extend([p1Deg, p2Deg]) self.pathHorizontalLinesPx.append(linePx) self.pathHorizontalLinesDeg.append(lineDeg) self.lenHorizontal += lineLength isEven = not isEven i += 2 processLines(self.scene.getMeridianLines(), True) processLines(self.scene.getHorizontalLines()) if self.getResultsAfterCompletion: self.done.emit() return self.__disableItems() # Make report pointHeader = f'"{self.lang.repPoint}","{self.lang.lblLatitude}","{self.lang.lblLongitude}"\n' if self.lenHorizontalWithTurns > self.lenMeridianWithTurns: directionWithTurns = self.lang.repFlyMeridians elif self.lenHorizontalWithTurns < self.lenMeridianWithTurns: directionWithTurns = self.lang.repFlyHorizontals else: directionWithTurns = self.lang.repFlyEqual if self.lenHorizontal > self.lenMeridian: directionWithoutTurns = self.lang.repFlyMeridians elif self.lenHorizontal < self.lenMeridian: directionWithoutTurns = self.lang.repFlyHorizontals else: directionWithoutTurns = self.lang.repFlyEqual report = ( f'"{self.lang.repCornersDescription}"\n' f'"{self.lang.lblCorner}","{self.lang.lblLongitude}","{self.lang.lblLatitude}"\n' f'"{self.lang.lblTopLeft}","{self.xTL}","{self.yTL}"\n' f'"{self.lang.lblTopRight}","{self.xTR}","{self.yTR}"\n' f'"{self.lang.lblBottomLeft}","{self.xBL}","{self.yBL}"\n' f'"{self.lang.lblBottomRight}","{self.xBR}","{self.yBR}"\n' f'"{self.lang.lblDelimiters}","{self.xD}","{self.yD}"\n\n' f'"{self.lang.repTotalWithTurns}"\n' f'"{self.lang.repByMeridians}","{self.lenMeridianWithTurns}"\n' f'"{self.lang.repByHorizontals}","{self.lenHorizontalWithTurns}"\n' f'"{self.lang.repBetterFlyBy}","{directionWithTurns}"\n\n' f'"{self.lang.repTotalWithoutTurns}"\n' f'"{self.lang.repByMeridians}","{self.lenMeridian}"\n' f'"{self.lang.repByHorizontals}","{self.lenHorizontal}"\n' f'"{self.lang.repBetterFlyBy}","{directionWithoutTurns}"\n\n' f'"{self.lang.repAerialParams}"\n' f'"{self.lang.repArea}","{self.maxHorizontal}","x","{self.maxVertical}"\n' f'"{self.lang.lblDesiredRes}","{self.camRatio}"\n' f'"{self.lang.lblRes}","{self.camWidth}","x","{self.camHeight}"\n' f'"{self.lang.lblHeight}","{self.flightHeight}"\n' f'"{self.lang.lblFocal}","{self.focalLength}"\n\n' f'"{self.lang.repMeridianPoints}"\n' + pointHeader + self.pointsMeridian + f'\n"{self.lang.repHorizontalPoints}"\n' + pointHeader + self.pointsHorizontal) # Save image self.__disableItems() file = QFileDialog.getSaveFileName(self, self.lang.saveFile, "", self.lang.vectorImage + " (*.svg)")[0] if file == "": self.__enableItems() return # And choose where to save report reportName = "" while reportName == "": reportName = QFileDialog.getSaveFileName( self, self.lang.saveFile, "", self.lang.table + " (*.csv)")[0] if reportName == "": msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText(self.lang.errSaveBoth) msg.exec_() rect = self.scene.sceneRect() gen = QSvgGenerator() gen.setFileName(file) gen.setSize(rect.size().toSize()) gen.setViewBox(rect) gen.setTitle("Flight paths generated by AerialWare") gen.setDescription(gen.title()) # Notice: painting will temporarily freeze application because QGraphicsScene::render is not thread safe. # Don't try putting this into python's threads and QThread, doesn't work, I've tried, trust me XD painter = QPainter(gen) self.scene.render(painter) painter.end() # Save report reportFile = open(reportName, "w") reportFile.write(report) reportFile.close() self.__enableItems()