Scene split and json export
0.5.0 - feat: add a render operator that render all scene - feat: split selected object to a separated scene - feat: crop border to objects - feat: export AE position coordinates to replace layers - change: ops gp.clean_compo_tree now take context.scene - fix: tick `use_compositing` and untick `sequencer` in new scenes
This commit is contained in:
		
							parent
							
								
									852b893f8a
								
							
						
					
					
						commit
						2aa4ecc00e
					
				| @ -14,6 +14,14 @@ Activate / deactivate layer opaticty according to prefix | |||||||
| Activate / deactivate all masks using MA layers | Activate / deactivate all masks using MA layers | ||||||
| --> | --> | ||||||
| 
 | 
 | ||||||
|  | 0.5.0 | ||||||
|  | 
 | ||||||
|  | - feat: add a render operator that render all scene | ||||||
|  | - feat: split selected object to a separated scene | ||||||
|  | - feat: crop border to objects | ||||||
|  | - feat: export AE position coordinates to replace layers | ||||||
|  | - change: ops gp.clean_compo_tree now take context.scene | ||||||
|  | - fix: tick `use_compositing` and untick `sequencer` in new scenes | ||||||
| 
 | 
 | ||||||
| 0.4.1 | 0.4.1 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -89,9 +89,29 @@ class GPEXP_OT_add_objects_to_render(bpy.types.Operator): | |||||||
| 
 | 
 | ||||||
|         return {"FINISHED"} |         return {"FINISHED"} | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | class GPEXP_OT_split_to_scene(bpy.types.Operator): | ||||||
|  |     bl_idname = "gp.split_to_scene" | ||||||
|  |     bl_label = "Split Objects To Scene" | ||||||
|  |     bl_description = "Take selected objects and send them to separate scene" | ||||||
|  |     bl_options = {"REGISTER"} | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def poll(cls, context): | ||||||
|  |         return context.object and context.object.type == 'GPENCIL' | ||||||
|  | 
 | ||||||
|  |     mode : bpy.props.StringProperty(default='ALL', options={'SKIP_SAVE'}) | ||||||
|  | 
 | ||||||
