CuraApplication.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from UM.Qt.QtApplication import QtApplication
  4. from UM.Scene.SceneNode import SceneNode
  5. from UM.Scene.Camera import Camera
  6. from UM.Scene.Platform import Platform
  7. from UM.Math.Vector import Vector
  8. from UM.Math.Quaternion import Quaternion
  9. from UM.Math.AxisAlignedBox import AxisAlignedBox
  10. from UM.Resources import Resources
  11. from UM.Scene.ToolHandle import ToolHandle
  12. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  13. from UM.Mesh.ReadMeshJob import ReadMeshJob
  14. from UM.Logger import Logger
  15. from UM.Preferences import Preferences
  16. from UM.JobQueue import JobQueue
  17. from UM.Scene.Selection import Selection
  18. from UM.Scene.GroupDecorator import GroupDecorator
  19. from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
  20. from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
  21. from UM.Operations.GroupedOperation import GroupedOperation
  22. from UM.Operations.SetTransformOperation import SetTransformOperation
  23. from UM.i18n import i18nCatalog
  24. from . import PlatformPhysics
  25. from . import BuildVolume
  26. from . import CameraAnimation
  27. from . import PrintInformation
  28. from . import CuraActions
  29. from . import MultiMaterialDecorator
  30. from . import ZOffsetDecorator
  31. from . import CuraSplashScreen
  32. from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
  33. from PyQt5.QtGui import QColor, QIcon
  34. from PyQt5.QtQml import qmlRegisterUncreatableType
  35. import platform
  36. import sys
  37. import os.path
  38. import numpy
  39. import copy
  40. numpy.seterr(all="ignore")
  41. #WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612
  42. if platform.system() == "Linux": # Needed for platform.linux_distribution, which is not available on Windows and OSX
  43. # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
  44. if platform.linux_distribution()[0] in ("Ubuntu", ): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
  45. import ctypes
  46. from ctypes.util import find_library
  47. ctypes.CDLL(find_library('GL'), ctypes.RTLD_GLOBAL)
  48. try:
  49. from cura.CuraVersion import CuraVersion
  50. except ImportError:
  51. CuraVersion = "master" # [CodeStyle: Reflecting imported value]
  52. class CuraApplication(QtApplication):
  53. class ResourceTypes:
  54. QmlFiles = Resources.UserType + 1
  55. Firmware = Resources.UserType + 2
  56. Q_ENUMS(ResourceTypes)
  57. def __init__(self):
  58. Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources"))
  59. if not hasattr(sys, "frozen"):
  60. Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
  61. self._open_file_queue = [] # Files to open when plug-ins are loaded.
  62. super().__init__(name = "cura", version = CuraVersion)
  63. self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
  64. self.setRequiredPlugins([
  65. "CuraEngineBackend",
  66. "MeshView",
  67. "LayerView",
  68. "STLReader",
  69. "SelectionTool",
  70. "CameraTool",
  71. "GCodeWriter",
  72. "LocalFileOutputDevice"
  73. ])
  74. self._physics = None
  75. self._volume = None
  76. self._platform = None
  77. self._output_devices = {}
  78. self._print_information = None
  79. self._i18n_catalog = None
  80. self._previous_active_tool = None
  81. self._platform_activity = False
  82. self._scene_boundingbox = AxisAlignedBox.Null
  83. self._job_name = None
  84. self._center_after_select = False
  85. self._camera_animation = None
  86. self._cura_actions = None
  87. self.getMachineManager().activeMachineInstanceChanged.connect(self._onActiveMachineChanged)
  88. self.getMachineManager().addMachineRequested.connect(self._onAddMachineRequested)
  89. self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
  90. self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
  91. Resources.addType(self.ResourceTypes.QmlFiles, "qml")
  92. Resources.addType(self.ResourceTypes.Firmware, "firmware")
  93. Preferences.getInstance().addPreference("cura/active_machine", "")
  94. Preferences.getInstance().addPreference("cura/active_mode", "simple")
  95. Preferences.getInstance().addPreference("cura/recent_files", "")
  96. Preferences.getInstance().addPreference("cura/categories_expanded", "")
  97. Preferences.getInstance().addPreference("view/center_on_select", True)
  98. Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
  99. Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
  100. Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode")
  101. JobQueue.getInstance().jobFinished.connect(self._onJobFinished)
  102. self._recent_files = []
  103. files = Preferences.getInstance().getValue("cura/recent_files").split(";")
  104. for f in files:
  105. if not os.path.isfile(f):
  106. continue
  107. self._recent_files.append(QUrl.fromLocalFile(f))
  108. @pyqtSlot(result = QUrl)
  109. def getDefaultPath(self):
  110. return QUrl.fromLocalFile(os.path.expanduser("~/"))
  111. ## Handle loading of all plugin types (and the backend explicitly)
  112. # \sa PluginRegistery
  113. def _loadPlugins(self):
  114. self._plugin_registry.addPluginLocation(os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
  115. if not hasattr(sys, "frozen"):
  116. self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))
  117. self._plugin_registry.loadPlugin("ConsoleLogger")
  118. self._plugin_registry.loadPlugin("CuraEngineBackend")
  119. self._plugin_registry.loadPlugins()
  120. if self.getBackend() == None:
  121. raise RuntimeError("Could not load the backend plugin!")
  122. self._plugins_loaded = True
  123. def addCommandLineOptions(self, parser):
  124. super().addCommandLineOptions(parser)
  125. parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
  126. parser.add_argument("--debug", dest="debug-mode", action="store_true", default=False, help="Enable detailed crash reports.")
  127. def run(self):
  128. self._i18n_catalog = i18nCatalog("cura");
  129. i18nCatalog.setTagReplacements({
  130. "filename": "font color=\"black\"",
  131. "message": "font color=UM.Theme.colors.message_text;",
  132. })
  133. self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
  134. controller = self.getController()
  135. controller.setActiveView("SolidView")
  136. controller.setCameraTool("CameraTool")
  137. controller.setSelectionTool("SelectionTool")
  138. t = controller.getTool("TranslateTool")
  139. if t:
  140. t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.YAxis,ToolHandle.ZAxis])
  141. Selection.selectionChanged.connect(self.onSelectionChanged)
  142. root = controller.getScene().getRoot()
  143. self._platform = Platform(root)
  144. self._volume = BuildVolume.BuildVolume(root)
  145. self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
  146. self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)
  147. camera = Camera("3d", root)
  148. camera.setPosition(Vector(-80, 250, 700))
  149. camera.setPerspective(True)
  150. camera.lookAt(Vector(0, 0, 0))
  151. controller.getScene().setActiveCamera("3d")
  152. self.getController().getTool("CameraTool").setOrigin(Vector(0, 100, 0))
  153. self._camera_animation = CameraAnimation.CameraAnimation()
  154. self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
  155. self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
  156. self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
  157. self.initializeEngine()
  158. if self._engine.rootObjects:
  159. self.closeSplash()
  160. for file in self.getCommandLineOption("file", []):
  161. self._openFile(file)
  162. for file_name in self._open_file_queue: #Open all the files that were queued up while plug-ins were loading.
  163. self._openFile(file_name)
  164. self.exec_()
  165. ## Handle Qt events
  166. def event(self, event):
  167. if event.type() == QEvent.FileOpen:
  168. if self._plugins_loaded:
  169. self._openFile(event.file())
  170. else:
  171. self._open_file_queue.append(event.file())
  172. return super().event(event)
  173. ## Get print information (duration / material used)
  174. def getPrintInformation(self):
  175. return self._print_information
  176. def registerObjects(self, engine):
  177. engine.rootContext().setContextProperty("Printer", self)
  178. self._print_information = PrintInformation.PrintInformation()
  179. engine.rootContext().setContextProperty("PrintInformation", self._print_information)
  180. self._cura_actions = CuraActions.CuraActions(self)
  181. engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
  182. qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
  183. def onSelectionChanged(self):
  184. if Selection.hasSelection():
  185. if not self.getController().getActiveTool():
  186. if self._previous_active_tool:
  187. self.getController().setActiveTool(self._previous_active_tool)
  188. self._previous_active_tool = None
  189. else:
  190. self.getController().setActiveTool("TranslateTool")
  191. if Preferences.getInstance().getValue("view/center_on_select"):
  192. self._center_after_select = True
  193. else:
  194. if self.getController().getActiveTool():
  195. self._previous_active_tool = self.getController().getActiveTool().getPluginId()
  196. self.getController().setActiveTool(None)
  197. else:
  198. self._previous_active_tool = None
  199. def _onToolOperationStopped(self, event):
  200. if self._center_after_select:
  201. self._center_after_select = False
  202. self._camera_animation.setStart(self.getController().getTool("CameraTool").getOrigin())
  203. self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
  204. self._camera_animation.start()
  205. requestAddPrinter = pyqtSignal()
  206. activityChanged = pyqtSignal()
  207. sceneBoundingBoxChanged = pyqtSignal()
  208. @pyqtProperty(bool, notify = activityChanged)
  209. def getPlatformActivity(self):
  210. return self._platform_activity
  211. @pyqtProperty(str, notify = sceneBoundingBoxChanged)
  212. def getSceneBoundingBoxString(self):
  213. return self._i18n_catalog.i18nc("@info", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_boundingbox.width.item(), 'depth': self._scene_boundingbox.depth.item(), 'height' : self._scene_boundingbox.height.item()}
  214. def updatePlatformActivity(self, node = None):
  215. count = 0
  216. scene_boundingbox = None
  217. for node in DepthFirstIterator(self.getController().getScene().getRoot()):
  218. if type(node) is not SceneNode or not node.getMeshData():
  219. continue
  220. count += 1
  221. if not scene_boundingbox:
  222. scene_boundingbox = copy.deepcopy(node.getBoundingBox())
  223. else:
  224. scene_boundingbox += node.getBoundingBox()
  225. if not scene_boundingbox:
  226. scene_boundingbox = AxisAlignedBox.Null
  227. if repr(self._scene_boundingbox) != repr(scene_boundingbox):
  228. self._scene_boundingbox = scene_boundingbox
  229. self.sceneBoundingBoxChanged.emit()
  230. self._platform_activity = True if count > 0 else False
  231. self.activityChanged.emit()
  232. @pyqtSlot(str)
  233. def setJobName(self, name):
  234. name = os.path.splitext(name)[0] #when a file is opened using the terminal; the filename comes from _onFileLoaded and still contains its extension. This cuts the extension off if nescessary.
  235. if self._job_name != name:
  236. self._job_name = name
  237. self.jobNameChanged.emit()
  238. jobNameChanged = pyqtSignal()
  239. @pyqtProperty(str, notify = jobNameChanged)
  240. def jobName(self):
  241. return self._job_name
  242. # Remove all selected objects from the scene.
  243. @pyqtSlot()
  244. def deleteSelection(self):
  245. if not self.getController().getToolsEnabled():
  246. return
  247. op = GroupedOperation()
  248. nodes = Selection.getAllSelectedObjects()
  249. for node in nodes:
  250. op.addOperation(RemoveSceneNodeOperation(node))
  251. op.push()
  252. pass
  253. ## Remove an object from the scene.
  254. # Note that this only removes an object if it is selected.
  255. @pyqtSlot("quint64")
  256. def deleteObject(self, object_id):
  257. if not self.getController().getToolsEnabled():
  258. return
  259. node = self.getController().getScene().findObject(object_id)
  260. if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
  261. node = Selection.getSelectedObject(0)
  262. if node:
  263. if node.getParent():
  264. group_node = node.getParent()
  265. if not group_node.callDecoration("isGroup"):
  266. op = RemoveSceneNodeOperation(node)
  267. else:
  268. while group_node.getParent().callDecoration("isGroup"):
  269. group_node = group_node.getParent()
  270. op = RemoveSceneNodeOperation(group_node)
  271. op.push()
  272. ## Create a number of copies of existing object.
  273. @pyqtSlot("quint64", int)
  274. def multiplyObject(self, object_id, count):
  275. node = self.getController().getScene().findObject(object_id)
  276. if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
  277. node = Selection.getSelectedObject(0)
  278. if node:
  279. op = GroupedOperation()
  280. for _ in range(count):
  281. if node.getParent() and node.getParent().callDecoration("isGroup"):
  282. new_node = copy.deepcopy(node.getParent()) #Copy the group node.
  283. new_node.callDecoration("setConvexHull",None)
  284. op.addOperation(AddSceneNodeOperation(new_node,node.getParent().getParent()))
  285. else:
  286. new_node = copy.deepcopy(node)
  287. new_node.callDecoration("setConvexHull", None)
  288. op.addOperation(AddSceneNodeOperation(new_node, node.getParent()))
  289. op.push()
  290. ## Center object on platform.
  291. @pyqtSlot("quint64")
  292. def centerObject(self, object_id):
  293. node = self.getController().getScene().findObject(object_id)
  294. if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
  295. node = Selection.getSelectedObject(0)
  296. if not node:
  297. return
  298. if node.getParent() and node.getParent().callDecoration("isGroup"):
  299. node = node.getParent()
  300. if node:
  301. op = SetTransformOperation(node, Vector())
  302. op.push()
  303. ## Delete all mesh data on the scene.
  304. @pyqtSlot()
  305. def deleteAll(self):
  306. if not self.getController().getToolsEnabled():
  307. return
  308. nodes = []
  309. for node in DepthFirstIterator(self.getController().getScene().getRoot()):
  310. if type(node) is not SceneNode:
  311. continue
  312. if not node.getMeshData() and not node.callDecoration("isGroup"):
  313. continue #Node that doesnt have a mesh and is not a group.
  314. if node.getParent() and node.getParent().callDecoration("isGroup"):
  315. continue #Grouped nodes don't need resetting as their parent (the group) is resetted)
  316. nodes.append(node)
  317. if nodes:
  318. op = GroupedOperation()
  319. for node in nodes:
  320. op.addOperation(RemoveSceneNodeOperation(node))
  321. op.push()
  322. ## Reset all translation on nodes with mesh data.
  323. @pyqtSlot()
  324. def resetAllTranslation(self):
  325. nodes = []
  326. for node in DepthFirstIterator(self.getController().getScene().getRoot()):
  327. if type(node) is not SceneNode:
  328. continue
  329. if not node.getMeshData() and not node.callDecoration("isGroup"):
  330. continue #Node that doesnt have a mesh and is not a group.
  331. if node.getParent() and node.getParent().callDecoration("isGroup"):
  332. continue #Grouped nodes don't need resetting as their parent (the group) is resetted)
  333. nodes.append(node)
  334. if nodes:
  335. op = GroupedOperation()
  336. for node in nodes:
  337. node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
  338. op.addOperation(SetTransformOperation(node, Vector(0,0,0)))
  339. op.push()
  340. ## Reset all transformations on nodes with mesh data.
  341. @pyqtSlot()
  342. def resetAll(self):
  343. nodes = []
  344. for node in DepthFirstIterator(self.getController().getScene().getRoot()):
  345. if type(node) is not SceneNode:
  346. continue
  347. if not node.getMeshData() and not node.callDecoration("isGroup"):
  348. continue #Node that doesnt have a mesh and is not a group.
  349. if node.getParent() and node.getParent().callDecoration("isGroup"):
  350. continue #Grouped nodes don't need resetting as their parent (the group) is resetted)
  351. nodes.append(node)
  352. if nodes:
  353. op = GroupedOperation()
  354. for node in nodes:
  355. # Ensure that the object is above the build platform
  356. node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
  357. op.addOperation(SetTransformOperation(node, Vector(0,0,0), Quaternion(), Vector(1, 1, 1)))
  358. op.push()
  359. ## Reload all mesh data on the screen from file.
  360. @pyqtSlot()
  361. def reloadAll(self):
  362. nodes = []
  363. for node in DepthFirstIterator(self.getController().getScene().getRoot()):
  364. if type(node) is not SceneNode or not node.getMeshData():
  365. continue
  366. nodes.append(node)
  367. if not nodes:
  368. return
  369. for node in nodes:
  370. if not node.getMeshData():
  371. continue
  372. file_name = node.getMeshData().getFileName()
  373. if file_name:
  374. job = ReadMeshJob(file_name)
  375. job._node = node
  376. job.finished.connect(self._reloadMeshFinished)
  377. job.start()
  378. ## Get logging data of the backend engine
  379. # \returns \type{string} Logging data
  380. @pyqtSlot(result=str)
  381. def getEngineLog(self):
  382. log = ""
  383. for entry in self.getBackend().getLog():
  384. log += entry.decode()
  385. return log
  386. recentFilesChanged = pyqtSignal()
  387. @pyqtProperty("QVariantList", notify = recentFilesChanged)
  388. def recentFiles(self):
  389. return self._recent_files
  390. @pyqtSlot("QStringList")
  391. def setExpandedCategories(self, categories):
  392. categories = list(set(categories))
  393. categories.sort()
  394. joined = ";".join(categories)
  395. if joined != Preferences.getInstance().getValue("cura/categories_expanded"):
  396. Preferences.getInstance().setValue("cura/categories_expanded", joined)
  397. self.expandedCategoriesChanged.emit()
  398. expandedCategoriesChanged = pyqtSignal()
  399. @pyqtProperty("QStringList", notify = expandedCategoriesChanged)
  400. def expandedCategories(self):
  401. return Preferences.getInstance().getValue("cura/categories_expanded").split(";")
  402. @pyqtSlot(str, result = "QVariant")
  403. def getSettingValue(self, key):
  404. if not self.getMachineManager().getWorkingProfile():
  405. return None
  406. return self.getMachineManager().getWorkingProfile().getSettingValue(key)
  407. #return self.getActiveMachine().getSettingValueByKey(key)
  408. ## Change setting by key value pair
  409. @pyqtSlot(str, "QVariant")
  410. def setSettingValue(self, key, value):
  411. if not self.getMachineManager().getWorkingProfile():
  412. return
  413. self.getMachineManager().getWorkingProfile().setSettingValue(key, value)
  414. @pyqtSlot()
  415. def mergeSelected(self):
  416. self.groupSelected()
  417. try:
  418. group_node = Selection.getAllSelectedObjects()[0]
  419. except Exception as e:
  420. Logger.log("d", "mergeSelected: Exception:", e)
  421. return
  422. multi_material_decorator = MultiMaterialDecorator.MultiMaterialDecorator()
  423. group_node.addDecorator(multi_material_decorator)
  424. # Reset the position of each node
  425. for node in group_node.getChildren():
  426. new_position = node.getMeshData().getCenterPosition()
  427. new_position = new_position.scale(node.getScale())
  428. node.setPosition(new_position)
  429. # Use the previously found center of the group bounding box as the new location of the group
  430. group_node.setPosition(group_node.getBoundingBox().center)
  431. @pyqtSlot()
  432. def groupSelected(self):
  433. group_node = SceneNode()
  434. group_decorator = GroupDecorator()
  435. group_node.addDecorator(group_decorator)
  436. group_node.setParent(self.getController().getScene().getRoot())
  437. group_node.setSelectable(True)
  438. center = Selection.getSelectionCenter()
  439. group_node.setPosition(center)
  440. group_node.setCenterPosition(center)
  441. for node in Selection.getAllSelectedObjects():
  442. world = node.getWorldPosition()
  443. node.setParent(group_node)
  444. node.setPosition(world - center)
  445. for node in group_node.getChildren():
  446. Selection.remove(node)
  447. Selection.add(group_node)
  448. @pyqtSlot()
  449. def ungroupSelected(self):
  450. ungrouped_nodes = []
  451. selected_objects = Selection.getAllSelectedObjects()[:] #clone the list
  452. for node in selected_objects:
  453. if node.callDecoration("isGroup" ):
  454. children_to_move = []
  455. for child in node.getChildren():
  456. if type(child) is SceneNode:
  457. children_to_move.append(child)
  458. for child in children_to_move:
  459. position = child.getWorldPosition()
  460. child.setParent(node.getParent())
  461. child.setPosition(position - node.getParent().getWorldPosition())
  462. child.scale(node.getScale())
  463. child.rotate(node.getOrientation())
  464. Selection.add(child)
  465. child.callDecoration("setConvexHull",None)
  466. node.setParent(None)
  467. ungrouped_nodes.append(node)
  468. for node in ungrouped_nodes:
  469. Selection.remove(node)
  470. def _createSplashScreen(self):
  471. return CuraSplashScreen.CuraSplashScreen()
  472. def _onActiveMachineChanged(self):
  473. pass
  474. def _onFileLoaded(self, job):
  475. node = job.getResult()
  476. if node != None:
  477. self.setJobName(os.path.basename(job.getFileName()))
  478. node.setSelectable(True)
  479. node.setName(os.path.basename(job.getFileName()))
  480. op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
  481. op.push()
  482. self.getController().getScene().sceneChanged.emit(node) #Force scene change.
  483. def _onJobFinished(self, job):
  484. if type(job) is not ReadMeshJob or not job.getResult():
  485. return
  486. f = QUrl.fromLocalFile(job.getFileName())
  487. if f in self._recent_files:
  488. self._recent_files.remove(f)
  489. self._recent_files.insert(0, f)
  490. if len(self._recent_files) > 10:
  491. del self._recent_files[10]
  492. pref = ""
  493. for path in self._recent_files:
  494. pref += path.toLocalFile() + ";"
  495. Preferences.getInstance().setValue("cura/recent_files", pref)
  496. self.recentFilesChanged.emit()
  497. def _reloadMeshFinished(self, job):
  498. # TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
  499. job._node.setMeshData(job.getResult().getMeshData())
  500. #job.getResult().setParent(self.getController().getScene().getRoot())
  501. #job._node.setParent(self.getController().getScene().getRoot())
  502. #job._node.meshDataChanged.emit(job._node)
  503. def _openFile(self, file):
  504. job = ReadMeshJob(os.path.abspath(file))
  505. job.finished.connect(self._onFileLoaded)
  506. job.start()
  507. def _onAddMachineRequested(self):
  508. self.requestAddPrinter.emit()