Replace space image drop-box with a File Handler #117707
|
@ -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
|
||||
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
Julian Eisel
commented
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
Jesse Yurkovich
commented
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 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.
Jesse Yurkovich
commented
When dropping 2 UDIM files "test.1001.png" and "test.1002.png", this code will replace the correct
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:
...
```
Guillermo Venegas
commented
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
Jesse Yurkovich
commented
The condition should be 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
Jesse Yurkovich
commented
To match current behavior we could drop the 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
Jesse Yurkovich
commented
Spelling of "images" here and the 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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
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