Skip to content

Commit

Permalink
Add option to choose type of images to move
Browse files Browse the repository at this point in the history
- I think it's better to set a fixed type of images in the preferences,
  so users won't have to choose it every time they save the file.
- Turn the `pasted_images` list into the `filepath_to_image` dictionary,
  so we can find images faster.
- Rewrite most of the the `execute` method. Now, the `filepath_to_image`
  will be updated when the path is changed so it can remember the pasted
  images later. Then we remove all pasted images that not in the
  `.blend` file (in case user undoes after pasting).
  • Loading branch information
thanhph111 committed Aug 5, 2021
1 parent 153eb1a commit e25fe8d
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 40 deletions.
4 changes: 2 additions & 2 deletions imagepaste/image.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Image:
"""A class to represent an image file."""

pasted_image = []
filepath_to_image = {}

def __init__(self, filepath: str, filename: str = None) -> None:
"""Constructor for the Image class.
Expand All @@ -16,7 +16,7 @@ def __init__(self, filepath: str, filename: str = None) -> None:
self.filepath = filepath
self.filename = filename or basename(filepath)
self.filebase = dirname(filepath)
Image.pasted_image.append(self)
Image.filepath_to_image[self.filepath] = self

def __repr__(self) -> str:
"""Return a string representation of the Image class.
Expand Down
95 changes: 68 additions & 27 deletions imagepaste/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,48 +209,92 @@ class IMAGEPASTE_OT_move_to_saved_directory(bpy.types.Operator):
bl_label = "Move to Target Directory"
bl_options = {"UNDO_GROUPED"}

is_move_only_pasted_images = bpy.props.BoolProperty(
name="Move only pasted images",
description="Move only pasted images to the target directory",
default=True,
)
def __init__(self) -> None:
from .helper import get_save_directory

super().__init__()
self.saved_directory = get_save_directory()
self.orphaned_images = self.get_orphaned_images(self.saved_directory)

def execute(self, _context):
from .helper import get_save_directory
from .image import Image

saved_directory = get_save_directory()
orphaned_images = self.get_orphaned_images(saved_directory)
for orphaned_image in orphaned_images:
self.change_image_directory(orphaned_image, saved_directory)
filepath_to_image = Image.filepath_to_image
orphaned_image_filepaths = []
# Change the paths the images refers to with the new one
for orphaned_image in self.orphaned_images:
old_filepath = self.get_abspath(orphaned_image.filepath)
self.change_image_directory(orphaned_image, self.saved_directory)
new_filepath = self.get_abspath(orphaned_image.filepath)
# Also change in the dictionary
if old_filepath in filepath_to_image:
filepath_to_image[new_filepath] = filepath_to_image.pop(old_filepath)
orphaned_image_filepaths.append(old_filepath)
# Remove pasted images which are not in `.blend` file (pasted but then undone)
for filepath in list(filepath_to_image.keys()):
if filepath in orphaned_image_filepaths:
del filepath_to_image[filepath]
return {"FINISHED"}

