cura_app.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2019 Ultimaker B.V.
  3. # Cura is released under the terms of the LGPLv3 or higher.
  4. import argparse
  5. import faulthandler
  6. import os
  7. import sys
  8. # Workaround for a race condition on certain systems where there
  9. # is a race condition between Arcus and PyQt. Importing Arcus
  10. # first seems to prevent Sip from going into a state where it
  11. # tries to create PyQt objects on a non-main thread.
  12. import Arcus # @UnusedImport
  13. import Savitar # @UnusedImport
  14. from UM.Platform import Platform
  15. from cura import ApplicationMetadata
  16. from cura.ApplicationMetadata import CuraAppName
  17. from cura.CrashHandler import CrashHandler
  18. try:
  19. import sentry_sdk
  20. with_sentry_sdk = True
  21. except ImportError:
  22. with_sentry_sdk = False
  23. parser = argparse.ArgumentParser(prog = "cura",
  24. add_help = False)
  25. parser.add_argument("--debug",
  26. action="store_true",
  27. default = False,
  28. help = "Turn on the debug mode by setting this option."
  29. )
  30. known_args = vars(parser.parse_known_args()[0])
  31. if with_sentry_sdk:
  32. sentry_env = "unknown" # Start off with a "IDK"
  33. if hasattr(sys, "frozen"):
  34. sentry_env = "production" # A frozen build has the posibility to be a "real" distribution.
  35. if ApplicationMetadata.CuraVersion == "master":
  36. sentry_env = "development" # Master is always a development version.
  37. elif ApplicationMetadata.CuraVersion in ["beta", "BETA"]:
  38. sentry_env = "beta"
  39. try:
  40. if ApplicationMetadata.CuraVersion.split(".")[2] == "99":
  41. sentry_env = "nightly"
  42. except IndexError:
  43. pass
  44. sentry_sdk.init("https://5034bf0054fb4b889f82896326e79b13@sentry.io/1821564",
  45. before_send = CrashHandler.sentryBeforeSend,
  46. environment = sentry_env,
  47. release = "cura%s" % ApplicationMetadata.CuraVersion,
  48. default_integrations = False,
  49. max_breadcrumbs = 300,
  50. server_name = "cura")
  51. if not known_args["debug"]:
  52. def get_cura_dir_path():
  53. if Platform.isWindows():
  54. appdata_path = os.getenv("APPDATA")
  55. if not appdata_path: #Defensive against the environment variable missing (should never happen).
  56. appdata_path = "."
  57. return os.path.join(appdata_path, CuraAppName)
  58. elif Platform.isLinux():
  59. return os.path.expanduser("~/.local/share/" + CuraAppName)
  60. elif Platform.isOSX():
  61. return os.path.expanduser("~/Library/Logs/" + CuraAppName)
  62. # Do not redirect stdout and stderr to files if we are running CLI.
  63. if hasattr(sys, "frozen") and "cli" not in os.path.basename(sys.argv[0]).lower():
  64. dirpath = get_cura_dir_path()
  65. os.makedirs(dirpath, exist_ok = True)
  66. sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8")
  67. sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w", encoding = "utf-8")
  68. # WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612
  69. if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX
  70. # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
  71. # The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
  72. try:
  73. import ctypes
  74. from ctypes.util import find_library
  75. libGL = find_library("GL")
  76. ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL)
  77. except:
  78. # GLES-only systems (e.g. ARM Mali) do not have libGL, ignore error
  79. pass
  80. # When frozen, i.e. installer version, don't let PYTHONPATH mess up the search path for DLLs.
  81. if Platform.isWindows() and hasattr(sys, "frozen"):
  82. try:
  83. del os.environ["PYTHONPATH"]
  84. except KeyError:
  85. pass
  86. # GITHUB issue #6194: https://github.com/Ultimaker/Cura/issues/6194
  87. # With AppImage 2 on Linux, the current working directory will be somewhere in /tmp/<rand>/usr, which is owned
  88. # by root. For some reason, QDesktopServices.openUrl() requires to have a usable current working directory,
  89. # otherwise it doesn't work. This is a workaround on Linux that before we call QDesktopServices.openUrl(), we
  90. # switch to a directory where the user has the ownership.
  91. if Platform.isLinux() and hasattr(sys, "frozen"):
  92. os.chdir(os.path.expanduser("~"))
  93. # WORKAROUND: GITHUB-704 GITHUB-708
  94. # It looks like setuptools creates a .pth file in
  95. # the default /usr/lib which causes the default site-packages
  96. # to be inserted into sys.path before PYTHONPATH.
  97. # This can cause issues such as having libsip loaded from
  98. # the system instead of the one provided with Cura, which causes
  99. # incompatibility issues with libArcus
  100. if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is used
  101. PYTHONPATH = os.environ["PYTHONPATH"].split(os.pathsep) # Get the value, split it..
  102. PYTHONPATH.reverse() # and reverse it, because we always insert at 1
  103. for PATH in PYTHONPATH: # Now beginning with the last PATH
  104. PATH_real = os.path.realpath(PATH) # Making the the path "real"
  105. if PATH_real in sys.path: # This should always work, but keep it to be sure..
  106. sys.path.remove(PATH_real)
  107. sys.path.insert(1, PATH_real) # Insert it at 1 after os.curdir, which is 0.
  108. def exceptHook(hook_type, value, traceback):
  109. from cura.CrashHandler import CrashHandler
  110. from cura.CuraApplication import CuraApplication
  111. has_started = False
  112. if CuraApplication.Created:
  113. has_started = CuraApplication.getInstance().started
  114. #
  115. # When the exception hook is triggered, the QApplication may not have been initialized yet. In this case, we don't
  116. # have an QApplication to handle the event loop, which is required by the Crash Dialog.
  117. # The flag "CuraApplication.Created" is set to True when CuraApplication finishes its constructor call.
  118. #
  119. # Before the "started" flag is set to True, the Qt event loop has not started yet. The event loop is a blocking
  120. # call to the QApplication.exec_(). In this case, we need to:
  121. # 1. Remove all scheduled events so no more unnecessary events will be processed, such as loading the main dialog,
  122. # loading the machine, etc.
  123. # 2. Start the Qt event loop with exec_() and show the Crash Dialog.
  124. #
  125. # If the application has finished its initialization and was running fine, and then something causes a crash,
  126. # we run the old routine to show the Crash Dialog.
  127. #
  128. from PyQt5.Qt import QApplication
  129. if CuraApplication.Created:
  130. _crash_handler = CrashHandler(hook_type, value, traceback, has_started)
  131. if CuraApplication.splash is not None:
  132. CuraApplication.splash.close()
  133. if not has_started:
  134. CuraApplication.getInstance().removePostedEvents(None)
  135. _crash_handler.early_crash_dialog.show()
  136. sys.exit(CuraApplication.getInstance().exec_())
  137. else:
  138. _crash_handler.show()
  139. else:
  140. application = QApplication(sys.argv)
  141. application.removePostedEvents(None)
  142. _crash_handler = CrashHandler(hook_type, value, traceback, has_started)
  143. # This means the QtApplication could be created and so the splash screen. Then Cura closes it
  144. if CuraApplication.splash is not None:
  145. CuraApplication.splash.close()
  146. _crash_handler.early_crash_dialog.show()
  147. sys.exit(application.exec_())
  148. # Set exception hook to use the crash dialog handler
  149. sys.excepthook = exceptHook
  150. # Enable dumping traceback for all threads
  151. if sys.stderr:
  152. faulthandler.enable(file = sys.stderr, all_threads = True)
  153. else:
  154. faulthandler.enable(file = sys.stdout, all_threads = True)
  155. from cura.CuraApplication import CuraApplication
  156. # WORKAROUND: CURA-6739
  157. # The CTM file loading module in Trimesh requires the OpenCTM library to be dynamically loaded. It uses
  158. # ctypes.util.find_library() to find libopenctm.dylib, but this doesn't seem to look in the ".app" application folder
  159. # on Mac OS X. Adding the search path to environment variables such as DYLD_LIBRARY_PATH and DYLD_FALLBACK_LIBRARY_PATH
  160. # makes it work. The workaround here uses DYLD_FALLBACK_LIBRARY_PATH.
  161. if Platform.isOSX() and getattr(sys, "frozen", False):
  162. old_env = os.environ.get("DYLD_FALLBACK_LIBRARY_PATH", "")
  163. # This is where libopenctm.so is in the .app folder.
  164. search_path = os.path.join(CuraApplication.getInstallPrefix(), "MacOS")
  165. path_list = old_env.split(":")
  166. if search_path not in path_list:
  167. path_list.append(search_path)
  168. os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = ":".join(path_list)
  169. import trimesh.exchange.load
  170. os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = old_env
  171. # WORKAROUND: CURA-6739
  172. # Similar CTM file loading fix for Linux, but NOTE THAT this doesn't work directly with Python 3.5.7. There's a fix
  173. # for ctypes.util.find_library() in Python 3.6 and 3.7. That fix makes sure that find_library() will check
  174. # LD_LIBRARY_PATH. With Python 3.5, that fix needs to be backported to make this workaround work.
  175. if Platform.isLinux() and getattr(sys, "frozen", False):
  176. old_env = os.environ.get("LD_LIBRARY_PATH", "")
  177. # This is where libopenctm.so is in the AppImage.
  178. search_path = os.path.join(CuraApplication.getInstallPrefix(), "bin")
  179. path_list = old_env.split(":")
  180. if search_path not in path_list:
  181. path_list.append(search_path)
  182. os.environ["LD_LIBRARY_PATH"] = ":".join(path_list)
  183. import trimesh.exchange.load
  184. os.environ["LD_LIBRARY_PATH"] = old_env
  185. app = CuraApplication()
  186. app.run()