Small fixes and bigger library
This commit is contained in:
79
icons/signal_cosine_source.json
Normal file
79
icons/signal_cosine_source.json
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"shapes": {
|
||||
"9ed4189a-123b-431f-a6bc-bbf169487703": {
|
||||
"layer": 0,
|
||||
"type": "rectangle",
|
||||
"pos": [-48, -48],
|
||||
"width": 96.0,
|
||||
"height": 96.0,
|
||||
"line_type": "solid",
|
||||
"line_thickness": 5.0,
|
||||
"corner_radius": 8.0,
|
||||
"line_color": "#00007fff",
|
||||
"fill_color": "#ebebebff"
|
||||
},
|
||||
"ac1b7cf2-ce50-44b7-9f8f-077ca36f7369": {
|
||||
"layer": 4,
|
||||
"type": "line",
|
||||
"pos": [-32, -32],
|
||||
"end": [-32, 32],
|
||||
"line_type": "solid",
|
||||
"line_thickness": 3.0,
|
||||
"line_color": "#000000ff"
|
||||
},
|
||||
"2e623060-349e-4a48-97b0-a81921df5870": {
|
||||
"layer": 2,
|
||||
"type": "line",
|
||||
"pos": [-32, 32],
|
||||
"end": [32, 32],
|
||||
"line_type": "solid",
|
||||
"line_thickness": 3.0,
|
||||
"line_color": "#000000ff"
|
||||
},
|
||||
"2c787197-3228-4f20-a57d-6fca410151b9": {
|
||||
"layer": 5, "type": "line", "pos": [-28, -16], "end": [-24, -15], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"53e49b2b-f3d6-4aa5-bd56-7ca7fbf68bb4": {
|
||||
"layer": 5, "type": "line", "pos": [-24, -15], "end": [-20, -11], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"7df5a7ce-b914-459e-93f6-f43450623baf": {
|
||||
"layer": 5, "type": "line", "pos": [-20, -11], "end": [-16, -7], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"dd89bdfb-f10f-467a-aa90-a534e1f30dd8": {
|
||||
"layer": 5, "type": "line", "pos": [-16, -7], "end": [-12, 0], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"df9439f1-588f-46d8-873e-ecc59f5b8e36": {
|
||||
"layer": 5, "type": "line", "pos": [-12, 0], "end": [-8, 7], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"4984ed23-a303-494f-947d-a34964220298": {
|
||||
"layer": 5, "type": "line", "pos": [-8, 7], "end": [-4, 13], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"af40c4f7-d33b-426e-b130-ce78184a350f": {
|
||||
"layer": 5, "type": "line", "pos": [-4, 13], "end": [0, 16], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"63c0188e-4f24-4ec7-b8d6-6258ab13c226": {
|
||||
"layer": 5, "type": "line", "pos": [0, 16], "end": [4, 13], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"c71d5e3b-a917-4dc8-8411-2046a54e2134": {
|
||||
"layer": 5, "type": "line", "pos": [4, 13], "end": [8, 7], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"51e128e6-c63f-4437-b56f-d20c31d195e2": {
|
||||
"layer": 5, "type": "line", "pos": [8, 7], "end": [12, 0], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"f135f861-3e98-4bb1-ab43-e51fa0eec8db": {
|
||||
"layer": 5, "type": "line", "pos": [12, 0], "end": [16, -7], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"422ce3eb-8d0f-4b05-bb54-126583521240": {
|
||||
"layer": 5, "type": "line", "pos": [16, -7], "end": [20, -11], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"c2c60fc4-fd4c-4cfc-a90a-dd729fe551b0": {
|
||||
"layer": 5, "type": "line", "pos": [20, -11], "end": [24, -15], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"0852263b-237a-4a32-a56d-91945864e01b": {
|
||||
"layer": 5, "type": "line", "pos": [24, -15], "end": [28, -16], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
}
|
||||
},
|
||||
"port_positions": {
|
||||
"37eb6e52-6e89-4e09-8923-43c6230f5486": [-8, -8]
|
||||
}
|
||||
}
|
||||
97
icons/signal_sine_source.json
Normal file
97
icons/signal_sine_source.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"shapes": {
|
||||
"9ed4189a-123b-431f-a6bc-bbf169487703": {
|
||||
"layer": 0,
|
||||
"type": "rectangle",
|
||||
"pos": [
|
||||
-48,
|
||||
-48
|
||||
],
|
||||
"width": 96.0,
|
||||
"height": 96.0,
|
||||
"line_type": "solid",
|
||||
"line_thickness": 5.0,
|
||||
"corner_radius": 8.0,
|
||||
"line_color": "#00007fff",
|
||||
"fill_color": "#ebebebff"
|
||||
},
|
||||
"ac1b7cf2-ce50-44b7-9f8f-077ca36f7369": {
|
||||
"layer": 4,
|
||||
"type": "line",
|
||||
"pos": [
|
||||
-32,
|
||||
-32
|
||||
],
|
||||
"end": [
|
||||
-32,
|
||||
32
|
||||
],
|
||||
"line_type": "solid",
|
||||
"line_thickness": 3.0,
|
||||
"line_color": "#000000ff"
|
||||
},
|
||||
"2e623060-349e-4a48-97b0-a81921df5870": {
|
||||
"layer": 2,
|
||||
"type": "line",
|
||||
"pos": [
|
||||
-32,
|
||||
32
|
||||
],
|
||||
"end": [
|
||||
32,
|
||||
32
|
||||
],
|
||||
"line_type": "solid",
|
||||
"line_thickness": 3.0,
|
||||
"line_color": "#000000ff"
|
||||
},
|
||||
"5cfcb42c-5cc3-48aa-ad92-7d19807c95a0": {
|
||||
"layer": 5, "type": "line", "pos": [-28, 0], "end": [-24, -7], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"78f194ed-dae0-470a-a354-f7c4c3e54c81": {
|
||||
"layer": 5, "type": "line", "pos": [-24, -7], "end": [-20, -13], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"cfa29bc9-5fac-4f00-bb02-52498fd14f92": {
|
||||
"layer": 5, "type": "line", "pos": [-20, -13], "end": [-16, -16], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"8f852157-0395-4028-a9ce-f7bd596f2976": {
|
||||
"layer": 5, "type": "line", "pos": [-16, -16], "end": [-12, -16], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"6f535277-a28e-4abc-82d4-4cf515a80357": {
|
||||
"layer": 5, "type": "line", "pos": [-12, -16], "end": [-8, -13], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"a40bba3a-d9fd-4a7c-b409-2222ce69c558": {
|
||||
"layer": 5, "type": "line", "pos": [-8, -13], "end": [-4, -7], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"2720ddd9-0332-46b6-bb79-dd90aa0892bd": {
|
||||
"layer": 5, "type": "line", "pos": [-4, -7], "end": [0, 0], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"e420ce57-f79d-4941-8f5a-8207e15f0306": {
|
||||
"layer": 5, "type": "line", "pos": [0, 0], "end": [4, 7], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"f53f0310-7e14-4a87-aeb3-e079e2a8460e": {
|
||||
"layer": 5, "type": "line", "pos": [4, 7], "end": [8, 13], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"915cce34-b991-4119-88b5-c2dd41eec73c": {
|
||||
"layer": 5, "type": "line", "pos": [8, 13], "end": [12, 16], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"56d62c0f-4156-49eb-b048-b3b42304d6b0": {
|
||||
"layer": 5, "type": "line", "pos": [12, 16], "end": [16, 16], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"de990624-dbb2-434c-bbdf-b0ea9caa34bd": {
|
||||
"layer": 5, "type": "line", "pos": [16, 16], "end": [20, 13], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"b48fb633-b679-433f-8bee-f7c9549235aa": {
|
||||
"layer": 5, "type": "line", "pos": [20, 13], "end": [24, 7], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
},
|
||||
"17f88457-c04d-4c08-90f6-f6ec5166c11c": {
|
||||
"layer": 5, "type": "line", "pos": [24, 7], "end": [28, 0], "line_type": "solid", "line_thickness": 2.0, "line_color": "#ffaa00ff"
|
||||
}
|
||||
},
|
||||
"port_positions": {
|
||||
"37eb6e52-6e89-4e09-8923-43c6230f5486": [
|
||||
-8,
|
||||
-8
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
BIN
lib/signal.beb
BIN
lib/signal.beb
Binary file not shown.
Binary file not shown.
@@ -5,40 +5,46 @@ from bedit_gui.models import Icon
|
||||
|
||||
|
||||
class AddEmptyGraphComponent(QUndoCommand):
|
||||
def __init__(self, document: object, parent: Component) -> None:
|
||||
def __init__(self, document: object, parent: Component | dict[ComponentID, Component]) -> None:
|
||||
super().__init__("Add graph component")
|
||||
if not isinstance(parent.implementation, GraphImplementation):
|
||||
raise TypeError("parent component must have a graph implementation")
|
||||
self.document = document
|
||||
self.graph = parent.implementation.graph
|
||||
if isinstance(parent, Component):
|
||||
if not isinstance(parent.implementation, GraphImplementation):
|
||||
raise TypeError("parent component must have a graph implementation")
|
||||
self.components = parent.implementation.graph.components
|
||||
else:
|
||||
self.components = parent
|
||||
self.component_id = ComponentID()
|
||||
self.component = Component(name="New Graph Component", interface=Interface(), parameters={}, implementation=GraphImplementation(Graph()))
|
||||
self.component = Component(name="new_graph_component", interface=Interface(), parameters={}, implementation=GraphImplementation(Graph()))
|
||||
|
||||
def redo(self) -> None:
|
||||
self.graph.components[self.component_id] = self.component
|
||||
self.components[self.component_id] = self.component
|
||||
self.document.model_changed.emit(self.document.model)
|
||||
|
||||
def undo(self) -> None:
|
||||
del self.graph.components[self.component_id]
|
||||
del self.components[self.component_id]
|
||||
self.document.model_changed.emit(self.document.model)
|
||||
|
||||
|
||||
class AddEmptyEquationComponent(QUndoCommand):
|
||||
def __init__(self, document: object, parent: Component) -> None:
|
||||
def __init__(self, document: object, parent: Component | dict[ComponentID, Component]) -> None:
|
||||
super().__init__("Add equation component")
|
||||
if not isinstance(parent.implementation, GraphImplementation):
|
||||
raise TypeError("parent component must have a graph implementation")
|
||||
self.document = document
|
||||
self.graph = parent.implementation.graph
|
||||
if isinstance(parent, Component):
|
||||
if not isinstance(parent.implementation, GraphImplementation):
|
||||
raise TypeError("parent component must have a graph implementation")
|
||||
self.components = parent.implementation.graph.components
|
||||
else:
|
||||
self.components = parent
|
||||
self.component_id = ComponentID()
|
||||
self.component = Component(name="New Equation Component", interface=Interface(), parameters={}, implementation=EquationImplementation())
|
||||
|
||||
def redo(self) -> None:
|
||||
self.graph.components[self.component_id] = self.component
|
||||
self.components[self.component_id] = self.component
|
||||
self.document.model_changed.emit(self.document.model)
|
||||
|
||||
def undo(self) -> None:
|
||||
del self.graph.components[self.component_id]
|
||||
del self.components[self.component_id]
|
||||
self.document.model_changed.emit(self.document.model)
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from bedit_gui.views.main_window import MainWindow
|
||||
from bedit_gui.views.models.document_tree_model import DocumentTreeModel
|
||||
from bedit_gui.utils.icon import render_fitted_icon
|
||||
|
||||
ICON_SIZE = QSize(32, 32)
|
||||
ICON_SIZE = QSize(16, 16)
|
||||
|
||||
class InterfaceEditorLike(Protocol):
|
||||
def exec(self) -> int: ...
|
||||
@@ -84,11 +84,11 @@ class DocumentTreeController(QObject):
|
||||
window.ui.documentTree.setHeaderHidden(True)
|
||||
window.ui.documentTree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
window.ui.documentTree.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
window.ui.documentTree.setIconSize(QSize(48, 48))
|
||||
window.ui.documentTree.setIconSize(QSize(24, 24))
|
||||
window.ui.documentTree.header().setStretchLastSection(False)
|
||||
window.ui.documentTree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
||||
window.ui.documentTree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
||||
window.ui.documentTree.setColumnWidth(1, 56)
|
||||
window.ui.documentTree.setColumnWidth(1, 28)
|
||||
self._tree_viewport = window.ui.documentTree.viewport()
|
||||
self._tree_viewport.installEventFilter(self)
|
||||
|
||||
@@ -175,11 +175,22 @@ class DocumentTreeController(QObject):
|
||||
|
||||
def _show_context_menu(self, position: QPoint) -> None:
|
||||
index = self.window.ui.documentTree.indexAt(position)
|
||||
component = self.model.value(index)
|
||||
if not isinstance(component, Component):
|
||||
return
|
||||
value = self.model.value(index)
|
||||
global_position = self.window.ui.documentTree.viewport().mapToGlobal(position)
|
||||
if isinstance(value, CoreDocument):
|
||||
self._show_root_context_menu(global_position)
|
||||
elif isinstance(value, Component):
|
||||
self._show_component_context_menu(value, global_position)
|
||||
|
||||
self._show_component_context_menu(component, self.window.ui.documentTree.viewport().mapToGlobal(position))
|
||||
def _show_root_context_menu(self, global_position: QPoint) -> None:
|
||||
menu = QMenu(self.window.ui.documentTree)
|
||||
add_graph_component = menu.addAction("Add Graph Component")
|
||||
add_equation_component = menu.addAction("Add Equation Component")
|
||||
selected = menu.exec(global_position)
|
||||
if selected is add_graph_component:
|
||||
self.document.add_empty_root_graph_component()
|
||||
elif selected is add_equation_component:
|
||||
self.document.add_empty_root_equation_component()
|
||||
|
||||
def _show_graph_component_context_menu(self, component_id: ComponentID, global_position: QPoint) -> None:
|
||||
component = self._components.get(component_id)
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from PySide6.QtWidgets import QAbstractItemView
|
||||
|
||||
from bedit_gui.services.application_logging import configure_logging
|
||||
from bedit_gui.views.main_window import MainWindow
|
||||
@@ -49,4 +50,6 @@ class LogController(QObject):
|
||||
self.emitter.message.connect(self.model.append)
|
||||
self.model.rowsInserted.connect(window.ui.listView.scrollToBottom)
|
||||
window.ui.listView.setModel(self.model)
|
||||
window.ui.listView.setWordWrap(True)
|
||||
window.ui.listView.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
configure_logging(self.handler, level)
|
||||
|
||||
@@ -65,7 +65,7 @@ class SimulationController(QObject):
|
||||
self.document.infer_causality(component)
|
||||
build = self.compiler(component, build_directory)
|
||||
except (OSError, RuntimeError, ValueError) as exc:
|
||||
logger.exception("Could not compile model %s", component_path)
|
||||
logger.error("Could not compile model %s:\n%s", component_path, exc)
|
||||
QMessageBox.critical(self.window, "Could not compile", str(exc))
|
||||
self.window.statusBar().showMessage("Compilation failed")
|
||||
return None
|
||||
|
||||
@@ -369,12 +369,22 @@ class Document(QObject):
|
||||
command.component.name = self._unique_component_name(component.implementation.graph.components, command.component.name)
|
||||
self.undo_stack.push(command)
|
||||
|
||||
def add_empty_root_graph_component(self) -> None:
|
||||
command = AddEmptyGraphComponent(self, self.model.root)
|
||||
command.component.name = self._unique_component_name(self.model.root, command.component.name)
|
||||
self.undo_stack.push(command)
|
||||
|
||||
def add_empty_equation_component(self, component: Component) -> None:
|
||||
if isinstance(component.implementation, GraphImplementation):
|
||||
command = AddEmptyEquationComponent(self, component)
|
||||
command.component.name = self._unique_component_name(component.implementation.graph.components, command.component.name)
|
||||
self.undo_stack.push(command)
|
||||
|
||||
def add_empty_root_equation_component(self) -> None:
|
||||
command = AddEmptyEquationComponent(self, self.model.root)
|
||||
command.component.name = self._unique_component_name(self.model.root, command.component.name)
|
||||
self.undo_stack.push(command)
|
||||
|
||||
def delete_component(self, component: Component) -> None:
|
||||
self.undo_stack.push(DeleteComponent(self, component))
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ class Shape:
|
||||
def from_data(cls, data: Mapping[str, Any]) -> Shape:
|
||||
if cls is Shape and data.get("type") == "rectangle":
|
||||
return Rectangle.from_data(data)
|
||||
if cls is Shape and data.get("type") == "ellipse":
|
||||
return Ellipse.from_data(data)
|
||||
if cls is Shape and data.get("type") == "text":
|
||||
return Text.from_data(data)
|
||||
if cls is Shape and data.get("type") == "line":
|
||||
@@ -77,6 +79,24 @@ class Rectangle(Shape):
|
||||
def to_data(self) -> dict[str, Any]:
|
||||
return {**super().to_data(), "width": self.width, "height": self.height, "line_type": self.line_type.value, "line_thickness": self.line_thickness, "corner_radius": self.corner_radius, "line_color": self.line_color, "fill_color": self.fill_color}
|
||||
|
||||
@dataclass
|
||||
class Ellipse(Shape):
|
||||
type: str = field(init=False, default="ellipse")
|
||||
width: float = 32.0
|
||||
height: float = 32.0
|
||||
line_type: LineType = LineType.SOLID
|
||||
line_thickness: float = 1.0
|
||||
line_color: str = "#000000ff"
|
||||
fill_color: str = "#ffffff00"
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data: Mapping[str, Any]) -> Ellipse:
|
||||
pos = data.get("pos", [0, 0])
|
||||
return cls(layer=int(data.get("layer", 0)), pos=(int(pos[0]), int(pos[1])), width=float(data.get("width", 100.0)), height=float(data.get("height", 100.0)), line_type=LineType(data.get("line_type", "solid")), line_thickness=float(data.get("line_thickness", 1.0)), line_color=str(data.get("line_color", "#000000ff")), fill_color=str(data.get("fill_color", "#ffffff00")))
|
||||
|
||||
def to_data(self) -> dict[str, Any]:
|
||||
return {**super().to_data(), "width": self.width, "height": self.height, "line_type": self.line_type.value, "line_thickness": self.line_thickness, "line_color": self.line_color, "fill_color": self.fill_color}
|
||||
|
||||
@dataclass
|
||||
class Text(Shape):
|
||||
type: str = field(init=False, default="text")
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>22</height>
|
||||
<height>19</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuEdit">
|
||||
@@ -80,6 +80,7 @@
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionAdd_Rectangle"/>
|
||||
<addaction name="actionAdd_Circle"/>
|
||||
<addaction name="actionAdd_Text"/>
|
||||
<addaction name="actionAdd_Line"/>
|
||||
</widget>
|
||||
@@ -236,6 +237,21 @@
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAdd_Circle">
|
||||
<property name="icon">
|
||||
<iconset resource="../../resources/resources.qrc">
|
||||
<normaloff>:/icons/icons/draw-circle.png</normaloff>:/icons/icons/draw-circle.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add Ellipse</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add a circle</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../resources/resources.qrc"/>
|
||||
|
||||
206
src/bedit_gui/ui/forms/icon_editor_window_ui.py
Normal file
206
src/bedit_gui/ui/forms/icon_editor_window_ui.py
Normal file
@@ -0,0 +1,206 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'icon_editor_window.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.11.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
|
||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||
QMetaObject, QObject, QPoint, QRect,
|
||||
QSize, QTime, QUrl, Qt)
|
||||
from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
|
||||
QCursor, QFont, QFontDatabase, QGradient,
|
||||
QIcon, QImage, QKeySequence, QLinearGradient,
|
||||
QPainter, QPalette, QPixmap, QRadialGradient,
|
||||
QTransform)
|
||||
from PySide6.QtWidgets import (QApplication, QGraphicsView, QMainWindow, QMenu,
|
||||
QMenuBar, QSizePolicy, QStatusBar, QToolBar,
|
||||
QVBoxLayout, QWidget)
|
||||
import resources_rc
|
||||
|
||||
class Ui_iconEditor(object):
|
||||
def setupUi(self, iconEditor):
|
||||
if not iconEditor.objectName():
|
||||
iconEditor.setObjectName(u"iconEditor")
|
||||
iconEditor.resize(800, 600)
|
||||
icon = QIcon()
|
||||
icon.addFile(u":/icons/icons/draw-path.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
iconEditor.setWindowIcon(icon)
|
||||
self.actionUndo = QAction(iconEditor)
|
||||
self.actionUndo.setObjectName(u"actionUndo")
|
||||
icon1 = QIcon()
|
||||
icon1.addFile(u":/icons/icons/edit-undo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionUndo.setIcon(icon1)
|
||||
self.actionUndo.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionRedo = QAction(iconEditor)
|
||||
self.actionRedo.setObjectName(u"actionRedo")
|
||||
icon2 = QIcon()
|
||||
icon2.addFile(u":/icons/icons/edit-redo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionRedo.setIcon(icon2)
|
||||
self.actionRedo.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionSave = QAction(iconEditor)
|
||||
self.actionSave.setObjectName(u"actionSave")
|
||||
icon3 = QIcon()
|
||||
icon3.addFile(u":/icons/icons/document-save.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSave.setIcon(icon3)
|
||||
self.actionSave.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionCancel = QAction(iconEditor)
|
||||
self.actionCancel.setObjectName(u"actionCancel")
|
||||
icon4 = QIcon()
|
||||
icon4.addFile(u":/icons/icons/dialog-close.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionCancel.setIcon(icon4)
|
||||
self.actionCancel.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionAdd_Rectangle = QAction(iconEditor)
|
||||
self.actionAdd_Rectangle.setObjectName(u"actionAdd_Rectangle")
|
||||
icon5 = QIcon()
|
||||
icon5.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionAdd_Rectangle.setIcon(icon5)
|
||||
self.actionAdd_Rectangle.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionAdd_Text = QAction(iconEditor)
|
||||
self.actionAdd_Text.setObjectName(u"actionAdd_Text")
|
||||
icon6 = QIcon()
|
||||
icon6.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionAdd_Text.setIcon(icon6)
|
||||
self.actionAdd_Text.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionSave_to_File = QAction(iconEditor)
|
||||
self.actionSave_to_File.setObjectName(u"actionSave_to_File")
|
||||
icon7 = QIcon()
|
||||
icon7.addFile(u":/icons/icons/document-save-as.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionSave_to_File.setIcon(icon7)
|
||||
self.actionSave_to_File.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionOpen_from_File = QAction(iconEditor)
|
||||
self.actionOpen_from_File.setObjectName(u"actionOpen_from_File")
|
||||
icon8 = QIcon()
|
||||
icon8.addFile(u":/icons/icons/document-open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionOpen_from_File.setIcon(icon8)
|
||||
self.actionOpen_from_File.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionAdd_Line = QAction(iconEditor)
|
||||
self.actionAdd_Line.setObjectName(u"actionAdd_Line")
|
||||
self.actionAdd_Line.setIcon(icon)
|
||||
self.actionAdd_Line.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionAdd_Circle = QAction(iconEditor)
|
||||
self.actionAdd_Circle.setObjectName(u"actionAdd_Circle")
|
||||
icon9 = QIcon()
|
||||
icon9.addFile(u":/icons/icons/draw-circle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.actionAdd_Circle.setIcon(icon9)
|
||||
self.actionAdd_Circle.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.centralwidget = QWidget(iconEditor)
|
||||
self.centralwidget.setObjectName(u"centralwidget")
|
||||
self.verticalLayout = QVBoxLayout(self.centralwidget)
|
||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||
self.graphicsView = QGraphicsView(self.centralwidget)
|
||||
self.graphicsView.setObjectName(u"graphicsView")
|
||||
|
||||
self.verticalLayout.addWidget(self.graphicsView)
|
||||
|
||||
iconEditor.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QMenuBar(iconEditor)
|
||||
self.menubar.setObjectName(u"menubar")
|
||||
self.menubar.setGeometry(QRect(0, 0, 800, 19))
|
||||
self.menuEdit = QMenu(self.menubar)
|
||||
self.menuEdit.setObjectName(u"menuEdit")
|
||||
self.menuFile = QMenu(self.menubar)
|
||||
self.menuFile.setObjectName(u"menuFile")
|
||||
iconEditor.setMenuBar(self.menubar)
|
||||
self.statusbar = QStatusBar(iconEditor)
|
||||
self.statusbar.setObjectName(u"statusbar")
|
||||
iconEditor.setStatusBar(self.statusbar)
|
||||
self.actionToolbar = QToolBar(iconEditor)
|
||||
self.actionToolbar.setObjectName(u"actionToolbar")
|
||||
iconEditor.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.actionToolbar)
|
||||
self.iconToolbar = QToolBar(iconEditor)
|
||||
self.iconToolbar.setObjectName(u"iconToolbar")
|
||||
iconEditor.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.iconToolbar)
|
||||
|
||||
self.menubar.addAction(self.menuFile.menuAction())
|
||||
self.menubar.addAction(self.menuEdit.menuAction())
|
||||
self.menuEdit.addAction(self.actionUndo)
|
||||
self.menuEdit.addAction(self.actionRedo)
|
||||
self.menuFile.addAction(self.actionOpen_from_File)
|
||||
self.menuFile.addAction(self.actionSave_to_File)
|
||||
self.menuFile.addSeparator()
|
||||
self.menuFile.addAction(self.actionSave)
|
||||
self.menuFile.addAction(self.actionCancel)
|
||||
self.actionToolbar.addAction(self.actionUndo)
|
||||
self.actionToolbar.addAction(self.actionRedo)
|
||||
self.actionToolbar.addAction(self.actionSave)
|
||||
self.actionToolbar.addAction(self.actionCancel)
|
||||
self.iconToolbar.addAction(self.actionAdd_Rectangle)
|
||||
self.iconToolbar.addAction(self.actionAdd_Circle)
|
||||
self.iconToolbar.addAction(self.actionAdd_Text)
|
||||
self.iconToolbar.addAction(self.actionAdd_Line)
|
||||
|
||||
self.retranslateUi(iconEditor)
|
||||
|
||||
QMetaObject.connectSlotsByName(iconEditor)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, iconEditor):
|
||||
iconEditor.setWindowTitle(QCoreApplication.translate("iconEditor", u"MainWindow", None))
|
||||
self.actionUndo.setText(QCoreApplication.translate("iconEditor", u"Undo", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionUndo.setToolTip(QCoreApplication.translate("iconEditor", u"Undo", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.actionUndo.setShortcut(QCoreApplication.translate("iconEditor", u"Ctrl+Z", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionRedo.setText(QCoreApplication.translate("iconEditor", u"Redo", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionRedo.setToolTip(QCoreApplication.translate("iconEditor", u"Redo", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.actionRedo.setShortcut(QCoreApplication.translate("iconEditor", u"Ctrl+Y", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionSave.setText(QCoreApplication.translate("iconEditor", u"Save", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionSave.setToolTip(QCoreApplication.translate("iconEditor", u"Save icon", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.actionSave.setShortcut(QCoreApplication.translate("iconEditor", u"Return", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionCancel.setText(QCoreApplication.translate("iconEditor", u"Cancel", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionCancel.setToolTip(QCoreApplication.translate("iconEditor", u"Cancel icon editing", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.actionCancel.setShortcut(QCoreApplication.translate("iconEditor", u"Shift+Esc", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionAdd_Rectangle.setText(QCoreApplication.translate("iconEditor", u"Add Rectangle", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionAdd_Rectangle.setToolTip(QCoreApplication.translate("iconEditor", u"Add a rectangle", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.actionAdd_Text.setText(QCoreApplication.translate("iconEditor", u"Add Text", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionAdd_Text.setToolTip(QCoreApplication.translate("iconEditor", u"Add a text field", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.actionSave_to_File.setText(QCoreApplication.translate("iconEditor", u"Save to File", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionSave_to_File.setToolTip(QCoreApplication.translate("iconEditor", u"Save icon to a file", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.actionSave_to_File.setShortcut(QCoreApplication.translate("iconEditor", u"Ctrl+Shift+S", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionOpen_from_File.setText(QCoreApplication.translate("iconEditor", u"Open from File", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionOpen_from_File.setToolTip(QCoreApplication.translate("iconEditor", u"Open icon from File", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.actionOpen_from_File.setShortcut(QCoreApplication.translate("iconEditor", u"Ctrl+Shift+O", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionAdd_Line.setText(QCoreApplication.translate("iconEditor", u"Add Line", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionAdd_Line.setToolTip(QCoreApplication.translate("iconEditor", u"Add a line", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.actionAdd_Circle.setText(QCoreApplication.translate("iconEditor", u"Add Ellipse", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionAdd_Circle.setToolTip(QCoreApplication.translate("iconEditor", u"Add a circle", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.menuEdit.setTitle(QCoreApplication.translate("iconEditor", u"Edit", None))
|
||||
self.menuFile.setTitle(QCoreApplication.translate("iconEditor", u"File", None))
|
||||
self.actionToolbar.setWindowTitle(QCoreApplication.translate("iconEditor", u"toolBar", None))
|
||||
self.iconToolbar.setWindowTitle(QCoreApplication.translate("iconEditor", u"toolBar", None))
|
||||
# retranslateUi
|
||||
|
||||
@@ -6,7 +6,7 @@ from PySide6.QtCore import QRectF, QSize, Qt
|
||||
from PySide6.QtGui import QBrush, QColor, QFont, QIcon, QPainter, QPen, QPixmap, QRegion
|
||||
|
||||
from bedit_core.models import Port, PortID, SignalDirection
|
||||
from bedit_gui.models import Icon, Line, LineType, Rectangle, Text
|
||||
from bedit_gui.models import Ellipse, Icon, Line, LineType, Rectangle, Text
|
||||
|
||||
PORT_SIZE = 16
|
||||
DEFAULT_ICON_SIZE = QSize(48, 48)
|
||||
@@ -21,7 +21,7 @@ def get_bounding_box(icon: Icon) -> QRectF:
|
||||
for shape in icon.shapes.values():
|
||||
x, y = shape.pos
|
||||
points.append((x, y))
|
||||
if isinstance(shape, (Rectangle, Text)):
|
||||
if isinstance(shape, (Rectangle, Ellipse, Text)):
|
||||
points.append((x + shape.width, y + shape.height))
|
||||
elif isinstance(shape, Line):
|
||||
points.append(shape.end)
|
||||
@@ -72,6 +72,10 @@ def render_icon(icon: Icon, ports: dict[PortID, Port], size: QSize = DEFAULT_ICO
|
||||
painter.setPen(_line_pen(shape.line_type, shape.line_thickness, shape.line_color))
|
||||
painter.setBrush(QBrush(_color(shape.fill_color)))
|
||||
painter.drawRoundedRect(QRectF(shape.pos[0], shape.pos[1], shape.width, shape.height), shape.corner_radius, shape.corner_radius)
|
||||
elif isinstance(shape, Ellipse):
|
||||
painter.setPen(_line_pen(shape.line_type, shape.line_thickness, shape.line_color))
|
||||
painter.setBrush(QBrush(_color(shape.fill_color)))
|
||||
painter.drawEllipse(QRectF(shape.pos[0], shape.pos[1], shape.width, shape.height))
|
||||
elif isinstance(shape, Text):
|
||||
font = QFont()
|
||||
font.setPixelSize(max(1, round(shape.size)))
|
||||
|
||||
@@ -162,7 +162,6 @@ class GraphComponentItem(QGraphicsPixmapItem):
|
||||
|
||||
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
|
||||
if event.button() == Qt.MouseButton.LeftButton and self.editor.mode is GraphEditorMode.CONNECTION:
|
||||
self.editor.choose_connection_component(self.component_id, event.screenPos())
|
||||
event.accept()
|
||||
return
|
||||
self._drag_start = QPointF(self.pos())
|
||||
@@ -171,6 +170,8 @@ class GraphComponentItem(QGraphicsPixmapItem):
|
||||
|
||||
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
|
||||
if self.editor.mode is GraphEditorMode.CONNECTION:
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
self.editor.choose_connection_component(self.component_id, event.screenPos())
|
||||
event.accept()
|
||||
return
|
||||
super().mouseReleaseEvent(event)
|
||||
@@ -723,7 +724,7 @@ class GraphEditorWidget(QWidget):
|
||||
@staticmethod
|
||||
def _port_available(port_id: PortID, port: Port, used_ports: set[PortID]) -> bool:
|
||||
if isinstance(port, SignalPort):
|
||||
return port.direction is SignalDirection.OUTPUT or port_id not in used_ports
|
||||
return port.direction is SignalDirection.OUTPUT or port.multiplicity or port_id not in used_ports
|
||||
if isinstance(port, BondPort):
|
||||
return port.multiplicity or port_id not in used_ports
|
||||
return False
|
||||
|
||||
@@ -11,7 +11,7 @@ from bedit_gui.models import Icon, Shape, ShapeID
|
||||
from bedit_gui.services import icon_files
|
||||
from bedit_gui.services.application_logging import get_logger
|
||||
from bedit_gui.ui.generated.ui_icon_editor_window import Ui_iconEditor
|
||||
from bedit_gui.views.icon_graphics_scene import IconGraphicsScene, LineCreationTool, RectangleCreationTool, TextCreationTool
|
||||
from bedit_gui.views.icon_graphics_scene import EllipseCreationTool, IconGraphicsScene, LineCreationTool, RectangleCreationTool, TextCreationTool
|
||||
from bedit_gui.views.shape_options_dialog import ShapeOptionsDialog
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -123,6 +123,8 @@ class IconEditorWindow(QMainWindow):
|
||||
self.ui.graphicsView.viewport().installEventFilter(self)
|
||||
self.ui.actionAdd_Rectangle.setCheckable(True)
|
||||
self.ui.actionAdd_Rectangle.triggered.connect(self._start_rectangle_tool)
|
||||
self.ui.actionAdd_Circle.setCheckable(True)
|
||||
self.ui.actionAdd_Circle.triggered.connect(self._start_ellipse_tool)
|
||||
self.ui.actionAdd_Text.setCheckable(True)
|
||||
self.ui.actionAdd_Text.triggered.connect(self._start_text_tool)
|
||||
self.ui.actionAdd_Line.setCheckable(True)
|
||||
@@ -306,14 +308,24 @@ class IconEditorWindow(QMainWindow):
|
||||
|
||||
def _start_rectangle_tool(self) -> None:
|
||||
layer = max((shape.layer for shape in self._icon.shapes.values()), default=-1) + 1
|
||||
self.ui.actionAdd_Circle.setChecked(False)
|
||||
self.ui.actionAdd_Text.setChecked(False)
|
||||
self.ui.actionAdd_Line.setChecked(False)
|
||||
self.scene.set_creation_tool(RectangleCreationTool(self.scene, layer))
|
||||
self.ui.actionAdd_Rectangle.setChecked(True)
|
||||
|
||||
def _start_ellipse_tool(self) -> None:
|
||||
layer = max((shape.layer for shape in self._icon.shapes.values()), default=-1) + 1
|
||||
self.ui.actionAdd_Rectangle.setChecked(False)
|
||||
self.ui.actionAdd_Text.setChecked(False)
|
||||
self.ui.actionAdd_Line.setChecked(False)
|
||||
self.scene.set_creation_tool(EllipseCreationTool(self.scene, layer))
|
||||
self.ui.actionAdd_Circle.setChecked(True)
|
||||
|
||||
def _start_text_tool(self) -> None:
|
||||
layer = max((shape.layer for shape in self._icon.shapes.values()), default=-1) + 1
|
||||
self.ui.actionAdd_Rectangle.setChecked(False)
|
||||
self.ui.actionAdd_Circle.setChecked(False)
|
||||
self.ui.actionAdd_Line.setChecked(False)
|
||||
self.scene.set_creation_tool(TextCreationTool(self.scene, layer))
|
||||
self.ui.actionAdd_Text.setChecked(True)
|
||||
@@ -321,6 +333,7 @@ class IconEditorWindow(QMainWindow):
|
||||
def _start_line_tool(self) -> None:
|
||||
layer = max((shape.layer for shape in self._icon.shapes.values()), default=-1) + 1
|
||||
self.ui.actionAdd_Rectangle.setChecked(False)
|
||||
self.ui.actionAdd_Circle.setChecked(False)
|
||||
self.ui.actionAdd_Text.setChecked(False)
|
||||
self.scene.set_creation_tool(LineCreationTool(self.scene, layer))
|
||||
self.ui.actionAdd_Line.setChecked(True)
|
||||
@@ -328,6 +341,7 @@ class IconEditorWindow(QMainWindow):
|
||||
def _tool_active_changed(self, active: bool) -> None:
|
||||
if not active:
|
||||
self.ui.actionAdd_Rectangle.setChecked(False)
|
||||
self.ui.actionAdd_Circle.setChecked(False)
|
||||
self.ui.actionAdd_Text.setChecked(False)
|
||||
self.ui.actionAdd_Line.setChecked(False)
|
||||
cursor = Qt.CursorShape.CrossCursor if active else Qt.CursorShape.ArrowCursor
|
||||
|
||||
@@ -6,10 +6,10 @@ from typing import Protocol
|
||||
|
||||
from PySide6.QtCore import QLineF, QObject, QPointF, QRectF, Qt, Signal
|
||||
from PySide6.QtGui import QBrush, QColor, QFont, QPainter, QPainterPath, QPen
|
||||
from PySide6.QtWidgets import QGraphicsItem, QGraphicsLineItem, QGraphicsPathItem, QGraphicsRectItem, QGraphicsScene, QGraphicsSceneContextMenuEvent, QGraphicsSceneMouseEvent, QMenu, QStyleOptionGraphicsItem, QWidget
|
||||
from PySide6.QtWidgets import QGraphicsEllipseItem, QGraphicsItem, QGraphicsLineItem, QGraphicsPathItem, QGraphicsRectItem, QGraphicsScene, QGraphicsSceneContextMenuEvent, QGraphicsSceneMouseEvent, QMenu, QStyleOptionGraphicsItem, QWidget
|
||||
|
||||
from bedit_core.models import Port, PortID, SignalDirection
|
||||
from bedit_gui.models import Icon, Line, LineType, Rectangle, Shape, ShapeID, Text
|
||||
from bedit_gui.models import Ellipse, Icon, Line, LineType, Rectangle, Shape, ShapeID, Text
|
||||
|
||||
ICON_SCENE_SIZE = 512
|
||||
|
||||
@@ -26,7 +26,7 @@ class RectangleCreationTool:
|
||||
self.scene = scene
|
||||
self.layer = layer
|
||||
self.start: QPointF | None = None
|
||||
self.preview: QGraphicsRectItem | None = None
|
||||
self.preview: QGraphicsRectItem | QGraphicsEllipseItem | None = None
|
||||
|
||||
def begin(self, position: QPointF) -> None:
|
||||
position = self._bounded(position)
|
||||
@@ -69,6 +69,16 @@ class TextCreationTool(RectangleCreationTool):
|
||||
return Text(layer=self.layer, pos=(round(rect.x()), round(rect.y())), width=rect.width(), height=rect.height(), text="Text")
|
||||
|
||||
|
||||
class EllipseCreationTool(RectangleCreationTool):
|
||||
def begin(self, position: QPointF) -> None:
|
||||
position = self._bounded(position)
|
||||
self.start = position
|
||||
self.preview = self.scene.addEllipse(QRectF(position, position), QPen(Qt.PenStyle.DashLine))
|
||||
|
||||
def create_shape(self, rect: QRectF) -> Shape:
|
||||
return Ellipse(layer=self.layer, pos=(round(rect.x()), round(rect.y())), width=rect.width(), height=rect.height())
|
||||
|
||||
|
||||
class LineCreationTool:
|
||||
def __init__(self, scene: QGraphicsScene, layer: int) -> None:
|
||||
self.scene = scene
|
||||
@@ -109,7 +119,8 @@ class LineCreationTool:
|
||||
|
||||
|
||||
class ShapeGraphicsItem(QGraphicsPathItem):
|
||||
handle_size = 6.0
|
||||
handle_size = 8.0
|
||||
handle_hit_size = 16.0
|
||||
|
||||
def __init__(self, shape_id: ShapeID, shape: Shape, scene: IconGraphicsScene) -> None:
|
||||
super().__init__()
|
||||
@@ -128,13 +139,24 @@ class ShapeGraphicsItem(QGraphicsPathItem):
|
||||
def resize_handles(self) -> dict[str, QRectF]:
|
||||
return {"size": self.resize_handle_rect()}
|
||||
|
||||
def resize_handle_hit_rects(self) -> dict[str, QRectF]:
|
||||
size = self.handle_hit_size
|
||||
return {name: QRectF(rect.center().x() - size / 2, rect.center().y() - size / 2, size, size) for name, rect in self.resize_handles().items()}
|
||||
|
||||
def boundingRect(self) -> QRectF:
|
||||
margin = self.handle_size / 2
|
||||
margin = self.handle_hit_size / 2
|
||||
return super().boundingRect().adjusted(-margin, -margin, margin, margin)
|
||||
|
||||
def shape(self) -> QPainterPath:
|
||||
path = super().shape()
|
||||
if self.isSelected():
|
||||
for rect in self.resize_handle_hit_rects().values():
|
||||
path.addRect(rect)
|
||||
return path
|
||||
|
||||
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
|
||||
self._original_shape = self.current_shape()
|
||||
self._resize_handle = next((name for name, rect in self.resize_handles().items() if self.isSelected() and rect.contains(event.pos())), None)
|
||||
self._resize_handle = next((name for name, rect in self.resize_handle_hit_rects().items() if self.isSelected() and rect.contains(event.pos())), None)
|
||||
if self._resize_handle is not None:
|
||||
event.accept()
|
||||
return
|
||||
@@ -226,6 +248,37 @@ class RectangleGraphicsItem(ShapeGraphicsItem):
|
||||
self.setPath(path)
|
||||
|
||||
|
||||
class EllipseGraphicsItem(ShapeGraphicsItem):
|
||||
def __init__(self, shape_id: ShapeID, shape: Ellipse, scene: IconGraphicsScene) -> None:
|
||||
super().__init__(shape_id, shape, scene)
|
||||
self.ellipse = deepcopy(shape)
|
||||
self.setPos(shape.pos[0], shape.pos[1])
|
||||
self._set_size(shape.width, shape.height)
|
||||
self.setPen(scene._pen(shape))
|
||||
self.setBrush(QBrush(scene._color(shape.fill_color)))
|
||||
self.setZValue(shape.layer)
|
||||
|
||||
def current_shape(self) -> Ellipse:
|
||||
shape = deepcopy(self.ellipse)
|
||||
shape.pos = (round(self.pos().x()), round(self.pos().y()))
|
||||
rect = self.path().boundingRect()
|
||||
shape.width = rect.width()
|
||||
shape.height = rect.height()
|
||||
return shape
|
||||
|
||||
def resize_to(self, position: QPointF, _handle: str) -> None:
|
||||
bounds = self.icon_scene.sceneRect()
|
||||
width = max(1.0, min(bounds.right(), round(position.x())) - self.pos().x())
|
||||
height = max(1.0, min(bounds.bottom(), round(position.y())) - self.pos().y())
|
||||
self._set_size(width, height)
|
||||
|
||||
def _set_size(self, width: float, height: float) -> None:
|
||||
self.prepareGeometryChange()
|
||||
path = QPainterPath()
|
||||
path.addEllipse(QRectF(0, 0, width, height))
|
||||
self.setPath(path)
|
||||
|
||||
|
||||
class TextGraphicsItem(ShapeGraphicsItem):
|
||||
def __init__(self, shape_id: ShapeID, shape: Text, scene: IconGraphicsScene) -> None:
|
||||
super().__init__(shape_id, shape, scene)
|
||||
@@ -440,13 +493,15 @@ class IconGraphicsScene(QGraphicsScene):
|
||||
def _add_shape_item(self, shape_id: ShapeID, shape: Shape) -> None:
|
||||
if isinstance(shape, Rectangle):
|
||||
self.addItem(RectangleGraphicsItem(shape_id, shape, self))
|
||||
elif isinstance(shape, Ellipse):
|
||||
self.addItem(EllipseGraphicsItem(shape_id, shape, self))
|
||||
elif isinstance(shape, Text):
|
||||
self.addItem(TextGraphicsItem(shape_id, shape, self))
|
||||
elif isinstance(shape, Line):
|
||||
self.addItem(LineGraphicsItem(shape_id, shape, self))
|
||||
|
||||
@staticmethod
|
||||
def _pen(shape: Rectangle) -> QPen:
|
||||
def _pen(shape: Rectangle | Ellipse) -> QPen:
|
||||
return IconGraphicsScene._line_pen(shape.line_type, shape.line_thickness, shape.line_color)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -5,7 +5,7 @@ from copy import deepcopy
|
||||
from PySide6.QtCore import QRectF
|
||||
from PySide6.QtWidgets import QCheckBox, QComboBox, QDialog, QDialogButtonBox, QDoubleSpinBox, QFormLayout, QLabel, QLineEdit, QSpinBox, QVBoxLayout, QWidget
|
||||
|
||||
from bedit_gui.models import Line, LineType, Rectangle, Shape, Text
|
||||
from bedit_gui.models import Ellipse, Line, LineType, Rectangle, Shape, Text
|
||||
from bedit_gui.views.color_button import ColorButton
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ class ShapeOptionsDialog(QDialog):
|
||||
|
||||
if isinstance(shape, Rectangle):
|
||||
self._add_rectangle_fields(shape)
|
||||
elif isinstance(shape, Ellipse):
|
||||
self._add_ellipse_fields(shape)
|
||||
elif isinstance(shape, Text):
|
||||
self._add_text_fields(shape)
|
||||
elif isinstance(shape, Line):
|
||||
@@ -51,6 +53,10 @@ class ShapeOptionsDialog(QDialog):
|
||||
width = min(self.width.value(), self._scene_rect.right() - self.x.value())
|
||||
height = min(self.height.value(), self._scene_rect.bottom() - self.y.value())
|
||||
return Rectangle(layer=self.layer.value(), pos=(self.x.value(), self.y.value()), width=width, height=height, line_type=self.line_type.currentData(), line_thickness=self.line_thickness.value(), corner_radius=self.corner_radius.value(), line_color=self.line_color.color(), fill_color=self.fill_color.color())
|
||||
if isinstance(self._shape, Ellipse):
|
||||
width = min(self.width.value(), self._scene_rect.right() - self.x.value())
|
||||
height = min(self.height.value(), self._scene_rect.bottom() - self.y.value())
|
||||
return Ellipse(layer=self.layer.value(), pos=(self.x.value(), self.y.value()), width=width, height=height, line_type=self.line_type.currentData(), line_thickness=self.line_thickness.value(), line_color=self.line_color.color(), fill_color=self.fill_color.color())
|
||||
if isinstance(self._shape, Text):
|
||||
width = min(self.width.value(), self._scene_rect.right() - self.x.value())
|
||||
height = min(self.height.value(), self._scene_rect.bottom() - self.y.value())
|
||||
@@ -89,6 +95,29 @@ class ShapeOptionsDialog(QDialog):
|
||||
self.form.addRow("Line color", self.line_color)
|
||||
self.form.addRow("Fill color", self.fill_color)
|
||||
|
||||
def _add_ellipse_fields(self, shape: Ellipse) -> None:
|
||||
self.width = QSpinBox()
|
||||
self.width.setRange(1, round(self._scene_rect.width()))
|
||||
self.width.setValue(round(shape.width))
|
||||
self.height = QSpinBox()
|
||||
self.height.setRange(1, round(self._scene_rect.height()))
|
||||
self.height.setValue(round(shape.height))
|
||||
self.line_type = QComboBox()
|
||||
for line_type in LineType:
|
||||
self.line_type.addItem(line_type.value.replace("_", " ").title(), line_type)
|
||||
self.line_type.setCurrentIndex(self.line_type.findData(shape.line_type))
|
||||
self.line_thickness = QDoubleSpinBox()
|
||||
self.line_thickness.setRange(0, 1000)
|
||||
self.line_thickness.setValue(shape.line_thickness)
|
||||
self.line_color = ColorButton(shape.line_color)
|
||||
self.fill_color = ColorButton(shape.fill_color)
|
||||
self.form.addRow("Width", self.width)
|
||||
self.form.addRow("Height", self.height)
|
||||
self.form.addRow("Line type", self.line_type)
|
||||
self.form.addRow("Line thickness", self.line_thickness)
|
||||
self.form.addRow("Line color", self.line_color)
|
||||
self.form.addRow("Fill color", self.fill_color)
|
||||
|
||||
def _add_text_fields(self, shape: Text) -> None:
|
||||
self.width = QSpinBox()
|
||||
self.width.setRange(1, round(self._scene_rect.width()))
|
||||
|
||||
@@ -24,6 +24,14 @@ class ProcessResult:
|
||||
stdout: str
|
||||
stderr: str
|
||||
|
||||
def diagnostics(self) -> str:
|
||||
sections = []
|
||||
if self.stdout.strip():
|
||||
sections.append(f"OpenModelica output:\n{self.stdout.strip()}")
|
||||
if self.stderr.strip():
|
||||
sections.append(f"OpenModelica errors:\n{self.stderr.strip()}")
|
||||
return "\n\n".join(sections)
|
||||
|
||||
|
||||
class OpenModelicaError(RuntimeError):
|
||||
"""Raised when OMC cannot start or reports a failure."""
|
||||
@@ -80,7 +88,7 @@ class OpenModelicaRunner:
|
||||
f"OpenModelica timed out after {error.timeout} seconds"
|
||||
) from error
|
||||
if result.return_code != 0:
|
||||
details = result.stderr.strip() or result.stdout.strip()
|
||||
details = result.diagnostics()
|
||||
suffix = f": {details}" if details else ""
|
||||
raise OpenModelicaError(
|
||||
f"OpenModelica exited with status {result.return_code}{suffix}"
|
||||
@@ -128,7 +136,7 @@ class OpenModelicaRunner:
|
||||
continue
|
||||
result = ProcessResult(command=tuple(command), return_code=process.returncode, stdout=stdout, stderr=stderr)
|
||||
if result.return_code != 0:
|
||||
details = result.stderr.strip() or result.stdout.strip()
|
||||
details = result.diagnostics()
|
||||
suffix = f": {details}" if details else ""
|
||||
raise OpenModelicaError(f"simulation exited with status {result.return_code}{suffix}")
|
||||
return result
|
||||
|
||||
@@ -27,6 +27,7 @@ from .results import SimulationResult, load_openmodelica_csv
|
||||
_MODEL_FILE = "model.mo"
|
||||
_RUN_SCRIPT_FILE = "run.mos"
|
||||
_RUN_OUTPUT_FILE = "simulation-output.txt"
|
||||
_DEFAULT_LIBRARIES = ("Modelica",)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -386,7 +387,7 @@ class Simulation:
|
||||
if windows_executable.is_file():
|
||||
executable = windows_executable
|
||||
else:
|
||||
details = process.stderr.strip() or process.stdout.strip()
|
||||
details = process.diagnostics()
|
||||
suffix = f": {details}" if details else ""
|
||||
raise OpenModelicaError(
|
||||
f"OpenModelica did not build {model_name!r}{suffix}"
|
||||
@@ -692,10 +693,20 @@ def _load_model_script(
|
||||
working_directory: Path,
|
||||
commands: Sequence[str],
|
||||
) -> str:
|
||||
"""Create a script that loads ``model.mo`` before custom commands."""
|
||||
"""Create a script that loads default libraries and ``model.mo`` before custom commands."""
|
||||
load_libraries = []
|
||||
for library in _DEFAULT_LIBRARIES:
|
||||
load_libraries.extend([
|
||||
f"loaded := loadModel({library});",
|
||||
"if not loaded then",
|
||||
" print(getErrorString());",
|
||||
" exit(1);",
|
||||
"end if;",
|
||||
])
|
||||
return "\n".join(
|
||||
[
|
||||
f"cd({json.dumps(str(working_directory.resolve()))});",
|
||||
*load_libraries,
|
||||
f"loaded := loadFile({json.dumps(_MODEL_FILE)});",
|
||||
"if not loaded then",
|
||||
" print(getErrorString());",
|
||||
|
||||
@@ -607,6 +607,179 @@
|
||||
"end for;"
|
||||
]
|
||||
}
|
||||
},
|
||||
"ac5011ce-4659-4bdd-900a-63a1c779652c": {
|
||||
"name": "pulse",
|
||||
"interface": {
|
||||
"ports": {
|
||||
"69974610-3bd6-42c1-a8bb-afe6086ef0f3": {
|
||||
"port_type": "signal",
|
||||
"name": "y",
|
||||
"direction": "output",
|
||||
"multiplicity": false,
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"description": null,
|
||||
"value_type": "real",
|
||||
"quantity": null,
|
||||
"unit": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"8e128f7a-cf97-4f27-a664-488921819395": {
|
||||
"name": "amplitude",
|
||||
"value": "1.0",
|
||||
"value_type": "real",
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"quantity": null,
|
||||
"unit": null,
|
||||
"description": null
|
||||
},
|
||||
"5ff17831-f2f9-4833-9e4b-4facf7c28c0c": {
|
||||
"name": "width",
|
||||
"value": "50.0",
|
||||
"value_type": "real",
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"quantity": null,
|
||||
"unit": null,
|
||||
"description": "Pulse width in %"
|
||||
},
|
||||
"b036592b-4dc0-498b-a966-7146cf9cf95b": {
|
||||
"name": "offset",
|
||||
"value": "0",
|
||||
"value_type": "real",
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"quantity": null,
|
||||
"unit": null,
|
||||
"description": null
|
||||
},
|
||||
"df82150d-973d-4a8a-9bd5-6ce21a2e8bbb": {
|
||||
"name": "period",
|
||||
"value": "1.0",
|
||||
"value_type": "real",
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"quantity": null,
|
||||
"unit": null,
|
||||
"description": null
|
||||
},
|
||||
"1195c93e-3309-494e-9a56-96e8fd5e330c": {
|
||||
"name": "nperiod",
|
||||
"value": "-1",
|
||||
"value_type": "int",
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"quantity": null,
|
||||
"unit": null,
|
||||
"description": "Number of periods (<0 means infinite)"
|
||||
},
|
||||
"3f5f7b5c-2794-4e75-a5c8-1ef2b0ecd87d": {
|
||||
"name": "startTime",
|
||||
"value": "0.0",
|
||||
"value_type": "real",
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"quantity": null,
|
||||
"unit": null,
|
||||
"description": null
|
||||
}
|
||||
},
|
||||
"implementation": {
|
||||
"implementation_type": "equation",
|
||||
"declarations": [
|
||||
"Integer count;",
|
||||
"Modelica.Units.SI.Time T_start;",
|
||||
"Modelica.Units.SI.Time T_width=period*width/100;"
|
||||
],
|
||||
"initial_equations": [
|
||||
"initial algorithm",
|
||||
"count := integer((time-startTime)/period);",
|
||||
"T_start := startTime + count*period;"
|
||||
],
|
||||
"equations": [
|
||||
"when time >= (pre(count)+1)*period + startTime then",
|
||||
"\tcount = pre(count) + 1;",
|
||||
"\tT_start = time;",
|
||||
"end when;",
|
||||
"y = offset + (if (time < startTime or nperiod == 0 or (nperiod > 0 and count >= nperiod)) then 0 else if time < T_start + T_width then amplitude else 0);"
|
||||
]
|
||||
}
|
||||
},
|
||||
"ec0599cb-b57b-4194-b166-526d5cd5382a": {
|
||||
"name": "plusminus",
|
||||
"interface": {
|
||||
"ports": {
|
||||
"23a8b215-7c2f-4ccf-b020-4d274d7a228f": {
|
||||
"port_type": "signal",
|
||||
"name": "plus",
|
||||
"direction": "input",
|
||||
"multiplicity": true,
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"description": null,
|
||||
"value_type": "real",
|
||||
"quantity": null,
|
||||
"unit": null
|
||||
},
|
||||
"4c4771f8-22c8-4829-a060-913470e2b2fc": {
|
||||
"port_type": "signal",
|
||||
"name": "minus",
|
||||
"direction": "input",
|
||||
"multiplicity": true,
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"description": null,
|
||||
"value_type": "real",
|
||||
"quantity": null,
|
||||
"unit": null
|
||||
},
|
||||
"a186a0e1-7aea-4fb6-871c-0ba70d78b8ac": {
|
||||
"port_type": "signal",
|
||||
"name": "y",
|
||||
"direction": "output",
|
||||
"multiplicity": false,
|
||||
"matrix_size": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"description": null,
|
||||
"value_type": "real",
|
||||
"quantity": null,
|
||||
"unit": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": {},
|
||||
"implementation": {
|
||||
"implementation_type": "equation",
|
||||
"declarations": [],
|
||||
"initial_equations": [],
|
||||
"equations": [
|
||||
"y = sum(plus) - sum(minus);"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"connections": {
|
||||
@@ -661,6 +834,16 @@
|
||||
"connection_type": "signal",
|
||||
"source": "a2709c23-7485-43b2-bc87-28a0f4c37c0b",
|
||||
"target": "8bd1cc42-03b9-4c46-b31b-c1f46933dc9e"
|
||||
},
|
||||
"42f8d11c-8b31-476a-8e56-dd1ac3703508": {
|
||||
"connection_type": "signal",
|
||||
"source": "69974610-3bd6-42c1-a8bb-afe6086ef0f3",
|
||||
"target": "23a8b215-7c2f-4ccf-b020-4d274d7a228f"
|
||||
},
|
||||
"81e6b0d7-65f2-4edc-a303-6eee78ac1972": {
|
||||
"connection_type": "signal",
|
||||
"source": "5807dc83-d049-4f0f-9708-d041a752fcd0",
|
||||
"target": "4c4771f8-22c8-4829-a060-913470e2b2fc"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1149,6 +1332,139 @@
|
||||
-32
|
||||
]
|
||||
}
|
||||
},
|
||||
"ac5011ce-4659-4bdd-900a-63a1c779652c": {
|
||||
"shapes": {
|
||||
"7a9115fe-4e40-49d9-ab50-2f399ca1038b": {
|
||||
"layer": 0,
|
||||
"type": "rectangle",
|
||||
"pos": [
|
||||
-48,
|
||||
-48
|
||||
],
|
||||
"width": 96.0,
|
||||
"height": 96.0,
|
||||
"line_type": "solid",
|
||||
"line_thickness": 5.0,
|
||||
"corner_radius": 8.0,
|
||||
"line_color": "#00007fff",
|
||||
"fill_color": "#ebebebff"
|
||||
},
|
||||
"80e150dc-07d3-4d27-9fe0-eecb82fb2689": {
|
||||
"layer": 4,
|
||||
"type": "line",
|
||||
"pos": [
|
||||
-32,
|
||||
-32
|
||||
],
|
||||
"end": [
|
||||
-32,
|
||||
32
|
||||
],
|
||||
"line_type": "solid",
|
||||
"line_thickness": 3.0,
|
||||
"line_color": "#000000ff"
|
||||
},
|
||||
"66c7fb78-2866-4f9f-9df2-95cfa4c57a2d": {
|
||||
"layer": 2,
|
||||
"type": "line",
|
||||
"pos": [
|
||||
-32,
|
||||
32
|
||||
],
|
||||
"end": [
|
||||
32,
|
||||
32
|
||||
],
|
||||
"line_type": "solid",
|
||||
"line_thickness": 3.0,
|
||||
"line_color": "#000000ff"
|
||||
},
|
||||
"b1b1f425-eea6-48e7-a61d-f9eb221984b7": {
|
||||
"layer": 3,
|
||||
"type": "line",
|
||||
"pos": [
|
||||
-32,
|
||||
14
|
||||
],
|
||||
"end": [
|
||||
3,
|
||||
14
|
||||
],
|
||||
"line_type": "solid",
|
||||
"line_thickness": 2.0,
|
||||
"line_color": "#ffaa00ff"
|
||||
},
|
||||
"0c0b7ec3-557d-4fb4-b8b8-b6b6a269ec55": {
|
||||
"layer": 5,
|
||||
"type": "line",
|
||||
"pos": [
|
||||
3,
|
||||
14
|
||||
],
|
||||
"end": [
|
||||
3,
|
||||
-13
|
||||
],
|
||||
"line_type": "solid",
|
||||
"line_thickness": 2.0,
|
||||
"line_color": "#ffaa00ff"
|
||||
},
|
||||
"e5c99d8a-69db-4915-b1e7-84ac5e52de02": {
|
||||
"layer": 6,
|
||||
"type": "line",
|
||||
"pos": [
|
||||
3,
|
||||
-13
|
||||
],
|
||||
"end": [
|
||||
32,
|
||||
-13
|
||||
],
|
||||
"line_type": "solid",
|
||||
"line_thickness": 2.0,
|
||||
"line_color": "#ffaa00ff"
|
||||
}
|
||||
},
|
||||
"port_positions": {
|
||||
"69974610-3bd6-42c1-a8bb-afe6086ef0f3": [
|
||||
-8,
|
||||
-8
|
||||
]
|
||||
}
|
||||
},
|
||||
"ec0599cb-b57b-4194-b166-526d5cd5382a": {
|
||||
"shapes": {
|
||||
"ecbbad0e-67e3-4d13-ba25-7660f50bc38e": {
|
||||
"layer": 0,
|
||||
"type": "rectangle",
|
||||
"pos": [
|
||||
-48,
|
||||
-48
|
||||
],
|
||||
"width": 96.0,
|
||||
"height": 96.0,
|
||||
"line_type": "solid",
|
||||
"line_thickness": 5.0,
|
||||
"corner_radius": 8.0,
|
||||
"line_color": "#00007fff",
|
||||
"fill_color": "#ebebebff"
|
||||
}
|
||||
},
|
||||
"port_positions": {
|
||||
"23a8b215-7c2f-4ccf-b020-4d274d7a228f": [
|
||||
-24,
|
||||
9
|
||||
],
|
||||
"4c4771f8-22c8-4829-a060-913470e2b2fc": [
|
||||
-24,
|
||||
-26
|
||||
],
|
||||
"a186a0e1-7aea-4fb6-871c-0ba70d78b8ac": [
|
||||
11,
|
||||
-8
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1207,8 +1523,8 @@
|
||||
48
|
||||
],
|
||||
"2b3844e8-1875-40d0-ae4e-7a14ca217bce": [
|
||||
-432,
|
||||
236
|
||||
-428,
|
||||
744
|
||||
],
|
||||
"bf08f999-bfe8-493a-9ad2-9c2965e17d6d": [
|
||||
-504,
|
||||
@@ -1233,6 +1549,14 @@
|
||||
"8e8dca73-2d11-4631-9c24-5e1a4e3b2920": [
|
||||
64,
|
||||
128
|
||||
],
|
||||
"ac5011ce-4659-4bdd-900a-63a1c779652c": [
|
||||
-432,
|
||||
560
|
||||
],
|
||||
"ec0599cb-b57b-4194-b166-526d5cd5382a": [
|
||||
-120,
|
||||
656
|
||||
]
|
||||
},
|
||||
"component_labels": {
|
||||
|
||||
Reference in New Issue
Block a user