263 lines
10 KiB
Python
263 lines
10 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
## Standalone based on SPA code
|
|
## Code from create_gp_texture_ref.py (Copyright (C) 2023, The SPA Studios. All rights reserved.)
|
|
## adapted to create as a single 'GP as image' and fit in camera with driver
|
|
|
|
import bpy
|
|
from bpy_extras.io_utils import ImportHelper
|
|
from bpy.props import (StringProperty,
|
|
CollectionProperty,
|
|
BoolProperty,
|
|
EnumProperty)
|
|
import json
|
|
import bpy_extras
|
|
from mathutils import Vector
|
|
import os
|
|
from pathlib import Path
|
|
from .. import fn
|
|
from .. constants import *
|
|
from .. export_psd_layers import export_psd_bg
|
|
|
|
|
|
def get_json_infos(json_path) -> tuple((list, tuple)):
|
|
'''return a tuple with (image paths list, [x, y] original psd resolution'''
|
|
|
|
import json
|
|
setup_json_path = Path(json_path)
|
|
setup_json = json.loads(setup_json_path.read_text())
|
|
|
|
# psd = Path(setup_json['psd']) # path to psd
|
|
# image_size = setup_json['image_size'] # image size (reduced)
|
|
# png_dir = Path(setup_json['png_dir']) # path to png directory
|
|
layers = setup_json['layers'] # dic : name, lbbox, bbox, index, path
|
|
org_image_size = setup_json['org_image_size'] # original PSD dimensions
|
|
img_list = [Path(l['path']) for l in layers] # (png_dir /l['name']).with_suffix('.png')
|
|
|
|
return img_list, org_image_size
|
|
|
|
def setup_bg_camera(image_size, context=None, scn=None, cam_type=None):
|
|
'''Create background camera from scene active camera according to background image resolution
|
|
image_size ::
|
|
cam_type :: wether the BG camera should be set to perspective or orthographic
|
|
'''
|
|
context = context or bpy.context
|
|
scn=scn or context.scene
|
|
|
|
movie_resolution = scn.render.resolution_x, scn.render.resolution_y
|
|
anim_cam = scn.camera
|
|
anim_cam_scale = 6 # anim_cam.data.ortho_scale
|
|
anim_cam_focale = 50 # anim_cam.data.lens
|
|
cam_type = cam_type or anim_cam.data.type
|
|
|
|
## scale up plane scale and bg_cam ortho_scale according to film res
|
|
bg_factor = (anim_cam_scale * image_size[0]) / movie_resolution[0]
|
|
#print(f'({anim_cam_scale} * {image_size[0]}) / {movie_resolution[0]} = {bg_factor}')
|
|
|
|
## scale down the focal according to film res
|
|
bg_focale = (anim_cam_focale * movie_resolution[0]) / image_size[0]
|
|
#print(f'({anim_cam_focale} * {movie_resolution[0]}) / {image_size[0]} = {bg_focale}')
|
|
|
|
bg_cam = scn.objects.get('bg_cam')
|
|
if not bg_cam:
|
|
bg_cam = fn.create_cam(name='bg_cam', type=cam_type, size=bg_factor, focale=bg_focale)
|
|
bg_cam['resolution'] = image_size
|
|
|
|
bg_cam.matrix_world = anim_cam.matrix_world
|
|
|
|
bg_cam.hide_select = True
|
|
bg_cam.lock_location = bg_cam.lock_rotation = bg_cam.lock_scale = [True]*3
|
|
|
|
fn.set_collection(bg_cam, 'Camera')
|
|
fn.set_collection(anim_cam, 'Camera')
|
|
return bg_cam
|
|
|
|
class BPM_OT_import_bg_images(bpy.types.Operator, ImportHelper):
|
|
bl_idname = "bpm.import_bg_images"
|
|
bl_label = "Import Background images"
|
|
bl_description = "Import either a Json, a PSD, multiple files"
|
|
bl_options = {"REGISTER"} # , "INTERNAL"
|
|
|
|
# filename_ext = '.json'
|
|
|
|
filter_glob: StringProperty(
|
|
default=';'.join([f'*{i}' for i in bpy.path.extensions_image]) + ';*.json',
|
|
options={'HIDDEN'} )
|
|
|
|
## Active selection
|
|
filepath : StringProperty(
|
|
name="File Path",
|
|
description="File path used for import",
|
|
maxlen= 1024) # the active file
|
|
|
|
## Handle multi-selection
|
|
files: CollectionProperty(
|
|
name="File Path",
|
|
type=bpy.types.OperatorFileListElement,
|
|
) # The filelist collection
|
|
|
|
## Choice to place before or after ?
|
|
|
|
import_type : EnumProperty(
|
|
name="Import As", description="Type of import to ", default='GPENCIL', options={'ANIMATABLE'},
|
|
items=(
|
|
('GPENCIL', 'Gpencil Object', 'Import bg planes as gpencil objects', 0),
|
|
('EMPTY', 'Empty Reference', 'Import bg planes as empty objects', 1),
|
|
('MESH', 'Texture Plane', 'Import bg planes as mesh objects', 2),
|
|
))
|
|
|
|
mode : EnumProperty(
|
|
name="Mode", description="", default='REPLACE', options={'ANIMATABLE'},
|
|
items=(
|
|
('REPLACE', 'Replace Existing', 'Replace the image if already exists', 0),
|
|
('SKIP', 'Skip Existing', 'Skip the import if the image alreaady exists in planes', 1),
|
|
# ('PURGE', 'Purge', 'When object exists, fully delete it before linking, even associated collection and holder', 2),
|
|
))
|
|
|
|
def execute(self, context):
|
|
org_image_size = None
|
|
scn = context.scene
|
|
active_file = Path(self.filepath)
|
|
|
|
if len(self.files) == 1 and active_file.suffix.lower() in ('.json', '.psd'):
|
|
json_path = None
|
|
print('active_file.suffix.lower(): ', active_file.suffix.lower())
|
|
if active_file.suffix.lower() == '.psd':
|
|
print('Is a PSD')
|
|
## Export layers and create json setup file
|
|
# Export passes in a 'render' or 'render_hd' folder aside psd
|
|
json_path = export_psd_bg(str(active_file))
|
|
|
|
elif active_file.suffix.lower() == '.json':
|
|
print('Is a json')
|
|
# Use json data to batch import
|
|
json_path = active_file
|
|
|
|
if not json_path:
|
|
self.report({'ERROR'}, 'No json path to load, you can try loading from psd or selecting image file directly')
|
|
return {'CANCELLED'}
|
|
|
|
file_list, org_image_size = get_json_infos(json_path)
|
|
folder = Path(json_path).parent # render folder
|
|
|
|
else:
|
|
folder = active_file.parent
|
|
# Filter out json (we may want ot import the PSD as imagelisted in bpy.path.extensions_image)
|
|
file_list = [folder / f.name for f in self.files if Path(f.name).suffix in bpy.path.extensions_image]
|
|
|
|
if not file_list:
|
|
self.report({'ERROR'}, 'Image file list is empty')
|
|
return {'CANCELLED'}
|
|
|
|
|
|
## simple_list
|
|
# existing_backgrounds = [o for o in context.scene.objects if o.get('is_background')]
|
|
# existing_holders = [o for o in context.scene.objects if o.name.startswith(PREFIX) and o.type == 'MESH' and o.children]
|
|
|
|
## Has dict
|
|
# existing_backgrounds = {o : img_info for o in context.scene.objects if o.get('is_background') and (img_info := o.get('is_background'))}
|
|
|
|
# FIXME: Use existing background custom prop? : o.get('is_background')
|
|
## list of Tuples : [(plane_object, img_data, transparency_value), ...]
|
|
|
|
existing_backgrounds = [(o, *img_info) for o in context.scene.objects \
|
|
if o.get('is_background') and (img_info := fn.get_image_infos_from_object(o)) and o.get('is_background') and o.parent]
|
|
|
|
existing_backgrounds.sort(key=lambda x: x[0].parent.location.z, reverse=True) # sort by parent (holder) location Z
|
|
|
|
far_plane = INIT_POS
|
|
if existing_backgrounds:
|
|
far_plane = existing_backgrounds[-1][0].parent.location.z
|
|
|
|
## Ensure bg_cam setup (option to use active camera ?)
|
|
bg_cam = scn.objects.get('bg_cam')
|
|
if not bg_cam:
|
|
if not org_image_size:
|
|
## Get image size from first file
|
|
img = bpy.data.images.load(file_list[0], check_existing=True)
|
|
org_image_size = (img.size[0], img.size[1])
|
|
bg_cam = setup_bg_camera(
|
|
org_image_size, context=context, scn=scn, cam_type=scn.camera.data.type)
|
|
|
|
## Ensure Background collection
|
|
backgrounds = fn.get_col(BGCOL) # Create if needed
|
|
|
|
for fp in file_list:
|
|
print(f'Importing {fp.name}')
|
|
current_holder = None
|
|
|
|
file_stem = Path(fp).stem
|
|
# current_bg = next((o for o if o.parent and fn.get_image_infos_from_object(o)), None)
|
|
current_bg = next((o for o in existing_backgrounds if o[1].filepath == fp), None)
|
|
if current_bg:
|
|
current_holder = current_bg.parent
|
|
# TODO store opacity or delete existing objects only after loop
|
|
|
|
if current_bg and self.mode == 'SKIP':
|
|
print(f' - SKIP: existing {current_bg.name}')
|
|
continue
|
|
|
|
if current_bg and self.mode == 'REPLACE':
|
|
print(f' - DEL: existing {current_bg.name}')
|
|
bpy.data.objects.remove(current_bg)
|
|
|
|
# Import image
|
|
if self.import_type == 'GPENCIL':
|
|
bg_img = fn.import_image_as_gp_reference(
|
|
context=context,
|
|
image=fp,
|
|
pack_image=False,
|
|
)
|
|
elif self.import_type == 'MESH':
|
|
bg_img = fn.create_image_plane(fp)
|
|
|
|
elif self.import_type == 'EMPTY':
|
|
bg_img = fn.create_empty_image(fp)
|
|
|
|
|
|
bg_img.name = file_stem + '_texgp'
|
|
bg_img.hide_select = True
|
|
|
|
if current_holder:
|
|
bg_img.parent = current_holder
|
|
fn.set_collection(bg_img, current_holder.users_collection[0].name)
|
|
continue
|
|
|
|
# img, opacity = fn.get_image_infos_from_object(bg_img)
|
|
print('bg_img: ', bg_img)
|
|
img = fn.get_image(bg_img)
|
|
# bg_name = fn.clean_image_name(img.name)
|
|
file_stem = file_stem
|
|
|
|
# Get create collection from image clean name
|
|
bg_col = fn.get_col(file_stem, parent=backgrounds)
|
|
|
|
## Set in collection
|
|
bg_col.objects.link(bg_img)
|
|
|
|
print('img: ', img.name, bg_img.name, bg_img.users_collection)
|
|
|
|
## create the holder, parent to camera, set driver and set collection. Could also pass 'fp'
|
|
holder = fn.create_plane_holder(img, name=PREFIX + file_stem, parent=bg_cam, link_in_col=bg_col)
|
|
print('holder: ', holder.name)
|
|
|
|
fn.create_plane_driver(holder, bg_cam, distance=far_plane)
|
|
bg_img.parent = holder
|
|
|
|
far_plane += 2
|
|
|
|
fn.reload_bg_list(scene=scn)
|
|
self.report({'INFO'}, f'Settings loaded from: {os.path.basename(self.filepath)}')
|
|
return {"FINISHED"}
|
|
|
|
classes=(
|
|
BPM_OT_import_bg_images,
|
|
)
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
def unregister():
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls) |