|  |     def execute(self, context): | ||||||
|  |         fn.split_object_to_scene() | ||||||
|  |         return {"FINISHED"} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| classes=( | classes=( | ||||||
| GPEXP_OT_add_layer_to_render, | GPEXP_OT_add_layer_to_render, | ||||||
| GPEXP_OT_add_objects_to_render, | GPEXP_OT_add_objects_to_render, | ||||||
|  | GPEXP_OT_split_to_scene, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| def register():  | def register():  | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								OP_clean.py
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								OP_clean.py
									
									
									
									
									
								
							| @ -98,11 +98,12 @@ class GPEXP_OT_clean_compo_tree(bpy.types.Operator): | |||||||
|         # box.prop(self, 'fo_clear_disconnected') |         # box.prop(self, 'fo_clear_disconnected') | ||||||
| 
 | 
 | ||||||
|     def execute(self, context):         |     def execute(self, context):         | ||||||
|         render = bpy.data.scenes.get('Render') |         # render = bpy.data.scenes.get('Render') | ||||||
|         if not render: |         # if not render: | ||||||
|             print('SKIP, no Render scene') |         #     print('SKIP, no Render scene') | ||||||
|             return {"CANCELLED"} |         #     return {"CANCELLED"} | ||||||
|          |         render = context.scene | ||||||
|  | 
 | ||||||
|         nodes = render.node_tree.nodes |         nodes = render.node_tree.nodes | ||||||
|         if self.clear_unused_view_layers: |         if self.clear_unused_view_layers: | ||||||
|             used_rlayer_names = [n.layer for n in nodes if n.type == 'R_LAYERS'] |             used_rlayer_names = [n.layer for n in nodes if n.type == 'R_LAYERS'] | ||||||
|  | |||||||
							
								
								
									
										51
									
								
								OP_crop_to_object.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								OP_crop_to_object.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | |||||||
|  | import bpy | ||||||
|  | from . import fn | ||||||
|  | 
 | ||||||
|  | class GPEXP_OT_set_crop_from_selection(bpy.types.Operator): | ||||||
|  |     bl_idname = "gp.set_crop_from_selection" | ||||||
|  |     bl_label = "Set Crop" | ||||||
|  |     bl_description = "Automatic set crop from selection" | ||||||
|  |     bl_options = {"REGISTER"} | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def poll(cls, context): | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     def execute(self, context): | ||||||
|  |         scn = context.scene | ||||||
|  |         fn.set_box_from_selected_objects(scn=scn, cam=scn.camera) | ||||||
|  |         scn.render.use_border = True | ||||||
|  |         scn.render.use_crop_to_border = True | ||||||
|  |         return {"FINISHED"} | ||||||
|  | 
 | ||||||
|  | class GPEXP_OT_export_crop_coord_to_json(bpy.types.Operator): | ||||||
|  |     bl_idname = "gp.export_crop_coord_to_json" | ||||||
|  |     bl_label = "Set Crop" | ||||||
|  |     bl_description = "Export json of all scenes borders (when enabled)" # Automatic set crop from selection | ||||||
|  |     bl_options = {"REGISTER"} | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def poll(cls, context): | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     def execute(self, context): | ||||||
|  |         # scn = context.scene | ||||||
|  |         # if not scn.render.use_border or not scn.render.use_crop_to_border: | ||||||
|  |         #     self.report({'ERROR'}, 'Current scene have cropping disabled or use crop_to_border disabled!') | ||||||
|  |         #     return {'CANCELLED'} | ||||||
|  |         fn.export_crop_to_json() | ||||||
|  |         return {"FINISHED"} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | classes=( | ||||||
|  | GPEXP_OT_set_crop_from_selection, | ||||||
|  | GPEXP_OT_export_crop_coord_to_json, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | def register():  | ||||||
|  |     for cls in classes: | ||||||
|  |         bpy.utils.register_class(cls) | ||||||
|  | 
 | ||||||
|  | def unregister(): | ||||||
|  |     for cls in reversed(classes): | ||||||
|  |         bpy.utils.unregister_class(cls) | ||||||
							
								
								
									
										47
									
								
								OP_render_scenes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								OP_render_scenes.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | import bpy | ||||||
|  | from . import fn | ||||||
|  | from time import time | ||||||
|  | 
 | ||||||
|  | class GPEXP_OT_render_all_scenes(bpy.types.Operator): | ||||||
|  |     bl_idname = "gp.render_all_scenes" | ||||||
|  |     bl_label = "Render all scenes" | ||||||
|  |     bl_description = "Render all scene except Render" | ||||||
|  |     bl_options = {"REGISTER"} | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def poll(cls, context): | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     def execute(self, context): | ||||||
|  |         start = time() | ||||||
|  |         ct = 0 | ||||||
|  |         for scn in bpy.data.scenes: | ||||||
|  |             if scn.name == 'Scene': | ||||||
|  |                 continue | ||||||
|  |             if not scn.use_nodes: | ||||||
|  |                 continue | ||||||
|  |             if not [n for n in scn.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.mute]: | ||||||
|  |                 # skip if no fileout | ||||||
|  |                 print(f'\n -!-> Skip {scn.name}, No output file, or all muted') | ||||||
|  |                 continue | ||||||
|  |              | ||||||
|  |             print(f'\n --> Rendering {scn.name}') | ||||||
|  |             # bpy.context.window.scene = scn | ||||||
|  |             bpy.ops.render.render(animation=True, scene=scn.name) | ||||||
|  |             ct += 1 | ||||||
|  | 
 | ||||||
|  |         print(f'\nDone. {ct} scenes rendered in {time()-start:.2f}s') | ||||||
|  |         return {"FINISHED"} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | classes=( | ||||||
|  | GPEXP_OT_render_all_scenes, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | def register():  | ||||||
|  |     for cls in classes: | ||||||
|  |         bpy.utils.register_class(cls) | ||||||
|  | 
 | ||||||
|  | def unregister(): | ||||||
|  |     for cls in reversed(classes): | ||||||
|  |         bpy.utils.unregister_class(cls) | ||||||
| @ -6,7 +6,7 @@ | |||||||
| - on renaming, correct also names in GP modifiers !!! | - on renaming, correct also names in GP modifiers !!! | ||||||
| - opt: multi-merge : also merge merged NG automatically disabling AA without group (or externalise AA node ?) | - opt: multi-merge : also merge merged NG automatically disabling AA without group (or externalise AA node ?) | ||||||
| - opt : How to disable main output | - opt : How to disable main output | ||||||
| 
 | - To add : Reconnect inside nodegroup for nodes when using clean nodes | ||||||
| 
 | 
 | ||||||
| ## Done | ## Done | ||||||
| - set exlude VL on non-used layers | - set exlude VL on non-used layers | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ bl_info = { | |||||||
|     "name": "GP Render", |     "name": "GP Render", | ||||||
|     "description": "Organise export of gp layers through compositor output", |     "description": "Organise export of gp layers through compositor output", | ||||||
|     "author": "Samuel Bernou", |     "author": "Samuel Bernou", | ||||||
|     "version": (0, 4, 1), |     "version": (0, 5, 0), | ||||||
|     "blender": (2, 93, 0), |     "blender": (2, 93, 0), | ||||||
|     "location": "View3D", |     "location": "View3D", | ||||||
|     "warning": "", |     "warning": "", | ||||||
| @ -17,6 +17,8 @@ from . import OP_clean | |||||||
| from . import OP_connect_toggle | from . import OP_connect_toggle | ||||||
| from . import OP_manage_outputs | from . import OP_manage_outputs | ||||||
| from . import OP_scene_switch | from . import OP_scene_switch | ||||||
|  | from . import OP_crop_to_object | ||||||
|  | from . import OP_render_scenes | ||||||
| # from . import OP_check_layer_status | # from . import OP_check_layer_status | ||||||
| from . import OP_render_pdf | from . import OP_render_pdf | ||||||
| from . import prefs | from . import prefs | ||||||
| @ -37,6 +39,8 @@ def register(): | |||||||
|     OP_merge_layers.register() |     OP_merge_layers.register() | ||||||
|     OP_manage_outputs.register() |     OP_manage_outputs.register() | ||||||
|     OP_scene_switch.register() |     OP_scene_switch.register() | ||||||
|  |     OP_crop_to_object.register() | ||||||
|  |     OP_render_scenes.register() | ||||||
|     # OP_check_layer_status.register() |     # OP_check_layer_status.register() | ||||||
|     OP_render_pdf.register() |     OP_render_pdf.register() | ||||||
|     OP_setup_layers.register() |     OP_setup_layers.register() | ||||||
| @ -51,6 +55,8 @@ def unregister(): | |||||||
|     OP_setup_layers.unregister() |     OP_setup_layers.unregister() | ||||||
|     # OP_check_layer_status.unregister() |     # OP_check_layer_status.unregister() | ||||||
|     OP_render_pdf.unregister() |     OP_render_pdf.unregister() | ||||||
|  |     OP_render_scenes.unregister() | ||||||
|  |     OP_crop_to_object.unregister() | ||||||
|     OP_scene_switch.unregister() |     OP_scene_switch.unregister() | ||||||
|     OP_manage_outputs.unregister() |     OP_manage_outputs.unregister() | ||||||
|     OP_merge_layers.unregister() |     OP_merge_layers.unregister() | ||||||
|  | |||||||
							
								
								
									
										382
									
								
								fn.py
									
									
									
									
									
								
							
							
						
						
									
										382
									
								
								fn.py
									
									
									
									
									
								
							| @ -1,10 +1,12 @@ | |||||||
|  | from typing import Coroutine | ||||||
| import bpy | import bpy | ||||||
| import re | import re | ||||||
| from mathutils import Vector | from mathutils import Vector | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from math import isclose | from math import isclose | ||||||
| from collections import defaultdict | from collections import defaultdict | ||||||
| 
 | from time import time | ||||||
|  | import json | ||||||
| 
 | 
 | ||||||
| def create_node(type, tree=None, **kargs): | def create_node(type, tree=None, **kargs): | ||||||
|     '''Get a type, a tree to add in, and optionnaly multiple attribute to set |     '''Get a type, a tree to add in, and optionnaly multiple attribute to set | ||||||
| @ -112,6 +114,8 @@ def set_settings(scene=None): | |||||||
|     scene.eevee.taa_render_samples = 1 |     scene.eevee.taa_render_samples = 1 | ||||||
|     scene.grease_pencil_settings.antialias_threshold = 0 |     scene.grease_pencil_settings.antialias_threshold = 0 | ||||||
|     scene.render.film_transparent = True |     scene.render.film_transparent = True | ||||||
|  |     scene.render.use_compositing = True | ||||||
|  |     scene.render.use_sequencer = False | ||||||
|     scene.view_settings.view_transform = 'Standard' |     scene.view_settings.view_transform = 'Standard' | ||||||
|      |      | ||||||
|     scene.render.resolution_percentage = 100 |     scene.render.resolution_percentage = 100 | ||||||
| @ -119,10 +123,41 @@ def set_settings(scene=None): | |||||||
|     # output (fast write settings since this is just to delete afterwards...) |     # output (fast write settings since this is just to delete afterwards...) | ||||||
|     scene.render.filepath = '//render/preview/preview_' |     scene.render.filepath = '//render/preview/preview_' | ||||||
|     scene.render.image_settings.file_format = 'JPEG' |     scene.render.image_settings.file_format = 'JPEG' | ||||||
|     scene.render.image_settings.color_mode = 'BW' |     scene.render.image_settings.color_mode = 'RGB' | ||||||
|     scene.render.image_settings.quality = 0 |     scene.render.image_settings.quality = 0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def new_scene_from(name, src_scn=None, regen=True, crop=True, link_cam=True, link_light=True): | ||||||
|  |     '''Get / Create a scene from name and source scene to get settings from''' | ||||||
|  |     scn = bpy.data.scenes.get(name) | ||||||
|  |     if scn and not regen: | ||||||
|  |         return scn | ||||||
|  |     elif scn and regen: | ||||||
|  |         bpy.data.scenes.remove(scn) | ||||||
|  | 
 | ||||||
|  |     src_scn = src_scn or bpy.context.scene # given scene, or active scene | ||||||
|  |     scn = bpy.data.scenes.new(name) | ||||||
|  |     ## copy original settings over to new scene | ||||||
|  |     # copy_settings(current, scn) # BAD | ||||||
|  |     for attr in ['frame_start', 'frame_end', 'frame_current', 'camera', 'world']: | ||||||
|  |         setattr(scn, attr, getattr(src_scn, attr)) | ||||||
|  |     copy_settings(src_scn.render, scn.render) | ||||||
|  |      | ||||||
|  |     ## link cameras (and lights ?) | ||||||
|  |     for ob in src_scn.objects: | ||||||
|  |         if link_cam and ob.type == 'CAMERA': | ||||||
|  |             scn.collection.objects.link(ob) | ||||||
|  |         if link_light and ob.type == 'LIGHT': | ||||||
|  |             scn.collection.objects.link(ob) | ||||||
|  | 
 | ||||||
|  |     # set adapted render settings (no AA) | ||||||
|  |     set_settings(scn) | ||||||
|  |      | ||||||
|  |     if crop: | ||||||
|  |         scn.render.use_border = True | ||||||
|  |         scn.render.use_crop_to_border = True | ||||||
|  |     scn.use_nodes = True | ||||||
|  |     return scn | ||||||
| 
 | 
 | ||||||
| def get_render_scene(): | def get_render_scene(): | ||||||
|     '''Get / Create a scene named Render''' |     '''Get / Create a scene named Render''' | ||||||
| @ -677,4 +712,345 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'): | |||||||
|      |      | ||||||
|     if isinstance(_message, str): |     if isinstance(_message, str): | ||||||
|         _message = [_message] |         _message = [_message] | ||||||
|     bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon) |     bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def get_bbox_3d(ob): | ||||||
|  |     bbox_coords = ob.bound_box | ||||||
|  |     return [ob.matrix_world @ Vector(b) for b in bbox_coords] | ||||||
|  | 
 | ||||||
|  | def get_crop_pixel_coord(scn): | ||||||
|  |     # width height probably not needed. might need  | ||||||
|  |     px_width = (scn.render.border_max_x - scn.render.border_min_x) * scn.render.resolution_x | ||||||
|  |     px_height = (scn.render.border_max_y - scn.render.border_min_y) * scn.render.resolution_y | ||||||
|  |      | ||||||
|  |     pos_x = (scn.render.border_min_x + ((scn.render.border_max_x - scn.render.border_min_x) / 2)) * scn.render.resolution_x | ||||||
|  | 
 | ||||||
|  |     ## coord y > image center coord from bottom-left (Blender) | ||||||
|  |     # pos_y = (scn.render.border_min_y + ((scn.render.border_max_y - scn.render.border_min_y) / 2)) * scn.render.resolution_y, | ||||||
|  |      | ||||||
|  |     ## image center coord from top-left (AE) | ||||||
|  |     pos_y = ((1 - scn.render.border_max_y) + ((scn.render.border_max_y - scn.render.border_min_y) / 2)) * scn.render.resolution_y | ||||||
|  | 
 | ||||||
|  |     coord = { | ||||||
|  |         'position_x' : round(pos_x), | ||||||
|  |         'position_y' : round(pos_y), | ||||||
|  |         'width' : round(px_width), | ||||||
|  |         'height' : round(px_height), | ||||||
|  |     } | ||||||
|  |     return coord | ||||||
|  | 
 | ||||||
|  | def export_crop_to_json(): | ||||||
|  |     '''Export crop to json coords for AE | ||||||
|  |     ''' | ||||||
|  |      | ||||||
|  |     blend = Path(bpy.data.filepath) | ||||||
|  |     json_path = blend.parent / 'render' / f'{blend.stem}.json' #f'{ob.name}.json' | ||||||
|  | 
 | ||||||
|  |     ## per scene : json_path = Path(bpy.data.filepath).parent / 'render' / f'{scn.name}.json' | ||||||
|  |     # json_path = Path(bpy.data.filepath).parent / 'render' / f'{scn.name}.json' #f'{ob.name}.json' | ||||||
|  |      | ||||||
|  |     coord_dic = {} | ||||||
|  |      | ||||||
|  |     for scn in bpy.data.scenes: | ||||||
|  |         # if scn.name in {'Scene', 'Render'}: | ||||||
|  |         if scn.name == 'Scene': | ||||||
|  |             continue | ||||||
|  |         if scn.render.use_border: | ||||||
|  |             scn_border = get_crop_pixel_coord(scn) | ||||||
|  |             for ob in [o for o in scn.objects if o.type == 'GPENCIL']: | ||||||
|  |                 coord_dic[ob.name] = scn_border | ||||||
|  | 
 | ||||||
|  |     # save bbox | ||||||
|  |     with json_path.open('w') as fd: | ||||||
|  |         json.dump(coord_dic, fd, indent='\t') | ||||||
|  |      | ||||||
|  |     print(f'coord saved at: {json_path}') | ||||||
|  |     return coord_dic | ||||||
|  | 
 | ||||||
|  | def set_border_region_from_coord(coords, scn=None, margin=30, export_json=True): | ||||||
|  |     '''Get a list of point coord in worldcamera view space (0 to 1) on each axis''' | ||||||
|  | 
 | ||||||
|  |     scn = scn or bpy.context.scene | ||||||
|  |      | ||||||
|  |     coords2d_x = sorted([c[0] for c in coords]) | ||||||
|  |     coords2d_y = sorted([c[1] for c in coords]) | ||||||
|  | 
 | ||||||
|  |     margin_width = margin / scn.render.resolution_x | ||||||
|  |     margin_height = margin / scn.render.resolution_y | ||||||
|  |      | ||||||
|  |     # set crop | ||||||
|  |     scn.render.border_min_x = coords2d_x[0] - margin_width | ||||||
|  |     scn.render.border_max_x = coords2d_x[-1] + margin_width | ||||||
|  | 
 | ||||||
|  |     scn.render.border_min_y = coords2d_y[0] - margin_height | ||||||
|  |     scn.render.border_max_y = coords2d_y[-1] + margin_height | ||||||
|  | 
 | ||||||
|  |     ## get clamped relative value | ||||||
|  |     # relative_bbox2d_coords = [ | ||||||
|  |     # (scn.render.border_min_x, scn.render.border_min_y), | ||||||
|  |     # (scn.render.border_min_x, scn.render.border_max_y), | ||||||
|  |     # (scn.render.border_max_x, scn.render.border_max_y), | ||||||
|  |     # (scn.render.border_max_x, scn.render.border_min_y), | ||||||
|  |     # ] | ||||||
|  | 
 | ||||||
|  |     pixel_bbox2d_coords = [ | ||||||
|  |     (scn.render.border_min_x*scn.render.resolution_x, scn.render.border_min_y*scn.render.resolution_y), | ||||||
|  |     (scn.render.border_min_x*scn.render.resolution_x, scn.render.border_max_y*scn.render.resolution_y), | ||||||
|  |     (scn.render.border_max_x*scn.render.resolution_x, scn.render.border_max_y*scn.render.resolution_y), | ||||||
|  |     (scn.render.border_max_x*scn.render.resolution_x, scn.render.border_min_y*scn.render.resolution_y), | ||||||
|  |     ] | ||||||
|  |     # if export_json: | ||||||
|  |     #     export_crop_to_json(scn) | ||||||
|  |     return pixel_bbox2d_coords | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def get_gp_box_all_frame(ob, cam=None): | ||||||
|  |     '''set crop to object bounding box considering whole animation. Cam should not be animated (render in bg_cam) | ||||||
|  |     return 2d bbox in pixels  | ||||||
|  |     ''' | ||||||
|  |     from bpy_extras.object_utils import world_to_camera_view | ||||||
|  |     coords_cam_list = [] | ||||||
|  |     scn = bpy.context.scene | ||||||
|  |     cam = cam or scn.camera | ||||||
|  |     start = time() | ||||||
|  |      | ||||||
|  |     if ob.animation_data and ob.animation_data.action: # use frame set on all frames | ||||||
|  |         print(f'{ob.name} has anim') | ||||||
|  |         # frame_nums = sorted(list(set([f.frame_number for l in ob.data.layers if len(l.frames) for f in l.frames if len(f.strokes) and scn.frame_start <= f.frame_number <= scn.frame_end])))         | ||||||
|  |         for num in range(scn.frame_start, scn.frame_end+1): | ||||||
|  |             scn.frame_set(num) | ||||||
|  |             for l in ob.data.layers: | ||||||
|  |                 if l.hide or l.opacity == 0.0: | ||||||
|  |                     continue | ||||||
|  |                 if l.active_frame: | ||||||
|  |                     for s in l.active_frame.strokes: | ||||||
|  |                         if len(s.points) == 1: # skip isolated points | ||||||
|  |                             continue | ||||||
|  |                         coords_cam_list += [world_to_camera_view(scn, cam, ob.matrix_world @ p.co) for p in s.points] | ||||||
|  |     else: | ||||||
|  |         # if object is not animated no need to frame_set to update object position | ||||||
|  |         print(f'{ob.name} no anim') | ||||||
|  |         for l in ob.data.layers: | ||||||
|  |             if l.hide or l.opacity == 0.0: | ||||||
|  |                 continue | ||||||
|  |             for f in l.frames: | ||||||
|  |                 if not (scn.frame_start <= f.frame_number <= scn.frame_end): | ||||||
|  |                     continue | ||||||
|  |                 for s in f.strokes: | ||||||
|  |                     if len(s.points) == 1: # skip isolated points | ||||||
|  |                         continue | ||||||
|  |                     coords_cam_list += [world_to_camera_view(scn, cam, ob.matrix_world @ p.co) for p in s.points] | ||||||
|  |      | ||||||
|  |     print(time() - start) # Dbg-time | ||||||
|  |     return coords_cam_list | ||||||
|  | 
 | ||||||
|  | def has_anim(ob): | ||||||
|  |     # TODO make a better check (check if there is only one key in each channel, count as not animated) | ||||||
|  |     return ob.animation_data and ob.animation_data.action | ||||||
|  | 
 | ||||||
|  | def get_gp_box_all_frame_selection(oblist=None, scn=None, cam=None): | ||||||
|  |     ''' | ||||||
|  |     get points of all selection | ||||||
|  |     return 2d bbox in pixels  | ||||||
|  |     ''' | ||||||
|  | 
 | ||||||
|  |     from bpy_extras.object_utils import world_to_camera_view | ||||||
|  | 
 | ||||||
|  |     coords_cam_list = [] | ||||||
|  |     scn = scn or bpy.context.scene | ||||||
|  |     oblist = oblist or [o for o in scn.objects if o.select_get()] | ||||||
|  | 
 | ||||||
|  |     cam = cam or scn.camera | ||||||
|  |     start = time() | ||||||
|  |      | ||||||
|  |     if any(has_anim(ob) for ob in oblist): | ||||||
|  |         print(f'at least one is animated: {oblist}') | ||||||
|  |         for num in range(scn.frame_start, scn.frame_end+1): | ||||||
|  |             scn.frame_set(num) | ||||||
|  |             for ob in oblist: | ||||||
|  |                 for l in ob.data.layers: | ||||||
|  |                     if l.hide or l.opacity == 0.0: | ||||||
|  |                         continue | ||||||
|  |                     if not l.active_frame: | ||||||
|  |                         continue | ||||||
|  |                     for s in l.active_frame.strokes: | ||||||
|  |                         if len(s.points) == 1: # skip isolated points | ||||||
|  |                             continue | ||||||
|  |                         coords_cam_list += [world_to_camera_view(scn, cam, ob.matrix_world @ p.co) for p in s.points] | ||||||
|  |     else: | ||||||
|  |         print(f'No anim') | ||||||
|  |         for ob in oblist: | ||||||
|  |             # if object is not animated no need to frame_set to update object position | ||||||
|  |             for l in ob.data.layers: | ||||||
|  |                 if l.hide or l.opacity == 0.0: | ||||||
|  |                     continue | ||||||
|  |                 for f in l.frames: | ||||||
|  |                     if not (scn.frame_start <= f.frame_number <= scn.frame_end): | ||||||
|  |                         continue | ||||||
|  |                     for s in f.strokes: | ||||||
|  |                         if len(s.points) == 1: # skip isolated points | ||||||
|  |                             continue | ||||||
|  |                         coords_cam_list += [world_to_camera_view(scn, cam, ob.matrix_world @ p.co) for p in s.points] | ||||||
|  |      | ||||||
|  |     print(f'{len(coords_cam_list)} gp points listed {time() - start:.1f}s') | ||||||
|  |     return coords_cam_list | ||||||
|  | 
 | ||||||
|  | def get_bbox_2d(ob, cam=None): | ||||||
|  |     from bpy_extras.object_utils import world_to_camera_view | ||||||
|  |     scn = bpy.context.scene  | ||||||
|  |     cam = cam or scn.camera | ||||||
|  |     coords2d = [world_to_camera_view(scn, cam, p) for p in get_bbox_3d(ob)]         | ||||||
|  |     coords2d_x = sorted([c[0] for c in coords2d]) | ||||||
|  |     coords2d_y = sorted([c[1] for c in coords2d]) | ||||||
|  | 
 | ||||||
|  |     bbox2d_coords = [ | ||||||
|  |         (coords2d_x[0], coords2d_y[0]), | ||||||
|  |         (coords2d_x[0], coords2d_y[-1]), | ||||||
|  |         (coords2d_x[-1], coords2d_y[-1]), | ||||||
|  |         (coords2d_x[-1], coords2d_y[0]), | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |     return [Vector(b) for b in bbox2d_coords] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def set_box_from_selected_objects(scn=None, cam=None, export_json=False): | ||||||
|  |     scn = scn or bpy.context.scene  | ||||||
|  |     cam = cam or scn.camera | ||||||
|  |      | ||||||
|  |     selection = [o for o in scn.objects if o.select_get()] # selected_objects | ||||||
|  |     coords = get_gp_box_all_frame_selection(oblist=selection, scn=scn, cam=cam) | ||||||
|  |     _bbox_px = set_border_region_from_coord(coords, margin=30, scn=scn, export_json=export_json) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def get_collection_childs_recursive(col, cols=[]): | ||||||
|  |     '''return a list of all the sub-collections in passed col''' | ||||||
|  |     for sub in col.children: | ||||||
|  |         if sub not in cols: | ||||||
|  |             cols.append(sub) | ||||||
|  |         if len(sub.children): | ||||||
|  |             cols = get_collection_childs_recursive(sub, cols) | ||||||
|  |     return cols | ||||||
|  | 
 | ||||||
|  | def unlink_objects_from_scene(oblist, scn): | ||||||
|  |     all_col = [scn.collection] | ||||||
|  |     all_col += get_collection_childs_recursive(scn.collection) | ||||||
|  |     for col in all_col: | ||||||
|  |         for ob in reversed(col.objects): | ||||||
|  |             if ob in oblist: | ||||||
|  |                 col.objects.unlink(ob) | ||||||
|  | 
 | ||||||
|  | def remove_scene_nodes_by_obj_names(scn, name_list, negative=False): | ||||||
|  |     for n in reversed(scn.node_tree.nodes): | ||||||
|  |         if negative: | ||||||
|  |             if (n.parent and n.parent.label not in name_list) or (n.type == 'FRAME' and n.label not in name_list): | ||||||
|  |                 scn.node_tree.nodes.remove(n) | ||||||
|  |         else: | ||||||
|  |             if (n.parent and n.parent.label in name_list) or (n.type == 'FRAME' and n.label in name_list): | ||||||
|  |                 scn.node_tree.nodes.remove(n) | ||||||
|  | 
 | ||||||
|  | def split_object_to_scene(): | ||||||
|  |     '''Create a new scene from object selection''' | ||||||
|  | 
 | ||||||
|  |     active = bpy.context.object | ||||||
|  |     scene_name = active.name | ||||||
|  |     objs = [o for o in bpy.context.selected_objects] | ||||||
|  |      | ||||||
|  |     if bpy.data.scenes.get(scene_name): | ||||||
|  |         print(f'Scene "{scene_name}" Already Exists') | ||||||
|  |         raise Exception(f'Scene "{scene_name}" Already Exists') | ||||||
|  | 
 | ||||||
|  |     src = bpy.context.scene | ||||||
|  | 
 | ||||||
|  |     bpy.ops.scene.new(type='LINK_COPY') | ||||||
|  |     new = bpy.context.scene | ||||||
|  |     new.name = scene_name | ||||||
|  | 
 | ||||||
|  |     ## unlink unwanted objects from collection | ||||||
|  |     all_col = [new.collection] | ||||||
|  |     all_col += get_collection_childs_recursive(new.collection) | ||||||
|  |     for col in all_col: | ||||||
|  |         for sob in reversed(col.objects): | ||||||
|  |             if sob.type in ('CAMERA', 'LIGHT'): | ||||||
|  |                 continue | ||||||
|  |             if sob not in objs: | ||||||
|  |                 col.objects.unlink(sob) | ||||||
|  |      | ||||||
|  |     frame_names = [n.label for n in new.node_tree.nodes if n.type == 'FRAME' if new.objects.get(n.label)] | ||||||
|  |     remove_scene_nodes_by_obj_names(new, frame_names, negative=True) | ||||||
|  | 
 | ||||||
|  |     bpy.ops.gp.clean_compo_tree() | ||||||
|  | 
 | ||||||
|  |     # add crop | ||||||
|  |     new.render.use_border = True | ||||||
|  |     new.render.use_crop_to_border = True | ||||||
|  |     new.render.use_compositing = True | ||||||
|  |     new.render.use_sequencer = False | ||||||
|  | 
 | ||||||
|  |     ## remove asset from original scene | ||||||
|  |     #src_frame_names = [n.label for n in src.node_tree.nodes if n.type == 'FRAME' and n.label in [o.name for o in objs]] | ||||||
|  |     #remove_scene_nodes_by_obj_names(src, src_frame_names)         | ||||||
|  |     remove_scene_nodes_by_obj_names(src, frame_names, negative=False) | ||||||
|  | 
 | ||||||
|  |     # unlink objects ? | ||||||
|  |     unlink_objects_from_scene(objs, src) | ||||||
|  | 
 | ||||||
|  |     # border to GP objects of the scene | ||||||
|  |     gp_objs = [o for o in new.objects if o.type == 'GPENCIL'] | ||||||
|  |     coords = get_gp_box_all_frame_selection(oblist=gp_objs, scn=new, cam=new.camera) | ||||||
|  |     set_border_region_from_coord(coords, margin=30, scn=new, export_json=True) | ||||||
|  | 
 | ||||||
|  |     export_crop_to_json() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | """ | ||||||
|  | def split_object_to_scene(): | ||||||
|  |     '''Create a new scene from selection''' | ||||||
|  | 
 | ||||||
|  |     # send objects in a new render scene | ||||||
|  |     ## define new scene name with active object names | ||||||
|  |     active = bpy.context.object | ||||||
|  |     scene_name = active.name | ||||||
|  |     objs = [o for o in bpy.context.selected_objects] | ||||||
|  |      | ||||||
|  |     rd_scn = bpy.data.scenes.get('Render') | ||||||
|  |     ## create scene and copy settings from render scene or current | ||||||
|  |     # src_scn = bpy.data.scenes.get('Render') | ||||||
|  |     # src_scn = src_scn or bpy.context.scene | ||||||
|  |     # if src_scn.name == scene_name: | ||||||
|  |     #     print('! Problem ! Trying to to create new render scene without source') | ||||||
|  |     #     return | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     ## From current scene (might be Render OR Scene) | ||||||
|  |     src_scn = bpy.context.scene | ||||||
|  | 
 | ||||||
|  |     new = new_scene_from(scene_name, src_scn=src_scn, regen=True) # crop=True, link_cam=True, link_light=True | ||||||
|  | 
 | ||||||
|  |     for ob in objs: | ||||||
|  |         new.collection.objects.link(ob) | ||||||
|  |         if ob.type == 'GPENCIL': | ||||||
|  |         # recreate VL | ||||||
|  |             vl_names = [l.viewlayer_render for l in ob.data.layers if l.viewlayer_render] | ||||||
|  |             for names in vl_names: | ||||||
|  |                 new.view_layers.new(names) | ||||||
|  |         # get_set_viewlayer_from_gp(ob, l, scene=new) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def set_crop_bbox_2d(ob, cam=None): | ||||||
|  |     '''Basic crop using bouding box on current frame''' | ||||||
|  |     from bpy_extras.object_utils import world_to_camera_view | ||||||
|  | 
 | ||||||
|  |     scn = bpy.context.scene  | ||||||
|  |     cam = cam or scn.camera | ||||||
|  |     # bbox = [ob.matrix_world @ Vector(b) for b in bbox_coords] | ||||||
|  |     coords2d = [world_to_camera_view(scn, cam, p) for p in get_bbox_3d(ob)] | ||||||
|  |          | ||||||
|  |     coords2d_x = sorted([c[0] for c in coords2d]) | ||||||
|  |     coords2d_y = sorted([c[1] for c in coords2d]) | ||||||
|  |     scn.render.border_min_x = coords2d_x[0] | ||||||
|  |     scn.render.border_max_x = coords2d_x[-1] | ||||||
|  |     scn.render.border_min_y = coords2d_y[0] | ||||||
|  |     scn.render.border_max_y = coords2d_y[-1] | ||||||
|  |     return | ||||||
|  | """ | ||||||
							
								
								
									
										10
									
								
								ui.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								ui.py
									
									
									
									
									
								
							| @ -118,6 +118,16 @@ class GPEXP_PT_gp_node_ui(Panel): | |||||||
|             col.operator('gp.clear_render_tree', icon='X', text='Clear Framed Nodes') |             col.operator('gp.clear_render_tree', icon='X', text='Clear Framed Nodes') | ||||||
|         col.operator('gp.clear_render_tree', icon='X', text='Clear & Delete Render Scene').mode = "COMPLETE" |         col.operator('gp.clear_render_tree', icon='X', text='Clear & Delete Render Scene').mode = "COMPLETE" | ||||||
| 
 | 
 | ||||||
|  |         layout.separator() | ||||||
|  |         layout.label(text='Sub Scenes:') | ||||||
|  |         layout.operator('gp.split_to_scene', icon='DUPLICATE', text='Split To Scene') | ||||||
|  | 
 | ||||||
|  |         row = layout.row(align=True) | ||||||
|  |         row.operator('gp.set_crop_from_selection', icon='CON_OBJECTSOLVER', text='Set Crop') | ||||||
|  |         row.operator('gp.export_crop_coord_to_json', icon='FILE', text='Export json') | ||||||
|  | 
 | ||||||
|  |         layout.operator('gp.render_all_scenes', icon='RENDER_ANIMATION', text='Render All Sub-Scene') | ||||||
|  | 
 | ||||||
|         layout.prop(prefs, 'advanced', text='Show Advanced Options') |         layout.prop(prefs, 'advanced', text='Show Advanced Options') | ||||||
|         # layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'ALL' |         # layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'ALL' | ||||||
|         # layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'SELECTED' |         # layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'SELECTED' | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user