mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-11-02 20:52:20 -07:00
CURA-4525 party working objects list and build plates
This commit is contained in:
parent
5050124699
commit
38670171f5
6 changed files with 252 additions and 58 deletions
|
|
@ -156,13 +156,8 @@ class CuraActions(QObject):
|
||||||
|
|
||||||
if not nodes_to_change:
|
if not nodes_to_change:
|
||||||
Logger.log("d", "Nothing to change.")
|
Logger.log("d", "Nothing to change.")
|
||||||
# If there are no changes to make, we still need to reset the selected extruders.
|
|
||||||
# This is a workaround for checked menu items being deselected while still being
|
|
||||||
# selected.
|
|
||||||
#ExtruderManager.getInstance().resetSelectedObjectExtruders()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
Logger.log("d", "Yes: %s", nodes_to_change)
|
|
||||||
for node in nodes_to_change:
|
for node in nodes_to_change:
|
||||||
operation.addOperation(SetBuildPlateNumberOperation(node, build_plate_nr))
|
operation.addOperation(SetBuildPlateNumberOperation(node, build_plate_nr))
|
||||||
operation.push()
|
operation.push()
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,8 @@ from cura.Settings.ContainerManager import ContainerManager
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
|
from cura.ObjectManager import ObjectManager
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from PyQt5.QtGui import QColor, QIcon
|
from PyQt5.QtGui import QColor, QIcon
|
||||||
|
|
@ -205,6 +207,7 @@ class CuraApplication(QtApplication):
|
||||||
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
||||||
self._machine_manager = None # This is initialized on demand.
|
self._machine_manager = None # This is initialized on demand.
|
||||||
self._material_manager = None
|
self._material_manager = None
|
||||||
|
self._object_manager = None
|
||||||
self._setting_inheritance_manager = None
|
self._setting_inheritance_manager = None
|
||||||
self._simple_mode_settings_manager = None
|
self._simple_mode_settings_manager = None
|
||||||
|
|
||||||
|
|
@ -292,8 +295,6 @@ class CuraApplication(QtApplication):
|
||||||
preferences.addPreference("metadata/setting_version", 0)
|
preferences.addPreference("metadata/setting_version", 0)
|
||||||
preferences.setValue("metadata/setting_version", self.SettingVersion) #Don't make it equal to the default so that the setting version always gets written to the file.
|
preferences.setValue("metadata/setting_version", self.SettingVersion) #Don't make it equal to the default so that the setting version always gets written to the file.
|
||||||
|
|
||||||
preferences.addPreference("view/build_plate_number", 0)
|
|
||||||
|
|
||||||
preferences.addPreference("cura/active_mode", "simple")
|
preferences.addPreference("cura/active_mode", "simple")
|
||||||
|
|
||||||
preferences.addPreference("cura/categories_expanded", "")
|
preferences.addPreference("cura/categories_expanded", "")
|
||||||
|
|
@ -384,7 +385,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
# research
|
# research
|
||||||
self._num_build_plates = 1 # default
|
self._num_build_plates = 1 # default
|
||||||
self._active_build_plate = 1
|
self._active_build_plate = 0
|
||||||
|
|
||||||
def _onEngineCreated(self):
|
def _onEngineCreated(self):
|
||||||
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||||
|
|
@ -717,6 +718,9 @@ class CuraApplication(QtApplication):
|
||||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager",
|
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager",
|
||||||
self.getSimpleModeSettingsManager)
|
self.getSimpleModeSettingsManager)
|
||||||
|
|
||||||
|
Logger.log("d", " #### going to register object manager")
|
||||||
|
qmlRegisterSingletonType(ObjectManager, "Cura", 1, 2, "ObjectManager", self.getObjectManager)
|
||||||
|
|
||||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||||
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
|
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
|
||||||
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
||||||
|
|
@ -744,6 +748,11 @@ class CuraApplication(QtApplication):
|
||||||
self._material_manager = MaterialManager.createMaterialManager()
|
self._material_manager = MaterialManager.createMaterialManager()
|
||||||
return self._material_manager
|
return self._material_manager
|
||||||
|
|
||||||
|
def getObjectManager(self, *args):
|
||||||
|
if self._object_manager is None:
|
||||||
|
self._object_manager = ObjectManager.createObjectManager()
|
||||||
|
return self._object_manager
|
||||||
|
|
||||||
def getSettingInheritanceManager(self, *args):
|
def getSettingInheritanceManager(self, *args):
|
||||||
if self._setting_inheritance_manager is None:
|
if self._setting_inheritance_manager is None:
|
||||||
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
||||||
|
|
@ -799,6 +808,7 @@ class CuraApplication(QtApplication):
|
||||||
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
||||||
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
||||||
qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel")
|
qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel")
|
||||||
|
# qmlRegisterSingletonType(ObjectManager, "Cura", 1, 0, "ObjectManager", ObjectManager.createObjectManager)
|
||||||
|
|
||||||
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
|
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
|
||||||
|
|
||||||
|
|
@ -1464,7 +1474,6 @@ class CuraApplication(QtApplication):
|
||||||
def setActiveBuildPlate(self, nr):
|
def setActiveBuildPlate(self, nr):
|
||||||
Logger.log("d", "Select build plate: %s" % nr)
|
Logger.log("d", "Select build plate: %s" % nr)
|
||||||
self._active_build_plate = nr
|
self._active_build_plate = nr
|
||||||
Preferences.setValue("view/build_plate_number", self._active_build_plate)
|
|
||||||
|
|
||||||
self.activeBuildPlateChanged.emit()
|
self.activeBuildPlateChanged.emit()
|
||||||
|
|
||||||
|
|
|
||||||
68
cura/ObjectManager.py
Normal file
68
cura/ObjectManager.py
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from UM.Scene.Selection import Selection
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectManager(ListModel):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._last_selected_index = 0
|
||||||
|
Application.getInstance().getController().getScene().sceneChanged.connect(self._update)
|
||||||
|
|
||||||
|
def _update(self, *args):
|
||||||
|
nodes = []
|
||||||
|
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
|
||||||
|
if type(node) is not SceneNode or (not node.getMeshData() and not node.callDecoration("getLayerData")):
|
||||||
|
continue
|
||||||
|
nodes.append({
|
||||||
|
"name": node.getName(),
|
||||||
|
"isSelected": Selection.isSelected(node),
|
||||||
|
"buildPlateNumber": node.callDecoration("getBuildPlateNumber"),
|
||||||
|
"node": node
|
||||||
|
})
|
||||||
|
nodes = sorted(nodes, key=lambda n: n["name"])
|
||||||
|
self.setItems(nodes)
|
||||||
|
|
||||||
|
self.itemsChanged.emit()
|
||||||
|
|
||||||
|
## Either select or deselect an item
|
||||||
|
@pyqtSlot(int)
|
||||||
|
def changeSelection(self, index):
|
||||||
|
modifiers = QApplication.keyboardModifiers()
|
||||||
|
ctrl_is_active = modifiers & Qt.ControlModifier
|
||||||
|
shift_is_active = modifiers & Qt.ShiftModifier
|
||||||
|
|
||||||
|
if ctrl_is_active:
|
||||||
|
item = self.getItem(index)
|
||||||
|
node = item["node"]
|
||||||
|
if Selection.isSelected(node):
|
||||||
|
Selection.remove(node)
|
||||||
|
else:
|
||||||
|
Selection.add(node)
|
||||||
|
elif shift_is_active:
|
||||||
|
polarity = 1 if index + 1 > self._last_selected_index else -1
|
||||||
|
for i in range(self._last_selected_index, index + polarity, polarity):
|
||||||
|
item = self.getItem(i)
|
||||||
|
node = item["node"]
|
||||||
|
Selection.add(node)
|
||||||
|
else:
|
||||||
|
# Single select
|
||||||
|
item = self.getItem(index)
|
||||||
|
node = item["node"]
|
||||||
|
Selection.clear()
|
||||||
|
Selection.add(node)
|
||||||
|
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||||
|
if build_plate_number is not None and build_plate_number != -1:
|
||||||
|
Application.getInstance().setActiveBuildPlate(build_plate_number)
|
||||||
|
|
||||||
|
self._last_selected_index = index
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def createObjectManager():
|
||||||
|
return ObjectManager()
|
||||||
|
|
@ -9,26 +9,9 @@ class BuildPlateDecorator(SceneNodeDecorator):
|
||||||
|
|
||||||
def setBuildPlateNumber(self, nr):
|
def setBuildPlateNumber(self, nr):
|
||||||
self._build_plate_number = nr
|
self._build_plate_number = nr
|
||||||
# self.getNode().childrenChanged.connect(self._onChildrenChanged)
|
|
||||||
|
|
||||||
def getBuildPlateNumber(self):
|
def getBuildPlateNumber(self):
|
||||||
return self._build_plate_number
|
return self._build_plate_number
|
||||||
|
|
||||||
# def setNode(self, node):
|
|
||||||
# super().setNode(node)
|
|
||||||
# self.getNode().childrenChanged.connect(self._onChildrenChanged)
|
|
||||||
|
|
||||||
# def _onChildrenChanged(self, node):
|
|
||||||
# if not self.getNode().hasChildren():
|
|
||||||
# # A group that no longer has children may remove itself from the scene
|
|
||||||
# self._old_parent = self.getNode().getParent()
|
|
||||||
# self.getNode().setParent(None)
|
|
||||||
# Selection.remove(self.getNode())
|
|
||||||
# else:
|
|
||||||
# # A group that has removed itself from the scene because it had no children may add itself back to the scene when a child is added to it
|
|
||||||
# if not self.getNode().getParent() and self._old_parent:
|
|
||||||
# self.getNode().setParent(self._old_parent)
|
|
||||||
# self._old_parent = None
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
return BuildPlateDecorator()
|
return BuildPlateDecorator()
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ class SolidView(View):
|
||||||
else:
|
else:
|
||||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))
|
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))
|
||||||
|
|
||||||
activeBuildPlateNumber = Preferences.getInstance().getValue("view/build_plate_number") or 0
|
activeBuildPlateNumber = Application.getInstance().activeBuildPlate
|
||||||
|
|
||||||
for node in DepthFirstIterator(scene.getRoot()):
|
for node in DepthFirstIterator(scene.getRoot()):
|
||||||
if not node.render(renderer):
|
if not node.render(renderer):
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import QtQuick.Layouts 1.1
|
||||||
import QtQuick.Dialogs 1.1
|
import QtQuick.Dialogs 1.1
|
||||||
|
|
||||||
import UM 1.3 as UM
|
import UM 1.3 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.2 as Cura
|
||||||
|
|
||||||
import "Menus"
|
import "Menus"
|
||||||
|
|
||||||
|
|
@ -21,6 +21,8 @@ Rectangle
|
||||||
width: UM.Theme.getSize("objects_menu_size").width
|
width: UM.Theme.getSize("objects_menu_size").width
|
||||||
height: UM.Theme.getSize("objects_menu_size").height
|
height: UM.Theme.getSize("objects_menu_size").height
|
||||||
|
|
||||||
|
SystemPalette { id: palette }
|
||||||
|
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
id: openFileButton;
|
id: openFileButton;
|
||||||
|
|
@ -38,51 +40,188 @@ Rectangle
|
||||||
action: Cura.Actions.open;
|
action: Cura.Actions.open;
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel
|
|
||||||
{
|
|
||||||
id: objectsListModel;
|
|
||||||
|
|
||||||
ListElement {
|
|
||||||
name: "Apple"
|
|
||||||
cost: 2.45
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
name: "Orange"
|
|
||||||
cost: 3.25
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
name: "Banana"
|
|
||||||
cost: 1.95
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: objectDelegate
|
id: objectDelegate
|
||||||
Rectangle {
|
Rectangle
|
||||||
height: 30
|
{
|
||||||
|
height: childrenRect.height
|
||||||
|
color: Cura.ObjectManager.getItem(index).isSelected ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
|
||||||
|
width: parent.width
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: nodeNameLabel
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
//anchors.right: parent.right
|
||||||
|
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
|
||||||
|
text: Cura.ObjectManager.getItem(index).name;
|
||||||
|
color: Cura.ObjectManager.getItem(index).isSelected ? palette.highlightedText : palette.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
Label
|
||||||
text: name
|
{
|
||||||
color: red
|
id: buildPlateNumberLabel
|
||||||
|
width: 20
|
||||||
|
anchors.left: nodeNameLabel.right
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.right: parent.right
|
||||||
|
text: Cura.ObjectManager.getItem(index).buildPlateNumber;
|
||||||
|
color: Cura.ObjectManager.getItem(index).isSelected ? palette.highlightedText : palette.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
anchors.fill: parent;
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
Cura.ObjectManager.changeSelection(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//Text { text: '$' + cost }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView
|
// list all the scene nodes
|
||||||
|
ScrollView
|
||||||
{
|
{
|
||||||
model: objectsListModel;
|
id: objectsList
|
||||||
|
frameVisible: true
|
||||||
|
width: parent.width - 2 * UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
anchors
|
anchors
|
||||||
{
|
{
|
||||||
top: openFileButton.bottom;
|
top: openFileButton.bottom;
|
||||||
topMargin: UM.Theme.getSize("default_margin").height;
|
topMargin: UM.Theme.getSize("default_margin").height;
|
||||||
left: parent.left;
|
left: parent.left;
|
||||||
leftMargin: UM.Theme.getSize("default_margin").height;
|
leftMargin: UM.Theme.getSize("default_margin").height;
|
||||||
|
bottom: buildPlateSelection.top;
|
||||||
|
bottomMargin: UM.Theme.getSize("default_margin").height;
|
||||||
}
|
}
|
||||||
width: parent.width - 2 * UM.Theme.getSize("default_margin").height
|
|
||||||
height: 100
|
|
||||||
|
|
||||||
delegate: objectDelegate
|
Rectangle
|
||||||
|
{
|
||||||
|
parent: viewport
|
||||||
|
anchors.fill: parent
|
||||||
|
color: palette.light
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView
|
||||||
|
{
|
||||||
|
id: listview
|
||||||
|
model: Cura.ObjectManager
|
||||||
|
//model: objectsListModel
|
||||||
|
|
||||||
|
onModelChanged:
|
||||||
|
{
|
||||||
|
//currentIndex = -1;
|
||||||
|
}
|
||||||
|
width: parent.width
|
||||||
|
currentIndex: -1
|
||||||
|
onCurrentIndexChanged:
|
||||||
|
{
|
||||||
|
//base.selectedPrinter = listview.model[currentIndex];
|
||||||
|
// Only allow connecting if the printer has responded to API query since the last refresh
|
||||||
|
//base.completeProperties = base.selectedPrinter != null && base.selectedPrinter.getProperty("incomplete") != "true";
|
||||||
|
}
|
||||||
|
//Component.onCompleted: manager.startDiscovery()
|
||||||
|
delegate: objectDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListModel
|
||||||
|
{
|
||||||
|
id: buildPlatesModel
|
||||||
|
|
||||||
|
ListElement
|
||||||
|
{
|
||||||
|
name: "build plate 0"
|
||||||
|
buildPlateNumber: 0
|
||||||
|
}
|
||||||
|
ListElement
|
||||||
|
{
|
||||||
|
name: "build plate 1"
|
||||||
|
buildPlateNumber: 1
|
||||||
|
}
|
||||||
|
ListElement
|
||||||
|
{
|
||||||
|
name: "build plate 2"
|
||||||
|
buildPlateNumber: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: buildPlateDelegate
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
height: childrenRect.height
|
||||||
|
color: CuraApplication.activeBuildPlate == buildPlateNumber ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
|
||||||
|
width: parent.width
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.right: parent.right
|
||||||
|
text: name //Cura.ObjectManager.getItem(index).name;
|
||||||
|
color: CuraApplication.activeBuildPlate == buildPlateNumber ? palette.highlightedText : palette.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
anchors.fill: parent;
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
CuraApplication.setActiveBuildPlate(buildPlateNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView
|
||||||
|
{
|
||||||
|
id: buildPlateSelection
|
||||||
|
frameVisible: true
|
||||||
|
height: 100
|
||||||
|
width: parent.width - 2 * UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
// top: objectsList.bottom;
|
||||||
|
topMargin: UM.Theme.getSize("default_margin").height;
|
||||||
|
left: parent.left;
|
||||||
|
leftMargin: UM.Theme.getSize("default_margin").height;
|
||||||
|
bottom: parent.bottom;
|
||||||
|
bottomMargin: UM.Theme.getSize("default_margin").height;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
parent: viewport
|
||||||
|
anchors.fill: parent
|
||||||
|
color: palette.light
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView
|
||||||
|
{
|
||||||
|
id: buildPlateListView
|
||||||
|
model: buildPlatesModel
|
||||||
|
|
||||||
|
onModelChanged:
|
||||||
|
{
|
||||||
|
//currentIndex = -1;
|
||||||
|
}
|
||||||
|
width: parent.width
|
||||||
|
currentIndex: -1
|
||||||
|
onCurrentIndexChanged:
|
||||||
|
{
|
||||||
|
//base.selectedPrinter = listview.model[currentIndex];
|
||||||
|
// Only allow connecting if the printer has responded to API query since the last refresh
|
||||||
|
//base.completeProperties = base.selectedPrinter != null && base.selectedPrinter.getProperty("incomplete") != "true";
|
||||||
|
}
|
||||||
|
//Component.onCompleted: manager.startDiscovery()
|
||||||
|
delegate: buildPlateDelegate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue