from pathlib import Path import bpy from bpy.types import Operator from mathutils import Vector from os.path import abspath from .. import core 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 = core.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 = core.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)