Bladeren bron

basic UI for example modeling

Lucas Heer 7 jaren geleden
bovenliggende
commit
7d9c4201aa

+ 6 - 2
commons.py

@@ -18,7 +18,11 @@ def all_instance_models():
 
 def all_example_models():
     """ Returns a list of paths of all example models """
-    example_models = mv.model_list("models/example")
+    try:
+        example_models = mv.model_list("models/example")
+    except mv.UnknownLocation:
+        # no example models
+        return []
     example_models_full = ["models/example/"+exm for exm in example_models]
     return example_models_full
 
@@ -82,7 +86,7 @@ def new_example_model():
             pass
     if nums:
         idx = sorted(nums)[-1] + 1
-    exm = "models/instance/ex{}".format(idx)
+    exm = "models/example/ex{}".format(idx)
     print("Adding new example model {}".format(exm))
     mv.model_add(exm, "formalisms/graphMM")
     mid = mv.instantiate(exm, "Model")

+ 5 - 7
main.py

@@ -45,7 +45,6 @@ def upload_instance_model():
     mv.model_add("models/instance/im1", "formalisms/graphMM", content)
     mv.verify("models/instance/im1", "formalisms/graphMM")
 
-
 def upload_rename_alc():
     print("Loading rename ALC ...")
     try:
@@ -120,20 +119,19 @@ if __name__ == "__main__":
     mv.init()
     mv.login("admin", "admin")
 
-    #upload_graph_MM()
+    upload_graph_MM()
     #upload_example_models()
     #upload_instance_model()
 
     #sys.exit()
 
-    from commons import new_instance_model
-    im = new_instance_model()
-
+    from commons import new_example_model
+    exm = new_example_model()
 
+    run_ui(mode="EXM", model=exm)
     #run_ui(mode="IM", model="models/instance/im1")
-    run_ui(mode="IM", model=im)
     print("verify ... ")
-    print(mv.verify(im, "formalisms/graphMM"))
+    print(mv.verify(exm, "formalisms/graphMM"))
     sys.exit(0)
 
     #upload_instance_model()

+ 238 - 1
sketchUI/exm_mainwindow.py

@@ -1,9 +1,246 @@
-from PyQt5.QtWidgets import QMainWindow
+from PyQt5.QtWidgets import QMainWindow, QAction, QActionGroup, QGraphicsItem, QGraphicsView
+from PyQt5.QtGui import QIcon
+from PyQt5.QtCore import QStateMachine, QState
 from sketchUI.ui import Ui_MainWindow
+from sketchUI.exm_scene import SketchScene, Mode
+from sketchUI import mvops
+from sketchUI.graphics_node_item import GraphicsNodeItem
+from wrappers.modelverse import element_list_nice
 
 
 class EXMMainWindow(QMainWindow, Ui_MainWindow):
     def __init__(self, model):
         QMainWindow.__init__(self)
         self.setupUi(self)
+        self.setWindowTitle(model)
         self._cur_model = model
