mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-04-22 13:49:39 +08:00
Merge pull request #20261 from Ultimaker/CURA-7435_3DConnexion
CURA-12047 3DConnexion devices support
This commit is contained in:
commit
620cb032e4
@ -125,6 +125,7 @@ pyinstaller:
|
||||
- "PyQt6.sip"
|
||||
- "stl"
|
||||
- "keyrings.alt"
|
||||
- "pynavlib"
|
||||
collect_all_WINDOWS_ONLY:
|
||||
- "PyQt6.Qt"
|
||||
- "PyQt6.Qt6"
|
||||
@ -567,6 +568,7 @@ pip_requirements_core:
|
||||
hashes:
|
||||
- sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5
|
||||
- sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413
|
||||
|
||||
Windows:
|
||||
twisted-iocpsupport:
|
||||
version: "1.0.2"
|
||||
@ -588,6 +590,22 @@ pip_requirements_core:
|
||||
hashes:
|
||||
- sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8
|
||||
- sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755
|
||||
pynavlib:
|
||||
version: "0.9.4"
|
||||
hashes:
|
||||
- sha256:fdd5ab5b6e0a2c9bbcebb154ac7303daf845865a1649be04e1bd8e8e5889401f
|
||||
- sha256:493c4b3cacc939b021a694d99723106dbd7ee5515ad4dfc1c7fc8219ef20cf3a
|
||||
- sha256:332831553a70be05fe58c43a08109b42970cfedc6086ffb4306859142a0e9210
|
||||
- sha256:9173f61ad83172c306b92bbe38f949889c158cd6dfdc924db01f257a437bf2a6
|
||||
|
||||
Macos:
|
||||
pynavlib:
|
||||
version: "0.9.4"
|
||||
hashes:
|
||||
- sha256:567efd0af97f9014326898b209eea94d9f5cc58e9f589ccf8354584568fcb87d
|
||||
- sha256:f0d7ce426e816788aa96b419fd7da263eafb99aca46ce3b6e5dbaf2bbf6b614a
|
||||
- sha256:33962a322033a78db05a8c2cc3d59e057fbea5b04879c3c54e2fe3041d691a12
|
||||
- sha256:d06d94b1dee4ba024b4a121869e572f571673a3b8c15b4055f52236d43c19a02
|
||||
|
||||
pip_requirements_dev:
|
||||
any_os:
|
||||
|
15
conanfile.py
15
conanfile.py
@ -411,7 +411,8 @@ class CuraConan(ConanFile):
|
||||
pyinstaller_metadata = self.conan_data["pyinstaller"]
|
||||
datas = []
|
||||
for data in pyinstaller_metadata["datas"].values():
|
||||
if (not self.options.internal and data.get("internal", False)) or (not self.options.enterprise and data.get("enterprise_only", False)):
|
||||
if (not self.options.internal and data.get("internal", False)) or (
|
||||
not self.options.enterprise and data.get("enterprise_only", False)):
|
||||
continue
|
||||
|
||||
if "oses" in data and self.settings.os not in data["oses"]:
|
||||
@ -572,8 +573,10 @@ class CuraConan(ConanFile):
|
||||
if self.options.enterprise:
|
||||
rmdir(self, str(Path(self.source_folder, "plugins", "NativeCADplugin")))
|
||||
native_cad_plugin = self.dependencies["native_cad_plugin"].cpp_info
|
||||
copy(self, "*", native_cad_plugin.resdirs[0], str(Path(self.source_folder, "plugins", "NativeCADplugin")), keep_path = True)
|
||||
copy(self, "bundled_*.json", native_cad_plugin.resdirs[1], str(Path(self.source_folder, "resources", "bundled_packages")), keep_path = False)
|
||||
copy(self, "*", native_cad_plugin.resdirs[0], str(Path(self.source_folder, "plugins", "NativeCADplugin")),
|
||||
keep_path = True)
|
||||
copy(self, "bundled_*.json", native_cad_plugin.resdirs[1],
|
||||
str(Path(self.source_folder, "resources", "bundled_packages")), keep_path = False)
|
||||
|
||||
# Copy resources of cura_binary_data
|
||||
cura_binary_data = self.dependencies["cura_binary_data"].cpp_info
|
||||
@ -611,7 +614,8 @@ class CuraConan(ConanFile):
|
||||
|
||||
def build(self):
|
||||
if self.settings.os == "Windows" and not self.conf.get("tools.microsoft.bash:path", check_type=str):
|
||||
self.output.warning("Skipping generation of binary translation files because Bash could not be found and is required")
|
||||
self.output.warning(
|
||||
"Skipping generation of binary translation files because Bash could not be found and is required")
|
||||
return
|
||||
|
||||
for po_file in Path(self.source_folder, "resources", "i18n").glob("**/*.po"):
|
||||
@ -624,7 +628,8 @@ class CuraConan(ConanFile):
|
||||
''' Note: this deploy step is actually used to prepare for building a Cura distribution with pyinstaller, which is not
|
||||
the original purpose in the Conan philosophy '''
|
||||
|
||||
copy(self, "*", os.path.join(self.package_folder, self.cpp.package.resdirs[2]), os.path.join(self.deploy_folder, "packaging"), keep_path = True)
|
||||
copy(self, "*", os.path.join(self.package_folder, self.cpp.package.resdirs[2]),
|
||||
os.path.join(self.deploy_folder, "packaging"), keep_path=True)
|
||||
|
||||
# Copy resources of Cura (keep folder structure) needed by pyinstaller to determine the module structure
|
||||
copy(self, "*", os.path.join(self.package_folder, self.cpp_info.bindirs[0]), str(self._base_dir), keep_path = False)
|
||||
|
@ -398,7 +398,8 @@ class MachineManager(QObject):
|
||||
self.setVariantByName(extruder.getMetaDataEntry("position"), machine_node.preferred_variant_name)
|
||||
variant_node = machine_node.variants.get(machine_node.preferred_variant_name)
|
||||
|
||||
material_node = variant_node.materials.get(extruder.material.getMetaDataEntry("base_file")) if variant_node else None
|
||||
material_node = variant_node.materials.get(
|
||||
extruder.material.getMetaDataEntry("base_file")) if variant_node else None
|
||||
if material_node is None:
|
||||
Logger.log("w", "An extruder has an unknown material, switching it to the preferred material")
|
||||
if not self.setMaterialById(extruder.getMetaDataEntry("position"), machine_node.preferred_material):
|
||||
|
@ -217,7 +217,8 @@ class WelcomePagesModel(ListModel):
|
||||
def _getBuiltinWelcomePagePath(page_filename: str) -> QUrl:
|
||||
"""Convenience function to get QUrl path to pages that's located in "resources/qml/WelcomePages"."""
|
||||
from cura.CuraApplication import CuraApplication
|
||||
return QUrl.fromLocalFile(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "WelcomePages", page_filename))
|
||||
return QUrl.fromLocalFile(
|
||||
Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "WelcomePages", page_filename))
|
||||
|
||||
# FIXME: HACKs for optimization that we don't update the model every time the active machine gets changed.
|
||||
def _onActiveMachineChanged(self) -> None:
|
||||
|
@ -154,7 +154,7 @@ if __name__ == "__main__":
|
||||
parser.add_argument("--app_name", required = True, type = str, help = "Filename of the .app that will be contained within the dmg/pkg")
|
||||
args = parser.parse_args()
|
||||
|
||||
cura_version = args.cura_conan_version.replace("+","-") # + is not allowed for bundle identifier
|
||||
cura_version = args.cura_conan_version.replace("+", "-") # + is not allowed for bundle identifier
|
||||
|
||||
app_name = f"{args.app_name}.app"
|
||||
|
||||
|
@ -78,7 +78,7 @@ if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = "Create Windows exe installer of Cura.")
|
||||
parser.add_argument("--source_path", type=str, help="Path to Conan install Cura folder.")
|
||||
parser.add_argument("--dist_path", type=str, help="Path to Pyinstaller dist folder")
|
||||
parser.add_argument("--filename", type = str, help = "Filename of the exe (e.g. 'UltiMaker-Cura-5.1.0-beta-Windows-X64.exe')")
|
||||
parser.add_argument("--filename", type=str, help="Filename of the exe (e.g. 'UltiMaker-Cura-5.1.0-beta-Windows-X64.exe')")
|
||||
parser.add_argument("--version", type=str, help="The full cura version, e.g. 5.9.0-beta.1+24132")
|
||||
args = parser.parse_args()
|
||||
generate_nsi(args.source_path, args.dist_path, args.filename, args.version)
|
||||
|
273
plugins/3DConnexion/NavlibClient.py
Normal file
273
plugins/3DConnexion/NavlibClient.py
Normal file
@ -0,0 +1,273 @@
|
||||
# Copyright (c) 2025 3Dconnexion, UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from cura.PickingPass import PickingPass
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Scene import Scene
|
||||
from UM.Resources import Resources
|
||||
from UM.Tool import Tool
|
||||
from UM.View.Renderer import Renderer
|
||||
from .OverlayNode import OverlayNode
|
||||
import pynavlib.pynavlib_interface as pynav
|
||||
|
||||
|
||||
class NavlibClient(pynav.NavlibNavigationModel, Tool):
|
||||
|
||||
def __init__(self, scene: Scene, renderer: Renderer) -> None:
|
||||
pynav.NavlibNavigationModel.__init__(self, False, pynav.NavlibOptions.RowMajorOrder)
|
||||
Tool.__init__(self)
|
||||
self._scene = scene
|
||||
self._renderer = renderer
|
||||
self._pointer_pick = None
|
||||
self._was_pick = False
|
||||
self._hit_selection_only = False
|
||||
self._picking_pass = None
|
||||
self._pivot_node = OverlayNode(node=SceneNode(), image_path=Resources.getPath(Resources.Images, "cor.png"), size=2.5)
|
||||
self.put_profile_hint("UltiMaker Cura")
|
||||
self.enable_navigation(True)
|
||||
|
||||
def pick(self, x: float, y: float, check_selection: bool = False, radius: float = 0.) -> Optional[Vector]:
|
||||
|
||||
if self._picking_pass is None or radius < 0.:
|
||||
return None
|
||||
|
||||
step = 0.
|
||||
if radius == 0.:
|
||||
grid_resolution = 0
|
||||
else:
|
||||
grid_resolution = 5
|
||||
step = (2. * radius) / float(grid_resolution)
|
||||
|
||||
min_depth = 99999.
|
||||
result_position = None
|
||||
|
||||
for i in range(grid_resolution + 1):
|
||||
for j in range(grid_resolution + 1):
|
||||
|
||||
coord_x = (x - radius) + i * step
|
||||
coord_y = (y - radius) + j * step
|
||||
|
||||
picked_depth = self._picking_pass.getPickedDepth(coord_x, coord_y)
|
||||
max_depth = 16777.215
|
||||
|
||||
if 0. < picked_depth < max_depth:
|
||||
|
||||
valid_hit = True
|
||||
if check_selection:
|
||||
selection_pass = self._renderer.getRenderPass("selection")
|
||||
picked_object_id = selection_pass.getIdAtPosition(coord_x, coord_y)
|
||||
picked_object = self._scene.findObject(picked_object_id)
|
||||
|
||||
from UM.Scene.Selection import Selection
|
||||
valid_hit = Selection.isSelected(picked_object)
|
||||
|
||||
if not valid_hit and grid_resolution > 0.:
|
||||
continue
|
||||
elif not valid_hit and grid_resolution == 0.:
|
||||
return None
|
||||
|
||||
if picked_depth < min_depth:
|
||||
min_depth = picked_depth
|
||||
result_position = self._picking_pass.getPickedPosition(coord_x, coord_y)
|
||||
|
||||
return result_position
|
||||
|
||||
def get_pointer_position(self) -> "pynav.NavlibVector":
|
||||
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
main_window = QtApplication.getInstance().getMainWindow()
|
||||
|
||||
x_n = 2. * main_window._mouse_x / main_window.width() - 1.
|
||||
y_n = 2. * main_window._mouse_y / main_window.height() - 1.
|
||||
|
||||
if self.get_is_view_perspective():
|
||||
self._was_pick = True
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
wrapped_pick = call_on_qt_thread(self.pick)
|
||||
|
||||
self._pointer_pick = wrapped_pick(x_n, y_n)
|
||||
|
||||
return pynav.NavlibVector(0., 0., 0.)
|
||||
else:
|
||||
ray = self._scene.getActiveCamera().getRay(x_n, y_n)
|
||||
pointer_position = ray.origin + ray.direction
|
||||
|
||||
return pynav.NavlibVector(pointer_position.x, pointer_position.y, pointer_position.z)
|
||||
|
||||
def get_view_extents(self) -> "pynav.NavlibBox":
|
||||
|
||||
view_width = self._scene.getActiveCamera().getViewportWidth()
|
||||
view_height = self._scene.getActiveCamera().getViewportHeight()
|
||||
horizontal_zoom = view_width * self._scene.getActiveCamera().getZoomFactor()
|
||||
vertical_zoom = view_height * self._scene.getActiveCamera().getZoomFactor()
|
||||
|
||||
pt_min = pynav.NavlibVector(-view_width / 2 - horizontal_zoom, -view_height / 2 - vertical_zoom, -9001)
|
||||
pt_max = pynav.NavlibVector(view_width / 2 + horizontal_zoom, view_height / 2 + vertical_zoom, 9001)
|
||||
|
||||
return pynav.NavlibBox(pt_min, pt_max)
|
||||
|
||||
def get_view_frustum(self) -> "pynav.NavlibFrustum":
|
||||
|
||||
projection_matrix = self._scene.getActiveCamera().getProjectionMatrix()
|
||||
half_height = 2. / projection_matrix.getData()[1,1]
|
||||
half_width = half_height * (projection_matrix.getData()[1,1] / projection_matrix.getData()[0,0])
|
||||
|
||||
return pynav.NavlibFrustum(-half_width, half_width, -half_height, half_height, 1., 5000.)
|
||||
|
||||
def get_is_view_perspective(self) -> bool:
|
||||
return self._scene.getActiveCamera().isPerspective()
|
||||
|
||||
def get_selection_extents(self) -> "pynav.NavlibBox":
|
||||
|
||||
from UM.Scene.Selection import Selection
|
||||
bounding_box = Selection.getBoundingBox()
|
||||
|
||||
if(bounding_box is not None) :
|
||||
pt_min = pynav.NavlibVector(bounding_box.minimum.x, bounding_box.minimum.y, bounding_box.minimum.z)
|
||||
pt_max = pynav.NavlibVector(bounding_box.maximum.x, bounding_box.maximum.y, bounding_box.maximum.z)
|
||||
return pynav.NavlibBox(pt_min, pt_max)
|
||||
|
||||
def get_selection_transform(self) -> "pynav.NavlibMatrix":
|
||||
return pynav.NavlibMatrix()
|
||||
|
||||
def get_is_selection_empty(self) -> bool:
|
||||
from UM.Scene.Selection import Selection
|
||||
return not Selection.hasSelection()
|
||||
|
||||
def get_pivot_visible(self) -> bool:
|
||||
return False
|
||||
|
||||
def get_camera_matrix(self) -> "pynav.NavlibMatrix":
|
||||
|
||||
transformation = self._scene.getActiveCamera().getLocalTransformation()
|
||||
|
||||
return pynav.NavlibMatrix([[transformation.at(0, 0), transformation.at(0, 1), transformation.at(0, 2), transformation.at(0, 3)],
|
||||
[transformation.at(1, 0), transformation.at(1, 1), transformation.at(1, 2), transformation.at(1, 3)],
|
||||
[transformation.at(2, 0), transformation.at(2, 1), transformation.at(2, 2), transformation.at(2, 3)],
|
||||
[transformation.at(3, 0), transformation.at(3, 1), transformation.at(3, 2), transformation.at(3, 3)]])
|
||||
|
||||
def get_coordinate_system(self) -> "pynav.NavlibMatrix":
|
||||
return pynav.NavlibMatrix()
|
||||
|
||||
def get_front_view(self) -> "pynav.NavlibMatrix":
|
||||
return pynav.NavlibMatrix()
|
||||
|
||||
def get_model_extents(self) -> "pynav.NavlibBox":
|
||||
|
||||
result_bbox = AxisAlignedBox()
|
||||
build_volume_bbox = None
|
||||
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
node.setCalculateBoundingBox(True)
|
||||
if node.__class__.__qualname__ == "CuraSceneNode" :
|
||||
result_bbox = result_bbox + node.getBoundingBox()
|
||||
elif node.__class__.__qualname__ == "BuildVolume":
|
||||
build_volume_bbox = node.getBoundingBox()
|
||||
|
||||
if not result_bbox.isValid():
|
||||
result_bbox = build_volume_bbox
|
||||
|
||||
if result_bbox is not None:
|
||||
pt_min = pynav.NavlibVector(result_bbox.minimum.x, result_bbox.minimum.y, result_bbox.minimum.z)
|
||||
pt_max = pynav.NavlibVector(result_bbox.maximum.x, result_bbox.maximum.y, result_bbox.maximum.z)
|
||||
self._scene_center = result_bbox.center
|
||||
self._scene_radius = (result_bbox.maximum - self._scene_center).length()
|
||||
return pynav.NavlibBox(pt_min, pt_max)
|
||||
|
||||
def get_pivot_position(self) -> "pynav.NavlibVector":
|
||||
return pynav.NavlibVector()
|
||||
|
||||
def get_hit_look_at(self) -> "pynav.NavlibVector":
|
||||
|
||||
if self._was_pick and self._pointer_pick is not None:
|
||||
return pynav.NavlibVector(self._pointer_pick.x, self._pointer_pick.y, self._pointer_pick.z)
|
||||
elif self._was_pick and self._pointer_pick is None:
|
||||
return None
|
||||
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
wrapped_pick = call_on_qt_thread(self.pick)
|
||||
picked_position = wrapped_pick(0, 0, self._hit_selection_only, 0.5)
|
||||
|
||||
if picked_position is not None:
|
||||
return pynav.NavlibVector(picked_position.x, picked_position.y, picked_position.z)
|
||||
|
||||
def get_units_to_meters(self) -> float:
|
||||
return 0.05
|
||||
|
||||
def is_user_pivot(self) -> bool:
|
||||
return False
|
||||
|
||||
def set_camera_matrix(self, matrix : "pynav.NavlibMatrix") -> None:
|
||||
|
||||
# !!!!!!
|
||||
# Hit testing in Orthographic view is not reliable
|
||||
# Picking starts in camera position, not on near plane
|
||||
# which results in wrong depth values (visible geometry
|
||||
# cannot be picked properly) - Workaround needed (camera position offset)
|
||||
# !!!!!!
|
||||
if not self.get_is_view_perspective():
|
||||
affine = matrix._matrix
|
||||
direction = Vector(-affine[0][2], -affine[1][2], -affine[2][2])
|
||||
distance = self._scene_center - Vector(affine[0][3], affine[1][3], affine[2][3])
|
||||
|
||||
cos_value = direction.dot(distance.normalized())
|
||||
|
||||
offset = 0.
|
||||
|
||||
if (distance.length() < self._scene_radius) and (cos_value > 0.):
|
||||
offset = self._scene_radius
|
||||
elif (distance.length() < self._scene_radius) and (cos_value < 0.):
|
||||
offset = 2. * self._scene_radius
|
||||
elif (distance.length() > self._scene_radius) and (cos_value < 0.):
|
||||
offset = 2. * distance.length()
|
||||
|
||||
matrix._matrix[0][3] = matrix._matrix[0][3] - offset * direction.x
|
||||
matrix._matrix[1][3] = matrix._matrix[1][3] - offset * direction.y
|
||||
matrix._matrix[2][3] = matrix._matrix[2][3] - offset * direction.z
|
||||
|
||||
transformation = Matrix(data = matrix._matrix)
|
||||
self._scene.getActiveCamera().setTransformation(transformation)
|
||||
|
||||
active_camera = self._scene.getActiveCamera()
|
||||
if active_camera.isPerspective():
|
||||
camera_position = active_camera.getWorldPosition()
|
||||
dist = (camera_position - self._pivot_node.getWorldPosition()).length()
|
||||
scale = dist / 400.
|
||||
else:
|
||||
view_width = active_camera.getViewportWidth()
|
||||
current_size = view_width + (2. * active_camera.getZoomFactor() * view_width)
|
||||
scale = current_size / view_width * 5.
|
||||
|
||||
self._pivot_node.scale(scale)
|
||||
|
||||
def set_view_extents(self, extents: "pynav.NavlibBox") -> None:
|
||||
view_width = self._scene.getActiveCamera().getViewportWidth()
|
||||
new_zoom = (extents._min._x + view_width / 2.) / - view_width
|
||||
self._scene.getActiveCamera().setZoomFactor(new_zoom)
|
||||
|
||||
def set_hit_selection_only(self, onlySelection : bool) -> None:
|
||||
self._hit_selection_only = onlySelection
|
||||
|
||||
def set_motion_flag(self, motion : bool) -> None:
|
||||
if motion:
|
||||
width = self._scene.getActiveCamera().getViewportWidth()
|
||||
height = self._scene.getActiveCamera().getViewportHeight()
|
||||
self._picking_pass = PickingPass(width, height)
|
||||
self._renderer.addRenderPass(self._picking_pass)
|
||||
else:
|
||||
self._was_pick = False
|
||||
self._renderer.removeRenderPass(self._picking_pass)
|
||||
|
||||
def set_pivot_position(self, position) -> None:
|
||||
self._pivot_node._target_node.setPosition(position=Vector(position._x, position._y, position._z), transform_space = SceneNode.TransformSpace.World)
|
||||
|
||||
def set_pivot_visible(self, visible) -> None:
|
||||
if visible:
|
||||
self._scene.getRoot().addChild(self._pivot_node)
|
||||
else:
|
||||
self._scene.getRoot().removeChild(self._pivot_node)
|
68
plugins/3DConnexion/OverlayNode.py
Normal file
68
plugins/3DConnexion/OverlayNode.py
Normal file
@ -0,0 +1,68 @@
|
||||
# Copyright (c) 2025 3Dconnexion, UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder # To create the overlay quad
|
||||
from UM.Resources import Resources # To find shader locations
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Application import Application
|
||||
|
||||
try:
|
||||
from PyQt6.QtGui import QImage
|
||||
except:
|
||||
from PyQt5.QtGui import QImage
|
||||
|
||||
class OverlayNode(SceneNode):
|
||||
def __init__(self, node, image_path, size, parent=None):
|
||||
super().__init__(parent)
|
||||
self._target_node = node
|
||||
self.setCalculateBoundingBox(False)
|
||||
|
||||
self._overlay_mesh = self._createOverlayQuad(size)
|
||||
self._drawed_mesh = self._overlay_mesh
|
||||
self._shader = None
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
self._scale = 1.
|
||||
self._image_path = image_path
|
||||
|
||||
def scale(self, factor):
|
||||
scale_matrix = Matrix()
|
||||
scale_matrix.setByScaleFactor(factor)
|
||||
self._drawed_mesh = self._overlay_mesh.getTransformed(scale_matrix)
|
||||
|
||||
def _createOverlayQuad(self, size):
|
||||
mb = MeshBuilder()
|
||||
mb.addFaceByPoints(-size / 2, -size / 2, 0, -size / 2, size / 2, 0, size / 2, -size / 2, 0)
|
||||
mb.addFaceByPoints(size / 2, size / 2, 0, -size / 2, size / 2, 0, size / 2, -size / 2, 0)
|
||||
|
||||
# Set UV coordinates so a texture can be created
|
||||
mb.setVertexUVCoordinates(0, 0, 1)
|
||||
mb.setVertexUVCoordinates(1, 0, 0)
|
||||
mb.setVertexUVCoordinates(4, 0, 0)
|
||||
mb.setVertexUVCoordinates(2, 1, 1)
|
||||
mb.setVertexUVCoordinates(5, 1, 1)
|
||||
mb.setVertexUVCoordinates(3, 1, 0)
|
||||
|
||||
return mb.build()
|
||||
|
||||
def render(self, renderer):
|
||||
|
||||
if not self._shader:
|
||||
# We now misuse the platform shader, as it actually supports textures
|
||||
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "platform.shader"))
|
||||
# Set the opacity to 0, so that the template is in full control.
|
||||
self._shader.setUniformValue("u_opacity", 0)
|
||||
self._texture = OpenGL.getInstance().createTexture()
|
||||
texture_image = QImage(self._image_path)
|
||||
self._texture.setImage(texture_image)
|
||||
self._shader.setTexture(0, self._texture)
|
||||
|
||||
node_position = self._target_node.getWorldPosition()
|
||||
position_matrix = Matrix()
|
||||
position_matrix.setByTranslation(node_position)
|
||||
camera_orientation = self._scene.getActiveCamera().getOrientation().toMatrix()
|
||||
|
||||
renderer.queueNode(self._scene.getRoot(), shader=self._shader, mesh=self._drawed_mesh.getTransformed(position_matrix.multiply(camera_orientation)), overlay=True)
|
||||
|
||||
return True # This node does it's own rendering.
|
26
plugins/3DConnexion/__init__.py
Normal file
26
plugins/3DConnexion/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
||||
from typing import TYPE_CHECKING, Dict, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Application import Application
|
||||
|
||||
|
||||
def getMetaData() -> Dict[str, Any]:
|
||||
return {
|
||||
"tool": {
|
||||
"visible": False
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def register(app: "Application") -> Dict[str, Any]:
|
||||
try:
|
||||
from .NavlibClient import NavlibClient
|
||||
return { "tool": NavlibClient(app.getController().getScene(), app.getRenderer()) }
|
||||
except BaseException as exception:
|
||||
Logger.warning(f"Unable to load 3Dconnexion library: {exception}")
|
||||
return { }
|
8
plugins/3DConnexion/plugin.json
Normal file
8
plugins/3DConnexion/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "3DConnexion mouses",
|
||||
"author": "3DConnexion",
|
||||
"version": "1.0.0",
|
||||
"description": "Allows working with 3D mouses inside Cura.",
|
||||
"api": 8,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -1,4 +1,21 @@
|
||||
{
|
||||
"3DConnexion": {
|
||||
"package_info": {
|
||||
"package_id": "3DConnexion",
|
||||
"package_type": "plugin",
|
||||
"display_name": "3DConnexion mouses",
|
||||
"description": "Allows working with 3D mouses inside Cura.\nOnly available on Windows and MacOS.",
|
||||
"package_version": "1.0.0",
|
||||
"sdk_version": "8.6.0",
|
||||
"website": "https://3dconnexion.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
"display_name": "3DConnexion",
|
||||
"email": "plugins@ultimaker.com",
|
||||
"website": "https://3dconnexion.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"3MFReader": {
|
||||
"package_info": {
|
||||
"package_id": "3MFReader",
|
||||
|
BIN
resources/images/cor.png
Normal file
BIN
resources/images/cor.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
@ -2,7 +2,8 @@ import requests
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
def get_package_wheel_hashes(package, version):
|
||||
|
||||
def get_package_wheel_hashes(package, version, os):
|
||||
url = f"https://pypi.org/pypi/{package}/{version}/json"
|
||||
data = requests.get(url).json()
|
||||
|
||||
@ -11,12 +12,15 @@ def get_package_wheel_hashes(package, version):
|
||||
print(f" hashes:")
|
||||
|
||||
for url in data["urls"]:
|
||||
print(f" - sha256:{url['digests']['sha256']}")
|
||||
if os is None or os in url["filename"]:
|
||||
print(f" - sha256:{url['digests']['sha256']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Display the hashes of the wheel files to be inserted in pip_requirements")
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Display the hashes of the wheel files to be inserted in pip_requirements")
|
||||
parser.add_argument("package", type=Path, help="Name of the target package")
|
||||
parser.add_argument("version", type=Path, help="Version of the target package")
|
||||
parser.add_argument('--os', type=str, help='Specific package OS', choices=['win', 'macosx', 'manylinux', 'musllinux'])
|
||||
args = parser.parse_args()
|
||||
get_package_wheel_hashes(args.package, args.version)
|
||||
get_package_wheel_hashes(args.package, args.version, args.os)
|
||||
|
@ -51,7 +51,8 @@ def test_createMachineWithUnknownDefinition(application, container_registry):
|
||||
application.getContainerRegistry = MagicMock(return_value=container_registry)
|
||||
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)):
|
||||
mocked_config_error = MagicMock()
|
||||
with patch("UM.ConfigurationErrorMessage.ConfigurationErrorMessage.getInstance", MagicMock(return_value=mocked_config_error)):
|
||||
with patch("UM.ConfigurationErrorMessage.ConfigurationErrorMessage.getInstance",
|
||||
MagicMock(return_value=mocked_config_error)):
|
||||
assert CuraStackBuilder.createMachine("Whatever", "NOPE") is None
|
||||
mocked_config_error.addFaultyContainers.assert_called_once_with("NOPE")
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user