Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
21bc549
[rust/rqd] Add OOM prevention logic with kill frame selection (#2064)
DiegoTavares Oct 27, 2025
0e758f4
[rust/rqd] Version Up rust/rqd (#2066)
DiegoTavares Oct 27, 2025
34f4fd8
[rust/rqd] Log OOM kill on rqd (#2067)
DiegoTavares Oct 27, 2025
271e69a
[cuenimby] Add Enhanced CueNimby System Tray with OpenCue Icons, Emoj…
KernAttila Oct 29, 2025
c879f4e
[cuenimby] Fix working icon description in documentation (#2070)
ramonfigueiredo Oct 29, 2025
016ea06
Bump next-auth from 4.24.10 to 4.24.12 in /cueweb (#2068)
dependabot[bot] Oct 30, 2025
22c9cd9
Fix PyOutline package name typo in installation docs (#2071)
ddlite92 Nov 2, 2025
fe9610b
[cuebot] Update Dockerfile to use openjdk:18-ea-18-slim-bullseye imag…
lithorus Nov 6, 2025
a5827b8
[pycue] Remove six and unpin PyYaml (#2072)
anton-ubi Nov 7, 2025
1705100
[cuebot] DB optional env vars to streamline custom deployment (#2076)
anton-ubi Nov 13, 2025
90d2d17
[cuegui] Add error handling for gRPC connection failures in FrameMoni…
ramonfigueiredo Nov 18, 2025
648191d
Bump glob from 10.4.5 to 10.5.0 in /cueweb (#2080)
dependabot[bot] Nov 19, 2025
b5959bb
Bump js-yaml in /cueweb (#2081)
dependabot[bot] Nov 24, 2025
b00240b
[pyoutline] Layer module cleanup (#2073)
anton-ubi Nov 24, 2025
bc8a320
Initial plan
Copilot Nov 27, 2025
979e59a
Update OpenCue UI and backend improvements
ddlite92 Nov 22, 2025
b8f5870
feat(Rust-RQD-for-Linux): Add Rust RQD build infrastructure and docum…
ddlite92 Nov 25, 2025
ebcda45
Add cuesubmit/plugins/blender directory and Blender OpenCue integrati…
ddlite92 Nov 25, 2025
1457bad
fix: update FrameMonitor.py with gRPC error handling from upstream
ddlite92 Nov 26, 2025
8ec6cbe
feat: static versions and deployment packages for windows deployment
ddlite92 Nov 27, 2025
6c87cc8
Add Windows deployment documentation
Copilot Nov 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ci/run_python_lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ python -m pylint --rcfile=../ci/pylintrc_main cuesubmit --disable=no-member
python -m pylint --rcfile=../ci/pylintrc_test tests --disable=no-member
cd ..

echo "Running lint for cuenimby/..."
pip install ./cuenimby[test] ${PIP_OPT}
cd cuenimby
python -m pylint --rcfile=../ci/pylintrc_main cuenimby
python -m pylint --rcfile=../ci/pylintrc_test tests
cd ..

echo "Running lint for rqd/..."
pip install ./rqd[test] ${PIP_OPT}
cd rqd
Expand Down
2 changes: 1 addition & 1 deletion cuebot/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ RUN chmod +x create_rpm.sh && ./create_rpm.sh cuebot "$(cat VERSION)"
# -----------------
# RUN
# -----------------
FROM openjdk:18-slim-bullseye
FROM openjdk:18-ea-18-slim-bullseye

# Install curl for healthchecking
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
Expand Down
6 changes: 3 additions & 3 deletions cuebot/src/main/resources/opencue.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ cue.proxy = tcp -h cuetest01-vm -p 9019 -t 10000:tcp -h cuetest02-vm -p 9019 -t
spring.velocity.checkTemplateLocation=false

datasource.cue-data-source.driver-class-name=org.postgresql.Driver
datasource.cue-data-source.jdbc-url=jdbc:postgresql://dbhost/dbname
datasource.cue-data-source.username=cue
datasource.cue-data-source.password=password
datasource.cue-data-source.jdbc-url=${CUEBOT_DB_URL:"jdbc:postgresql://dbhost/dbname"}
datasource.cue-data-source.username=${CUEBOT_DB_USER:cue}
datasource.cue-data-source.password=${CUEBOT_DB_PASSWORD:password}

# HikariCP Configuration
# Maximum lifetime of a connection in milliseconds (5 hours = 18000000 ms)
Expand Down
26 changes: 20 additions & 6 deletions cuegui/cuegui/AbstractTreeWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ def __init__(self, parent):

self.setItemDelegate(cuegui.ItemDelegate.ItemDelegate(self))

self.__setupColumns()
# Only setup columns if they have been defined by the subclass
if hasattr(self, '_AbstractTreeWidget__columnInfoByType'):
self.__setupColumns()

self.__setupColumnMenu()

Expand Down Expand Up @@ -172,6 +174,13 @@ def addColumn(self, name, width, id=0, default=True,
columnsInfo = self.__columnInfoByType[self.__columnCurrent]
columnsInfo.append([name, width, data, sort, delegate, tip, default, id])

def _finalizeColumns(self):
"""Call this after all columns have been added to set up the tree widget.
This is automatically called by __init__ if columns were set up before,
but subclasses that set up columns after calling super().__init__ must
call this manually."""
self.__setupColumns()

def __setupColumns(self):
"""Setup the QTreeWidget based on the column information"""
primaryColumnInfo = self.__columnInfoByType[self.__columnPrimaryType]
Expand Down Expand Up @@ -334,11 +343,16 @@ def _removeItem(self, item):
"""Removes an item from the TreeWidget without locking
@type item: AbstractTreeWidgetItem or String
@param item: A tree widget item or the string with the id of the item"""
if item in self._items:
item = self._items[item]
elif not isinstance(item, cuegui.AbstractWidgetItem.AbstractWidgetItem):
# if the parent was already deleted, then this one was too
return
# Avoid hashing a QTreeWidgetItem (may be unhashable if __eq__ defined).
# First branch: string key. Second branch: already an item instance.
if isinstance(item, cuegui.AbstractWidgetItem.AbstractWidgetItem):
pass # already have the item instance
else:
if item in self._items:
item = self._items[item]
else:
# if the parent was already deleted, then this one was too
return

# If it has children, they must be deleted first
if item.childCount() > 0:
Expand Down
18 changes: 15 additions & 3 deletions cuegui/cuegui/Constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,29 @@ def __loadConfigFromFile():


def __packaged_version():
"""Return a version string from VERSION.in or package metadata.

Prefers the repository root VERSION.in. If not present (e.g. installed
wheel lacking the file), falls back to importlib.metadata version info.
"""
version_file_path = os.path.join(
os.path.abspath(os.path.join(__file__, "../../..")), 'VERSION.in')
try:
with open(version_file_path, encoding='utf-8') as fp:
version = fp.read().strip()
return version
if version:
return version
except FileNotFoundError:
print(f"VERSION.in not found at: {version_file_path}")
except Exception as e:
except Exception as e: # pragma: no cover - defensive
print(f"An unexpected error occurred while reading VERSION.in: {e}")
return None

# Fallback to package metadata
try:
from importlib.metadata import version as _pkg_version
return _pkg_version('cuegui')
except Exception:
return None


def __get_version_from_cmd(command):
Expand Down
9 changes: 7 additions & 2 deletions cuegui/cuegui/CueJobMonitorTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from builtins import str
from builtins import map
from collections import namedtuple
import sys
import time

from qtpy import QtCore
Expand Down Expand Up @@ -71,6 +72,7 @@ class CueJobMonitorTree(cuegui.AbstractTreeWidget.AbstractTreeWidget):
single_click = QtCore.Signal(object)

def __init__(self, parent):
cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)

self.__shows = {}
self.currtime = time.time()
Expand Down Expand Up @@ -213,7 +215,7 @@ def __init__(self, parent):
group.data.department or ""))
self.addColumn("", 0, id=21)

cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)
self._finalizeColumns()

self.setAnimated(False)
self.setAcceptDrops(True)
Expand Down Expand Up @@ -247,8 +249,11 @@ def __itemSingleClickedCopy(self, item, col):
del col
selected = [job.data.name for job in self.selectedObjects() if cuegui.Utils.isJob(job)]
if selected:
mode = QtGui.QClipboard.Selection
if sys.platform.startswith('win'):
mode = QtGui.QClipboard.Clipboard
QtWidgets.QApplication.clipboard().setText(
" ".join(selected), QtGui.QClipboard.Selection)
" ".join(selected), mode)

def __itemSingleClickedComment(self, item, col):
"""If the comment column is clicked on, and there is a comment on the
Expand Down
4 changes: 2 additions & 2 deletions cuegui/cuegui/DependMonitorTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class DependMonitorTree(cuegui.AbstractTreeWidget.AbstractTreeWidget):
"""Tree for displaying a list of depends."""

def __init__(self, parent, rpcObject):
cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)
self.startColumnsForType(cuegui.Constants.TYPE_DEPEND)
self.addColumn("Type", 130, id=1,
data=lambda depend: depend_pb2.DependType.Name(depend.type()))
Expand All @@ -55,11 +56,10 @@ def __init__(self, parent, rpcObject):
data=lambda depend: (depend.dependOnLayer()))
self.addColumn("OnFrame", 100, id=9,
data=lambda depend: (depend.dependOnFrame()))
self._finalizeColumns()

self.rpcObject = rpcObject

cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)

# Set columns to auto-resize to content
header = self.header()
for col in range(self.columnCount()):
Expand Down
12 changes: 6 additions & 6 deletions cuegui/cuegui/FilterDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class FilterMonitorTree(cuegui.AbstractTreeWidget.AbstractTreeWidget):
"""Tree displaying a list of filters."""

def __init__(self, show, parent):
cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)
self.startColumnsForType(cuegui.Constants.TYPE_FILTER)
self.addColumn("Order", 100, id=1,
data=lambda filter:(filter.data.order),
Expand All @@ -158,11 +159,10 @@ def __init__(self, show, parent):
self.addColumn("Filter Name", 270, id=3,
data=lambda filter:(filter.data.name))
self.addColumn("Type", 100, id=4)
self._finalizeColumns()

self.__show = show

cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)

self.hideColumn(0)
self.setSortingEnabled(False)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
Expand Down Expand Up @@ -214,6 +214,7 @@ class MatcherMonitorTree(cuegui.AbstractTreeWidget.AbstractTreeWidget):
"""Tree for displaying a list of filter matchers."""

def __init__(self, parent_filter, parent):
cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)
self.startColumnsForType(cuegui.Constants.TYPE_MATCHER)
self.addColumn("Matcher Subject", 130, id=1,
data=lambda matcher:(matcher.subject()))
Expand All @@ -222,11 +223,10 @@ def __init__(self, parent_filter, parent):
self.addColumn("Input", 130, id=3,
data=lambda matcher:(matcher.input()))
self.addColumn("", 20, id=4)
self._finalizeColumns()

self.__filter = parent_filter

cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)

# Used to build right click context menus
# pylint: disable=unused-private-member
self.__menuActions = cuegui.MenuActions.MenuActions(
Expand Down Expand Up @@ -373,6 +373,7 @@ class ActionMonitorTree(cuegui.AbstractTreeWidget.AbstractTreeWidget):
"""Tree for displaying a list of actions."""

def __init__(self, show, parent_filter, parent):
cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)
self.startColumnsForType(cuegui.Constants.TYPE_ACTION)
self.addColumn(
"Action Type",
Expand All @@ -381,12 +382,11 @@ def __init__(self, show, parent_filter, parent):
data=lambda action: (opencue_proto.filter_pb2.ActionType.Name(action.type())))
self.addColumn("", 180, id=2)
self.addColumn("", 20, id=3)
self._finalizeColumns()

self.__show = show
self.__filter = parent_filter

cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)

self.groupNames = {}
self.groupIds = {}
for group in show.getGroups():
Expand Down
78 changes: 52 additions & 26 deletions cuegui/cuegui/FrameMonitor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright Contributors to the OpenCue Project
# Copyright Contributors to the OpenCue Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,6 +27,7 @@
from qtpy import QtCore
from qtpy import QtGui
from qtpy import QtWidgets
import grpc

import FileSequence
from opencue_proto import job_pb2
Expand Down Expand Up @@ -131,31 +132,44 @@ def _frameRangeSelectionFilterUpdate(self):
if not self.frameMonitorTree.getJob():
self.frameRangeSelection.setFrameRange(["1", str(self.frameSearchLimit)])
else:
layers = self.frameMonitorTree.getJob().getLayers()

_min = None
_max = None

for layer in layers:
seq = FileSequence.FrameSet(layer.range())
seq.normalize()
frameList = seq.getAll()
if _min is not None:
_min = min(_min, int(frameList[0]))
else:
_min = int(frameList[0])

if _max is not None:
_max = max(_max, int(frameList[-1]))
try:
layers = self.frameMonitorTree.getJob().getLayers()

_min = None
_max = None

for layer in layers:
seq = FileSequence.FrameSet(layer.range())
seq.normalize()
frameList = seq.getAll()
if _min is not None:
_min = min(_min, int(frameList[0]))
else:
_min = int(frameList[0])

if _max is not None:
_max = max(_max, int(frameList[-1]))
else:
_max = int(frameList[-1])

if _min == _max:
_max += 1

self.frameRangeSelection.default_select_size = self.frameSearchLimit // len(layers)

self.frameRangeSelection.setFrameRange([str(_min), str(_max)])
except grpc.RpcError as e:
# Handle gRPC connection errors gracefully
# pylint: disable=no-member
if hasattr(e, 'code') and e.code() in [grpc.StatusCode.CANCELLED,
grpc.StatusCode.UNAVAILABLE]:
log.warning(
"gRPC connection interrupted while updating frame range filter, will retry")
else:
_max = int(frameList[-1])

if _min == _max:
_max += 1

self.frameRangeSelection.default_select_size = self.frameSearchLimit // len(layers)

self.frameRangeSelection.setFrameRange([str(_min), str(_max)])
log.error("gRPC error in _frameRangeSelectionFilterUpdate: %s", e)
# pylint: enable=no-member
# Set a default range if we can't get layers
self.frameRangeSelection.setFrameRange(["1", str(self.frameSearchLimit)])

def _frameRangeSelectionFilterHandle(self, start, end):
self.frameMonitorTree.frameSearch.options['range'] = "%s-%s" % (start, end)
Expand Down Expand Up @@ -354,7 +368,19 @@ def _filterLayersUpdate(self):
menu.triggered[QtWidgets.QAction].connect(self._filterLayersHandle) # pylint: disable=unsubscriptable-object

if self.frameMonitorTree.getJob():
layers = [x.data.name for x in self.frameMonitorTree.getJob().getLayers()]
try:
layers = [x.data.name for x in self.frameMonitorTree.getJob().getLayers()]
except grpc.RpcError as e:
# Handle gRPC connection errors gracefully
# pylint: disable=no-member
if hasattr(e, 'code') and e.code() in [grpc.StatusCode.CANCELLED,
grpc.StatusCode.UNAVAILABLE]:
log.warning(
"gRPC connection interrupted while updating layer filter, will retry")
else:
log.error("gRPC error in _filterLayersUpdate: %s", e)
# pylint: enable=no-member
layers = []
else:
layers = []

Expand Down
15 changes: 11 additions & 4 deletions cuegui/cuegui/FrameMonitorTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import glob
import os
import re
import sys
import time

from qtpy import QtCore
Expand Down Expand Up @@ -71,6 +72,8 @@ class FrameMonitorTree(cuegui.AbstractTreeWidget.AbstractTreeWidget):
handle_filter_layers_byLayer = QtCore.Signal(list)

def __init__(self, parent):
cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)

self.frameLogDataBuffer = FrameLogDataBuffer()
self.frameEtaDataBuffer = FrameEtaDataBuffer()

Expand Down Expand Up @@ -212,13 +215,13 @@ def getFrameStateOverride(frame):
job, frame)[FrameLogDataBuffer.LASTLINE] or ""),
tip="The last line of a running frame's log file.")

self._finalizeColumns()

self.frameSearch = opencue.search.FrameSearch()

self.__job = None
self.__jobState = None

cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)

# Used to build right click context menus
# pylint: disable=unused-private-member
self.__menuActions = cuegui.MenuActions.MenuActions(
Expand Down Expand Up @@ -343,8 +346,10 @@ def __itemSingleClickedCopy(self, item, col):
selected = [
frame.data.name for frame in self.selectedObjects() if cuegui.Utils.isFrame(frame)]
if selected:
QtWidgets.QApplication.clipboard().setText(" ".join(selected),
QtGui.QClipboard.Selection)
mode = QtGui.QClipboard.Selection
if sys.platform.startswith('win'):
mode = QtGui.QClipboard.Clipboard
QtWidgets.QApplication.clipboard().setText(" ".join(selected), mode)

def __itemSingleClickedViewLog(self, item, col):
"""Called when an item is clicked on. Views the log file contents
Expand Down Expand Up @@ -991,6 +996,8 @@ def __init__(self, widget, filterSelectedLayersCallback, readonly=False):
self.__menuActions.frames().addAction(self, "previewAovs")
self.addSeparator()
self.__menuActions.frames().addAction(self, "retry").setEnabled(not readonly)
self.__menuActions.frames().addAction(self, "retryIndividualFrames").setEnabled(not readonly)
self.__menuActions.frames().addAction(self, "forceRerender") # Always enabled
self.__menuActions.frames().addAction(self, "eat").setEnabled(not readonly)
self.__menuActions.frames().addAction(self, "kill").setEnabled(not readonly)
self.__menuActions.frames().addAction(self, "eatandmarkdone").setEnabled(not readonly)
Expand Down
Loading
Loading