+
+        self._scene = SketchScene(model, self)
+        self._scene.setSceneRect(0, 0, 200, 200)
+        self.graphicsView.setScene(self._scene)
+        self._scene.set_mode(Mode.SELECT)
+
+        self.populate_types()
+        self.listWidget.itemDoubleClicked.connect(self._on_list_item_clicked)
+
+        self._setup_toolbar()
+        self._setup_statemachine()
+
+        # load model
+        self._load_model()
+
+        # lastly, run state machine
+        self._statemachine.start()
+
+    def populate_types(self):
+        # sync example model types to list widget
+        self.listWidget.clear()
+        self.listWidget.addItems(mvops.get_available_types())
+
+    def _setup_toolbar(self):
+        # create action buttons on toolbar
+        self.select_action = QAction("Select", self)
+        self.select_action.setIcon(QIcon("sketchUI/icons/select.png"))
+        self.select_action.setCheckable(True)
+
+        self.connect_action = QAction("Connect", self)
+        self.connect_action.setIcon(QIcon("sketchUI/icons/connect.png"))
+        self.connect_action.setCheckable(True)
+
+        self.line_action = QAction("Draw line", self)
+        self.line_action.setIcon(QIcon("sketchUI/icons/line.png"))
+        self.line_action.setCheckable(True)
+
+        self.rect_action = QAction("Draw Rectangle", self)
+        self.rect_action.setIcon(QIcon("sketchUI/icons/rect.png"))
+        self.rect_action.setCheckable(True)
+
+        self.circle_action = QAction("Draw Circle", self)
+        self.circle_action.setIcon(QIcon("sketchUI/icons/circle.png"))
+        self.circle_action.setCheckable(True)
+
+        self.pen_action = QAction("Pen", self)
+        self.pen_action.setIcon(QIcon("sketchUI/icons/pen.svg"))
+        self.pen_action.setCheckable(True)
+
+        action_group = QActionGroup(self)
+        action_group.setExclusive(True)
+        action_group.addAction(self.select_action)
+        action_group.addAction(self.connect_action)
+        action_group.addAction(self.line_action)
+        action_group.addAction(self.rect_action)
+        action_group.addAction(self.circle_action)
+        action_group.addAction(self.pen_action)
+
+        for item in action_group.actions():
+            self.toolBar.addAction(item)
+
+    def _setup_statemachine(self):
+        self._statemachine = QStateMachine()
+        state_select = QState()
+        state_connect = QState()
+        state_draw_line = QState()
+        state_draw_rect = QState()
+        state_draw_circle = QState()
+        state_draw_free = QState()
+
+        # from select
+        state_select.addTransition(self.select_action.triggered, state_select)
+        state_select.addTransition(self.connect_action.triggered, state_connect)
+        state_select.addTransition(self.line_action.triggered, state_draw_line)
+        state_select.addTransition(self.rect_action.triggered, state_draw_rect)
+        state_select.addTransition(self.circle_action.triggered, state_draw_circle)
+        state_select.addTransition(self.pen_action.triggered, state_draw_free)
+        state_select.entered.connect(self._state_select_entered)
+        # from connect
+        state_connect.addTransition(self.connect_action.triggered, state_connect)
+        state_connect.addTransition(self.select_action.triggered, state_select)
+        state_connect.addTransition(self.line_action.triggered, state_draw_line)
+        state_connect.addTransition(self.rect_action.triggered, state_draw_rect)
+        state_connect.addTransition(self.circle_action.triggered, state_draw_circle)
+        state_connect.addTransition(self.pen_action.triggered, state_draw_free)
+        state_connect.entered.connect(self._state_connect_entered)
+        # from rectangle
+        state_draw_rect.addTransition(self.rect_action.triggered, state_draw_rect)
+        state_draw_rect.addTransition(self.connect_action.triggered, state_connect)
+        state_draw_rect.addTransition(self.select_action.triggered, state_select)
+        state_draw_rect.addTransition(self.line_action.triggered, state_draw_line)
+        state_draw_rect.addTransition(self.circle_action.triggered, state_draw_circle)
+        state_draw_rect.addTransition(self.pen_action.triggered, state_draw_free)
+        state_draw_rect.entered.connect(self._state_draw_rect_entered)
+        # from line
+        state_draw_line.addTransition(self.line_action.triggered, state_draw_line)
+        state_draw_line.addTransition(self.rect_action.triggered, state_draw_rect)
+        state_draw_line.addTransition(self.connect_action.triggered, state_connect)
+        state_draw_line.addTransition(self.select_action.triggered, state_select)
+        state_draw_line.addTransition(self.circle_action.triggered, state_draw_circle)
+        state_draw_line.addTransition(self.pen_action.triggered, state_draw_free)
+        state_draw_line.entered.connect(self._state_draw_line_entered)
+        # from circle
+        state_draw_circle.addTransition(self.circle_action.triggered, state_draw_circle)
+        state_draw_circle.addTransition(self.line_action.triggered, state_draw_line)
+        state_draw_circle.addTransition(self.rect_action.triggered, state_draw_rect)
+        state_draw_circle.addTransition(self.connect_action.triggered, state_connect)
+        state_draw_circle.addTransition(self.select_action.triggered, state_select)
+        state_draw_circle.addTransition(self.pen_action.triggered, state_draw_free)
+        state_draw_circle.entered.connect(self._state_draw_circle_entered)
+        # from freehand
+        state_draw_free.addTransition(self.pen_action.triggered, state_draw_free)
+        state_draw_free.addTransition(self.circle_action.triggered, state_draw_circle)
+        state_draw_free.addTransition(self.line_action.triggered, state_draw_line)
+        state_draw_free.addTransition(self.rect_action.triggered, state_draw_rect)
+        state_draw_free.addTransition(self.connect_action.triggered, state_connect)
+        state_draw_free.addTransition(self.select_action.triggered, state_select)
+        state_draw_free.entered.connect(self._state_draw_free_entered)
+
+        self._statemachine.addState(state_select)
+        self._statemachine.addState(state_connect)
+        self._statemachine.addState(state_draw_rect)
+        self._statemachine.addState(state_draw_line)
+        self._statemachine.addState(state_draw_circle)
+        self._statemachine.addState(state_draw_free)
+        self._statemachine.setInitialState(state_select)
+
+    def _state_select_entered(self):
+        self._scene.set_mode(Mode.SELECT)
+        self._make_items_movable(True)
+        self._enable_box_select(True)
+
+    def _state_connect_entered(self):
+        self._scene.set_mode(Mode.CONNECT)
+        self._make_items_movable(False)
+        self._enable_box_select(False)
+
+    def _state_draw_rect_entered(self):
+        self._scene.set_mode(Mode.RECT)
+        self._make_items_movable(False)
+        self._enable_box_select(False)
+
+    def _state_draw_line_entered(self):
+        self._scene.set_mode(Mode.LINE)
+        self._make_items_movable(False)
+        self._enable_box_select(False)
+
+    def _state_draw_circle_entered(self):
+        self._scene.set_mode(Mode.CIRCLE)
+        self._make_items_movable(False)
+        self._enable_box_select(False)
+
+    def _state_draw_free_entered(self):
+        self._scene.set_mode(Mode.FREE)
+        self._make_items_movable(False)
+        self._enable_box_select(False)
+
+    def _make_items_movable(self, movable):
+        for item in self._scene.items():
+            try:
+                # hacky hack because of pythons isinstance fails due to import chaos
+                # edges are not selectable or movable
+                item.__hack__()
+                item.setFlag(QGraphicsItem.ItemIsMovable, False)
+                item.setFlag(QGraphicsItem.ItemIsSelectable, False)
+                continue
+            except AttributeError:
+                pass
+            item.setFlag(QGraphicsItem.ItemIsMovable, movable)
+            item.setFlag(QGraphicsItem.ItemIsSelectable, movable)
+
+    def _enable_box_select(self, enable):
+        if enable:
+            self.graphicsView.setDragMode(QGraphicsView.RubberBandDrag)
+        else:
+            self.graphicsView.setDragMode(QGraphicsView.NoDrag)
+
+    def _load_model(self):
+        model = element_list_nice(self._cur_model)
+        if not model:
+            # empty
+            return
+
+        for item in model:
+            typ = item["type"]
+            if typ == "Node":
+                # first, draw all nodes
+                node_type = item["typeID"]
+                # workaround since get_attrs returns strings with " and list_nice doesnt
+                if not node_type.startswith("\""):
+                    node_type = "\"" + node_type + "\""
+                self._add_node_to_scene(item["id"], node_type)
+
+        for item in model:
+            typ = item["type"]
+            if typ == "Edge":
+                target = item["__target"]
+                src = item["__source"]
+                self._add_edge_to_scene(src, target)
+
+    def _add_node_to_scene(self, node_id, node_type, x=0, y=0):
+        item = GraphicsNodeItem(node_id)
+        item.setText(node_type)
+        item.setPos(x, y)
+        item.setFlag(QGraphicsItem.ItemIsMovable, True)
+        item.setFlag(QGraphicsItem.ItemIsSelectable, True)
+        self._scene.addItem(item)
+
+    def _add_edge_to_scene(self, from_id, to_id):
+        from_item = None
+        to_item = None
+        for item in self._scene.items():
+            try:
+                node_id = item.node_id
+            except AttributeError:
+                # no node item, continue
+                continue
+            if node_id == from_id:
+                from_item = item
+                continue
+            if node_id == to_id:
+                to_item = item
+
+        self._scene.draw_edge(from_item, to_item, is_new=False)
+
+    def _on_list_item_clicked(self, event):
+        # add node to model
+        node_id = mvops.add_node(self._cur_model, event.text())
+        # render node
+        self._add_node_to_scene(node_id, event.text())

