digsim-logic-simulator 0.22.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- digsim/__init__.py +6 -0
- digsim/app/__main__.py +12 -0
- digsim/app/cli.py +68 -0
- digsim/app/gui/__init__.py +6 -0
- digsim/app/gui/_circuit_area.py +468 -0
- digsim/app/gui/_component_selection.py +154 -0
- digsim/app/gui/_main_window.py +163 -0
- digsim/app/gui/_top_bar.py +339 -0
- digsim/app/gui/_utils.py +26 -0
- digsim/app/gui/_warning_dialog.py +46 -0
- digsim/app/gui_objects/__init__.py +7 -0
- digsim/app/gui_objects/_bus_bit_object.py +94 -0
- digsim/app/gui_objects/_buzzer_object.py +97 -0
- digsim/app/gui_objects/_component_context_menu.py +79 -0
- digsim/app/gui_objects/_component_object.py +374 -0
- digsim/app/gui_objects/_component_port_item.py +63 -0
- digsim/app/gui_objects/_dip_switch_object.py +104 -0
- digsim/app/gui_objects/_gui_note_object.py +80 -0
- digsim/app/gui_objects/_gui_object_factory.py +80 -0
- digsim/app/gui_objects/_hexdigit_object.py +53 -0
- digsim/app/gui_objects/_image_objects.py +239 -0
- digsim/app/gui_objects/_label_object.py +97 -0
- digsim/app/gui_objects/_logic_analyzer_object.py +86 -0
- digsim/app/gui_objects/_seven_segment_object.py +131 -0
- digsim/app/gui_objects/_shortcut_objects.py +82 -0
- digsim/app/gui_objects/_yosys_object.py +32 -0
- digsim/app/gui_objects/images/AND.png +0 -0
- digsim/app/gui_objects/images/Analyzer.png +0 -0
- digsim/app/gui_objects/images/BUF.png +0 -0
- digsim/app/gui_objects/images/Buzzer.png +0 -0
- digsim/app/gui_objects/images/Clock.png +0 -0
- digsim/app/gui_objects/images/DFF.png +0 -0
- digsim/app/gui_objects/images/DIP_SWITCH.png +0 -0
- digsim/app/gui_objects/images/FlipFlop.png +0 -0
- digsim/app/gui_objects/images/IC.png +0 -0
- digsim/app/gui_objects/images/LED_OFF.png +0 -0
- digsim/app/gui_objects/images/LED_ON.png +0 -0
- digsim/app/gui_objects/images/MUX.png +0 -0
- digsim/app/gui_objects/images/NAND.png +0 -0
- digsim/app/gui_objects/images/NOR.png +0 -0
- digsim/app/gui_objects/images/NOT.png +0 -0
- digsim/app/gui_objects/images/ONE.png +0 -0
- digsim/app/gui_objects/images/OR.png +0 -0
- digsim/app/gui_objects/images/PB.png +0 -0
- digsim/app/gui_objects/images/Switch_OFF.png +0 -0
- digsim/app/gui_objects/images/Switch_ON.png +0 -0
- digsim/app/gui_objects/images/XNOR.png +0 -0
- digsim/app/gui_objects/images/XOR.png +0 -0
- digsim/app/gui_objects/images/YOSYS.png +0 -0
- digsim/app/gui_objects/images/ZERO.png +0 -0
- digsim/app/images/app_icon.png +0 -0
- digsim/app/model/__init__.py +6 -0
- digsim/app/model/_model.py +210 -0
- digsim/app/model/_model_components.py +162 -0
- digsim/app/model/_model_new_wire.py +57 -0
- digsim/app/model/_model_objects.py +155 -0
- digsim/app/model/_model_settings.py +35 -0
- digsim/app/model/_model_shortcuts.py +72 -0
- digsim/app/settings/__init__.py +8 -0
- digsim/app/settings/_component_settings.py +415 -0
- digsim/app/settings/_gui_settings.py +71 -0
- digsim/app/settings/_shortcut_dialog.py +39 -0
- digsim/circuit/__init__.py +7 -0
- digsim/circuit/_circuit.py +329 -0
- digsim/circuit/_waves_writer.py +61 -0
- digsim/circuit/components/__init__.py +26 -0
- digsim/circuit/components/_bus_bits.py +68 -0
- digsim/circuit/components/_button.py +44 -0
- digsim/circuit/components/_buzzer.py +45 -0
- digsim/circuit/components/_clock.py +54 -0
- digsim/circuit/components/_dip_switch.py +73 -0
- digsim/circuit/components/_flip_flops.py +99 -0
- digsim/circuit/components/_gates.py +246 -0
- digsim/circuit/components/_hexdigit.py +82 -0
- digsim/circuit/components/_ic.py +36 -0
- digsim/circuit/components/_label_wire.py +167 -0
- digsim/circuit/components/_led.py +18 -0
- digsim/circuit/components/_logic_analyzer.py +60 -0
- digsim/circuit/components/_mem64kbyte.py +42 -0
- digsim/circuit/components/_memstdout.py +37 -0
- digsim/circuit/components/_note.py +25 -0
- digsim/circuit/components/_on_off_switch.py +54 -0
- digsim/circuit/components/_seven_segment.py +28 -0
- digsim/circuit/components/_static_level.py +28 -0
- digsim/circuit/components/_static_value.py +44 -0
- digsim/circuit/components/_yosys_atoms.py +1353 -0
- digsim/circuit/components/_yosys_component.py +232 -0
- digsim/circuit/components/atoms/__init__.py +23 -0
- digsim/circuit/components/atoms/_component.py +280 -0
- digsim/circuit/components/atoms/_digsim_exception.py +8 -0
- digsim/circuit/components/atoms/_port.py +398 -0
- digsim/circuit/components/ic/74162.json +1331 -0
- digsim/circuit/components/ic/7448.json +834 -0
- digsim/storage_model/__init__.py +7 -0
- digsim/storage_model/_app.py +58 -0
- digsim/storage_model/_circuit.py +126 -0
- digsim/synth/__init__.py +6 -0
- digsim/synth/__main__.py +67 -0
- digsim/synth/_synthesis.py +156 -0
- digsim/utils/__init__.py +6 -0
- digsim/utils/_yosys_netlist.py +134 -0
- digsim_logic_simulator-0.22.0.dist-info/METADATA +140 -0
- digsim_logic_simulator-0.22.0.dist-info/RECORD +107 -0
- digsim_logic_simulator-0.22.0.dist-info/WHEEL +5 -0
- digsim_logic_simulator-0.22.0.dist-info/entry_points.txt +2 -0
- digsim_logic_simulator-0.22.0.dist-info/licenses/LICENSE.md +32 -0
- digsim_logic_simulator-0.22.0.dist-info/top_level.txt +1 -0
digsim/__init__.py
ADDED
digsim/app/__main__.py
ADDED
digsim/app/cli.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) Fredrik Andersson, 2023-2025
|
|
2
|
+
# All rights reserved
|
|
3
|
+
|
|
4
|
+
"""The main class module of the digsim.app namespace"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import importlib
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from PySide6.QtCore import Qt
|
|
12
|
+
from PySide6.QtGui import QIcon, QPainter, QPixmap
|
|
13
|
+
from PySide6.QtWidgets import QApplication
|
|
14
|
+
|
|
15
|
+
from digsim.app.gui import MainWindow
|
|
16
|
+
from digsim.app.model import AppModel
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
PACKAGE_NAME = "digsim-logic-simulator"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _create_app_icon(image_path: Path) -> QIcon:
|
|
23
|
+
image_pixmap = QPixmap(image_path)
|
|
24
|
+
size = max(image_pixmap.size().height(), image_pixmap.size().width())
|
|
25
|
+
icon_pixmap = QPixmap(size, size)
|
|
26
|
+
icon_pixmap.fill(Qt.transparent)
|
|
27
|
+
painter = QPainter(icon_pixmap)
|
|
28
|
+
painter.drawPixmap(
|
|
29
|
+
(icon_pixmap.size().width() - image_pixmap.size().width()) // 2,
|
|
30
|
+
(icon_pixmap.size().height() - image_pixmap.size().height()) // 2,
|
|
31
|
+
image_pixmap,
|
|
32
|
+
)
|
|
33
|
+
painter.end()
|
|
34
|
+
return QIcon(icon_pixmap)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _start(args, package_version):
|
|
38
|
+
app = QApplication(sys.argv)
|
|
39
|
+
main_path = Path(__file__).parent
|
|
40
|
+
image_path = main_path / "images/app_icon.png"
|
|
41
|
+
icon = _create_app_icon(image_path)
|
|
42
|
+
app.setWindowIcon(icon)
|
|
43
|
+
|
|
44
|
+
app_model = AppModel()
|
|
45
|
+
window = MainWindow(app_model, package_version)
|
|
46
|
+
window.show()
|
|
47
|
+
|
|
48
|
+
if args.load is not None:
|
|
49
|
+
app_model.load_circuit(args.load)
|
|
50
|
+
|
|
51
|
+
return app.exec()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main():
|
|
55
|
+
parser = argparse.ArgumentParser()
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--version", "-v", action="store_true", help="Print the version of digsim.app"
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument("--load", "-l", help="The circuit to load when starting the application")
|
|
60
|
+
args = parser.parse_args()
|
|
61
|
+
|
|
62
|
+
package_version = importlib.metadata.version(PACKAGE_NAME)
|
|
63
|
+
|
|
64
|
+
if args.version:
|
|
65
|
+
print(f"DigSim '{PACKAGE_NAME}' [v{package_version}]")
|
|
66
|
+
return 0
|
|
67
|
+
else:
|
|
68
|
+
return _start(args, package_version)
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# Copyright (c) Fredrik Andersson, 2023-2025
|
|
2
|
+
# All rights reserved
|
|
3
|
+
|
|
4
|
+
"""The circuit area and component widget"""
|
|
5
|
+
|
|
6
|
+
from functools import partial
|
|
7
|
+
|
|
8
|
+
from PySide6.QtCore import QPoint, QPointF, QRect, QRectF, Qt, QTimer
|
|
9
|
+
from PySide6.QtGui import QBrush, QColor, QPainterPath, QPen
|
|
10
|
+
from PySide6.QtWidgets import (
|
|
11
|
+
QGraphicsItem,
|
|
12
|
+
QGraphicsPathItem,
|
|
13
|
+
QGraphicsRectItem,
|
|
14
|
+
QGraphicsScene,
|
|
15
|
+
QGraphicsView,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from digsim.app.settings import ComponentSettingsDialog
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WirePartGraphicsItem(QGraphicsRectItem):
|
|
22
|
+
"""A part of a wire graphcis item"""
|
|
23
|
+
|
|
24
|
+
CLOSE_TO_WIRE_MARGIN = 10
|
|
25
|
+
|
|
26
|
+
def __init__(self, app_model, src_port, parent, point_pair):
|
|
27
|
+
src, dst = point_pair
|
|
28
|
+
x_low, x_high = (src.x(), dst.x()) if src.x() < dst.x() else (dst.x(), src.x())
|
|
29
|
+
y_low, y_high = (src.y(), dst.y()) if src.y() < dst.y() else (dst.y(), src.y())
|
|
30
|
+
rect = QRectF(
|
|
31
|
+
x_low - self.CLOSE_TO_WIRE_MARGIN,
|
|
32
|
+
y_low - self.CLOSE_TO_WIRE_MARGIN,
|
|
33
|
+
x_high - x_low + 2 * self.CLOSE_TO_WIRE_MARGIN,
|
|
34
|
+
y_high - y_low + 2 * self.CLOSE_TO_WIRE_MARGIN,
|
|
35
|
+
)
|
|
36
|
+
self._app_model = app_model
|
|
37
|
+
self._src_port = src_port
|
|
38
|
+
self._parent = parent
|
|
39
|
+
self._start = QPointF(x_low, y_low)
|
|
40
|
+
self._end = QPointF(x_high, y_high)
|
|
41
|
+
self._selected = False
|
|
42
|
+
super().__init__(rect, parent)
|
|
43
|
+
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
|
|
44
|
+
|
|
45
|
+
def wire_selected(self, selected):
|
|
46
|
+
"""Set Wire Selected"""
|
|
47
|
+
self._selected = selected
|
|
48
|
+
|
|
49
|
+
def itemChange(self, change, value):
|
|
50
|
+
"""QT event callback function"""
|
|
51
|
+
if change == QGraphicsItem.ItemSelectedHasChanged:
|
|
52
|
+
self._parent.select(self.isSelected())
|
|
53
|
+
return super().itemChange(change, value)
|
|
54
|
+
|
|
55
|
+
def _get_wire_color(self, port_value, bus_width):
|
|
56
|
+
if port_value == "X":
|
|
57
|
+
return Qt.red
|
|
58
|
+
if port_value == 0:
|
|
59
|
+
return Qt.darkGray
|
|
60
|
+
|
|
61
|
+
max_value = 2**bus_width - 1
|
|
62
|
+
# Start with dark gray
|
|
63
|
+
green = 128
|
|
64
|
+
# Calculate the green component, ranging from 128 to 255
|
|
65
|
+
green += int(127 * port_value / max_value)
|
|
66
|
+
return QColor(128, green, 128)
|
|
67
|
+
|
|
68
|
+
def paint(self, painter, option, widget=None):
|
|
69
|
+
"""QT function"""
|
|
70
|
+
pen = QPen(Qt.darkGray)
|
|
71
|
+
bus_width = self._src_port.width
|
|
72
|
+
if bus_width > 1:
|
|
73
|
+
pen.setWidth(4)
|
|
74
|
+
else:
|
|
75
|
+
pen.setWidth(2)
|
|
76
|
+
|
|
77
|
+
if not self._app_model.is_running and self._selected:
|
|
78
|
+
pen.setColor(Qt.black)
|
|
79
|
+
elif self._app_model.settings.get("color_wires"):
|
|
80
|
+
port_value = self._src_port.value
|
|
81
|
+
pen.setColor(self._get_wire_color(port_value, bus_width))
|
|
82
|
+
|
|
83
|
+
painter.setPen(pen)
|
|
84
|
+
painter.drawLine(self._start, self._end)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class WireGraphicsItem(QGraphicsPathItem):
|
|
88
|
+
"""A wire graphics item"""
|
|
89
|
+
|
|
90
|
+
WIRE_TO_COMPONENT_DIST = 5
|
|
91
|
+
X_OFFSET = 10
|
|
92
|
+
|
|
93
|
+
def __init__(self, app_model, connection, src_port_item, dst_port_item):
|
|
94
|
+
super().__init__()
|
|
95
|
+
self._app_model = app_model
|
|
96
|
+
self._src_port, self._dst_port = connection
|
|
97
|
+
self._src_port_item = src_port_item
|
|
98
|
+
self._dst_port_item = dst_port_item
|
|
99
|
+
self._part_items = []
|
|
100
|
+
self.setZValue(-1)
|
|
101
|
+
self.update_wire()
|
|
102
|
+
self._selected = False
|
|
103
|
+
|
|
104
|
+
def disconnect(self):
|
|
105
|
+
"""Disconnect (delete) the wire"""
|
|
106
|
+
self._src_port.disconnect(self._dst_port)
|
|
107
|
+
|
|
108
|
+
def select(self, selected):
|
|
109
|
+
"""Select all parts of the wiregrapgicsitem"""
|
|
110
|
+
for item in self._part_items:
|
|
111
|
+
item.wire_selected(selected)
|
|
112
|
+
if selected:
|
|
113
|
+
self.setZValue(self._app_model.objects.components.get_top_zlevel() + 1)
|
|
114
|
+
else:
|
|
115
|
+
self.setZValue(-1)
|
|
116
|
+
self._selected = selected
|
|
117
|
+
|
|
118
|
+
def is_selected(self):
|
|
119
|
+
"""Is the wire selected"""
|
|
120
|
+
return self._selected
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def create_points(cls, source, dest, rect):
|
|
124
|
+
"""Create a wire path"""
|
|
125
|
+
points = []
|
|
126
|
+
points.append(source)
|
|
127
|
+
|
|
128
|
+
if source.x() < dest.x():
|
|
129
|
+
half_dist_x = (dest.x() - source.x()) / 2
|
|
130
|
+
points.append(QPointF(source.x() + half_dist_x, source.y()))
|
|
131
|
+
points.append(QPointF(source.x() + half_dist_x, dest.y()))
|
|
132
|
+
else:
|
|
133
|
+
half_dist_y = (source.y() + dest.y()) / 2
|
|
134
|
+
if dest.y() < source.y():
|
|
135
|
+
comp_top = rect.y() - cls.WIRE_TO_COMPONENT_DIST
|
|
136
|
+
y_mid = min(comp_top, half_dist_y)
|
|
137
|
+
else:
|
|
138
|
+
comp_bottom = rect.y() + rect.height() + cls.WIRE_TO_COMPONENT_DIST
|
|
139
|
+
y_mid = max(comp_bottom, half_dist_y)
|
|
140
|
+
|
|
141
|
+
points.append(QPointF(source.x() + cls.X_OFFSET, source.y()))
|
|
142
|
+
points.append(QPointF(source.x() + cls.X_OFFSET, y_mid))
|
|
143
|
+
points.append(QPointF(dest.x() - cls.X_OFFSET, y_mid))
|
|
144
|
+
points.append(QPointF(dest.x() - cls.X_OFFSET, dest.y()))
|
|
145
|
+
|
|
146
|
+
points.append(dest)
|
|
147
|
+
return points
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def create_path(cls, source, dest, rect):
|
|
151
|
+
"""Create a wire path"""
|
|
152
|
+
path = QPainterPath()
|
|
153
|
+
points = cls.create_points(source, dest, rect)
|
|
154
|
+
path.moveTo(points[0])
|
|
155
|
+
for point in points[1:]:
|
|
156
|
+
path.lineTo(point)
|
|
157
|
+
return path
|
|
158
|
+
|
|
159
|
+
def update_wire(self):
|
|
160
|
+
"""Update the wire path"""
|
|
161
|
+
source = self._src_port_item.portPos()
|
|
162
|
+
dest = self._dst_port_item.portPos()
|
|
163
|
+
rect = self._src_port_item.portParentRect()
|
|
164
|
+
points = self.create_points(source, dest, rect)
|
|
165
|
+
self._part_items = []
|
|
166
|
+
for idx, p1 in enumerate(points[0:-1]):
|
|
167
|
+
p2 = points[idx + 1]
|
|
168
|
+
item = WirePartGraphicsItem(self._app_model, self._src_port, self, (p1, p2))
|
|
169
|
+
self._part_items.append(item)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class NewWireGraphicsItem(QGraphicsPathItem):
|
|
173
|
+
"""A new wire graphics item"""
|
|
174
|
+
|
|
175
|
+
def __init__(self, app_model):
|
|
176
|
+
super().__init__()
|
|
177
|
+
self._app_model = app_model
|
|
178
|
+
|
|
179
|
+
def paint(self, painter, option, widget=None):
|
|
180
|
+
"""QT function"""
|
|
181
|
+
end_pos = None
|
|
182
|
+
start_port = self._app_model.objects.new_wire.start_port()
|
|
183
|
+
end_pos = self._app_model.objects.new_wire.end_pos()
|
|
184
|
+
pen = QPen(Qt.darkGray)
|
|
185
|
+
if start_port is None or end_pos is None:
|
|
186
|
+
return
|
|
187
|
+
if start_port.width > 1:
|
|
188
|
+
pen.setWidth(4)
|
|
189
|
+
else:
|
|
190
|
+
pen.setWidth(2)
|
|
191
|
+
component_object = self._app_model.objects.components.get_object(start_port.parent())
|
|
192
|
+
start_pos = component_object.get_port_pos(start_port.name()) + component_object.pos()
|
|
193
|
+
if start_port.is_output():
|
|
194
|
+
path = WireGraphicsItem.create_path(start_pos, end_pos, component_object.rect())
|
|
195
|
+
else:
|
|
196
|
+
path = WireGraphicsItem.create_path(end_pos, start_pos, component_object.rect())
|
|
197
|
+
self.setPath(path)
|
|
198
|
+
self.setPen(pen)
|
|
199
|
+
super().paint(painter, option, widget)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class _CircuitAreaScene(QGraphicsScene):
|
|
203
|
+
"""The circuit area graphics scene"""
|
|
204
|
+
|
|
205
|
+
def __init__(self, app_model, view):
|
|
206
|
+
super().__init__()
|
|
207
|
+
self._app_model = app_model
|
|
208
|
+
self._view = view
|
|
209
|
+
self._app_model.sig_repaint.connect(self._repaint)
|
|
210
|
+
self._app_model.sig_synchronize_gui.connect(self._synchronize_gui)
|
|
211
|
+
self._app_model.sig_update_wires.connect(self._update_wires)
|
|
212
|
+
self._app_model.sig_delete_component.connect(self._delete_component)
|
|
213
|
+
self._app_model.sig_delete_wires.connect(self._delete_wires)
|
|
214
|
+
self._wire_items = []
|
|
215
|
+
self._select_start_pos = None
|
|
216
|
+
self._selection_rect_item = None
|
|
217
|
+
self._synchronize_gui()
|
|
218
|
+
|
|
219
|
+
def _repaint(self):
|
|
220
|
+
self.update()
|
|
221
|
+
|
|
222
|
+
def mousePressEvent(self, event):
|
|
223
|
+
"""QT event callback function"""
|
|
224
|
+
super().mousePressEvent(event)
|
|
225
|
+
pos = event.scenePos()
|
|
226
|
+
items = self.items(pos)
|
|
227
|
+
if len(items) == 0:
|
|
228
|
+
self._select_start_pos = pos
|
|
229
|
+
self._repaint()
|
|
230
|
+
|
|
231
|
+
def mouseMoveEvent(self, event):
|
|
232
|
+
"""QT event callback function"""
|
|
233
|
+
super().mouseMoveEvent(event)
|
|
234
|
+
pos = event.scenePos()
|
|
235
|
+
if self._select_start_pos is not None:
|
|
236
|
+
path = QPainterPath()
|
|
237
|
+
rect = QRect(
|
|
238
|
+
self._select_start_pos.x(),
|
|
239
|
+
self._select_start_pos.y(),
|
|
240
|
+
pos.x() - self._select_start_pos.x(),
|
|
241
|
+
pos.y() - self._select_start_pos.y(),
|
|
242
|
+
)
|
|
243
|
+
self._selection_rect_item.setRect(rect)
|
|
244
|
+
self._selection_rect_item.setVisible(True)
|
|
245
|
+
path.addRect(rect)
|
|
246
|
+
self.setSelectionArea(path)
|
|
247
|
+
self._repaint()
|
|
248
|
+
|
|
249
|
+
# Make sure to loop through complete list to clear has_moved()
|
|
250
|
+
has_moved_component = False
|
|
251
|
+
for component_object in self._app_model.objects.components.get_object_list():
|
|
252
|
+
if component_object.has_moved():
|
|
253
|
+
has_moved_component = True
|
|
254
|
+
if has_moved_component:
|
|
255
|
+
self._app_model.sig_update_wires.emit()
|
|
256
|
+
|
|
257
|
+
def mouseReleaseEvent(self, event):
|
|
258
|
+
"""QT event callback function"""
|
|
259
|
+
super().mouseReleaseEvent(event)
|
|
260
|
+
self._select_start_pos = None
|
|
261
|
+
self._selection_rect_item.setVisible(False)
|
|
262
|
+
self._repaint()
|
|
263
|
+
|
|
264
|
+
def _delete_component(self, component_object):
|
|
265
|
+
self.removeItem(component_object)
|
|
266
|
+
self._update_wires()
|
|
267
|
+
|
|
268
|
+
def _delete_wires(self):
|
|
269
|
+
change = False
|
|
270
|
+
for item in self._wire_items:
|
|
271
|
+
if item.is_selected():
|
|
272
|
+
item.disconnect()
|
|
273
|
+
change = True
|
|
274
|
+
if change:
|
|
275
|
+
self._update_wires()
|
|
276
|
+
self._app_model.model_changed()
|
|
277
|
+
|
|
278
|
+
def _update_wires(self):
|
|
279
|
+
for item in self._wire_items:
|
|
280
|
+
self.removeItem(item)
|
|
281
|
+
|
|
282
|
+
self._wire_items = []
|
|
283
|
+
component_objects = self._app_model.objects.components.get_object_list()
|
|
284
|
+
for src_comp_item in component_objects:
|
|
285
|
+
for src_port in src_comp_item.component.outports():
|
|
286
|
+
for dst_port in src_port.wired_ports:
|
|
287
|
+
dst_comp_item = self._app_model.objects.components.get_object(
|
|
288
|
+
dst_port.parent()
|
|
289
|
+
)
|
|
290
|
+
src_port_item = src_comp_item.get_port_item(src_port)
|
|
291
|
+
dst_port_item = dst_comp_item.get_port_item(dst_port)
|
|
292
|
+
item = WireGraphicsItem(
|
|
293
|
+
self._app_model, (src_port, dst_port), src_port_item, dst_port_item
|
|
294
|
+
)
|
|
295
|
+
self.addItem(item)
|
|
296
|
+
self._wire_items.append(item)
|
|
297
|
+
|
|
298
|
+
def add_scene_component(self, component_object, update_wires=False):
|
|
299
|
+
"""Add component to scene"""
|
|
300
|
+
self.addItem(component_object)
|
|
301
|
+
component_object.set_parent_widget(self._view)
|
|
302
|
+
if update_wires:
|
|
303
|
+
self._update_wires()
|
|
304
|
+
|
|
305
|
+
def _synchronize_gui(self):
|
|
306
|
+
"""Remove everything from scene"""
|
|
307
|
+
self.clear()
|
|
308
|
+
self._wire_items = []
|
|
309
|
+
# Add new wire item
|
|
310
|
+
self.addItem(NewWireGraphicsItem(self._app_model))
|
|
311
|
+
# Add selection rect
|
|
312
|
+
self._selection_rect_item = QGraphicsRectItem()
|
|
313
|
+
self._selection_rect_item.setPen(Qt.DashLine)
|
|
314
|
+
self._selection_rect_item.setBrush(Qt.Dense7Pattern)
|
|
315
|
+
self.addItem(self._selection_rect_item)
|
|
316
|
+
# Add component
|
|
317
|
+
for component_object in self._app_model.objects.components.get_object_list():
|
|
318
|
+
self.add_scene_component(component_object)
|
|
319
|
+
# Update (add) wires
|
|
320
|
+
self._update_wires()
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class CircuitArea(QGraphicsView):
|
|
324
|
+
"""The circuit area graphics view"""
|
|
325
|
+
|
|
326
|
+
ZOOM_IN_FACTOR = 1.25
|
|
327
|
+
ZOOM_OUT_FACTOR = 0.8
|
|
328
|
+
|
|
329
|
+
def __init__(self, app_model, parent):
|
|
330
|
+
super().__init__(parent=parent)
|
|
331
|
+
self._app_model = app_model
|
|
332
|
+
self._app_model.sig_zoom_in_gui.connect(self._zoom_in)
|
|
333
|
+
self._app_model.sig_zoom_out_gui.connect(self._zoom_out)
|
|
334
|
+
self._scene = _CircuitAreaScene(app_model, self)
|
|
335
|
+
self._wheel_zoom_mode = False
|
|
336
|
+
self.setScene(self._scene)
|
|
337
|
+
self.setBackgroundBrush(QBrush(Qt.lightGray))
|
|
338
|
+
self.setAcceptDrops(True)
|
|
339
|
+
self.setTransformationAnchor(QGraphicsView.NoAnchor)
|
|
340
|
+
self._mouse_pos = QPoint(0, 0)
|
|
341
|
+
|
|
342
|
+
def has_selection(self):
|
|
343
|
+
"""Return true if items are selected"""
|
|
344
|
+
return len(self._scene.selectedItems()) > 0
|
|
345
|
+
|
|
346
|
+
def _zoom_in(self):
|
|
347
|
+
self.scale(self.ZOOM_IN_FACTOR, self.ZOOM_IN_FACTOR)
|
|
348
|
+
|
|
349
|
+
def _zoom_out(self):
|
|
350
|
+
self.scale(self.ZOOM_OUT_FACTOR, self.ZOOM_OUT_FACTOR)
|
|
351
|
+
|
|
352
|
+
def _repaint(self):
|
|
353
|
+
"""Make scene repaint for component update"""
|
|
354
|
+
self._app_model.sig_repaint.emit()
|
|
355
|
+
|
|
356
|
+
def keyPressEvent(self, event):
|
|
357
|
+
"""QT event callback function"""
|
|
358
|
+
super().keyPressEvent(event)
|
|
359
|
+
if event.isAutoRepeat():
|
|
360
|
+
return
|
|
361
|
+
if event.key() == Qt.Key_Shift:
|
|
362
|
+
self.setCursor(Qt.OpenHandCursor)
|
|
363
|
+
self.setDragMode(QGraphicsView.ScrollHandDrag)
|
|
364
|
+
event.accept()
|
|
365
|
+
elif event.key() == Qt.Key_Control:
|
|
366
|
+
self.setCursor(Qt.ArrowCursor)
|
|
367
|
+
self._wheel_zoom_mode = True
|
|
368
|
+
event.accept()
|
|
369
|
+
|
|
370
|
+
def keyReleaseEvent(self, event):
|
|
371
|
+
"""QT event callback function"""
|
|
372
|
+
super().keyReleaseEvent(event)
|
|
373
|
+
if event.isAutoRepeat():
|
|
374
|
+
return
|
|
375
|
+
if event.key() == Qt.Key_Shift:
|
|
376
|
+
self.setCursor(Qt.ArrowCursor)
|
|
377
|
+
self.setDragMode(QGraphicsView.NoDrag)
|
|
378
|
+
event.accept()
|
|
379
|
+
elif event.key() == Qt.Key_Control:
|
|
380
|
+
self.setCursor(Qt.ArrowCursor)
|
|
381
|
+
self._wheel_zoom_mode = False
|
|
382
|
+
event.accept()
|
|
383
|
+
|
|
384
|
+
def dragEnterEvent(self, event):
|
|
385
|
+
"""QT event callback function"""
|
|
386
|
+
event.accept()
|
|
387
|
+
|
|
388
|
+
def dragLeaveEvent(self, event):
|
|
389
|
+
"""QT event callback function"""
|
|
390
|
+
event.accept()
|
|
391
|
+
|
|
392
|
+
def dragMoveEvent(self, event):
|
|
393
|
+
"""QT event callback function"""
|
|
394
|
+
event.accept()
|
|
395
|
+
|
|
396
|
+
def dropEvent(self, event):
|
|
397
|
+
"""QT event callback function"""
|
|
398
|
+
event.setDropAction(Qt.IgnoreAction)
|
|
399
|
+
event.accept()
|
|
400
|
+
self.setFocus()
|
|
401
|
+
|
|
402
|
+
scene_pos = self.mapToScene(event.pos())
|
|
403
|
+
|
|
404
|
+
component_name = event.mimeData().text()
|
|
405
|
+
QTimer.singleShot(0, partial(self.add_component, component_name, scene_pos))
|
|
406
|
+
|
|
407
|
+
def enterEvent(self, event):
|
|
408
|
+
"""QT event callback function"""
|
|
409
|
+
self.setFocus()
|
|
410
|
+
event.accept()
|
|
411
|
+
|
|
412
|
+
def mousePressEvent(self, event):
|
|
413
|
+
"""QT event callback function"""
|
|
414
|
+
super().mousePressEvent(event)
|
|
415
|
+
|
|
416
|
+
def mouseReleaseEvent(self, event):
|
|
417
|
+
"""QT event callback function"""
|
|
418
|
+
super().mouseReleaseEvent(event)
|
|
419
|
+
|
|
420
|
+
def mouseMoveEvent(self, event):
|
|
421
|
+
"""QT event callback function"""
|
|
422
|
+
super().mouseMoveEvent(event)
|
|
423
|
+
self._mouse_pos = event.pos()
|
|
424
|
+
# Draw unfinished wire
|
|
425
|
+
if self._app_model.objects.new_wire.ongoing():
|
|
426
|
+
scene_pos = self.mapToScene(event.pos())
|
|
427
|
+
self._app_model.objects.new_wire.set_end_pos(scene_pos)
|
|
428
|
+
self._repaint()
|
|
429
|
+
|
|
430
|
+
def wheelEvent(self, event):
|
|
431
|
+
"""QT event callback function"""
|
|
432
|
+
# Zoom on wheel
|
|
433
|
+
if not self._wheel_zoom_mode:
|
|
434
|
+
super().wheelEvent(event)
|
|
435
|
+
return
|
|
436
|
+
if event.angleDelta().y() > 0:
|
|
437
|
+
before = self.mapToScene(self._mouse_pos)
|
|
438
|
+
self._zoom_in()
|
|
439
|
+
after = self.mapToScene(self._mouse_pos)
|
|
440
|
+
translation = after - before
|
|
441
|
+
self.translate(translation.x(), translation.y())
|
|
442
|
+
elif event.angleDelta().y() < 0:
|
|
443
|
+
before = self.mapToScene(self._mouse_pos)
|
|
444
|
+
self._zoom_out()
|
|
445
|
+
after = self.mapToScene(self._mouse_pos)
|
|
446
|
+
translation = after - before
|
|
447
|
+
self.translate(translation.x(), translation.y())
|
|
448
|
+
event.accept()
|
|
449
|
+
|
|
450
|
+
def add_component(self, name, position):
|
|
451
|
+
"""
|
|
452
|
+
Add component to circuit area
|
|
453
|
+
Used be drag'n'drop into Circuit area or double click in SelectableComponentWidget
|
|
454
|
+
"""
|
|
455
|
+
if position is None:
|
|
456
|
+
position = self.mapToScene(0, 0) + QPoint(100, 100)
|
|
457
|
+
|
|
458
|
+
component_parameters = self._app_model.objects.components.get_object_parameters(name)
|
|
459
|
+
ok, settings = ComponentSettingsDialog.start(
|
|
460
|
+
self, self._app_model, name, component_parameters
|
|
461
|
+
)
|
|
462
|
+
if settings.get("name") is not None:
|
|
463
|
+
name = settings["name"]
|
|
464
|
+
if ok:
|
|
465
|
+
component_object = self._app_model.objects.components.add_object_by_name(
|
|
466
|
+
name, position, settings
|
|
467
|
+
)
|
|
468
|
+
self._scene.add_scene_component(component_object, True)
|