def get_orphaned_images(self, saved_directory: str) -> list[bpy.types.Image]:
"""Get images that are not in the target directory
def invoke(self, context, _event):
if self.orphaned_images:
self.execute(context)
return {"CANCELLED"}

@classmethod
def poll(_cls, _context):
return bool(bpy.data.filepath)

def get_abspath(self, path: str) -> str:
"""Get the absolute path of a file or directory.
Args:
path (str): The path to get the absolute path of
Returns:
str: The absolute path of the file or directory
"""
import os

return os.path.abspath(bpy.path.abspath(path))

def get_orphaned_images(self, saved_directory) -> list[bpy.types.Image]:
"""Get images that are not in the target directory.
Args:
saved_directory (str): The target directory
Returns:
list[bpy.types.Image]: A list of orphaned images
"""
import os
from os.path import dirname
from .image import Image
from .helper import get_addon_preferences

pasted_image_paths = [image.filepath for image in Image.pasted_image]
preferences = get_addon_preferences()
if preferences.image_type_to_move == "no_moving":
return []
filepath_to_image = Image.filepath_to_image
existing_images = bpy.data.images
if not self.is_move_only_pasted_images:
return existing_images
return [
image
for image in existing_images
if os.path.abspath(bpy.path.abspath(image.filepath)) in pasted_image_paths
and os.path.dirname(image.filepath) != saved_directory
]
orphaned_images = []
for image in existing_images:
# Example: 'Render Result'
if not image.filepath:
continue
filepath = self.get_abspath(image.filepath)
if dirname(filepath) == saved_directory:
continue
if preferences.image_type_to_move == "all_images":
orphaned_images.append(image)
continue
if filepath in filepath_to_image:
orphaned_images.append(image)
return orphaned_images

def change_image_directory(
self, orphaned_image: bpy.types.Image, saved_directory: str
) -> None:
"""Change the directory of an orphaned image
"""Change the directory of an orphaned image.
Args:
orphaned_image (bpy.types.Image): An orphaned image
Expand All @@ -262,10 +306,7 @@ def change_image_directory(
new_filepath = join(saved_directory, bpy.path.basename(orphaned_image.filepath))
copyfile(bpy.path.abspath(orphaned_image.filepath), new_filepath)
orphaned_image.filepath = new_filepath

@classmethod
def poll(_cls, _context):
return bool(bpy.data.filepath)
orphaned_image.reload()


classes = (
Expand Down
54 changes: 43 additions & 11 deletions imagepaste/preferences.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import bpy
from bpy.props import BoolProperty
from bpy.props import StringProperty
from bpy.types import AddonPreferences

from .helper import ADDON_NAME
from .operators import (
Expand All @@ -14,39 +11,50 @@
)


class IMAGEPASTE_AddonPreferences(AddonPreferences):
class IMAGEPASTE_AddonPreferences(bpy.types.AddonPreferences):
"""Add-on preferences for ImagePaste"""

bl_idname = ADDON_NAME
is_use_another_directory: BoolProperty(

is_use_another_directory: bpy.props.BoolProperty(
name="Use a directory",
description=(
"Save images to another directory instead of temporary directory"
" when the file is not saved"
),
default=False,
)
another_directory: StringProperty(
another_directory: bpy.props.StringProperty(
name="Saving directory",
description="A path to directory where images saved to",
subtype="DIR_PATH",
)
is_force_use_another_directory: BoolProperty(
is_force_use_another_directory: bpy.props.BoolProperty(
name="Force use another directory",
description="Save images to above directory even when the file is saved or not",
default=False,
)
is_use_subdirectory: BoolProperty(
is_use_subdirectory: bpy.props.BoolProperty(
name="Use subdirectory",
description="Save images to a subdirectory where the file is saved",
default=True,
)
subdirectory_name: StringProperty(
subdirectory_name: bpy.props.StringProperty(
name="Sub directory name",
description="A name for subdirectory",
default="ImagePaste",
)
is_disable_debug: BoolProperty(
image_type_to_move: bpy.props.EnumProperty(
name="Image type to move",
description="Which type of image will be moved",
items=[
("pasted_images", "Pasted images", "Only pasted images will be moved"),
("all_images", "All images", "All images will be moved"),
("no_moving", "No moving", "Don't move anything when saving"),
],
default="pasted_images",
)
is_disable_debug: bpy.props.BoolProperty(
name="Disable debug message",
description="Debug message will not printed in console",
default=False,
Expand Down Expand Up @@ -106,6 +114,23 @@ def draw(self, _context):
column_2_sub = column_2.column()
column_2_sub.prop(self, "subdirectory_name", text="")

# New box
box = layout.box().column()
box.label(
text=("Choose the type of images that will be moved when saving the file")
)

# New property
prop = box.row(align=True)
split = prop.split(factor=split_ratio)
# First column
column_1 = split.column()
column_1.alignment = "RIGHT"
column_1.label(text="Image type to move")
# Second column
column_2 = split.row()
column_2.prop(self, "image_type_to_move", expand=True)

# New box
box = layout.box().column()
box.label(text="Miscellaneous")
Expand All @@ -118,7 +143,7 @@ def draw(self, _context):
column_1.alignment = "RIGHT"
column_1.label(text="Disable debug message")
# Second column
column_2 = split.column()
column_2 = split.row()
column_2.prop(self, "is_disable_debug", text="")


Expand Down Expand Up @@ -165,6 +190,11 @@ def view3d_paste_reference_imageaddmenu_draw(self, _context):
)


@bpy.app.handlers.persistent
def move_to_saved_directory_handler(self, _context):
bpy.ops.imagepaste.move_to_saved_directory("INVOKE_DEFAULT")


addon_keymaps = []


Expand All @@ -177,6 +207,7 @@ def register():
bpy.types.NODE_MT_context_menu.append(shadereditor_paste_contextmenu_draw)
bpy.types.VIEW3D_MT_image_add.append(view3d_paste_plane_imageaddmenu_draw)
bpy.types.VIEW3D_MT_image_add.append(view3d_paste_reference_imageaddmenu_draw)
bpy.app.handlers.save_post.append(move_to_saved_directory_handler)

kc = bpy.context.window_manager.keyconfigs.addon

Expand Down Expand Up @@ -248,6 +279,7 @@ def unregister():
km.keymap_items.remove(kmi)
addon_keymaps.clear()

bpy.app.handlers.save_post.remove(move_to_saved_directory_handler)
bpy.types.VIEW3D_MT_image_add.remove(view3d_paste_reference_imageaddmenu_draw)
bpy.types.VIEW3D_MT_image_add.remove(view3d_paste_plane_imageaddmenu_draw)
bpy.types.NODE_MT_context_menu.remove(shadereditor_paste_contextmenu_draw)
Expand Down

0 comments on commit e25fe8d

Please sign in to comment.