background_plane_manager/operators/manage_planes.py

320 lines
11 KiB
Python
Raw Normal View History

2023-09-28 11:34:41 +02:00
from pathlib import Path
import bpy
from bpy.types import Operator
from mathutils import Vector
from os.path import abspath
from .. import fn
from .. constants import BGCOL
## Open image folder
## Open change material alpha mode when using texture
## Selection ops (select toggle / all)
## Set distance modal
## Move plane by index
## Rebuild collection (Scan Background collection)
class BPM_OT_open_bg_folder(Operator):
bl_idname = "bpm.open_bg_folder"
bl_label = "Open bg folder"
bl_description = "Open folder of active element"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if context.scene.bg_props.planes and context.scene.bg_props.planes[context.scene.bg_props.index].type == 'bg':
return True
else:
cls.poll_message_set("Only available when a background image planes is selected")
return False
def execute(self, context):
item = context.scene.bg_props.planes[context.scene.bg_props.index]
ob = item.plane
# name = f'{item.plane.name}_texempty'
tex_obj = next((o for o in ob.children), None)
if not tex_obj:
self.report({'ERROR'}, f'Could not found child for holder: {ob.name}')
return {"CANCELLED"}
img = fn.get_image(tex_obj)
fp = Path(abspath(bpy.path.abspath(img.filepath)))
if not fp.exists():
self.report({'ERROR'}, f'Not found: {fp}')
return {"CANCELLED"}
bpy.ops.wm.path_open(filepath=str(fp.parent))
return {"FINISHED"}
class BPM_OT_change_material_alpha_mode(Operator):
bl_idname = "bpm.change_material_alpha_mode"
bl_label = "Swap Material Aplha"
bl_description = "Swap alpha : blend <-> Clip"
bl_options = {"REGISTER"} # , "UNDO"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
print('----')
# maybe add a shift to act on selection only
on_selection = False
ct = 0
mode = None
for col in bpy.context.scene.collection.children:
if col.name != BGCOL:
continue
for bgcol in col.children:
for o in bgcol.all_objects:
if o.type == 'MESH' and len(o.data.materials):
if on_selection and o.parent and not o.parent.select_get():
continue
if mode is None:
mode = 'BLEND' if o.data.materials[0].blend_method == 'CLIP' else 'CLIP'
print(o.name, '>>', mode)
ct += 1
o.data.materials[0].blend_method = mode
self.report({'INFO'}, f'{ct} swapped to alpha {mode}')
return {"FINISHED"}
class BPM_OT_select_swap(Operator):
bl_idname = "bpm.select_swap_active_bg"
bl_label = "Select / Deselect"
bl_description = "Select/Deselect Active Background"
bl_options = {"REGISTER"} # , "UNDO"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
settings = context.scene.bg_props
plane = settings.planes[settings.index].plane
if not plane:
self.report({'ERROR'}, 'could not found current plane\ntry a refresh')
return{'CANCELLED'}
plane.select_set(not plane.select_get())
return {"FINISHED"}
class BPM_OT_select_all(Operator):
bl_idname = "bpm.select_all"
bl_label = "Select All Background"
bl_description = "Select all Background"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
bg_col = bpy.data.collections.get('Background')
if bg_col:
bg_col.hide_select = False
settings = context.scene.bg_props
for p in settings.planes:
if p and p.type == 'bg':
if p.plane.visible_get():
p.plane.select_set(True)
return {"FINISHED"}
# class BPM_OT_frame_backgrounds(Operator):
# bl_idname = "bpm.frame_backgrounds"
# bl_label = "Frame Backgrounds"
# bl_description = "Get out of camera and frame backgrounds from a quarter point or view"
# bl_options = {"REGISTER"}
# @classmethod
# def poll(cls, context):
# return True
# def execute(self, context):
# if context.space_data.region_3d.view_perspective != 'CAMERA':
# context.space_data.region_3d.view_perspective = 'CAMERA'
# return {"FINISHED"}
# ## go out of camera, frame all bg in view at wauter angle
# settings = context.scene.bg_props
# visible_planes = [p.plane for p in settings.planes if p and p.type == 'bg' and p.plane.visible_get()]
# # TODO get out of camera and view frame planes
# context.space_data.region_3d.view_perspective = 'PERSPECTIVE'
# return {"FINISHED"}
class BPM_OT_set_distance(Operator):
bl_idname = "bpm.set_distance"
bl_label = "Distance"
bl_description = "Set distance of the active plane object\
\nShift move all further plane\
\nCtrl move all closer planes\
\nAlt move All"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return True
def invoke(self, context, event):
self.init_mouse_x = event.mouse_x
context.window_manager.modal_handler_add(self)
self.active = context.scene.bg_props.planes[context.scene.bg_props.index].plane
if context.scene.bg_props.move_hided:
self.plane_list = [i.plane for i in context.scene.bg_props.planes if i.type == 'bg']
else:
# o.visible_get() << if not get_view_layer_col(o.users_collection[0]).exclude
self.plane_list = [i.plane for i in context.scene.bg_props.planes if i.type == 'bg' and i.plane.visible_get()]
if not self.plane_list:
self.report({'ERROR'}, f'No plane found in list with current filter')
return {"CANCELLED"}
self.plane_list.sort(key=lambda o: o.get('distance'), reverse=True)
self.active_index = self.plane_list.index(self.active)
self.offset = 0.0
self.further = self.plane_list[:self.active_index+1] # need +1 to include active
self.closer = self.plane_list[self.active_index:]
self.init_dist = [o.get('distance') for o in self.plane_list]
# context.area.header_text_set(f'{self.mode}')
return {'RUNNING_MODAL'}
def modal(self, context, event):
context.area.header_text_set(f'Shift : move active and all further planes |\
Ctrl : move active and all closer planes | Shift + Ctrl :move all| Offset : {self.offset:.3f}m')
if event.type in {'LEFTMOUSE'} and event.value == 'PRESS':
# VALID
context.area.header_text_set(None)
return {"FINISHED"}
if event.type in {'ESC', 'RIGHTMOUSE'} and event.value == 'PRESS':
# CANCEL
context.area.header_text_set(None)
for plane, init_dist in zip(self.plane_list, self.init_dist):
plane['distance'] = init_dist
plane.location = plane.location
return {"CANCELLED"}
if event.type in {'MOUSEMOVE'}:
self.mouse = Vector((event.mouse_x, event.mouse_y))
self.offset = (event.mouse_x - self.init_mouse_x) * 0.1
for plane, init_dist in zip(self.plane_list, self.init_dist):
# reset to init distance dist
plane['distance'] = init_dist
if event.ctrl and event.shift or event.alt:
# all
plane['distance'] = init_dist + self.offset
elif event.shift:
if plane in self.further:
plane['distance'] = init_dist + self.offset
elif event.ctrl:
if plane in self.closer:
plane['distance'] = init_dist + self.offset
else:
if plane == self.active:
plane['distance'] = init_dist + self.offset
## needed esle no update...
plane.location = plane.location
return {"RUNNING_MODAL"}
class BPM_OT_move_plane(Operator):
bl_idname = "bpm.move_plane"
bl_label = "Move Plane"
bl_description = "Move active plane up or down"
bl_options = {"REGISTER", "UNDO"}
direction : bpy.props.StringProperty()
@classmethod
def poll(cls, context):
props = context.scene.bg_props
return props \
and props.planes \
and props.index >= 0 \
and props.index < len(props.planes) \
and props.planes[props.index].type == 'bg'
def execute(self, context):
props = context.scene.bg_props
planes = props.planes
plane_ob = planes[props.index].plane
plane_objects = sorted([p.plane for p in planes if p.type == 'bg'], key=lambda x :x['distance'])
index = plane_objects.index(plane_ob)
if self.direction == 'UP':
if index == 0:
return {"FINISHED"}
other_plane = plane_objects[index - 1]
elif self.direction == 'DOWN':
# If index == len(planes)-1: # Invalid when there are GP as well
if index == len(planes) - 1 or planes[index + 1].type != 'bg':
# End of list or avoid going below a GP object item.
return {"FINISHED"}
other_plane = plane_objects[index + 1]
other_plane['distance'], plane_ob['distance'] = plane_ob['distance'], other_plane['distance']
plane_ob.location = plane_ob.location
other_plane.location = other_plane.location
return {"FINISHED"}
class BPM_OT_reload(Operator):
bl_idname = "bpm.reload_list"
bl_label = "Refresh Bg List"
bl_description = "Refresh the background list (scan for objects using a distance prop)"
bl_options = {"REGISTER"} # , "UNDO"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
error = fn.reload_bg_list()
if error:
if isinstance(error, list):
self.report({'WARNING'}, 'Wrong name for some object, see console:' + '\n'.join(error))
else:
self.report({'ERROR'}, error)
return{'CANCELLED'}
return {"FINISHED"}
classes=(
# BPM_OT_change_background_type,
BPM_OT_select_swap,
BPM_OT_change_material_alpha_mode,
BPM_OT_select_all,
BPM_OT_reload,
BPM_OT_open_bg_folder,
BPM_OT_set_distance,
BPM_OT_move_plane
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)