def __init__(self, parent=None): super(MainWindow, self).__init__(parent) # save from garbage collection self.windows.append(self) # state self.filename = None self.result = None self.modelDescription = None self.variables = dict() self.selectedVariables = set() self.startValues = dict() self.simulationThread = None # self.progressDialog = None self.plotUpdateTimer = QTimer(self) self.plotUpdateTimer.timeout.connect(self.updatePlotData) self.curves = [] # UI self.ui = Ui_MainWindow() self.ui.setupUi(self) # use a smaller default font size on Mac and Linux if sys.platform in ['darwin', 'linux']: defaultFont = QFont() defaultFont.setPixelSize(11) QApplication.setFont(defaultFont) self.setStyleSheet("QWidget { font-size: 11px; }") self.ui.treeView.setAttribute(Qt.WA_MacShowFocusRect, False) self.ui.tableView.setAttribute(Qt.WA_MacShowFocusRect, False) self.ui.logTreeView.setAttribute(Qt.WA_MacShowFocusRect, False) # set the window size to 85% of the available space geo = QApplication.desktop().availableGeometry() width = min(geo.width() * 0.85, 1100.0) height = min(geo.height() * 0.85, 900.0) self.resize(width, height) # hide the variables self.ui.dockWidget.hide() # toolbar self.startTimeLineEdit = QLineEdit("1") self.startTimeLineEdit.setToolTip("Start time") self.startTimeLineEdit.setFixedWidth(50) self.startTimeValidator = QDoubleValidator(self) self.startTimeValidator.setBottom(0) self.startTimeLineEdit.setValidator(self.startTimeValidator) self.ui.toolBar.addWidget(self.startTimeLineEdit) self.stopTimeLineEdit = QLineEdit("1") self.stopTimeLineEdit.setToolTip("Stop time") self.stopTimeLineEdit.setFixedWidth(50) self.stopTimeValidator = QDoubleValidator(self) self.stopTimeValidator.setBottom(0) self.stopTimeLineEdit.setValidator(self.stopTimeValidator) self.ui.toolBar.addWidget(self.stopTimeLineEdit) spacer = QWidget(self) spacer.setFixedWidth(10) self.ui.toolBar.addWidget(spacer) self.fmiTypeComboBox = QComboBox(self) self.fmiTypeComboBox.addItem("Co-Simulation") self.fmiTypeComboBox.setToolTip("FMI type") self.fmiTypeComboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.ui.toolBar.addWidget(self.fmiTypeComboBox) # disable widgets self.ui.actionSettings.setEnabled(False) self.ui.actionShowLog.setEnabled(False) self.ui.actionShowResults.setEnabled(False) self.ui.actionSimulate.setEnabled(False) self.ui.actionSaveResult.setEnabled(False) self.ui.actionSavePlottedResult.setEnabled(False) self.startTimeLineEdit.setEnabled(False) self.stopTimeLineEdit.setEnabled(False) self.fmiTypeComboBox.setEnabled(False) # hide the dock's title bar self.ui.dockWidget.setTitleBarWidget(QWidget()) self.ui.dockWidgetContents.setMinimumWidth(500) self.tableModel = VariablesTableModel(self.selectedVariables, self.startValues) self.tableFilterModel = VariablesFilterModel() self.tableFilterModel.setSourceModel(self.tableModel) self.tableFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.ui.tableView.setModel(self.tableFilterModel) self.treeModel = VariablesTreeModel(self.selectedVariables, self.startValues) self.treeFilterModel = VariablesFilterModel() self.treeFilterModel.setSourceModel(self.treeModel) self.treeFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.ui.treeView.setModel(self.treeFilterModel) for i, (w, n) in enumerate( zip(VariablesModel.COLUMN_WIDTHS, VariablesModel.COLUMN_NAMES)): self.ui.treeView.setColumnWidth(i, w) self.ui.tableView.setColumnWidth(i, w) if n in ['Value Reference', 'Initial', 'Causality', 'Variability']: self.ui.treeView.setColumnHidden(i, True) self.ui.tableView.setColumnHidden(i, True) # populate the recent files list settings = QSettings() recent_files = settings.value("recentFiles", defaultValue=[]) recent_files = self.removeDuplicates(recent_files) vbox = QVBoxLayout() if recent_files: added = set() for file in recent_files[:5]: link = QLabel( '<a href="%s" style="text-decoration: none">%s</a>' % (file, os.path.basename(file))) link.setToolTip(file) link.linkActivated.connect(self.load) vbox.addWidget(link) added.add(file) self.ui.recentFilesGroupBox.setLayout(vbox) self.ui.recentFilesGroupBox.setVisible(len(recent_files) > 0) # settings page self.inputFileMenu = QMenu() self.inputFileMenu.addAction("New input file...", self.createInputFile) self.inputFileMenu.addSeparator() self.inputFileMenu.addAction("Show in Explorer", self.showInputFileInExplorer) self.inputFileMenu.addAction("Open in default application", self.openInputFile) self.ui.selectInputButton.setMenu(self.inputFileMenu) # log page self.log = Log(self) self.logFilterModel = LogMessagesFilterProxyModel(self) self.logFilterModel.setSourceModel(self.log) self.logFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.ui.logTreeView.setModel(self.logFilterModel) self.ui.clearLogButton.clicked.connect(self.log.clear) self.log.numberOfDebugMessagesChanged.connect( lambda n: self.ui.showDebugMessagesButton.setText(str(n))) self.log.numberOfInfoMessagesChanged.connect( lambda n: self.ui.showInfoMessagesButton.setText(str(n))) self.log.numberOfWarningMessagesChanged.connect( lambda n: self.ui.showWarningMessagesButton.setText(str(n))) self.log.numberOfErrorMessagesChanged.connect( lambda n: self.ui.showErrorMessagesButton.setText(str(n))) self.ui.logFilterLineEdit.textChanged.connect( self.logFilterModel.setFilterFixedString) self.ui.showDebugMessagesButton.toggled.connect( self.logFilterModel.setShowDebugMessages) self.ui.showInfoMessagesButton.toggled.connect( self.logFilterModel.setShowInfoMessages) self.ui.showWarningMessagesButton.toggled.connect( self.logFilterModel.setShowWarningMessages) self.ui.showErrorMessagesButton.toggled.connect( self.logFilterModel.setShowErrorMessages) # context menu self.contextMenu = QMenu() self.actionExpandAll = self.contextMenu.addAction("Expand all") self.actionExpandAll.triggered.connect(self.ui.treeView.expandAll) self.actionCollapseAll = self.contextMenu.addAction("Collapse all") self.actionCollapseAll.triggered.connect(self.ui.treeView.collapseAll) self.contextMenu.addSeparator() self.actionCopyVariableName = self.contextMenu.addAction( "Copy Variable Name", self.copyVariableName) self.actionCopyValueReference = self.contextMenu.addAction( "Copy Value Reference", self.copyValueReference) self.contextMenu.addSeparator() self.actionEditTable = self.contextMenu.addAction( "Edit Table", self.editTable) self.contextMenu.addSeparator() self.columnsMenu = self.contextMenu.addMenu('Columns') for column in [ 'Value Reference', 'Initial', 'Causality', 'Variability' ]: action = self.columnsMenu.addAction(column) action.setCheckable(True) action.toggled.connect( lambda show, col=column: self.showColumn(col, show)) # file menu self.ui.actionExit.triggered.connect(QApplication.closeAllWindows) self.ui.actionLoadStartValues.triggered.connect(self.loadStartValues) self.ui.actionReload.triggered.connect( lambda: self.load(self.filename)) self.ui.actionSaveChanges.triggered.connect(self.saveChanges) # help menu self.ui.actionOpenFMI1SpecCS.triggered.connect( lambda: QDesktopServices.openUrl( QUrl( 'https://svn.modelica.org/fmi/branches/public/specifications/v1.0/FMI_for_CoSimulation_v1.0.1.pdf' ))) self.ui.actionOpenFMI1SpecME.triggered.connect( lambda: QDesktopServices.openUrl( QUrl( 'https://svn.modelica.org/fmi/branches/public/specifications/v1.0/FMI_for_ModelExchange_v1.0.1.pdf' ))) self.ui.actionOpenFMI2Spec.triggered.connect( lambda: QDesktopServices.openUrl( QUrl( 'https://svn.modelica.org/fmi/branches/public/specifications/v2.0/FMI_for_ModelExchange_and_CoSimulation_v2.0.pdf' ))) self.ui.actionOpenTestFMUs.triggered.connect( lambda: QDesktopServices.openUrl( QUrl( 'https://github.com/modelica/fmi-cross-check/tree/master/fmus' ))) self.ui.actionOpenWebsite.triggered.connect( lambda: QDesktopServices.openUrl( QUrl('https://github.com/CATIA-Systems/FMPy'))) self.ui.actionShowReleaseNotes.triggered.connect( lambda: QDesktopServices.openUrl( QUrl('https://fmpy.readthedocs.io/en/latest/changelog/'))) self.ui.actionCompilePlatformBinary.triggered.connect( self.compilePlatformBinary) self.ui.actionCreateCMakeProject.triggered.connect( self.createCMakeProject) # filter menu self.filterMenu = QMenu() self.filterMenu.addAction(self.ui.actionFilterInputs) self.filterMenu.addAction(self.ui.actionFilterOutputs) self.filterMenu.addAction(self.ui.actionFilterParameters) self.filterMenu.addAction(self.ui.actionFilterCalculatedParameters) self.filterMenu.addAction(self.ui.actionFilterIndependentVariables) self.filterMenu.addAction(self.ui.actionFilterLocalVariables) self.ui.filterToolButton.setMenu(self.filterMenu) # status bar self.statusIconLabel = ClickableLabel(self) self.statusIconLabel.setStyleSheet("QLabel { margin-left: 5px; }") self.statusIconLabel.clicked.connect( lambda: self.setCurrentPage(self.ui.logPage)) self.ui.statusBar.addPermanentWidget(self.statusIconLabel) self.statusTextLabel = ClickableLabel(self) self.statusTextLabel.setMinimumWidth(10) self.statusTextLabel.clicked.connect( lambda: self.setCurrentPage(self.ui.logPage)) self.ui.statusBar.addPermanentWidget(self.statusTextLabel) self.ui.statusBar.addPermanentWidget(QWidget(self), 1) # spacer self.simulationProgressBar = QProgressBar(self) self.simulationProgressBar.setFixedHeight(18) self.ui.statusBar.addPermanentWidget(self.simulationProgressBar) self.simulationProgressBar.setVisible(False) # connect signals and slots self.ui.actionNewWindow.triggered.connect(self.newWindow) self.ui.openButton.clicked.connect(self.open) self.ui.actionOpen.triggered.connect(self.open) self.ui.actionSaveResult.triggered.connect(self.saveResult) self.ui.actionSavePlottedResult.triggered.connect( lambda: self.saveResult(plotted=True)) self.ui.actionSimulate.triggered.connect(self.startSimulation) self.ui.actionSettings.triggered.connect( lambda: self.setCurrentPage(self.ui.settingsPage)) self.ui.actionShowLog.triggered.connect( lambda: self.setCurrentPage(self.ui.logPage)) self.ui.actionShowResults.triggered.connect( lambda: self.setCurrentPage(self.ui.resultPage)) self.fmiTypeComboBox.currentTextChanged.connect( self.updateSimulationSettings) self.ui.solverComboBox.currentTextChanged.connect( self.updateSimulationSettings) self.variableSelected.connect(self.updatePlotLayout) self.variableDeselected.connect(self.updatePlotLayout) self.tableModel.variableSelected.connect(self.selectVariable) self.tableModel.variableDeselected.connect(self.deselectVariable) self.treeModel.variableSelected.connect(self.selectVariable) self.treeModel.variableDeselected.connect(self.deselectVariable) self.ui.filterLineEdit.textChanged.connect( self.treeFilterModel.setFilterFixedString) self.ui.filterLineEdit.textChanged.connect( self.tableFilterModel.setFilterFixedString) self.ui.filterToolButton.toggled.connect( self.treeFilterModel.setFilterByCausality) self.ui.filterToolButton.toggled.connect( self.tableFilterModel.setFilterByCausality) self.log.currentMessageChanged.connect(self.setStatusMessage) self.ui.selectInputButton.clicked.connect(self.selectInputFile) self.ui.actionShowAboutDialog.triggered.connect(self.showAboutDialog) if os.name == 'nt': self.ui.actionCreateDesktopShortcut.triggered.connect( self.createDesktopShortcut) self.ui.actionAddFileAssociation.triggered.connect( self.addFileAssociation) else: self.ui.actionCreateDesktopShortcut.setEnabled(False) self.ui.actionAddFileAssociation.setEnabled(False) self.ui.tableViewToolButton.toggled.connect( lambda show: self.ui.variablesStackedWidget.setCurrentWidget( self.ui.tablePage if show else self.ui.treePage)) for model in [self.treeFilterModel, self.tableFilterModel]: self.ui.actionFilterInputs.triggered.connect(model.setFilterInputs) self.ui.actionFilterOutputs.triggered.connect( model.setFilterOutputs) self.ui.actionFilterParameters.triggered.connect( model.setFilterParameters) self.ui.actionFilterCalculatedParameters.triggered.connect( model.setFilterCalculatedParameters) self.ui.actionFilterIndependentVariables.triggered.connect( model.setFilterIndependentVariables) self.ui.actionFilterLocalVariables.triggered.connect( model.setFilterLocalVariables) self.ui.treeView.customContextMenuRequested.connect( self.showContextMenu) self.ui.tableView.customContextMenuRequested.connect( self.showContextMenu)
class MainWindow(QMainWindow): variableSelected = pyqtSignal(ScalarVariable, name='variableSelected') variableDeselected = pyqtSignal(ScalarVariable, name='variableDeselected') windows = [] windowOffset = QPoint() def __init__(self, parent=None): super(MainWindow, self).__init__(parent) # save from garbage collection self.windows.append(self) # state self.filename = None self.result = None self.modelDescription = None self.variables = dict() self.selectedVariables = set() self.startValues = dict() self.simulationThread = None # self.progressDialog = None self.plotUpdateTimer = QTimer(self) self.plotUpdateTimer.timeout.connect(self.updatePlotData) self.curves = [] # UI self.ui = Ui_MainWindow() self.ui.setupUi(self) # use a smaller default font size on Mac and Linux if sys.platform in ['darwin', 'linux']: defaultFont = QFont() defaultFont.setPixelSize(11) QApplication.setFont(defaultFont) self.setStyleSheet("QWidget { font-size: 11px; }") self.ui.treeView.setAttribute(Qt.WA_MacShowFocusRect, False) self.ui.tableView.setAttribute(Qt.WA_MacShowFocusRect, False) self.ui.logTreeView.setAttribute(Qt.WA_MacShowFocusRect, False) # set the window size to 85% of the available space geo = QApplication.desktop().availableGeometry() width = min(geo.width() * 0.85, 1100.0) height = min(geo.height() * 0.85, 900.0) self.resize(width, height) # hide the variables self.ui.dockWidget.hide() # toolbar self.startTimeLineEdit = QLineEdit("1") self.startTimeLineEdit.setToolTip("Start time") self.startTimeLineEdit.setFixedWidth(50) self.startTimeValidator = QDoubleValidator(self) self.startTimeValidator.setBottom(0) self.startTimeLineEdit.setValidator(self.startTimeValidator) self.ui.toolBar.addWidget(self.startTimeLineEdit) self.stopTimeLineEdit = QLineEdit("1") self.stopTimeLineEdit.setToolTip("Stop time") self.stopTimeLineEdit.setFixedWidth(50) self.stopTimeValidator = QDoubleValidator(self) self.stopTimeValidator.setBottom(0) self.stopTimeLineEdit.setValidator(self.stopTimeValidator) self.ui.toolBar.addWidget(self.stopTimeLineEdit) spacer = QWidget(self) spacer.setFixedWidth(10) self.ui.toolBar.addWidget(spacer) self.fmiTypeComboBox = QComboBox(self) self.fmiTypeComboBox.addItem("Co-Simulation") self.fmiTypeComboBox.setToolTip("FMI type") self.fmiTypeComboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.ui.toolBar.addWidget(self.fmiTypeComboBox) # disable widgets self.ui.actionSettings.setEnabled(False) self.ui.actionShowLog.setEnabled(False) self.ui.actionShowResults.setEnabled(False) self.ui.actionSimulate.setEnabled(False) self.ui.actionSaveResult.setEnabled(False) self.ui.actionSavePlottedResult.setEnabled(False) self.startTimeLineEdit.setEnabled(False) self.stopTimeLineEdit.setEnabled(False) self.fmiTypeComboBox.setEnabled(False) # hide the dock's title bar self.ui.dockWidget.setTitleBarWidget(QWidget()) self.ui.dockWidgetContents.setMinimumWidth(500) self.tableModel = VariablesTableModel(self.selectedVariables, self.startValues) self.tableFilterModel = VariablesFilterModel() self.tableFilterModel.setSourceModel(self.tableModel) self.tableFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.ui.tableView.setModel(self.tableFilterModel) self.treeModel = VariablesTreeModel(self.selectedVariables, self.startValues) self.treeFilterModel = VariablesFilterModel() self.treeFilterModel.setSourceModel(self.treeModel) self.treeFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.ui.treeView.setModel(self.treeFilterModel) for i, (w, n) in enumerate( zip(VariablesModel.COLUMN_WIDTHS, VariablesModel.COLUMN_NAMES)): self.ui.treeView.setColumnWidth(i, w) self.ui.tableView.setColumnWidth(i, w) if n in ['Value Reference', 'Initial', 'Causality', 'Variability']: self.ui.treeView.setColumnHidden(i, True) self.ui.tableView.setColumnHidden(i, True) # populate the recent files list settings = QSettings() recent_files = settings.value("recentFiles", defaultValue=[]) recent_files = self.removeDuplicates(recent_files) vbox = QVBoxLayout() if recent_files: added = set() for file in recent_files[:5]: link = QLabel( '<a href="%s" style="text-decoration: none">%s</a>' % (file, os.path.basename(file))) link.setToolTip(file) link.linkActivated.connect(self.load) vbox.addWidget(link) added.add(file) self.ui.recentFilesGroupBox.setLayout(vbox) self.ui.recentFilesGroupBox.setVisible(len(recent_files) > 0) # settings page self.inputFileMenu = QMenu() self.inputFileMenu.addAction("New input file...", self.createInputFile) self.inputFileMenu.addSeparator() self.inputFileMenu.addAction("Show in Explorer", self.showInputFileInExplorer) self.inputFileMenu.addAction("Open in default application", self.openInputFile) self.ui.selectInputButton.setMenu(self.inputFileMenu) # log page self.log = Log(self) self.logFilterModel = LogMessagesFilterProxyModel(self) self.logFilterModel.setSourceModel(self.log) self.logFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.ui.logTreeView.setModel(self.logFilterModel) self.ui.clearLogButton.clicked.connect(self.log.clear) self.log.numberOfDebugMessagesChanged.connect( lambda n: self.ui.showDebugMessagesButton.setText(str(n))) self.log.numberOfInfoMessagesChanged.connect( lambda n: self.ui.showInfoMessagesButton.setText(str(n))) self.log.numberOfWarningMessagesChanged.connect( lambda n: self.ui.showWarningMessagesButton.setText(str(n))) self.log.numberOfErrorMessagesChanged.connect( lambda n: self.ui.showErrorMessagesButton.setText(str(n))) self.ui.logFilterLineEdit.textChanged.connect( self.logFilterModel.setFilterFixedString) self.ui.showDebugMessagesButton.toggled.connect( self.logFilterModel.setShowDebugMessages) self.ui.showInfoMessagesButton.toggled.connect( self.logFilterModel.setShowInfoMessages) self.ui.showWarningMessagesButton.toggled.connect( self.logFilterModel.setShowWarningMessages) self.ui.showErrorMessagesButton.toggled.connect( self.logFilterModel.setShowErrorMessages) # context menu self.contextMenu = QMenu() self.actionExpandAll = self.contextMenu.addAction("Expand all") self.actionExpandAll.triggered.connect(self.ui.treeView.expandAll) self.actionCollapseAll = self.contextMenu.addAction("Collapse all") self.actionCollapseAll.triggered.connect(self.ui.treeView.collapseAll) self.contextMenu.addSeparator() self.actionCopyVariableName = self.contextMenu.addAction( "Copy Variable Name", self.copyVariableName) self.actionCopyValueReference = self.contextMenu.addAction( "Copy Value Reference", self.copyValueReference) self.contextMenu.addSeparator() self.actionEditTable = self.contextMenu.addAction( "Edit Table", self.editTable) self.contextMenu.addSeparator() self.columnsMenu = self.contextMenu.addMenu('Columns') for column in [ 'Value Reference', 'Initial', 'Causality', 'Variability' ]: action = self.columnsMenu.addAction(column) action.setCheckable(True) action.toggled.connect( lambda show, col=column: self.showColumn(col, show)) # file menu self.ui.actionExit.triggered.connect(QApplication.closeAllWindows) self.ui.actionLoadStartValues.triggered.connect(self.loadStartValues) self.ui.actionReload.triggered.connect( lambda: self.load(self.filename)) self.ui.actionSaveChanges.triggered.connect(self.saveChanges) # help menu self.ui.actionOpenFMI1SpecCS.triggered.connect( lambda: QDesktopServices.openUrl( QUrl( 'https://svn.modelica.org/fmi/branches/public/specifications/v1.0/FMI_for_CoSimulation_v1.0.1.pdf' ))) self.ui.actionOpenFMI1SpecME.triggered.connect( lambda: QDesktopServices.openUrl( QUrl( 'https://svn.modelica.org/fmi/branches/public/specifications/v1.0/FMI_for_ModelExchange_v1.0.1.pdf' ))) self.ui.actionOpenFMI2Spec.triggered.connect( lambda: QDesktopServices.openUrl( QUrl( 'https://svn.modelica.org/fmi/branches/public/specifications/v2.0/FMI_for_ModelExchange_and_CoSimulation_v2.0.pdf' ))) self.ui.actionOpenTestFMUs.triggered.connect( lambda: QDesktopServices.openUrl( QUrl( 'https://github.com/modelica/fmi-cross-check/tree/master/fmus' ))) self.ui.actionOpenWebsite.triggered.connect( lambda: QDesktopServices.openUrl( QUrl('https://github.com/CATIA-Systems/FMPy'))) self.ui.actionShowReleaseNotes.triggered.connect( lambda: QDesktopServices.openUrl( QUrl('https://fmpy.readthedocs.io/en/latest/changelog/'))) self.ui.actionCompilePlatformBinary.triggered.connect( self.compilePlatformBinary) self.ui.actionCreateCMakeProject.triggered.connect( self.createCMakeProject) # filter menu self.filterMenu = QMenu() self.filterMenu.addAction(self.ui.actionFilterInputs) self.filterMenu.addAction(self.ui.actionFilterOutputs) self.filterMenu.addAction(self.ui.actionFilterParameters) self.filterMenu.addAction(self.ui.actionFilterCalculatedParameters) self.filterMenu.addAction(self.ui.actionFilterIndependentVariables) self.filterMenu.addAction(self.ui.actionFilterLocalVariables) self.ui.filterToolButton.setMenu(self.filterMenu) # status bar self.statusIconLabel = ClickableLabel(self) self.statusIconLabel.setStyleSheet("QLabel { margin-left: 5px; }") self.statusIconLabel.clicked.connect( lambda: self.setCurrentPage(self.ui.logPage)) self.ui.statusBar.addPermanentWidget(self.statusIconLabel) self.statusTextLabel = ClickableLabel(self) self.statusTextLabel.setMinimumWidth(10) self.statusTextLabel.clicked.connect( lambda: self.setCurrentPage(self.ui.logPage)) self.ui.statusBar.addPermanentWidget(self.statusTextLabel) self.ui.statusBar.addPermanentWidget(QWidget(self), 1) # spacer self.simulationProgressBar = QProgressBar(self) self.simulationProgressBar.setFixedHeight(18) self.ui.statusBar.addPermanentWidget(self.simulationProgressBar) self.simulationProgressBar.setVisible(False) # connect signals and slots self.ui.actionNewWindow.triggered.connect(self.newWindow) self.ui.openButton.clicked.connect(self.open) self.ui.actionOpen.triggered.connect(self.open) self.ui.actionSaveResult.triggered.connect(self.saveResult) self.ui.actionSavePlottedResult.triggered.connect( lambda: self.saveResult(plotted=True)) self.ui.actionSimulate.triggered.connect(self.startSimulation) self.ui.actionSettings.triggered.connect( lambda: self.setCurrentPage(self.ui.settingsPage)) self.ui.actionShowLog.triggered.connect( lambda: self.setCurrentPage(self.ui.logPage)) self.ui.actionShowResults.triggered.connect( lambda: self.setCurrentPage(self.ui.resultPage)) self.fmiTypeComboBox.currentTextChanged.connect( self.updateSimulationSettings) self.ui.solverComboBox.currentTextChanged.connect( self.updateSimulationSettings) self.variableSelected.connect(self.updatePlotLayout) self.variableDeselected.connect(self.updatePlotLayout) self.tableModel.variableSelected.connect(self.selectVariable) self.tableModel.variableDeselected.connect(self.deselectVariable) self.treeModel.variableSelected.connect(self.selectVariable) self.treeModel.variableDeselected.connect(self.deselectVariable) self.ui.filterLineEdit.textChanged.connect( self.treeFilterModel.setFilterFixedString) self.ui.filterLineEdit.textChanged.connect( self.tableFilterModel.setFilterFixedString) self.ui.filterToolButton.toggled.connect( self.treeFilterModel.setFilterByCausality) self.ui.filterToolButton.toggled.connect( self.tableFilterModel.setFilterByCausality) self.log.currentMessageChanged.connect(self.setStatusMessage) self.ui.selectInputButton.clicked.connect(self.selectInputFile) self.ui.actionShowAboutDialog.triggered.connect(self.showAboutDialog) if os.name == 'nt': self.ui.actionCreateDesktopShortcut.triggered.connect( self.createDesktopShortcut) self.ui.actionAddFileAssociation.triggered.connect( self.addFileAssociation) else: self.ui.actionCreateDesktopShortcut.setEnabled(False) self.ui.actionAddFileAssociation.setEnabled(False) self.ui.tableViewToolButton.toggled.connect( lambda show: self.ui.variablesStackedWidget.setCurrentWidget( self.ui.tablePage if show else self.ui.treePage)) for model in [self.treeFilterModel, self.tableFilterModel]: self.ui.actionFilterInputs.triggered.connect(model.setFilterInputs) self.ui.actionFilterOutputs.triggered.connect( model.setFilterOutputs) self.ui.actionFilterParameters.triggered.connect( model.setFilterParameters) self.ui.actionFilterCalculatedParameters.triggered.connect( model.setFilterCalculatedParameters) self.ui.actionFilterIndependentVariables.triggered.connect( model.setFilterIndependentVariables) self.ui.actionFilterLocalVariables.triggered.connect( model.setFilterLocalVariables) self.ui.treeView.customContextMenuRequested.connect( self.showContextMenu) self.ui.tableView.customContextMenuRequested.connect( self.showContextMenu) def newWindow(self): window = MainWindow() window.show() def show(self): super(MainWindow, self).show() self.move(self.frameGeometry().topLeft() + self.windowOffset) self.windowOffset += QPoint(20, 20) def showContextMenu(self, point): """ Update and show the variables context menu """ from .TableDialog import TableDialog if self.ui.variablesStackedWidget.currentWidget() == self.ui.treePage: currentView = self.ui.treeView else: currentView = self.ui.tableView self.actionExpandAll.setEnabled(currentView == self.ui.treeView) self.actionCollapseAll.setEnabled(currentView == self.ui.treeView) selected = self.getSelectedVariables() self.actionEditTable.setEnabled( len(selected) == 1 and TableDialog.canEdit(selected[0])) can_copy = len(selected) > 0 self.actionCopyVariableName.setEnabled(can_copy) self.actionCopyValueReference.setEnabled(can_copy) self.contextMenu.exec_(currentView.mapToGlobal(point)) def load(self, filename): if not self.isVisible(): self.show() try: self.modelDescription = md = read_model_description(filename) except Exception as e: QMessageBox.warning(self, "Failed to load FMU", "Failed to load %s. %s" % (filename, e)) return self.filename = filename platforms = supported_platforms(self.filename) self.variables.clear() self.selectedVariables.clear() self.startValues.clear() for v in md.modelVariables: self.variables[v.name] = v if v.causality == 'output': self.selectedVariables.add(v) fmi_types = [] if md.coSimulation: fmi_types.append('Co-Simulation') if md.modelExchange: fmi_types.append('Model Exchange') # toolbar if md.defaultExperiment is not None: if md.defaultExperiment.stopTime is not None: self.stopTimeLineEdit.setText( str(md.defaultExperiment.stopTime)) if md.defaultExperiment.startTime is not None: self.startTimeLineEdit.setText( str(md.defaultExperiment.startTime)) # actions can_compile = md.fmiVersion == '2.0' and 'c-code' in platforms self.ui.actionCompilePlatformBinary.setEnabled(can_compile) self.ui.actionCreateCMakeProject.setEnabled(can_compile) # variables view self.treeModel.setModelDescription(md) self.tableModel.setModelDescription(md) self.treeFilterModel.invalidate() self.tableFilterModel.invalidate() self.ui.treeView.reset() self.ui.tableView.reset() # settings page self.ui.fmiVersionLabel.setText(md.fmiVersion) self.ui.fmiTypeLabel.setText(', '.join(fmi_types)) self.ui.platformsLabel.setText(', '.join(platforms)) self.ui.modelNameLabel.setText(md.modelName) self.ui.descriptionLabel.setText(md.description) self.ui.numberOfContinuousStatesLabel.setText( str(md.numberOfContinuousStates)) self.ui.numberOfEventIndicatorsLabel.setText( str(md.numberOfEventIndicators)) self.ui.numberOfVariablesLabel.setText(str(len(md.modelVariables))) self.ui.generationToolLabel.setText(md.generationTool) self.ui.generationDateAndTimeLabel.setText(md.generationDateAndTime) if md.defaultExperiment is not None and md.defaultExperiment.stepSize is not None: output_interval = float(md.defaultExperiment.stepSize) while output_interval > 1000: output_interval *= 0.5 else: output_interval = float(self.stopTimeLineEdit.text()) / 500 self.ui.outputIntervalLineEdit.setText(str(output_interval)) self.fmiTypeComboBox.clear() self.fmiTypeComboBox.addItems(fmi_types) self.updateSimulationSettings() self.setCurrentPage(self.ui.settingsPage) self.ui.dockWidget.show() self.ui.actionSettings.setEnabled(True) self.ui.actionShowLog.setEnabled(True) self.ui.actionShowResults.setEnabled(False) can_simulate = platform in platforms self.ui.actionSimulate.setEnabled(can_simulate) self.startTimeLineEdit.setEnabled(can_simulate) self.stopTimeLineEdit.setEnabled(can_simulate) self.fmiTypeComboBox.setEnabled(can_simulate and len(fmi_types) > 1) self.ui.settingsGroupBox.setEnabled(can_simulate) settings = QSettings() recent_files = settings.value("recentFiles", defaultValue=[]) recent_files = self.removeDuplicates([filename] + recent_files) # save the 10 most recent files settings.setValue('recentFiles', recent_files[:10]) self.setWindowTitle("%s - FMPy" % os.path.normpath(filename)) self.createGraphics() def open(self): start_dir = QDir.homePath() settings = QSettings() recent_files = settings.value("recentFiles", defaultValue=[]) for filename in recent_files: dirname = os.path.dirname(filename) if os.path.isdir(dirname): start_dir = dirname break filename, _ = QFileDialog.getOpenFileName( parent=self, caption="Open File", directory=start_dir, filter="FMUs (*.fmu);;All Files (*.*)") if filename: self.load(filename) def setCurrentPage(self, widget): """ Set the current page and the actions """ # block the signals during the update self.ui.actionSettings.blockSignals(True) self.ui.actionShowLog.blockSignals(True) self.ui.actionShowResults.blockSignals(True) self.ui.stackedWidget.setCurrentWidget(widget) # toggle the actions self.ui.actionSettings.setChecked(widget == self.ui.settingsPage) self.ui.actionShowLog.setChecked(widget == self.ui.logPage) self.ui.actionShowResults.setChecked(widget == self.ui.resultPage) # un-block the signals during the update self.ui.actionSettings.blockSignals(False) self.ui.actionShowLog.blockSignals(False) self.ui.actionShowResults.blockSignals(False) def selectInputFile(self): start_dir = os.path.dirname(self.filename) filename, _ = QFileDialog.getOpenFileName( parent=self, caption="Select Input File", directory=start_dir, filter="FMUs (*.csv);;All Files (*.*)") if filename: self.ui.inputFilenameLineEdit.setText(filename) def createInputFile(self): """ Create an input file based on the input variables in the model description """ input_variables = [] for variable in self.modelDescription.modelVariables: if variable.causality == 'input': input_variables.append(variable) if len(input_variables) == 0: QMessageBox.warning( self, "Cannot create input file", "The input file cannot be created because the model has no input variables" ) return filename, _ = os.path.splitext(self.filename) filename, _ = QFileDialog.getSaveFileName( parent=self, caption="Save Input File", directory=filename + '_in.csv', filter="Comma Separated Values (*.csv);;All Files (*.*)") if not filename: return with open(filename, 'w') as f: # column names f.write('"time"') for variable in input_variables: f.write(',"%s"' % variable.name) f.write('\n') # example data f.write(','.join(['0'] * (len(input_variables) + 1)) + '\n') self.ui.inputFilenameLineEdit.setText(filename) def showInputFileInExplorer(self): """ Reveal the input file in the file browser """ filename = self.ui.inputFilenameLineEdit.text() if not os.path.isfile(filename): QMessageBox.warning(self, "Cannot show input file", "The input file does not exist") return QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.dirname(filename))) def openInputFile(self): """ Open the input file in the default application """ filename = self.ui.inputFilenameLineEdit.text() if not os.path.isfile(filename): QMessageBox.warning(self, "Cannot open input file", "The input file does not exist") return QDesktopServices.openUrl(QUrl.fromLocalFile(filename)) def updateSimulationSettings(self): if self.fmiTypeComboBox.currentText() == 'Co-Simulation': self.ui.solverComboBox.setEnabled(False) self.ui.stepSizeLineEdit.setEnabled(False) self.ui.relativeToleranceLineEdit.setEnabled(True) else: self.ui.solverComboBox.setEnabled(True) fixed_step = self.ui.solverComboBox.currentText() == 'Fixed-step' self.ui.stepSizeLineEdit.setEnabled(fixed_step) self.ui.relativeToleranceLineEdit.setEnabled(not fixed_step) def selectVariable(self, variable): self.selectedVariables.add(variable) self.variableSelected.emit(variable) def deselectVariable(self, variable): self.selectedVariables.remove(variable) self.variableDeselected.emit(variable) def startSimulation(self): from fmpy.gui.simulation import SimulationThread try: start_time = float(self.startTimeLineEdit.text()) stop_time = float(self.stopTimeLineEdit.text()) step_size = float(self.ui.stepSizeLineEdit.text()) relative_tolerance = float( self.ui.relativeToleranceLineEdit.text()) if self.ui.outputIntervalRadioButton.isChecked(): output_interval = float(self.ui.outputIntervalLineEdit.text()) else: max_samples = float(self.ui.maxSamplesLineEdit.text()) output_interval = stop_time / max_samples except Exception as ex: self.log.log('error', "Failed to start simulation: %s" % ex) self.ui.stackedWidget.setCurrentWidget(self.ui.logPage) return step_size = min(step_size, output_interval) if self.ui.solverComboBox.currentText() == 'Fixed-step': solver = 'Euler' else: solver = 'CVode' if self.ui.inputCheckBox.isChecked(): input_variables = [] for variable in self.modelDescription.modelVariables: if variable.causality == 'input': input_variables.append(variable.name) try: from fmpy.util import read_csv filename = self.ui.inputFilenameLineEdit.text() input = read_csv(filename, variable_names=input_variables) except Exception as e: self.log.log( 'error', "Failed to load input from '%s'. %s" % (filename, e)) return else: input = None output = [] for variable in self.modelDescription.modelVariables: output.append(variable.name) fmi_type = 'CoSimulation' if self.fmiTypeComboBox.currentText( ) == 'Co-Simulation' else 'ModelExchange' self.simulationThread = SimulationThread( filename=self.filename, fmiType=fmi_type, startTime=start_time, stopTime=stop_time, solver=solver, stepSize=step_size, relativeTolerance=relative_tolerance, outputInterval=output_interval, startValues=self.startValues, applyDefaultStartValues=self.ui.applyDefaultStartValuesCheckBox. isChecked(), input=input, output=output, debugLogging=self.ui.debugLoggingCheckBox.isChecked(), fmiLogging=self.ui.logFMICallsCheckBox.isChecked()) self.ui.actionSimulate.setIcon(QIcon(':/icons/stop.png')) self.ui.actionSimulate.setToolTip("Stop simulation") self.ui.actionSimulate.triggered.disconnect(self.startSimulation) self.ui.actionSimulate.triggered.connect(self.simulationThread.stop) self.simulationProgressBar.setVisible(True) self.simulationThread.messageChanged.connect(self.log.log) self.simulationThread.progressChanged.connect( self.simulationProgressBar.setValue) self.simulationThread.finished.connect(self.simulationFinished) if self.ui.clearLogOnStartButton.isChecked(): self.log.clear() self.setCurrentPage(self.ui.resultPage) self.simulationThread.start() self.plotUpdateTimer.start(100) self.updatePlotLayout() def simulationFinished(self): # update UI self.ui.actionSimulate.triggered.disconnect(self.simulationThread.stop) self.ui.actionSimulate.triggered.connect(self.startSimulation) self.ui.actionSimulate.setIcon(QIcon(':/icons/play.png')) self.ui.actionSimulate.setToolTip("Start simulation") self.plotUpdateTimer.stop() self.simulationProgressBar.setVisible(False) self.ui.actionShowResults.setEnabled(True) self.ui.actionSettings.setEnabled(True) self.setCurrentPage(self.ui.resultPage) self.updatePlotLayout() if self.result is None: self.setCurrentPage(self.ui.logPage) else: self.ui.actionSaveResult.setEnabled(True) self.ui.actionSavePlottedResult.setEnabled(True) self.result = self.simulationThread.result self.simulationThread = None self.updatePlotData() def updatePlotData(self): import numpy as np if self.simulationThread is not None and len( self.simulationThread.rows) > 1: # get results from current simulation self.result = np.array(self.simulationThread.rows, dtype=np.dtype(self.simulationThread.cols)) if self.result is None: return # no results available yet time = self.result['time'] for variable, curve in self.curves: if variable.name not in self.result.dtype.names: continue y = self.result[variable.name] if variable.type == 'Real': curve.setData(x=time, y=y) else: curve.setData(x=np.repeat(time, 2)[1:], y=np.repeat(y, 2)[:-1]) def updatePlotLayout(self): self.ui.plotWidget.clear() self.curves[:] = [] if self.simulationThread is not None: start_time = self.simulationThread.startTime stop_time = self.simulationThread.stopTime elif self.result is not None: stop_time = self.result['time'][-1] else: stop_time = 1.0 pen = (0, 0, 255) for variable in self.selectedVariables: self.ui.plotWidget.nextRow() plot = self.ui.plotWidget.addPlot() if variable.type == 'Real': curve = plot.plot(pen=pen) else: if variable.type == 'Boolean': plot.setYRange(0, 1, padding=0.2) plot.getAxis('left').setTicks([[(0, 'false'), (1, 'true')], []]) curve = plot.plot(pen=pen, fillLevel=0, fillBrush=(0, 0, 255, 50), antialias=False) else: curve = plot.plot(pen=pen, antialias=False) plot.setXRange(start_time, stop_time, padding=0.05) plot.setLabel('left', variable.name) plot.showGrid(x=True, y=True, alpha=0.25) # hide the auto-scale button and disable context menu and mouse interaction plot.hideButtons() plot.setMouseEnabled(False, False) plot.setMenuEnabled(False) self.curves.append((variable, curve)) self.updatePlotData() def showColumn(self, name, show): i = VariablesModel.COLUMN_NAMES.index(name) self.ui.treeView.setColumnHidden(i, not show) self.ui.tableView.setColumnHidden(i, not show) def setStatusMessage(self, level, text): if level in ['debug', 'info', 'warning', 'error']: self.statusIconLabel.setPixmap( QPixmap(':/icons/%s-16x16.png' % level)) else: self.statusIconLabel.setPixmap(QPixmap()) self.statusTextLabel.setText(text) def dragEnterEvent(self, event): for url in event.mimeData().urls(): if not url.isLocalFile(): return event.acceptProposedAction() def dropEvent(self, event): urls = event.mimeData().urls() for url in urls: if url == urls[0]: window = self else: window = MainWindow() window.load(url.toLocalFile()) def saveResult(self, plotted=False): filename, _ = os.path.splitext(self.filename) filename, _ = QFileDialog.getSaveFileName( parent=self, caption="Save Result", directory=filename + '_out.csv', filter="Comma Separated Values (*.csv);;All Files (*.*)") if filename: from ..util import write_csv if plotted: columns = [ variable.name for variable in self.selectedVariables ] else: columns = None try: write_csv(filename=filename, result=self.result, columns=columns) except Exception as e: QMessageBox.critical( self, "Failed to write result", '"Failed to write "%s". %s' % (filename, e)) def createDesktopShortcut(self): """ Create a desktop shortcut to start the GUI """ import os from win32com.client import Dispatch import sys desktop_locations = QStandardPaths.standardLocations( QStandardPaths.DesktopLocation) path = os.path.join(desktop_locations[0], "FMPy GUI.lnk") python = sys.executable root, ext = os.path.splitext(python) pythonw = root + 'w' + ext if os.path.isfile(pythonw): target = pythonw else: target = python file_path = os.path.dirname(__file__) icon = os.path.join(file_path, 'icons', 'app_icon.ico') shell = Dispatch('WScript.Shell') shortcut = shell.CreateShortCut(path) shortcut.Targetpath = target shortcut.Arguments = '-m fmpy.gui' # shortcut.WorkingDirectory = ... shortcut.IconLocation = icon shortcut.save() def showAboutDialog(self): dialog = AboutDialog(self) dialog.show() @staticmethod def removeDuplicates(seq): """ Remove duplicates from a sequence """ seen = set() seen_add = seen.add return [x for x in seq if not (x in seen or seen_add(x))] def addFileAssociation(self): """ Associate *.fmu with the FMPy GUI """ try: from winreg import HKEY_CURRENT_USER, KEY_WRITE, REG_SZ, OpenKey, CreateKey, SetValueEx, CloseKey python = sys.executable root, ext = os.path.splitext(python) pythonw = root + 'w' + ext if os.path.isfile(pythonw): target = pythonw else: target = python key_path = r'Software\Classes\fmpy.gui\shell\open\command' CreateKey(HKEY_CURRENT_USER, key_path) key = OpenKey(HKEY_CURRENT_USER, key_path, 0, KEY_WRITE) SetValueEx(key, '', 0, REG_SZ, '"%s" -m fmpy.gui "%%1"' % target) CloseKey(key) key_path = r'SOFTWARE\Classes\.fmu' CreateKey(HKEY_CURRENT_USER, key_path) key = OpenKey(HKEY_CURRENT_USER, key_path, 0, KEY_WRITE) SetValueEx(key, '', 0, REG_SZ, 'fmpy.gui') CloseKey(key) QMessageBox.information( self, "File association added", "The file association for *.fmu has been added") except Exception as e: QMessageBox.critical( self, "File association failed", "The file association for *.fmu could not be added. %s" % e) def copyValueReference(self): """ Copy the value references of the selected variables to the clipboard """ text = '\n'.join( [str(v.valueReference) for v in self.getSelectedVariables()]) QApplication.clipboard().setText(text) def copyVariableName(self): """ Copy the names of the selected variables to the clipboard """ text = '\n'.join([str(v.name) for v in self.getSelectedVariables()]) QApplication.clipboard().setText(text) def getSelectedVariables(self): """ Returns a list of selected variables in the current view """ variables = [] if self.ui.variablesStackedWidget.currentWidget() == self.ui.treePage: for index in self.ui.treeView.selectionModel().selectedRows(): sourceIndex = self.treeFilterModel.mapToSource(index) treeItem = sourceIndex.internalPointer() if treeItem.variable is not None: variables.append(treeItem.variable) else: for index in self.ui.tableView.selectionModel().selectedRows(): sourceIndex = self.tableFilterModel.mapToSource(index) variable = sourceIndex.internalPointer() variables.append(variable) return variables def createGraphics(self): """ Create the graphical representation of the FMU's inputs and outputs """ def variableColor(variable): if variable.type == 'Real': return QColor.fromRgb(0, 0, 127) elif variable.type in ['Integer', 'Enumeration']: return QColor.fromRgb(255, 127, 0) elif variable.type == 'Boolean': return QColor.fromRgb(255, 0, 255) elif variable.type == 'String': return QColor.fromRgb(0, 128, 0) else: return QColor.fromRgb(0, 0, 0) inputVariables = [] outputVariables = [] maxInputLabelWidth = 0 maxOutputLabelWidth = 0 textItem = QGraphicsTextItem() fontMetrics = QFontMetricsF(textItem.font()) for variable in self.modelDescription.modelVariables: if variable.causality == 'input': inputVariables.append(variable) elif variable.causality == 'output': outputVariables.append(variable) for variable in inputVariables: maxInputLabelWidth = max(maxInputLabelWidth, fontMetrics.width(variable.name)) for variable in outputVariables: maxOutputLabelWidth = max(maxOutputLabelWidth, fontMetrics.width(variable.name)) from math import floor scene = QGraphicsScene() self.ui.graphicsView.setScene(scene) group = QGraphicsItemGroup() scene.addItem(group) group.setPos(200.5, -50.5) lh = 15 # line height w = max(150., maxInputLabelWidth + maxOutputLabelWidth + 20) h = max(50., 10 + lh * max(len(inputVariables), len(outputVariables))) block = QGraphicsRectItem(0, 0, w, h, group) block.setPen(QColor.fromRgb(0, 0, 255)) pen = QPen() pen.setWidthF(1) font = QFont() font.setPixelSize(10) # inputs y = floor((h - len(inputVariables) * lh) / 2 - 2) for variable in inputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(3) text.setY(y) polygon = QPolygonF([ QPointF(-13.5, y + 4), QPointF(1, y + 11), QPointF(-13.5, y + 18) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) contour.setPen(QPen(Qt.NoPen)) contour.setBrush(variableColor(variable)) y += lh # outputs y = floor((h - len(outputVariables) * lh) / 2 - 2) for variable in outputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(w - 3 - text.boundingRect().width()) text.setY(y) polygon = QPolygonF([ QPointF(w, y + 0 + 7.5), QPointF(w + 7, y + 3.5 + 7.5), QPointF(w, y + 7 + 7.5) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) pen = QPen() pen.setColor(variableColor(variable)) pen.setJoinStyle(Qt.MiterJoin) contour.setPen(pen) y += lh def saveChanges(self): from ..util import change_fmu output_file, _ = QFileDialog.getSaveFileName( parent=self, caption='Save Changed FMU', directory=self.filename, filter='FMUs (*.fmu)') if output_file: change_fmu(input_file=self.filename, output_file=output_file, start_values=self.startValues) def loadStartValues(self): from ..util import get_start_values start_values = get_start_values(self.filename) self.startValues.update(start_values) self.ui.treeView.reset() self.ui.tableView.reset() def editTable(self): """ Open the table dialog """ from .TableDialog import TableDialog variables = self.getSelectedVariables() if len(variables) == 1: start_values = self.startValues.copy() dialog = TableDialog( modelVariables=self.modelDescription.modelVariables, variable=variables[0], startValues=start_values) if dialog.exec_() == QDialog.Accepted: self.startValues.clear() self.startValues.update(start_values) def compilePlatformBinary(self): """ Compile the platform binary """ from ..util import compile_platform_binary platforms = supported_platforms(self.filename) if fmpy.platform in platforms: button = QMessageBox.question( self, "Platform binary already exists", "The FMU already contains a binary for the current platform. Do you want to compile and overwrite the existing binary?" ) if button == QMessageBox.No: return try: compile_platform_binary(self.filename) except Exception as e: QMessageBox.critical(self, "Failed to compile platform binaries", str(e)) return self.load(self.filename) def createCMakeProject(self): """ Create a CMake project from a C code FMU """ from fmpy.util import create_cmake_project project_dir = QFileDialog.getExistingDirectory( parent=self, caption='Select CMake Project Folder', directory=os.path.dirname(self.filename)) if project_dir: create_cmake_project(self.filename, project_dir)