From b2a2e8ed6445e95356aa3521c87a22b22d8cb019 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 28 Feb 2018 13:44:45 +0100 Subject: [PATCH 01/28] support_wall_count setting --- resources/definitions/fdmprinter.def.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index d282eeae0a..883cd1ecef 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3574,6 +3574,19 @@ "settable_per_mesh": false, "settable_per_extruder": true }, + "support_wall_count": + { + "label": "Support Wall Line Count", + "description": "The number of walls with which to surround support infill. Adding a wall can make support print more reliably and can support overhangs better, but increases print time and material used.", + "default_value": 1, + "minimum_value": "0", + "minimum_value_warning": "1 if support_pattern == 'concentric' else 0", + "maximum_value_warning": "3", + "type": "int", + "value": "1 if (support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'concentric') else 0", + "limit_to_extruder": "support_infill_extruder_nr", + "settable_per_mesh": true + }, "zig_zaggify_support": { "label": "Connect Support Lines", From 8b83be150f7fafca4a59414f429394a214cc10f0 Mon Sep 17 00:00:00 2001 From: Jinbuhm Kim Date: Sat, 21 Apr 2018 00:11:43 +0900 Subject: [PATCH 02/28] Fix some typo and wrong translation. --- resources/i18n/ko_KR/cura.po | 12 ++-- resources/i18n/ko_KR/fdmprinter.def.json.po | 68 ++++++++++----------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/resources/i18n/ko_KR/cura.po b/resources/i18n/ko_KR/cura.po index 4097741cb0..d0a18dd3a5 100644 --- a/resources/i18n/ko_KR/cura.po +++ b/resources/i18n/ko_KR/cura.po @@ -675,7 +675,7 @@ msgstr "G 코드 수정" #: /home/ruben/Projects/Cura/plugins/SupportEraser/__init__.py:12 msgctxt "@label" msgid "Support Blocker" -msgstr "지지대 차단기" +msgstr "서포트 차단기" #: /home/ruben/Projects/Cura/plugins/SupportEraser/__init__.py:13 msgctxt "@info:tooltip" @@ -971,7 +971,7 @@ msgstr "펌웨어 업그레이드" #: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py:14 msgctxt "@action" msgid "Checkup" -msgstr "대조" +msgstr "검사" #: /home/ruben/Projects/Cura/plugins/UltimakerMachineActions/BedLevelMachineAction.py:21 msgctxt "@action" @@ -991,7 +991,7 @@ msgstr "내벽" #: /home/ruben/Projects/Cura/cura/PrintInformation.py:98 msgctxt "@tooltip" msgid "Skin" -msgstr "외판" +msgstr "스킨" #: /home/ruben/Projects/Cura/cura/PrintInformation.py:99 msgctxt "@tooltip" @@ -2348,7 +2348,7 @@ msgstr "밀리미터 단위의 빌드 플레이트에서 기저부 높이." #: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:61 msgctxt "@action:label" msgid "Base (mm)" -msgstr "바다 (mm)" +msgstr "바닥 (mm)" #: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:79 msgctxt "@info:tooltip" @@ -2420,7 +2420,7 @@ msgstr "서포터로 프린팅" #: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84 msgctxt "@label" msgid "Don't support overlap with other models" -msgstr "다른 모델과 오버랩되도록 지지하지 않음" +msgstr "다른 모델과 오버랩되도록 지원하지 않음" #: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92 msgctxt "@label" @@ -3631,7 +3631,7 @@ msgstr "버전: %1" #: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:56 msgctxt "@label" msgid "End-to-end solution for fused filament 3D printing." -msgstr "3D 인쇄를 위해 필라멘트를 한줄로 용." +msgstr "3D 프린팅을 위한 엔드 투 엔트 솔루션." #: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:69 msgctxt "@info:credit" diff --git a/resources/i18n/ko_KR/fdmprinter.def.json.po b/resources/i18n/ko_KR/fdmprinter.def.json.po index e2bf9e52bd..12f7ae306c 100644 --- a/resources/i18n/ko_KR/fdmprinter.def.json.po +++ b/resources/i18n/ko_KR/fdmprinter.def.json.po @@ -433,7 +433,7 @@ msgstr "Repetier" #: fdmprinter.def.json msgctxt "machine_firmware_retract label" msgid "Firmware Retraction" -msgstr "펌웨어 제거" +msgstr "펌웨어 리트렉션" #: fdmprinter.def.json msgctxt "machine_firmware_retract description" @@ -521,7 +521,7 @@ msgstr "노즐의 내경. 비표준 노즐 크기를 사용할 때 이 설정을 #: fdmprinter.def.json msgctxt "machine_use_extruder_offset_to_offset_coords label" msgid "Offset With Extruder" -msgstr "압출기로 오프셋" +msgstr "익스트루더로 오프셋" #: fdmprinter.def.json msgctxt "machine_use_extruder_offset_to_offset_coords description" @@ -531,7 +531,7 @@ msgstr "익스트루더 오프셋을 좌표계에 적용하십시오." #: fdmprinter.def.json msgctxt "extruder_prime_pos_z label" msgid "Extruder Prime Z Position" -msgstr "압출기 프라임 Z 포지션" +msgstr "익스트루더 프라임 Z 포지션" #: fdmprinter.def.json msgctxt "extruder_prime_pos_z description" @@ -543,7 +543,7 @@ msgstr "프린팅가 시작될 때 노즐 위치의 Z 좌표입니다." #: fdmprinter.def.json msgctxt "extruder_prime_pos_abs label" msgid "Absolute Extruder Prime Position" -msgstr "독립 압출 기 프라임 포지션" +msgstr "독립 익스트루더 프라임 포지션" #: fdmprinter.def.json msgctxt "extruder_prime_pos_abs description" @@ -667,7 +667,7 @@ msgstr "Z 방향 모터의 기본 Jerk." #: fdmprinter.def.json msgctxt "machine_max_jerk_e label" msgid "Default Filament Jerk" -msgstr "기본 Filament Jerk" +msgstr "기본 필라멘트 Jerk" #: fdmprinter.def.json msgctxt "machine_max_jerk_e description" @@ -1868,7 +1868,7 @@ msgstr "내부채움 패턴이 Y축을 따라 이 거리만큼 이동합니다." #: fdmprinter.def.json msgctxt "sub_div_rad_add label" msgid "Cubic Subdivision Shell" -msgstr "입방 세분 내관" +msgstr "입방 세분 쉘" #: fdmprinter.def.json msgctxt "sub_div_rad_add description" @@ -2339,7 +2339,7 @@ msgctxt "material_flow description" msgid "" "Flow compensation: the amount of material extruded is multiplied by this " "value." -msgstr "유량 보상: 압출 된 재료의 양에 이 값을 곱합니다." +msgstr "압출량 보상: 압출 된 재료의 양에 이 값을 곱합니다." #: fdmprinter.def.json msgctxt "material_flow_layer_0 label" @@ -2420,7 +2420,7 @@ msgstr "리트렉션 이동 중에 필라멘트가 프라이밍되는 속도입 #: fdmprinter.def.json msgctxt "retraction_extra_prime_amount label" msgid "Retraction Extra Prime Amount" -msgstr "후퇴 Extra 초기 속도" +msgstr "추가적인 리트렉션 정도" #: fdmprinter.def.json msgctxt "retraction_extra_prime_amount description" @@ -2433,7 +2433,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "retraction_min_travel label" msgid "Retraction Minimum Travel" -msgstr "리트렉션 최소 움직임" +msgstr "리트렉션 최소 이동" #: fdmprinter.def.json msgctxt "retraction_min_travel description" @@ -2724,7 +2724,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "speed_travel label" msgid "Travel Speed" -msgstr "움직임 속도" +msgstr "이동 속도" #: fdmprinter.def.json msgctxt "speed_travel description" @@ -2841,7 +2841,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "speed_equalize_flow_max label" msgid "Maximum Speed for Flow Equalization" -msgstr "유량 균등화를위한 최대 속도" +msgstr "압출량 균등화를위한 최대 속도" #: fdmprinter.def.json msgctxt "speed_equalize_flow_max description" @@ -3008,7 +3008,7 @@ msgstr "프라임 타워가 프린팅되는 가속도." #: fdmprinter.def.json msgctxt "acceleration_travel label" msgid "Travel Acceleration" -msgstr "움직임 가속" +msgstr "이동 가속" #: fdmprinter.def.json msgctxt "acceleration_travel description" @@ -3038,7 +3038,7 @@ msgstr "초기 레이어 프린팅 중 가속도." #: fdmprinter.def.json msgctxt "acceleration_travel_layer_0 label" msgid "Initial Layer Travel Acceleration" -msgstr "초기 레이어 움직임 가속도" +msgstr "초기 레이어 이동 가속도" #: fdmprinter.def.json msgctxt "acceleration_travel_layer_0 description" @@ -3229,7 +3229,7 @@ msgstr "프라임 타워가 프린팅되는 최대 순간 속도 변화." #: fdmprinter.def.json msgctxt "jerk_travel label" msgid "Travel Jerk" -msgstr "움직임 Jerk" +msgstr "이동 Jerk" #: fdmprinter.def.json msgctxt "jerk_travel description" @@ -3262,7 +3262,7 @@ msgstr "초기 층의 프린팅 중 최대 순간 속도 변화." #: fdmprinter.def.json msgctxt "jerk_travel_layer_0 label" msgid "Initial Layer Travel Jerk" -msgstr "초기 레이어 움직임 Jerk" +msgstr "초기 레이어 이동 Jerk" #: fdmprinter.def.json msgctxt "jerk_travel_layer_0 description" @@ -3284,12 +3284,12 @@ msgstr "스커트와 브림이 프린팅되는 최대 순간 속도 변화." #: fdmprinter.def.json msgctxt "travel label" msgid "Travel" -msgstr "움직임" +msgstr "이동" #: fdmprinter.def.json msgctxt "travel description" msgid "travel" -msgstr "움직임" +msgstr "이동" #: fdmprinter.def.json msgctxt "retraction_combing label" @@ -3320,7 +3320,7 @@ msgstr "모두" #: fdmprinter.def.json msgctxt "retraction_combing option noskin" msgid "No Skin" -msgstr "피부가 없다" +msgstr "스킨이 없음" #: fdmprinter.def.json msgctxt "travel_retract_before_outer_wall label" @@ -3349,7 +3349,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "travel_avoid_distance label" msgid "Travel Avoid Distance" -msgstr "움직일 때 피하기 거리" +msgstr "이동중 피하는 거리" #: fdmprinter.def.json msgctxt "travel_avoid_distance description" @@ -3826,7 +3826,7 @@ msgstr "십자" #: fdmprinter.def.json msgctxt "zig_zaggify_support label" msgid "Connect Support Lines" -msgstr "지지대 선 연결" +msgstr "서포트 선 연결" #: fdmprinter.def.json msgctxt "zig_zaggify_support description" @@ -3966,7 +3966,7 @@ msgstr "X/Y 방향에서 오버행으로부터 서포트까지의 거리. " #: fdmprinter.def.json msgctxt "support_bottom_stair_step_height label" msgid "Support Stair Step Height" -msgstr "계단 Step Height 지지대" +msgstr "계단 Step Height 서포트" #: fdmprinter.def.json msgctxt "support_bottom_stair_step_height description" @@ -3998,7 +3998,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "support_join_distance label" msgid "Support Join Distance" -msgstr "지지대 Join 거리" +msgstr "서포트 Join 거리" #: fdmprinter.def.json msgctxt "support_join_distance description" @@ -4013,7 +4013,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "support_offset label" msgid "Support Horizontal Expansion" -msgstr "수평 확장 지지대" +msgstr "수평 확장 서포트" #: fdmprinter.def.json msgctxt "support_offset description" @@ -5879,7 +5879,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "support_zag_skip_count label" msgid "Support Chunk Line Count" -msgstr "Chunk 라인 카운트 지지대" +msgstr "Chunk 라인 카운트 서포트" #: fdmprinter.def.json msgctxt "support_zag_skip_count description" @@ -6046,7 +6046,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "skin_alternate_rotation label" msgid "Alternate Skin Rotation" -msgstr "대체 피부 회전" +msgstr "대체 스킨 회전" #: fdmprinter.def.json msgctxt "skin_alternate_rotation description" @@ -6429,7 +6429,7 @@ msgid "" "Flow compensation: the amount of material extruded is multiplied by this " "value. Only applies to Wire Printing." msgstr "" -"유량 보상 : 압출 된 재료의 양에 이 값을 곱합니다. 와이어 프린팅에만 적용됩니" +"압출량 보상 : 압출 된 재료의 양에 이 값을 곱합니다. 와이어 프린팅에만 적용됩니" "다." #: fdmprinter.def.json @@ -6440,7 +6440,7 @@ msgstr "WP 연결 흐름" #: fdmprinter.def.json msgctxt "wireframe_flow_connection description" msgid "Flow compensation when going up or down. Only applies to Wire Printing." -msgstr "위 또는 아래로 이동할 때 유량 보정. 와이어 프린팅에만 적용됩니다." +msgstr "위 또는 아래로 이동할 때 압출량 보정. 와이어 프린팅에만 적용됩니다." #: fdmprinter.def.json msgctxt "wireframe_flow_flat label" @@ -6451,7 +6451,7 @@ msgstr "WP 플랫 플로우" msgctxt "wireframe_flow_flat description" msgid "" "Flow compensation when printing flat lines. Only applies to Wire Printing." -msgstr "평평한 선을 프린팅 할 때 유량 보정. 와이어 프린팅에만 적용됩니다." +msgstr "평평한 선을 프린팅 할 때 압출량 보정. 와이어 프린팅에만 적용됩니다." #: fdmprinter.def.json msgctxt "wireframe_top_delay label" @@ -6496,7 +6496,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "wireframe_up_half_speed label" msgid "WP Ease Upward" -msgstr "WP는 쉽게 상향 조정" +msgstr "WP 상향 조정" #: fdmprinter.def.json msgctxt "wireframe_up_half_speed description" @@ -6761,7 +6761,7 @@ msgstr "" #: fdmprinter.def.json msgctxt "bridge_wall_max_overhang label" msgid "Bridge Wall Max Overhang" -msgstr "브리지 벽 최대 돌출" +msgstr "브리지 벽 최대 오버행" #: fdmprinter.def.json msgctxt "bridge_wall_max_overhang description" @@ -6808,7 +6808,7 @@ msgstr "브릿지 벽이 프린팅되는 속도." #: fdmprinter.def.json msgctxt "bridge_wall_material_flow label" msgid "Bridge Wall Flow" -msgstr "브리지 벽 유량" +msgstr "브리지 벽 압출량" #: fdmprinter.def.json msgctxt "bridge_wall_material_flow description" @@ -6831,7 +6831,7 @@ msgstr "브릿지 스킨 층이 프린팅되는 속도." #: fdmprinter.def.json msgctxt "bridge_skin_material_flow label" msgid "Bridge Skin Flow" -msgstr "브리지 스킨 유량" +msgstr "브리지 스킨 압출량" #: fdmprinter.def.json msgctxt "bridge_skin_material_flow description" @@ -6892,7 +6892,7 @@ msgstr "두번째 브릿지 스킨 레이어를 인쇄 할 때 사용할 인쇄 #: fdmprinter.def.json msgctxt "bridge_skin_material_flow_2 label" msgid "Bridge Second Skin Flow" -msgstr "브리지 두 번째 스킨 유량" +msgstr "브리지 두 번째 스킨 압출량" #: fdmprinter.def.json msgctxt "bridge_skin_material_flow_2 description" @@ -6939,7 +6939,7 @@ msgstr "세번째 브릿지 스킨 레이어를 인쇄 할 때 사용할 인쇄 #: fdmprinter.def.json msgctxt "bridge_skin_material_flow_3 label" msgid "Bridge Third Skin Flow" -msgstr "브리지 세 번째 스킨 유량" +msgstr "브리지 세 번째 스킨 압출량" #: fdmprinter.def.json msgctxt "bridge_skin_material_flow_3 description" From 0f0b6a9712592a81b0b8e95af703c0da27921387 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Mon, 14 May 2018 16:01:59 +0200 Subject: [PATCH 03/28] Fix/Implement update in toolbox --- cura/CuraPackageManager.py | 26 ++++++------ .../resources/qml/ToolboxInstalledTile.qml | 2 +- .../qml/ToolboxInstalledTileActions.qml | 7 +++- plugins/Toolbox/src/Toolbox.py | 41 ++++++++++++++++--- resources/packages.json | 2 +- 5 files changed, 57 insertions(+), 21 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 2b91081e4d..4b21096c2f 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -136,14 +136,14 @@ class CuraPackageManager(QObject): all_installed_ids = all_installed_ids.union(set(self._bundled_package_dict.keys())) if self._installed_package_dict.keys(): all_installed_ids = all_installed_ids.union(set(self._installed_package_dict.keys())) + all_installed_ids = all_installed_ids.difference(self._to_remove_package_set) + # If it's going to be installed and to be removed, then the package is being updated and it should be listed. if self._to_install_package_dict.keys(): all_installed_ids = all_installed_ids.union(set(self._to_install_package_dict.keys())) - all_installed_ids = all_installed_ids.difference(self._to_remove_package_set) # map of -> -> installed_packages_dict = {} for package_id in all_installed_ids: - # Skip required plugins as they should not be tampered with if package_id in Application.getInstance().getRequiredPlugins(): continue @@ -194,10 +194,10 @@ class CuraPackageManager(QObject): return package_id = package_info["package_id"] - # Check the delayed installation and removal lists first - if package_id in self._to_remove_package_set: - self._to_remove_package_set.remove(package_id) - has_changes = True + # # Check the delayed installation and removal lists first + # if package_id in self._to_remove_package_set: + # self._to_remove_package_set.remove(package_id) + # has_changes = True # Check if it is installed installed_package_info = self.getInstalledPackageInfo(package_info["package_id"]) @@ -235,20 +235,22 @@ class CuraPackageManager(QObject): self.installedPackagesChanged.emit() # Schedules the given package to be removed upon the next start. + # \param package_id id of the package + # \param force_add is used when updating. In that case you actually want to uninstall & install @pyqtSlot(str) - def removePackage(self, package_id: str) -> None: + def removePackage(self, package_id: str, force_add: bool = False) -> None: # Check the delayed installation and removal lists first if not self.isPackageInstalled(package_id): Logger.log("i", "Attempt to remove package [%s] that is not installed, do nothing.", package_id) return - # Remove from the delayed installation list if present - if package_id in self._to_install_package_dict: + if package_id not in self._to_install_package_dict or force_add: + # Schedule for a delayed removal: + self._to_remove_package_set.add(package_id) + else: + # Remove from the delayed installation list if present del self._to_install_package_dict[package_id] - # Schedule for a delayed removal: - self._to_remove_package_set.add(package_id) - self._saveManagementData() self.installedPackagesChanged.emit() diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml index 6004832a57..e788c3788c 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml @@ -102,4 +102,4 @@ Item onMetadataChanged: canUpdate = toolbox.canUpdate(model.id) } } -} \ No newline at end of file +} diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml index 1921bcb58e..d63326288d 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml @@ -37,7 +37,10 @@ Column font: UM.Theme.getFont("default_bold") } } - onClicked: toolbox.update(model.id) + onClicked: { + // Must do all stuff in 1 function as the current ToolboxInstalledTile object is going to disappear... + toolbox.update(model.id) + } } ProgressBar { @@ -90,4 +93,4 @@ Column } onClicked: toolbox.uninstall(model.id) } -} \ No newline at end of file +} diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 3ee6787cf4..3dd1ec90ec 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -61,6 +61,7 @@ class Toolbox(QObject, Extension): "plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)), "materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)) } + self._to_update = [] # Package_ids that are waiting to be updated # Data: self._metadata = { @@ -216,13 +217,29 @@ class Toolbox(QObject, Extension): @pyqtSlot(str) def uninstall(self, plugin_id: str) -> None: - self._package_manager.removePackage(plugin_id) + self._package_manager.removePackage(plugin_id, force_add = True) self.installChanged.emit() self._updateInstalledModels() self.metadataChanged.emit() self._restart_required = True self.restartRequiredChanged.emit() + ## Actual update packages that are in self._to_update + def _update(self) -> None: + if self._to_update: + plugin_id = self._to_update.pop(0) + Logger.log("d", "Updating package [%s]..." % plugin_id) + self.uninstall(plugin_id) + self.startDownload(self.getRemotePackageURL(plugin_id)) + if self._to_update: + self._application.callLater(self._update) + + ## Update a plugin by plugin_id + @pyqtSlot(str) + def update(self, plugin_id: str) -> None: + self._to_update.append(plugin_id) + self._application.callLater(self._update) + @pyqtSlot(str) def enable(self, plugin_id: str) -> None: self._plugin_registry.enablePlugin(plugin_id) @@ -251,6 +268,23 @@ class Toolbox(QObject, Extension): def restart(self): CuraApplication.getInstance().windowClosed() + def getRemotePackage(self, package_id: str) -> Optional[Dict]: + # TODO: make the lookup in a dict, not a loop. canUpdate is called for every item. + remote_package = None + for package in self._metadata["packages"]: + if package["package_id"] == package_id: + remote_package = package + break + return remote_package + + @pyqtSlot(str, result = str) + def getRemotePackageURL(self, package_id: str) -> str: + remote_package = self.getRemotePackage(package_id) + if remote_package: + return remote_package["download_url"] + else: + return "" + # Checks # -------------------------------------------------------------------------- @pyqtSlot(str, result = bool) @@ -259,10 +293,7 @@ class Toolbox(QObject, Extension): if local_package is None: return False - remote_package = None - for package in self._metadata["packages"]: - if package["package_id"] == package_id: - remote_package = package + remote_package = self.getRemotePackage(package_id) if remote_package is None: return False diff --git a/resources/packages.json b/resources/packages.json index 8d58f226b0..acda5e0a9e 100644 --- a/resources/packages.json +++ b/resources/packages.json @@ -736,7 +736,7 @@ "package_type": "material", "display_name": "Dagoma Chromatik PLA", "description": "Filament testé et approuvé pour les imprimantes 3D Dagoma. Chromatik est l'idéal pour débuter et suivre les tutoriels premiers pas. Il vous offre qualité et résistance pour chacune de vos impressions.", - "package_version": "1.0.0", + "package_version": "0.9.5", "cura_version": 4, "website": "https://dagoma.fr/boutique/filaments.html", "author": { From 1f088aabc4b02ac050647e0a6e75318293ab3c37 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Mon, 14 May 2018 16:11:44 +0200 Subject: [PATCH 04/28] Cleanup and simplify toolbox plugin update --- cura/CuraPackageManager.py | 5 ----- .../qml/ToolboxInstalledTileActions.qml | 5 +---- plugins/Toolbox/src/Toolbox.py | 20 +++++++++---------- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 4b21096c2f..41718a9e3b 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -194,11 +194,6 @@ class CuraPackageManager(QObject): return package_id = package_info["package_id"] - # # Check the delayed installation and removal lists first - # if package_id in self._to_remove_package_set: - # self._to_remove_package_set.remove(package_id) - # has_changes = True - # Check if it is installed installed_package_info = self.getInstalledPackageInfo(package_info["package_id"]) to_install_package = installed_package_info is None # Install if the package has not been installed diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml index d63326288d..07f4ed632c 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml @@ -37,10 +37,7 @@ Column font: UM.Theme.getFont("default_bold") } } - onClicked: { - // Must do all stuff in 1 function as the current ToolboxInstalledTile object is going to disappear... - toolbox.update(model.id) - } + onClicked: toolbox.update(model.id) } ProgressBar { diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 3dd1ec90ec..81046b51ce 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -228,9 +228,15 @@ class Toolbox(QObject, Extension): def _update(self) -> None: if self._to_update: plugin_id = self._to_update.pop(0) - Logger.log("d", "Updating package [%s]..." % plugin_id) - self.uninstall(plugin_id) - self.startDownload(self.getRemotePackageURL(plugin_id)) + remote_package = self.getRemotePackage(plugin_id) + if remote_package: + download_url = remote_package["download_url"] + Logger.log("d", "Updating package [%s]..." % plugin_id) + self.uninstall(plugin_id) + self.startDownload(download_url) + else: + Logger.log("e", "Could not update package [%s] because there is no remote package info available.", plugin_id) + if self._to_update: self._application.callLater(self._update) @@ -277,14 +283,6 @@ class Toolbox(QObject, Extension): break return remote_package - @pyqtSlot(str, result = str) - def getRemotePackageURL(self, package_id: str) -> str: - remote_package = self.getRemotePackage(package_id) - if remote_package: - return remote_package["download_url"] - else: - return "" - # Checks # -------------------------------------------------------------------------- @pyqtSlot(str, result = bool) From 0547ad38bdee8899642ab733b325080ea9b867a2 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Mon, 14 May 2018 16:17:14 +0200 Subject: [PATCH 05/28] Undo setting testing version --- resources/packages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/packages.json b/resources/packages.json index acda5e0a9e..8d58f226b0 100644 --- a/resources/packages.json +++ b/resources/packages.json @@ -736,7 +736,7 @@ "package_type": "material", "display_name": "Dagoma Chromatik PLA", "description": "Filament testé et approuvé pour les imprimantes 3D Dagoma. Chromatik est l'idéal pour débuter et suivre les tutoriels premiers pas. Il vous offre qualité et résistance pour chacune de vos impressions.", - "package_version": "0.9.5", + "package_version": "1.0.0", "cura_version": 4, "website": "https://dagoma.fr/boutique/filaments.html", "author": { From d433c54a0a986e7cfb149f2ffef6cd4f85e58c4c Mon Sep 17 00:00:00 2001 From: alekseisasin Date: Mon, 14 May 2018 17:42:54 +0200 Subject: [PATCH 06/28] If the file type is not registered still show it's name in project field CURA-5323 --- cura/PrintInformation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index 6737ebdfb9..a59f235741 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -345,8 +345,8 @@ class PrintInformation(QObject): except: Logger.log("w", "Unsupported Mime Type Database file extension") - if data is not None: - self._base_name = data + if data is not None and check_name is not None: + self._base_name = check_name else: self._base_name = '' From fccfff14bcd230148981cbc25879e1356128f0f6 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 15 May 2018 09:44:58 +0200 Subject: [PATCH 07/28] Now using api.ultimaker.com instead of api-staging.ultimaker.com --- plugins/Toolbox/src/Toolbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 3ee6787cf4..2ba91dcdba 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -34,7 +34,7 @@ class Toolbox(QObject, Extension): self._plugin_registry = Application.getInstance().getPluginRegistry() self._packages_version = self._getPackagesVersion() self._api_version = 1 - self._api_url = "https://api-staging.ultimaker.com/cura-packages/v{api_version}/cura/v{package_version}".format( api_version = self._api_version, package_version = self._packages_version) + self._api_url = "https://api.ultimaker.com/cura-packages/v{api_version}/cura/v{package_version}".format( api_version = self._api_version, package_version = self._packages_version) # Network: self._get_packages_request = None From 49c4f66d9526d0caeee8f14e682ced5f047e45d7 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 15 May 2018 10:50:33 +0200 Subject: [PATCH 08/28] Ugly fix for uninstalling installed packages, but not uninstalling bundled packages. --- cura/CuraPackageManager.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 41718a9e3b..cb6ce307fb 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -239,12 +239,18 @@ class CuraPackageManager(QObject): Logger.log("i", "Attempt to remove package [%s] that is not installed, do nothing.", package_id) return + # Temp hack + if package_id not in self._installed_package_dict and package_id in self._bundled_package_dict: + Logger.log("i", "Not uninstalling [%s] because it is a bundled package.") + return + if package_id not in self._to_install_package_dict or force_add: # Schedule for a delayed removal: self._to_remove_package_set.add(package_id) else: - # Remove from the delayed installation list if present - del self._to_install_package_dict[package_id] + if package_id in self._to_install_package_dict: + # Remove from the delayed installation list if present + del self._to_install_package_dict[package_id] self._saveManagementData() self.installedPackagesChanged.emit() From 0a9f38939659e584cfa79f14a32cf3582d203eee Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 15 May 2018 11:13:59 +0200 Subject: [PATCH 09/28] Search for bundled packages file in all search paths For this we need to rename bundled_packages to disambiguate between that and the other packages.json file for user-installed packages. Contributes to issue CURA-5364. --- cura/CuraApplication.py | 1 - cura/CuraPackageManager.py | 17 ++++++++--------- .../{packages.json => bundled_packages.json} | 0 3 files changed, 8 insertions(+), 10 deletions(-) rename resources/{packages.json => bundled_packages.json} (100%) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c8e49b186d..80390c907f 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -153,7 +153,6 @@ class CuraApplication(QtApplication): if not hasattr(sys, "frozen"): resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources") Resources.addSearchPath(resource_path) - Resources.setBundledResourcesPath(resource_path) self._use_gui = True self._open_file_queue = [] # Files to open when plug-ins are loaded. diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 2b91081e4d..69378ffade 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -28,15 +28,14 @@ class CuraPackageManager(QObject): self._container_registry = self._application.getContainerRegistry() self._plugin_registry = self._application.getPluginRegistry() - # JSON file that keeps track of all installed packages. - self._bundled_package_management_file_path = os.path.join( - os.path.abspath(Resources.getBundledResourcesPath()), - "packages.json" - ) - self._user_package_management_file_path = os.path.join( - os.path.abspath(Resources.getDataStoragePath()), - "packages.json" - ) + #JSON files that keep track of all installed packages. + for search_path in Resources.getSearchPaths(): + candidate_bundled_path = os.path.join(search_path, "bundled_packages.json") + if os.path.exists(candidate_bundled_path): + self._bundled_package_management_file_path = candidate_bundled_path + candidate_user_path = os.path.join(search_path, "packages.json") + if os.path.exists(candidate_user_path): + self._user_package_management_file_path = candidate_user_path self._bundled_package_dict = {} # A dict of all bundled packages self._installed_package_dict = {} # A dict of all installed packages diff --git a/resources/packages.json b/resources/bundled_packages.json similarity index 100% rename from resources/packages.json rename to resources/bundled_packages.json From 0aa1aaf11b273534a50c8c6778068b75e6663593 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 15 May 2018 11:23:26 +0200 Subject: [PATCH 10/28] Fix loading packages.json when file doesn't exist yet Contributes to issue CURA-5364. --- cura/CuraPackageManager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 69378ffade..c765587eef 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -29,6 +29,8 @@ class CuraPackageManager(QObject): self._plugin_registry = self._application.getPluginRegistry() #JSON files that keep track of all installed packages. + self._user_package_management_file_path = None + self._bundled_package_management_file_path = None for search_path in Resources.getSearchPaths(): candidate_bundled_path = os.path.join(search_path, "bundled_packages.json") if os.path.exists(candidate_bundled_path): @@ -36,6 +38,8 @@ class CuraPackageManager(QObject): candidate_user_path = os.path.join(search_path, "packages.json") if os.path.exists(candidate_user_path): self._user_package_management_file_path = candidate_user_path + if self._user_package_management_file_path is None: #Doesn't exist yet. + self._user_package_management_file_path = os.path.join(Resources.getDataStoragePath(), "packages.json") self._bundled_package_dict = {} # A dict of all bundled packages self._installed_package_dict = {} # A dict of all installed packages From 8707d12bf308f83a18234a8b5856715a73df5ca4 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 15 May 2018 11:25:56 +0200 Subject: [PATCH 11/28] Show package version in installed tab --- plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml index 6004832a57..b585a084b3 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml @@ -90,6 +90,16 @@ Item color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining") linkColor: UM.Theme.getColor("text_link") } + + Label + { + text: model.version + width: parent.width + height: UM.Theme.getSize("toolbox_property_label").height + color: UM.Theme.getColor("text") + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + } } ToolboxInstalledTileActions { From 5d7976c302b97aef8a930b7698f411893a4c9fc4 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 15 May 2018 11:41:58 +0200 Subject: [PATCH 12/28] Add the aluminum build plate variant to avoid crashes when synching to a S5 with that build plate selected. --- resources/variants/ultimaker_s5_aluminum.inst.cfg | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 resources/variants/ultimaker_s5_aluminum.inst.cfg diff --git a/resources/variants/ultimaker_s5_aluminum.inst.cfg b/resources/variants/ultimaker_s5_aluminum.inst.cfg new file mode 100644 index 0000000000..ea349d05f5 --- /dev/null +++ b/resources/variants/ultimaker_s5_aluminum.inst.cfg @@ -0,0 +1,13 @@ +[general] +name = Aluminum +version = 4 +definition = ultimaker_s5 + +[metadata] +setting_version = 4 +type = variant +hardware_type = buildplate + +[values] +material_bed_temperature = =default_material_bed_temperature + 10 +machine_buildplate_type = aluminum From ed1ba41aa6d0cf03a3d563035135e0f0e6eb949b Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 15 May 2018 11:42:17 +0200 Subject: [PATCH 13/28] Undo setting wrong name in PrintInformation. CURA-5323 --- cura/PrintInformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index a59f235741..01d0b43f5b 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -346,7 +346,7 @@ class PrintInformation(QObject): Logger.log("w", "Unsupported Mime Type Database file extension") if data is not None and check_name is not None: - self._base_name = check_name + self._base_name = data else: self._base_name = '' From d30bbd48fb416c895e33b88849bd22593aa8db7b Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Tue, 15 May 2018 12:53:05 +0200 Subject: [PATCH 14/28] CURA-5296 - Changed API to not use staging - Fixed crash when pressing cancel - Added loading icon --- plugins/Toolbox/resources/images/loading.gif | Bin 0 -> 93386 bytes plugins/Toolbox/resources/images/loading.svg | 1 + .../resources/qml/ToolboxDetailTile.qml | 67 +++------- .../qml/ToolboxInstalledTileActions.qml | 64 +++------ .../resources/qml/ToolboxProgressButton.qml | 125 ++++++++++++++++++ plugins/Toolbox/src/Toolbox.py | 2 +- resources/themes/cura-light/theme.json | 3 +- 7 files changed, 162 insertions(+), 100 deletions(-) create mode 100644 plugins/Toolbox/resources/images/loading.gif create mode 100644 plugins/Toolbox/resources/images/loading.svg create mode 100644 plugins/Toolbox/resources/qml/ToolboxProgressButton.qml diff --git a/plugins/Toolbox/resources/images/loading.gif b/plugins/Toolbox/resources/images/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..43cc1ed6d72fbe9b118747d3fc3365c469ef5061 GIT binary patch literal 93386 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 z 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 literal 0 HcmV?d00001 diff --git a/plugins/Toolbox/resources/images/loading.svg b/plugins/Toolbox/resources/images/loading.svg new file mode 100644 index 0000000000..1ceb4a8d7f --- /dev/null +++ b/plugins/Toolbox/resources/images/loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml index 80d50616e8..739dc4ccfe 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml @@ -20,7 +20,7 @@ Item { left: parent.left right: controls.left - rightMargin: UM.Theme.getSize("default_margin").width + rightMargin: UM.Theme.getSize("default_margin").width * 2 + UM.Theme.getSize("toolbox_loader").width top: parent.top } Label @@ -53,60 +53,25 @@ Item anchors.top: tile.top width: childrenRect.width height: childrenRect.height - Button + + ToolboxProgressButton { id: installButton - text: - { - if (installed) - { - return catalog.i18nc("@action:button", "Installed") - } - else - { - if (toolbox.isDownloading && toolbox.activePackage == model) - { - return catalog.i18nc("@action:button", "Cancel") - } - else - { - return catalog.i18nc("@action:button", "Install") - } - } + active: toolbox.isDownloading && toolbox.activePackage == model + complete: tile.installed + readyAction: function() { + toolbox.activePackage = model + toolbox.startDownload(model.download_url) } - enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model) //Don't allow installing while another download is running. + activeAction: function() { + toolbox.cancelDownload() + } + completeAction: function() { + toolbox.viewCategory = "installed" + } + // Don't allow installing while another download is running + enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model) opacity: enabled ? 1.0 : 0.5 - - property alias installed: tile.installed - style: UM.Theme.styles.toolbox_action_button - onClicked: - { - if (installed) - { - toolbox.viewCategory = "installed" - } - else - { - // if ( toolbox.isDownloading && toolbox.activePackage == model ) - if ( toolbox.isDownloading ) - { - toolbox.cancelDownload(); - } - else - { - toolbox.activePackage = model - // toolbox.activePackage = model; - if ( model.can_upgrade ) - { - // toolbox.downloadAndInstallPlugin( model.update_url ); - } - else - { - toolbox.startDownload( model.download_url ); - } - } - } - } } } diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml index 07f4ed632c..5bbed2351c 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml @@ -11,56 +11,26 @@ Column width: UM.Theme.getSize("toolbox_action_button").width spacing: UM.Theme.getSize("narrow_margin").height - Item + ToolboxProgressButton { - width: parent.width - height: childrenRect.height + id: updateButton + active: toolbox.isDownloading && toolbox.activePackage == model + readyLabel: catalog.i18nc("@action:button", "Update") + activeLabel: catalog.i18nc("@action:button", "Updating") + completeLabel: catalog.i18nc("@action:button", "Updated") + readyAction: function() { + toolbox.activePackage = model + toolbox.update(model.id) + } + activeAction: function() { + toolbox.cancelDownload() + } + // Don't allow installing while another download is running + enabled: !(toolbox.isDownloading && toolbox.activePackage != model) + opacity: enabled ? 1.0 : 0.5 visible: canUpdate - Button - { - id: updateButton - text: catalog.i18nc("@action:button", "Update") - style: ButtonStyle - { - background: Rectangle - { - implicitWidth: UM.Theme.getSize("toolbox_action_button").width - implicitHeight: UM.Theme.getSize("toolbox_action_button").height - color: control.hovered ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary") - } - label: Label - { - text: control.text - color: control.hovered ? UM.Theme.getColor("button_text") : UM.Theme.getColor("button_text_hover") - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - font: UM.Theme.getFont("default_bold") - } - } - onClicked: toolbox.update(model.id) - } - ProgressBar - { - id: progressbar - width: parent.width - value: toolbox.isDownloading ? toolbox.downloadProgress : 0 - visible: toolbox.isDownloading - style: ProgressBarStyle - { - background: Rectangle - { - color: "transparent" - implicitHeight: UM.Theme.getSize("toolbox_action_button").height - } - progress: Rectangle - { - // TODO Define a good color that fits the purpuse - color: "blue" - opacity: 0.5 - } - } - } } + Button { id: removeButton diff --git a/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml new file mode 100644 index 0000000000..d3aafae987 --- /dev/null +++ b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml @@ -0,0 +1,125 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Toolbox is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import UM 1.1 as UM + + +Item +{ + id: base + + property var active: false + property var complete: false + + property var readyLabel: "Install" + property var activeLabel: "Installing" + property var completeLabel: "Installed" + + property var readyAction: null // Action when button is ready and clicked (likely install) + property var activeAction: null // Action when button is active and clicked (likely cancel) + property var completeAction: null // Action when button is complete and clicked (likely go to installed) + + width: UM.Theme.getSize("toolbox_action_button").width + height: UM.Theme.getSize("toolbox_action_button").height + + Button + { + id: button + text: + { + if (complete) + { + return catalog.i18nc("@action:button", "Installed") + } + else if (active) + { + return catalog.i18nc("@action:button", "Cancel") + } + else + { + return catalog.i18nc("@action:button", "Install") + } + } + onClicked: + { + if (complete) + { + return completeAction() + } + else if (active) + { + return activeAction() + } + else + { + return readyAction() + } + } + style: ButtonStyle + { + background: Rectangle + { + implicitWidth: UM.Theme.getSize("toolbox_action_button").width + implicitHeight: UM.Theme.getSize("toolbox_action_button").height + color: + { + if (base.complete) + { + return UM.Theme.getColor("action_button_disabled") + } + else + { + if (control.hovered) + { + return UM.Theme.getColor("primary_hover") + } + else + { + return UM.Theme.getColor("primary") + } + } + } + } + label: Label + { + text: control.text + color: + { + if (base.complete) + { + return UM.Theme.getColor("action_button_disabled_text") + } + else + { + if (control.hovered) + { + return UM.Theme.getColor("button_text_hover") + } + else + { + return UM.Theme.getColor("button_text") + } + } + } + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font: UM.Theme.getFont("default_bold") + } + } + } + + AnimatedImage + { + id: loader + visible: active + source: "../images/loading.gif" + width: UM.Theme.getSize("toolbox_loader").width + height: UM.Theme.getSize("toolbox_loader").height + anchors.right: button.left + anchors.rightMargin: UM.Theme.getSize("default_margin").width + anchors.verticalCenter: button.verticalCenter + } +} diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 20206d919f..28fb2242d4 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -350,8 +350,8 @@ class Toolbox(QObject, Extension): def resetDownload(self) -> None: if self._download_reply: - self._download_reply.abort() self._download_reply.downloadProgress.disconnect(self._onDownloadProgress) + self._download_reply.abort() self._download_reply = None self._download_request = None self.setDownloadProgress(0) diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index f2309fb4a9..4d75ecc1f1 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -458,6 +458,7 @@ "toolbox_header": [1.0, 4.0], "toolbox_progress_bar": [8.0, 0.5], "toolbox_chart_row": [1.0, 2.0], - "toolbox_action_button": [8.0, 2.5] + "toolbox_action_button": [8.0, 2.5], + "toolbox_loader": [2.0, 2.0] } } From b7f26ddcc775a37043459ee0efe4dad0fee29a27 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 15 May 2018 13:28:33 +0200 Subject: [PATCH 15/28] Small code improvements --- .../Toolbox/resources/qml/ToolboxInstalledTileActions.qml | 6 ++---- plugins/Toolbox/src/Toolbox.py | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml index 5bbed2351c..3a1b9ba3d9 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml @@ -18,13 +18,11 @@ Column readyLabel: catalog.i18nc("@action:button", "Update") activeLabel: catalog.i18nc("@action:button", "Updating") completeLabel: catalog.i18nc("@action:button", "Updated") - readyAction: function() { + readyAction: { toolbox.activePackage = model toolbox.update(model.id) } - activeAction: function() { - toolbox.cancelDownload() - } + activeAction: toolbox.cancelDownload() // Don't allow installing while another download is running enabled: !(toolbox.isDownloading && toolbox.activePackage != model) opacity: enabled ? 1.0 : 0.5 diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 28fb2242d4..07a7bcbe73 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -295,9 +295,9 @@ class Toolbox(QObject, Extension): if remote_package is None: return False - local_version = local_package["package_version"] - remote_version = remote_package["package_version"] - return Version(remote_version) > Version(local_version) + local_version = Version(local_package["package_version"]) + remote_version = Version(remote_package["package_version"]) + return remote_version > local_version @pyqtSlot(str, result = bool) def isInstalled(self, package_id: str) -> bool: From bb94ad904af97e0b888d6283edb26b16f28e3dbc Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 15 May 2018 14:05:40 +0200 Subject: [PATCH 16/28] fix --- .../Toolbox/resources/qml/ToolboxInstalledTileActions.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml index 3a1b9ba3d9..5bbed2351c 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml @@ -18,11 +18,13 @@ Column readyLabel: catalog.i18nc("@action:button", "Update") activeLabel: catalog.i18nc("@action:button", "Updating") completeLabel: catalog.i18nc("@action:button", "Updated") - readyAction: { + readyAction: function() { toolbox.activePackage = model toolbox.update(model.id) } - activeAction: toolbox.cancelDownload() + activeAction: function() { + toolbox.cancelDownload() + } // Don't allow installing while another download is running enabled: !(toolbox.isDownloading && toolbox.activePackage != model) opacity: enabled ? 1.0 : 0.5 From 70ca0538320d640c843f3d46b1ae6c39e841102d Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 15 May 2018 15:32:02 +0200 Subject: [PATCH 17/28] CURA-5358 Add the quality folder to the scope of the VersionUpgrade --- cura/CuraApplication.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 80390c907f..034d045ce6 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -207,6 +207,7 @@ class CuraApplication(QtApplication): UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions( { + ("quality", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"), ("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityChangesInstanceContainer, "application/x-uranium-instancecontainer"), ("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"), ("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"), From 409b90504514d6c4d335f04b03e131273c7e4174 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 15 May 2018 15:43:02 +0200 Subject: [PATCH 18/28] Only trigger settings filter on enter or after 500ms timeout --- resources/qml/Settings/SettingView.qml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 272569daea..9152b463f0 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -179,6 +179,15 @@ Item height: visible ? UM.Theme.getSize("setting_control").height : 0 Behavior on height { NumberAnimation { duration: 100 } } + Timer + { + id: settingsSearchTimer + onTriggered: filter.editingFinished() + interval: 500 + running: false + repeat: false + } + TextField { id: filter; @@ -201,6 +210,11 @@ Item property bool lastFindingSettings: false onTextChanged: + { + settingsSearchTimer.restart() + } + + onEditingFinished: { definitionsModel.filter = {"i18n_label": "*" + text}; findingSettings = (text.length > 0); From 5345bc2867842b9798fa6ecac5dcc6b5ab3e83f2 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 15 May 2018 16:08:19 +0200 Subject: [PATCH 19/28] CURA-5358 Fill the dictionaries also if it's a custom material. But making difference between those materials that depend on a built-in material and those that don't depend on others. --- cura/Machines/MaterialManager.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 719795ac45..ad3c7f165f 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -113,8 +113,6 @@ class MaterialManager(QObject): grouped_by_type_dict = dict() material_types_without_fallback = set() for root_material_id, material_node in self._material_group_map.items(): - if not self._container_registry.isReadOnly(root_material_id): - continue material_type = material_node.root_material_node.metadata["material"] if material_type not in grouped_by_type_dict: grouped_by_type_dict[material_type] = {"generic": None, @@ -127,9 +125,15 @@ class MaterialManager(QObject): diameter = material_node.root_material_node.metadata.get("approximate_diameter") if diameter != self._default_approximate_diameter_for_quality_search: to_add = False # don't add if it's not the default diameter + if to_add: - grouped_by_type_dict[material_type] = material_node.root_material_node.metadata - material_types_without_fallback.remove(material_type) + # Checking this first allow us to differentiate between not read only materials: + # - if it's in the list, it means that is a new material without fallback + # - if it is not, then it is a custom material with a fallback material (parent) + if material_type in material_types_without_fallback: + grouped_by_type_dict[material_type] = material_node.root_material_node.metadata + material_types_without_fallback.remove(material_type) + # Remove the materials that have no fallback materials for material_type in material_types_without_fallback: del grouped_by_type_dict[material_type] @@ -147,9 +151,6 @@ class MaterialManager(QObject): material_group_dict = dict() keys_to_fetch = ("name", "material", "brand", "color") for root_material_id, machine_node in self._material_group_map.items(): - if not self._container_registry.isReadOnly(root_material_id): - continue - root_material_metadata = machine_node.root_material_node.metadata key_data = [] @@ -157,8 +158,13 @@ class MaterialManager(QObject): key_data.append(root_material_metadata.get(key)) key_data = tuple(key_data) + # If the key_data doesn't exist, no matter if the material is read only... if key_data not in material_group_dict: material_group_dict[key_data] = dict() + else: + # ...but if key_data exists, we just overrite it if the material is read only, otherwise we skip it + if not machine_node.is_read_only: + continue approximate_diameter = root_material_metadata.get("approximate_diameter") material_group_dict[key_data][approximate_diameter] = root_material_metadata["id"] From fd73751b91f32a5b0b1b3ed8a4af6933211b84c5 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 15 May 2018 16:10:01 +0200 Subject: [PATCH 20/28] Make the ToolboxProgressButton labels actually overrideable --- .../Toolbox/resources/qml/ToolboxProgressButton.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml index d3aafae987..a977ef999b 100644 --- a/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml +++ b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml @@ -14,9 +14,9 @@ Item property var active: false property var complete: false - property var readyLabel: "Install" - property var activeLabel: "Installing" - property var completeLabel: "Installed" + property var readyLabel: catalog.i18nc("@action:button", "Install") + property var activeLabel: catalog.i18nc("@action:button", "Cancel") + property var completeLabel: catalog.i18nc("@action:button", "Installed") property var readyAction: null // Action when button is ready and clicked (likely install) property var activeAction: null // Action when button is active and clicked (likely cancel) @@ -32,15 +32,15 @@ Item { if (complete) { - return catalog.i18nc("@action:button", "Installed") + return completeLabel } else if (active) { - return catalog.i18nc("@action:button", "Cancel") + return activeLabel } else { - return catalog.i18nc("@action:button", "Install") + return readyLabel } } onClicked: From 9281c235557e22102fc5d6f418178c5c36cb27a9 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Tue, 15 May 2018 16:23:54 +0200 Subject: [PATCH 21/28] Fix uninstallable user package which was also bundled; make update package somewhat better by not trying to uninstall builtin package --- cura/CuraPackageManager.py | 10 +++++++--- plugins/Toolbox/src/Toolbox.py | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index ec75174db4..2006c8804b 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -171,7 +171,7 @@ class CuraPackageManager(QObject): package_info["is_active"] = self._plugin_registry.isActivePlugin(package_id) # If the package ID is in bundled, label it as such - package_info["is_bundled"] = package_info["package_id"] in self._bundled_package_dict.keys() + package_info["is_bundled"] = package_info["package_id"] in self._bundled_package_dict.keys() and not self.isUserInstalledPackage(package_info["package_id"]) # If there is not a section in the dict for this type, add it if package_info["package_type"] not in installed_packages_dict: @@ -182,7 +182,7 @@ class CuraPackageManager(QObject): return installed_packages_dict - # Checks if the given package is installed. + # Checks if the given package is installed (at all). def isPackageInstalled(self, package_id: str) -> bool: return self.getInstalledPackageInfo(package_id) is not None @@ -242,7 +242,7 @@ class CuraPackageManager(QObject): Logger.log("i", "Attempt to remove package [%s] that is not installed, do nothing.", package_id) return - # Temp hack + # Extra safety check if package_id not in self._installed_package_dict and package_id in self._bundled_package_dict: Logger.log("i", "Not uninstalling [%s] because it is a bundled package.") return @@ -258,6 +258,10 @@ class CuraPackageManager(QObject): self._saveManagementData() self.installedPackagesChanged.emit() + ## Is the package an user installed package? + def isUserInstalledPackage(self, package_id: str): + return package_id in self._installed_package_dict + # Removes everything associated with the given package ID. def _purgePackage(self, package_id: str) -> None: # Iterate through all directories in the data storage directory and look for sub-directories that belong to diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 07a7bcbe73..622198666d 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -232,7 +232,8 @@ class Toolbox(QObject, Extension): if remote_package: download_url = remote_package["download_url"] Logger.log("d", "Updating package [%s]..." % plugin_id) - self.uninstall(plugin_id) + if self._package_manager.isUserInstalledPackage(plugin_id): + self.uninstall(plugin_id) self.startDownload(download_url) else: Logger.log("e", "Could not update package [%s] because there is no remote package info available.", plugin_id) From 27c8b43133e8c2d7f04d46c6e9ef8b956c07260f Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Tue, 15 May 2018 16:43:02 +0200 Subject: [PATCH 22/28] Added setting "support_wall_count" to the expert setting's visibility list --- resources/definitions/fdmprinter.def.json | 1 + resources/setting_visibility/expert.cfg | 1 + 2 files changed, 2 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 38d50fb7e3..5d7eed5c6d 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3713,6 +3713,7 @@ "maximum_value_warning": "3", "type": "int", "value": "1 if (support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'concentric') else 0", + "enabled": "support_enable", "limit_to_extruder": "support_infill_extruder_nr", "settable_per_mesh": true }, diff --git a/resources/setting_visibility/expert.cfg b/resources/setting_visibility/expert.cfg index d6989f8b26..db271cc985 100644 --- a/resources/setting_visibility/expert.cfg +++ b/resources/setting_visibility/expert.cfg @@ -220,6 +220,7 @@ support_bottom_extruder_nr support_type support_angle support_pattern +support_wall_count zig_zaggify_support support_connect_zigzags support_infill_rate From bc965bacfc9528d123a39e8061d3e48f3c09771c Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 15 May 2018 16:46:07 +0200 Subject: [PATCH 23/28] CURA-5358 Not use the prefix for installing packages and so no need to rename directories for the packaged files --- cura/CuraPackageManager.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index c765587eef..de57e5417a 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -18,9 +18,6 @@ from UM.Version import Version class CuraPackageManager(QObject): Version = 1 - # The prefix that's added to all files for an installed package to avoid naming conflicts with user created files. - PREFIX_PLACE_HOLDER = "-CP;" - def __init__(self, parent = None): super().__init__(parent) @@ -305,27 +302,15 @@ class CuraPackageManager(QObject): if not os.path.exists(src_dir_path): continue - - # Need to rename the container files so they don't get ID conflicts - to_rename_files = sub_dir_name not in ("plugins",) - self.__installPackageFiles(package_id, src_dir_path, dst_dir_path, need_to_rename_files= to_rename_files) + self.__installPackageFiles(package_id, src_dir_path, dst_dir_path) # Remove the file os.remove(filename) - def __installPackageFiles(self, package_id: str, src_dir: str, dst_dir: str, need_to_rename_files: bool = True) -> None: + def __installPackageFiles(self, package_id: str, src_dir: str, dst_dir: str) -> None: + Logger.log("i", "Moving package {package_id} from {src_dir} to {dst_dir}".format(package_id=package_id, src_dir=src_dir, dst_dir=dst_dir)) shutil.move(src_dir, dst_dir) - # Rename files if needed - if not need_to_rename_files: - return - for root, _, file_names in os.walk(dst_dir): - for filename in file_names: - new_filename = self.PREFIX_PLACE_HOLDER + package_id + "-" + filename - old_file_path = os.path.join(root, filename) - new_file_path = os.path.join(root, new_filename) - os.rename(old_file_path, new_file_path) - # Gets package information from the given file. def getPackageInfo(self, filename: str) -> Dict[str, Any]: with zipfile.ZipFile(filename) as archive: From eab150d7ee87e419ed637c73945f6f95f26fbbe7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 16 May 2018 09:29:03 +0200 Subject: [PATCH 24/28] Only look for user package management path in storage dirs If we happen to find it in the installation (or some other place we can't write to) then it's going to crash. So don't do that. Contributes to issue CURA-5364. --- cura/CuraPackageManager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 322a9639cf..62ebdb9ff2 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -32,6 +32,7 @@ class CuraPackageManager(QObject): candidate_bundled_path = os.path.join(search_path, "bundled_packages.json") if os.path.exists(candidate_bundled_path): self._bundled_package_management_file_path = candidate_bundled_path + for search_path in (Resources.getDataStoragePath(), Resources.getConfigStoragePath()): candidate_user_path = os.path.join(search_path, "packages.json") if os.path.exists(candidate_user_path): self._user_package_management_file_path = candidate_user_path From e53d4aaab686a011e27a190037e121b3f27e1904 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 16 May 2018 09:35:03 +0200 Subject: [PATCH 25/28] 101Hero definition: Set the preferred quality to draft (0.2mm) and remove default layer_height so normal profile is back to 0.1. CURA-5217 --- resources/definitions/101Hero.def.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/101Hero.def.json b/resources/definitions/101Hero.def.json index 620fcfb519..9a371106bd 100644 --- a/resources/definitions/101Hero.def.json +++ b/resources/definitions/101Hero.def.json @@ -8,7 +8,8 @@ "manufacturer": "101Hero", "file_formats": "text/x-gcode", "platform": "101hero-platform.stl", - "supports_usb_connection": true + "supports_usb_connection": true, + "preferred_quality_type": "draft" }, "overrides": { @@ -19,7 +20,6 @@ "machine_depth": { "default_value": 149.86 }, "machine_height": { "default_value": 99.822 }, "machine_center_is_zero": { "default_value": true }, - "layer_height": { "default_value": 0.2 }, "machine_nozzle_size": { "default_value": 0.4 }, "material_diameter": { "default_value": 1.75 }, "machine_head_with_fans_polygon": { From 60a21b3d7de3856d44f03c8c5c125fe7c9d72a51 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 16 May 2018 09:42:34 +0200 Subject: [PATCH 26/28] 3DMaker Starter definition: Set the preferred quality to draft (0.2mm) and remove default layer_height so normal profile is back to 0.1. CURA-5217 --- resources/definitions/maker_starter.def.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/resources/definitions/maker_starter.def.json b/resources/definitions/maker_starter.def.json index e7e6cb5dcd..de0267dc8e 100644 --- a/resources/definitions/maker_starter.def.json +++ b/resources/definitions/maker_starter.def.json @@ -8,7 +8,8 @@ "manufacturer": "3DMaker", "file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj", "icon": "icon_ultimaker2.png", - "platform": "makerstarter_platform.stl" + "platform": "makerstarter_platform.stl", + "preferred_quality_type": "draft" }, "overrides": { @@ -49,9 +50,6 @@ "machine_nozzle_expansion_angle": { "default_value": 45 }, - "layer_height": { - "default_value": 0.2 - }, "layer_height_0": { "default_value": 0.2 }, @@ -167,4 +165,4 @@ "default_value": 2 } } -} \ No newline at end of file +} From 310aee07ac19cfd0695390e4ec69a791445c792a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 16 May 2018 13:39:43 +0200 Subject: [PATCH 27/28] Code style: Brackets on new line As per our code style regulation. Contributes to issue CURA-5296. --- plugins/Toolbox/resources/qml/ToolboxDetailTile.qml | 9 ++++++--- .../resources/qml/ToolboxInstalledTileActions.qml | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml index 739dc4ccfe..da53fc94af 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml @@ -59,14 +59,17 @@ Item id: installButton active: toolbox.isDownloading && toolbox.activePackage == model complete: tile.installed - readyAction: function() { + readyAction: function() + { toolbox.activePackage = model toolbox.startDownload(model.download_url) } - activeAction: function() { + activeAction: function() + { toolbox.cancelDownload() } - completeAction: function() { + completeAction: function() + { toolbox.viewCategory = "installed" } // Don't allow installing while another download is running diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml index 5bbed2351c..8bdec4da5f 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml @@ -18,11 +18,13 @@ Column readyLabel: catalog.i18nc("@action:button", "Update") activeLabel: catalog.i18nc("@action:button", "Updating") completeLabel: catalog.i18nc("@action:button", "Updated") - readyAction: function() { + readyAction: function() + { toolbox.activePackage = model toolbox.update(model.id) } - activeAction: function() { + activeAction: function() + { toolbox.cancelDownload() } // Don't allow installing while another download is running From 1d8a405e8658a95be6ffb6ba81ad7af1c8b8b76f Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 16 May 2018 15:16:16 +0200 Subject: [PATCH 28/28] CURA-5357 Search for license files The old code had the problem that by definition it skipped the `files/` folder and any other dir which of course is where the licenses are. Now, so long as the license is always just called `LICENSE` (which is the convention in open source software), it can be located anywhere. --- cura/CuraPackageManager.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 62ebdb9ff2..0c2c438fcc 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -338,13 +338,7 @@ class CuraPackageManager(QObject): with zipfile.ZipFile(filename) as archive: # Go through all the files and use the first successful read as the result for file_info in archive.infolist(): - is_dir = lambda file_info: file_info.filename.endswith('/') - if is_dir or not file_info.filename.startswith("files/"): - continue - - filename_parts = os.path.basename(file_info.filename.lower()).split(".") - stripped_filename = filename_parts[0] - if stripped_filename in ("license", "licence"): + if file_info.filename.endswith("LICENSE"): Logger.log("d", "Found potential license file '%s'", file_info.filename) try: with archive.open(file_info.filename, "r") as f: