├── .gitattributes ├── .github └── workflows │ ├── conan-package.yml │ ├── process-pull-request.yml │ ├── security_badge.yml │ ├── unit-test-post.yml │ ├── unit-test.yml │ └── update-translation.yml ├── .gitignore ├── CMakeLists.txt ├── FUNDING.yml ├── LICENSE ├── README.md ├── SECURITY.md ├── UM ├── Application.py ├── Backend │ ├── Backend.py │ ├── SignalSocket.py │ └── __init__.py ├── CentralFileStorage.py ├── ColorGenerator.py ├── ColorImage.py ├── ConfigurationErrorMessage.py ├── Controller.py ├── Decorators.py ├── Dictionary.py ├── Event.py ├── Extension.py ├── FastConfigParser.py ├── FileHandler │ ├── FileHandler.py │ ├── FileReader.py │ ├── FileWriter.py │ ├── ReadFileJob.py │ ├── WriteFileJob.py │ └── __init__.py ├── FileProvider.py ├── FlameProfiler.py ├── InputDevice.py ├── Job.py ├── JobQueue.py ├── LockFile.py ├── Logger.py ├── Math │ ├── AxisAlignedBox.py │ ├── Color.py │ ├── Float.py │ ├── Matrix.py │ ├── NumPyUtil.py │ ├── Plane.py │ ├── Polygon.py │ ├── Quaternion.py │ ├── Ray.py │ ├── Vector.py │ └── __init__.py ├── Mesh │ ├── MeshBuilder.py │ ├── MeshData.py │ ├── MeshFileHandler.py │ ├── MeshReader.py │ ├── MeshWriter.py │ ├── ReadMeshJob.py │ └── __init__.py ├── Message.py ├── MimeTypeDatabase.py ├── Operations │ ├── AddSceneNodeOperation.py │ ├── GravityOperation.py │ ├── GroupedOperation.py │ ├── LayFlatOperation.py │ ├── MirrorOperation.py │ ├── Operation.py │ ├── OperationStack.py │ ├── RemoveSceneNodeOperation.py │ ├── RotateOperation.py │ ├── ScaleOperation.py │ ├── SetTransformOperation.py │ ├── TranslateOperation.py │ └── __init__.py ├── OutputDevice │ ├── OutputDevice.py │ ├── OutputDeviceError.py │ ├── OutputDeviceManager.py │ ├── OutputDevicePlugin.py │ ├── ProjectOutputDevice.py │ └── __init__.py ├── PackageManager.py ├── Platform.py ├── PluginError.py ├── PluginObject.py ├── PluginRegistry.py ├── Preferences.py ├── Qt │ ├── Bindings │ │ ├── ActiveToolProxy.py │ │ ├── ApplicationProxy.py │ │ ├── BackendProxy.py │ │ ├── Bindings.py │ │ ├── ContainerProxy.py │ │ ├── ControllerProxy.py │ │ ├── ExtensionModel.py │ │ ├── FileProviderModel.py │ │ ├── MainWindow.py │ │ ├── OpenGLContextProxy.py │ │ ├── OperationStackProxy.py │ │ ├── OutputDeviceManagerProxy.py │ │ ├── OutputDevicesModel.py │ │ ├── PointingRectangle.py │ │ ├── PreferencesProxy.py │ │ ├── ProjectOutputDevicesModel.py │ │ ├── ResourcesProxy.py │ │ ├── SelectionProxy.py │ │ ├── StageModel.py │ │ ├── TableModel.py │ │ ├── Theme.py │ │ ├── ToolModel.py │ │ ├── Utilities.py │ │ ├── ViewModel.py │ │ ├── VisibleMessagesModel.py │ │ ├── Window.py │ │ ├── __init__.py │ │ └── i18nCatalogProxy.py │ ├── Duration.py │ ├── ListModel.py │ ├── QtApplication.py │ ├── QtKeyDevice.py │ ├── QtMouseDevice.py │ ├── QtRenderer.py │ ├── __init__.py │ └── qml │ │ └── UM │ │ ├── ApplicationMenu.qml │ │ ├── BurgerButton.qml │ │ ├── CheckBox.qml │ │ ├── ComponentWithIcon.qml │ │ ├── Dialog.qml │ │ ├── Enums.qml │ │ ├── HelpIcon.qml │ │ ├── ImageButton.qml │ │ ├── Label.qml │ │ ├── Menu.qml │ │ ├── MenuItem.qml │ │ ├── MessageDialog.qml │ │ ├── MessageStack.qml │ │ ├── Preferences │ │ ├── ConfirmRemoveDialog.qml │ │ ├── GeneralPage.qml │ │ ├── MachinesPage.qml │ │ ├── ManagementPage.qml │ │ ├── PreferencesDialog.qml │ │ ├── PreferencesPage.qml │ │ └── RenameDialog.qml │ │ ├── ProgressBar.qml │ │ ├── ScrollBar.qml │ │ ├── ScrollableTextArea.qml │ │ ├── SimpleButton.qml │ │ ├── Slider.qml │ │ ├── StatusIcon.qml │ │ ├── Switch.qml │ │ ├── TabRow.qml │ │ ├── TabRowButton.qml │ │ ├── TextField.qml │ │ ├── ToolTip.qml │ │ ├── ToolbarButton.qml │ │ ├── TooltipArea.qml │ │ ├── UnderlineBackground.qml │ │ ├── Validators │ │ ├── FloatValidator.qml │ │ ├── HexColorValidator.qml │ │ ├── IntListValidator.qml │ │ └── IntValidator.qml │ │ └── qmldir ├── Resources.py ├── SaveFile.py ├── Scene │ ├── Camera.py │ ├── GroupDecorator.py │ ├── Iterator │ │ ├── BreadthFirstIterator.py │ │ ├── DepthFirstIterator.py │ │ ├── Iterator.py │ │ └── __init__.py │ ├── Platform.py │ ├── Scene.py │ ├── SceneNode.py │ ├── SceneNodeDecorator.py │ ├── SceneNodeSettings.py │ ├── Selection.py │ ├── ToolHandle.py │ └── __init__.py ├── Settings │ ├── AdditionalSettingDefinitionsAppender.py │ ├── ContainerFormatError.py │ ├── ContainerProvider.py │ ├── ContainerQuery.py │ ├── ContainerRegistry.py │ ├── ContainerStack.py │ ├── DatabaseContainerMetadataController.py │ ├── DefinitionContainer.py │ ├── DefinitionContainerUnpickler.py │ ├── EmptyInstanceContainer.py │ ├── InstanceContainer.py │ ├── Interfaces.py │ ├── Models │ │ ├── ContainerPropertyProvider.py │ │ ├── ContainerStacksModel.py │ │ ├── DefinitionContainersModel.py │ │ ├── InstanceContainersModel.py │ │ ├── SettingDefinitionsModel.py │ │ ├── SettingPreferenceVisibilityHandler.py │ │ ├── SettingPropertyProvider.py │ │ ├── SettingVisibilityHandler.py │ │ └── __init__.py │ ├── PropertyEvaluationContext.py │ ├── SQLQueryFactory.py │ ├── SettingDefinition.py │ ├── SettingFunction.py │ ├── SettingInstance.py │ ├── SettingRelation.py │ ├── Validator.py │ ├── __init__.py │ └── constant_instance_containers.py ├── Signal.py ├── SortedList.py ├── Stage.py ├── TaskManagement │ ├── HttpRequestData.py │ ├── HttpRequestManager.py │ ├── HttpRequestScope.py │ ├── TaskManager.py │ └── __init__.py ├── Tool.py ├── Trust.py ├── Util.py ├── Version.py ├── VersionUpgrade.py ├── VersionUpgradeManager.py ├── View │ ├── CompositePass.py │ ├── DefaultPass.py │ ├── GL │ │ ├── FrameBufferObject.py │ │ ├── OpenGL.py │ │ ├── OpenGLContext.py │ │ ├── ShaderProgram.py │ │ ├── Texture.py │ │ └── __init__.py │ ├── RenderBatch.py │ ├── RenderPass.py │ ├── Renderer.py │ ├── SelectionPass.py │ ├── View.py │ └── __init__.py ├── Workspace │ ├── WorkspaceFileHandler.py │ ├── WorkspaceMetadataStorage.py │ ├── WorkspaceReader.py │ ├── WorkspaceWriter.py │ └── __init__.py ├── __init__.py └── i18n.py ├── conandata.yml ├── conanfile.py ├── examples ├── definition_query │ └── query.py └── definition_viewer │ ├── DefinitionTreeModel.py │ ├── main.py │ └── main.qml ├── plugins ├── ConsoleLogger │ ├── ConsoleLogger.py │ ├── __init__.py │ └── plugin.json ├── FileHandlers │ ├── OBJReader │ │ ├── OBJReader.py │ │ ├── __init__.py │ │ ├── plugin.json │ │ └── tests │ │ │ ├── TestOBJReader.py │ │ │ ├── negative_indexed.obj │ │ │ ├── negative_interweaved.obj │ │ │ ├── sphere.obj │ │ │ ├── vertex_duplicated.obj │ │ │ ├── vertex_indexed.obj │ │ │ ├── vertex_normal_indexed.obj │ │ │ ├── vertex_texture_indexed.obj │ │ │ └── vertex_texture_normal_indexed.obj │ ├── OBJWriter │ │ ├── OBJWriter.py │ │ ├── __init__.py │ │ └── plugin.json │ ├── STLReader │ │ ├── STLReader.py │ │ ├── __init__.py │ │ └── plugin.json │ └── STLWriter │ │ ├── STLWriter.py │ │ ├── __init__.py │ │ └── plugin.json ├── FileLogger │ ├── FileLogger.py │ ├── __init__.py │ └── plugin.json ├── LocalContainerProvider │ ├── LocalContainerProvider.py │ ├── __init__.py │ └── plugin.json ├── LocalFileOutputDevice │ ├── LocalFileOutputDevice.py │ ├── LocalFileOutputDevicePlugin.py │ ├── __init__.py │ └── plugin.json ├── Tools │ ├── CameraTool │ │ ├── CameraTool.py │ │ ├── __init__.py │ │ ├── plugin.json │ │ └── tests │ │ │ └── TestCameraTool.py │ ├── MirrorTool │ │ ├── MirrorTool.py │ │ ├── MirrorToolHandle.py │ │ ├── __init__.py │ │ └── plugin.json │ ├── RotateTool │ │ ├── RotateTool.py │ │ ├── RotateTool.qml │ │ ├── RotateToolHandle.py │ │ ├── __init__.py │ │ ├── plugin.json │ │ └── tests │ │ │ └── TestRotateTool.py │ ├── ScaleTool │ │ ├── ScaleTool.py │ │ ├── ScaleTool.qml │ │ ├── ScaleToolHandle.py │ │ ├── __init__.py │ │ ├── plugin.json │ │ └── tests │ │ │ └── TestScaleTool.py │ ├── SelectionTool │ │ ├── SelectionTool.py │ │ ├── __init__.py │ │ └── plugin.json │ └── TranslateTool │ │ ├── TranslateTool.py │ │ ├── TranslateTool.qml │ │ ├── TranslateToolHandle.py │ │ ├── __init__.py │ │ ├── plugin.json │ │ └── tests │ │ └── TestTranslateTool.py ├── UpdateChecker │ ├── AnnotatedUpdateMessage.py │ ├── NewBetaVersionMessage.py │ ├── NewVersionMessage.py │ ├── UpdateChecker.py │ ├── __init__.py │ ├── plugin.json │ └── tests │ │ ├── CuraAndCuraBeta1-0-0.json │ │ ├── CuraOnly1-0-0.json │ │ ├── TestUpdateChecker.py │ │ └── __init__.py └── Views │ └── SimpleView │ ├── SimpleView.py │ ├── __init__.py │ └── plugin.json ├── pylint.cfg ├── pytest.ini ├── resources ├── bundled_packages │ └── uranium.json ├── i18n │ ├── cs_CZ │ │ └── uranium.po │ ├── de_DE │ │ └── uranium.po │ ├── es_ES │ │ └── uranium.po │ ├── fi_FI │ │ └── uranium.po │ ├── fr_FR │ │ └── uranium.po │ ├── hu_HU │ │ └── uranium.po │ ├── it_IT │ │ └── uranium.po │ ├── ja_JP │ │ └── uranium.po │ ├── ko_KR │ │ └── uranium.po │ ├── nl_NL │ │ └── uranium.po │ ├── pl_PL │ │ └── uranium.po │ ├── pt_BR │ │ └── uranium.po │ ├── pt_PT │ │ └── uranium.po │ ├── ru_RU │ │ └── uranium.po │ ├── tr_TR │ │ └── uranium.po │ ├── uranium.pot │ ├── zh_CN │ │ └── uranium.po │ └── zh_TW │ │ └── uranium.po └── shaders │ ├── color.shader │ ├── composite.shader │ ├── default.shader │ ├── object.shader │ ├── platform.shader │ ├── select_face.shader │ ├── selection.shader │ └── toolhandle.shader ├── scripts ├── compile-shaders ├── signfile.py └── signfolder.py ├── test_package ├── conanfile.py └── test.py └── tests ├── Bindings ├── TestActiveToolProxy.py ├── TestOutputDeviceManagerProxy.py ├── TestPointingRectangle.py ├── TestSelectionProxy.py └── TestToolModel.py ├── Jobs ├── TestJob.py └── TestJobQueue.py ├── Math ├── TestAxisAlignedBox.py ├── TestMatrix.py ├── TestPlane.py ├── TestPolygon.py ├── TestQuaternion.py └── TestVector.py ├── Mesh ├── TestMeshBuilder.py └── TestMeshData.py ├── MimeTypes ├── TestMimeTypes.py ├── file.long.test └── file.test ├── Operations ├── TestAddSceneNodeOperation.py ├── TestGroupedOperation.py ├── TestOperationStack.py └── TestScaleOperation.py ├── PluginRegistry ├── EmptyPlugin │ ├── __init__.py │ └── plugin.json ├── OldTestPlugin │ └── __init__.py ├── PluginNoVersionNumber │ ├── __init__.py │ └── plugin.json ├── TestNested │ └── TestPlugin2 │ │ ├── __init__.py │ │ └── plugin.json ├── TestPlugin │ ├── __init__.py │ └── plugin.json ├── TestPluginRegistry.py └── UraniumExampleExtensionPlugin.umplugin ├── SaveFile └── TestSaveFile.py ├── Scene ├── TestCamera.py ├── TestScene.py ├── TestSceneNode.py ├── TestSceneNodeDecorator.py └── TestSelection.py ├── Settings ├── ContainerTestPlugin │ ├── ContainerTestPlugin.py │ ├── __init__.py │ └── plugin.json ├── MockContainer.py ├── TestAppendAdditionalSettings.py ├── TestContainerQuery.py ├── TestContainerRegistry.py ├── TestContainerStack.py ├── TestDefinitionContainer.py ├── TestFastConfigParser.py ├── TestInstanceContainer.py ├── TestInstanceContainersModel.py ├── TestRoundtripping.py ├── TestSettingDefinition.py ├── TestSettingDefinitionsModel.py ├── TestSettingFunction.py ├── TestSettingInstance.py ├── TestSettingPropertyProvider.py ├── TestSettingRelation.py ├── TestValidator.py ├── __init__.py ├── additional_settings │ └── append_extra_settings.def.json ├── config_parser_files │ ├── multi_line.cfg │ ├── spacing.cfg │ └── weird_values.cfg ├── conftest.py ├── definitions │ ├── basic_definition.def.json │ ├── children.def.json │ ├── functions.def.json │ ├── inherits.def.json │ ├── metadata_definition.def.json │ ├── multiple_settings.def.json │ └── single_setting.def.json └── instances │ ├── basic_instance.inst.cfg │ ├── metadata_instance.inst.cfg │ └── setting_values.inst.cfg ├── Shaders ├── empty.shader ├── invalid.shader ├── invalid2.shader └── test.shader ├── TaskManagement ├── TestHttpRequestManager.py └── conftest.py ├── TestBackend.py ├── TestCentralFileStorage.py ├── TestColor.py ├── TestContainerPropertyProvider.py ├── TestController.py ├── TestDecorators.py ├── TestDepthFirstDecorator.py ├── TestDictionary.py ├── TestDuration.py ├── TestExtension.py ├── TestFileHandler.py ├── TestFileWriter.py ├── TestListModel.py ├── TestMessage.py ├── TestOpenGLContext.py ├── TestOutputDevice.py ├── TestOutputDeviceManager.py ├── TestPackageManager.py ├── TestPluginObject.py ├── TestPreferences.py ├── TestQtRenderer.py ├── TestRenderBatch.py ├── TestRenderer.py ├── TestResources.py ├── TestShaderProgram.py ├── TestSignals.py ├── TestSortedList.py ├── TestSortedListModulo.py ├── TestStage.py ├── TestTheme.py ├── TestToolHandle.py ├── TestTools.py ├── TestTrust.py ├── TestUrlUtil.py ├── TestUtil.py ├── TestVersion.py ├── TestWorkspaceMetadataStorage.py ├── UnitTestPackage.package ├── __init__.py ├── benchmarks ├── Math │ └── profile_rayintersection.py ├── MeshData │ ├── profile_addvertex.py │ ├── profile_calcnormals.py │ ├── profile_getbytearray.py │ └── profile_reserve.py ├── Settings │ ├── BenchmarkContainerRegistry.py │ └── BenchmarkSettingDefinition.py └── conftest.py ├── conftest.py ├── preferences_test.cfg └── test_theme ├── icons └── test.svg ├── images └── kitten.jpg └── theme.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Always use line feeds, no carriage return! 2 | *.py text eol=lf -------------------------------------------------------------------------------- /.github/workflows/conan-package.yml: -------------------------------------------------------------------------------- 1 | name: conan-package 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'plugins/**' 7 | - 'resources/**' 8 | - 'UM/**' 9 | - 'conanfile.py' 10 | - 'conandata.yml' 11 | - '.github/workflows/conan-package.yml' 12 | branches: 13 | - main 14 | - 'CURA-*' 15 | - 'PP-*' 16 | - 'NP-*' 17 | - '[0-9].[0-9]*' 18 | - '[0-9].[0-9][0-9]*' 19 | - 20 | 21 | jobs: 22 | conan-package: 23 | uses: ultimaker/cura-workflows/.github/workflows/conan-package.yml@main 24 | with: 25 | platform_windows: false 26 | platform_mac: false 27 | secrets: inherit 28 | -------------------------------------------------------------------------------- /.github/workflows/process-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: process-pull-request 2 | 3 | on: 4 | pull_request_target: 5 | types: [ opened, reopened, edited, review_requested, ready_for_review, assigned ] 6 | 7 | # FIXME: Use `main` instead of `CURA-10831` once merged 8 | jobs: 9 | add_label: 10 | uses: ultimaker/cura-workflows/.github/workflows/process-pull-request.yml@main 11 | secrets: inherit -------------------------------------------------------------------------------- /.github/workflows/unit-test-post.yml: -------------------------------------------------------------------------------- 1 | name: unit-test-post 2 | 3 | on: 4 | workflow_run: 5 | workflows: [ unit-test ] 6 | types: [ completed ] 7 | 8 | jobs: 9 | publish-test-results: 10 | uses: ultimaker/cura-workflows/.github/workflows/unit-test-post.yml@main 11 | with: 12 | workflow_run_json: ${{ toJSON(github.event.workflow_run) }} 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: unit-test 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'plugins/**' 7 | - 'resources/**' 8 | - 'UM/**' 9 | - 'tests/**' 10 | - '.github/workflows/unit-test.yml' 11 | - 'requirements*.txt' 12 | - 'conanfile.py' 13 | - 'conandata.yml' 14 | - '*.jinja' 15 | branches: 16 | - main 17 | - 'CURA-*' 18 | - 'PP-*' 19 | - '[0-9]+.[0-9]+' 20 | 21 | pull_request: 22 | paths: 23 | - 'plugins/**' 24 | - 'resources/**' 25 | - 'UM/**' 26 | - 'icons/**' 27 | - 'tests/**' 28 | - '.github/workflows/unit-test.yml' 29 | - 'requirements*.txt' 30 | - 'conanfile.py' 31 | - 'conandata.yml' 32 | - '*.jinja' 33 | branches: 34 | - main 35 | - '[0-9]+.[0-9]+' 36 | 37 | jobs: 38 | testing: 39 | name: Run unit tests 40 | uses: ultimaker/cura-workflows/.github/workflows/unit-test.yml@main 41 | with: 42 | test_use_pytest: true 43 | -------------------------------------------------------------------------------- /.github/workflows/update-translation.yml: -------------------------------------------------------------------------------- 1 | name: Update translations 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | branch: 7 | description: 'Specific branch to update translations on' 8 | required: false 9 | type: string 10 | 11 | jobs: 12 | update-translations: 13 | uses: ultimaker/cura-workflows/.github/workflows/update-translations.yml@main 14 | with: 15 | branch: ${{ inputs.branch }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake 2 | CMakeFiles/* 3 | cmake_install.cmake 4 | CMakeCache.txt 5 | CPackSourceConfig.cmake 6 | 7 | # CMake - Makefile generator 8 | Makefile 9 | 10 | # Build results 11 | build 12 | LC_MESSAGES 13 | *.mo 14 | *.qm 15 | *.qmlc 16 | *.qsb 17 | resources/i18n/en_US 18 | resources/i18n/x-test 19 | resources/i18n/en_7S 20 | .mypy_cache 21 | .coverage* 22 | htmlcov/ 23 | 24 | # JUnit 25 | junit.xml 26 | junit-pytest-* 27 | 28 | # Different files 29 | *.pyc 30 | *kdev* 31 | __pycache__ 32 | docs/html 33 | *.lprof 34 | *.log 35 | *~ 36 | Uranium.e4p 37 | _eric6project/Uranium.e4q 38 | _eric6project/Uranium.e4t 39 | .cache 40 | *.kate-swp 41 | *.bak 42 | 43 | ## Eclipse+PyDev incl. Mylyn 44 | .project 45 | .settings/org.eclipse.mylyn.tasks.ui.prefs 46 | .pydevproject 47 | 48 | ## IntelliJ projects 49 | .idea 50 | 51 | #Test results. 52 | tests/Settings/instances/machine_settings_with_overrides_test.cfg 53 | tests/Settings/profiles/simple_machine_with_overrides_test.cfg 54 | .pytest_cache/ 55 | 56 | #MacOS 57 | .DS_Store 58 | UM/.DS_Store 59 | resources/.DS_Store 60 | 61 | #Common plug-ins 62 | plugins/UraniumExample*Plugin 63 | /conanbuildinfo.txt 64 | /graph_info.json 65 | /venv/ 66 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | # For MSVC flags, will be ignored on non-Windows OS's and this project in general. Only needed for cura-build-environment. 5 | cmake_policy(SET CMP0091 NEW) 6 | project(uranium NONE) 7 | 8 | cmake_minimum_required(VERSION 3.18) 9 | 10 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) 11 | 12 | include(GNUInstallDirs) 13 | 14 | include(UraniumTranslationTools) 15 | 16 | # Extract Strings 17 | add_custom_target(extract-messages ${CMAKE_SOURCE_DIR}/scripts/extract-messages ${CMAKE_SOURCE_DIR} uranium) 18 | 19 | # Build Translations 20 | CREATE_TRANSLATION_TARGETS() 21 | 22 | 23 | install(DIRECTORY UM DESTINATION "${Python_SITELIB_LOCAL}") 24 | 25 | install(FILES ${CMAKE_SOURCE_DIR}/cmake/UraniumTranslationTools.cmake 26 | DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake-${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}/Modules/ ) 27 | install(DIRECTORY resources DESTINATION ${CMAKE_INSTALL_DATADIR}/uranium) 28 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ultimaker] 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 | [![Badge License]][License]    7 | [![Badge Contributors]][Contributors]    8 | [![Badge Size]][#] 9 | 10 | [![Badge Conan]][Conan]    11 | [![Badge Test]][Test] 12 | 13 |
14 |
15 | 16 | # Uranium 17 | 18 | *Python framework for 3D printing applications.* 19 | 20 |
21 |
22 | 23 | [![Button Developing]][Developing] 24 | 25 |
26 | 27 | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Ultimaker/Uranium/badge)](https://api.securityscorecards.dev/projects/github.com/Ultimaker/Uranium) 28 | 29 |
30 | 31 | 32 | 33 | [Contributors]: https://github.com/Ultimaker/Uranium/graphs/contributors 34 | [Plugins]: https://github.com/Ultimaker/Uranium/wiki/Plugins 35 | [Conan]: https://github.com/Ultimaker/Uranium/actions/workflows/conan-package.yml 36 | [Test]: https://github.com/Ultimaker/Uranium/actions/workflows/unit-test.yml 37 | 38 | [Developing]: https://github.com/Ultimaker/Uranium/wiki/Building-And-Developing 39 | [License]: LICENSE 40 | [#]: # 41 | 42 | 43 | 44 | 45 | [Badge Contributors]: https://img.shields.io/github/contributors/ultimaker/Uranium?style=for-the-badge&logoColor=white&labelColor=db5e8a&color=ab4a6c&logo=GitHub 46 | [Badge License]: https://img.shields.io/badge/License-LGPL3-336887.svg?style=for-the-badge&labelColor=458cb5&logoColor=white&logo=GNU 47 | [Badge Conan]: https://img.shields.io/github/workflow/status/Ultimaker/Uranium/conan-package?style=for-the-badge&logoColor=white&labelColor=6185aa&color=4c6987&logo=Conan&label=Conan%20Package 48 | [Badge Test]: https://img.shields.io/github/workflow/status/Ultimaker/Uranium/unit-test?style=for-the-badge&logoColor=white&labelColor=715a97&color=584674&logo=Codacy&label=Unit%20Test 49 | [Badge Size]: https://img.shields.io/github/repo-size/ultimaker/Uranium?style=for-the-badge&logoColor=white&labelColor=629944&color=446a30&logo=GoogleAnalytics 50 | 51 | 52 | 53 | 54 | [Button Developing]: https://img.shields.io/badge/Developing-715a97?style=for-the-badge&logoColor=white&logo=VisualStudioCode 55 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting vulnerabilities 2 | 3 | If you discover a vulnerability, please let us know as soon as possible via `security@ultimaker.com`. 4 | Please do not take advantage of the vulnerability and do not reveal the problem to others. 5 | To allow us to resolve the issue, please do provide us with sufficient information to reproduce the problem. 6 | -------------------------------------------------------------------------------- /UM/Backend/SignalSocket.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import pyArcus as Arcus 5 | 6 | from UM.Signal import Signal, signalemitter 7 | 8 | 9 | @signalemitter 10 | class SignalSocket(Arcus.Socket): 11 | """A small extension of an Arcus socket that emits queued signals when socket events happen.""" 12 | 13 | def __init__(self): 14 | super().__init__() 15 | 16 | self._listener = _SocketListener() 17 | self._listener.stateChangedCallback = self._onStateChanged 18 | self._listener.messageReceivedCallback = self._onMessageReceived 19 | self._listener.errorCallback = self._onError 20 | self.addListener(self._listener) 21 | 22 | stateChanged = Signal() 23 | messageReceived = Signal() 24 | error = Signal() 25 | 26 | def _onStateChanged(self, state): 27 | self.stateChanged.emit(state) 28 | 29 | def _onMessageReceived(self): 30 | self.messageReceived.emit() 31 | 32 | def _onError(self, error): 33 | self.error.emit(error) 34 | 35 | class _SocketListener(Arcus.SocketListener): 36 | def __init__(self): 37 | super().__init__() 38 | 39 | self.stateChangedCallback = None 40 | self.messageReceivedCallback = None 41 | self.errorCallback = None 42 | 43 | def stateChanged(self, state): 44 | try: 45 | if self.stateChangedCallback: 46 | self.stateChangedCallback(state) 47 | except AttributeError: 48 | # For some reason, every so often, it seems to feel that the attribute stateChangedCallback doesn't exist. 49 | # Ignoring this prevents crashes. 50 | pass 51 | 52 | def messageReceived(self): 53 | if self.messageReceivedCallback: 54 | self.messageReceivedCallback() 55 | 56 | def error(self, error): 57 | if self.errorCallback: 58 | self.errorCallback(error) 59 | -------------------------------------------------------------------------------- /UM/Backend/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #Shoopdawoop 5 | 6 | ## \package Backend 7 | # Classes intended for backend communication. 8 | -------------------------------------------------------------------------------- /UM/Dictionary.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from typing import Dict, Any 5 | 6 | """Helper functions for dealing with Python dictionaries.""" 7 | 8 | 9 | def findKey(dictionary: Dict[Any, Any], search_value: Any) -> Any: 10 | """Find the key corresponding to a certain value 11 | 12 | :param dictionary: :type{dict} The dictionary to search for the value 13 | :param search_value: The value to search for. 14 | 15 | :return: The key matching to value. Note that if the dictionary contains multiple instances of value it is undefined which exact key is returned. 16 | 17 | :exception ValueError: is raised when the value is not found in the dictionary. 18 | """ 19 | 20 | for key, value in dictionary.items(): 21 | if value == search_value: 22 | return key 23 | 24 | raise ValueError("Value {0} not found in dictionary".format(search_value)) 25 | -------------------------------------------------------------------------------- /UM/Extension.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.PluginObject import PluginObject 5 | import collections 6 | from typing import Optional, Any, Callable, List, Dict 7 | 8 | 9 | class Extension(PluginObject): 10 | """Base class for plugins that extend the functionality of Uranium. 11 | 12 | Every extension adds a (sub) menu to the extension menu with one or 13 | more menu items. 14 | """ 15 | 16 | def __init__(self) -> None: 17 | super().__init__() 18 | self._menu_function_dict = collections.OrderedDict() # type: Dict[str, Callable[[], Any]] 19 | self._menu_name = None # type: Optional[str] 20 | 21 | def addMenuItem(self, name: str, function: Callable[[], Any]) -> None: 22 | """Add an item to the sub-menu of the extension 23 | 24 | :param name: :type{string} 25 | :param function: :type{function} 26 | """ 27 | 28 | self._menu_function_dict[name] = function 29 | 30 | def setMenuName(self, name: str) -> None: 31 | """Set name of the menu where all menu items are placed in 32 | 33 | :param name: :type{string} 34 | """ 35 | 36 | self._menu_name = name 37 | 38 | def getMenuName(self) -> Optional[str]: 39 | """Get the name of the menu where all menu items are placed in""" 40 | 41 | return self._menu_name 42 | 43 | def activateMenuItem(self, name: str) -> None: 44 | """Call function associated with option 45 | 46 | :param name: :type{string} 47 | """ 48 | 49 | if name in self._menu_function_dict: 50 | self._menu_function_dict[name]() 51 | 52 | def getMenuItemList(self) -> List[str]: 53 | """Get list of all menu item names 54 | 55 | :return: :type{list} 56 | """ 57 | 58 | return list(self._menu_function_dict.keys()) 59 | -------------------------------------------------------------------------------- /UM/FastConfigParser.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Dict 2 | 3 | import re 4 | 5 | supported_data = Union[int, float, str] 6 | 7 | 8 | class FastConfigParser: 9 | """ 10 | This class is to replace the much slower configparser provided by Python itself. 11 | It's probably nowhere near as robust and supports only a fraction of the functionality of the real deal. 12 | 13 | In it's current state it supports reading config headers and the key value pairs beneath it. 14 | It also supports the contains syntax (So if the config has a header [Foo], "Foo" in config will be true) as well 15 | as the getItem syntax config["foo"] returns a dict with the key value pairs in the header. 16 | """ 17 | header_regex = re.compile(r"\[(\w+?)\]\n(.*?)(?:(?=\n\[(?:\w+?)\])|\Z)", re.S) 18 | key_value_regex = re.compile(r"([^=\n !]+)[ \t]*=[ \t]*(.*?)(?:(?=\s+(?:^[^=\n\t !<>\[]+)[ \t]*=[ \t]*[^=])|(?=\n\[)|\Z)", flags = re.S|re.M) 19 | 20 | def __init__(self, data: str) -> None: 21 | header_result = self.header_regex.findall(data) 22 | 23 | self._parsed_data = {} # type: Dict[str, Dict[str, supported_data]] 24 | 25 | for header, content in header_result: 26 | extracted_key_value_pairs = {} 27 | for key, value in self.key_value_regex.findall(content.rstrip()): 28 | # Multiline are stored with a tab, so we need to remove that again 29 | extracted_key_value_pairs[key] = value.replace("\n\t", "\n") 30 | self._parsed_data[header] = extracted_key_value_pairs 31 | 32 | def __contains__(self, key: str) -> bool: 33 | return key in self._parsed_data 34 | 35 | def __getitem__(self, key: str) -> Dict[str, supported_data]: 36 | return self._parsed_data[key] 37 | 38 | def __iter__(self): 39 | return iter(self._parsed_data) 40 | -------------------------------------------------------------------------------- /UM/FileHandler/FileWriter.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | from UM.PluginObject import PluginObject 4 | 5 | 6 | class FileWriter(PluginObject): 7 | """Base class for writer objects""" 8 | 9 | class OutputMode: 10 | TextMode = 1 11 | BinaryMode = 2 12 | 13 | def __init__(self, add_to_recent_files: bool = True, *args, **kwargs) -> None: 14 | super().__init__() 15 | self._information = "" # type: str 16 | 17 | # Indicates if the file should be added to the "recent files" list if it's saved successfully. 18 | self._add_to_recent_files = add_to_recent_files # type: bool 19 | 20 | def getAddToRecentFiles(self) -> bool: 21 | return self._add_to_recent_files 22 | 23 | def write(self, stream, data, mode = OutputMode.TextMode): 24 | raise NotImplementedError("Writer plugin was not correctly implemented, no write was specified") 25 | 26 | def setInformation(self, information_message: str): 27 | self._information = information_message 28 | 29 | def getInformation(self) -> str: 30 | return self._information 31 | -------------------------------------------------------------------------------- /UM/FileHandler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/UM/FileHandler/__init__.py -------------------------------------------------------------------------------- /UM/FileProvider.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtCore import pyqtSignal, QObject 5 | 6 | from UM.PluginObject import PluginObject 7 | from typing import Optional 8 | 9 | 10 | class FileProvider(PluginObject, QObject): 11 | """Base class for plugins that aim to provide a file to Cura in an alternate fashion, other than using the local file 12 | explorer. 13 | 14 | Every new file provider adds an option to the Open File(s) menu. 15 | """ 16 | 17 | enabledChanged = pyqtSignal() 18 | """Signal which informs whether the file provider has been enabled or disabled, so that it can be removed or added 19 | in the Open File(s) submenu""" 20 | 21 | def __init__(self) -> None: 22 | PluginObject.__init__(self) 23 | QObject.__init__(self) 24 | 25 | self.menu_item_display_text = None # type: Optional[str] 26 | """ 27 | Text that will be displayed as an option in the Open File(s) menu. 28 | """ 29 | 30 | self.shortcut = None # type: Optional[str] 31 | """ 32 | Shortcut key combination (e.g. "Ctrl+O"). 33 | """ 34 | 35 | self.enabled = True 36 | """ 37 | If the provider is not enabled, it should not be displayed in the interface. 38 | """ 39 | 40 | self.priority = 0 41 | """ 42 | Where it should be sorted in lists, or which should be tried first. 43 | """ 44 | 45 | def run(self) -> None: 46 | """Call function associated with the file provider""" 47 | raise NotImplementedError 48 | -------------------------------------------------------------------------------- /UM/InputDevice.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Signal import Signal, signalemitter 5 | from UM.PluginObject import PluginObject 6 | 7 | 8 | @signalemitter 9 | class InputDevice(PluginObject): 10 | """Abstract base class for all input devices (Human Input Devices) 11 | 12 | Examples of this are mouse & keyboard 13 | """ 14 | 15 | def __init__(self) -> None: 16 | super().__init__() 17 | 18 | event = Signal() 19 | """Emitted whenever the device produces an event. 20 | 21 | All actions performed with the device should be seen as an event. 22 | :param event: The event that is emitted. 23 | """ 24 | -------------------------------------------------------------------------------- /UM/Math/Float.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | 5 | class Float: 6 | """Class containing helper functions for dealing with IEEE-754 floating point numbers.""" 7 | 8 | @staticmethod 9 | def fuzzyCompare(f1: float, f2: float, tolerance: float = 1e-8) -> bool: 10 | """Compare two floats to check if they are equal with a tolerance value. 11 | 12 | This method will compare two floats and check whether they are equal to 13 | within a certain tolerance value. 14 | 15 | :param f1: :type{float} The first value to compare. 16 | :param f2: :type{float} The second value to compare. 17 | :param tolerance: The amount of tolerance used to consider the two numbers "equal". 18 | 19 | :return: True if the two numbers are considered equal, False if not. 20 | """ 21 | 22 | if f1 == f2: 23 | return True 24 | 25 | return f1 > (f2 - tolerance) and f1 < (f2 + tolerance) 26 | 27 | @staticmethod 28 | def clamp(f1: float, minimum: float, maximum: float) -> float: 29 | """Return the value clamped to a minimum and maximum value. 30 | 31 | :param f1: :type{float} The value to clamp. 32 | :param minimum: :type{float} The minimum value. 33 | :param maximum: :type{float} The maximum value. 34 | 35 | :return: :type{float} Minimum if f1 < minimum, maximum if f1 > maximum, else f1. 36 | """ 37 | 38 | return min(max(f1, minimum), maximum) 39 | -------------------------------------------------------------------------------- /UM/Math/NumPyUtil.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | from typing import Union, List, cast 4 | 5 | import numpy 6 | from copy import deepcopy 7 | 8 | 9 | def immutableNDArray(nda: Union[List, numpy.ndarray]) -> numpy.ndarray: 10 | """Creates an immutable copy of the given narray 11 | 12 | If the array is already immutable then it just returns it. 13 | :param nda: :type{numpy.ndarray} the array to copy. May be a list 14 | :return: :type{numpy.ndarray} an immutable narray 15 | """ 16 | 17 | if nda is None: 18 | return None 19 | 20 | if type(nda) is list: 21 | data = numpy.array(nda, numpy.float32) 22 | data.flags.writeable = False 23 | else: 24 | data = cast(numpy.ndarray, nda) 25 | if not data.flags.writeable: 26 | return data 27 | 28 | copy = deepcopy(data) 29 | copy.flags.writeable = False 30 | return copy 31 | -------------------------------------------------------------------------------- /UM/Math/Plane.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Math.Vector import Vector 5 | from UM.Math.Float import Float 6 | 7 | 8 | class Plane: 9 | """Plane representation using normal and distance.""" 10 | 11 | def __init__(self, normal = Vector(), distance = 0.0): 12 | super().__init__() 13 | 14 | self._normal = normal 15 | self._distance = distance 16 | 17 | @property 18 | def normal(self): 19 | return self._normal 20 | 21 | @property 22 | def distance(self): 23 | return self._distance 24 | 25 | def intersectsRay(self, ray): 26 | w = ray.origin - (self._normal * self._distance) 27 | 28 | nDotR = self._normal.dot(ray.direction) 29 | nDotW = -self._normal.dot(w) 30 | 31 | if Float.fuzzyCompare(nDotR, 0.0): 32 | return False 33 | 34 | t = nDotW / nDotR 35 | if t < 0: 36 | return False 37 | 38 | return t 39 | 40 | def __repr__(self): 41 | return "Plane(normal = {0}, distance = {1})".format(self._normal, self._distance) 42 | -------------------------------------------------------------------------------- /UM/Math/Ray.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Math.Vector import Vector 5 | 6 | 7 | class Ray: 8 | def __init__(self, origin: Vector = Vector(), direction: Vector = Vector()) -> None: 9 | self._origin = origin 10 | self._direction = direction 11 | self._inverse_direction = 1.0 / direction 12 | 13 | @property 14 | def origin(self) -> Vector: 15 | return self._origin 16 | 17 | @property 18 | def direction(self) -> Vector: 19 | return self._direction 20 | 21 | @property 22 | def inverseDirection(self) -> Vector: 23 | return self._inverse_direction 24 | 25 | def getPointAlongRay(self, distance: float) -> Vector: 26 | return self._origin + (self._direction * distance) 27 | 28 | def __repr__(self): 29 | return "Ray(origin = {0}, direction = {1})".format(self._origin, self._direction) 30 | -------------------------------------------------------------------------------- /UM/Math/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #Shoopdawoooop! Charging ze las0r 5 | 6 | ## \package Math 7 | # Math related classes. 8 | -------------------------------------------------------------------------------- /UM/Mesh/MeshReader.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from typing import Union, List 5 | 6 | import UM.Application 7 | from UM.FileHandler.FileReader import FileReader 8 | from UM.FileHandler.FileHandler import resolveAnySymlink 9 | from UM.Logger import Logger 10 | from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError 11 | from UM.Scene.SceneNode import SceneNode 12 | 13 | 14 | class MeshReader(FileReader): 15 | def __init__(self) -> None: 16 | super().__init__() 17 | 18 | def read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]: 19 | """Read mesh data from file and returns a node that contains the data 20 | Note that in some cases you can get an entire scene of nodes in this way (eg; 3MF) 21 | 22 | :return: node :type{SceneNode} or :type{list(SceneNode)} The SceneNode or SceneNodes read from file. 23 | """ 24 | 25 | file_name = resolveAnySymlink(file_name) 26 | result = self._read(file_name) 27 | UM.Application.Application.getInstance().getController().getScene().addWatchedFile(file_name) 28 | 29 | # The mesh reader may set a MIME type itself if it knows a more specific MIME type than just going by extension. 30 | # If not, automatically generate one from our MIME type database, going by the file extension. 31 | if not isinstance(result, list): 32 | meshes = [result] 33 | else: 34 | meshes = result 35 | for mesh in meshes: 36 | if mesh.source_mime_type is None: 37 | try: 38 | mesh.source_mime_type = MimeTypeDatabase.getMimeTypeForFile(file_name) 39 | except MimeTypeNotFoundError: 40 | Logger.warning(f"Loaded file {file_name} has no associated MIME type.") 41 | # Leave MIME type at None then. 42 | 43 | return result 44 | 45 | def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]: 46 | raise NotImplementedError("MeshReader plugin was not correctly implemented, no read was specified") 47 | -------------------------------------------------------------------------------- /UM/Mesh/MeshWriter.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.FileHandler.FileWriter import FileWriter 5 | from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator 6 | from UM.Scene.SceneNode import SceneNode 7 | 8 | 9 | class MeshWriter(FileWriter): 10 | """Base class for mesh writer objects""" 11 | 12 | def __init__(self, *args, **kwargs): 13 | super().__init__(*args, **kwargs) 14 | 15 | def write(self, stream, node, mode = FileWriter.OutputMode.BinaryMode, **kwargs): 16 | """Output a collection of nodes to stream in such a way that it makes sense 17 | for the file format. 18 | 19 | For example, in case of STL, it makes sense to go through all children 20 | of the nodes and write all those as transformed vertices to a single 21 | file. 22 | 23 | :param stream: :type{IOStream} The stream to output to. 24 | :param node: A collection of scene nodes to write to the stream. 25 | :param kwargs extra arguments to customize the output (e.g. target mime_type) 26 | """ 27 | 28 | raise NotImplementedError("MeshWriter plugin was not correctly implemented, no write was specified") 29 | 30 | @staticmethod 31 | def _meshNodes(nodes): 32 | """Filters a collection of nodes to only include nodes that are actual 33 | meshes. 34 | 35 | This does not include auxiliary nodes such as tool handles. 36 | 37 | :param nodes: A sequence of nodes. 38 | :return: The nodes among those that are actual scene nodes. 39 | """ 40 | 41 | for root in nodes: 42 | yield from filter( 43 | lambda child: isinstance(child, SceneNode) and child.isSelectable() and child.getMeshData(), 44 | BreadthFirstIterator(root) 45 | ) -------------------------------------------------------------------------------- /UM/Mesh/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #Shoopdawoop 5 | 6 | ## \package Mesh 7 | # Classes for loading and working with mesh data. 8 | -------------------------------------------------------------------------------- /UM/Operations/AddSceneNodeOperation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Operations.Operation import Operation 5 | 6 | from UM.Scene.Selection import Selection 7 | 8 | from UM.Scene.SceneNode import SceneNode 9 | 10 | from typing import Optional 11 | 12 | 13 | class AddSceneNodeOperation(Operation): 14 | """Operation that adds a new node to the scene.""" 15 | 16 | def __init__(self, node: SceneNode, parent: Optional[SceneNode]) -> None: 17 | """Creates the scene node operation. 18 | 19 | This saves the node and its parent to be able to search for the node to 20 | remove the node if we want to undo, and to be able to re-do the adding 21 | of the node. 22 | 23 | :param node: The node to add to the scene. 24 | :param parent: The parent of the new node. 25 | """ 26 | 27 | super().__init__() 28 | self._node = node 29 | self._parent = parent 30 | self._selected = False # Was the node selected while the operation is undone? If so, we must re-select it when redoing it. 31 | 32 | def undo(self) -> None: 33 | """Reverses the operation of adding a scene node. 34 | 35 | This removes the scene node again. 36 | """ 37 | 38 | self._node.setParent(None) 39 | self._selected = Selection.isSelected(self._node) 40 | if self._selected: 41 | Selection.remove(self._node) # Also remove the node from the selection. 42 | 43 | def redo(self) -> None: 44 | """Re-applies this operation after it has been undone.""" 45 | 46 | self._node.setParent(self._parent) 47 | if self._selected: # It was selected while the operation was undone. We should restore that selection. 48 | Selection.add(self._node) 49 | -------------------------------------------------------------------------------- /UM/Operations/GravityOperation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Scene.SceneNode import SceneNode 5 | 6 | from . import Operation 7 | from UM.Math.Vector import Vector 8 | 9 | 10 | class GravityOperation(Operation.Operation): 11 | """An operation that moves a scene node down to 0 on the y-axis. 12 | """ 13 | 14 | def __init__(self, node): 15 | """Initialises this GravityOperation. 16 | 17 | :param node: The node to translate. 18 | """ 19 | 20 | super().__init__() 21 | self._node = node 22 | self._old_transformation = node.getLocalTransformation() # To restore the transformation to in case of an undo. 23 | 24 | def undo(self): 25 | """Undoes the gravity operation, restoring the old transformation.""" 26 | 27 | self._node.setTransformation(self._old_transformation) 28 | 29 | def redo(self): 30 | """(Re-)Applies the gravity operation.""" 31 | 32 | # Drop to bottom of usable space (if not already there): 33 | height_move = -self._node.getBoundingBox().bottom 34 | if self._node.hasDecoration("getZOffset"): 35 | height_move += self._node.callDecoration("getZOffset") 36 | if abs(height_move) > 1e-5: 37 | self._node.translate(Vector(0.0, height_move, 0.0), SceneNode.TransformSpace.World) 38 | 39 | def mergeWith(self, other): 40 | """Merges this operation with another gravity operation. 41 | 42 | This prevents the user from having to undo multiple operations if they 43 | were not his operations. 44 | 45 | You should ONLY merge this operation with an older operation. It is NOT 46 | symmetric. 47 | 48 | :param other: The older gravity operation to merge this operation with. 49 | """ 50 | 51 | if type(other) is not GravityOperation: 52 | return False 53 | if other._node != self._node: # Must be moving the same node. 54 | return False 55 | return other 56 | 57 | def __repr__(self): 58 | """Returns a programmer-readable representation of this operation. 59 | 60 | :return: A programmer-readable representation of this operation. 61 | """ 62 | 63 | return "GravityOp.(node={0})".format(self._node) 64 | -------------------------------------------------------------------------------- /UM/Operations/Operation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import time 5 | 6 | 7 | class Operation: 8 | """Base class for operations that should support undo and redo.""" 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | self._timestamp = time.time() 13 | self._always_merge = False 14 | 15 | def undo(self) -> None: 16 | """Undo the operation. 17 | 18 | This should be reimplemented by subclasses to perform all actions necessary to 19 | redo the operation. 20 | """ 21 | 22 | raise NotImplementedError("Undo should be reimplemented by subclasses") 23 | 24 | def redo(self) -> None: 25 | """Redo the operation. 26 | 27 | This should be reimplemented by subclasses to perform all actions necessary to 28 | redo the operation. 29 | 30 | :note This is automatically called when the operation is first put onto the OperationStack. 31 | """ 32 | 33 | raise NotImplementedError("Redo should be reimplemented by subclasses") 34 | 35 | def mergeWith(self, other): 36 | """Perform operation merging. 37 | 38 | This will be called by OperationStack to perform merging of operations. 39 | If this operation can be merged with `other`, it should return a new operation that 40 | is the combination of this operation and `other`. If it cannot be merged, False should 41 | be returned. 42 | 43 | :param other: :type{Operation} The operation to merge with. 44 | 45 | :return: An operation when this operation and `other` can be merged, or False if they cannot be merged. 46 | """ 47 | 48 | return False 49 | 50 | def push(self) -> None: 51 | """Push the operation onto the stack. 52 | 53 | This is a convenience method that pushes this operation onto the Application's 54 | operation stack. 55 | """ 56 | 57 | # Because of circular dependency 58 | from UM.Application import Application 59 | Application.getInstance().getOperationStack().push(self) 60 | -------------------------------------------------------------------------------- /UM/Operations/RemoveSceneNodeOperation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | from UM.Scene.SceneNode import SceneNode 4 | from . import Operation 5 | 6 | from UM.Scene.Selection import Selection 7 | from UM.Application import Application 8 | 9 | 10 | class RemoveSceneNodeOperation(Operation.Operation): 11 | """An operation that removes a SceneNode from the scene.""" 12 | 13 | def __init__(self, node: SceneNode) -> None: 14 | """Initialises the RemoveSceneNodeOperation. 15 | 16 | :param node: The node to remove. 17 | """ 18 | 19 | super().__init__() 20 | self._node = node 21 | self._parent = node.getParent() 22 | 23 | def undo(self) -> None: 24 | """Undoes the operation, putting the node back in the scene.""" 25 | 26 | self._node.setParent(self._parent) # Hanging it back under its original parent puts it back in the scene. 27 | 28 | def redo(self) -> None: 29 | """Redo the operation, removing the node again.""" 30 | 31 | old_parent = self._parent 32 | self._node.setParent(None) 33 | 34 | if old_parent and old_parent.callDecoration("isGroup"): 35 | old_parent.callDecoration("recomputeConvexHull") 36 | 37 | # Hack to ensure that the _onchanged is triggered correctly. 38 | # We can't do it the right way as most remove changes don't need to trigger 39 | # a reslice (eg; removing hull nodes don't need to trigger a reslice). 40 | try: 41 | Application.getInstance().getBackend().needsSlicing() # type: ignore 42 | except AttributeError: # Todo: This should be removed, Uranium doesn't care or know about slicing! 43 | pass 44 | if Selection.isSelected(self._node): # Also remove the selection. 45 | Selection.remove(self._node) 46 | -------------------------------------------------------------------------------- /UM/Operations/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | -------------------------------------------------------------------------------- /UM/OutputDevice/OutputDeviceError.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | 5 | class WriteRequestFailedError(Exception): 6 | """Base class for error raised by OutputDevice::requestWrite() 7 | 8 | This class serves as a base class for more specialized errors raised 9 | by OutputDevice::requestWrite(). Additionally, it can be raised whenever 10 | an error occurs for which no specialized error is available. 11 | """ 12 | 13 | pass 14 | 15 | 16 | class UserCanceledError(WriteRequestFailedError): 17 | """The user canceled the operation.""" 18 | 19 | pass 20 | 21 | 22 | class PermissionDeniedError(WriteRequestFailedError): 23 | """Permission was denied when trying to write to the device.""" 24 | 25 | pass 26 | 27 | 28 | class DeviceBusyError(WriteRequestFailedError): 29 | """The device is busy and cannot accept write requests at the moment.""" 30 | 31 | pass 32 | -------------------------------------------------------------------------------- /UM/OutputDevice/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/UM/OutputDevice/__init__.py -------------------------------------------------------------------------------- /UM/Platform.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | import sys 5 | 6 | 7 | class Platform: 8 | """Convenience class to simplify OS checking and similar platform-specific handling.""" 9 | 10 | class PlatformType: 11 | Windows = 1 12 | Linux = 2 13 | OSX = 3 14 | Other = 4 15 | 16 | @classmethod 17 | def isOSX(cls) -> bool: 18 | """Check to see if we are currently running on OSX.""" 19 | 20 | return cls.__platform_type == cls.PlatformType.OSX 21 | 22 | @classmethod 23 | def isWindows(cls) -> bool: 24 | """Check to see if we are currently running on Windows.""" 25 | 26 | return cls.__platform_type == cls.PlatformType.Windows 27 | 28 | @classmethod 29 | def isLinux(cls) -> bool: 30 | """Check to see if we are currently running on Linux.""" 31 | 32 | return cls.__platform_type == cls.PlatformType.Linux 33 | 34 | @classmethod 35 | def getType(cls) -> int: 36 | """Get the platform type.""" 37 | 38 | return cls.__platform_type 39 | 40 | __platform_type = PlatformType.Other 41 | if sys.platform == "win32": 42 | __platform_type = PlatformType.Windows 43 | elif sys.platform == "linux": 44 | __platform_type = PlatformType.Linux 45 | elif sys.platform == "darwin": 46 | __platform_type = PlatformType.OSX 47 | -------------------------------------------------------------------------------- /UM/PluginError.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | 5 | class PluginError(Exception): 6 | """Error classes that are used by PluginRegistry or other plugins to signal errors. 7 | 8 | A general class for any error raised by a plugin. 9 | """ 10 | pass 11 | 12 | 13 | class PluginNotFoundError(PluginError): 14 | """Raised when a plugin could not be found.""" 15 | 16 | def __str__(self): 17 | name = super().__str__() 18 | return "Could not find plugin " + name 19 | 20 | 21 | class InvalidMetaDataError(PluginError): 22 | """Raised when a plugin provides incorrect metadata.""" 23 | 24 | def __str__(self): 25 | name = super().__str__() 26 | return "Invalid metadata for plugin " + name 27 | -------------------------------------------------------------------------------- /UM/PluginObject.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | from typing import Optional, Dict, Any 4 | 5 | 6 | class PluginObject: 7 | """Base class for objects that can be provided by a plugin. 8 | 9 | This class should be inherited by any class that can be provided 10 | by a plugin. Its only function is to serve as a mapping between 11 | the plugin and the object. 12 | """ 13 | 14 | def __init__(self, *args, **kwags) -> None: 15 | self._plugin_id = None # type: Optional[str] 16 | self._version = None # type: Optional[str] 17 | self._metadata = {} # type: Dict[str, Any] 18 | self._name = None # type: Optional[str] 19 | 20 | # This returns a globally unique id for this plugin object. 21 | # It prepends it's set name (which should be locally (eg; within the plugin) unique) with the plugin_id, making it 22 | # globally unique. 23 | def getId(self) -> str: 24 | result = self.getPluginId() 25 | if self._name: 26 | result += "_%s" % self._name 27 | return result 28 | 29 | def setPluginId(self, plugin_id: str) -> None: 30 | self._plugin_id = plugin_id 31 | 32 | # The metadata of the plugin is set at the moment it is loaded. 33 | def setMetaData(self, metadata: Dict[str, Any]) -> None: 34 | self._metadata = metadata 35 | 36 | def getMetaData(self) -> Dict[str, Any]: 37 | return self._metadata 38 | 39 | def getPluginId(self) -> str: 40 | if not self._plugin_id: 41 | raise ValueError("The plugin ID needs to be set before the plugin can be used") 42 | return self._plugin_id 43 | 44 | def setVersion(self, version: str) -> None: 45 | self._version = version 46 | 47 | def getVersion(self) -> str: 48 | if not self._version: 49 | raise ValueError("The plugin version needs to be set before the plugin can be used") 50 | return self._version 51 | 52 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/ApplicationProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal 5 | 6 | from UM.Application import Application 7 | 8 | #ApplicationProxy is deprecated and will be removed in major SDK release, use CuraApplication.version() in place of UM.Application.version 9 | class ApplicationProxy(QObject): 10 | def __init__(self, parent = None): 11 | super().__init__(parent) 12 | self._application = Application.getInstance() 13 | 14 | @pyqtProperty(str, constant = True) 15 | def version(self): 16 | return self._application.getVersion() 17 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/BackendProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty 5 | 6 | from UM.i18n import i18nCatalog 7 | from UM.Application import Application 8 | from UM.Backend.Backend import BackendState 9 | 10 | i18n_catalog = i18nCatalog("uranium") 11 | 12 | 13 | class BackendProxy(QObject): 14 | 15 | def __init__(self, parent = None): 16 | super().__init__(parent) 17 | self._backend = Application.getInstance().getBackend() 18 | self._progress = -1 19 | self._state = BackendState.NotStarted 20 | if self._backend: 21 | self._backend.processingProgress.connect(self._onProcessingProgress) 22 | self._backend.backendStateChange.connect(self._onBackendStateChange) 23 | 24 | processingProgress = pyqtSignal(float, arguments = ["amount"]) 25 | 26 | @pyqtProperty(float, notify = processingProgress) 27 | def progress(self): 28 | return self._progress 29 | 30 | @pyqtProperty(int, constant = True) 31 | def NotStarted(self): 32 | return 1 33 | 34 | @pyqtProperty(int, constant=True) 35 | def Processing(self): 36 | return 2 37 | 38 | @pyqtProperty(int, constant=True) 39 | def Done(self): 40 | return 3 41 | 42 | @pyqtProperty(int, constant=True) 43 | def Error(self): 44 | return 4 45 | 46 | @pyqtProperty(int, constant=True) 47 | def Disabled(self): 48 | return 5 49 | 50 | backendStateChange = pyqtSignal(int, arguments = ["state"]) 51 | 52 | @pyqtProperty(int, notify = backendStateChange) 53 | def state(self): 54 | """Returns the current state of processing of the backend. 55 | 56 | :return: :type{IntEnum} The current state of the backend. 57 | """ 58 | 59 | return self._state 60 | 61 | def _onProcessingProgress(self, amount): 62 | if self._progress != amount: 63 | self._progress = amount 64 | self.processingProgress.emit(amount) 65 | 66 | def _onBackendStateChange(self, state): 67 | if self._state != state: 68 | self._state = state 69 | self.backendStateChange.emit(state) 70 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/ContainerProxy.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import QObject 2 | from UM.FlameProfiler import pyqtSlot 3 | from typing import Dict, Any, Optional 4 | 5 | 6 | # This class wraps a Python container and provides access to its elements 7 | # 8 | # It is primarily intended as a reasonably fast wrapper around containers 9 | # that need to be updated and need to notify QML of their changes. 10 | # 11 | # To use it, define a property that returns a QObject with a notify signal. 12 | # From the property getter, return an instance of this object. Whenever the 13 | # contents of the container change, simply emit the notify signal, do not 14 | # recreate the proxy. 15 | class ContainerProxy(QObject): 16 | def __init__(self, container: Dict[str, Any], parent: Optional[QObject] = None) -> None: 17 | super().__init__(parent) 18 | self._container = container 19 | 20 | @pyqtSlot(str, result = "QVariant") 21 | def getValue(self, value: str): 22 | return self._container.get(value, None) 23 | 24 | @pyqtSlot(str, result="QVariantList") 25 | def getValueList(self, value: str): 26 | return self._container.get(value, None) 27 | 28 | @pyqtSlot(str, "QVariant") 29 | def setValue(self, key: str, value: Any) -> None: 30 | self._container[key] = value 31 | 32 | @pyqtSlot(result = int) 33 | def getCount(self) -> int: 34 | return len(self._container) 35 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/OpenGLContextProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal 5 | from UM.View.GL.OpenGLContext import OpenGLContext 6 | 7 | 8 | class OpenGLContextProxy(QObject): 9 | """Expose OpenGLContext functions to qml.""" 10 | 11 | dummySignal = pyqtSignal(int, arguments = ["state"]) 12 | 13 | @pyqtProperty(bool, notify = dummySignal) 14 | def isLegacyOpenGL(self): 15 | return OpenGLContext.isLegacyOpenGL() 16 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/OperationStackProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal 5 | 6 | from UM.Application import Application 7 | 8 | 9 | #"OperationStackProxy is deprecated and will be removed in major SDK release" 10 | class OperationStackProxy(QObject): 11 | def __init__(self, parent = None): 12 | super().__init__(parent) 13 | 14 | self._operation_stack = Application.getInstance().getOperationStack() 15 | self._operation_stack.changed.connect(self._onUndoStackChanged) 16 | 17 | undoStackChanged = pyqtSignal() 18 | 19 | @pyqtProperty(bool, notify=undoStackChanged) 20 | def canUndo(self): 21 | return self._operation_stack.canUndo() 22 | 23 | @pyqtProperty(bool, notify=undoStackChanged) 24 | def canRedo(self): 25 | return self._operation_stack.canRedo() 26 | 27 | @pyqtSlot() 28 | def undo(self): 29 | self._operation_stack.undo() 30 | 31 | @pyqtSlot() 32 | def redo(self): 33 | self._operation_stack.redo() 34 | 35 | def _onUndoStackChanged(self): 36 | self.undoStackChanged.emit() 37 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/PreferencesProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal 5 | 6 | from UM.Application import Application 7 | 8 | 9 | class PreferencesProxy(QObject): 10 | def __init__(self, parent = None): 11 | super().__init__(parent) 12 | self._preferences = Application.getInstance().getPreferences() 13 | self._preferences.preferenceChanged.connect(self._onPreferenceChanged) 14 | 15 | preferenceChanged = pyqtSignal(str, arguments = ["preference"]) 16 | 17 | @pyqtSlot(str, result = "QVariant") 18 | def getValue(self, key): 19 | return self._preferences.getValue(key) 20 | 21 | @pyqtSlot(str, "QVariant") 22 | def setValue(self, key, value): 23 | self._preferences.setValue(key, value) 24 | 25 | @pyqtSlot(str) 26 | def resetPreference(self, key): 27 | self._preferences.resetPreference(key) 28 | 29 | def _onPreferenceChanged(self, preference): 30 | self.preferenceChanged.emit(preference) 31 | 32 | def createPreferencesProxy(engine, script_engine): 33 | return PreferencesProxy() 34 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/ResourcesProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import enum 5 | 6 | from PyQt6.QtCore import QObject, pyqtEnum 7 | from UM.FlameProfiler import pyqtSlot 8 | 9 | import UM.Resources 10 | from UM.Logger import Logger 11 | 12 | 13 | class ResourcesProxy(QObject): 14 | class Type(enum.IntEnum): 15 | Resources = UM.Resources.Resources.Resources 16 | Preferences = UM.Resources.Resources.Preferences 17 | Themes = UM.Resources.Resources.Themes 18 | Images = UM.Resources.Resources.Images 19 | Meshes = UM.Resources.Resources.Meshes 20 | i18n = UM.Resources.Resources.i18n 21 | Shaders = UM.Resources.Resources.Shaders 22 | UserType = UM.Resources.Resources.UserType 23 | pyqtEnum(Type) 24 | 25 | def __init__(self, parent = None): 26 | super().__init__(parent) 27 | 28 | @pyqtSlot(int, str, result = str) 29 | def getPath(self, type, name): 30 | try: 31 | return UM.Resources.Resources.getPath(type, name) 32 | except: 33 | Logger.log("w", "Could not find the requested resource: %s", name) 34 | return "" 35 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/SelectionProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot 5 | 6 | from UM.Scene.Selection import Selection 7 | 8 | 9 | class SelectionProxy(QObject): 10 | def __init__(self, parent = None): 11 | super().__init__(parent) 12 | Selection.selectionChanged.connect(self._onSelectionChanged) 13 | Selection.selectedFaceChanged.connect(self._onSelectedFaceChanged) 14 | 15 | selectionChanged = pyqtSignal() 16 | selectedFaceChanged = pyqtSignal() 17 | 18 | @pyqtProperty(bool, notify = selectionChanged) 19 | def hasSelection(self): 20 | return Selection.hasSelection() 21 | 22 | @pyqtProperty(bool, notify = selectedFaceChanged) 23 | def faceSelectMode(self): 24 | return Selection.getFaceSelectMode() 25 | 26 | @pyqtSlot(bool) 27 | def setFaceSelectMode(self, select: bool) -> None: 28 | Selection.setFaceSelectMode(select) 29 | if not select: 30 | Selection.clearFace() 31 | 32 | @pyqtProperty(bool, notify = selectedFaceChanged) 33 | def hasFaceSelected(self): 34 | return Selection.getSelectedFace() is not None 35 | 36 | @pyqtProperty(int, notify = selectionChanged) 37 | def selectionCount(self): 38 | return Selection.getCount() 39 | 40 | @pyqtProperty("QVariantList", notify = selectionChanged) 41 | def selectionNames(self): 42 | return [node.getName() for node in Selection.getAllSelectedObjects()] 43 | 44 | def _onSelectionChanged(self): 45 | self.selectionChanged.emit() 46 | 47 | def _onSelectedFaceChanged(self): 48 | self.selectedFaceChanged.emit() 49 | 50 | @pyqtProperty(bool, notify=selectionChanged) 51 | def isGroupSelected(self): 52 | for node in Selection.getAllSelectedObjects(): 53 | if node.callDecoration("isGroup"): 54 | return True 55 | return False 56 | 57 | def createSelectionProxy(engine, script_engine): 58 | return SelectionProxy() 59 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/StageModel.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtCore import Qt 5 | 6 | from UM.Application import Application 7 | from UM.PluginRegistry import PluginRegistry 8 | from UM.Qt.ListModel import ListModel 9 | 10 | 11 | class StageModel(ListModel): 12 | """The StageModel is a representation of all stages in QML. 13 | 14 | Use it to populate a stage based menu (like top bar). 15 | """ 16 | 17 | IdRole = Qt.ItemDataRole.UserRole + 1 18 | NameRole = Qt.ItemDataRole.UserRole + 2 19 | StageRole = Qt.ItemDataRole.UserRole + 4 20 | 21 | def __init__(self, parent = None): 22 | super().__init__(parent) 23 | self._controller = Application.getInstance().getController() 24 | self._controller.stagesChanged.connect(self._onStagesChanged) 25 | self._onStagesChanged() 26 | 27 | self.addRoleName(self.IdRole, "id") 28 | self.addRoleName(self.NameRole, "name") 29 | self.addRoleName(self.StageRole, "stage") 30 | 31 | def _onStagesChanged(self): 32 | items = [] 33 | stages = self._controller.getAllStages() 34 | 35 | for stage_id, stage in stages.items(): 36 | view_meta_data = PluginRegistry.getInstance().getMetaData(stage_id).get("stage", {}) 37 | 38 | # Skip view modes that are marked as not visible 39 | if "visible" in view_meta_data and not view_meta_data["visible"]: 40 | continue 41 | 42 | # Metadata elements 43 | name = view_meta_data.get("name", stage_id) 44 | weight = view_meta_data.get("weight", 99) 45 | 46 | items.append({ 47 | "id": stage_id, 48 | "name": name, 49 | "stage": stage, 50 | "weight": weight 51 | }) 52 | 53 | items.sort(key=lambda t: t["weight"]) 54 | self.setItems(items) 55 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/Window.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | # The Window class inherits from QQuickWindow, making the use of Window roughly the same as QQuickWindow. As not all 5 | # event handlers are exposed in the qml version of QQuickWindow we use python bindings to give us more control over the 6 | # window object. 7 | 8 | from PyQt6.QtCore import pyqtSlot 9 | from PyQt6 import QtCore 10 | from PyQt6.QtQuick import QQuickWindow 11 | 12 | 13 | class Window(QQuickWindow): 14 | def __init__(self, parent=None) -> None: 15 | super(Window, self).__init__(parent=parent) 16 | self.update() 17 | 18 | def moveEvent(self, event): 19 | # When dragging a window between screens with different scaling settings we see some rendering artifacts. By 20 | # intercepting the window move event, and triggering an update on this move event we circumvent this issue. 21 | self.update() 22 | return super(Window, self).moveEvent(event) 23 | 24 | def showEvent(self, event): 25 | self.update() 26 | return super(Window, self).showEvent(event) 27 | 28 | def resizeEvent(self, event): 29 | self.update() 30 | return super(Window, self).resizeEvent(event) 31 | -------------------------------------------------------------------------------- /UM/Qt/Bindings/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | ## \package Bindings 5 | # Qt-based classes providing data bindings between Python and QML. 6 | -------------------------------------------------------------------------------- /UM/Qt/QtKeyDevice.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtCore import Qt, QEvent 5 | 6 | from UM.InputDevice import InputDevice 7 | from UM.Event import KeyEvent 8 | 9 | 10 | class QtKeyDevice(InputDevice): 11 | """In between class that converts QT key events to Uranium events.""" 12 | 13 | def __init__(self): 14 | super().__init__() 15 | 16 | def handleEvent(self, event): 17 | if event.type() == QEvent.Type.KeyPress: 18 | e = KeyEvent(KeyEvent.KeyPressEvent, self._qtKeyToUMKey(event.key())) 19 | self.event.emit(e) 20 | elif event.type() == QEvent.Type.KeyRelease: 21 | e = KeyEvent(KeyEvent.KeyReleaseEvent, self._qtKeyToUMKey(event.key())) 22 | self.event.emit(e) 23 | 24 | def _qtKeyToUMKey(self, key): 25 | if key == Qt.Key.Key_Shift: 26 | return KeyEvent.ShiftKey 27 | elif key == Qt.Key.Key_Control: 28 | return KeyEvent.ControlKey 29 | elif key == Qt.Key.Key_Alt: 30 | return KeyEvent.AltKey 31 | elif key == Qt.Key.Key_Space: 32 | return KeyEvent.SpaceKey 33 | elif key == Qt.Key.Key_Meta: 34 | return KeyEvent.MetaKey 35 | elif key == Qt.Key.Key_Enter or key == Qt.Key.Key_Return: 36 | return KeyEvent.EnterKey 37 | elif key == Qt.Key.Key_Up: 38 | return KeyEvent.UpKey 39 | elif key == Qt.Key.Key_Down: 40 | return KeyEvent.DownKey 41 | elif key == Qt.Key.Key_Left: 42 | return KeyEvent.LeftKey 43 | elif key == Qt.Key.Key_Right: 44 | return KeyEvent.RightKey 45 | elif key == Qt.Key.Key_Minus: 46 | return KeyEvent.MinusKey 47 | elif key == Qt.Key.Key_Underscore: 48 | return KeyEvent.UnderscoreKey 49 | elif key == Qt.Key.Key_Plus: 50 | return KeyEvent.PlusKey 51 | elif key == Qt.Key.Key_Equal: 52 | return KeyEvent.EqualKey 53 | 54 | return key 55 | -------------------------------------------------------------------------------- /UM/Qt/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #Shoopdawoop 5 | 6 | ## \package Qt 7 | # Qt-specific classes. 8 | -------------------------------------------------------------------------------- /UM/Qt/qml/UM/ApplicationMenu.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.1 5 | import QtQuick.Controls 2.4 6 | 7 | /** 8 | * This is a workaround for lacking API in the QtQuick Controls MenuBar. 9 | * It replicates some of the functionality included in QtQuick Controls' 10 | * ApplicationWindow class to make the menu bar actually work. 11 | */ 12 | Rectangle 13 | { 14 | id: menuBackground; 15 | 16 | property QtObject window; 17 | Binding 18 | { 19 | target: menu.__contentItem 20 | property: "width" 21 | value: window.width 22 | when: !menu.__isNative 23 | } 24 | 25 | default property alias menus: menu.menus 26 | 27 | width: menu.__isNative ? 0 : menu.__contentItem.width 28 | height: menu.__isNative ? 0 : menu.__contentItem.height 29 | 30 | Keys.forwardTo: menu.__contentItem; 31 | 32 | MenuBar 33 | { 34 | id: menu 35 | 36 | Component.onCompleted: 37 | { 38 | __contentItem.parent = menuBackground; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /UM/Qt/qml/UM/BurgerButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.7 5 | import QtQuick.Controls 2.3 6 | 7 | import UM 1.5 as UM 8 | 9 | Button 10 | { 11 | id: control 12 | padding: 0 13 | 14 | implicitWidth: Math.round(UM.Theme.getSize("button").width * 0.75) 15 | implicitHeight: implicitWidth 16 | 17 | background: Rectangle 18 | { 19 | radius: Math.round(width * 0.5) 20 | color: control.hovered ? UM.Theme.getColor("toolbar_button_hover"): UM.Theme.getColor("toolbar_background") 21 | } 22 | 23 | contentItem: Item 24 | { 25 | UM.ColorImage 26 | { 27 | anchors.centerIn: parent 28 | implicitWidth: Math.round(UM.Theme.getSize("button").width / 2) 29 | implicitHeight: implicitWidth 30 | source: UM.Theme.getIcon("Hamburger") 31 | color: UM.Theme.getColor("icon") 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/ComponentWithIcon.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 UltiMaker 2 | // Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.7 5 | import QtQuick.Controls 2.0 6 | import QtQuick.Layouts 1.3 7 | 8 | import UM 1.5 as UM 9 | 10 | // Reusable component that holds an (re-colorable) icon on the left with a component on the right. 11 | Item 12 | { 13 | property alias source: icon.source 14 | property alias iconSize: icon.width 15 | property alias iconColor: icon.color 16 | property real spacing: UM.Theme.getSize("narrow_margin").width 17 | default property alias contents: displayComponentContainer.children 18 | 19 | property string tooltipText: "" 20 | 21 | UM.ColorImage 22 | { 23 | id: icon 24 | // Icon will always match loaded component height 25 | width: displayComponentContainer.height 26 | height: width 27 | color: UM.Theme.getColor("icon") 28 | 29 | anchors 30 | { 31 | left: parent.left 32 | verticalCenter: parent.verticalCenter 33 | } 34 | } 35 | 36 | 37 | Item 38 | { 39 | id: displayComponentContainer 40 | height: childrenRect.height 41 | 42 | anchors 43 | { 44 | left: icon.right 45 | right: parent.right 46 | verticalCenter: parent.verticalCenter 47 | leftMargin: spacing 48 | } 49 | } 50 | 51 | MouseArea 52 | { 53 | enabled: tooltipText != "" 54 | anchors.fill: parent 55 | hoverEnabled: true 56 | onEntered: base.showTooltip(parent, Qt.point(-UM.Theme.getSize("thick_margin").width, 0), tooltipText) 57 | onExited: base.hideTooltip() 58 | } 59 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Enums.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.15 5 | import QtQuick.Controls 2.10 6 | 7 | Item 8 | { 9 | enum ContentAlignment 10 | { 11 | AlignLeft, 12 | AlignRight 13 | } 14 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/HelpIcon.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.7 5 | import QtQuick.Controls 2.3 6 | 7 | import UM 1.8 as UM 8 | 9 | MouseArea 10 | { 11 | property alias text: tooltip.text 12 | property alias icon: image.source 13 | property alias color: image.color 14 | 15 | id: helpIconMouseArea 16 | hoverEnabled: true 17 | 18 | implicitWidth: UM.Theme.getSize("section_icon").width 19 | implicitHeight: UM.Theme.getSize("section_icon").height 20 | 21 | UM.ColorImage 22 | { 23 | id: image 24 | anchors.fill: parent 25 | color: UM.Theme.getColor("warning") 26 | source: UM.Theme.getIcon("Help") 27 | 28 | UM.ToolTip 29 | { 30 | UM.I18nCatalog { id: catalog; name: "cura" } 31 | 32 | id: tooltip 33 | visible: helpIconMouseArea.containsMouse 34 | targetPoint: Qt.point(parent.x + Math.round(parent.width / 2), parent.y) 35 | x: 0 36 | y: parent.y + parent.height + UM.Theme.getSize("default_margin").height 37 | width: UM.Theme.getSize("tooltip").width 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Label.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 2.15 2 | import QtQuick 2.15 3 | import UM 1.0 as UM 4 | 5 | Label 6 | { 7 | color: UM.Theme.getColor("text") 8 | font: UM.Theme.getFont("default") 9 | wrapMode: Text.Wrap 10 | renderType: Qt.platform.os == "osx" ? Text.QtRendering : Text.NativeRendering 11 | linkColor: UM.Theme.getColor("text_link") 12 | verticalAlignment: Text.AlignVCenter 13 | } 14 | -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Menu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | 4 | import UM 1.5 as UM 5 | 6 | Menu 7 | { 8 | id: root 9 | 10 | // This is a work around for menu's not actually being visual items. Upon creation, a visual item is created 11 | // The work around is based on the suggestion given in https://stackoverflow.com/questions/59167352/qml-how-to-hide-submenu 12 | property bool shouldBeVisible: true 13 | 14 | // Automatically set the width to fit the widest MenuItem 15 | // Based on https://martin.rpdev.net/2018/03/13/qt-quick-controls-2-automatically-set-the-width-of-menus.html 16 | function setWidth() 17 | { 18 | var result = 0; 19 | var padding = 0; 20 | var result = 0; 21 | for (var i = 0; i < count; ++i) { 22 | var item = itemAt(i); 23 | if (item.hasOwnProperty("contentWidth")) 24 | { 25 | var itemWidth = item.contentWidth; 26 | if (item.hasOwnProperty("shortcut") && item.shortcut != null) { 27 | itemWidth += UM.Theme.getSize("default_margin").width; 28 | } 29 | result = Math.max(itemWidth, result); 30 | padding = Math.max(item.padding, padding); 31 | } 32 | } 33 | return result + padding * 2; 34 | } 35 | 36 | function adjustWidth() 37 | { 38 | root.width = shouldBeVisible ? setWidth() : 0 39 | } 40 | 41 | function handleVisibility() 42 | { 43 | if(parent) 44 | { 45 | root.parent.visible = shouldBeVisible 46 | root.parent.height = shouldBeVisible ? UM.Theme.getSize("menu").height : 0 47 | adjustWidth() 48 | } 49 | } 50 | 51 | onParentChanged: handleVisibility() 52 | Component.onCompleted: handleVisibility() 53 | onShouldBeVisibleChanged: handleVisibility() 54 | onAboutToShow: adjustWidth() 55 | implicitWidth: setWidth() 56 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Preferences/ConfirmRemoveDialog.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.1 5 | import QtQuick.Controls 2.1 6 | import QtQuick.Layouts 1.1 7 | 8 | import UM 1.5 as UM 9 | import Cura 1.5 as Cura 10 | 11 | Cura.MessageDialog 12 | { 13 | property string object: ""; 14 | 15 | title: catalog.i18nc("@title:window", "Confirm Remove"); 16 | text: catalog.i18nc("@label (%1 is object name)", "Are you sure you wish to remove %1? This cannot be undone!").arg(object); 17 | standardButtons: Dialog.Yes | Dialog.No 18 | 19 | property variant catalog: UM.I18nCatalog { name: "uranium"; } 20 | } 21 | -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Preferences/GeneralPage.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.1 5 | import QtQuick.Controls 2.1 6 | 7 | import UM 1.1 as UM 8 | 9 | PreferencesPage 10 | { 11 | title: "General"; 12 | 13 | function reset() { 14 | UM.Preferences.resetPreference("general/language") 15 | } 16 | 17 | Column 18 | { 19 | Label 20 | { 21 | id: languageLabel 22 | text: "Language" 23 | } 24 | 25 | ComboBox 26 | { 27 | id: languageComboBox 28 | model: ListModel { 29 | id: languageList 30 | ListElement { text: "English"; code: "en_US" } 31 | } 32 | 33 | currentIndex: 34 | { 35 | var code = UM.Preferences.getValue("general/language"); 36 | var index = 0; 37 | for(var i = 0; i < languageList.count; ++i) 38 | { 39 | if(model.get(i).code == code) 40 | { 41 | index = i; 42 | break; 43 | } 44 | } 45 | return index; 46 | } 47 | 48 | onActivated: UM.Preferences.setValue("general/language", model.get(index).code) 49 | } 50 | 51 | Label 52 | { 53 | id: languageCaption; 54 | text: "You will need to restart the application for language changes to have effect." 55 | wrapMode: Text.WordWrap 56 | font.italic: true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /UM/Qt/qml/UM/ProgressBar.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.10 5 | import QtQuick.Controls 2.3 as Controls 6 | 7 | import UM 1.3 as UM 8 | 9 | // 10 | // Styled progress bar, with colours from the theme and rounded corners. 11 | // 12 | Controls.ProgressBar 13 | { 14 | id: progressBar 15 | width: parent.width 16 | height: UM.Theme.getSize("progressbar").height 17 | 18 | background: Rectangle 19 | { 20 | anchors.fill: parent 21 | radius: UM.Theme.getSize("progressbar_radius").width 22 | color: UM.Theme.getColor("progressbar_background") 23 | } 24 | 25 | contentItem: Item 26 | { 27 | anchors.fill: parent 28 | 29 | // The progress block for showing progress value 30 | Rectangle 31 | { 32 | id: progressBlockDeterminate 33 | x: progressBar.indeterminate ? progressBar.visualPosition * parent.width : 0 34 | width: progressBar.indeterminate ? parent.width * 0.1 : progressBar.visualPosition * parent.width 35 | height: parent.height 36 | radius: UM.Theme.getSize("progressbar_radius").width 37 | color: UM.Theme.getColor("progressbar_control") 38 | } 39 | SequentialAnimation 40 | { 41 | PropertyAnimation 42 | { 43 | target: progressBar 44 | property: "value" 45 | from: 0 46 | to: 0.9 // The block is not centered, so let it go to 90% (since it's 10% long) 47 | duration: 3000 48 | } 49 | PropertyAnimation 50 | { 51 | target: progressBar 52 | property: "value" 53 | from: 0.9 // The block is not centered, so let it go to 90% (since it's 10% long) 54 | to: 0 55 | duration: 3000 56 | } 57 | 58 | loops: Animation.Infinite 59 | running: progressBar.visible && progressBar.indeterminate 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /UM/Qt/qml/UM/ScrollBar.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.15 5 | import QtQuick.Controls 2.15 6 | import UM 1.4 as UM 7 | 8 | //Scroll bar that uses our own theme. 9 | ScrollBar 10 | { 11 | policy: (size < 1.0) ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff 12 | implicitWidth: (size < 1.0) ? UM.Theme.getSize("scrollbar").width + leftPadding + rightPadding : 0 13 | minimumSize: orientation === Qt.Horizontal ? height / width : width / height 14 | 15 | background: Rectangle 16 | { 17 | implicitWidth: UM.Theme.getSize("scrollbar").width 18 | 19 | radius: implicitWidth / 2 20 | color: UM.Theme.getColor("scrollbar_background") 21 | } 22 | 23 | contentItem: Rectangle 24 | { 25 | implicitWidth: UM.Theme.getSize("scrollbar").width 26 | 27 | radius: implicitWidth / 2 28 | color: parent.pressed ? UM.Theme.getColor("scrollbar_handle_down") : parent.hovered ? UM.Theme.getColor("scrollbar_handle_hover") : UM.Theme.getColor("scrollbar_handle") 29 | Behavior on color { ColorAnimation { duration: 50; } } 30 | } 31 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/ScrollableTextArea.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.10 5 | import QtQuick.Controls 2.3 6 | import UM 1.7 as UM 7 | 8 | //TextArea with scrolls that uses our own theme. 9 | Flickable 10 | { 11 | id: scrollableTextAreaBase 12 | property bool do_borders: true 13 | property var back_color: UM.Theme.getColor("main_background") 14 | property alias textArea: flickableTextArea 15 | 16 | ScrollBar.vertical: UM.ScrollBar {} 17 | 18 | TextArea.flickable: TextArea 19 | { 20 | id: flickableTextArea 21 | 22 | background: Rectangle //Providing the background color and border. 23 | { 24 | anchors.fill: parent 25 | anchors.margins: -border.width 26 | 27 | color: scrollableTextAreaBase.back_color 28 | border.color: UM.Theme.getColor("thick_lining") 29 | border.width: scrollableTextAreaBase.do_borders ? UM.Theme.getSize("default_lining").width : 0 30 | } 31 | 32 | font: UM.Theme.getFont("default") 33 | color: UM.Theme.getColor("text") 34 | textFormat: TextEdit.PlainText 35 | renderType: Text.NativeRendering 36 | wrapMode: Text.Wrap 37 | selectByMouse: true 38 | } 39 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/SimpleButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Ultimaker 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.1 5 | import UM 1.5 as UM 6 | 7 | MouseArea 8 | { 9 | id: base 10 | 11 | hoverEnabled: true 12 | 13 | property color color: "black" 14 | property color hoverColor: color 15 | property color backgroundColor: "transparent" 16 | property color hoverBackgroundColor: backgroundColor 17 | property alias iconSource: image.source 18 | property real iconMargin: 0 19 | 20 | property alias hovered: base.containsMouse 21 | property alias backgroundRadius: background.radius 22 | 23 | Rectangle 24 | { 25 | id: background 26 | anchors.fill: parent 27 | color: base.containsMouse ? base.hoverBackgroundColor : base.backgroundColor 28 | radius: 0 29 | } 30 | 31 | UM.ColorImage 32 | { 33 | id: image 34 | 35 | anchors.fill: parent 36 | anchors.margins: base.iconMargin 37 | 38 | color: base.containsMouse ? base.hoverColor : base.color 39 | 40 | visible: source != "" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Switch.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 UltiMaker 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.12 5 | import QtQuick.Controls 2.12 6 | 7 | import UM 1.5 as UM 8 | 9 | Switch 10 | { 11 | id: control 12 | property double indicatorMargin: 2 13 | 14 | indicator: Rectangle 15 | { 16 | id: switchBackground 17 | implicitWidth: 32 18 | implicitHeight: 16 19 | x: control.leftPadding 20 | y: (parent.height - height) / 2 21 | radius: height / 2 22 | color: UM.Theme.getColor("switch_state_unchecked") 23 | 24 | Rectangle 25 | { 26 | x: control.checked ? parent.width - width - control.indicatorMargin : control.indicatorMargin 27 | y: control.indicatorMargin 28 | width: parent.height - 2 * control.indicatorMargin 29 | height: width 30 | radius: width / 2 31 | color: UM.Theme.getColor("switch") 32 | } 33 | } 34 | 35 | states: 36 | [ 37 | State 38 | { 39 | name: "checked" 40 | when: control.checked 41 | PropertyChanges { target: switchBackground; color: UM.Theme.getColor("switch_state_checked") } 42 | }, 43 | State 44 | { 45 | name: "disabled" 46 | when: !control.enabled 47 | PropertyChanges { target: switchBackground; color: UM.Theme.getColor("background_2") } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/TabRow.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.10 5 | import QtQuick.Controls 2.3 6 | import UM 1.2 as UM 7 | 8 | /* 9 | * Wrapper around TabBar that uses our theming and more sane defaults. 10 | */ 11 | TabBar 12 | { 13 | id: base 14 | 15 | width: parent.width 16 | height: visible ? 40 * screenScaleFactor : 0 17 | 18 | spacing: UM.Theme.getSize("narrow_margin").width //Space between the tabs. 19 | 20 | background: Rectangle 21 | { 22 | width: parent.width 23 | anchors.bottom: parent.bottom 24 | height: UM.Theme.getSize("default_lining").height 25 | color: UM.Theme.getColor("border_main") 26 | } 27 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/TabRowButton.qml: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2022 Ultimaker B.V. 2 | //Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.10 5 | import QtQuick.Controls 2.3 6 | import UM 1.5 as UM 7 | 8 | /* 9 | * Wrapper around TabButton to use our theming and sane defaults. 10 | */ 11 | TabButton 12 | { 13 | anchors.top: parent.top 14 | height: parent.height 15 | 16 | background: Rectangle 17 | { 18 | radius: UM.Theme.getSize("default_radius").width 19 | border.color: UM.Theme.getColor("border_main") 20 | border.width: UM.Theme.getSize("default_lining").width 21 | color: UM.Theme.getColor("main_background") 22 | 23 | //Make the lining go straight down on the bottom side of the left and right sides. 24 | Rectangle 25 | { 26 | anchors.bottom: parent.bottom 27 | width: parent.width 28 | //We take almost the entire height of the tab button, since this "manual" lining has no anti-aliasing. 29 | //We can hardly prevent anti-aliasing on the border of the tab since the tabs are positioned with some spacing that is not necessarily a multiple of the number of tabs. 30 | height: parent.height - (parent.radius + parent.border.width) 31 | color: UM.Theme.getColor("border_main") 32 | 33 | //Don't add lining at the bottom side. 34 | Rectangle 35 | { 36 | anchors 37 | { 38 | bottom: parent.bottom 39 | bottomMargin: parent.parent.parent.checked ? 0 : parent.parent.border.width //Allow margin if tab is not selected. 40 | left: parent.left 41 | leftMargin: parent.parent.border.width 42 | right: parent.right 43 | rightMargin: parent.parent.border.width 44 | } 45 | color: parent.parent.color 46 | height: parent.height - anchors.bottomMargin 47 | } 48 | } 49 | } 50 | contentItem: UM.Label 51 | { 52 | anchors.centerIn: parent 53 | horizontalAlignment: Text.AlignHCenter 54 | text: parent.text 55 | font: parent.checked ? UM.Theme.getFont("default_bold") : UM.Theme.getFont("default") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /UM/Qt/qml/UM/TooltipArea.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Ultimaker B.V. 2 | // Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.4 5 | 6 | import UM 1.5 as UM 7 | 8 | // TooltipArea.qml 9 | Item 10 | { 11 | id: _root 12 | 13 | property alias text: tooltip.text 14 | property alias acceptedButtons: mouse_area.acceptedButtons 15 | property alias hoverEnabled: mouse_area.hoverEnabled 16 | signal exited() 17 | signal canceled() 18 | signal entered() 19 | signal clicked() 20 | 21 | MouseArea 22 | { 23 | id: mouse_area 24 | anchors.fill: _root 25 | z: 1000 26 | propagateComposedEvents: true 27 | hoverEnabled: _root.enabled 28 | acceptedButtons: Qt.NoButton 29 | 30 | onExited: 31 | { 32 | tooltip.hide() 33 | _root.exited() 34 | } 35 | 36 | onCanceled: 37 | { 38 | tooltip.hide() 39 | _root.canceled() 40 | } 41 | onEntered: _root.entered() 42 | onClicked: _root.clicked() 43 | 44 | UM.ToolTip 45 | { 46 | id: tooltip 47 | arrowSize: 0 48 | } 49 | 50 | Timer 51 | { 52 | interval: 1000 53 | running: _root.enabled && mouse_area.containsMouse && _root.text.length 54 | onTriggered: 55 | { 56 | tooltip.x = mouse_area.mouseX 57 | tooltip.y = mouse_area.height - mouse_area.mouseY 58 | tooltip.show() 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /UM/Qt/qml/UM/UnderlineBackground.qml: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2022 Ultimaker B.V. 2 | //Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.2 5 | 6 | import UM 1.5 as UM 7 | 8 | Rectangle 9 | { 10 | property alias liningColor: lining.color 11 | property var borderColor: "transparent" 12 | 13 | color: UM.Theme.getColor("detail_background") 14 | border.width: UM.Theme.getSize("default_lining").width 15 | border.color: borderColor 16 | anchors.fill: parent; 17 | 18 | Rectangle 19 | { 20 | id: lining 21 | anchors.bottom: parent.bottom 22 | width: parent.width 23 | height: UM.Theme.getSize("default_lining").height 24 | color: UM.Theme.getColor("border_field_light") 25 | } 26 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Validators/FloatValidator.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 UltiMaker 2 | // Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.15 5 | 6 | RegularExpressionValidator 7 | { 8 | property int maxBeforeDecimal: 11 9 | property int maxAfterDecimal: 3 10 | 11 | readonly property string regexString: "^-?[0-9]{0,%0}[.,]?[0-9]{0,%1}$".arg(maxBeforeDecimal).arg(maxAfterDecimal) 12 | 13 | regularExpression: new RegExp(regexString) 14 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Validators/HexColorValidator.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 UltiMaker 2 | // Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.15 5 | 6 | RegularExpressionValidator 7 | { 8 | regularExpression: /^#([a-fA-F0-9]{0,6})$/ 9 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Validators/IntListValidator.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 UltiMaker 2 | // Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.15 5 | 6 | RegularExpressionValidator 7 | { 8 | property string regexString: "^\\[?(\\s*-?[0-9]{0,11}\\s*,)*(\\s*-?[0-9]{0,11})\\s*\\]?$" 9 | 10 | regularExpression: new RegExp(regexString) 11 | } -------------------------------------------------------------------------------- /UM/Qt/qml/UM/Validators/IntValidator.qml: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 UltiMaker 2 | // Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | import QtQuick 2.15 5 | 6 | RegularExpressionValidator 7 | { 8 | property int maxNumbers: 12 9 | readonly property string regexString: "^-?[0-9]{0,%0}$".arg(maxNumbers) 10 | 11 | regularExpression: new RegExp(regexString) 12 | } -------------------------------------------------------------------------------- /UM/Scene/GroupDecorator.py: -------------------------------------------------------------------------------- 1 | from UM.Scene.SceneNodeDecorator import SceneNodeDecorator 2 | from UM.Scene.Selection import Selection 3 | from typing import TYPE_CHECKING, Optional 4 | 5 | if TYPE_CHECKING: 6 | from UM.Scene.SceneNode import SceneNode 7 | 8 | 9 | class GroupDecorator(SceneNodeDecorator): 10 | def __init__(self, remove_when_empty: bool = True) -> None: 11 | super().__init__() 12 | # Used to keep track of previous parent when an empty group removes itself from the scene. 13 | # We keep this option so that it's possible to undo it. 14 | self._old_parent = None # type: Optional[SceneNode] 15 | self._remove_when_empty = remove_when_empty 16 | 17 | def setNode(self, node: "SceneNode") -> None: 18 | super().setNode(node) 19 | if self._node is not None: 20 | self._node.childrenChanged.connect(self._onChildrenChanged) 21 | 22 | def isGroup(self) -> bool: 23 | return True 24 | 25 | def getOldParent(self) -> Optional["SceneNode"]: 26 | return self._old_parent 27 | 28 | def _onChildrenChanged(self, node: "SceneNode") -> None: 29 | if self._node is None: 30 | return 31 | if not self._remove_when_empty: 32 | return 33 | 34 | if not self._node.hasChildren(): 35 | # A group that no longer has children may remove itself from the scene 36 | self._old_parent = self._node.getParent() 37 | self._node.setParent(None) 38 | Selection.remove(self._node) 39 | else: 40 | # A group that has removed itself from the scene because it had no children may add itself back to the scene 41 | # when a child is added to it. 42 | if not self._node.getParent() and self._old_parent: 43 | self._node.setParent(self._old_parent) 44 | self._old_parent = None 45 | 46 | def __deepcopy__(self, memo): 47 | return GroupDecorator() -------------------------------------------------------------------------------- /UM/Scene/Iterator/BreadthFirstIterator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import Iterator 5 | from UM.Scene.SceneNode import SceneNode 6 | 7 | 8 | class BreadthFirstIterator(Iterator.Iterator): 9 | def __init__(self, scene_node: SceneNode) -> None: 10 | super().__init__(scene_node) 11 | 12 | def _fillStack(self) -> None: 13 | self._node_stack.append(self._scene_node) 14 | for node in self._node_stack: # Loop through list, add children to back of list 15 | self._node_stack.extend(node.getChildren()) -------------------------------------------------------------------------------- /UM/Scene/Iterator/DepthFirstIterator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | from UM.Scene.SceneNode import SceneNode 4 | from . import Iterator 5 | 6 | 7 | class DepthFirstIterator(Iterator.Iterator): 8 | def __init__(self, scene_node: SceneNode) -> None: 9 | super().__init__(scene_node) 10 | 11 | def _fillStack(self) -> None: 12 | self._node_stack.append(self._scene_node) 13 | self._node_stack.extend(self._scene_node.getAllChildren()) 14 | -------------------------------------------------------------------------------- /UM/Scene/Iterator/Iterator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from typing import List, TYPE_CHECKING 5 | 6 | import typing 7 | 8 | if TYPE_CHECKING: 9 | from UM.Scene.SceneNode import SceneNode 10 | 11 | 12 | class Iterator: 13 | """Abstract iterator class.""" 14 | 15 | def __init__(self, scene_node: "SceneNode") -> None: 16 | super().__init__() # Call super to make multiple inheritance work. 17 | self._scene_node = scene_node 18 | self._node_stack = [] # type: List[SceneNode] 19 | self._fillStack() 20 | 21 | def _fillStack(self) -> None: 22 | """Fills the list of nodes by a certain order. The strategy to do this is to be defined by the child.""" 23 | 24 | raise NotImplementedError("Iterator is not correctly implemented. Requires a _fill_stack implementation.") 25 | 26 | def __iter__(self) -> typing.Iterator["SceneNode"]: 27 | return iter(self._node_stack) 28 | -------------------------------------------------------------------------------- /UM/Scene/Iterator/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #SHoop. Da. WOOOP (chargin mah laz0r, etc). -------------------------------------------------------------------------------- /UM/Scene/SceneNodeDecorator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from typing import Dict, Optional, TYPE_CHECKING 5 | 6 | if TYPE_CHECKING: 7 | from UM.Scene.SceneNode import SceneNode 8 | 9 | 10 | class SceneNodeDecorator: 11 | """The point of a SceneNodeDecorator is that it can be added to a SceneNode, where it then provides decorations 12 | Decorations are functions of a SceneNodeDecorator that can be called (except for functions already defined 13 | in SceneNodeDecorator). 14 | :sa SceneNode 15 | """ 16 | 17 | def __init__(self, node: Optional["SceneNode"] = None) -> None: 18 | super().__init__() 19 | self._node = node # type: Optional["SceneNode"] 20 | 21 | def setNode(self, node: "SceneNode") -> None: 22 | self._node = node 23 | 24 | def getNode(self) -> Optional["SceneNode"]: 25 | return self._node 26 | 27 | def clear(self) -> None: 28 | """Clear all data associated with this decorator. This will be called before the decorator is removed""" 29 | 30 | pass 31 | 32 | def __deepcopy__(self, memo: Dict[int, object]) -> "SceneNodeDecorator": 33 | raise NotImplementedError("Subclass {0} of SceneNodeDecorator should implement their own __deepcopy__() method.".format(str(self))) 34 | -------------------------------------------------------------------------------- /UM/Scene/SceneNodeSettings.py: -------------------------------------------------------------------------------- 1 | 2 | class SceneNodeSettings: 3 | LockPosition = "LockPosition" 4 | AutoDropDown = "AutoDropDown" 5 | -------------------------------------------------------------------------------- /UM/Scene/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #shoopdawoop 5 | 6 | ## \package Scene 7 | # Scene related classes. 8 | -------------------------------------------------------------------------------- /UM/Settings/ContainerFormatError.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | class ContainerFormatError(Exception): 5 | """A marker exception to indicate that something went wrong in deserialising a 6 | container because the file is corrupt. 7 | """ 8 | 9 | pass -------------------------------------------------------------------------------- /UM/Settings/DefinitionContainerUnpickler.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | safe_globals = { 4 | "UM.Settings.DefinitionContainer.DefinitionContainer", 5 | "collections.OrderedDict", 6 | "UM.Settings.SettingDefinition.SettingDefinition", 7 | "UM.Settings.SettingFunction.SettingFunction", 8 | "UM.Settings.SettingRelation.SettingRelation", 9 | "UM.Settings.SettingRelation.RelationType" 10 | } 11 | 12 | 13 | class DefinitionContainerUnpickler(pickle.Unpickler): 14 | """ 15 | Pickle by itself is not safe at all. We can't use any kind of signing, since the code itself is open (and at that 16 | point it would be trivially easy to read the secret used to sign it). 17 | 18 | What we can do is simply whitelist the things that the DefinitionContainer needs (and prevent everything else!) 19 | 20 | This obviously only protects against malicious cache files where the attacker (or user) can't modify the python 21 | files. This is common for setups where users don't have admin permission or have other more strict restrictions. 22 | """ 23 | def find_class(self, module, name): 24 | if module + "." + name in safe_globals: 25 | return super().find_class(module, name) 26 | 27 | # Raise exception for everything that isn't in the safe list. 28 | raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden") 29 | -------------------------------------------------------------------------------- /UM/Settings/EmptyInstanceContainer.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | from typing import Optional, Any, TYPE_CHECKING, Set 5 | 6 | from UM.Logger import Logger 7 | from UM.Settings.InstanceContainer import InstanceContainer 8 | 9 | if TYPE_CHECKING: 10 | from UM.Settings.Interfaces import ContainerInterface 11 | from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext 12 | 13 | 14 | # 15 | # Represents an empty instance container which is not allowed to store any 16 | # 17 | class EmptyInstanceContainer(InstanceContainer): 18 | def isDirty(self) -> bool: 19 | return False 20 | 21 | def getProperty(self, key: str, property_name: str, context: Optional["PropertyEvaluationContext"] = None) -> Any: 22 | return None 23 | 24 | def setProperty(self, key: str, property_name: str, property_value: Any, container: Optional["ContainerInterface"] = None, set_from_cache: bool = False) -> None: 25 | Logger.log("e", "Setting property %s of container %s which should remain empty", key, self.getName()) 26 | return 27 | 28 | def getConfigurationType(self) -> str: 29 | return "" # FIXME: not sure if this is correct 30 | 31 | def serialize(self, ignored_metadata_keys: Optional[Set[str]] = None) -> str: 32 | return "[general]\n version = " + str(InstanceContainer.Version) + "\n name = empty\n definition = fdmprinter\n" 33 | -------------------------------------------------------------------------------- /UM/Settings/Models/SettingPreferenceVisibilityHandler.py: -------------------------------------------------------------------------------- 1 | from UM.Application import Application 2 | 3 | from . import SettingVisibilityHandler 4 | 5 | 6 | class SettingPreferenceVisibilityHandler(SettingVisibilityHandler.SettingVisibilityHandler): 7 | def __init__(self, parent = None, *args, **kwargs): 8 | super().__init__(parent = parent, *args, **kwargs) 9 | 10 | Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged) 11 | self._onPreferencesChanged("general/visible_settings") 12 | 13 | self.visibilityChanged.connect(self._onVisibilityChanged) 14 | 15 | def _onPreferencesChanged(self, name): 16 | if name != "general/visible_settings": 17 | return 18 | 19 | new_visible = set() 20 | visibility_string = Application.getInstance().getPreferences().getValue("general/visible_settings") 21 | if visibility_string is None: 22 | return 23 | for key in visibility_string.split(";"): 24 | new_visible.add(key.strip()) 25 | 26 | self.setVisible(new_visible) 27 | 28 | def _onVisibilityChanged(self): 29 | preference = ";".join(self.getVisible()) 30 | Application.getInstance().getPreferences().setValue("general/visible_settings", preference) 31 | -------------------------------------------------------------------------------- /UM/Settings/Models/SettingVisibilityHandler.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from typing import Set 5 | from PyQt6.QtCore import QObject, pyqtSignal 6 | 7 | 8 | class SettingVisibilityHandler(QObject): 9 | def __init__(self, parent = None, *args, **kwargs) -> None: 10 | super().__init__(parent = parent, *args, **kwargs) 11 | 12 | self._visible = set() # type: Set[str] 13 | 14 | visibilityChanged = pyqtSignal() 15 | 16 | def setVisible(self, visible: Set[str]) -> None: 17 | if visible != self._visible: 18 | self._visible = visible 19 | self.visibilityChanged.emit() 20 | 21 | def getVisible(self) -> Set[str]: 22 | return self._visible.copy() 23 | 24 | def forceVisibilityChanged(self) -> None: 25 | self.visibilityChanged.emit() 26 | -------------------------------------------------------------------------------- /UM/Settings/Models/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | -------------------------------------------------------------------------------- /UM/Settings/PropertyEvaluationContext.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from collections import deque 5 | 6 | 7 | class PropertyEvaluationContext: 8 | """Context for evaluating a property value 9 | 10 | It contains: 11 | 1. a stack of containers during the evaluation in the function call stack fashion 12 | 2. a context dictionary which contains all the current context 13 | """ 14 | 15 | 16 | def __init__(self, source_stack = None): 17 | self.stack_of_containers = deque() 18 | if source_stack is not None: 19 | self.stack_of_containers.append(source_stack) 20 | self.context = {} 21 | 22 | def rootStack(self): 23 | if self.stack_of_containers: 24 | return self.stack_of_containers[0] 25 | 26 | def pushContainer(self, container): 27 | self.stack_of_containers.append(container) 28 | 29 | def popContainer(self) -> None: 30 | return self.stack_of_containers.pop() 31 | -------------------------------------------------------------------------------- /UM/Settings/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | -------------------------------------------------------------------------------- /UM/Settings/constant_instance_containers.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Cura is released under the terms of the LGPLv3 or higher. 3 | 4 | from .EmptyInstanceContainer import EmptyInstanceContainer 5 | 6 | # 7 | # This file contains all constant instance containers. 8 | # 9 | 10 | 11 | # Instance of an empty container 12 | EMPTY_CONTAINER_ID = "empty" 13 | empty_container = EmptyInstanceContainer(EMPTY_CONTAINER_ID) 14 | 15 | 16 | __all__ = ["EMPTY_CONTAINER_ID", "empty_container"] 17 | -------------------------------------------------------------------------------- /UM/Stage.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from typing import Union, Dict, Optional 5 | 6 | from PyQt6.QtCore import QObject, QUrl 7 | 8 | from UM.PluginObject import PluginObject 9 | 10 | 11 | class Stage(QObject, PluginObject): 12 | """Stages handle combined views in an Uranium application. 13 | 14 | The active stage decides which QML component to show where. 15 | Uranium has no notion of specific view locations as that's application specific. 16 | """ 17 | 18 | def __init__(self, parent: Optional[QObject] = None) -> None: 19 | super().__init__(parent) 20 | self._components: Dict[str, QUrl] = {} 21 | self._icon_source: QUrl = QUrl() 22 | 23 | def onStageSelected(self) -> None: 24 | """Something to do when this Stage is selected""" 25 | pass 26 | 27 | def onStageDeselected(self) -> None: 28 | """Something to do when this Stage is deselected""" 29 | pass 30 | 31 | def addDisplayComponent(self, name: str, source: Union[str, QUrl]) -> None: 32 | """Add a QML component to the stage""" 33 | 34 | if type(source) == str: 35 | source = QUrl.fromLocalFile(source) 36 | self._components[name] = source 37 | 38 | def getDisplayComponent(self, name: str) -> QUrl: 39 | """Get a QUrl by name.""" 40 | 41 | if name in self._components: 42 | return self._components[name] 43 | return QUrl() 44 | -------------------------------------------------------------------------------- /UM/TaskManagement/HttpRequestScope.py: -------------------------------------------------------------------------------- 1 | import platform 2 | from typing import Dict 3 | 4 | from PyQt6.QtNetwork import QNetworkRequest 5 | 6 | from UM.Application import Application 7 | 8 | 9 | class HttpRequestScope: 10 | """Modifies a request in some way. This concept is sometimes called a request interceptor. 11 | 12 | Could be used to add authorization headers or set user agents, for example 13 | """ 14 | 15 | def requestHook(self, request: QNetworkRequest) -> None: 16 | """Invoked after request-specific headers are set and before HttpRequestData is created""" 17 | 18 | pass 19 | 20 | @staticmethod 21 | def addHeaders(request: QNetworkRequest, header_dict: Dict) -> None: 22 | for key, value in header_dict.items(): 23 | request.setRawHeader(key.encode("utf-8"), value.encode("utf-8")) 24 | 25 | 26 | class DefaultUserAgentScope(HttpRequestScope): 27 | """Adds a User-Agent header""" 28 | 29 | def __init__(self, application: Application) -> None: 30 | self.header_dict = { 31 | "User-Agent": "%s/%s (%s %s)" % (application.getApplicationName(), 32 | application.getVersion(), 33 | platform.system(), 34 | platform.machine()) 35 | } 36 | 37 | def requestHook(self, request: QNetworkRequest) -> None: 38 | super().requestHook(request) 39 | self.addHeaders(request, self.header_dict) 40 | 41 | 42 | class JsonDecoratorScope(HttpRequestScope): 43 | """Extends a scope by adding Content-Type and Accept for application/json 44 | 45 | Should be used for Json requests which only accept a Json response 46 | """ 47 | 48 | def __init__(self, base: HttpRequestScope) -> None: 49 | self.base = base 50 | self.header_dict = { 51 | "Content-Type": "application/json", 52 | "Accept": "application/json" 53 | } 54 | 55 | def requestHook(self, request: QNetworkRequest) -> None: 56 | # not calling super().request_hook() because base will do that. 57 | self.base.requestHook(request) 58 | self.addHeaders(request, self.header_dict) 59 | -------------------------------------------------------------------------------- /UM/TaskManagement/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/UM/TaskManagement/__init__.py -------------------------------------------------------------------------------- /UM/Util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | 5 | def parseBool(value): 6 | """Convert a value to a boolean 7 | 8 | :param :type{bool|str|int} any value. 9 | :return: :type{bool} 10 | """ 11 | 12 | return value in [True, "True", "true", "Yes", "yes", 1] 13 | 14 | 15 | -------------------------------------------------------------------------------- /UM/View/DefaultPass.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import UM.Qt.QtApplication 5 | from UM.View.RenderPass import RenderPass 6 | 7 | 8 | class DefaultPass(RenderPass): 9 | """A render pass subclass that renders everything with the default parameters. 10 | 11 | This class provides the basic rendering of the objects in the scene. 12 | """ 13 | def __init__(self, width: int, height: int) -> None: 14 | super().__init__("default", width, height, 0) 15 | 16 | self._renderer = UM.Qt.QtApplication.QtApplication.getInstance().getRenderer() 17 | 18 | def render(self) -> None: 19 | self.bind() 20 | 21 | camera = UM.Qt.QtApplication.QtApplication.getInstance().getController().getScene().getActiveCamera() 22 | 23 | for batch in self._renderer.getBatches(): 24 | batch.render(camera) 25 | 26 | self.release() 27 | 28 | 29 | -------------------------------------------------------------------------------- /UM/View/GL/FrameBufferObject.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from PyQt6.QtGui import QImage 5 | from PyQt6.QtOpenGL import QOpenGLFramebufferObject, QOpenGLFramebufferObjectFormat 6 | 7 | class FrameBufferObject: 8 | """An interface for OpenGL FrameBuffer Objects. 9 | 10 | This class describes a minimal interface that is expected of FrameBuffer Object 11 | classes. 12 | """ 13 | def __init__(self, width: int, height: int) -> None: 14 | super().__init__() 15 | 16 | buffer_format = QOpenGLFramebufferObjectFormat() 17 | buffer_format.setAttachment(QOpenGLFramebufferObject.Attachment.Depth) 18 | self._fbo = QOpenGLFramebufferObject(width, height, buffer_format) 19 | 20 | self._contents = None 21 | 22 | def getTextureId(self) -> int: 23 | """Get the texture ID of the texture target of this FBO.""" 24 | return self._fbo.texture() 25 | 26 | def bind(self) -> None: 27 | """Bind the FBO so it can be rendered to.""" 28 | self._contents = None 29 | self._fbo.bind() 30 | 31 | def release(self) -> None: 32 | """Release the FBO so it will no longer be rendered to.""" 33 | self._fbo.release() 34 | 35 | def getContents(self) -> QImage: 36 | """Get the contents of the FBO as an image data object.""" 37 | if self._contents is None: 38 | self._contents = self._fbo.toImage() 39 | 40 | return self._contents 41 | -------------------------------------------------------------------------------- /UM/View/GL/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/UM/View/GL/__init__.py -------------------------------------------------------------------------------- /UM/View/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #shoopdawoop 5 | 6 | ## \package View 7 | # View related classes. 8 | -------------------------------------------------------------------------------- /UM/Workspace/WorkspaceMetadataStorage.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from collections import defaultdict 5 | from typing import Dict, Union, List 6 | 7 | basic_metadata_type = Union[str, int, float, bool, None] 8 | full_metadata_type = Union[List[basic_metadata_type], basic_metadata_type, Dict[str, basic_metadata_type]] 9 | 10 | 11 | # modified from Original source: https://github.com/python/mypy/issues/731#issuecomment-539905783 12 | # The recursive type doesn't seem to work for us. MyPy crashes with it. So this limits the type to 3 levels deep nesting. 13 | JSONPrimitive = Union[str, int, bool, None] 14 | JSONTypeLevel1 = Union[JSONPrimitive, Dict[str, JSONPrimitive], List[JSONPrimitive]] 15 | JSONTypeLevel2 = Union[JSONPrimitive, Dict[str, JSONTypeLevel1], List[JSONTypeLevel1]] 16 | JSONType = Union[JSONPrimitive, Dict[str, JSONTypeLevel2], List[JSONTypeLevel2]] 17 | 18 | 19 | class WorkspaceMetadataStorage: 20 | # The WorkspaceMetadataStorage, as the name implies, allows for plugins to store (and retrieve) extra information 21 | # to a workspace. 22 | def __init__(self) -> None: 23 | # We allow for a set of key value pairs to be stored per plugin. 24 | self._data = defaultdict(dict) # type: Dict[str, Dict[str, JSONType]] 25 | 26 | def setEntryToStore(self, plugin_id: str, key: str, data: JSONType) -> None: 27 | self._data[plugin_id][key] = data 28 | 29 | def setAllData(self, data: Dict[str, Dict[str, JSONType]]) -> None: 30 | self._data = defaultdict(dict, data) 31 | 32 | def getAllData(self) -> Dict[str, Dict[str, JSONType]]: 33 | return self._data 34 | 35 | def getPluginMetadata(self, plugin_id: str) -> Dict[str, JSONType]: 36 | return self._data[plugin_id] 37 | 38 | def getPluginMetadataEntry(self, plugin_id: str, key: str) -> JSONType: 39 | return self._data[plugin_id].get(key) 40 | 41 | def clear(self) -> None: 42 | self._data = defaultdict(dict) -------------------------------------------------------------------------------- /UM/Workspace/WorkspaceReader.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.FileHandler.FileReader import FileReader 5 | from typing import Optional, Union, Tuple, List, Dict, Any 6 | 7 | from UM.Scene.SceneNode import SceneNode 8 | 9 | 10 | class WorkspaceReader(FileReader): 11 | def __init__(self) -> None: 12 | super().__init__() 13 | self._workspace_name = None # type: Optional[str] 14 | 15 | ## Read an entire workspace 16 | def read(self, file_name: str) -> Union[Tuple[List[SceneNode], Dict[str, Dict[str, Any]]], List[SceneNode]]: 17 | pass 18 | 19 | def workspaceName(self) -> Optional[str]: 20 | return self._workspace_name 21 | 22 | def setWorkspaceName(self, workspace_name: str) -> None: 23 | self._workspace_name = workspace_name 24 | -------------------------------------------------------------------------------- /UM/Workspace/WorkspaceWriter.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.FileHandler.FileWriter import FileWriter 5 | 6 | 7 | class WorkspaceWriter(FileWriter): 8 | def __init__(self, add_to_recent_files: bool = True) -> None: 9 | super().__init__(add_to_recent_files) 10 | 11 | def write(self, stream, node, mode = FileWriter.OutputMode.BinaryMode, **kwargs): 12 | raise NotImplementedError("WorkspaceWriter plugin was not correctly implemented, no write was specified") -------------------------------------------------------------------------------- /UM/Workspace/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/UM/Workspace/__init__.py -------------------------------------------------------------------------------- /UM/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #Shoopdawoop 5 | 6 | ## \package UM 7 | # This is the main library for Uranium applications. 8 | 9 | from UM.i18n import i18nCatalog 10 | i18n_catalog = i18nCatalog("uranium") 11 | 12 | import warnings 13 | warnings.simplefilter("once", DeprecationWarning) 14 | -------------------------------------------------------------------------------- /examples/definition_query/query.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2016 Ultimaker B.V. 4 | # Cura is released under the terms of the LGPLv3 or higher. 5 | 6 | # 7 | # A simple example/tool to query a definition and get a list of settings matching those criteria. 8 | # Call it with query.py 9 | # For example: query.py fdmprinter.def.json "{ \"key\": \"layer_height\" }" 10 | # 11 | 12 | import os 13 | import sys 14 | import json 15 | 16 | from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType 17 | from UM.Settings.Validator import Validator 18 | from UM.Settings.DefinitionContainer import DefinitionContainer 19 | 20 | if len(sys.argv) < 3: 21 | print("Usage: query.py [file] [query]") 22 | exit(1) 23 | 24 | file_path = sys.argv[1] 25 | 26 | # These are defined by Cura but we would still like to be able to query them. 27 | SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True) 28 | SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True) 29 | SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True, read_only = True) 30 | SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True, read_only = True) 31 | SettingDefinition.addSupportedProperty("force_depends_on_settings", DefinitionPropertyType.Any, default = [], read_only = True) 32 | SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1") 33 | SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None) 34 | SettingDefinition.addSettingType("extruder", None, str, Validator) 35 | 36 | definition = DefinitionContainer(os.path.basename(file_path)) 37 | with open(file_path, encoding = "utf-8") as f: 38 | definition.deserialize(f.read()) 39 | 40 | query = json.loads(sys.argv[2]) 41 | 42 | results = definition.findDefinitions(**query) 43 | 44 | for result in results: 45 | print("Found setting:", result.key) 46 | -------------------------------------------------------------------------------- /plugins/ConsoleLogger/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #Shoopdawoop 5 | from . import ConsoleLogger 6 | 7 | 8 | def getMetaData(): 9 | return {} 10 | 11 | 12 | def register(app): 13 | return { "logger": ConsoleLogger.ConsoleLogger() } 14 | -------------------------------------------------------------------------------- /plugins/ConsoleLogger/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Console Logger", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Outputs log information to the console.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJReader/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #Shoopdawoop 5 | from . import OBJReader 6 | 7 | from UM.i18n import i18nCatalog 8 | i18n_catalog = i18nCatalog("uranium") 9 | 10 | def getMetaData(): 11 | return { 12 | "mesh_reader": [ 13 | { 14 | "extension": "obj", 15 | "description": i18n_catalog.i18nc("@item:inlistbox", "Wavefront OBJ File") 16 | } 17 | ] 18 | } 19 | 20 | 21 | def register(app): 22 | return {"mesh_reader": OBJReader.OBJReader()} 23 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJReader/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Wavefront OBJ Reader", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Makes it possible to read Wavefront OBJ files.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJReader/tests/negative_indexed.obj: -------------------------------------------------------------------------------- 1 | v 10 10 0 2 | v 10 0 0 3 | v 10 10 10 4 | v 10 0 10 5 | v 0 10 0 6 | v 0 0 0 7 | v 0 10 10 8 | v 0 0 10 9 | f -8 -4 -2 -6 10 | f -5 -6 -2 -1 11 | f -1 -2 -4 -3 12 | f -3 -7 -5 -1 13 | f -7 -8 -6 -5 14 | f -3 -4 -8 -7 15 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJReader/tests/negative_interweaved.obj: -------------------------------------------------------------------------------- 1 | v 10 10 0 2 | v 0 10 0 3 | v 0 10 10 4 | f -3 -2 -1 5 | v 10 10 0 6 | v 0 10 10 7 | v 10 10 10 8 | f -3 -2 -1 9 | v 10 0 10 10 | v 10 10 10 11 | v 0 10 10 12 | f -3 -2 -1 13 | v 10 0 10 14 | v 0 10 10 15 | v 0 0 10 16 | f -3 -2 -1 17 | v 0 0 10 18 | v 0 10 10 19 | v 0 10 0 20 | f -3 -2 -1 21 | v 0 0 10 22 | v 0 10 0 23 | v 0 0 0 24 | f -3 -2 -1 25 | v 0 0 0 26 | v 10 0 0 27 | v 10 0 10 28 | f -3 -2 -1 29 | v 0 0 0 30 | v 10 0 10 31 | v 0 0 10 32 | f -3 -2 -1 33 | v 10 0 0 34 | v 10 10 0 35 | v 10 10 10 36 | f -3 -2 -1 37 | v 10 0 0 38 | v 10 10 10 39 | v 10 0 10 40 | f -3 -2 -1 41 | v 0 0 0 42 | v 0 10 0 43 | v 10 10 0 44 | f -3 -2 -1 45 | v 0 0 0 46 | v 10 10 0 47 | v 10 0 0 48 | f -3 -2 -1 49 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJReader/tests/vertex_duplicated.obj: -------------------------------------------------------------------------------- 1 | v 10 10 0 2 | v 0 10 0 3 | v 0 10 10 4 | v 10 10 0 5 | v 0 10 10 6 | v 10 10 10 7 | v 10 0 10 8 | v 10 10 10 9 | v 0 10 10 10 | v 10 0 10 11 | v 0 10 10 12 | v 0 0 10 13 | v 0 0 10 14 | v 0 10 10 15 | v 0 10 0 16 | v 0 0 10 17 | v 0 10 0 18 | v 0 0 0 19 | v 0 0 0 20 | v 10 0 0 21 | v 10 0 10 22 | v 0 0 0 23 | v 10 0 10 24 | v 0 0 10 25 | v 10 0 0 26 | v 10 10 0 27 | v 10 10 10 28 | v 10 0 0 29 | v 10 10 10 30 | v 10 0 10 31 | v 0 0 0 32 | v 0 10 0 33 | v 10 10 0 34 | v 0 0 0 35 | v 10 10 0 36 | v 10 0 0 37 | f 1 2 3 38 | f 4 5 6 39 | f 7 8 9 40 | f 10 11 12 41 | f 13 14 15 42 | f 16 17 18 43 | f 19 20 21 44 | f 22 23 24 45 | f 25 26 27 46 | f 28 29 30 47 | f 31 32 33 48 | f 34 35 36 49 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJReader/tests/vertex_indexed.obj: -------------------------------------------------------------------------------- 1 | v 10 10 0 2 | v 10 0 0 3 | v 10 10 10 4 | v 10 0 10 5 | v 0 10 0 6 | v 0 0 0 7 | v 0 10 10 8 | v 0 0 10 9 | f 1 5 7 3 10 | f 4 3 7 8 11 | f 8 7 5 6 12 | f 6 2 4 8 13 | f 2 1 3 4 14 | f 6 5 1 2 15 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJReader/tests/vertex_normal_indexed.obj: -------------------------------------------------------------------------------- 1 | v 10 10 0 2 | v 10 0 0 3 | v 10 10 10 4 | v 10 0 10 5 | v 0 10 0 6 | v 0 0 0 7 | v 0 10 10 8 | v 0 0 10 9 | vn 0 1 0 10 | vn 0 0 1 11 | vn -1 0 0 12 | vn 0 -1 0 13 | vn 1 0 0 14 | vn 0 0 -1 15 | f 1//1 5//1 7//1 3//1 16 | f 4//2 3//2 7//2 8//2 17 | f 8//3 7//3 5//3 6//3 18 | f 6//4 2//4 4//4 8//4 19 | f 2//5 1//5 3//5 4//5 20 | f 6//6 5//6 1//6 2//6 21 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJReader/tests/vertex_texture_indexed.obj: -------------------------------------------------------------------------------- 1 | v 10 10 0 2 | v 10 0 0 3 | v 10 10 10 4 | v 10 0 10 5 | v 0 10 0 6 | v 0 0 0 7 | v 0 10 10 8 | v 0 0 10 9 | vt 0.625 0.5 10 | vt 0.875 0.5 11 | vt 0.875 0.75 12 | vt 0.625 0.75 13 | vt 0.375 0.75 14 | vt 0.625 1 15 | vt 0.375 1 16 | vt 0.375 0 17 | vt 0.625 0 18 | vt 0.625 0.25 19 | vt 0.375 0.25 20 | vt 0.125 0.5 21 | vt 0.375 0.5 22 | vt 0.125 0.75 23 | f 1/1 5/2 7/3 3/4 24 | f 4/5 3/4 7/6 8/7 25 | f 8/8 7/9 5/10 6/11 26 | f 6/12 2/13 4/5 8/14 27 | f 2/13 1/1 3/4 4/5 28 | f 6/11 5/10 1/1 2/13 29 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJReader/tests/vertex_texture_normal_indexed.obj: -------------------------------------------------------------------------------- 1 | v 10 10 0 2 | v 10 0 0 3 | v 10 10 10 4 | v 10 0 10 5 | v 0 10 0 6 | v 0 0 0 7 | v 0 10 10 8 | v 0 0 10 9 | vt 0.625 0.5 10 | vt 0.875 0.5 11 | vt 0.875 0.75 12 | vt 0.625 0.75 13 | vt 0.375 0.75 14 | vt 0.625 1 15 | vt 0.375 1 16 | vt 0.375 0 17 | vt 0.625 0 18 | vt 0.625 0.25 19 | vt 0.375 0.25 20 | vt 0.125 0.5 21 | vt 0.375 0.5 22 | vt 0.125 0.75 23 | vn 0 1 0 24 | vn 0 0 1 25 | vn -1 0 0 26 | vn 0 -1 0 27 | vn 1 0 0 28 | vn 0 0 -1 29 | f 1/1/1 5/2/1 7/3/1 3/4/1 30 | f 4/5/2 3/4/2 7/6/2 8/7/2 31 | f 8/8/3 7/9/3 5/10/3 6/11/3 32 | f 6/12/4 2/13/4 4/5/4 8/14/4 33 | f 2/13/5 1/1/5 3/4/5 4/5/5 34 | f 6/11/6 5/10/6 1/1/6 2/13/6 35 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJWriter/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import OBJWriter 5 | 6 | from UM.i18n import i18nCatalog 7 | i18n_catalog = i18nCatalog("uranium") 8 | 9 | #TODO: We can't quite finish this as we have no real faces to save yet. This writer should work, but is not tested. 10 | def getMetaData(): 11 | return { 12 | "mesh_writer": { 13 | "output": [{ 14 | "extension": "obj", 15 | "description": i18n_catalog.i18nc("@item:inlistbox", "Wavefront OBJ File"), 16 | "mime_type": "application/x-wavefront-obj", 17 | "mode": OBJWriter.OBJWriter.OutputMode.TextMode 18 | }] 19 | } 20 | } 21 | 22 | def register(app): 23 | return { "mesh_writer": OBJWriter.OBJWriter() } 24 | -------------------------------------------------------------------------------- /plugins/FileHandlers/OBJWriter/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Wavefront OBJ Writer", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Makes it possible to write Wavefront OBJ files.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/FileHandlers/STLReader/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import STLReader 5 | 6 | from UM.i18n import i18nCatalog 7 | i18n_catalog = i18nCatalog("uranium") 8 | 9 | def getMetaData(): 10 | return { 11 | "mesh_reader": [ 12 | { 13 | "extension": "stl", 14 | "description": i18n_catalog.i18nc("@item:inlistbox", "STL File") 15 | } 16 | ] 17 | } 18 | 19 | 20 | def register(app): 21 | return {"mesh_reader": STLReader.STLReader()} 22 | -------------------------------------------------------------------------------- /plugins/FileHandlers/STLReader/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "STL Reader", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides support for reading STL files.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/FileHandlers/STLWriter/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import STLWriter 5 | 6 | from UM.i18n import i18nCatalog 7 | i18n_catalog = i18nCatalog("uranium") 8 | 9 | def getMetaData(): 10 | return { 11 | "mesh_writer": { 12 | "output": [ 13 | { 14 | "mime_type": "model/stl", 15 | "mode": STLWriter.STLWriter.OutputMode.TextMode, 16 | "extension": "stl", 17 | "description": i18n_catalog.i18nc("@item:inlistbox", "STL File (ASCII)") 18 | }, 19 | { 20 | "mime_type": "model/stl", 21 | "mode": STLWriter.STLWriter.OutputMode.BinaryMode, 22 | "extension": "stl", 23 | "description": i18n_catalog.i18nc("@item:inlistbox", "STL File (Binary)") 24 | } 25 | ] 26 | } 27 | } 28 | 29 | def register(app): 30 | return { "mesh_writer": STLWriter.STLWriter() } 31 | -------------------------------------------------------------------------------- /plugins/FileHandlers/STLWriter/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "STL Writer", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides support for writing STL files.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/FileLogger/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | #Shoopdawoop 5 | from . import FileLogger 6 | 7 | 8 | def getMetaData(): 9 | return { 10 | } 11 | 12 | def register(app): 13 | return { "logger": FileLogger.FileLogger("{0}.log".format(app.getApplicationName())) } 14 | -------------------------------------------------------------------------------- /plugins/FileLogger/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "File Logger", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Outputs log information to a file in your settings folder.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/LocalContainerProvider/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import LocalContainerProvider 5 | 6 | def getMetaData(): 7 | return { 8 | "container_provider": { 9 | "priority": 0 10 | } 11 | } 12 | 13 | def register(app): 14 | return { "container_provider": LocalContainerProvider.LocalContainerProvider() } 15 | -------------------------------------------------------------------------------- /plugins/LocalContainerProvider/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Local Container Provider", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides built-in setting containers that come with the installation of the application.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/LocalFileOutputDevice/LocalFileOutputDevicePlugin.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 UltiMaker 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import os 5 | 6 | from UM.Application import Application 7 | from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin 8 | from UM.i18n import i18nCatalog 9 | from .LocalFileOutputDevice import LocalFileOutputDevice 10 | 11 | catalog = i18nCatalog("uranium") 12 | 13 | 14 | class LocalFileOutputDevicePlugin(OutputDevicePlugin): 15 | """Implements an OutputDevicePlugin that provides a single instance of LocalFileOutputDevice""" 16 | 17 | def __init__(self): 18 | super().__init__() 19 | 20 | Application.getInstance().getPreferences().addPreference("local_file/last_used_type", "") 21 | Application.getInstance().getPreferences().addPreference("local_file/dialog_save_path", 22 | os.path.expanduser("~/")) 23 | 24 | def start(self): 25 | self.getOutputDeviceManager().addProjectOutputDevice(LocalFileOutputDevice(add_to_output_devices = True)) 26 | 27 | def stop(self): 28 | self.getOutputDeviceManager().removeProjectOutputDevice("local_file") -------------------------------------------------------------------------------- /plugins/LocalFileOutputDevice/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import LocalFileOutputDevicePlugin 5 | 6 | 7 | def getMetaData(): 8 | return { 9 | } 10 | 11 | def register(app): 12 | return { "output_device": LocalFileOutputDevicePlugin.LocalFileOutputDevicePlugin() } 13 | -------------------------------------------------------------------------------- /plugins/LocalFileOutputDevice/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Local File Output Device", 3 | "description": "Enables saving to local files.", 4 | "author": "Ultimaker B.V.", 5 | "version": "1.0.1", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/Tools/CameraTool/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import CameraTool 5 | 6 | 7 | def getMetaData(): 8 | return { 9 | "tool": { 10 | "visible": False 11 | } 12 | } 13 | 14 | 15 | def register(app): 16 | return {"tool": CameraTool.CameraTool()} 17 | -------------------------------------------------------------------------------- /plugins/Tools/CameraTool/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Camera Tool", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides the tool to manipulate the camera.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/Tools/MirrorTool/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import MirrorTool 5 | 6 | from UM.i18n import i18nCatalog 7 | i18n_catalog = i18nCatalog("uranium") 8 | 9 | def getMetaData(): 10 | return { 11 | "tool": { 12 | "name": i18n_catalog.i18nc("@label", "Mirror"), 13 | "description": i18n_catalog.i18nc("@info:tooltip", "Mirror Model"), 14 | "icon": "Mirror", 15 | "weight": 2 16 | }, 17 | } 18 | 19 | def register(app): 20 | return { "tool": MirrorTool.MirrorTool() } 21 | -------------------------------------------------------------------------------- /plugins/Tools/MirrorTool/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mirror Tool", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides the Mirror tool.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/Tools/RotateTool/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import RotateTool 5 | 6 | from UM.i18n import i18nCatalog 7 | i18n_catalog = i18nCatalog("uranium") 8 | 9 | def getMetaData(): 10 | return { 11 | "tool": { 12 | "name": i18n_catalog.i18nc("@label", "Rotate"), 13 | "description": i18n_catalog.i18nc("@info:tooltip", "Rotate Model"), 14 | "icon": "Rotate", 15 | "tool_panel": "RotateTool.qml", 16 | "weight": 1 17 | } 18 | } 19 | 20 | def register(app): 21 | return { "tool": RotateTool.RotateTool() } 22 | -------------------------------------------------------------------------------- /plugins/Tools/RotateTool/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rotate Tool", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides the Rotate tool.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/Tools/RotateTool/tests/TestRotateTool.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest.mock import patch, MagicMock 3 | import sys 4 | import os 5 | import pytest 6 | 7 | sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) 8 | 9 | import RotateTool 10 | 11 | mocked_scene = MagicMock() 12 | 13 | @pytest.fixture() 14 | def rotate_tool(): 15 | application = MagicMock() 16 | controller = MagicMock() 17 | application.getController = MagicMock(return_value=controller) 18 | controller.getScene = MagicMock(return_value=mocked_scene) 19 | mocked_scene.reset_mock() 20 | with patch("UM.Application.Application.getInstance", MagicMock(return_value=application)): 21 | return RotateTool.RotateTool() 22 | 23 | 24 | @pytest.mark.parametrize("data", [ 25 | {"attribute": "rotationSnapAngle", "value": 32}, 26 | {"attribute": "rotationSnap", "value": False}, 27 | ]) 28 | def test_getAndSet(data, rotate_tool): 29 | # Convert the first letter into a capital 30 | attribute = list(data["attribute"]) 31 | attribute[0] = attribute[0].capitalize() 32 | attribute = "".join(attribute) 33 | 34 | # mock the correct emit 35 | rotate_tool.propertyChanged = MagicMock() 36 | 37 | # Attempt to set the value 38 | getattr(rotate_tool, "set" + attribute)(data["value"]) 39 | 40 | # Check if signal fired. 41 | assert rotate_tool.propertyChanged.emit.call_count == 1 42 | 43 | # Ensure that the value got set 44 | assert getattr(rotate_tool, "get" + attribute)() == data["value"] 45 | 46 | # Attempt to set the value again 47 | getattr(rotate_tool, "set" + attribute)(data["value"]) 48 | # The signal should not fire again 49 | assert rotate_tool.propertyChanged.emit.call_count == 1 -------------------------------------------------------------------------------- /plugins/Tools/ScaleTool/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import ScaleTool 5 | 6 | from UM.i18n import i18nCatalog 7 | i18n_catalog = i18nCatalog("uranium") 8 | 9 | def getMetaData(): 10 | return { 11 | "tool": { 12 | "name": i18n_catalog.i18nc("@label", "Scale"), 13 | "description": i18n_catalog.i18nc("@info:tooltip", "Scale Model"), 14 | "icon": "Scale", 15 | "tool_panel": "ScaleTool.qml", 16 | "weight": 0 17 | } 18 | } 19 | 20 | def register(app): 21 | return { "tool": ScaleTool.ScaleTool() } 22 | -------------------------------------------------------------------------------- /plugins/Tools/ScaleTool/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Scale Tool", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides the Scale tool.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/Tools/SelectionTool/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import SelectionTool 5 | 6 | 7 | def getMetaData(): 8 | return { 9 | "tool": { 10 | "visible": False 11 | } 12 | } 13 | 14 | def register(app): 15 | return { "tool": SelectionTool.SelectionTool() } 16 | -------------------------------------------------------------------------------- /plugins/Tools/SelectionTool/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Selection Tool", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides the Selection tool.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/Tools/TranslateTool/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import TranslateTool 5 | 6 | from UM.i18n import i18nCatalog 7 | i18n_catalog = i18nCatalog("uranium") 8 | 9 | def getMetaData(): 10 | return { 11 | "tool": { 12 | "name": i18n_catalog.i18nc("@action:button", "Move"), 13 | "description": i18n_catalog.i18nc("@info:tooltip", "Move Model"), 14 | "icon": "ArrowFourWay", 15 | "tool_panel": "TranslateTool.qml", 16 | "weight": -1 17 | } 18 | } 19 | 20 | def register(app): 21 | return { "tool": TranslateTool.TranslateTool() } 22 | -------------------------------------------------------------------------------- /plugins/Tools/TranslateTool/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Move Tool", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides the Move tool.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/Tools/TranslateTool/tests/TestTranslateTool.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest.mock import patch, MagicMock 3 | import sys 4 | import os 5 | import pytest 6 | 7 | sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) 8 | 9 | import TranslateTool 10 | 11 | mocked_scene = MagicMock() 12 | 13 | @pytest.fixture() 14 | def translate_tool(): 15 | application = MagicMock() 16 | controller = MagicMock() 17 | application.getController = MagicMock(return_value=controller) 18 | controller.getScene = MagicMock(return_value=mocked_scene) 19 | mocked_scene.reset_mock() 20 | with patch("UM.Application.Application.getInstance", MagicMock(return_value=application)): 21 | return TranslateTool.TranslateTool() 22 | 23 | 24 | def test_getXYZ_no_selection(translate_tool): 25 | assert translate_tool.getX() == 0 26 | assert translate_tool.getY() == 0 27 | assert translate_tool.getZ() == 0 28 | 29 | @pytest.mark.parametrize("input, result", [("12", 12), 30 | ("MONKEY", 0), 31 | ("-2", -2), 32 | ("12.6", 12.6)]) 33 | def test_parseFloat(input, result): 34 | assert TranslateTool.TranslateTool._parseFloat(input) == result 35 | 36 | 37 | def test_setEnabledAxis(translate_tool): 38 | mock_handle = MagicMock() 39 | translate_tool.setHandle(mock_handle) 40 | 41 | translate_tool.setEnabledAxis([1, 2]) 42 | 43 | mock_handle.setEnabledAxis.assert_called_with([1, 2]) 44 | 45 | 46 | def test_getLockPosition_no_selection(translate_tool): 47 | assert not translate_tool.getLockPosition() -------------------------------------------------------------------------------- /plugins/UpdateChecker/AnnotatedUpdateMessage.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from typing import Optional 5 | 6 | from UM.Message import Message 7 | 8 | 9 | class AnnotatedUpdateMessage(Message): 10 | 11 | def __init__(self, parent = None, *args, **kwargs): 12 | super().__init__(parent = parent, *args, **kwargs) 13 | 14 | self.download_url: Optional[str] = None 15 | self.change_log_url: Optional[str] = None 16 | self._lifetime = 60 17 | -------------------------------------------------------------------------------- /plugins/UpdateChecker/NewBetaVersionMessage.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM import i18nCatalog 5 | from UM.Application import Application 6 | from UM.Message import Message 7 | from UM.Version import Version 8 | 9 | from .AnnotatedUpdateMessage import AnnotatedUpdateMessage 10 | 11 | I18N_CATALOG = i18nCatalog("uranium") 12 | 13 | 14 | class NewBetaVersionMessage(AnnotatedUpdateMessage): 15 | def __init__(self, application_display_name: str, newest_version: Version) -> None: 16 | super().__init__( 17 | title = I18N_CATALOG.i18nc("@info:status", 18 | "{application_name} {version_number} is available!").format( 19 | application_name = application_display_name, 20 | version_number = newest_version), 21 | text = I18N_CATALOG.i18nc("@info:status", 22 | "Try out the latest BETA version and help us improve {application_name}.").format( 23 | application_name = application_display_name) 24 | ) 25 | 26 | self.change_log_url = Application.getInstance().beta_change_log_url 27 | 28 | self.addAction("download", I18N_CATALOG.i18nc("@action:button", "Download"), "[no_icon]", "[no_description]") 29 | 30 | self.addAction("new_features", 31 | I18N_CATALOG.i18nc("@action:button", "Learn more"), 32 | "[no_icon]", 33 | "[no_description]", 34 | button_style = Message.ActionButtonStyle.LINK, 35 | button_align = Message.ActionButtonAlignment.ALIGN_LEFT) 36 | -------------------------------------------------------------------------------- /plugins/UpdateChecker/NewVersionMessage.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM import i18nCatalog 5 | from UM.Application import Application 6 | from UM.Message import Message 7 | from UM.Version import Version 8 | 9 | from .AnnotatedUpdateMessage import AnnotatedUpdateMessage 10 | 11 | I18N_CATALOG = i18nCatalog("uranium") 12 | 13 | 14 | class NewVersionMessage(AnnotatedUpdateMessage): 15 | def __init__(self, application_display_name: str, newest_version: Version) -> None: 16 | super().__init__( 17 | title = I18N_CATALOG.i18nc("@info:status", 18 | "{application_name} {version_number} is available!").format( 19 | application_name = application_display_name, 20 | version_number = newest_version), 21 | text = I18N_CATALOG.i18nc("@info:status", 22 | "{application_name} {version_number} provides a better and more reliable printing experience.").format( 23 | application_name = application_display_name, 24 | version_number = newest_version)) 25 | 26 | self.change_log_url = Application.getInstance().change_log_url 27 | 28 | self.addAction("download", I18N_CATALOG.i18nc("@action:button", "Download"), "[no_icon]", "[no_description]") 29 | 30 | self.addAction("new_features", I18N_CATALOG.i18nc("@action:button", "Learn more"), "[no_icon]", 31 | "[no_description]", 32 | button_style = Message.ActionButtonStyle.LINK, 33 | button_align = Message.ActionButtonAlignment.ALIGN_LEFT) 34 | -------------------------------------------------------------------------------- /plugins/UpdateChecker/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import UpdateChecker 5 | 6 | 7 | def getMetaData(): 8 | return { 9 | } 10 | 11 | def register(app): 12 | return { "extension": UpdateChecker.UpdateChecker() } 13 | -------------------------------------------------------------------------------- /plugins/UpdateChecker/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Update Checker", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Checks for updates of the software.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /plugins/UpdateChecker/tests/CuraAndCuraBeta1-0-0.json: -------------------------------------------------------------------------------- 1 | { 2 | "cura": { 3 | "Windows": { 4 | "major": 1, 5 | "minor": 0, 6 | "revision": 0, 7 | "url": "https://www.ultimaker.com" 8 | }, 9 | "Linux": { 10 | "major": 1, 11 | "minor": 0, 12 | "revision": 0, 13 | "url": "https://www.ultimaker.com" 14 | }, 15 | "Darwin": { 16 | "major": 1, 17 | "minor": 0, 18 | "revision": 0, 19 | "url": "https://www.ultimaker.com" 20 | } 21 | }, 22 | "cura-beta": { 23 | "Windows": { 24 | "major": 1, 25 | "minor": 0, 26 | "revision": 0, 27 | "url": "https://www.ultimaker.com/beta" 28 | }, 29 | "Linux": { 30 | "major": 1, 31 | "minor": 0, 32 | "revision": 0, 33 | "url": "https://www.ultimaker.com/beta" 34 | }, 35 | "Darwin": { 36 | "major": 1, 37 | "minor": 0, 38 | "revision": 0, 39 | "url": "https://www.ultimaker.com/beta" 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /plugins/UpdateChecker/tests/CuraOnly1-0-0.json: -------------------------------------------------------------------------------- 1 | { 2 | "cura": { 3 | "Windows": { 4 | "major": 1, 5 | "minor": 0, 6 | "revision": 0, 7 | "url": "https://www.ultimaker.com" 8 | }, 9 | "Linux": { 10 | "major": 1, 11 | "minor": 0, 12 | "revision": 0, 13 | "url": "https://www.ultimaker.com" 14 | }, 15 | "Darwin": { 16 | "major": 1, 17 | "minor": 0, 18 | "revision": 0, 19 | "url": "https://www.ultimaker.com" 20 | } 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /plugins/UpdateChecker/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/plugins/UpdateChecker/tests/__init__.py -------------------------------------------------------------------------------- /plugins/Views/SimpleView/SimpleView.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Resources import Resources 5 | from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator 6 | from UM.View.GL.OpenGL import OpenGL 7 | from UM.View.View import View 8 | 9 | 10 | class SimpleView(View): 11 | """Standard view for mesh models.""" 12 | 13 | def __init__(self): 14 | super().__init__() 15 | 16 | self._shader = None 17 | 18 | def beginRendering(self): 19 | scene = self.getController().getScene() 20 | renderer = self.getRenderer() 21 | 22 | if not self._shader: 23 | self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "object.shader")) 24 | 25 | for node in DepthFirstIterator(scene.getRoot()): 26 | if not node.render(renderer): 27 | if node.getMeshData() and node.isVisible(): 28 | renderer.queueNode(node, shader = self._shader) 29 | 30 | def endRendering(self): 31 | pass 32 | -------------------------------------------------------------------------------- /plugins/Views/SimpleView/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from . import SimpleView 5 | 6 | from UM.i18n import i18nCatalog 7 | i18n_catalog = i18nCatalog("uranium") 8 | 9 | def getMetaData(): 10 | return { 11 | "view": { 12 | "name": i18n_catalog.i18nc("@item:inmenu", "Simple"), 13 | "visible": False 14 | } 15 | } 16 | 17 | def register(app): 18 | return { "view": SimpleView.SimpleView() } 19 | 20 | -------------------------------------------------------------------------------- /plugins/Views/SimpleView/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Simple View", 3 | "author": "Ultimaker B.V.", 4 | "version": "1.0.1", 5 | "description": "Provides a simple solid mesh view.", 6 | "api": 8, 7 | "i18n-catalog": "uranium" 8 | } 9 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests 3 | python_files = Test*.py Benchmark*.py 4 | python_classes = Test 5 | python_functions = test benchmark 6 | -------------------------------------------------------------------------------- /resources/shaders/color.shader: -------------------------------------------------------------------------------- 1 | [shaders] 2 | vertex = 3 | uniform highp mat4 u_modelMatrix; 4 | uniform highp mat4 u_viewMatrix; 5 | uniform highp mat4 u_projectionMatrix; 6 | 7 | attribute highp vec4 a_vertex; //Vertex coordinate. 8 | void main() 9 | { 10 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; //Transform the vertex coordinates with the model view projection. 11 | } 12 | 13 | fragment = 14 | uniform lowp vec4 u_color; 15 | 16 | void main() 17 | { 18 | gl_FragColor = u_color; //Always use the uniform colour. The entire mesh will be the same colour. 19 | } 20 | 21 | vertex41core = 22 | #version 410 23 | uniform highp mat4 u_modelMatrix; 24 | uniform highp mat4 u_viewMatrix; 25 | uniform highp mat4 u_projectionMatrix; 26 | 27 | in highp vec4 a_vertex; //Vertex coordinate. 28 | void main() 29 | { 30 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; //Transform the vertex coordinates with the model view projection. 31 | } 32 | 33 | fragment41core = 34 | #version 410 35 | uniform lowp vec4 u_color; 36 | uniform lowp float u_z_bias; //Bias in the depth buffer for rendering this object (to make an object be rendered in front of or behind other objects). 37 | 38 | out vec4 frag_color; 39 | 40 | void main() 41 | { 42 | frag_color = u_color; //Always use the uniform colour. The entire mesh will be the same colour.*/ 43 | gl_FragDepth = gl_FragCoord.z + u_z_bias; 44 | } 45 | 46 | [defaults] 47 | u_color = [0.5, 0.5, 0.5, 1.0] 48 | u_z_bias = 0.0 49 | 50 | [bindings] 51 | u_modelMatrix = model_matrix 52 | u_viewMatrix = view_matrix 53 | u_projectionMatrix = projection_matrix 54 | 55 | [attributes] 56 | a_vertex = vertex 57 | -------------------------------------------------------------------------------- /resources/shaders/default.shader: -------------------------------------------------------------------------------- 1 | [shaders] 2 | vertex = 3 | uniform highp mat4 u_modelMatrix; 4 | uniform highp mat4 u_viewMatrix; 5 | uniform highp mat4 u_projectionMatrix; 6 | 7 | attribute highp vec4 a_vertex; 8 | attribute lowp vec4 a_color; 9 | varying lowp vec4 v_color; 10 | void main() 11 | { 12 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; 13 | v_color = a_color; 14 | } 15 | 16 | fragment = 17 | uniform lowp vec4 u_color; 18 | varying lowp vec4 v_color; 19 | 20 | void main() 21 | { 22 | if(v_color != vec4(0.0, 0.0, 0.0, 1.0)) 23 | { 24 | gl_FragColor = v_color; 25 | } 26 | else 27 | { 28 | gl_FragColor = u_color; 29 | } 30 | } 31 | 32 | vertex41core = 33 | #version 410 34 | uniform highp mat4 u_modelMatrix; 35 | uniform highp mat4 u_viewMatrix; 36 | uniform highp mat4 u_projectionMatrix; 37 | 38 | in highp vec4 a_vertex; 39 | in lowp vec4 a_color; 40 | out lowp vec4 v_color; 41 | void main() 42 | { 43 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; 44 | v_color = a_color; 45 | } 46 | 47 | fragment41core = 48 | #version 410 49 | uniform lowp vec4 u_color; 50 | in lowp vec4 v_color; 51 | out vec4 frag_color; 52 | 53 | void main() 54 | { 55 | if(v_color != vec4(0.0, 0.0, 0.0, 1.0)) 56 | { 57 | frag_color = v_color; 58 | } 59 | else 60 | { 61 | frag_color = u_color; 62 | } 63 | } 64 | 65 | [defaults] 66 | u_color = [0.5, 0.5, 0.5, 1.0] 67 | 68 | [bindings] 69 | u_modelMatrix = model_matrix 70 | u_viewMatrix = view_matrix 71 | u_projectionMatrix = projection_matrix 72 | 73 | [attributes] 74 | a_vertex = vertex 75 | a_color = color 76 | -------------------------------------------------------------------------------- /resources/shaders/select_face.shader: -------------------------------------------------------------------------------- 1 | [shaders] 2 | vertex = 3 | // NOTE: These legacy shaders are compiled, but not used. Select-by-face isn't possible in legacy-render-mode. 4 | uniform highp mat4 u_modelMatrix; 5 | uniform highp mat4 u_viewMatrix; 6 | uniform highp mat4 u_projectionMatrix; 7 | uniform highp int u_modelId; 8 | 9 | attribute highp vec4 a_vertex; 10 | 11 | void main() 12 | { 13 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; 14 | } 15 | 16 | fragment = 17 | // NOTE: These legacy shaders are compiled, but not used. Select-by-face isn't possible in legacy-render-mode. 18 | 19 | void main() 20 | { 21 | gl_FragColor = vec4(0., 0., 0., 1.); 22 | // NOTE: Select face can't be used in compatibility-mode; 23 | // the __VERSION__ macro may give back the max the graphics driver supports, 24 | // rather than the one supported by the selected OpenGL version. 25 | } 26 | 27 | vertex41core = 28 | #version 410 29 | 30 | uniform highp mat4 u_modelMatrix; 31 | uniform highp mat4 u_viewMatrix; 32 | uniform highp mat4 u_projectionMatrix; 33 | uniform highp int u_modelId; 34 | 35 | in highp vec4 a_vertex; 36 | 37 | flat out highp int v_modelId; 38 | 39 | void main() 40 | { 41 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; 42 | v_modelId = u_modelId; 43 | } 44 | 45 | fragment41core = 46 | #version 410 47 | 48 | flat in highp int v_modelId; 49 | 50 | out vec4 frag_color; 51 | 52 | void main() 53 | { 54 | frag_color.r = (gl_PrimitiveID & 0xff) / 255.; 55 | frag_color.g = ((gl_PrimitiveID >> 8) & 0xff) / 255.; 56 | frag_color.b = ((gl_PrimitiveID >> 16) & 0xff) / 255.; 57 | frag_color.a = (255 - v_modelId) / 255.; // Invert to ease visualization in images, and so that 0 means no object 58 | } 59 | 60 | [defaults] 61 | 62 | [bindings] 63 | u_modelMatrix = model_matrix 64 | u_viewMatrix = view_matrix 65 | u_projectionMatrix = projection_matrix 66 | u_modelId = model_id 67 | 68 | [attributes] 69 | a_vertex = vertex 70 | -------------------------------------------------------------------------------- /resources/shaders/selection.shader: -------------------------------------------------------------------------------- 1 | [shaders] 2 | vertex = 3 | uniform highp mat4 u_modelMatrix; 4 | uniform highp mat4 u_viewMatrix; 5 | uniform highp mat4 u_projectionMatrix; 6 | 7 | attribute highp vec4 a_vertex; 8 | 9 | void main() 10 | { 11 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; 12 | } 13 | 14 | fragment = 15 | uniform lowp vec4 u_color; 16 | 17 | void main() 18 | { 19 | gl_FragColor = u_color; 20 | } 21 | 22 | vertex41core = 23 | #version 410 24 | uniform highp mat4 u_modelMatrix; 25 | uniform highp mat4 u_viewMatrix; 26 | uniform highp mat4 u_projectionMatrix; 27 | 28 | in highp vec4 a_vertex; 29 | 30 | void main() 31 | { 32 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; 33 | } 34 | 35 | fragment41core = 36 | #version 410 37 | uniform lowp vec4 u_color; 38 | out vec4 frag_color; 39 | 40 | void main() 41 | { 42 | frag_color = u_color; 43 | } 44 | 45 | [defaults] 46 | 47 | [bindings] 48 | u_modelMatrix = model_matrix 49 | u_viewMatrix = view_matrix 50 | u_projectionMatrix = projection_matrix 51 | u_color = selection_color 52 | 53 | [attributes] 54 | a_vertex = vertex 55 | a_color = color 56 | -------------------------------------------------------------------------------- /resources/shaders/toolhandle.shader: -------------------------------------------------------------------------------- 1 | [shaders] 2 | vertex = 3 | uniform highp mat4 u_modelMatrix; 4 | uniform highp mat4 u_viewMatrix; 5 | uniform highp mat4 u_projectionMatrix; 6 | 7 | attribute highp vec4 a_vertex; 8 | attribute lowp vec4 a_color; 9 | 10 | varying lowp vec4 v_color; 11 | 12 | void main() 13 | { 14 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; 15 | v_color = a_color; 16 | } 17 | 18 | fragment = 19 | uniform lowp vec4 u_disabledColor; 20 | uniform lowp vec4 u_activeColor; 21 | uniform lowp float u_enableMultiplier; 22 | 23 | varying lowp vec4 v_color; 24 | 25 | void main() 26 | { 27 | if(u_activeColor == v_color) 28 | { 29 | gl_FragColor = v_color * u_enableMultiplier; 30 | } 31 | else 32 | { 33 | gl_FragColor = v_color; 34 | } 35 | } 36 | 37 | vertex41core = 38 | #version 410 39 | uniform highp mat4 u_modelMatrix; 40 | uniform highp mat4 u_viewMatrix; 41 | uniform highp mat4 u_projectionMatrix; 42 | 43 | in highp vec4 a_vertex; 44 | in lowp vec4 a_color; 45 | 46 | out lowp vec4 v_color; 47 | 48 | void main() 49 | { 50 | gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; 51 | v_color = a_color; 52 | } 53 | 54 | fragment41core = 55 | #version 410 56 | uniform lowp vec4 u_disabledColor; 57 | uniform lowp vec4 u_activeColor; 58 | uniform lowp float u_enableMultiplier; 59 | 60 | in lowp vec4 v_color; 61 | out vec4 frag_color; 62 | 63 | void main() 64 | { 65 | if(u_activeColor == v_color) 66 | { 67 | frag_color = v_color * u_enableMultiplier; 68 | } 69 | else 70 | { 71 | frag_color = v_color; 72 | } 73 | } 74 | 75 | [defaults] 76 | u_disabledColor = [0.5, 0.5, 0.5, 1.0] 77 | u_activeColor = [0.5, 0.5, 0.5, 1.0] 78 | u_enableMultiplier = 1.2 79 | 80 | 81 | [bindings] 82 | u_modelMatrix = model_matrix 83 | u_viewMatrix = view_matrix 84 | u_projectionMatrix = projection_matrix 85 | 86 | [attributes] 87 | a_vertex = vertex 88 | a_color = color 89 | -------------------------------------------------------------------------------- /scripts/compile-shaders: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script will compile vertex and fragment shader files into qsb shader files (required by Qt) 4 | # For more information see https://doc.qt.io/qt-6/qtshadertools-index.html 5 | # 6 | # Usage: 7 | # provide two arguments with the script; dir and des 8 | # - dir location of the shader files (with *.vert or *.frag extension) 9 | # - dest location where the *.qsb files will be written to 10 | # note: the script will ignore the folder structure from the dir, the files in the dest folder will be flattened 11 | # 12 | # This script requires qsb (part of qtshadertools) to be installed 13 | # 14 | dir=$1 15 | dest=$2 16 | 17 | for file in $( find $dir . \( -iname '*.VERT' -o -iname '*.FRAG' \); ); do 18 | qsb $file --glsl "100 es,120,150" --hlsl 50 --msl 12 --output $dest/$(basename ${file}).qsb 19 | done 20 | -------------------------------------------------------------------------------- /test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | from io import StringIO 2 | 3 | from conan import ConanFile 4 | from conan.tools.env import VirtualRunEnv 5 | from conan.tools.build import can_run 6 | from conan.errors import ConanException 7 | from conan.tools.files import copy 8 | 9 | 10 | class UraniumTestConan(ConanFile): 11 | settings = "os", "compiler", "build_type", "arch" 12 | test_type = "explicit" 13 | 14 | def requirements(self): 15 | self.requires(self.tested_reference_str) 16 | 17 | def generate(self): 18 | venv = VirtualRunEnv(self) 19 | venv.generate() 20 | 21 | cpp_info = self.dependencies[self.tested_reference_str].cpp_info 22 | copy(self, "*.pyd", src=cpp_info.libdirs[0], dst=self.build_folder) 23 | 24 | for dep in self.dependencies.values(): 25 | for bin_dir in dep.cpp_info.bindirs: 26 | copy(self, "*.dll", src=bin_dir, dst=self.build_folder) 27 | 28 | def test(self): 29 | if can_run(self): 30 | test_buf = StringIO() 31 | self.run(f"python test.py", env="conanrun", stdout=test_buf, scope="run") 32 | if "True" not in test_buf.getvalue(): 33 | raise ConanException("Uranium wasn't build correctly!") 34 | -------------------------------------------------------------------------------- /test_package/test.py: -------------------------------------------------------------------------------- 1 | import UM 2 | 3 | from UM import Util 4 | 5 | print(Util.parseBool("True")) 6 | -------------------------------------------------------------------------------- /tests/Bindings/TestOutputDeviceManagerProxy.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from unittest.mock import MagicMock, patch 4 | 5 | from UM.Qt.Bindings.OutputDeviceManagerProxy import OutputDeviceManagerProxy 6 | 7 | 8 | class TestOutputDeviceManagerProxy(TestCase): 9 | proxy = None 10 | mock_application = None 11 | mocked_device_manager = None 12 | 13 | def setUp(self): 14 | # These objects only need to be set / created once. 15 | if TestOutputDeviceManagerProxy.proxy is None: 16 | TestOutputDeviceManagerProxy.mock_application = MagicMock() 17 | TestOutputDeviceManagerProxy.mocked_device_manager = MagicMock() 18 | self.mock_application.getOutputDeviceManager = MagicMock(return_value = self.mocked_device_manager) 19 | with patch("UM.Application.Application.getInstance", MagicMock(return_value=self.mock_application)): 20 | TestOutputDeviceManagerProxy.proxy = OutputDeviceManagerProxy() 21 | 22 | def tearDown(self): 23 | pass 24 | 25 | def test_startAndRefreshDiscovery(self): 26 | self.proxy.startDiscovery() 27 | assert self.mocked_device_manager.startDiscovery.call_count == 1 28 | 29 | self.proxy.refreshConnections() 30 | assert self.mocked_device_manager.refreshConnections.call_count == 1 -------------------------------------------------------------------------------- /tests/Bindings/TestSelectionProxy.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from unittest.mock import MagicMock 3 | 4 | from UM.Math.AxisAlignedBox import AxisAlignedBox 5 | from UM.Math.Vector import Vector 6 | from UM.Qt.Bindings.SelectionProxy import SelectionProxy 7 | from UM.Scene.SceneNode import SceneNode 8 | from UM.Scene.Selection import Selection 9 | from UM.Operations.TranslateOperation import TranslateOperation 10 | 11 | 12 | class TestSelectionProxy(TestCase): 13 | 14 | def setUp(self): 15 | Selection.clear() 16 | self.proxy = SelectionProxy() 17 | 18 | def tearDown(self): 19 | Selection.clear() 20 | 21 | def test_hasSelection(self): 22 | # Nothing is selected by default 23 | assert not self.proxy.hasSelection 24 | 25 | node_1 = SceneNode() 26 | Selection.add(node_1) 27 | 28 | assert self.proxy.hasSelection 29 | 30 | Selection.remove(node_1) 31 | assert not self.proxy.hasSelection 32 | 33 | def test_selectionCount(self): 34 | assert self.proxy.selectionCount == 0 35 | 36 | node_1 = SceneNode() 37 | Selection.add(node_1) 38 | assert self.proxy.selectionCount == 1 39 | 40 | node_2 = SceneNode() 41 | Selection.add(node_2) 42 | assert self.proxy.selectionCount == 2 43 | 44 | def test_selectionNames(self): 45 | node_1 = SceneNode(name="TestNode1") 46 | node_2 = SceneNode(name="TestNode2") 47 | Selection.add(node_2) 48 | Selection.add(node_1) 49 | assert self.proxy.selectionNames == ["TestNode2", "TestNode1"] 50 | 51 | -------------------------------------------------------------------------------- /tests/Bindings/TestToolModel.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import patch, mock_open, MagicMock 3 | 4 | from UM.Qt.Bindings.ToolModel import ToolModel 5 | 6 | controller = MagicMock() 7 | 8 | 9 | @pytest.fixture 10 | def tool_model(): 11 | mocked_application = MagicMock() 12 | mocked_application.getController = MagicMock(return_value = controller) 13 | with patch("UM.PluginRegistry.PluginRegistry.getInstance"): 14 | with patch("UM.Application.Application.getInstance", MagicMock(return_value = mocked_application)): 15 | model = ToolModel() 16 | 17 | return model 18 | 19 | 20 | def test_onToolsChanged_visible_tool(tool_model): 21 | tool = MagicMock(getMetaData = MagicMock(return_value = {"visible": True})) 22 | controller.getAllTools = MagicMock(return_value = {"beep_tool": tool}) 23 | with patch("UM.PluginRegistry.PluginRegistry.getInstance"): 24 | tool_model._onToolsChanged() 25 | assert len(tool_model.items) == 1 26 | 27 | 28 | def test_onToolsChanged_invisible_tool(tool_model): 29 | tool = MagicMock(getMetaData=MagicMock(return_value={"visible": False})) 30 | controller.getAllTools = MagicMock(return_value={"beep_tool": tool}) 31 | with patch("UM.PluginRegistry.PluginRegistry.getInstance"): 32 | tool_model._onToolsChanged() 33 | assert len(tool_model.items) == 0 -------------------------------------------------------------------------------- /tests/Jobs/TestJob.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import patch, MagicMock 3 | 4 | from UM.Job import Job 5 | 6 | 7 | 8 | 9 | def test_getSetError(): 10 | job = Job() 11 | exception = Exception("Some Error :(") 12 | job.setError(exception) 13 | 14 | assert job.getError() == exception 15 | assert job.hasError() 16 | 17 | 18 | def test_getSetResult(): 19 | job = Job() 20 | job.setResult("blarg") 21 | assert job.getResult() == "blarg" 22 | 23 | 24 | def test_run(): 25 | job = Job() 26 | with pytest.raises(NotImplementedError): 27 | job.run() 28 | 29 | 30 | def test_start(): 31 | job = Job() 32 | job_queue = MagicMock() 33 | with patch("UM.JobQueue.JobQueue.getInstance", MagicMock(return_value = job_queue)): 34 | job.start() 35 | job_queue.add.assert_called_once_with(job) 36 | 37 | 38 | def test_cancel(): 39 | job = Job() 40 | job_queue = MagicMock() 41 | with patch("UM.JobQueue.JobQueue.getInstance", MagicMock(return_value=job_queue)): 42 | job.cancel() 43 | job_queue.remove.assert_called_once_with(job) 44 | 45 | 46 | def test_isRunning(): 47 | job = Job() 48 | assert not job.isRunning() 49 | -------------------------------------------------------------------------------- /tests/Jobs/TestJobQueue.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import pytest 5 | 6 | from UM.Application import Application 7 | from UM.Job import Job 8 | from UM.JobQueue import JobQueue 9 | 10 | import time 11 | import threading 12 | 13 | 14 | class ShortTestJob(Job): 15 | def run(self): 16 | self.setResult("TestJob") 17 | 18 | 19 | class LongTestJob(Job): 20 | def run(self): 21 | time.sleep(1.5) 22 | self.setResult("LongTestJob") 23 | 24 | @pytest.fixture 25 | def job_queue(): 26 | JobQueue._JobQueue__instance = None 27 | return JobQueue() 28 | 29 | 30 | class TestJobQueue: 31 | def test_create(self): 32 | JobQueue._JobQueue__instance = None 33 | jq = JobQueue() 34 | 35 | assert len(jq._threads) > 0 36 | assert jq == JobQueue.getInstance() 37 | 38 | JobQueue._JobQueue__instance = None 39 | 40 | jq = JobQueue(4) 41 | assert len(jq._threads) == 4 42 | 43 | def test_addShort(self, job_queue): 44 | job = ShortTestJob() 45 | job.start() 46 | 47 | assert job in job_queue._jobs 48 | 49 | time.sleep(0.1) 50 | 51 | assert job.isFinished() 52 | assert job.getResult() == "TestJob" 53 | 54 | test_addMultiple_data = [2, 5, 10] 55 | @pytest.mark.parametrize("count", test_addMultiple_data) 56 | def test_addMultiple(self, job_queue, count): 57 | jobs = [] 58 | for i in range(count): 59 | job = ShortTestJob() 60 | job.start() 61 | 62 | jobs.append(job) 63 | 64 | assert job in job_queue._jobs 65 | 66 | time.sleep(0.01 * count) 67 | 68 | for job in jobs: 69 | assert job.isFinished() 70 | assert job.getResult() == "TestJob" 71 | 72 | def test_remove(self): 73 | pass 74 | -------------------------------------------------------------------------------- /tests/Math/TestPlane.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Math.Plane import Plane 5 | from UM.Math.Vector import Vector 6 | from UM.Math.Ray import Ray 7 | 8 | import unittest 9 | 10 | class TestPlane(unittest.TestCase): 11 | def setUp(self): 12 | # Called before the first testfunction is executed 13 | pass 14 | 15 | def tearDown(self): 16 | # Called after the last testfunction was executed 17 | pass 18 | 19 | def test_create(self): 20 | p = Plane() 21 | self.assertEqual(Vector(), p.normal) 22 | self.assertEqual(0.0, p.distance) 23 | 24 | p = Plane(Vector.Unit_Y, 1.0) 25 | self.assertEqual(Vector.Unit_Y, p.normal) 26 | self.assertEqual(1.0, p.distance) 27 | 28 | def test_intersects(self): 29 | p = Plane(Vector.Unit_Y, 0.0) 30 | 31 | r = Ray(Vector(0, 10, 0), -Vector.Unit_Y) 32 | result = p.intersectsRay(r) 33 | self.assertNotEqual(False, result) 34 | self.assertEqual(10.0, result) 35 | 36 | r = Ray(Vector(0, -10, 0), Vector.Unit_Y) 37 | result = p.intersectsRay(r) 38 | self.assertNotEqual(False, result) 39 | self.assertEqual(10.0, result) 40 | 41 | r = Ray(Vector(0, 10, 0), Vector.Unit_Y) 42 | self.assertEqual(False, p.intersectsRay(r)) 43 | 44 | r = Ray(Vector(0, 0, 0), Vector.Unit_X) 45 | self.assertEqual(False, p.intersectsRay(r)) 46 | 47 | 48 | if __name__ == "__main__": 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tests/MimeTypes/file.long.test: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/MimeTypes/file.test: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/Operations/TestAddSceneNodeOperation.py: -------------------------------------------------------------------------------- 1 | from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation 2 | from UM.Scene.SceneNode import SceneNode 3 | from UM.Scene.Selection import Selection 4 | 5 | 6 | def test_SimpleRedoUndo(): 7 | node = SceneNode() 8 | parent_node = SceneNode() 9 | operation = AddSceneNodeOperation(node, parent_node) 10 | operation.redo() 11 | 12 | assert node.getParent() == parent_node 13 | 14 | operation.undo() 15 | 16 | assert node.getParent() is None 17 | 18 | 19 | def test_UndoRedoWithSelection(): 20 | node = SceneNode() 21 | parent_node = SceneNode() 22 | Selection.add(node) 23 | 24 | operation = AddSceneNodeOperation(node, parent_node) 25 | operation.undo() 26 | 27 | assert not Selection.isSelected(node) 28 | 29 | operation.redo() 30 | assert Selection.isSelected(node) 31 | -------------------------------------------------------------------------------- /tests/Operations/TestGroupedOperation.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from UM.Math.Vector import Vector 4 | from UM.Operations.GroupedOperation import GroupedOperation 5 | import pytest 6 | 7 | from UM.Operations.Operation import Operation 8 | from UM.Operations.TranslateOperation import TranslateOperation 9 | from UM.Scene.SceneNode import SceneNode 10 | 11 | 12 | def test_addOperationFinalised(): 13 | operation_1 = GroupedOperation() 14 | operation_2 = GroupedOperation() 15 | 16 | operation_1.redo() # The operation is now finalized, so it shouldn't be possible to add operations to it now. 17 | with pytest.raises(Exception): 18 | operation_1.addOperation(operation_2) 19 | 20 | 21 | def test_addAndMergeOperations(): 22 | group_operation_1 = GroupedOperation() 23 | group_operation_2 = GroupedOperation() 24 | group_operation_3 = GroupedOperation() 25 | 26 | scene_node = SceneNode() 27 | 28 | operation_1 = TranslateOperation(scene_node, Vector(10, 10)) 29 | operation_2 = TranslateOperation(scene_node, Vector(10, 20)) 30 | operation_3 = Operation() 31 | 32 | operation_1.mergeWith = MagicMock() 33 | 34 | group_operation_1.addOperation(operation_1) 35 | assert group_operation_1.getNumChildrenOperations() == 1 36 | 37 | # Length of the grouped operations must be the same. 38 | assert not group_operation_1.mergeWith(group_operation_2) 39 | 40 | group_operation_3.addOperation(operation_3) 41 | 42 | # The base operation always says it can't be merged (so one child says it can't be merged). This should result 43 | # in the parent (grouped) operation also failing. 44 | assert not group_operation_3.mergeWith(group_operation_1) 45 | 46 | group_operation_2.addOperation(operation_2) 47 | merged_operation = group_operation_1.mergeWith(group_operation_2) 48 | 49 | # The merge of the nested operation should have been called once. 50 | assert operation_1.mergeWith.call_count == 1 51 | 52 | # Number of operations should still be the same. 53 | assert merged_operation.getNumChildrenOperations() == 1 -------------------------------------------------------------------------------- /tests/Operations/TestOperationStack.py: -------------------------------------------------------------------------------- 1 | import time 2 | from unittest.mock import MagicMock 3 | 4 | from UM.Operations.GroupedOperation import GroupedOperation 5 | from UM.Operations.OperationStack import OperationStack 6 | 7 | 8 | def test_push(): 9 | operation_stack = OperationStack(MagicMock()) 10 | operation_stack.changed.emit = MagicMock() 11 | test_operation = GroupedOperation() 12 | 13 | test_operation_2 = GroupedOperation() 14 | 15 | operation_stack.push(test_operation) 16 | operation_stack.push(test_operation_2) 17 | 18 | # Since we added two operations that can be merged, we should end up with one operation! 19 | assert len(operation_stack.getOperations()) == 1 20 | 21 | test_operation_3 = GroupedOperation() 22 | 23 | # Fake call to notify the operation stack that the tool has stopped doing something 24 | operation_stack._onToolOperationStopped(None) 25 | # Pretend like another operation was added but with a lot of time in between. 26 | test_operation_3._timestamp = time.time() + 2000000 27 | operation_stack.push(test_operation_3) 28 | assert len(operation_stack.getOperations()) == 2 29 | 30 | operation_stack.undo() 31 | # The count should be at 4, since we added 3 operations and then undid the last one. 32 | assert operation_stack.changed.emit.call_count == 4 33 | operation_stack.undo() 34 | assert operation_stack.changed.emit.call_count == 5 35 | 36 | # There is nothing to undo! 37 | assert not operation_stack.canUndo() 38 | operation_stack.undo() 39 | assert operation_stack.changed.emit.call_count == 5 40 | 41 | operation_stack.redo() 42 | assert operation_stack.changed.emit.call_count == 6 43 | operation_stack.redo() 44 | assert operation_stack.changed.emit.call_count == 7 45 | assert not operation_stack.canRedo() -------------------------------------------------------------------------------- /tests/Operations/TestScaleOperation.py: -------------------------------------------------------------------------------- 1 | from UM.Math.Vector import Vector 2 | from UM.Operations.ScaleOperation import ScaleOperation 3 | from UM.Scene.SceneNode import SceneNode 4 | 5 | 6 | def test_setSimpleScale(): 7 | node = SceneNode() 8 | op = ScaleOperation(node, Vector(1, 2, 3), set_scale = True) 9 | 10 | op.redo() 11 | assert node.getScale() == Vector(1, 2, 3) 12 | 13 | op.undo() 14 | assert node.getScale() == Vector(1, 1, 1) 15 | 16 | 17 | def test_addScale(): 18 | node = SceneNode() 19 | op = ScaleOperation(node, Vector(1, 2, 3), set_scale = True) 20 | op.redo() 21 | 22 | op2 = ScaleOperation(node, Vector(1, 2, 3), add_scale = True) 23 | op2.redo() 24 | assert node.getScale() == Vector(2, 4, 6) -------------------------------------------------------------------------------- /tests/PluginRegistry/EmptyPlugin/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | 5 | def getMetaData(): 6 | return {} 7 | 8 | 9 | def register(app): 10 | # Explicitly do not return an object to be registered. 11 | return { } -------------------------------------------------------------------------------- /tests/PluginRegistry/EmptyPlugin/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EmptyPlugin", 3 | "api": 5, 4 | "version": "1.0.0" 5 | } -------------------------------------------------------------------------------- /tests/PluginRegistry/OldTestPlugin/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | def getMetaData(): 5 | return { "name": "OldTestPlugin" } 6 | 7 | def register(app): 8 | app.registerTestPlugin("OldTestPlugin") -------------------------------------------------------------------------------- /tests/PluginRegistry/PluginNoVersionNumber/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.PluginObject import PluginObject 5 | 6 | def getMetaData(): 7 | return {} 8 | 9 | def register(app): 10 | return { "test": PluginObject() } 11 | -------------------------------------------------------------------------------- /tests/PluginRegistry/PluginNoVersionNumber/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PluginNoVersionNumber", 3 | "api": 5 4 | } -------------------------------------------------------------------------------- /tests/PluginRegistry/TestNested/TestPlugin2/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.PluginObject import PluginObject 5 | 6 | def getMetaData(): 7 | return {} 8 | 9 | def register(app): 10 | return { "test": PluginObject() } 11 | -------------------------------------------------------------------------------- /tests/PluginRegistry/TestNested/TestPlugin2/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TestPlugin2", 3 | "api": 5, 4 | "version": "1.0.0" 5 | } -------------------------------------------------------------------------------- /tests/PluginRegistry/TestPlugin/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.PluginObject import PluginObject 5 | 6 | def getMetaData(): 7 | return { 8 | } 9 | 10 | def register(app): 11 | return { "test": PluginObject() } 12 | -------------------------------------------------------------------------------- /tests/PluginRegistry/TestPlugin/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TestPlugin", 3 | "api": 5, 4 | "version": "1.0.0", 5 | "i18n-catalog": "bla", 6 | "description": "test" 7 | } -------------------------------------------------------------------------------- /tests/PluginRegistry/UraniumExampleExtensionPlugin.umplugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/tests/PluginRegistry/UraniumExampleExtensionPlugin.umplugin -------------------------------------------------------------------------------- /tests/SaveFile/TestSaveFile.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import unittest 5 | import os.path 6 | import tempfile 7 | 8 | from multiprocessing import Pool 9 | 10 | from UM.SaveFile import SaveFile 11 | 12 | write_count = 0 13 | 14 | def write_dual(path): 15 | with SaveFile(path, "w") as f: 16 | f.write("test file") 17 | 18 | class TestSaveFile(unittest.TestCase): 19 | def setUp(self): 20 | self._temp_dir = tempfile.TemporaryDirectory() 21 | 22 | def tearDown(self): 23 | self._temp_dir.cleanup() 24 | self._temp_dir = None 25 | 26 | def test_singleWrite(self): 27 | path = os.path.join(self._temp_dir.name, "single_write") 28 | with SaveFile(path, "w") as f: 29 | f.write("test file") 30 | 31 | with open(path, encoding = "utf-8") as f: 32 | self.assertEqual(f.readline(), "test file") 33 | 34 | def test_multiWrite(self): 35 | path = os.path.join(self._temp_dir.name, "dual_write") 36 | 37 | # Start two processes that try to write to the same file 38 | with Pool(processes = 2) as p: 39 | p.apply_async(write_dual, [path]) 40 | p.apply(write_dual, [path]) 41 | 42 | # Once done, there should be just one file 43 | self.assertEqual(len(os.listdir(self._temp_dir.name)), 1) 44 | 45 | # And file contents should be correct. 46 | with open(path, encoding = "utf-8") as f: 47 | data = f.read() 48 | self.assertEqual(len(data), 9) 49 | self.assertEqual(data, "test file") 50 | 51 | if __name__ == "__main__": 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /tests/Scene/TestScene.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from UM.Scene.Camera import Camera 4 | from UM.Scene.Scene import Scene 5 | from UM.Scene.SceneNode import SceneNode 6 | 7 | 8 | def test_ignoreSceneChanges(): 9 | scene = Scene() 10 | scene.sceneChanged.emit = MagicMock() 11 | scene.setIgnoreSceneChanges(ignore_scene_changes = True) 12 | root = scene.getRoot() 13 | 14 | root.addChild(SceneNode()) 15 | assert scene.sceneChanged.emit.call_count == 0 16 | 17 | scene.setIgnoreSceneChanges(ignore_scene_changes=False) 18 | root.addChild(SceneNode()) 19 | assert scene.sceneChanged.emit.call_count == 2 20 | 21 | 22 | def test_switchRoot(): 23 | scene = Scene() 24 | new_root = SceneNode() 25 | scene.rootChanged = MagicMock() 26 | 27 | scene.setRoot(new_root) 28 | assert scene.getRoot() == new_root 29 | assert scene.rootChanged.emit.call_count == 1 30 | 31 | scene.setRoot(new_root) 32 | assert scene.rootChanged.emit.call_count == 1 33 | 34 | 35 | def test_findObject(): 36 | scene = Scene() 37 | node = SceneNode() 38 | scene.getRoot().addChild(node) 39 | 40 | assert scene.findObject(id(node)) == node 41 | assert scene.findObject(12) is None 42 | 43 | 44 | def test_cameras(): 45 | scene = Scene() 46 | camera_1 = Camera("camera_one") 47 | camera_2 = Camera("camera_two") 48 | scene.getRoot().addChild(camera_1) 49 | 50 | assert scene.findCamera("camera_one") == camera_1 51 | assert scene.findCamera("camera_nope") is None 52 | assert scene.findCamera("camera_two") is None 53 | scene.getRoot().addChild(camera_2) 54 | assert scene.findCamera("camera_two") == camera_2 55 | 56 | all_cameras = scene.getAllCameras() 57 | assert camera_1 in all_cameras 58 | assert camera_2 in all_cameras 59 | 60 | scene.setActiveCamera("camera_one") 61 | assert scene.getActiveCamera() == camera_1 62 | scene.setActiveCamera("camera_one") # Ensure that setting it again doesn't break things. 63 | 64 | -------------------------------------------------------------------------------- /tests/Settings/ContainerTestPlugin/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from .ContainerTestPlugin import ContainerTestPlugin 5 | from UM.Settings.ContainerRegistry import ContainerRegistry 6 | 7 | def getMetaData(): 8 | return { 9 | "settings_container": { 10 | "mimetype": "application/x-uranium-test" 11 | } 12 | } 13 | 14 | def register(app): 15 | return { "settings_container": ContainerTestPlugin() } 16 | -------------------------------------------------------------------------------- /tests/Settings/ContainerTestPlugin/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TestContainerPlugin", 3 | "api": 8, 4 | "version": "1.0.0" 5 | } 6 | -------------------------------------------------------------------------------- /tests/Settings/TestContainerQuery.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import pytest 5 | 6 | from UM.Settings.ContainerQuery import ContainerQuery 7 | 8 | 9 | def test_matchMultipleTokens(): 10 | cq = ContainerQuery(None) 11 | 12 | # Test #1: if property_name doesn't exist, it will return False 13 | result = cq._matchRegMultipleTokens({"name": "test"}, "type", "123") 14 | assert result is False 15 | 16 | test_cases = [ 17 | {"input": {"metadata": {"name": "test"}, # Single token, match 18 | "property_name": "name", 19 | "value": "[test]", 20 | }, 21 | "match": True, 22 | }, 23 | {"input": {"metadata": {"name": "test1"}, # Single token, no match 24 | "property_name": "name", 25 | "value": "[test]", 26 | }, 27 | "match": False, 28 | }, 29 | {"input": {"metadata": {"name": "test"}, # Multiple token, match 30 | "property_name": "name", 31 | "value": "[abc|123|test|456]", 32 | }, 33 | "match": True, 34 | }, 35 | {"input": {"metadata": {"name": "test"}, # Multiple token, no match 36 | "property_name": "name", 37 | "value": "[abc|_test_|test2|tst]", 38 | }, 39 | "match": False, 40 | }, 41 | ] 42 | 43 | for test_case in test_cases: 44 | result = cq._matchRegMultipleTokens(**test_case["input"]) 45 | if test_case["match"]: 46 | assert result is not None 47 | else: 48 | assert result is None 49 | -------------------------------------------------------------------------------- /tests/Settings/TestSettingRelation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import pytest 5 | 6 | import UM.Settings.SettingRelation 7 | 8 | def test_create(): 9 | with pytest.raises(ValueError): 10 | relation = UM.Settings.SettingRelation.SettingRelation(None, 2, UM.Settings.SettingRelation.RelationType.RequiresTarget, "max") 11 | 12 | with pytest.raises(ValueError): 13 | relation = UM.Settings.SettingRelation.SettingRelation(1, None, UM.Settings.SettingRelation.RelationType.RequiresTarget, "max") 14 | 15 | relation = UM.Settings.SettingRelation.SettingRelation(1, 2, UM.Settings.SettingRelation.RelationType.RequiresTarget, "max") 16 | assert relation.owner == 1 17 | assert relation.target == 2 18 | assert relation.type == UM.Settings.SettingRelation.RelationType.RequiresTarget 19 | assert relation.role == "max" 20 | 21 | relation = UM.Settings.SettingRelation.SettingRelation(1, 2, UM.Settings.SettingRelation.RelationType.RequiredByTarget, "min") 22 | assert relation.owner == 1 23 | assert relation.target == 2 24 | assert relation.type == UM.Settings.SettingRelation.RelationType.RequiredByTarget 25 | assert relation.role == "min" -------------------------------------------------------------------------------- /tests/Settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/tests/Settings/__init__.py -------------------------------------------------------------------------------- /tests/Settings/additional_settings/append_extra_settings.def.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_setting": 3 | { 4 | "children": 5 | { 6 | "glombump": 7 | { 8 | "label": "Snosmozea", 9 | "description": "Sudoriferous.", 10 | "unit": "mm/s", 11 | "type": "float", 12 | "minimum_value": "0.1", 13 | "maximum_value_warning": "150", 14 | "maximum_value": "500", 15 | "default_value": 60, 16 | "settable_per_mesh": true 17 | } 18 | } 19 | }, 20 | "category_too": 21 | { 22 | "label": "Warble!", 23 | "type": "category", 24 | "description": "Blomblimg", 25 | "icon": "Printer", 26 | "children": 27 | { 28 | "zharbler": 29 | { 30 | "label": "Frumg", 31 | "description": "Pleonastic Pellerator.", 32 | "unit": "mm/s", 33 | "type": "float", 34 | "minimum_value": "0.1", 35 | "maximum_value_warning": "150", 36 | "maximum_value": "500", 37 | "default_value": 60, 38 | "value": "glombump * 1.2", 39 | "settable_per_mesh": false 40 | } 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /tests/Settings/config_parser_files/multi_line.cfg: -------------------------------------------------------------------------------- 1 | [general] 2 | 3 | [values] 4 | beep = omg 5 | zomg 6 | bbq 7 | zomg = 200 8 | foo = yay 9 | so 10 | much 11 | text to show! 12 | short_foo = some text 13 | to show 14 | 15 | [metadata] 16 | oh_noes = 42 -------------------------------------------------------------------------------- /tests/Settings/config_parser_files/spacing.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [whatever] 5 | a=1 6 | b =2 7 | c = 3 8 | d = 4 9 | e = 5 -------------------------------------------------------------------------------- /tests/Settings/config_parser_files/weird_values.cfg: -------------------------------------------------------------------------------- 1 | [YAY_omg] 2 | the_value = [10] 3 | more_weirdness = [] 4 | weird_value = [20,30] 5 | even_more_weirdness = [yay!] 6 | woah_weird = =(200 if the_value != "whatever" or more_weirdness == "derp" else 0) 7 | lesser_equal = =60 if 1 <= 2 else 30 8 | greater_equal = =20 if 1 >= 2 else "yay" 9 | key_value_in_string = G1 10 | ; speed_z = {speed_z} 11 | speed_something = 20 12 | some_other_value = 25 13 | speed_y = {speed_z} 14 | [12] 15 | 16 | -------------------------------------------------------------------------------- /tests/Settings/definitions/basic_definition.def.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "Test", 4 | 5 | "metadata": { }, 6 | "settings": { } 7 | } 8 | -------------------------------------------------------------------------------- /tests/Settings/definitions/children.def.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "Test", 4 | 5 | "metadata": {}, 6 | "settings": { 7 | "test_setting": { 8 | "label": "Test", 9 | "description": "A Test Setting", 10 | "default_value": 10, 11 | "type": "int", 12 | 13 | "children": { 14 | "test_child_0": { 15 | "label": "Test Child 0", 16 | "description": "A Test Setting", 17 | "default_value": 10, 18 | "type": "int" 19 | }, 20 | "test_child_1": { 21 | "label": "Test Child 1", 22 | "description": "A Test Setting", 23 | "default_value": 10, 24 | "type": "int" 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Settings/definitions/functions.def.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "Test", 4 | 5 | "metadata": { }, 6 | "settings": { 7 | "test_setting_0": { 8 | "label": "Test 0", 9 | "description": "A Test Setting", 10 | "default_value": 10, 11 | "type": "int" 12 | }, 13 | "test_setting_1": { 14 | "label": "Test 1", 15 | "description": "A Test Setting", 16 | "default_value": 10, 17 | "type": "int", 18 | "value": "test_setting_0 * 10" 19 | } 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /tests/Settings/definitions/inherits.def.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "Inherits", 4 | 5 | "inherits": "single_setting", 6 | 7 | "metadata": { 8 | "category": "Other", 9 | "manufacturer": "Ultimaker B.V." 10 | }, 11 | "settings": { 12 | "test_setting_1": { 13 | "label": "Test 1", 14 | "description": "A Test Setting", 15 | "default_value": 10, 16 | "type": "int" 17 | } 18 | }, 19 | "overrides": 20 | { 21 | "test_setting": { 22 | "default_value": 11 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Settings/definitions/metadata_definition.def.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "Test", 4 | 5 | "metadata": { 6 | "author": "Ultimaker", 7 | "category": "Test" 8 | }, 9 | "settings": { } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Settings/definitions/multiple_settings.def.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "Test", 4 | 5 | "metadata": {}, 6 | "settings": { 7 | "test_setting_0": { 8 | "label": "Test 0", 9 | "description": "A Test Setting", 10 | "default_value": 10, 11 | "type": "int" 12 | }, 13 | "test_setting_1": { 14 | "label": "Test 1", 15 | "description": "A Test Setting", 16 | "default_value": 10, 17 | "type": "int" 18 | }, 19 | "test_setting_2": { 20 | "label": "Test 2", 21 | "description": "A Test Setting", 22 | "default_value": 10, 23 | "type": "int" 24 | }, 25 | "test_setting_3": { 26 | "label": "Test 3", 27 | "description": "A Test Setting", 28 | "default_value": 10, 29 | "type": "int" 30 | }, 31 | "test_setting_4": { 32 | "label": "Test 4", 33 | "description": "A Test Setting", 34 | "default_value": 10, 35 | "type": "int" 36 | }, 37 | "test_setting_5": 38 | { 39 | "label": "Test 5 (category)", 40 | "description": "A Test Category", 41 | "type": "category" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Settings/definitions/single_setting.def.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "Test", 4 | 5 | "metadata": { 6 | "author": "Ultimaker", 7 | "category": "Test" 8 | }, 9 | "settings": { 10 | "test_setting": { 11 | "label": "Test", 12 | "description": "A Test Setting", 13 | "default_value": 10, 14 | "type": "int" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Settings/instances/basic_instance.inst.cfg: -------------------------------------------------------------------------------- 1 | [general] 2 | version = 4 3 | name = Basic 4 | definition = basic_definition 5 | 6 | [metadata] 7 | type = basic_profile 8 | 9 | [values] 10 | 11 | -------------------------------------------------------------------------------- /tests/Settings/instances/metadata_instance.inst.cfg: -------------------------------------------------------------------------------- 1 | [general] 2 | version = 4 3 | name = Metadata 4 | definition = basic_definition 5 | 6 | [metadata] 7 | author = Ultimaker 8 | bool = False 9 | integer = 6 10 | type = not_profile 11 | 12 | [values] 13 | 14 | -------------------------------------------------------------------------------- /tests/Settings/instances/setting_values.inst.cfg: -------------------------------------------------------------------------------- 1 | [general] 2 | version = 4 3 | name = Setting Values 4 | definition = multiple_settings 5 | 6 | [metadata] 7 | type = profile 8 | 9 | [values] 10 | test_setting_0 = 20 11 | test_setting_1 = 20 12 | test_setting_2 = 20 13 | test_setting_3 = 20 14 | test_setting_4 = 20 15 | 16 | -------------------------------------------------------------------------------- /tests/Shaders/empty.shader: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/tests/Shaders/empty.shader -------------------------------------------------------------------------------- /tests/Shaders/invalid.shader: -------------------------------------------------------------------------------- 1 | [shaders] 2 | vertex = 3 | uniform highp mat4 u_modelViewProjectionMatrix; 4 | 5 | attribute highp vec4 a_vertex; 6 | 7 | void main() 8 | { 9 | gl_Position = u_modelViewProjectionMatrix * a_vertex; 10 | } -------------------------------------------------------------------------------- /tests/Shaders/invalid2.shader: -------------------------------------------------------------------------------- 1 | [shaders] 2 | fragment = 3 | uniform lowp vec4 u_color; 4 | 5 | void main() 6 | { 7 | gl_FragColor = u_color; 8 | } -------------------------------------------------------------------------------- /tests/Shaders/test.shader: -------------------------------------------------------------------------------- 1 | [shaders] 2 | vertex = vertex_code 3 | fragment = fragment_code 4 | geometry = geometry_code 5 | 6 | [defaults] 7 | u_disabledMultiplier = 0.75 8 | [bindings] 9 | u_modelViewProjectionMatrix = model_view_projection_matrix 10 | u_color = selection_color 11 | 12 | [attributes] 13 | a_vertex = vertex 14 | a_color = color 15 | -------------------------------------------------------------------------------- /tests/TestColor.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from UM.Math.Color import Color 4 | 5 | half_color = 127/255 6 | 7 | test_validate_data = [ 8 | {"data_to_set": [255, 255, 255, 0], "expected": [1.0, 1.0, 1.0, 0.0]}, 9 | {"data_to_set": [0, 0, 0, 255], "expected": [0, 0, 0, 1.0]}, 10 | {"data_to_set": [127, 127, 127, 127], "expected": [half_color, half_color, half_color, half_color]}, 11 | {"data_to_set": [127, 1.0, 127, 127], "expected": [half_color, 1.0, half_color, half_color]} 12 | ] 13 | 14 | @pytest.mark.parametrize("data", test_validate_data) 15 | def test_getAndSet(data): 16 | color = Color(*data["data_to_set"]) 17 | 18 | assert color == Color(*data["expected"]) 19 | 20 | assert color.r == data["expected"][0] 21 | assert color.g == data["expected"][1] 22 | assert color.b == data["expected"][2] 23 | assert color.a == data["expected"][3] 24 | 25 | # And flip the data around to set the values one by one 26 | reversed_data = data["data_to_set"][::-1] 27 | color.setR(reversed_data[0]) 28 | color.setG(reversed_data[1]) 29 | color.setB(reversed_data[2]) 30 | color.setA(reversed_data[3]) 31 | 32 | assert color.r == data["expected"][3] 33 | assert color.g == data["expected"][2] 34 | assert color.b == data["expected"][1] 35 | assert color.a == data["expected"][0] 36 | 37 | 38 | hex_data =[ 39 | {"data_to_set": "#FFFFFF", "expected": [1.0, 1.0, 1.0, 1.0]}, 40 | {"data_to_set": "#00000000", "expected": [0.0, 0.0, 0.0, 0.0]}, 41 | {"data_to_set": "#00cc99", "expected": [0 / 255, 204 / 255, 153 / 255, 1.0]}, 42 | 43 | ] 44 | 45 | @pytest.mark.parametrize("data", hex_data) 46 | def test_fromHexString(data): 47 | color = Color.fromHexString(data["data_to_set"]) 48 | expected_color = Color(*data["expected"]) 49 | assert color == expected_color 50 | -------------------------------------------------------------------------------- /tests/TestDepthFirstDecorator.py: -------------------------------------------------------------------------------- 1 | from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator 2 | from UM.Scene.SceneNode import SceneNode 3 | 4 | 5 | def test_DepthFirstIterator(): 6 | 7 | root_node = SceneNode() 8 | 9 | node_1 = SceneNode() 10 | root_node.addChild(node_1) 11 | 12 | node_1_child_1 = SceneNode() 13 | node_1_child_2 = SceneNode() 14 | 15 | node_1.addChild(node_1_child_1) 16 | node_1.addChild(node_1_child_2) 17 | 18 | node_2 = SceneNode() 19 | root_node.addChild(node_2) 20 | 21 | node_2_child_1 = SceneNode() 22 | node_2.addChild(node_2_child_1) 23 | 24 | assert list(DepthFirstIterator(root_node)) == [root_node, node_1, node_2, node_1_child_1, node_1_child_2, node_2_child_1] -------------------------------------------------------------------------------- /tests/TestDictionary.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import UM.Dictionary 3 | 4 | 5 | def test_findKey(): 6 | test_dict = {"omg": "zomg", "beep": "meep"} 7 | assert UM.Dictionary.findKey(test_dict, "zomg") == "omg" 8 | assert UM.Dictionary.findKey(test_dict, "meep") == "beep" 9 | 10 | with pytest.raises(ValueError): 11 | UM.Dictionary.findKey(test_dict, "Nope") 12 | -------------------------------------------------------------------------------- /tests/TestDuration.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from UM.Qt.Duration import Duration, DurationFormat 4 | 5 | test_data = [ 6 | {"total_time": 22, "seconds": 22, "minutes": 0, "hours": 0, "days": 0}, 7 | {"total_time": 60, "seconds": 0, "minutes": 1, "hours": 0, "days": 0}, 8 | {"total_time": 3600, "seconds": 0, "minutes": 0, "hours": 1, "days": 0}, 9 | {"total_time": 86400, "seconds": 0, "minutes": 0, "hours": 0, "days": 1}, 10 | {"total_time": 90061, "seconds": 1, "minutes": 1, "hours": 1, "days": 1}, 11 | ] 12 | 13 | @pytest.mark.parametrize("data", test_data) 14 | def test_durationCreation(data): 15 | duration = Duration(data["total_time"]) 16 | assert duration.days == data["days"] 17 | assert duration.seconds == data["seconds"] 18 | assert duration.minutes == data["minutes"] 19 | assert duration.hours == data["hours"] 20 | assert int(duration) == data["total_time"] 21 | assert duration.valid 22 | assert not duration.isTotalDurationZero 23 | 24 | def test_invalidDuration(): 25 | duration = Duration() 26 | assert not duration.valid 27 | 28 | 29 | def test_zeroDuration(): 30 | zero_duration = Duration(0) 31 | assert zero_duration.isTotalDurationZero 32 | assert zero_duration.valid 33 | 34 | 35 | def test_negativeDuration(): 36 | negative_duration = Duration(-10) 37 | assert not negative_duration.valid 38 | 39 | 40 | def test_hugeDuration(): 41 | duration = Duration(2147483648) 42 | # Reaaaaaaly big numbers should be reset to zero because of python C++ conversion issues. 43 | assert duration.isTotalDurationZero 44 | 45 | 46 | def test_getDisplayString(): 47 | # We only test the ones that are not depending on translations. 48 | assert Duration(1).getDisplayString(DurationFormat.Format.Seconds) == "1" 49 | assert Duration(1).getDisplayString(9002) == "" # Unkown format. 50 | assert Duration(1).getDisplayString(DurationFormat.Format.ISO8601) == "00:00:01" 51 | assert Duration(86401).getDisplayString(DurationFormat.Format.ISO8601) == "24:00:01" -------------------------------------------------------------------------------- /tests/TestExtension.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from UM.Extension import Extension 4 | 5 | 6 | def test_activateMenuItem(): 7 | test_extension = Extension() 8 | mock_object = MagicMock() 9 | mock_function = mock_object.bla 10 | test_extension.addMenuItem("test", mock_function) 11 | test_extension.activateMenuItem("test") 12 | 13 | # Check that the function was called without any attributes being passed along. 14 | mock_function.assert_called_once_with() 15 | 16 | 17 | def test_menuItemOrder(): 18 | test_extension = Extension() 19 | 20 | test_extension.addMenuItem("b", MagicMock()) 21 | test_extension.addMenuItem("a", MagicMock()) 22 | 23 | # Ensure that the order by which the menu items were added is the same. 24 | assert test_extension.getMenuItemList() == ["b", "a"] 25 | 26 | 27 | # Super simple test, there is no reason this should ever change, but now extension is 100% tested 28 | def test_menuName(): 29 | test_extension = Extension() 30 | assert test_extension.getMenuName() is None 31 | test_extension.setMenuName("bloop") 32 | assert test_extension.getMenuName() == "bloop" 33 | -------------------------------------------------------------------------------- /tests/TestFileWriter.py: -------------------------------------------------------------------------------- 1 | from UM.FileHandler.FileWriter import FileWriter 2 | import pytest 3 | import io 4 | 5 | def test_getAddToRecentFiles(): 6 | writer = FileWriter(add_to_recent_files = True) 7 | assert writer.getAddToRecentFiles() 8 | 9 | other_writer = FileWriter(add_to_recent_files = False) 10 | assert not other_writer.getAddToRecentFiles() 11 | 12 | 13 | def test_write(): 14 | writer = FileWriter() 15 | with pytest.raises(Exception): 16 | writer.write(io.StringIO(), "Some data to write") 17 | 18 | 19 | def test_information(): 20 | writer = FileWriter() 21 | writer.setInformation("Some information about the writer") 22 | assert writer.getInformation() == "Some information about the writer" -------------------------------------------------------------------------------- /tests/TestMessage.py: -------------------------------------------------------------------------------- 1 | from UM.Message import Message 2 | 3 | 4 | def test_addAction(): 5 | message = Message() 6 | message.addAction(action_id = "blarg", name = "zomg", icon = "NO ICON", description="SuperAwesomeMessage") 7 | 8 | assert len(message.getActions()) == 1 9 | 10 | 11 | def test_gettersAndSetters(): 12 | message = Message(text = "OMG", title="YAY", image_caption="DERP", image_source= "HERP", option_text="FOO", 13 | option_state= False, message_type = Message.MessageType.POSITIVE) 14 | message.setMaxProgress(200) 15 | assert message.getText() == "OMG" 16 | assert message.getTitle() == "YAY" 17 | assert message.getImageCaption() == "DERP" 18 | assert message.getImageSource() == "HERP" 19 | assert message.getOptionText() == "FOO" 20 | assert message.getMaxProgress() == 200 21 | assert message.getOptionState() == False 22 | 23 | message.setTitle("whoo") 24 | assert message.getTitle() == "whoo" 25 | 26 | 27 | def test_dismissable(): 28 | # In certain conditions the message is always set to dismissable (even if we asked for a non dimissiable message) 29 | message = Message(lifetime=0, dismissable=False) 30 | assert message.isDismissable() 31 | -------------------------------------------------------------------------------- /tests/TestOutputDevice.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | import pytest 4 | 5 | from UM.OutputDevice.OutputDevice import OutputDevice 6 | 7 | test_validate_data = [ 8 | {"attribute": "IconName", "value": "blarg"}, 9 | {"attribute": "ShortDescription", "value": "omg"}, 10 | {"attribute": "Name", "value": "SHeWhoShallNotBeNamed"}, 11 | {"attribute": "Description", "value": "OH NOES!"}, 12 | {"attribute": "Priority", "value": 12} 13 | ] 14 | 15 | 16 | def test_createOutputDevice(): 17 | output_device = OutputDevice("Random_id") 18 | assert output_device.getId() == "Random_id" 19 | 20 | 21 | @pytest.mark.parametrize("data", test_validate_data) 22 | def test_getAndSet(data): 23 | output_device = OutputDevice("Random_id") 24 | 25 | output_device.metaDataChanged = MagicMock() 26 | # Attempt to set the value 27 | getattr(output_device, "set" + data["attribute"])(data["value"]) 28 | 29 | # Ensure that the value got set 30 | assert getattr(output_device, "get" + data["attribute"])() == data["value"] 31 | 32 | # Check if signal fired. 33 | assert output_device.metaDataChanged.emit.call_count == 1 34 | 35 | # Attempt to set the setter to the same value again. 36 | getattr(output_device, "set" + data["attribute"])(data["value"]) 37 | # Ensure signal fired only once! 38 | assert output_device.metaDataChanged.emit.call_count == 1 39 | -------------------------------------------------------------------------------- /tests/TestPluginObject.py: -------------------------------------------------------------------------------- 1 | from UM.PluginObject import PluginObject 2 | import pytest 3 | 4 | 5 | def test_getId_unhappy(): 6 | plugin = PluginObject() 7 | with pytest.raises(ValueError): 8 | plugin.getPluginId() # We didn't set an id yet. 9 | 10 | 11 | def test_getVersion_unhappy(): 12 | plugin = PluginObject() 13 | with pytest.raises(ValueError): 14 | plugin.getVersion() # We didn't set a version yet. 15 | 16 | 17 | def test_getVersion_happy(): 18 | plugin = PluginObject() 19 | plugin.setVersion("12.0.0") 20 | assert plugin.getVersion() == "12.0.0" 21 | 22 | 23 | def test_getId_happy(): 24 | plugin = PluginObject() 25 | plugin.setPluginId("UltiBot") 26 | assert plugin.getPluginId() == "UltiBot" -------------------------------------------------------------------------------- /tests/TestQtRenderer.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from UM.Qt.QtRenderer import QtRenderer 4 | 5 | 6 | def test_getAndSetViewportSize(): 7 | renderer = QtRenderer() 8 | mocked_render_pass = MagicMock() 9 | renderer.addRenderPass(mocked_render_pass) 10 | renderer.setViewportSize(100, 200) 11 | mocked_render_pass.setSize.assert_called_with(100, 200) 12 | 13 | assert renderer.getViewportWidth() == 100 14 | assert renderer.getViewportHeight() == 200 15 | 16 | 17 | def test_getAndSetWindowSize(): 18 | renderer = QtRenderer() 19 | renderer.setWindowSize(300, 400) 20 | assert (300, 400) == renderer.getWindowSize() 21 | 22 | -------------------------------------------------------------------------------- /tests/TestRenderer.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | 3 | from UM.View.RenderPass import RenderPass 4 | from UM.View.Renderer import Renderer 5 | 6 | 7 | def testAddRemoveRenderPas(): 8 | renderer = Renderer() 9 | with patch("UM.View.GL.OpenGL.OpenGL.getInstance"): 10 | render_pass_1 = RenderPass("test1", 1, 1) 11 | 12 | renderer.addRenderPass(render_pass_1) 13 | 14 | assert renderer.getRenderPass("test1") == render_pass_1 15 | assert renderer.getRenderPass("test2") is None 16 | 17 | assert len(renderer.getRenderPasses()) == 1 18 | 19 | renderer.removeRenderPass(render_pass_1) 20 | 21 | assert renderer.getRenderPass("test1") is None -------------------------------------------------------------------------------- /tests/TestStage.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import QUrl 2 | 3 | from UM.Stage import Stage 4 | 5 | 6 | def test_addGetDisplayComponent(): 7 | stage = Stage() 8 | stage.addDisplayComponent("BLORP", "location") 9 | assert stage.getDisplayComponent("BLORP") == QUrl.fromLocalFile("location") 10 | 11 | stage.addDisplayComponent("MEEP!", QUrl.fromLocalFile("MEEP")) 12 | assert stage.getDisplayComponent("MEEP!") == QUrl.fromLocalFile("MEEP") 13 | 14 | 15 | def test_getUnknownDisplayComponent(): 16 | stage = Stage() 17 | # Just an empty QUrl 18 | assert stage.getDisplayComponent("BLORP") == QUrl() 19 | -------------------------------------------------------------------------------- /tests/TestTheme.py: -------------------------------------------------------------------------------- 1 | from UM.Qt.Bindings.Theme import Theme 2 | from unittest.mock import MagicMock, patch 3 | from PyQt6.QtGui import QColor, QFont 4 | from PyQt6.QtCore import QSizeF, QUrl 5 | import pytest 6 | import os 7 | 8 | @pytest.fixture 9 | def theme(): 10 | application = MagicMock() 11 | with patch("UM.Qt.Bindings.Theme.QFontMetrics"): 12 | with patch("UM.Qt.Bindings.Theme.QCoreApplication.instance"): 13 | with patch("UM.Application.Application.getInstance", MagicMock(return_value = application)): 14 | with patch("UM.Resources.Resources.getPath", MagicMock(return_value = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_theme"))): 15 | return Theme(MagicMock()) 16 | 17 | 18 | def test_getColor(theme): 19 | assert theme.getColor("test_color") == QColor(255, 255, 255, 255) 20 | 21 | 22 | def test_getUnknownColor(theme): 23 | assert theme.getColor("Dunno") == QColor() 24 | 25 | 26 | def test_getKnownSize(theme): 27 | assert theme.getSize("test_size") == QSizeF(42, 1337) 28 | 29 | 30 | def test_getUnknownSize(theme): 31 | assert theme.getSize("Dunno?") == QSizeF() 32 | 33 | 34 | def test_getKnownIcon(theme): 35 | icon_location = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_theme", "icons", "test.svg") 36 | assert theme.getIcon("test") == QUrl.fromLocalFile(icon_location) 37 | 38 | 39 | def test_getUnknownIcon(theme): 40 | assert theme.getIcon("BoringAndProfesisonalIcon") == QUrl() 41 | 42 | 43 | def test_knownFont(theme): 44 | font = theme.getFont("test_font") 45 | assert font.family() == "Felidae" 46 | assert font.weight() == 350 47 | 48 | 49 | def test_unknownFont(theme): 50 | assert theme.getFont("whatever") == QFont() 51 | 52 | 53 | def test_knownImage(theme): 54 | image_location = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_theme", "images", "kitten.jpg") 55 | assert theme.getImage("kitten") == QUrl.fromLocalFile(image_location) 56 | 57 | 58 | def test_unknownImage(theme): 59 | assert theme.getImage("BoringBusinessPictureWhichIsAbsolutelyNotAKitten") == QUrl() 60 | -------------------------------------------------------------------------------- /tests/TestToolHandle.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | 3 | import pytest 4 | 5 | from UM.Scene.ToolHandle import ToolHandle 6 | 7 | test_validate_data = [ 8 | {"attribute": "LineMesh", "value": "whatever"}, 9 | {"attribute": "SolidMesh", "value": "blorp"}, 10 | {"attribute": "SelectionMesh", "value": "omgzomg"} 11 | ] 12 | 13 | @pytest.mark.parametrize("data", test_validate_data) 14 | def test_getAndSet(data): 15 | with patch("UM.Application.Application.getInstance"): 16 | tool_handle = ToolHandle() 17 | 18 | # Attempt to set the value 19 | getattr(tool_handle, "set" + data["attribute"])(data["value"]) 20 | 21 | # Ensure that the value got set 22 | assert getattr(tool_handle, "get" + data["attribute"])() == data["value"] 23 | -------------------------------------------------------------------------------- /tests/TestUtil.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from UM.Util import parseBool 4 | import pytest 5 | 6 | positive_results = [True, "True", "true", "Yes", "yes", 1] 7 | negative_results = [False, "False", "false", "No", "no", 0, None, "I like turtles", MagicMock()] 8 | 9 | 10 | @pytest.mark.parametrize("value", positive_results) 11 | def test_positive(value): 12 | assert parseBool(value) 13 | 14 | 15 | @pytest.mark.parametrize("value", negative_results) 16 | def test_negative(value): 17 | assert not parseBool(value) -------------------------------------------------------------------------------- /tests/UnitTestPackage.package: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/tests/UnitTestPackage.package -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/tests/__init__.py -------------------------------------------------------------------------------- /tests/benchmarks/Math/profile_rayintersection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Math.AxisAlignedBox import AxisAlignedBox 5 | from UM.Math.Ray import Ray 6 | from UM.Math.Vector import Vector 7 | 8 | @profile 9 | def intersects(box, ray): 10 | return box.intersectsRay(ray) 11 | 12 | ray = Ray(Vector(10, 10, 10), Vector(-1, -1, -1)) 13 | box = AxisAlignedBox(10, 10, 10) 14 | 15 | for i in range(100000): 16 | intersects(box, ray) 17 | -------------------------------------------------------------------------------- /tests/benchmarks/MeshData/profile_addvertex.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Mesh.MeshData import MeshData 5 | 6 | @profile 7 | def addVertex(mesh): 8 | mesh.addVertex(0, 1, 0) 9 | 10 | mesh = MeshData() 11 | mesh.reserveVertexCount(1000000) 12 | for i in range(1000000): 13 | addVertex(mesh) 14 | -------------------------------------------------------------------------------- /tests/benchmarks/MeshData/profile_calcnormals.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Mesh.MeshData import MeshData 5 | 6 | @profile 7 | def calcNormals(mesh): 8 | mesh.calculateNormals() 9 | 10 | mesh = MeshData() 11 | mesh.reserveVertexCount(99999) 12 | for i in range(33333): 13 | mesh.addVertex(0, 1, 0) 14 | mesh.addVertex(1, 1, 0) 15 | mesh.addVertex(1, 0, 0) 16 | 17 | for i in range(100): 18 | calcNormals(mesh) 19 | -------------------------------------------------------------------------------- /tests/benchmarks/MeshData/profile_getbytearray.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Mesh.MeshData import MeshData 5 | 6 | @profile 7 | def getByteArray(mesh): 8 | return mesh.getVerticesAsByteArray() 9 | 10 | mesh = MeshData() 11 | mesh.reserveVertexCount(10000) 12 | for i in range(10000): 13 | mesh.addVertex(0, 1, 0) 14 | 15 | for i in range(100): 16 | a = getByteArray(mesh) 17 | -------------------------------------------------------------------------------- /tests/benchmarks/MeshData/profile_reserve.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | from UM.Mesh.MeshData import MeshData 5 | 6 | @profile 7 | def reserveFaceCount(mesh): 8 | mesh.reserveFaceCount(10000) 9 | 10 | @profile 11 | def reserveVertexCount(mesh): 12 | mesh.reserveVertexCount(30000) 13 | 14 | mesh = MeshData() 15 | for i in range(100): 16 | reserveFaceCount(mesh) 17 | 18 | mesh = MeshData() 19 | for i in range(100): 20 | reserveVertexCount(mesh) 21 | -------------------------------------------------------------------------------- /tests/benchmarks/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Ultimaker B.V. 2 | # Uranium is released under the terms of the LGPLv3 or higher. 3 | 4 | import pytest 5 | import warnings 6 | 7 | warn = True 8 | 9 | @pytest.hookimpl 10 | def pytest_ignore_collect(path, config): 11 | if config.pluginmanager.hasplugin("pytest-benchmark"): 12 | return False 13 | else: 14 | global warn 15 | if warn: 16 | warnings.warn(pytest.PytestWarning("Skipping benchmarks because pytest-benchmark plugin was not found.")) 17 | warn = False 18 | 19 | return True 20 | -------------------------------------------------------------------------------- /tests/preferences_test.cfg: -------------------------------------------------------------------------------- 1 | [general] 2 | foo = omgzomg 3 | derp = True 4 | version = 7 5 | 6 | [test] 7 | more_test = 12 8 | even_more_test = 9001 -------------------------------------------------------------------------------- /tests/test_theme/images/kitten.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/Uranium/792ab393f14ab29827b8c8533d5b2eb174f4c927/tests/test_theme/images/kitten.jpg -------------------------------------------------------------------------------- /tests/test_theme/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "Ultimaker" 4 | }, 5 | "fonts": { 6 | "test_font": { 7 | "weight": 350, 8 | "family": "Felidae" 9 | } 10 | }, 11 | "colors": { 12 | "test_color": [ 13 | 255, 14 | 255, 15 | 255, 16 | 255 17 | ] 18 | }, 19 | "sizes": { 20 | "test_size": [42, 1337] 21 | } 22 | } --------------------------------------------------------------------------------