+ 193 - 0
sketchUI/exm_scene.py

@@ -0,0 +1,193 @@
+from enum import Enum
+from PyQt5.QtWidgets import QGraphicsScene, QGraphicsItem, QGraphicsLineItem, QGraphicsRectItem, \
+    QGraphicsEllipseItem, QInputDialog
+from PyQt5.Qt import Qt, QPointF, QPen, QTransform
+from PyQt5.QtCore import pyqtSignal
+from sketchUI.graphics_edge_item import GraphicsEdgeItem
+from sketchUI.graphics_node_item import GraphicsNodeItem
+from sketchUI import mvops
+
+
+class Mode(Enum):
+    SELECT = 0
+    CONNECT = 1
+    LINE = 2
+    RECT = 3
+    CIRCLE = 4
+    FREE = 5
+
+
+class SketchScene(QGraphicsScene):
+    def __init__(self, model, parent):
+        QGraphicsScene.__init__(self)
+        self._cur_drawing = False
+        self._mode = None  # set from mainwindow on start
+        self._from_item = None  # mouse pressed on this item if in connect mode
+        self._cur_model = model
+
+        self._orig_point = QPointF()
+        self._free_draw_lines = []
+
+        self._parent = parent
+
+    def set_mode(self, mode):
+        self._mode = mode
+
+    def _in_draw_mode(self):
+        # are we in one of the draw modes? (rect, line, free, ...)
+        if self._mode == Mode.CONNECT or self._mode == Mode.SELECT:
+            return False
+        return True
+
+    def draw_edge(self, from_item, to_item, is_new):
+        line = GraphicsEdgeItem(from_item, to_item)
+        line.setFlag(QGraphicsItem.ItemIsMovable, False)
+        line.setFlag(QGraphicsItem.ItemIsSelectable, False)
+        self.addItem(line)
+        line.redraw()
+
+        if is_new:
+            mvops.add_edge(self._cur_model, from_item.node_id, to_item.node_id)
+
+    def mousePressEvent(self, event):
+        if event.button() == Qt.LeftButton and self._in_draw_mode():
+            # start drawing, save click point
+            self._orig_point = event.scenePos()
+            self._cur_drawing = True
+        elif event.button() == Qt.LeftButton and self._mode == Mode.CONNECT:
+            item = self.itemAt(event.scenePos(), QTransform())
+            if not item:
+                return
+            self._from_item = item
+        else:
+            pass
+
+        QGraphicsScene.mousePressEvent(self, event)
+
+    def mouseMoveEvent(self, event):
+        # if in freehand mode, draw lines from move movement
+        if self._mode == Mode.FREE and self._cur_drawing:
+            pt = event.scenePos()
+            line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(), pt.x(), pt.y())
+            line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
+            self.addItem(line)
+
+            self._orig_point = pt
+            self._free_draw_lines.append(line)
+
+        QGraphicsScene.mouseMoveEvent(self, event)
+
+    def mouseReleaseEvent(self, event):
+        if self._cur_drawing:
+            end_point = event.scenePos()
+
+            if self._mode == Mode.LINE:
+                line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(),
+                                         end_point.x(), end_point.y())
+                line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
+                self.addItem(line)
+
+            elif self._mode == Mode.RECT:
+                width = abs(end_point.x() - self._orig_point.x())
+                height = abs(end_point.y() - self._orig_point.y())
+                rect = QGraphicsRectItem(self._orig_point.x(), self._orig_point.y(),
+                                         width, height)
+                rect.setPen(QPen(Qt.black, 2, Qt.SolidLine))
+                self.addItem(rect)
+
+            elif self._mode == Mode.CIRCLE:
+                width = abs(end_point.x() - self._orig_point.x())
+                height = abs(end_point.y() - self._orig_point.y())
+                ellipse = QGraphicsEllipseItem(self._orig_point.x(), self._orig_point.y(),
+                                               width, height)
+                ellipse.setPen(QPen(Qt.black, 2, Qt.SolidLine))
+                self.addItem(ellipse)
+
+            elif self._mode == Mode.FREE:
+                line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(),
+                                         end_point.x(), end_point.y())
+                line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
+                self.addItem(line)
+
+                # group lines together
+                self._free_draw_lines.append(line)
+                group = self.createItemGroup(self._free_draw_lines)
+                group.setFlag(QGraphicsItem.ItemIsSelectable, False)
+                group.setFlag(QGraphicsItem.ItemIsMovable, False)
+                del self._free_draw_lines[:]
+
+            else:
+                pass
+
+            self._cur_drawing = False
+
+        else:
+            if self._mode == Mode.SELECT:
+                item = self.itemAt(event.scenePos(), QTransform())
+                if not item:
+                    return
+                for item in self.items():
+                    try:
+                        item.__hack__()  # hack to check if item is of edge type
+                        item.redraw()
+                    except AttributeError:
+                        continue
+
+            elif self._mode == Mode.CONNECT:
+                item = self.itemAt(event.scenePos(), QTransform())
+                if not item:
+                    return
+                self.draw_edge(self._from_item, item, is_new=True)
+
+            else:
+                pass
+
+        QGraphicsScene.mouseReleaseEvent(self, event)
+
+    def keyPressEvent(self, event):
+        if not self._mode == Mode.SELECT:
+            return
+        # "del" deletes all selected items
+        if event.key() == Qt.Key_Delete:
+            for item in self.selectedItems():
+                self.removeItem(item)
+
+        # "G" groups selected items
+        elif event.key() == Qt.Key_G:
+            selected = self.selectedItems()
+            self.clearSelection()
+
+            group = self.createItemGroup(selected)
+            bb_rect = QGraphicsRectItem(group.boundingRect())
+            bb_rect.setPen(QPen(Qt.gray, 1, Qt.DashLine))
+            group.addToGroup(bb_rect)
+            group.setFlag(QGraphicsItem.ItemIsSelectable, True)
+            group.setFlag(QGraphicsItem.ItemIsMovable, True)
+
+        elif event.key() == Qt.Key_T:
+            selected = self.selectedItems()
+            if len(selected) != 1:
+                return
+            item = selected[0]
+            # "T" lets the user type the selected item
+            text, ok = QInputDialog.getText(self._parent, "Text input", "Enter type")
+            if ok and text:
+                print("Typing item {} to type {}".format(item, text))
+                if text in mvops.get_available_types():
+                    print("Already such a type: {}".format(text))
+                    return
+                self._type_item(item, text)
+        else:
+            QGraphicsScene.keyPressEvent(self, event)
+
+    def _type_item(self, item, typ):
+        # typing means elevating it to a real node
+        nid = mvops.add_node(self._cur_model, typ)
+        nodeitem = GraphicsNodeItem(nid)
+        nodeitem.setText(typ)
+        nodeitem.setPos(item.x(), item.y())
+        nodeitem.setFlag(QGraphicsItem.ItemIsSelectable, True)
+        nodeitem.setFlag(QGraphicsItem.ItemIsMovable, True)
+        self.removeItem(item)
+        self.addItem(nodeitem)
+        self._parent.populate_types()

BIN
sketchUI/icons/circle.png


BIN
sketchUI/icons/line.png


File diff suppressed because it is too large
+ 1 - 0
sketchUI/icons/pen.svg


BIN
sketchUI/icons/rect.png


+ 5 - 5
sketchUI/im_mainwindow.py

@@ -1,4 +1,4 @@
-from PyQt5.QtWidgets import QMainWindow, QGraphicsItem, QAction, QActionGroup
+from PyQt5.QtWidgets import QMainWindow, QGraphicsItem, QAction, QActionGroup, QGraphicsView
 from PyQt5.QtGui import QIcon
 from PyQt5.QtCore import QStateMachine, QState
 from sketchUI.ui import Ui_MainWindow
@@ -12,6 +12,7 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
     def __init__(self, model):
         QMainWindow.__init__(self)
         self.setupUi(self)
+        self.setWindowTitle(model)
         self._cur_model = model
 
         self._scene = CustomScene(model)
@@ -87,7 +88,6 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
                 src = item["__source"]
                 self._add_edge_to_scene(src, target)
 
-
     def _state_connect_entered(self):
         self._scene.set_mode(Mode.CONNECT)
         self._make_items_movable(False)
@@ -140,8 +140,8 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
 
         self._scene.draw_edge(from_item, to_item, is_new=False)
 
-    def _on_list_item_clicked(self, action):
+    def _on_list_item_clicked(self, event):
         # add new node to model in mv
-        node_id = mvops.add_node(self._cur_model, action.text())
+        node_id = mvops.add_node(self._cur_model, event.text())
         # render to scene
-        self._add_node_to_scene(node_id, action.text())
+        self._add_node_to_scene(node_id, event.text())

+ 0 - 2
sketchUI/im_scene.py

@@ -65,5 +65,3 @@ class CustomScene(QGraphicsScene):
 
         if is_new:
             mvops.add_edge(self._cur_model, from_item.node_id, to_item.node_id)
-
-        self._from_item = None

+ 0 - 1
sketchUI/main.py

@@ -22,4 +22,3 @@ def run_ui(mode, model):
 if __name__ == "__main__":
     sys.path.append(".")
     sys.exit(run_ui(mode="IM", model="models/instance/im1"))
-

+ 1 - 1
wrappers/modelverse_SCCD.py

@@ -1,7 +1,7 @@
 """
 Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, and Yentl Van Tendeloo (for the inspiration)
 
-Date:   Wed Apr 18 12:31:05 2018
+Date:   Thu Apr 19 16:12:36 2018
 
 Model author: Yentl Van Tendeloo
 Model name:   MvK Server