ObjectCollection.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # http://flatcam.org #
  4. # Author: Juan Pablo Caram (c) #
  5. # Date: 2/5/2014 #
  6. # MIT Licence #
  7. # ##########################################################
  8. # ##########################################################
  9. # File modified by: Dennis Hayrullin #
  10. # File modified by: Marius Stanciu #
  11. # ##########################################################
  12. from PyQt5 import QtGui, QtCore, QtWidgets
  13. from PyQt5.QtCore import Qt, QSettings
  14. from PyQt5.QtGui import QColor
  15. # from PyQt5.QtCore import QModelIndex
  16. from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon, FlatCAMCNCjob, FlatCAMDocument, FlatCAMScript, \
  17. FlatCAMObj
  18. import inspect # TODO: Remove
  19. import FlatCAMApp
  20. import re
  21. import logging
  22. import collections
  23. from copy import deepcopy
  24. from numpy import Inf
  25. import gettext
  26. import FlatCAMTranslation as fcTranslate
  27. import builtins
  28. fcTranslate.apply_language('strings')
  29. if '_' not in builtins.__dict__:
  30. _ = gettext.gettext
  31. log = logging.getLogger('base')
  32. class KeySensitiveListView(QtWidgets.QTreeView):
  33. """
  34. QtGui.QListView extended to emit a signal on key press.
  35. """
  36. def __init__(self, app, parent=None):
  37. super(KeySensitiveListView, self).__init__(parent)
  38. self.setHeaderHidden(True)
  39. self.setEditTriggers(QtWidgets.QTreeView.SelectedClicked)
  40. # self.setRootIsDecorated(False)
  41. # self.setExpandsOnDoubleClick(False)
  42. # Enable dragging and dropping onto the GUI
  43. self.setAcceptDrops(True)
  44. self.filename = ""
  45. self.app = app
  46. # Enabling Drag and Drop for the items in the Project Tab
  47. # Example: https://github.com/d1vanov/PyQt5-reorderable-list-model/blob/master/reorderable_list_model.py
  48. # https://github.com/jimmykuu/PyQt-PySide-Cookbook/blob/master/tree/drop_indicator.md
  49. # self.setDragEnabled(True)
  50. # self.viewport().setAcceptDrops(True)
  51. # self.setDropIndicatorShown(True)
  52. # self.DragDropMode(QtWidgets.QAbstractItemView.InternalMove)
  53. # self.current_idx = None
  54. # self.current_group = None
  55. # self.dropped_obj = None
  56. keyPressed = QtCore.pyqtSignal(int)
  57. def keyPressEvent(self, event):
  58. # super(KeySensitiveListView, self).keyPressEvent(event)
  59. self.keyPressed.emit(event.key())
  60. def dragEnterEvent(self, event):
  61. # if event.source():
  62. # self.current_idx = self.currentIndex()
  63. # self.current_group = self.model().group_items[self.current_idx.internalPointer().obj.kind]
  64. # self.dropped_obj = self.current_idx.internalPointer().obj
  65. if event.mimeData().hasUrls:
  66. event.accept()
  67. else:
  68. event.ignore()
  69. def dragMoveEvent(self, event):
  70. self.setDropIndicatorShown(True)
  71. if event.mimeData().hasUrls:
  72. event.accept()
  73. else:
  74. event.ignore()
  75. def dropEvent(self, event):
  76. drop_indicator = self.dropIndicatorPosition()
  77. # if event.source():
  78. # new_index = self.indexAt(event.pos())
  79. # new_group = self.model().group_items[new_index.internalPointer().obj.kind]
  80. # if self.current_group == new_group:
  81. #
  82. # # delete it from the model
  83. # deleted_obj_name = self.dropped_obj.options['name']
  84. # self.model().delete_by_name(deleted_obj_name)
  85. #
  86. # # add the object to the new index
  87. # self.model().append(self.dropped_obj, to_index=new_index)
  88. #
  89. # return
  90. m = event.mimeData()
  91. if m.hasUrls:
  92. event.accept()
  93. for url in m.urls():
  94. self.filename = str(url.toLocalFile())
  95. # file drop from outside application
  96. if drop_indicator == QtWidgets.QAbstractItemView.OnItem:
  97. if self.filename == "":
  98. self.app.inform.emit(_("Open cancelled."))
  99. else:
  100. if self.filename.lower().rpartition('.')[-1] in self.app.grb_list:
  101. self.app.worker_task.emit({'fcn': self.app.open_gerber,
  102. 'params': [self.filename]})
  103. else:
  104. event.ignore()
  105. if self.filename.lower().rpartition('.')[-1] in self.app.exc_list:
  106. self.app.worker_task.emit({'fcn': self.app.open_excellon,
  107. 'params': [self.filename]})
  108. else:
  109. event.ignore()
  110. if self.filename.lower().rpartition('.')[-1] in self.app.gcode_list:
  111. self.app.worker_task.emit({'fcn': self.app.open_gcode,
  112. 'params': [self.filename]})
  113. else:
  114. event.ignore()
  115. if self.filename.lower().rpartition('.')[-1] in self.app.svg_list:
  116. object_type = 'geometry'
  117. self.app.worker_task.emit({'fcn': self.app.import_svg,
  118. 'params': [self.filename, object_type, None]})
  119. if self.filename.lower().rpartition('.')[-1] in self.app.dxf_list:
  120. object_type = 'geometry'
  121. self.app.worker_task.emit({'fcn': self.app.import_dxf,
  122. 'params': [self.filename, object_type, None]})
  123. if self.filename.lower().rpartition('.')[-1] in self.app.prj_list:
  124. # self.app.open_project() is not Thread Safe
  125. self.app.open_project(self.filename)
  126. else:
  127. event.ignore()
  128. else:
  129. pass
  130. else:
  131. event.ignore()
  132. class TreeItem(KeySensitiveListView):
  133. """
  134. Item of a tree model
  135. """
  136. def __init__(self, data, icon=None, obj=None, parent_item=None):
  137. super(TreeItem, self).__init__(parent_item)
  138. self.parent_item = parent_item
  139. self.item_data = data # Columns string data
  140. self.icon = icon # Decoration
  141. self.obj = obj # FlatCAMObj
  142. self.child_items = []
  143. if parent_item:
  144. parent_item.append_child(self)
  145. def append_child(self, item):
  146. self.child_items.append(item)
  147. item.set_parent_item(self)
  148. def remove_child(self, item):
  149. child = self.child_items.pop(self.child_items.index(item))
  150. child.obj.clear(True)
  151. child.obj.delete()
  152. del child.obj
  153. del child
  154. def remove_children(self):
  155. for child in self.child_items:
  156. child.obj.clear()
  157. child.obj.delete()
  158. del child.obj
  159. del child
  160. self.child_items = []
  161. def child(self, row):
  162. return self.child_items[row]
  163. def child_count(self):
  164. return len(self.child_items)
  165. def column_count(self):
  166. return len(self.item_data)
  167. def data(self, column):
  168. return self.item_data[column]
  169. def row(self):
  170. return self.parent_item.child_items.index(self)
  171. def set_parent_item(self, parent_item):
  172. self.parent_item = parent_item
  173. def __del__(self):
  174. del self.icon
  175. class ObjectCollection(QtCore.QAbstractItemModel):
  176. """
  177. Object storage and management.
  178. """
  179. groups = [
  180. ("gerber", "Gerber"),
  181. ("excellon", "Excellon"),
  182. ("geometry", "Geometry"),
  183. ("cncjob", "CNC Job"),
  184. ("script", "Scripts"),
  185. ("document", "Document"),
  186. ]
  187. classdict = {
  188. "gerber": FlatCAMGerber,
  189. "excellon": FlatCAMExcellon,
  190. "cncjob": FlatCAMCNCjob,
  191. "geometry": FlatCAMGeometry,
  192. "script": FlatCAMScript,
  193. "document": FlatCAMDocument
  194. }
  195. icon_files = {
  196. "gerber": "share/flatcam_icon16.png",
  197. "excellon": "share/drill16.png",
  198. "cncjob": "share/cnc16.png",
  199. "geometry": "share/geometry16.png",
  200. "script": "share/script_new16.png",
  201. "document": "share/notes16_1.png"
  202. }
  203. # will emit the name of the object that was just selected
  204. item_selected = QtCore.pyqtSignal(str)
  205. update_list_signal = QtCore.pyqtSignal()
  206. root_item = None
  207. # app = None
  208. def __init__(self, app, parent=None):
  209. QtCore.QAbstractItemModel.__init__(self)
  210. self.app = app
  211. # ## Icons for the list view
  212. self.icons = {}
  213. for kind in ObjectCollection.icon_files:
  214. self.icons[kind] = QtGui.QPixmap(
  215. ObjectCollection.icon_files[kind].replace('share', self.app.resource_location))
  216. # Create root tree view item
  217. self.root_item = TreeItem(["root"])
  218. # Create group items
  219. self.group_items = {}
  220. for kind, title in ObjectCollection.groups:
  221. item = TreeItem([title], self.icons[kind])
  222. self.group_items[kind] = item
  223. self.root_item.append_child(item)
  224. # Create test sub-items
  225. # for i in self.root_item.m_child_items:
  226. # print i.data(0)
  227. # i.append_child(TreeItem(["empty"]))
  228. # ## Data # ##
  229. self.checked_indexes = []
  230. # Names of objects that are expected to become available.
  231. # For example, when the creation of a new object will run
  232. # in the background and will complete some time in the
  233. # future. This is a way to reserve the name and to let other
  234. # tasks know that they have to wait until available.
  235. self.promises = set()
  236. # same as above only for objects that are plotted
  237. self.plot_promises = set()
  238. # ## View
  239. self.view = KeySensitiveListView(self.app)
  240. self.view.setModel(self)
  241. self.view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
  242. self.view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
  243. # self.view.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
  244. # self.view.setDragEnabled(True)
  245. # self.view.setAcceptDrops(True)
  246. # self.view.setDropIndicatorShown(True)
  247. settings = QSettings("Open Source", "FlatCAM")
  248. if settings.contains("notebook_font_size"):
  249. fsize = settings.value('notebook_font_size', type=int)
  250. else:
  251. fsize = 12
  252. font = QtGui.QFont()
  253. font.setPixelSize(fsize)
  254. font.setFamily("Seagoe UI")
  255. self.view.setFont(font)
  256. # ## GUI Events
  257. self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change)
  258. # self.view.activated.connect(self.on_item_activated)
  259. self.view.keyPressed.connect(self.app.ui.keyPressEvent)
  260. # self.view.clicked.connect(self.on_mouse_down)
  261. self.view.customContextMenuRequested.connect(self.on_menu_request)
  262. self.click_modifier = None
  263. self.update_list_signal.connect(self.on_update_list_signal)
  264. def promise(self, obj_name):
  265. FlatCAMApp.App.log.debug("Object %s has been promised." % obj_name)
  266. self.promises.add(obj_name)
  267. def has_promises(self):
  268. return len(self.promises) > 0
  269. def plot_promise(self, plot_obj_name):
  270. self.plot_promises.add(plot_obj_name)
  271. def plot_remove_promise(self, plot_obj_name):
  272. if plot_obj_name in self.plot_promises:
  273. self.plot_promises.remove(plot_obj_name)
  274. def has_plot_promises(self):
  275. return len(self.plot_promises) > 0
  276. def on_mouse_down(self, event):
  277. FlatCAMApp.App.log.debug("Mouse button pressed on list")
  278. def on_menu_request(self, pos):
  279. sel = len(self.view.selectedIndexes()) > 0
  280. self.app.ui.menuprojectenable.setEnabled(sel)
  281. self.app.ui.menuprojectdisable.setEnabled(sel)
  282. self.app.ui.menuprojectcolor.setEnabled(sel)
  283. self.app.ui.menuprojectviewsource.setEnabled(sel)
  284. self.app.ui.menuprojectcopy.setEnabled(sel)
  285. self.app.ui.menuprojectedit.setEnabled(sel)
  286. self.app.ui.menuprojectdelete.setEnabled(sel)
  287. self.app.ui.menuprojectsave.setEnabled(sel)
  288. self.app.ui.menuprojectproperties.setEnabled(sel)
  289. if sel:
  290. self.app.ui.menuprojectgeneratecnc.setVisible(True)
  291. self.app.ui.menuprojectedit.setVisible(True)
  292. self.app.ui.menuprojectsave.setVisible(True)
  293. self.app.ui.menuprojectviewsource.setVisible(True)
  294. self.app.ui.menuprojectcolor.setEnabled(False)
  295. for obj in self.get_selected():
  296. if type(obj) == FlatCAMGerber or type(obj) == FlatCAMExcellon:
  297. self.app.ui.menuprojectcolor.setEnabled(True)
  298. if type(obj) != FlatCAMGeometry:
  299. self.app.ui.menuprojectgeneratecnc.setVisible(False)
  300. if type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMGerber:
  301. self.app.ui.menuprojectedit.setVisible(False)
  302. if type(obj) != FlatCAMGerber and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMCNCjob:
  303. self.app.ui.menuprojectviewsource.setVisible(False)
  304. if type(obj) != FlatCAMGerber and type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and \
  305. type(obj) != FlatCAMCNCjob:
  306. # meaning for Scripts and for Document type of FlatCAM object
  307. self.app.ui.menuprojectenable.setVisible(False)
  308. self.app.ui.menuprojectdisable.setVisible(False)
  309. self.app.ui.menuprojectedit.setVisible(False)
  310. self.app.ui.menuprojectproperties.setVisible(False)
  311. self.app.ui.menuprojectgeneratecnc.setVisible(False)
  312. else:
  313. self.app.ui.menuprojectgeneratecnc.setVisible(False)
  314. self.app.ui.menuproject.popup(self.view.mapToGlobal(pos))
  315. def index(self, row, column=0, parent=None, *args, **kwargs):
  316. if not self.hasIndex(row, column, parent):
  317. return QtCore.QModelIndex()
  318. # if not parent.isValid():
  319. # parent_item = self.root_item
  320. # else:
  321. # parent_item = parent.internalPointer()
  322. parent_item = parent.internalPointer() if parent.isValid() else self.root_item
  323. child_item = parent_item.child(row)
  324. if child_item:
  325. return self.createIndex(row, column, child_item)
  326. else:
  327. return QtCore.QModelIndex()
  328. def parent(self, index=None):
  329. if not index.isValid():
  330. return QtCore.QModelIndex()
  331. parent_item = index.internalPointer().parent_item
  332. if parent_item == self.root_item:
  333. return QtCore.QModelIndex()
  334. return self.createIndex(parent_item.row(), 0, parent_item)
  335. def rowCount(self, index=None, *args, **kwargs):
  336. if index.column() > 0:
  337. return 0
  338. if not index.isValid():
  339. parent_item = self.root_item
  340. else:
  341. parent_item = index.internalPointer()
  342. return parent_item.child_count()
  343. def columnCount(self, index=None, *args, **kwargs):
  344. if index.isValid():
  345. return index.internalPointer().column_count()
  346. else:
  347. return self.root_item.column_count()
  348. def data(self, index, role=None):
  349. if not index.isValid():
  350. return None
  351. if role in [Qt.DisplayRole, Qt.EditRole]:
  352. obj = index.internalPointer().obj
  353. if obj:
  354. return obj.options["name"]
  355. else:
  356. return index.internalPointer().data(index.column())
  357. if role == Qt.ForegroundRole:
  358. color = QColor(self.app.defaults['global_proj_item_color'])
  359. color_disabled = QColor(self.app.defaults['global_proj_item_dis_color'])
  360. obj = index.internalPointer().obj
  361. if obj:
  362. return QtGui.QBrush(color) if obj.options["plot"] else QtGui.QBrush(color_disabled)
  363. else:
  364. return index.internalPointer().data(index.column())
  365. elif role == Qt.DecorationRole:
  366. icon = index.internalPointer().icon
  367. if icon:
  368. return icon
  369. else:
  370. return QtGui.QPixmap()
  371. elif role == Qt.ToolTipRole:
  372. try:
  373. obj = index.internalPointer().obj
  374. except AttributeError:
  375. return None
  376. if obj:
  377. text = obj.options['name']
  378. return text
  379. else:
  380. QtWidgets.QToolTip.hideText()
  381. return None
  382. else:
  383. return None
  384. def setData(self, index, data, role=None):
  385. if index.isValid():
  386. obj = index.internalPointer().obj
  387. if obj:
  388. old_name = deepcopy(obj.options['name'])
  389. new_name = str(data)
  390. if old_name != new_name and new_name != '':
  391. # rename the object
  392. obj.options["name"] = deepcopy(data)
  393. self.app.object_status_changed.emit(obj, 'rename', old_name)
  394. # update the SHELL auto-completer model data
  395. try:
  396. self.app.myKeywords.remove(old_name)
  397. self.app.myKeywords.append(new_name)
  398. self.app.shell._edit.set_model_data(self.app.myKeywords)
  399. except Exception as e:
  400. log.debug(
  401. "setData() --> Could not remove the old object name from auto-completer model list. %s" %
  402. str(e))
  403. # obj.build_ui()
  404. self.app.inform.emit(_("Object renamed from <b>{old}</b> to <b>{new}</b>").format(old=old_name,
  405. new=new_name))
  406. self.dataChanged.emit(index, index)
  407. return True
  408. else:
  409. return False
  410. def supportedDropActions(self):
  411. return Qt.MoveAction
  412. def flags(self, index):
  413. default_flags = QtCore.QAbstractItemModel.flags(self, index)
  414. if not index.isValid():
  415. return Qt.ItemIsEnabled | default_flags
  416. # Prevent groups from selection
  417. try:
  418. if not index.internalPointer().obj:
  419. return Qt.ItemIsEnabled
  420. else:
  421. return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable | \
  422. Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
  423. except AttributeError:
  424. return Qt.ItemIsEnabled
  425. # return QtWidgets.QAbstractItemModel.flags(self, index)
  426. def append(self, obj, active=False, to_index=None):
  427. FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
  428. name = obj.options["name"]
  429. # Check promises and clear if exists
  430. if name in self.promises:
  431. self.promises.remove(name)
  432. # FlatCAMApp.App.log.debug("Promised object %s became available." % name)
  433. # FlatCAMApp.App.log.debug("%d promised objects remaining." % len(self.promises))
  434. # Prevent same name
  435. while name in self.get_names():
  436. # ## Create a new name
  437. # Ends with number?
  438. FlatCAMApp.App.log.debug("new_object(): Object name (%s) exists, changing." % name)
  439. match = re.search(r'(.*[^\d])?(\d+)$', name)
  440. if match: # Yes: Increment the number!
  441. base = match.group(1) or ''
  442. num = int(match.group(2))
  443. name = base + str(num + 1)
  444. else: # No: add a number!
  445. name += "_1"
  446. obj.options["name"] = name
  447. obj.set_ui(obj.ui_type(app=self.app))
  448. # Required before appending (Qt MVC)
  449. group = self.group_items[obj.kind]
  450. group_index = self.index(group.row(), 0, QtCore.QModelIndex())
  451. if to_index is None:
  452. self.beginInsertRows(group_index, group.child_count(), group.child_count())
  453. # Append new item
  454. obj.item = TreeItem(None, self.icons[obj.kind], obj, group)
  455. # Required after appending (Qt MVC)
  456. self.endInsertRows()
  457. else:
  458. self.beginInsertRows(group_index, to_index.row()-1, to_index.row()-1)
  459. # Append new item
  460. obj.item = TreeItem(None, self.icons[obj.kind], obj, group)
  461. # Required after appending (Qt MVC)
  462. self.endInsertRows()
  463. # Expand group
  464. if group.child_count() == 1:
  465. self.view.setExpanded(group_index, True)
  466. self.app.should_we_save = True
  467. self.app.object_status_changed.emit(obj, 'append', name)
  468. # decide if to show or hide the Notebook side of the screen
  469. if self.app.defaults["global_project_autohide"] is True:
  470. # always open the notebook on object added to collection
  471. self.app.ui.splitter.setSizes([1, 1])
  472. def get_names(self):
  473. """
  474. Gets a list of the names of all objects in the collection.
  475. :return: List of names.
  476. :rtype: list
  477. """
  478. # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
  479. return [x.options['name'] for x in self.get_list()]
  480. def get_bounds(self):
  481. """
  482. Finds coordinates bounding all objects in the collection.
  483. :return: [xmin, ymin, xmax, ymax]
  484. :rtype: list
  485. """
  486. FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()")
  487. # TODO: Move the operation out of here.
  488. xmin = Inf
  489. ymin = Inf
  490. xmax = -Inf
  491. ymax = -Inf
  492. # for obj in self.object_list:
  493. for obj in self.get_list():
  494. try:
  495. gxmin, gymin, gxmax, gymax = obj.bounds()
  496. xmin = min([xmin, gxmin])
  497. ymin = min([ymin, gymin])
  498. xmax = max([xmax, gxmax])
  499. ymax = max([ymax, gymax])
  500. except Exception as e:
  501. FlatCAMApp.App.log.warning("DEV WARNING: Tried to get bounds of empty geometry. %s" % str(e))
  502. return [xmin, ymin, xmax, ymax]
  503. def get_by_name(self, name, isCaseSensitive=None):
  504. """
  505. Fetches the FlatCAMObj with the given `name`.
  506. :param name: The name of the object.
  507. :type name: str
  508. :param isCaseSensitive: whether searching of the object is done by name where the name is case sensitive
  509. :return: The requested object or None if no such object.
  510. :rtype: FlatCAMObj or None
  511. """
  512. # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()")
  513. if isCaseSensitive is None or isCaseSensitive is True:
  514. for obj in self.get_list():
  515. if obj.options['name'] == name:
  516. return obj
  517. else:
  518. for obj in self.get_list():
  519. if obj.options['name'].lower() == name.lower():
  520. return obj
  521. return None
  522. def delete_active(self, select_project=True):
  523. selections = self.view.selectedIndexes()
  524. if len(selections) == 0:
  525. return
  526. active = selections[0].internalPointer()
  527. group = active.parent_item
  528. # send signal with the object that is deleted
  529. # self.app.object_status_changed.emit(active.obj, 'delete')
  530. # some objects add a Tab on creation, close it here
  531. for idx in range(self.app.ui.plot_tab_area.count()):
  532. if self.app.ui.plot_tab_area.widget(idx).objectName() == active.obj.options['name']:
  533. self.app.ui.plot_tab_area.removeTab(idx)
  534. break
  535. # update the SHELL auto-completer model data
  536. name = active.obj.options['name']
  537. try:
  538. self.app.myKeywords.remove(name)
  539. self.app.shell._edit.set_model_data(self.app.myKeywords)
  540. # this is not needed any more because now the code editor is created on demand
  541. # self.app.ui.code_editor.set_model_data(self.app.myKeywords)
  542. except Exception as e:
  543. log.debug(
  544. "delete_active() --> Could not remove the old object name from auto-completer model list. %s" % str(e))
  545. self.app.object_status_changed.emit(active.obj, 'delete', name)
  546. # ############ OBJECT DELETION FROM MODEL STARTS HERE ####################
  547. self.beginRemoveRows(self.index(group.row(), 0, QtCore.QModelIndex()), active.row(), active.row())
  548. group.remove_child(active)
  549. # after deletion of object store the current list of objects into the self.app.all_objects_list
  550. self.app.all_objects_list = self.get_list()
  551. self.endRemoveRows()
  552. # ############ OBJECT DELETION FROM MODEL STOPS HERE ####################
  553. if self.app.is_legacy is False:
  554. self.app.plotcanvas.redraw()
  555. if select_project:
  556. # always go to the Project Tab after object deletion as it may be done with a shortcut key
  557. self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
  558. self.app.should_we_save = True
  559. # decide if to show or hide the Notebook side of the screen
  560. if self.app.defaults["global_project_autohide"] is True:
  561. # hide the notebook if there are no objects in the collection
  562. if not self.get_list():
  563. self.app.ui.splitter.setSizes([0, 1])
  564. def delete_by_name(self, name, select_project=True):
  565. obj = self.get_by_name(name=name)
  566. item = obj.item
  567. group = self.group_items[obj.kind]
  568. group_index = self.index(group.row(), 0, QtCore.QModelIndex())
  569. item_index = self.index(item.row(), 0, group_index)
  570. deleted = item_index.internalPointer()
  571. group = deleted.parent_item
  572. # some objects add a Tab on creation, close it here
  573. for idx in range(self.app.ui.plot_tab_area.count()):
  574. if self.app.ui.plot_tab_area.widget(idx).objectName() == deleted.obj.options['name']:
  575. self.app.ui.plot_tab_area.removeTab(idx)
  576. break
  577. # update the SHELL auto-completer model data
  578. name = deleted.obj.options['name']
  579. try:
  580. self.app.myKeywords.remove(name)
  581. self.app.shell._edit.set_model_data(self.app.myKeywords)
  582. # this is not needed any more because now the code editor is created on demand
  583. # self.app.ui.code_editor.set_model_data(self.app.myKeywords)
  584. except Exception as e:
  585. log.debug(
  586. "delete_by_name() --> Could not remove the old object name from auto-completer model list. %s" % str(e))
  587. self.app.object_status_changed.emit(deleted.obj, 'delete', name)
  588. # ############ OBJECT DELETION FROM MODEL STARTS HERE ####################
  589. self.beginRemoveRows(self.index(group.row(), 0, QtCore.QModelIndex()), deleted.row(), deleted.row())
  590. group.remove_child(deleted)
  591. # after deletion of object store the current list of objects into the self.app.all_objects_list
  592. self.update_list_signal.emit()
  593. self.endRemoveRows()
  594. # ############ OBJECT DELETION FROM MODEL STOPS HERE ####################
  595. if self.app.is_legacy is False:
  596. self.app.plotcanvas.redraw()
  597. if select_project:
  598. # always go to the Project Tab after object deletion as it may be done with a shortcut key
  599. self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
  600. self.app.should_we_save = True
  601. # decide if to show or hide the Notebook side of the screen
  602. if self.app.defaults["global_project_autohide"] is True:
  603. # hide the notebook if there are no objects in the collection
  604. if not self.get_list():
  605. self.app.ui.splitter.setSizes([0, 1])
  606. def on_update_list_signal(self):
  607. self.app.all_objects_list = self.get_list()
  608. def delete_all(self):
  609. FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
  610. self.app.object_status_changed.emit(None, 'delete_all', '')
  611. try:
  612. self.app.all_objects_list.clear()
  613. self.app.geo_editor.clear()
  614. self.app.exc_editor.clear()
  615. self.app.dblsidedtool.reset_fields()
  616. self.app.panelize_tool.reset_fields()
  617. self.app.cutout_tool.reset_fields()
  618. self.app.film_tool.reset_fields()
  619. self.beginResetModel()
  620. self.checked_indexes = []
  621. for group in self.root_item.child_items:
  622. group.remove_children()
  623. self.endResetModel()
  624. self.app.plotcanvas.redraw()
  625. except Exception as e:
  626. log.debug("ObjectCollection.delete_all() --> %s" % str(e))
  627. def get_active(self):
  628. """
  629. Returns the active object or None
  630. :return: FlatCAMObj or None
  631. """
  632. selections = self.view.selectedIndexes()
  633. if len(selections) == 0:
  634. return None
  635. return selections[0].internalPointer().obj
  636. def get_selected(self):
  637. """
  638. Returns list of objects selected in the view.
  639. :return: List of objects
  640. """
  641. return [sel.internalPointer().obj for sel in self.view.selectedIndexes()]
  642. def get_non_selected(self):
  643. """
  644. Returns list of objects non-selected in the view.
  645. :return: List of objects
  646. """
  647. obj_list = self.get_list()
  648. for sel in self.get_selected():
  649. obj_list.remove(sel)
  650. return obj_list
  651. def set_active(self, name):
  652. """
  653. Selects object by name from the project list. This triggers the
  654. list_selection_changed event and call on_list_selection_changed.
  655. :param name: Name of the FlatCAM Object
  656. :return: None
  657. """
  658. try:
  659. obj = self.get_by_name(name)
  660. item = obj.item
  661. group = self.group_items[obj.kind]
  662. group_index = self.index(group.row(), 0, QtCore.QModelIndex())
  663. item_index = self.index(item.row(), 0, group_index)
  664. self.view.selectionModel().select(item_index, QtCore.QItemSelectionModel.Select)
  665. except Exception as e:
  666. log.error("[ERROR] Cause: %s" % str(e))
  667. raise
  668. def set_all_active(self):
  669. """
  670. Select all objects from the project list. This triggers the
  671. list_selection_changed event and call on_list_selection_changed.
  672. :return: None
  673. """
  674. for name in self.get_names():
  675. self.set_active(name)
  676. def set_exclusive_active(self, name):
  677. """
  678. Make the object with the name in parameters the only selected object
  679. :param name: name of object to be selected and made the only active object
  680. :return: None
  681. """
  682. self.set_all_inactive()
  683. self.set_active(name)
  684. def set_inactive(self, name):
  685. """
  686. Unselect object by name from the project list. This triggers the
  687. list_selection_changed event and call on_list_selection_changed.
  688. :param name: Name of the FlatCAM Object
  689. :return: None
  690. """
  691. # log.debug("ObjectCollection.set_inactive()")
  692. obj = self.get_by_name(name)
  693. item = obj.item
  694. group = self.group_items[obj.kind]
  695. group_index = self.index(group.row(), 0, QtCore.QModelIndex())
  696. item_index = self.index(item.row(), 0, group_index)
  697. self.view.selectionModel().select(item_index, QtCore.QItemSelectionModel.Deselect)
  698. def set_all_inactive(self):
  699. """
  700. Unselect all objects from the project list. This triggers the
  701. list_selection_changed event and call on_list_selection_changed.
  702. :return: None
  703. """
  704. for name in self.get_names():
  705. self.set_inactive(name)
  706. def on_list_selection_change(self, current, previous):
  707. # FlatCAMApp.App.log.debug("on_list_selection_change()")
  708. # FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
  709. try:
  710. obj = current.indexes()[0].internalPointer().obj
  711. self.item_selected.emit(obj.options['name'])
  712. if obj.kind == 'gerber':
  713. self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
  714. color='green',
  715. name=str(obj.options['name']),
  716. tx=_("selected"))
  717. )
  718. elif obj.kind == 'excellon':
  719. self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
  720. color='brown',
  721. name=str(obj.options['name']),
  722. tx=_("selected"))
  723. )
  724. elif obj.kind == 'cncjob':
  725. self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
  726. color='blue',
  727. name=str(obj.options['name']),
  728. tx=_("selected"))
  729. )
  730. elif obj.kind == 'geometry':
  731. self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
  732. color='red',
  733. name=str(obj.options['name']),
  734. tx=_("selected"))
  735. )
  736. elif obj.kind == 'script':
  737. self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
  738. color='orange',
  739. name=str(obj.options['name']),
  740. tx=_("selected"))
  741. )
  742. elif obj.kind == 'document':
  743. self.app.inform.emit('[selected]<span style="color:{color};">{name}</span> {tx}'.format(
  744. color='darkCyan',
  745. name=str(obj.options['name']),
  746. tx=_("selected"))
  747. )
  748. except IndexError:
  749. self.item_selected.emit('none')
  750. # FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
  751. self.app.inform.emit('')
  752. try:
  753. self.app.ui.selected_scroll_area.takeWidget()
  754. except Exception as e:
  755. FlatCAMApp.App.log.debug("Nothing to remove. %s" % str(e))
  756. self.app.setup_component_editor()
  757. return
  758. if obj:
  759. obj.build_ui()
  760. def on_item_activated(self, index):
  761. """
  762. Double-click or Enter on item.
  763. :param index: Index of the item in the list.
  764. :return: None
  765. """
  766. a_idx = index.internalPointer().obj
  767. if a_idx is None:
  768. return
  769. else:
  770. try:
  771. a_idx.build_ui()
  772. except Exception as e:
  773. self.app.inform.emit('[ERROR] %s: %s' % (_("Cause of error"), str(e)))
  774. raise
  775. def get_list(self):
  776. """
  777. Will return a list of all objects currently opened.
  778. :return:
  779. """
  780. obj_list = []
  781. for group in self.root_item.child_items:
  782. for item in group.child_items:
  783. obj_list.append(item.obj)
  784. return obj_list
  785. def update_view(self):
  786. self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())