ObjectCollection.py 35 KB

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