diff --git a/Tools/HolocronToolset/src/toolset/gui/dialogs/load_from_location_result.py b/Tools/HolocronToolset/src/toolset/gui/dialogs/load_from_location_result.py index d1de7a708..b2decfb42 100644 --- a/Tools/HolocronToolset/src/toolset/gui/dialogs/load_from_location_result.py +++ b/Tools/HolocronToolset/src/toolset/gui/dialogs/load_from_location_result.py @@ -8,6 +8,7 @@ import shutil import subprocess import sys +import tempfile import time from collections import OrderedDict @@ -697,13 +698,21 @@ def _prepare_func( if not resource.exists(): missing_files.append(resource._path_ident_obj) # noqa: SLF001 return - with TemporaryDirectory("_tmpext2", "toolset_") as tempdir: - tempdir_path = Path(tempdir) - assert tempdir_path.safe_isdir() - temp_file = tempdir_path / resource.filename() - with BinaryWriterFile.to_file(temp_file) as writer: - writer.write_bytes(resource.data()) - func(temp_file, tableItem) + + # Create a temporary directory that persists until application shutdown + tempdir = tempfile.mkdtemp(prefix="toolset_", suffix="_tmpext2") + + # Register a cleanup function to delete the temporary directory at exit + def cleanup_tempdir(): + shutil.rmtree(tempdir, ignore_errors=True) + + atexit.register(cleanup_tempdir) + tempdir_path = Path(tempdir) + assert tempdir_path.safe_isdir() + temp_file = tempdir_path / resource.filename() + with BinaryWriterFile.to_file(temp_file) as writer: + writer.write_bytes(resource.data()) + func(temp_file, tableItem) else: func(path, tableItem) diff --git a/Tools/HolocronToolset/src/toolset/gui/widgets/renderer/module.py b/Tools/HolocronToolset/src/toolset/gui/widgets/renderer/module.py index cd53d2f66..5efa681b1 100644 --- a/Tools/HolocronToolset/src/toolset/gui/widgets/renderer/module.py +++ b/Tools/HolocronToolset/src/toolset/gui/widgets/renderer/module.py @@ -328,7 +328,7 @@ def moveCamera(self, forward: float, right: float, up: float): self.scene.camera.y += upward.y + sideward.y + forward_vec.y self.scene.camera.z += upward.z + sideward.z + forward_vec.z - def rotateCamera(self, yaw: float, pitch: float, snapRotations: bool = True): + def rotateCamera(self, yaw: float, pitch: float, *, snapRotations: bool = True): """Rotates the camera by the angles (radians) specified. Args: @@ -369,7 +369,7 @@ def mouseMoveEvent(self, e: QMouseEvent): 3. Get world position of cursor 4. Emit signal with mouse data if time since press > threshold """ - super().mouseMoveEvent(e) + #super().mouseMoveEvent(e) screen = Vector2(e.x(), e.y()) if self.freeCam: screenDelta = Vector2(screen.x - self.width() / 2, screen.y - self.height() / 2) diff --git a/Tools/HolocronToolset/src/toolset/gui/widgets/renderer/walkmesh.py b/Tools/HolocronToolset/src/toolset/gui/widgets/renderer/walkmesh.py index da9b67099..96f1e45db 100644 --- a/Tools/HolocronToolset/src/toolset/gui/widgets/renderer/walkmesh.py +++ b/Tools/HolocronToolset/src/toolset/gui/widgets/renderer/walkmesh.py @@ -822,7 +822,7 @@ def paintEvent(self, e: QPaintEvent): def wheelEvent(self, e: QWheelEvent): self.mouseScrolled.emit(Vector2(e.angleDelta().x(), e.angleDelta().y()), self._mouseDown, self._keysDown) - def mouseMoveEvent(self, e: QMouseEvent): # TODO: something here is causing the camera to continually zoom out while middlemouse is held down. + def mouseMoveEvent(self, e: QMouseEvent): """Handles mouse move events. Args: diff --git a/Tools/HolocronToolset/src/toolset/gui/widgets/settings/installations.py b/Tools/HolocronToolset/src/toolset/gui/widgets/settings/installations.py index 2734ff8fd..df11661ea 100644 --- a/Tools/HolocronToolset/src/toolset/gui/widgets/settings/installations.py +++ b/Tools/HolocronToolset/src/toolset/gui/widgets/settings/installations.py @@ -290,7 +290,7 @@ def _handle_firsttime_user(self, installations: dict[str, dict[str, Any]]): ) moduleSortOption = Settings.addSetting( "moduleSortOption", - 1, + 2, ) # endregion diff --git a/Tools/HolocronToolset/src/toolset/gui/windows/main.py b/Tools/HolocronToolset/src/toolset/gui/windows/main.py index 383bbdd5a..f20e9b8ae 100644 --- a/Tools/HolocronToolset/src/toolset/gui/windows/main.py +++ b/Tools/HolocronToolset/src/toolset/gui/windows/main.py @@ -829,7 +829,12 @@ def openModuleDesigner(self): if self.active is None: QMessageBox(QMessageBox.Icon.Information, "No installation loaded.", "Load an installation before opening the Module Designer.").exec_() return + # Retrieve the icon from self (assuming it's set as window icon) + window_icon = self.windowIcon() + + # Initialize the designer and set its window icon designer = ModuleDesigner(None, self.active) + designer.setWindowIcon(window_icon) addWindow(designer) def openSettingsDialog(self): diff --git a/Tools/HolocronToolset/src/toolset/gui/windows/module_designer.py b/Tools/HolocronToolset/src/toolset/gui/windows/module_designer.py index d6e73d3f2..413570c8a 100644 --- a/Tools/HolocronToolset/src/toolset/gui/windows/module_designer.py +++ b/Tools/HolocronToolset/src/toolset/gui/windows/module_designer.py @@ -1,6 +1,7 @@ from __future__ import annotations import math +import time from copy import deepcopy from typing import TYPE_CHECKING @@ -9,7 +10,7 @@ from qtpy import QtCore from qtpy.QtCore import QPoint, QTimer -from qtpy.QtGui import QColor, QIcon, QPixmap +from qtpy.QtGui import QColor, QCursor, QIcon, QPixmap from qtpy.QtWidgets import QAction, QApplication, QListWidgetItem, QMainWindow, QMenu, QMessageBox, QTreeWidgetItem from pykotor.gl.scene import Camera @@ -50,7 +51,7 @@ from toolset.gui.dialogs.insert_instance import InsertInstanceDialog from toolset.gui.dialogs.select_module import SelectModuleDialog from toolset.gui.editor import Editor -from toolset.gui.editors.git import DuplicateCommand, MoveCommand, RotateCommand, _GeometryMode, _InstanceMode, _SpawnMode, openInstanceDialog +from toolset.gui.editors.git import DeleteCommand, DuplicateCommand, MoveCommand, RotateCommand, _GeometryMode, _InstanceMode, _SpawnMode, calculate_zoom_strength, openInstanceDialog from toolset.gui.widgets.settings.module_designer import ModuleDesignerSettings from toolset.gui.windows.help import HelpWindow from toolset.utils.misc import QtMouse @@ -144,6 +145,7 @@ def __init__( self.ui: Ui_MainWindow = Ui_MainWindow() self.ui.setupUi(self) self._setupSignals() + self.last_free_cam_time = 0 # Initialize the last toggle time def intColorToQColor(intvalue: int) -> QColor: """Converts an integer color value to a QColor object. @@ -193,8 +195,8 @@ def intColorToQColor(intvalue: int) -> QColor: self.ui.flatRenderer.hideWalkmeshEdges = True self.ui.flatRenderer.highlightBoundaries = False - # self._controls3d: ModuleDesignerControls3d | ModuleDesignerControlsFreeCam = ModuleDesignerControls3d(self, self.ui.mainRenderer) - self._controls3d: ModuleDesignerControls3d | ModuleDesignerControlsFreeCam = ModuleDesignerControlsFreeCam(self, self.ui.mainRenderer) + self._controls3d: ModuleDesignerControls3d | ModuleDesignerControlsFreeCam = ModuleDesignerControls3d(self, self.ui.mainRenderer) + # self._controls3d: ModuleDesignerControls3d | ModuleDesignerControlsFreeCam = ModuleDesignerControlsFreeCam(self, self.ui.mainRenderer) self._controls2d: ModuleDesignerControls2d = ModuleDesignerControls2d(self, self.ui.flatRenderer) if mod_filepath is None: # Use singleShot timer so the ui window opens while the loading is happening. @@ -888,7 +890,7 @@ def setSelection(self, instances: list[GITInstance]): def deleteSelected(self): for instance in self.selectedInstances: self._module.git().resource().remove(instance) - + self.undoStack.push(DeleteCommand(self.git(), self.selectedInstances, self)) # noqa: SLF001 self.selectedInstances.clear() self.ui.mainRenderer.scene.selection.clear() self.ui.flatRenderer.instanceSelection.clear() @@ -1114,14 +1116,14 @@ def onContextMenu(self, world: Vector3, point: QPoint, *, isFlatRendererCall: bo if len(self.ui.mainRenderer.scene.selection) == 0: self.log.debug("onContextMenu No selection") - menu = self.onContextMenuSelectionNone(world) + menu = self.buildInsertInstanceMenu(world) else: menu = self.onContextMenuSelectionExists(world, isFlatRendererCall=isFlatRendererCall, getMenu=True) menu.popup(self.cursor().pos()) menu.aboutToHide.connect(self.ui.mainRenderer.resetMouseButtons) - def onContextMenuSelectionNone(self, world: Vector3): + def buildInsertInstanceMenu(self, world: Vector3): """Displays a context menu for object insertion. Args: @@ -1271,38 +1273,245 @@ def __init__(self, editor: ModuleDesigner, renderer: ModuleRenderer): self.renderer: ModuleRenderer = renderer self.renderer.setCursor(QtCore.Qt.CursorShape.ArrowCursor) # Show the cursor - self.moveXYCamera: ControlItem = ControlItem(self.settings.moveCameraXY3dBind) - self.moveZCamera: ControlItem = ControlItem(self.settings.moveCameraZ3dBind) - self.moveCameraPlane: ControlItem = ControlItem(self.settings.moveCameraPlane3dBind) - self.rotateCamera: ControlItem = ControlItem(self.settings.rotateCamera3dBind) - self.zoomCamera: ControlItem = ControlItem(self.settings.zoomCamera3dBind) - self.zoomCameraMM: ControlItem = ControlItem(self.settings.zoomCameraMM3dBind) - self.rotateSelected: ControlItem = ControlItem(self.settings.rotateSelected3dBind) - self.moveXYSelected: ControlItem = ControlItem(self.settings.moveSelectedXY3dBind) - self.moveZSelected: ControlItem = ControlItem(self.settings.moveSelectedZ3dBind) - self.selectUnderneath: ControlItem = ControlItem(self.settings.selectObject3dBind) - self.moveCameraToSelected: ControlItem = ControlItem(self.settings.moveCameraToSelected3dBind) - self.moveCameraToCursor: ControlItem = ControlItem(self.settings.moveCameraToCursor3dBind) - self.moveCameraToEntryPoint: ControlItem = ControlItem(self.settings.moveCameraToEntryPoint3dBind) - self.toggleFreeCam: ControlItem = ControlItem(self.settings.toggleFreeCam3dBind) - self.deleteSelected: ControlItem = ControlItem(self.settings.deleteObject3dBind) - self.duplicateSelected: ControlItem = ControlItem(self.settings.duplicateObject3dBind) - self.openContextMenu: ControlItem = ControlItem((set(), {int(QtMouse.RightButton)})) - self.rotateCameraLeft: ControlItem = ControlItem(self.settings.rotateCameraLeft3dBind) - self.rotateCameraRight: ControlItem = ControlItem(self.settings.rotateCameraRight3dBind) - self.rotateCameraUp: ControlItem = ControlItem(self.settings.rotateCameraUp3dBind) - self.rotateCameraDown: ControlItem = ControlItem(self.settings.rotateCameraDown3dBind) - self.moveCameraUp: ControlItem = ControlItem(self.settings.moveCameraUp3dBind) - self.moveCameraDown: ControlItem = ControlItem(self.settings.moveCameraDown3dBind) - self.moveCameraForward: ControlItem = ControlItem(self.settings.moveCameraForward3dBind) - self.moveCameraBackward: ControlItem = ControlItem(self.settings.moveCameraBackward3dBind) - self.moveCameraLeft: ControlItem = ControlItem(self.settings.moveCameraLeft3dBind) - self.moveCameraRight: ControlItem = ControlItem(self.settings.moveCameraRight3dBind) - self.zoomCameraIn: ControlItem = ControlItem(self.settings.zoomCameraIn3dBind) - self.zoomCameraOut: ControlItem = ControlItem(self.settings.zoomCameraOut3dBind) - self.toggleInstanceLock: ControlItem = ControlItem(self.settings.toggleLockInstancesBind) - self.renderer.freeCam = False - self.renderer.setCursor(QtCore.Qt.CursorShape.ArrowCursor) + @property + def moveXYCamera(self) -> ControlItem: + return ControlItem(self.settings.moveCameraXY3dBind) + + @moveXYCamera.setter + def moveXYCamera(self, value): + pass + + @property + def moveZCamera(self) -> ControlItem: + return ControlItem(self.settings.moveCameraZ3dBind) + + @moveZCamera.setter + def moveZCamera(self, value): + pass + + @property + def moveCameraPlane(self) -> ControlItem: + return ControlItem(self.settings.moveCameraPlane3dBind) + + @moveCameraPlane.setter + def moveCameraPlane(self, value): + pass + + @property + def rotateCamera(self) -> ControlItem: + return ControlItem(self.settings.rotateCamera3dBind) + + @rotateCamera.setter + def rotateCamera(self, value): + pass + + @property + def zoomCamera(self) -> ControlItem: + return ControlItem(self.settings.zoomCamera3dBind) + + @zoomCamera.setter + def zoomCamera(self, value): + pass + + @property + def zoomCameraMM(self) -> ControlItem: + return ControlItem(self.settings.zoomCameraMM3dBind) + + @zoomCameraMM.setter + def zoomCameraMM(self, value): + pass + + @property + def rotateSelected(self) -> ControlItem: + return ControlItem(self.settings.rotateSelected3dBind) + + @rotateSelected.setter + def rotateSelected(self, value): + pass + + @property + def moveXYSelected(self) -> ControlItem: + return ControlItem(self.settings.moveSelectedXY3dBind) + + @moveXYSelected.setter + def moveXYSelected(self, value): + pass + + @property + def moveZSelected(self) -> ControlItem: + return ControlItem(self.settings.moveSelectedZ3dBind) + + @moveZSelected.setter + def moveZSelected(self, value): + pass + + @property + def selectUnderneath(self) -> ControlItem: + return ControlItem(self.settings.selectObject3dBind) + + @selectUnderneath.setter + def selectUnderneath(self, value): + pass + + @property + def moveCameraToSelected(self) -> ControlItem: + return ControlItem(self.settings.moveCameraToSelected3dBind) + + @moveCameraToSelected.setter + def moveCameraToSelected(self, value): + pass + + @property + def moveCameraToCursor(self) -> ControlItem: + return ControlItem(self.settings.moveCameraToCursor3dBind) + + @moveCameraToCursor.setter + def moveCameraToCursor(self, value): + pass + + @property + def moveCameraToEntryPoint(self) -> ControlItem: + return ControlItem(self.settings.moveCameraToEntryPoint3dBind) + + @moveCameraToEntryPoint.setter + def moveCameraToEntryPoint(self, value): + pass + + @property + def toggleFreeCam(self) -> ControlItem: + return ControlItem(self.settings.toggleFreeCam3dBind) + + @toggleFreeCam.setter + def toggleFreeCam(self, value): + pass + + @property + def deleteSelected(self) -> ControlItem: + return ControlItem(self.settings.deleteObject3dBind) + + @deleteSelected.setter + def deleteSelected(self, value): + pass + + @property + def duplicateSelected(self) -> ControlItem: + return ControlItem(self.settings.duplicateObject3dBind) + + @duplicateSelected.setter + def duplicateSelected(self, value): + pass + + @property + def openContextMenu(self) -> ControlItem: + return ControlItem((set(), {int(QtMouse.RightButton)})) + + @openContextMenu.setter + def openContextMenu(self, value): + pass + + @property + def rotateCameraLeft(self) -> ControlItem: + return ControlItem(self.settings.rotateCameraLeft3dBind) + + @rotateCameraLeft.setter + def rotateCameraLeft(self, value): + pass + + @property + def rotateCameraRight(self) -> ControlItem: + return ControlItem(self.settings.rotateCameraRight3dBind) + + @rotateCameraRight.setter + def rotateCameraRight(self, value): + pass + + @property + def rotateCameraUp(self) -> ControlItem: + return ControlItem(self.settings.rotateCameraUp3dBind) + + @rotateCameraUp.setter + def rotateCameraUp(self, value): + pass + + @property + def rotateCameraDown(self) -> ControlItem: + return ControlItem(self.settings.rotateCameraDown3dBind) + + @rotateCameraDown.setter + def rotateCameraDown(self, value): + pass + + @property + def moveCameraUp(self) -> ControlItem: + return ControlItem(self.settings.moveCameraUp3dBind) + + @moveCameraUp.setter + def moveCameraUp(self, value): + pass + + @property + def moveCameraDown(self) -> ControlItem: + return ControlItem(self.settings.moveCameraDown3dBind) + + @moveCameraDown.setter + def moveCameraDown(self, value): + pass + + @property + def moveCameraForward(self) -> ControlItem: + return ControlItem(self.settings.moveCameraForward3dBind) + + @moveCameraForward.setter + def moveCameraForward(self, value): + pass + + @property + def moveCameraBackward(self) -> ControlItem: + return ControlItem(self.settings.moveCameraBackward3dBind) + + @moveCameraBackward.setter + def moveCameraBackward(self, value): + pass + + @property + def moveCameraLeft(self) -> ControlItem: + return ControlItem(self.settings.moveCameraLeft3dBind) + + @moveCameraLeft.setter + def moveCameraLeft(self, value): + pass + + @property + def moveCameraRight(self) -> ControlItem: + return ControlItem(self.settings.moveCameraRight3dBind) + + @moveCameraRight.setter + def moveCameraRight(self, value): + pass + + @property + def zoomCameraIn(self) -> ControlItem: + return ControlItem(self.settings.zoomCameraIn3dBind) + + @zoomCameraIn.setter + def zoomCameraIn(self, value): + pass + + @property + def zoomCameraOut(self) -> ControlItem: + return ControlItem(self.settings.zoomCameraOut3dBind) + + @zoomCameraOut.setter + def zoomCameraOut(self, value): + pass + + @property + def toggleInstanceLock(self) -> ControlItem: + return ControlItem(self.settings.toggleLockInstancesBind) + + @toggleInstanceLock.setter + def toggleInstanceLock(self, value): + pass def onMouseScrolled(self, delta: Vector2, buttons: set[int], keys: set[int]): if self.zoomCamera.satisfied(buttons, keys): @@ -1332,29 +1541,33 @@ def onMouseMoved( keys (set[int]): Pressed keyboard keys """ # Handle movement of View - if self.moveXYCamera.satisfied(buttons, keys): - forward = -screenDelta.y * self.renderer.scene.camera.forward() - sideward = screenDelta.x * self.renderer.scene.camera.sideward() - strength = self.settings.moveCameraSensitivity3d / 1000 - self.renderer.scene.camera.x -= (forward.x + sideward.x) * strength - self.renderer.scene.camera.y -= (forward.y + sideward.y) * strength - - if self.moveCameraPlane.satisfied(buttons, keys): # sourcery skip: extract-method - upward = screenDelta.y * self.renderer.scene.camera.upward(ignore_xy=False) - sideward = screenDelta.x * self.renderer.scene.camera.sideward() - strength = self.settings.moveCameraSensitivity3d / 1000 - self.renderer.scene.camera.z -= (upward.z + sideward.z) * strength - self.renderer.scene.camera.y -= (upward.y + sideward.y) * strength - self.renderer.scene.camera.x -= (upward.x + sideward.x) * strength - - if self.rotateCamera.satisfied(buttons, keys): - strength = self.settings.moveCameraSensitivity3d / 10000 - self.renderer.rotateCamera(-screenDelta.x * strength, screenDelta.y * strength) - return # save users from motion sickness: don't process other commands during view rotations. - - if self.zoomCameraMM.satisfied(buttons, keys): - strength = self.settings.zoomCameraSensitivity3d / 10000 - self.renderer.scene.camera.distance -= screenDelta.y * strength + moveXyCameraSatisfied = self.moveXYCamera.satisfied(buttons, keys) + moveCameraPlaneSatisfied = self.moveCameraPlane.satisfied(buttons, keys) + rotateCameraSatisfied = self.rotateCamera.satisfied(buttons, keys) + zoomCameraSatisfied = self.zoomCameraMM.satisfied(buttons, keys) + if moveXyCameraSatisfied or moveCameraPlaneSatisfied or rotateCameraSatisfied or zoomCameraSatisfied: + if moveXyCameraSatisfied: + forward = -screenDelta.y * self.renderer.scene.camera.forward() + sideward = screenDelta.x * self.renderer.scene.camera.sideward() + strength = self.settings.moveCameraSensitivity3d / 1000 + self.renderer.scene.camera.x -= (forward.x + sideward.x) * strength + self.renderer.scene.camera.y -= (forward.y + sideward.y) * strength + if moveCameraPlaneSatisfied: # sourcery skip: extract-method + upward = screenDelta.y * self.renderer.scene.camera.upward(ignore_xy=False) + sideward = screenDelta.x * self.renderer.scene.camera.sideward() + strength = self.settings.moveCameraSensitivity3d / 1000 + self.renderer.scene.camera.z -= (upward.z + sideward.z) * strength + self.renderer.scene.camera.y -= (upward.y + sideward.y) * strength + self.renderer.scene.camera.x -= (upward.x + sideward.x) * strength + if rotateCameraSatisfied: + strength = self.settings.moveCameraSensitivity3d / 10000 + self.renderer.rotateCamera(-screenDelta.x * strength, screenDelta.y * strength) + return # save users from motion sickness: don't process other commands during view rotations. + if zoomCameraSatisfied: + strength = self.settings.zoomCameraSensitivity3d / 10000 + self.renderer.scene.camera.distance -= screenDelta.y * strength + + return # Don't process instance commands while moving the camera. # Handle movement of selected instances. if self.moveXYSelected.satisfied(buttons, keys): @@ -1455,8 +1668,10 @@ def onKeyboardPressed(self, buttons: set[int], keys: set[int]): - Zooms camera - Toggles instance locking. """ - if self.toggleFreeCam.satisfied(buttons, keys): + current_time = time.time() + if self.toggleFreeCam.satisfied(buttons, keys) and (current_time - self.editor.last_free_cam_time > 0.5): # 0.5 seconds delay self.editor.toggleFreeCam() + self.editor.last_free_cam_time = current_time # Update the last toggle time if self.moveCameraToSelected.satisfied(buttons, keys): for instance in self.editor.selectedInstances: @@ -1474,7 +1689,7 @@ def onKeyboardPressed(self, buttons: set[int], keys: set[int]): if self.deleteSelected.satisfied(buttons, keys): self.editor.deleteSelected() - if self.rotateCameraLeft.satisfied(buttons, keys): + if self.rotateCameraLeft.satisfied(buttons, keys): # TODO(th3w1zard1): rotate sensitivity calcs. RobustRootLogger().debug("rotateCameraLeft") self.renderer.rotateCamera(math.pi / 4, 0) if self.rotateCameraRight.satisfied(buttons, keys): @@ -1531,66 +1746,109 @@ def __init__(self, editor: ModuleDesigner, renderer: ModuleRenderer): self.editor: ModuleDesigner = editor self.settings: ModuleDesignerSettings = ModuleDesignerSettings() self.renderer: ModuleRenderer = renderer + self.renderer._keysDown.clear() + self.renderer.setCursor(QtCore.Qt.CursorShape.BlankCursor) + self.last_mouse_pos = QCursor.pos() + self.renderer.scene.show_cursor = False - self.toggleFreeCam: ControlItem = ControlItem(self.settings.toggleFreeCam3dBind) - self.moveCameraUp: ControlItem = ControlItem(self.settings.moveCameraUpFcBind) - self.moveCameraDown: ControlItem = ControlItem(self.settings.moveCameraDownFcBind) - self.moveCameraForward: ControlItem = ControlItem(self.settings.moveCameraForwardFcBind) - self.moveCameraBackward: ControlItem = ControlItem(self.settings.moveCameraBackwardFcBind) - self.moveCameraLeft: ControlItem = ControlItem(self.settings.moveCameraLeftFcBind) - self.moveCameraRight: ControlItem = ControlItem(self.settings.moveCameraRightFcBind) - self.renderer.freeCam = True - self.renderer.setCursor(QtCore.Qt.CursorShape.BlankCursor) - self.renderer._keysDown.clear() + @property + def toggleFreeCam(self) -> ControlItem: + return ControlItem(self.settings.toggleFreeCam3dBind) + + @toggleFreeCam.setter + def toggleFreeCam(self, value): + pass + + @property + def moveCameraUp(self) -> ControlItem: + return ControlItem(self.settings.moveCameraUpFcBind) + + @moveCameraUp.setter + def moveCameraUp(self, value): + pass + + @property + def moveCameraDown(self) -> ControlItem: + return ControlItem(self.settings.moveCameraDownFcBind) - rendererPos = self.renderer.mapToGlobal(self.renderer.pos()) - mouseX = rendererPos.x() + self.renderer.width() // 2 - mouseY = rendererPos.y() + self.renderer.height() // 2 - self.renderer.cursor().setPos(mouseX, mouseY) + @moveCameraDown.setter + def moveCameraDown(self, value): + pass + + @property + def moveCameraForward(self) -> ControlItem: + return ControlItem(self.settings.moveCameraForwardFcBind) + + @moveCameraForward.setter + def moveCameraForward(self, value): + pass + + @property + def moveCameraBackward(self) -> ControlItem: + return ControlItem(self.settings.moveCameraBackwardFcBind) + + @moveCameraBackward.setter + def moveCameraBackward(self, value): + pass + + @property + def moveCameraLeft(self) -> ControlItem: + return ControlItem(self.settings.moveCameraLeftFcBind) + + @moveCameraLeft.setter + def moveCameraLeft(self, value): + pass + + @property + def moveCameraRight(self) -> ControlItem: + return ControlItem(self.settings.moveCameraRightFcBind) + + @moveCameraRight.setter + def moveCameraRight(self, value): + pass def onMouseScrolled(self, delta: Vector2, buttons: set[int], keys: set[int]): ... def onMouseMoved(self, screen: Vector2, screenDelta: Vector2, world: Vector3, buttons: set[int], keys: set[int]): - if self.renderer._scene and self.renderer.scene.show_cursor: # HACK(th3w1zard1): fix later. - self.renderer.scene.show_cursor = False - rendererPos = self.renderer.mapToGlobal(self.renderer.pos()) - mouseX: int = rendererPos.x() + self.renderer.width() // 2 - mouseY: int = rendererPos.y() + self.renderer.height() // 2 - strength: float = self.settings.rotateCameraSensitivityFC / 10000 + current_mouse_pos = QCursor.pos() + screen_center = self.renderer.mapToGlobal(self.renderer.rect().center()) + + # Calculate the mouse delta before resetting the cursor + mouse_delta = current_mouse_pos - self.last_mouse_pos - print ("onMouseMoved, next call is rotateCamera.") - self.renderer.rotateCamera(-screenDelta.x * strength, screenDelta.y * strength, snapRotations=False) - self.renderer.cursor().setPos(mouseX, mouseY) + # Reset the cursor to the center of the screen to prevent it from going off screen + QCursor.setPos(screen_center) + self.last_mouse_pos = screen_center # Immediately update last_mouse_pos to the center + + # Calculate rotation strength based on sensitivity settings + strength = self.settings.rotateCameraSensitivityFC / 10000 + # Apply the camera rotation using the calculated mouse delta + self.renderer.rotateCamera(-mouse_delta.x() * strength, mouse_delta.y() * strength, snapRotations=False) def onMousePressed(self, screen: Vector2, buttons: set[int], keys: set[int]): - print("onMousePressed2d") + ... def onMouseReleased(self, screen: Vector2, buttons: set[int], keys: set[int]): ... def onKeyboardPressed(self, buttons: set[int], keys: set[int]): - RobustRootLogger().debug(f"onKeyboardPressed, buttons: {buttons}, keys: {keys}") - if self.toggleFreeCam.satisfied(buttons, keys): + current_time = time.time() + if self.toggleFreeCam.satisfied(buttons, keys) and (current_time - self.editor.last_free_cam_time > 2): # 2 seconds delay self.editor.toggleFreeCam() + self.editor.last_free_cam_time = current_time # Update the last toggle time strength = self.settings.flyCameraSpeedFC / 100 if self.moveCameraUp.satisfied(buttons, keys, exactKeys=False): - RobustRootLogger().debug(f"moveCameraUp satisfied in onKeyboardPressed (strength {strength})") self.renderer.moveCamera(0, 0, strength) if self.moveCameraDown.satisfied(buttons, keys, exactKeys=False): - RobustRootLogger().debug(f"moveCameraDown satisfied in onKeyboardPressed (strength {strength})") self.renderer.moveCamera(0, 0, -strength) if self.moveCameraLeft.satisfied(buttons, keys, exactKeys=False): - RobustRootLogger().debug(f"moveCameraLeft satisfied in onKeyboardPressed (strength {strength})") self.renderer.moveCamera(0, -strength, 0) if self.moveCameraRight.satisfied(buttons, keys, exactKeys=False): - RobustRootLogger().debug(f"moveCameraRight satisfied in onKeyboardPressed (strength {strength})") self.renderer.moveCamera(0, strength, 0) if self.moveCameraForward.satisfied(buttons, keys, exactKeys=False): - RobustRootLogger().debug(f"moveCameraForward satisfied in onKeyboardPressed (strength {strength})") self.renderer.moveCamera(strength, 0, 0) if self.moveCameraBackward.satisfied(buttons, keys, exactKeys=False): - RobustRootLogger().debug(f"moveCameraBackward satisfied in onKeyboardPressed (strength {strength})") self.renderer.moveCamera(-strength, 0, 0) def onKeyboardReleased(self, buttons: set[int], keys: set[int]): ... @@ -1616,17 +1874,93 @@ def __init__(self, editor: ModuleDesigner, renderer: WalkmeshRenderer): self.settings: ModuleDesignerSettings = ModuleDesignerSettings() self._mode: _InstanceMode | _GeometryMode | _SpawnMode - self.moveCamera: ControlItem = ControlItem(self.settings.moveCamera2dBind) - self.rotateCamera: ControlItem = ControlItem(self.settings.rotateCamera2dBind) - self.zoomCamera: ControlItem = ControlItem(self.settings.zoomCamera2dBind) - self.rotateSelected: ControlItem = ControlItem(self.settings.rotateObject2dBind) - self.moveSelected: ControlItem = ControlItem(self.settings.moveObject2dBind) - self.selectUnderneath: ControlItem = ControlItem(self.settings.selectObject2dBind) - self.deleteSelected: ControlItem = ControlItem(self.settings.deleteObject2dBind) - self.duplicateSelected: ControlItem = ControlItem(self.settings.duplicateObject2dBind) - self.snapCameraToSelected: ControlItem = ControlItem(self.settings.moveCameraToSelected2dBind) - self.openContextMenu: ControlItem = ControlItem((set(), {QtMouse.RightButton})) - self.toggleInstanceLock: ControlItem = ControlItem(self.settings.toggleLockInstancesBind) + @property + def moveCamera(self): + return ControlItem(self.settings.moveCamera2dBind) + + @moveCamera.setter + def moveCamera(self, value): + pass + + @property + def rotateCamera(self): + return ControlItem(self.settings.rotateCamera2dBind) + + @rotateCamera.setter + def rotateCamera(self, value): + pass + + @property + def zoomCamera(self): + return ControlItem(self.settings.zoomCamera2dBind) + + @zoomCamera.setter + def zoomCamera(self, value): + pass + + @property + def rotateSelected(self): + return ControlItem(self.settings.rotateObject2dBind) + + @rotateSelected.setter + def rotateSelected(self, value): + pass + + @property + def moveSelected(self): + return ControlItem(self.settings.moveObject2dBind) + + @moveSelected.setter + def moveSelected(self, value): + pass + + @property + def selectUnderneath(self): + return ControlItem(self.settings.selectObject2dBind) + + @selectUnderneath.setter + def selectUnderneath(self, value): + pass + + @property + def deleteSelected(self): + return ControlItem(self.settings.deleteObject2dBind) + + @deleteSelected.setter + def deleteSelected(self, value): + pass + + @property + def duplicateSelected(self): + return ControlItem(self.settings.duplicateObject2dBind) + + @duplicateSelected.setter + def duplicateSelected(self, value): + pass + + @property + def snapCameraToSelected(self): + return ControlItem(self.settings.moveCameraToSelected2dBind) + + @snapCameraToSelected.setter + def snapCameraToSelected(self, value): + pass + + @property + def openContextMenu(self): + return ControlItem((set(), {QtMouse.RightButton})) + + @openContextMenu.setter + def openContextMenu(self, value): + pass + + @property + def toggleInstanceLock(self): + return ControlItem(self.settings.toggleLockInstancesBind) + + @toggleInstanceLock.setter + def toggleInstanceLock(self, value): + pass def onMouseScrolled(self, delta: Vector2, buttons: set[int], keys: set[int]): """Scrolls camera zoom on mouse scroll. @@ -1645,12 +1979,12 @@ def onMouseScrolled(self, delta: Vector2, buttons: set[int], keys: set[int]): """ self.editor.log.debug("onMouseScrolled, delta: %s, buttons: %s, keys: %s", delta, buttons, keys) if self.zoomCamera.satisfied(buttons, keys): - strength = self.editor.settings.zoomCameraSensitivity2d / 100 / 50 - zoomInFactor = 1.1 + strength - zoomOutFactor = 0.90 - strength - - zoomFactor = zoomInFactor if delta.y > 0 else zoomOutFactor - self.renderer.camera.nudgeZoom(zoomFactor) + if not delta.y: + return # sometimes it'll be zero when holding middlemouse-down. + sensSetting = ModuleDesignerSettings().zoomCameraSensitivity2d + zoom_factor = calculate_zoom_strength(delta.y, sensSetting) + RobustRootLogger.debug(f"onMouseScrolled zoomCamera (delta.y={delta.y}, zoom_factor={zoom_factor}, sensSetting={sensSetting}))") + self.renderer.camera.nudgeZoom(zoom_factor) def onMouseMoved(self, screen: Vector2, screenDelta: Vector2, world: Vector2, worldDelta: Vector2, buttons: set[int], keys: set[int]): """Handles mouse movement events in the editor. @@ -1672,41 +2006,33 @@ def onMouseMoved(self, screen: Vector2, screenDelta: Vector2, world: Vector2, wo - Rotates selected instances around world position if rotate selected key is held. """ #self.editor.log.debug("onMouseMoved, screen: %s, screenDelta: %s, world: %s, worldDelta: %s, buttons: %s, keys: %s", screen, screenDelta, world, worldDelta, buttons, keys) - if self.moveCamera.satisfied(buttons, keys): - strength = self.settings.moveCameraSensitivity2d / 100 - self.renderer.camera.nudgePosition(-worldDelta.x * strength, -worldDelta.y * strength) - - if self.rotateCamera.satisfied(buttons, keys): - strength = self.settings.rotateCameraSensitivity2d / 100 / 50 - self.renderer.camera.nudgeRotation(screenDelta.x * strength) + shouldMoveCamera = self.moveCamera.satisfied(buttons, keys) + shouldRotateCamera = self.rotateCamera.satisfied(buttons, keys) + if shouldMoveCamera or shouldRotateCamera: + if shouldMoveCamera: + strength = self.settings.moveCameraSensitivity2d / 100 + self.renderer.camera.nudgePosition(-worldDelta.x * strength, -worldDelta.y * strength) + + if shouldRotateCamera: + strength = self.settings.rotateCameraSensitivity2d / 100 / 50 + self.renderer.camera.nudgeRotation(screenDelta.x * strength) + return if self.moveSelected.satisfied(buttons, keys): if isinstance(self._mode, _GeometryMode): - RobustRootLogger().debug("Move geometry point? %s, %s", worldDelta.x, worldDelta.y) + RobustRootLogger().debug("Move geometry point %s, %s", worldDelta.x, worldDelta.y) self._mode.moveSelected(worldDelta.x, worldDelta.y) return + # handle undo/redo for moveSelected. if not self.editor.isDragMoving: RobustRootLogger().debug("moveSelected instance in 2d") self.editor.initialPositions = {instance: instance.position for instance in self.editor.selectedInstances} self.editor.isDragMoving = True self.editor.moveSelected(worldDelta.x, worldDelta.y, noUndoStack=True, noZCoord=True) - if self.rotateSelected.satisfied(buttons, keys): - for instance in self.editor.selectedInstances: - old_yaw = instance.yaw() - if old_yaw is None: - print(instance.resref, "does not support rotating yaw") - continue - if not self.editor.isDragRotating: - RobustRootLogger().debug("rotateSelected instance in 2d") - if not isinstance(instance, (GITCamera, GITCreature, GITDoor, GITPlaceable, GITStore, GITWaypoint)): - continue # doesn't support rotations. - self.editor.initialRotations[instance] = instance.orientation if isinstance(instance, GITCamera) else instance.bearing - - rotation: float = -math.atan2(world.x - instance.position.x, world.y - instance.position.y) - new_yaw = old_yaw - rotation if isinstance(instance, GITCamera) else -old_yaw + rotation - instance.rotate(new_yaw, 0, 0) + if self.rotateSelected.satisfied(buttons, keys) and isinstance(self._mode, _InstanceMode): + self._mode.rotateSelectedToPoint(world.x, world.y) if not self.editor.isDragRotating: print("2d rotate set isDragRotating") self.editor.isDragRotating = True @@ -1732,10 +2058,10 @@ def onMousePressed(self, screen: Vector2, buttons: set[int], keys: set[int]): RobustRootLogger().debug("selectUnderneathGeometry?") self._mode.selectUnderneath() elif self.renderer.instancesUnderMouse(): - RobustRootLogger().debug("onMousePressed, selectUnderneath FOUND INSTANCES") + RobustRootLogger().debug("onMousePressed, selectUnderneath found one or more instances under mouse.") self.editor.setSelection([self.renderer.instancesUnderMouse()[-1]]) else: - RobustRootLogger().debug("onMousePressed, selectUnderneath DID NOT FIND INSTANCES!") + RobustRootLogger().debug("onMousePressed, selectUnderneath did not find any instances.") self.editor.setSelection([]) if self.duplicateSelected.satisfied(buttons, keys) and self.editor.selectedInstances: