IO: Replace Image space drop-box with a File Handler

Replaces `Space Image` drop-box with a file handler in order to support
opening multiple sets of files at once and to allow users to add their
own file handler to the Image editor space if desired.

A new `Image_OT_open_images` operator is introduced that uses
`Image_OT_open` internally. It groups files by different sequences and
UDIMs and loads each group accordingly.

Co-authored-by: Jesse Yurkovich <deadpin>
Pull Request: https://projects.blender.org/blender/blender/pulls/117707
This commit is contained in:
Guillermo Venegas
2024-04-25 03:40:14 +02:00
committed by Jesse Yurkovich
parent a03bf7e055
commit b97ac126f8
2 changed files with 92 additions and 30 deletions

View File

@@ -3,8 +3,12 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Operator
from bpy.props import StringProperty
from bpy.types import (
Operator, OperatorFileListElement, FileHandler,)
from bpy.props import (
BoolProperty,
CollectionProperty,
StringProperty,)
from bpy.app.translations import pgettext_rpt as rpt_
@@ -191,8 +195,93 @@ class ProjectApply(Operator):
return {'FINISHED'}
bl_file_extensions_image_movie = (*bpy.path.extensions_image, *bpy.path.extensions_movie)
class IMAGE_OT_open_images(Operator):
bl_idname = "image.open_images"
bl_label = "Open Images"
bl_options = {'REGISTER', 'UNDO'}
directory: StringProperty(subtype='FILE_PATH', options={'SKIP_SAVE', 'HIDDEN'})
files: CollectionProperty(type=OperatorFileListElement, options={'SKIP_SAVE', 'HIDDEN'})
relative_path: BoolProperty(name="Use relative path", default=True)
use_sequence_detection: BoolProperty(name="Use sequence detection", default=True)
use_udim_detection: BoolProperty(name="Use UDIM detection", default=True)
@classmethod
def poll(cls, context):
return context.area and context.area.ui_type == 'IMAGE_EDITOR'
def execute(self, context):
if not self.directory or len(self.files) == 0:
return {'CANCELLED'}
# List of files that are not part of an image sequence or UDIM group
files = []
# Groups of files that may be part of an image sequence or a UDIM group.
sequences = []
import re
regex_extension = re.compile("(" + "|".join([re.escape(ext) for ext in bl_file_extensions_image_movie]) + ")$")
regex_sequence = re.compile("(\\d+)(\\.[\\w\\d]+)$")
for file in self.files:
# Filter by extension
if not regex_extension.search(file.name):
continue
match = regex_sequence.search(file.name)
if not (match and (self.use_sequence_detection or self.use_udim_detection)):
files.append(file.name)
continue
seq = {'prefix': file.name[:len(file.name) - len(match.group(0))],
'ext': match.group(2),
'frame_size': len(match.group(1)),
'files': [file.name,]}
for test_seq in sequences:
if (test_seq['prefix'] == seq['prefix'] and test_seq['ext'] == seq['ext'] and
test_seq['frame_size'] == seq['frame_size']):
test_seq['files'].append(file.name)
seq = None
break
if seq:
sequences.append(seq)
import os
for file in files:
filepath = os.path.join(self.directory, file)
bpy.ops.image.open(filepath=filepath, relative_path=self.relative_path)
for seq in sequences:
seq['files'].sort()
filepath = os.path.join(self.directory, seq['files'][0])
files = [{"name": file} for file in seq['files']]
bpy.ops.image.open(
filepath=filepath,
directory=self.directory,
files=files,
use_sequence_detection=self.use_sequence_detection,
use_udim_detecting=self.use_udim_detection,
relative_path=self.relative_path)
is_tiled = context.edit_image.source == 'TILED'
if len(files) > 1 and self.use_sequence_detection and not is_tiled:
context.edit_image.name = "{prefix}{hash}{ext}".format(
prefix=seq['prefix'], hash=("#" * seq['frame_size']), ext=seq['ext'])
return {'FINISHED'}
class IMAGE_FH_drop_handler(FileHandler):
bl_idname = "IMAGE_FH_drop_handler"
bl_label = "Open images"
bl_import_operator = "image.open_images"
bl_file_extensions = ';'.join(bl_file_extensions_image_movie)
@classmethod
def poll_drop(cls, context):
return (context.area and context.area.ui_type == 'IMAGE_EDITOR'
and context.region and context.region.type == 'WINDOW')
classes = (
EditExternally,
ProjectApply,
IMAGE_OT_open_images,
IMAGE_FH_drop_handler,
ProjectEdit,
)

View File

@@ -262,35 +262,8 @@ static void image_keymap(wmKeyConfig *keyconf)
WM_keymap_ensure(keyconf, "Image", SPACE_IMAGE, RGN_TYPE_WINDOW);
}
/* dropboxes */
static bool image_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
{
ScrArea *area = CTX_wm_area(C);
if (ED_region_overlap_isect_any_xy(area, event->xy)) {
return false;
}
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
return true;
}
}
return false;
}
static void image_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop)
{
/* copy drag path to properties */
RNA_string_set(drop->ptr, "filepath", WM_drag_get_single_path(drag));
}
/* area+region dropbox definition */
static void image_dropboxes()
{
ListBase *lb = WM_dropboxmap_find("Image", SPACE_IMAGE, RGN_TYPE_WINDOW);
WM_dropbox_add(lb, "IMAGE_OT_open", image_drop_poll, image_drop_copy, nullptr, nullptr);
}
static void image_dropboxes() {}
/**
* \note take care not to get into feedback loop here,