66
77from PySide6 import __version__ as PySideVersion
88from PySide6 import QtCore
9- from PySide6 .QtCore import Qt , QUrl , QJsonValue , qInstallMessageHandler , QtMsgType , QSettings
9+ from PySide6 .QtCore import QUrl , QJsonValue , qInstallMessageHandler , QtMsgType , QSettings
1010from PySide6 .QtGui import QIcon
1111from PySide6 .QtQml import QQmlDebuggingEnabler
1212from PySide6 .QtQuickControls2 import QQuickStyle
@@ -243,6 +243,13 @@ def __init__(self, inputArgs):
243243 meshroom .core .initNodes ()
244244 meshroom .core .initSubmitters ()
245245
246+ # Initialize the list of recent project files
247+ self ._recentProjectFiles = self ._getRecentProjectFilesFromSettings ()
248+ # Flag set to True if, for all the project files in the list, thumbnails have been retrieved when they
249+ # are available. If set to False, then all the paths in the list are accurate, but some thumbnails might
250+ # be retrievable
251+ self ._updatedRecentProjectFilesThumbnails = True
252+
246253 # QML engine setup
247254 qmlDir = os .path .join (pwd , "qml" )
248255 url = os .path .join (qmlDir , "main.qml" )
@@ -347,38 +354,92 @@ def reloadTemplateList(self):
347354 meshroom .core .initPipelines ()
348355 self .pipelineTemplateFilesChanged .emit ()
349356
350- def _recentProjectFiles (self ):
357+ def _retrieveThumbnailPath (self , filepath : str ) -> str :
358+ """
359+ Given the path of a project file, load this file and try to retrieve the path to its thumbnail, i.e. its
360+ first viewpoint image.
361+
362+ Args:
363+ filepath: the path of the project file to retrieve the thumbnail from
364+
365+ Returns:
366+ The path to the thumbnail if it could be found, an empty string otherwise
367+ """
368+ try :
369+ with open (filepath ) as file :
370+ fileData = json .load (file )
371+
372+ graphData = fileData .get ("graph" , {})
373+ for node in graphData .values ():
374+ if node .get ("nodeType" ) != "CameraInit" :
375+ continue
376+ if viewpoints := node .get ("inputs" , {}).get ("viewpoints" ):
377+ return viewpoints [0 ].get ("path" , "" )
378+
379+ except FileNotFoundError :
380+ logging .info ("File {} not found on disk." .format (filepath ))
381+ except (json .JSONDecodeError , UnicodeDecodeError ):
382+ logging .info ("Error while loading file {}." .format (filepath ))
383+ except KeyError as err :
384+ logging .info ("The following key does not exist: {}" .format (str (err )))
385+ except Exception as err :
386+ logging .info ("Exception: {}" .format (str (err )))
387+
388+ return ""
389+
390+ def _getRecentProjectFilesFromSettings (self ) -> list [dict [str , str ]]:
391+ """
392+ Read the list of recent project files from the QSettings, retrieve their filepath, and if it exists, their
393+ thumbnail.
394+
395+ Returns:
396+ The list containing dictionaries of the form {"path": "/path/to/project/file", "thumbnail":
397+ "/path/to/thumbnail"} based on the recent projects stored in the QSettings.
398+ """
351399 projects = []
352400 settings = QSettings ()
353401 settings .beginGroup ("RecentFiles" )
354402 size = settings .beginReadArray ("Projects" )
355403 for i in range (size ):
356404 settings .setArrayIndex (i )
357- p = settings .value ("filepath" )
358- thumbnail = ""
359- if p :
360- # get the first image path from the project
361- try :
362- with open (p ) as file :
363- file = json .load (file )
364- # find the first camerainit node
365- file = file ["graph" ]
366- for node in file :
367- if file [node ]["nodeType" ] == "CameraInit" and file [node ]["inputs" ].get ("viewpoints" ):
368- if len (file [node ]["inputs" ]["viewpoints" ]) > 0 :
369- thumbnail = file [node ]["inputs" ]["viewpoints" ][0 ]["path" ]
370- break
371- except FileNotFoundError :
372- pass
373- p = {"path" : p , "thumbnail" : thumbnail }
405+ path = settings .value ("filepath" )
406+ if path :
407+ p = {"path" : path , "thumbnail" : self ._retrieveThumbnailPath (path )}
374408 projects .append (p )
375409 settings .endArray ()
376410 settings .endGroup ()
377411 return projects
378412
413+ @Slot ()
414+ def updateRecentProjectFilesThumbnails (self ) -> None :
415+ """
416+ If there are thumbnails that may be retrievable (meaning the list of projects has been updated minimally),
417+ update the list of recent project files by reading the QSettings and retrieving the thumbnails if they are
418+ available.
419+ """
420+ if not self ._updatedRecentProjectFilesThumbnails :
421+ self ._updateRecentProjectFilesThumbnails ()
422+ self ._updatedRecentProjectFilesThumbnails = True
423+
424+ def _updateRecentProjectFilesThumbnails (self ) -> None :
425+ for project in self ._recentProjectFiles :
426+ path = project ["path" ]
427+ project ["thumbnail" ] = self ._retrieveThumbnailPath (path )
428+
379429 @Slot (str )
380430 @Slot (QUrl )
381- def addRecentProjectFile (self , projectFile ):
431+ def addRecentProjectFile (self , projectFile ) -> None :
432+ """
433+ Add a project file to the list of recent project files.
434+ The function ensures that the file is not present more than once in the list and trims it so it
435+ never exceeds a set number of projects.
436+ QSettings are updated accordingly.
437+ The update of the list of recent projects files is minimal: the filepath is added, but there is no
438+ attempt to retrieve its corresponding thumbnail.
439+
440+ Args:
441+ projectFile (str or QUrl): path to the project file to add to the list
442+ """
382443 if not isinstance (projectFile , (QUrl , str )):
383444 raise TypeError ("Unexpected data type: {}" .format (projectFile .__class__ ))
384445 if isinstance (projectFile , QUrl ):
@@ -390,37 +451,47 @@ def addRecentProjectFile(self, projectFile):
390451 if not projectFileNorm :
391452 projectFileNorm = QUrl .fromLocalFile (projectFile ).toLocalFile ()
392453
393- projects = self . _recentProjectFiles ()
394- projects = [ p [ "path" ] for p in projects ]
454+ # Get the list of recent projects without re-reading the QSettings
455+ projects = self . _recentProjectFiles
395456
396- # remove duplicates while preserving order
397- from collections import OrderedDict
398- uniqueProjects = OrderedDict .fromkeys (projects )
399- projects = list (uniqueProjects )
400- # remove previous usage of the value
401- if projectFileNorm in uniqueProjects :
402- projects .remove (projectFileNorm )
403- # add the new value in the first place
404- projects .insert (0 , projectFileNorm )
457+ # Checks whether the path is already in the list of recent projects
458+ filepaths = [p ["path" ] for p in projects ]
459+ if projectFileNorm in filepaths :
460+ idx = filepaths .index (projectFileNorm )
461+ del projects [idx ] # If so, delete its entry
462+
463+ # Insert the newest entry at the top of the list
464+ projects .insert (0 , {"path" : projectFileNorm , "thumbnail" : "" })
405465
406- # keep only the 40 first elements
407- projects = projects [0 :40 ]
466+ # Only keep the first 40 projects
467+ maxNbProjects = 40
468+ if len (projects ) > maxNbProjects :
469+ projects = projects [0 :maxNbProjects ]
408470
471+ # Update the general settings
409472 settings = QSettings ()
410473 settings .beginGroup ("RecentFiles" )
411474 settings .beginWriteArray ("Projects" )
412475 for i , p in enumerate (projects ):
413476 settings .setArrayIndex (i )
414- settings .setValue ("filepath" , p )
477+ settings .setValue ("filepath" , p [ "path" ] )
415478 settings .endArray ()
416479 settings .endGroup ()
417480 settings .sync ()
418481
482+ # Update the final list of recent projects
483+ self ._recentProjectFiles = projects
484+ self ._updatedRecentProjectFilesThumbnails = False # Thumbnails may not be up-to-date
419485 self .recentProjectFilesChanged .emit ()
420486
421487 @Slot (str )
422488 @Slot (QUrl )
423- def removeRecentProjectFile (self , projectFile ):
489+ def removeRecentProjectFile (self , projectFile ) -> None :
490+ """
491+ Remove a given project file from the list of recent project files.
492+ If the provided filepath is not already present in the list of recent project files, nothing is done.
493+ Otherwise, it is effectively removed and the QSettings are updated accordingly.
494+ """
424495 if not isinstance (projectFile , (QUrl , str )):
425496 raise TypeError ("Unexpected data type: {}" .format (projectFile .__class__ ))
426497 if isinstance (projectFile , QUrl ):
@@ -432,28 +503,30 @@ def removeRecentProjectFile(self, projectFile):
432503 if not projectFileNorm :
433504 projectFileNorm = QUrl .fromLocalFile (projectFile ).toLocalFile ()
434505
435- projects = self . _recentProjectFiles ()
436- projects = [ p [ "path" ] for p in projects ]
506+ # Get the list of recent projects without re-reading the QSettings
507+ projects = self . _recentProjectFiles
437508
438- # remove duplicates while preserving order
439- from collections import OrderedDict
440- uniqueProjects = OrderedDict .fromkeys (projects )
441- projects = list (uniqueProjects )
442- # remove previous usage of the value
443- if projectFileNorm not in uniqueProjects :
509+ # Ensure the filepath is in the list of recent projects
510+ filepaths = [p ["path" ] for p in projects ]
511+ if projectFileNorm not in filepaths :
444512 return
445513
446- projects .remove (projectFileNorm )
514+ # Delete it from the list
515+ idx = filepaths .index (projectFileNorm )
516+ del projects [idx ]
447517
518+ # Update the general settings
448519 settings = QSettings ()
449520 settings .beginGroup ("RecentFiles" )
450521 settings .beginWriteArray ("Projects" )
451522 for i , p in enumerate (projects ):
452523 settings .setArrayIndex (i )
453- settings .setValue ("filepath" , p )
524+ settings .setValue ("filepath" , p [ "path" ] )
454525 settings .endArray ()
455526 settings .sync ()
456527
528+ # Update the final list of recent projects
529+ self ._recentProjectFiles = projects
457530 self .recentProjectFilesChanged .emit ()
458531
459532 def _recentImportedImagesFolders (self ):
@@ -620,7 +693,7 @@ def _defaultSequencePlayerEnabled(self):
620693 recentImportedImagesFoldersChanged = Signal ()
621694 pipelineTemplateFiles = Property ("QVariantList" , _pipelineTemplateFiles , notify = pipelineTemplateFilesChanged )
622695 pipelineTemplateNames = Property ("QVariantList" , _pipelineTemplateNames , notify = pipelineTemplateFilesChanged )
623- recentProjectFiles = Property ("QVariantList" , _recentProjectFiles , notify = recentProjectFilesChanged )
696+ recentProjectFiles = Property ("QVariantList" , lambda self : self . _recentProjectFiles , notify = recentProjectFilesChanged )
624697 recentImportedImagesFolders = Property ("QVariantList" , _recentImportedImagesFolders , notify = recentImportedImagesFoldersChanged )
625698 default8bitViewerEnabled = Property (bool , _default8bitViewerEnabled , constant = True )
626699 defaultSequencePlayerEnabled = Property (bool , _defaultSequencePlayerEnabled , constant = True )
0 commit comments