def register(app): plugin_data = {} if Platform.isWindows(): reader = SolidWorksReader.SolidWorksReader() if reader.isOperational(): plugin_data["mesh_reader"] = reader else: no_valid_installation_message = Message( i18n_catalog.i18nc( "@info:status", "Dear customer,\nWe could not find a valid installation of SolidWorks on your system. That means that either SolidWorks is not installed or you don't own an valid license. Please make sure that running SolidWorks itself works without issues and/or contact your ICT.\n\nWith kind regards\n - Thomas Karl Pietrowski" ), 0) no_valid_installation_message.setTitle("SolidWorks plugin") no_valid_installation_message.show() plugin_data[ "extension"] = SolidWorksDialogHandler.SolidWorksDialogHandler( reader) else: not_correct_os_message = Message( i18n_catalog.i18nc( "@info:status", "Dear customer,\nYou are currently running this plugin on an operating system other than Windows. This plugin will only work on Windows with SolidWorks installed, including an valid license. Please install this plugin on a Windows machine with SolidWorks installed.\n\nWith kind regards\n - Thomas Karl Pietrowski" ), 0) not_correct_os_message.setTitle("SolidWorks plugin") not_correct_os_message.show() return plugin_data
def run(self) -> None: if not self.job_type: error_message = Message() error_message.setTitle("Smart Slice") error_message.setText(i18n_catalog.i18nc("@info:status", "Job type not set for processing:\nDon't know what to do!")) error_message.show() self.connector.cancelCurrentJob() Job.yieldThread() # Should allow the UI to update earlier try: job = self.prepareJob() Logger.log("i", "Smart Slice job prepared") except SmartSliceCloudJob.JobException as exc: Logger.log("w", "Smart Slice job cannot be prepared: {}".format(exc.problem)) self.setError(exc) return task = self.processCloudJob(job) try: os.remove(job) except: Logger.log("w", "Unable to remove temporary 3MF {}".format(job)) if task and task.result: self._result = task.result
def test_gettersAndSetters(): message = Message(text = "OMG", title="YAY", image_caption="DERP", image_source= "HERP", option_text="FOO", option_state= False) message.setMaxProgress(200) assert message.getText() == "OMG" assert message.getTitle() == "YAY" assert message.getImageCaption() == "DERP" assert message.getImageSource() == "HERP" assert message.getOptionText() == "FOO" assert message.getMaxProgress() == 200 assert message.getOptionState() == False message.setTitle("whoo") assert message.getTitle() == "whoo"
def test_gettersAndSetters(): message = Message(text="OMG", title="YAY", image_caption="DERP", image_source="HERP", option_text="FOO", option_state=False, message_type=Message.MessageType.POSITIVE) message.setMaxProgress(200) assert message.getText() == "OMG" assert message.getTitle() == "YAY" assert message.getImageCaption() == "DERP" assert message.getImageSource() == "HERP" assert message.getOptionText() == "FOO" assert message.getMaxProgress() == 200 assert message.getOptionState() == False message.setTitle("whoo") assert message.getTitle() == "whoo"
def openForeignFile(self, options): open_file_paths = self.getOpenDocumentPaths(options) # SolidWorks API: X options["sw_previous_active_file"] = options["app_instance"].ActiveDoc # If the file has not been loaded open it! if not os.path.normpath(options["foreignFile"]) in open_file_paths: Logger.log("d", "Opening the foreign file!") if options["foreignFormat"].upper() == self._extension_part: filetype = SolidWorksEnums.swDocumentTypes_e.swDocPART elif options["foreignFormat"].upper() == self._extension_assembly: filetype = SolidWorksEnums.swDocumentTypes_e.swDocASSEMBLY elif options["foreignFormat"].upper() == self._extension_drawing: filetype = SolidWorksEnums.swDocumentTypes_e.swDocDRAWING else: raise NotImplementedError( "Unknown extension. Something went terribly wrong!") # SolidWorks API: 2008 FCS (Rev 16.0) documentSpecification = options["app_instance"].GetOpenDocSpec( options["foreignFile"]) ## NOTE: SPEC: FileName #documentSpecification.FileName ## NOTE: SPEC: DocumentType ## TODO: Really needed here?! documentSpecification.DocumentType = filetype ## TODO: Test the impact of LightWeight = True #documentSpecification.LightWeight = True documentSpecification.Silent = True ## TODO: Double check, whether file was really opened read-only.. documentSpecification.ReadOnly = True documentSpecificationObject = ComConnector.GetComObject( documentSpecification) # SolidWorks API: 2008 FCS (Rev 16.0) options["sw_model"] = options["app_instance"].OpenDoc7( documentSpecificationObject) if documentSpecification.Warning: Logger.log( "w", "Warnings happened while opening your SolidWorks file!") if documentSpecification.Error: Logger.log( "e", "Errors happened while opening your SolidWorks file!") error_message = Message( i18n_catalog.i18nc( "@info:status", "SolidWorks reported errors, while opening your file. We recommend to solve these issues inside SolidWorks itself." )) error_message.setTitle("SolidWorks plugin") error_message.show() options["sw_opened_file"] = True else: Logger.log("d", "Foreign file has already been opened!") options["sw_model"] = self.getOpenDocumentFilepathDict(options)[ os.path.normpath(options["foreignFile"])] options["sw_opened_file"] = False if options["foreignFormat"].upper() == self._extension_drawing: count_of_documents = self.countDocumentsInDrawing(options) if count_of_documents == 0: error_message = Message( i18n_catalog.i18nc( "@info:status", "Found no models inside your drawing. Could you please check it's content again and make sure one part or assembly is inside?\n\n Thanks!." )) error_message.setTitle("SolidWorks plugin") error_message.show() elif count_of_documents > 1: error_message = Message( i18n_catalog.i18nc( "@info:status", "Found more then one part or assembly inside your drawing. We currently only support drawings with exactly one part or assembly inside.\n\nSorry!" )) error_message.setTitle("SolidWorks plugin") error_message.show() else: options["sw_drawing"] = options["sw_model"] options["sw_drawing_opened"] = options["sw_opened_file"] options["foreignFile"] = self.getDocumentsInDrawing(options)[0] options["foreignFormat"] = os.path.splitext( options["foreignFile"])[1] self.activatePreviousFile(options) options = self.openForeignFile(options) error = ComConnector.getByVarInt() # SolidWorks API: >= 20.0.x # SolidWorks API: 2001Plus FCS (Rev. 10.0) - GetTitle options["app_instance"].ActivateDoc3( options["sw_model"].GetTitle, True, SolidWorksEnums.swRebuildOnActivation_e.swDontRebuildActiveDoc, error, ) # Might be useful in the future, but no need for this ATM #self.configuration = options["sw_model"].getActiveConfiguration #self.root_component = self.configuration.GetRootComponent ## EXPERIMENTAL: Browse single parts in assembly #if filetype == SolidWorksEnums.FileTypes.SWassembly: # Logger.log("d", 'walkComponentsInAssembly: ' + repr(self.walkComponentsInAssembly())) return options
def submitSmartSliceJob(self, cloud_job, threemf_data): thor_status_code, task = self.executeApiCall( lambda: self._client.new_smartslice_job(threemf_data), self.ConnectionErrorCodes.genericInternetConnectionError ) job_status_tracker = JobStatusTracker(self.connector, self.connector.status) Logger.log("d", "API Status after posting: {}".format(thor_status_code)) if thor_status_code != 200: self._handleThorErrors(thor_status_code, task) self.connector.cancelCurrentJob() if getattr(task, 'status', None): Logger.log("d", "Job status after posting: {}".format(task.status)) # While the task status is not finished/failed/crashed/aborted continue to # wait on the status using the API. thor_status_code = None while thor_status_code != self.ConnectionErrorCodes.genericInternetConnectionError and not cloud_job.canceled and task.status not in ( pywim.http.thor.JobInfo.Status.failed, pywim.http.thor.JobInfo.Status.crashed, pywim.http.thor.JobInfo.Status.aborted, pywim.http.thor.JobInfo.Status.finished ): self.job_status = task.status cloud_job.api_job_id = task.id thor_status_code, task = self.executeApiCall( lambda: self._client.smartslice_job_wait(task.id, callback=job_status_tracker), self.ConnectionErrorCodes.genericInternetConnectionError ) if thor_status_code == 200: thor_status_code, task = self.executeApiCall( lambda: self._client.smartslice_job_wait(task.id, callback=job_status_tracker), self.ConnectionErrorCodes.genericInternetConnectionError ) if thor_status_code not in (200, None): self._handleThorErrors(thor_status_code, task) self.connector.cancelCurrentJob() if not cloud_job.canceled: self.connector.propertyHandler._cancelChanges = False if task.status == pywim.http.thor.JobInfo.Status.failed: error_message = Message() error_message.setTitle("Smart Slice Solver") error_message.setText(i18n_catalog.i18nc( "@info:status", "Error while processing the job:\n{}".format(task.errors[0].message) )) error_message.show() self.connector.cancelCurrentJob() cloud_job.setError(SmartSliceCloudJob.JobException(error_message.getText())) Logger.log( "e", "An error occured while sending and receiving cloud job: {}".format(error_message.getText()) ) self.connector.propertyHandler._cancelChanges = False return None elif task.status == pywim.http.thor.JobInfo.Status.finished: return task elif len(task.errors) > 0: error_message = Message() error_message.setTitle("Smart Slice Solver") error_message.setText(i18n_catalog.i18nc( "@info:status", "Unexpected status occured:\n{}".format(task.errors[0].message) )) error_message.show() self.connector.cancelCurrentJob() cloud_job.setError(SmartSliceCloudJob.JobException(error_message.getText())) Logger.log( "e", "An unexpected status occured while sending and receiving cloud job: {}".format(error_message.getText()) ) self.connector.propertyHandler._cancelChanges = False return None
class SmartSliceAPIClient(QObject): class ConnectionErrorCodes(Enum): genericInternetConnectionError = 1 loginCredentialsError = 2 def __init__(self, connector): super().__init__() self._client = None self.connector = connector self.extension = connector.extension self._token = None self._error_message = None self._number_of_timeouts = 20 self._timeout_sleep = 3 self._username_preference = "smartslice/username" self._app_preferences = Application.getInstance().getPreferences() #Login properties self._login_username = "" self._login_password = "" self._badCredentials = False self._plugin_metadata = connector.extension.metadata # If the user has logged in before, we will hold on to the email. If they log out, or # the login is unsuccessful, the email will not persist. def _usernamePreferenceExists(self): username = self._app_preferences.getValue(self._username_preference) if username is not None and username != "" and self._login_username == "": self._login_username = username else: self._app_preferences.addPreference(self._username_preference, "") @property def _token_file_path(self): config_path = os.path.join( QStandardPaths.writableLocation(QStandardPaths.GenericConfigLocation), "smartslice" ) if not os.path.exists(config_path): os.makedirs(config_path) return os.path.join(config_path, ".token") # Opening a connection is our main goal with the API client object. We get the address to connect to, # then we check to see if the user has a valid token, if they are already logged in. If not, we log # them in. def openConnection(self): url = urlparse(self._plugin_metadata.url) protocol = url.scheme hostname = url.hostname if url.port: port = url.port else: port = 443 self._usernamePreferenceExists() if type(port) is not int: port = int(port) self._client = pywim.http.thor.Client( protocol=protocol, hostname=hostname, port=port, cluster=self._plugin_metadata.cluster ) # To ensure that the user is tracked and has a proper subscription, we let them login and then use the token we recieve # to track them and their login status. self._getToken() #If there is a token, ensure it is a valid token self._checkToken() #If invalid token, attempt to Login. if not self.logged_in: self.loggedInChanged.emit() self._login() #If now a valid token, allow access if self.logged_in: self.loggedInChanged.emit() Logger.log("d", "SmartSlice HTTP Client: {}".format(self._client.address)) def _connectionCheck(self): try: self._client.info() except Exception as error: Logger.log("e", "An error has occured checking the internet connection: {}".format(error)) return (self.ConnectionErrorCodes.genericInternetConnectionError) return None # API calls need to be executed through this function using a lambda passed in, as well as a failure code. # This prevents a fatal crash of Cura in some circumstances, as well as allows for a timeout/retry system. # The failure codes give us better control over the messages that come from an internet disconnect issue. def executeApiCall(self, endpoint: Callable[[], Tuple[int, object]], failure_code): api_code = self._connectionCheck() timeout_counter = 0 self.clearErrorMessage() if api_code is not None: return api_code, None while api_code is None and timeout_counter < self._number_of_timeouts: try: api_code, api_result = endpoint() except Exception as error: # If this error occurs, there was a connection issue Logger.log("e", "An error has occured with an API call: {}".format(error)) timeout_counter += 1 time.sleep(self._timeout_sleep) if timeout_counter == self._number_of_timeouts: return failure_code, None self.clearErrorMessage() return api_code, api_result def clearErrorMessage(self): if self._error_message is not None: self._error_message.hide() self._error_message = None # Login is fairly simple, the email and password is pulled from the Login popup that is displayed # on the Cura stage, and then sent to the API. def _login(self): username = self._login_username password = self._login_password if self._token is None: self.loggedInChanged.emit() if password != "": api_code, user_auth = self.executeApiCall( lambda: self._client.basic_auth_login(username, password), self.ConnectionErrorCodes.loginCredentialsError ) if api_code != 200: # If we get bad login credentials, this will set the flag that alerts the user on the popup if api_code == 400: Logger.log("d", "API Code 400") self.badCredentials = True self._login_password = "" self.badCredentialsChanged.emit() self._token = None else: self._handleThorErrors(api_code, user_auth) # If all goes well, we will be able to store the login token for the user else: self.clearErrorMessage() self.badCredentials = False self._login_password = "" self._app_preferences.setValue(self._username_preference, username) self._token = self._client.get_token() self._createTokenFile() # Logout removes the current token, clears the last logged in username and signals the popup to reappear. def logout(self): self._token = None self._login_password = "" self._createTokenFile() self._app_preferences.setValue(self._username_preference, "") self.loggedInChanged.emit() # If our user has logged in before, their login token will be in the file. def _getToken(self): #TODO: If no token file, try to login and create one. For now, we will just create a token file. if not os.path.exists(self._token_file_path): self._token = None else: try: with open(self._token_file_path, "r") as token_file: self._token = json.load(token_file) except: Logger.log("d", "Unable to read Token JSON") self._token = None if self._token == "" or self._token is None: self._token = None # Once we have pulled the token, we want to check with the API to make sure the token is valid. def _checkToken(self): self._client.set_token(self._token) api_code, api_result = self.executeApiCall( lambda: self._client.whoami(), self.ConnectionErrorCodes.loginCredentialsError ) if api_code != 200: self._token = None self._createTokenFile() # If there is no token in the file, or the file does not exist, we create one. def _createTokenFile(self): with open(self._token_file_path, "w") as token_file: json.dump(self._token, token_file) def getSubscription(self): api_code, api_result = self.executeApiCall( lambda: self._client.smartslice_subscription(), self.ConnectionErrorCodes.genericInternetConnectionError ) if api_code != 200: self._handleThorErrors(api_code, api_result) return None return api_result def cancelJob(self, job_id): api_code, api_result = self.executeApiCall( lambda: self._client.smartslice_job_abort(job_id), self.ConnectionErrorCodes.genericInternetConnectionError ) if api_code != 200: self._handleThorErrors(api_code, api_result) # If the user is correctly logged in, and has a valid token, we can use the 3mf data from # the plugin to submit a job to the API, and the results will be handled when they are returned. def submitSmartSliceJob(self, cloud_job, threemf_data): thor_status_code, task = self.executeApiCall( lambda: self._client.new_smartslice_job(threemf_data), self.ConnectionErrorCodes.genericInternetConnectionError ) job_status_tracker = JobStatusTracker(self.connector, self.connector.status) Logger.log("d", "API Status after posting: {}".format(thor_status_code)) if thor_status_code != 200: self._handleThorErrors(thor_status_code, task) self.connector.cancelCurrentJob() if getattr(task, 'status', None): Logger.log("d", "Job status after posting: {}".format(task.status)) # While the task status is not finished/failed/crashed/aborted continue to # wait on the status using the API. thor_status_code = None while thor_status_code != self.ConnectionErrorCodes.genericInternetConnectionError and not cloud_job.canceled and task.status not in ( pywim.http.thor.JobInfo.Status.failed, pywim.http.thor.JobInfo.Status.crashed, pywim.http.thor.JobInfo.Status.aborted, pywim.http.thor.JobInfo.Status.finished ): self.job_status = task.status cloud_job.api_job_id = task.id thor_status_code, task = self.executeApiCall( lambda: self._client.smartslice_job_wait(task.id, callback=job_status_tracker), self.ConnectionErrorCodes.genericInternetConnectionError ) if thor_status_code == 200: thor_status_code, task = self.executeApiCall( lambda: self._client.smartslice_job_wait(task.id, callback=job_status_tracker), self.ConnectionErrorCodes.genericInternetConnectionError ) if thor_status_code not in (200, None): self._handleThorErrors(thor_status_code, task) self.connector.cancelCurrentJob() if not cloud_job.canceled: self.connector.propertyHandler._cancelChanges = False if task.status == pywim.http.thor.JobInfo.Status.failed: error_message = Message() error_message.setTitle("Smart Slice Solver") error_message.setText(i18n_catalog.i18nc( "@info:status", "Error while processing the job:\n{}".format(task.errors[0].message) )) error_message.show() self.connector.cancelCurrentJob() cloud_job.setError(SmartSliceCloudJob.JobException(error_message.getText())) Logger.log( "e", "An error occured while sending and receiving cloud job: {}".format(error_message.getText()) ) self.connector.propertyHandler._cancelChanges = False return None elif task.status == pywim.http.thor.JobInfo.Status.finished: return task elif len(task.errors) > 0: error_message = Message() error_message.setTitle("Smart Slice Solver") error_message.setText(i18n_catalog.i18nc( "@info:status", "Unexpected status occured:\n{}".format(task.errors[0].message) )) error_message.show() self.connector.cancelCurrentJob() cloud_job.setError(SmartSliceCloudJob.JobException(error_message.getText())) Logger.log( "e", "An unexpected status occured while sending and receiving cloud job: {}".format(error_message.getText()) ) self.connector.propertyHandler._cancelChanges = False return None # When something goes wrong with the API, the errors are sent here. The http_error_code is an int that indicates # the problem that has occurred. The returned object may hold additional information about the error, or it may be None. def _handleThorErrors(self, http_error_code, returned_object): if self._error_message is not None: self._error_message.hide() self._error_message = Message(lifetime= 180) self._error_message.setTitle("Smart Slice API") if http_error_code == 400: if returned_object.error.startswith('User\'s maximum job queue count reached'): print(self._error_message.getActions()) self._error_message.setTitle("") self._error_message.setText("You have exceeded the maximum allowable " "number of queued\n jobs. Please cancel a " "queued job or wait for your queue to clear.") self._error_message.addAction( "continue", i18n_catalog.i18nc("@action", "Ok"), "", "" ) self._error_message.actionTriggered.connect(self.errorMessageAction) else: self._error_message.setText(i18n_catalog.i18nc("@info:status", "SmartSlice Server Error (400: Bad Request):\n{}".format(returned_object.error))) elif http_error_code == 401: self._error_message.setText(i18n_catalog.i18nc("@info:status", "SmartSlice Server Error (401: Unauthorized):\nAre you logged in?")) elif http_error_code == 429: self._error_message.setText(i18n_catalog.i18nc("@info:status", "SmartSlice Server Error (429: Too Many Attempts)")) elif http_error_code == self.ConnectionErrorCodes.genericInternetConnectionError: self._error_message.setText(i18n_catalog.i18nc("@info:status", "Internet connection issue:\nPlease check your connection and try again.")) elif http_error_code == self.ConnectionErrorCodes.loginCredentialsError: self._error_message.setText(i18n_catalog.i18nc("@info:status", "Internet connection issue:\nCould not verify your login credentials.")) else: self._error_message.setText(i18n_catalog.i18nc("@info:status", "SmartSlice Server Error (HTTP Error: {})".format(http_error_code))) self._error_message.show() @staticmethod def errorMessageAction(msg, action): msg.hide() @pyqtSlot() def onLoginButtonClicked(self): self.openConnection() @pyqtProperty(str, constant=True) def smartSliceUrl(self): return self._plugin_metadata.url badCredentialsChanged = pyqtSignal() loggedInChanged = pyqtSignal() @pyqtProperty(bool, notify=loggedInChanged) def logged_in(self): return self._token is not None @pyqtProperty(str, constant=True) def loginUsername(self): return self._login_username @loginUsername.setter def loginUsername(self,value): self._login_username = value @pyqtProperty(str, constant=True) def loginPassword(self): return self._login_password @loginPassword.setter def loginPassword(self,value): self._login_password = value @pyqtProperty(bool, notify=badCredentialsChanged) def badCredentials(self): return self._badCredentials @badCredentials.setter def badCredentials(self, value): self._badCredentials = value self.badCredentialsChanged.emit()