Replace space image drop-box with a File Handler #117707

Merged
Jesse Yurkovich merged 11 commits from guishe/blender:multiple-image-import into main 2024-04-25 03:40:28 +02:00
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'})
guishe marked this conversation as resolved Outdated

It's probably better to set these 3 properties to True to match the existing option defaults. What do you think?

It's probably better to set these 3 properties to True to match the existing option defaults. What do you think?

I had it as False because there was no feedback when the images were imported as sequences.
But since the sequence is replaced with # in the name, I think it can be set to true

I had it as `False` because there was no feedback when the images were imported as sequences. But since the sequence is replaced with `#` in the name, I think it can be set to true
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'
guishe marked this conversation as resolved
Review

I don't think you have to enforce a region type here? That would only be required when getting data from context that is only available in this region, that doesn't seem to be the case here. So I think this can be removed.

I don't think you have to enforce a region type here? That would only be required when getting data from context that is only available in this region, that doesn't seem to be the case here. So I think this can be removed.
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:
guishe marked this conversation as resolved Outdated

This is a nice substitution I think. But it could end up doing this for the name of UDIMs too. I guess this is the primary downside of keeping use_sequence_detection and use_udim_detection set to True at the same time but it could easily happen.

This is a nice substitution I think. But it could end up doing this for the name of UDIMs too. I guess this is the primary downside of keeping `use_sequence_detection` and `use_udim_detection` set to True at the same time but it could easily happen.

When dropping 2 UDIM files "test.1001.png" and "test.1002.png", this code will replace the correct test.<UDIM>.png name with test.####.png. How about doing the following to only do this replacement for sequences:

            is_tiled = context.edit_image.source == 'TILED'
            if len(files) > 1 and self.use_sequence_detection and not is_tiled:
                ...
When dropping 2 UDIM files "test.1001.png" and "test.1002.png", this code will replace the correct `test.<UDIM>.png` name with `test.####.png`. How about doing the following to only do this replacement for sequences: ```python is_tiled = context.edit_image.source == 'TILED' if len(files) > 1 and self.use_sequence_detection and not is_tiled: ... ```

Added, but I think it would be good to be able to distinguish in some way the udims images in the name

Added, but I think it would be good to be able to distinguish in some way the udims images in the name
context.edit_image.name = "{prefix}{hash}{ext}".format(
guishe marked this conversation as resolved
Review

The condition should be not is_tiled - seems like the not got dropped?

The condition should be `not is_tiled` - seems like the `not` got dropped?
prefix=seq['prefix'], hash=("#" * seq['frame_size']), ext=seq['ext'])
return {'FINISHED'}
guishe marked this conversation as resolved
Review

To match current behavior we could drop the invoke and add bl_options = {'REGISTER', 'UNDO'} to this operator. That would allow users to drag/drop without a popup but still adjust things after the fact with the redo panel. Most folks will be dropping regular files so, if possible, we should try to keep that seamless and quick.

To match current behavior we could drop the `invoke` and add `bl_options = {'REGISTER', 'UNDO'}` to this operator. That would allow users to drag/drop without a popup but still adjust things after the fact with the redo panel. Most folks will be dropping regular files so, if possible, we should try to keep that seamless and quick.
class IMAGE_FH_drop_handler(FileHandler):
guishe marked this conversation as resolved
Review

Spelling of "images" here and the bl_label below

Spelling of "images" here and the `bl_label` below
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

@ -248,35 +248,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,