├── .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 | [](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 | }
--------------------------------------------------------------------------------