from functools import partial
from collections import defaultdict
import sys
import numpy as np
from OMChem.Flowsheet import Flowsheet
import pandas as pd
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QTextDocument ,QTextCursor ,QTextCharFormat ,QFont ,QPixmap
from PyQt5.uic import loadUiType
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QGraphicsProxyWidget, QGraphicsObject, QGraphicsEllipseItem ,QGraphicsPixmapItem,QApplication, QGraphicsView, QGraphicsScene, QHBoxLayout, QWidget, QLabel, QUndoStack
from PyQt5.QtGui import QBrush ,QTransform ,QMouseEvent
import PyQt5.QtGui as QtGui
import PyQt5.QtCore as QtCore
import PyQt5.QtWidgets as QtWidgets
from ComponentSelector import *
from Bin_Phase_env import *
from UnitOperations import *
from Streams import *
import datetime
from Container import *
from Graphics import *
import pickle
import threading
import os
import ctypes
import sys
ui,_ = loadUiType('main.ui')
'''
MainApp class is responsible for all the main App Ui operations
'''
class MainApp(QMainWindow,ui):
'''
Initializing the application
'''
def __init__(self):
QMainWindow.__init__(self)
# Loading and setting up style sheet
self.setupUi(self)
# style = open('cyan.css','r')
# style = style.read()
# self.setStyleSheet(style)
# Initializing attributes
self.zoom_count = 0
self.thrd = None
# Creating instances of classes for the main app
self.container = Container.Container(self.textBrowser, self.graphicsView)
self.comp = ComponentSelector(self)
# Setting up interactive canvas
self.scene = self.container.graphics.get_scene() ###
self.graphicsView.setScene(self.scene)
self.graphicsView.setMouseTracking(True)
self.graphicsView.keyPressEvent=self.delete_call
self.setDockNestingEnabled(True)
self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea)
self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea)
self.addDockWidget(Qt.BottomDockWidgetArea,self.dockWidget_2)
# Calling initialisation
self.menu_bar()
self.button_handler()
self.comp.show()
'''
MenuBar function handels all the all the operations of
menu bar like new,zoom,comounds selector, simulation options.
'''
def menu_bar(self):
self.actionSelectCompounds.triggered.connect(self.select_compounds)
self.actionSelectCompounds.setShortcut('Ctrl+C')
self.actionZoomIn.triggered.connect(self.zoom_in)
self.actionZoomIn.setShortcut('Ctrl++')
self.actionNew.triggered.connect(self.new)
self.actionNew.setShortcut('Ctrl+N')
self.actionZoomOut.triggered.connect(self.zoom_out)
self.actionZoomOut.setShortcut('Ctrl+-')
self.actionResetZoom.triggered.connect(self.zoom_reset)
self.actionResetZoom.setShortcut('Ctrl+R')
self.actionHelp.triggered.connect(self.help)
self.actionHelp.setShortcut('Ctrl+H')
self.actionSequentialMode.triggered.connect(partial(self.simulate,'SM'))
self.actionSequentialMode.setShortcut('Ctrl+M')
self.actionEquationOriented.triggered.connect(partial(self.simulate,'EQN'))
self.actionEquationOriented.setShortcut('Ctrl+E')
self.actionUndo.triggered.connect(self.undo)
self.actionUndo.setShortcut('Ctrl+Z')
self.actionRedo.triggered.connect(self.redo)
self.actionRedo.setShortcut('Ctrl+Y')
self.actionSave.triggered.connect(self.save)
self.actionSave.setShortcut('Ctrl+S')
self.actionOpen.triggered.connect(self.open)
self.actionOpen.setShortcut('Ctrl+O')
self.actionTerminate.triggered.connect(self.terminate)
self.actionTerminate.setShortcut('Ctrl+T')
self.actionBinaryPhaseEnvelope.triggered.connect(self.bin_phase_env)
'''
Handles all the buttons of different components.
'''
def button_handler(self):
self.pushButton.clicked.connect(partial(self.component,'MaterialStream'))
self.pushButton_7.clicked.connect(partial(self.component,'Mixer'))
self.pushButton_14.clicked.connect(partial(self.component,'Pump'))
self.pushButton_26.clicked.connect(partial(self.component,'DistillationColumn'))
self.pushButton_18.clicked.connect(partial(self.component,'ShortcutColumn'))
self.pushButton_11.clicked.connect(partial(self.component,'Heater'))
self.pushButton_10.clicked.connect(partial(self.component,'Splitter'))
self.pushButton_9.clicked.connect(partial(self.component,'Flash'))
self.pushButton_25.clicked.connect(partial(self.component,'Valve'))
self.pushButton_12.clicked.connect(partial(self.component,'Cooler'))
self.pushButton_13.clicked.connect(partial(self.component,'CompoundSeparator'))
self.pushButton_15.clicked.connect(partial(self.component,'AdiabaticCompressor'))
self.pushButton_16.clicked.connect(partial(self.component,'AdiabaticExpander'))
'''
Displays help box
'''
def help(self):
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Question)
msgBox.setTextFormat(Qt.RichText);
msgBox.setText("For any Help or Suggestion you can contact us at\n contact-om@fossee.in or at Visit fossee.in!")
msgBox.setStandardButtons(QMessageBox.Ok)
msgBox.exec_()
def bin_phase_env(self):
#compounds = self.comp.getComp()
if len(self.comp.get_compounds())<2:
QMessageBox.about(self, 'Important', "Please select at least 2 Compounds first")
self.comp.show()
#compunds = self.comp.getComp()
else:
self.bin_phase = BinPhaseEnv(self.comp)
self.bin_phase.show()
'''
Shows Compounds Selector Dialog
'''
def select_compounds(self):
self.comp.show()
'''
Returns current time in a required particular format
'''
def current_time(self):
now = datetime.datetime.now()
time = str(now.hour) + ":" + str(now.minute) + ":" +str(now.second)
return time
'''
Simulate function is responsible for the simulation
of the designed flowsheet in a particular mode
selected by the user.
'''
def simulate(self,mode):
self.thrd = threading.Thread(target=self.container.simulate, args=(mode,))
self.thrd.start()
def terminate(self):
os.chdir(self.container.flowsheet.root_dir)
if self.thrd:
thread_id = self.thrd.ident
print('____________________Going to terminate simulation thread with Thread ID:',thread_id,'____________________')
print('____________________Going to terminate the new process created for omc____________________')
self.container.flowsheet.process.terminate()
print('____________________New process created for omc is terminated.____________________')
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
self.textBrowser.append("["+str(self.current_time())+"] Terminating the simulation ")
print('____________________Simulation thread terminated____________________')
if res > 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
print('Exception raise (Thread termination) failure')
'''
Resets the zoom level to default scaling
'''
def zoom_reset(self):
if(self.zoom_count>0):
for i in range(self.zoom_count):
self.zoomout()
elif(self.zoom_count<0):
for i in range(abs(self.zoom_count)):
self.zoomin()
'''
ZoomOut the canvas
'''
def zoom_out(self):
self.graphicsView.scale(1.0/1.15,1.0/1.15)
self.zoom_count -=1
'''
ZoomIn the canvas
'''
def zoom_in(self):
self.graphicsView.scale(1.15,1.15)
self.zoom_count +=1
'''
Instantiate a NodeItem object for selected type of
component and added that on canvas/flowsheeting area.
'''
def component(self,unit_operation_type):
if(self.comp.is_compound_selected()):
self.type = unit_operation_type
if(self.type=="MaterialStream"):
self.obj = MaterialStream(compound_names = compound_selected)
else:
self.obj = eval(self.type)()
self.container.add_unit_operation(self.obj)
else:
QMessageBox.about(self, 'Important', "Please Select Compounds first")
self.comp.show()
'''
New is used to delete all the existing work.
'''
def new(self):
self.undo_redo_helper()
self.comp.tableWidget.setRowCount(0)
self.textBrowser.append("[" + str(self.current_time()) + "] New flowsheet is created ... ")
dock_widget_lst.clear()
'''
Handels all the operations which will happen when delete button is pressed.
'''
def delete_call(self,event):
try:
if event.key() == QtCore.Qt.Key_Delete:
l=self.scene.selectedItems()
for i in l:
eval(i.type).counter -= 1
self.container.delete(l)
except Exception as e:
print(e)
'''
It helps by clearing screen and loading the objects by undo redo methods
'''
def undo_redo_helper(self):
for i in self.container.unit_operations:
type(i).counter = 1
del self.container
for i in dock_widget_lst:
i.hide()
del i
lst.clear()
self.container = Container.Container(self.textBrowser, self.graphicsView)
compound_selected.clear()
self.scene = self.container.graphics.get_scene()
self.graphicsView.setScene(self.scene)
self.graphicsView.setMouseTracking(True)
self.graphicsView.keyPressEvent=self.delete_call
'''
Function for undo
'''
def undo(self):
redo_data = pop('Undo')
if redo_data is not None:
push('Redo', redo_data)
undo_data = get_last_list('Undo')
messages = self.textBrowser.toPlainText()
try:
self.undo_redo_helper()
self.container.graphics.load_canvas(undo_data, self.container)
self.textBrowser.setText(messages)
except Exception as e:
print(e)
self.textBrowser.append(messages)
else:
messages = self.textBrowser.toPlainText()
self.textBrowser.setText(messages)
self.textBrowser.append("[" + str(self.current_time()) + "] No more undo can be done!... ")
'''
Function for redo
'''
def redo(self):
redo_data = pop('Redo')
if redo_data is not None:
push('Undo', redo_data)
messages = self.textBrowser.toPlainText()
self.undo_redo_helper()
self.container.graphics.load_canvas(redo_data, self.container)
self.textBrowser.setText(messages)
else:
messages = self.textBrowser.toPlainText()
self.textBrowser.setText(messages)
self.textBrowser.append("[" + str(self.current_time()) + "] No more redo can be done!... ")
'''
Function for saving the current canvas items and compound_selected
'''
def save(self):
data = []
for i in self.container.unit_operations:
data.append(i)
print(i.pos)
data.append(compound_selected)
print(data)
file_format = 'sim'
initial_path = QDir.currentPath() + 'untitled.' + file_format
file_name, _ = QFileDialog.getSaveFileName(self, "Save As",
initial_path, "%s Files (*.%s);; All Files (*)" %
(file_format.upper(), file_format))
#if file_name != "":
try:
with open(file_name, 'wb') as f: #'saved_file.sim'
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
except Exception as e:
pass
'''
Function for loading previous saved canvas and simulation
'''
def open(self):
try:
file_format = 'sim'
initial_path = QDir.currentPath() + 'untitled.' + file_format
file_name, _ = QFileDialog.getOpenFileName(self, "Open As",
initial_path, "%s Files (*.%s);; All Files (*)" %
(file_format.upper(), file_format))
# if fileName != "":
# self.new()
self.undo_redo_helper()
with open(file_name, 'rb') as f:
obj = pickle.load(f)
print(obj)
self.container.graphics.load_canvas(obj, self.container)
except Exception as e:
pass
def main():
clean_file('Undo')
clean_file('Redo')
app = QApplication(sys.argv)
window = MainApp()
window.showMaximized()
app.exec()
if __name__ == '__main__':
main()