Compare commits
8 Commits
256-numpy-
...
242-replay
Author | SHA1 | Date | |
---|---|---|---|
48eded1a9b | |||
3fbe769928 | |||
fe998214be | |||
3a4f691b8f | |||
0e20d35e7d | |||
8d15e69b50 | |||
8000ce9931 | |||
b96f600f15 |
@ -130,14 +130,29 @@ def load_pre_handler(dummy):
|
||||
if session and session.state in [STATE_ACTIVE, STATE_SYNCING]:
|
||||
bpy.ops.session.stop()
|
||||
|
||||
|
||||
@persistent
|
||||
def update_client_frame(scene):
|
||||
setting = bpy.context.window_manager.session
|
||||
if setting.replay_mode == 'TIMELINE' and \
|
||||
setting.replay_files and \
|
||||
scene.active_replay_file != setting.replay_frame_current :
|
||||
index = bpy.context.scene.active_replay_file
|
||||
bpy.ops.session.load(filepath=bpy.context.window_manager.session.replay_files[index].name,
|
||||
draw_users=True,
|
||||
replay=True)
|
||||
setting.replay_frame_current = index
|
||||
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
porcelain.update_user_metadata(session.repository, {
|
||||
'frame_current': scene.frame_current
|
||||
})
|
||||
|
||||
@persistent
|
||||
def post_frame_update(scene):
|
||||
if bpy.context.window_manager.session.replay_mode == 'TIMELINE' and \
|
||||
not bpy.context.scene.animation_data:
|
||||
bpy.context.scene.animation_data_create()
|
||||
bpy.context.scene.animation_data.action = bpy.data.actions.get('replay_action')
|
||||
|
||||
def register():
|
||||
bpy.app.handlers.undo_post.append(resolve_deps_graph)
|
||||
|
@ -35,6 +35,7 @@ from operator import itemgetter
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from time import gmtime, strftime
|
||||
from numpy import interp
|
||||
|
||||
from bpy.props import FloatProperty
|
||||
import bmesh
|
||||
@ -67,13 +68,51 @@ deleyables = []
|
||||
stop_modal_executor = False
|
||||
|
||||
|
||||
CLEARED_DATABLOCKS = ['actions', 'armatures', 'cache_files', 'cameras',
|
||||
'collections', 'curves', 'fonts',
|
||||
'grease_pencils', 'images', 'lattices', 'libraries',
|
||||
'lightprobes', 'lights', 'linestyles', 'masks',
|
||||
'materials', 'meshes', 'metaballs', 'movieclips',
|
||||
'node_groups', 'objects', 'paint_curves', 'particles',
|
||||
'scenes', 'shape_keys', 'sounds', 'speakers', 'texts',
|
||||
'textures', 'volumes', 'worlds']
|
||||
|
||||
PERSISTENT_DATABLOCKS = ['LineStyle', 'Dots Stroke', 'replay_action']
|
||||
|
||||
def clean_scene(ignored_datablocks: list = None):
|
||||
"""
|
||||
Delete all datablock of the scene except PERSISTENT_DATABLOCKS and ignored
|
||||
ones in ignored_datablocks.
|
||||
"""
|
||||
PERSISTENT_DATABLOCKS.extend(ignored_datablocks)
|
||||
# Avoid to trigger a runtime error by keeping the last scene
|
||||
PERSISTENT_DATABLOCKS.append(bpy.data.scenes[0].name)
|
||||
|
||||
for type_name in CLEARED_DATABLOCKS:
|
||||
type_collection = getattr(bpy.data, type_name)
|
||||
for datablock in type_collection:
|
||||
if datablock.name in PERSISTENT_DATABLOCKS:
|
||||
logging.debug(f"Skipping {datablock.name}")
|
||||
continue
|
||||
else:
|
||||
logging.debug(f"Removing {datablock.name}")
|
||||
type_collection.remove(datablock)
|
||||
|
||||
# Clear sequencer
|
||||
bpy.context.scene.sequence_editor_clear()
|
||||
|
||||
|
||||
def draw_user(username, metadata, radius=0.01, intensity=10.0):
|
||||
"""
|
||||
Generate a mesh representation of a given user frustum and
|
||||
sight of view.
|
||||
"""
|
||||
view_corners = metadata.get('view_corners')
|
||||
color = metadata.get('color', (1,1,1,0))
|
||||
objects = metadata.get('selected_objects', None)
|
||||
scene = metadata.get('scene_current', bpy.context.scene.name)
|
||||
|
||||
user_collection = bpy.data.collections.new(username)
|
||||
|
||||
# User Color
|
||||
user_mat = bpy.data.materials.new(username)
|
||||
user_mat.use_nodes = True
|
||||
@ -115,7 +154,7 @@ def draw_user(username, metadata, radius=0.01, intensity=10.0):
|
||||
|
||||
camera_obj.modifiers.new("wireframe", "SKIN")
|
||||
camera_obj.data.skin_vertices[0].data[0].use_root = True
|
||||
for v in camera_mesh.skin_vertices[0].data:
|
||||
for v in camera_obj.data.skin_vertices[0].data:
|
||||
v.radius = [radius, radius]
|
||||
|
||||
camera_mesh.materials.append(user_mat)
|
||||
@ -176,7 +215,7 @@ def draw_user(username, metadata, radius=0.01, intensity=10.0):
|
||||
|
||||
bbox_mesh.materials.append(user_mat)
|
||||
|
||||
bpy.context.scene.collection.children.link(user_collection)
|
||||
bpy.data.scenes[scene].collection.children.link(user_collection)
|
||||
|
||||
|
||||
def session_callback(name):
|
||||
@ -363,7 +402,7 @@ class SessionConnectOperator(bpy.types.Operator):
|
||||
|
||||
# Join a session
|
||||
if not active_server.use_admin_password:
|
||||
utils.clean_scene()
|
||||
clean_scene()
|
||||
|
||||
try:
|
||||
porcelain.remote_add(
|
||||
@ -434,7 +473,7 @@ class SessionHostOperator(bpy.types.Operator):
|
||||
|
||||
# Host a session
|
||||
if settings.init_method == 'EMPTY':
|
||||
utils.clean_scene()
|
||||
clean_scene()
|
||||
|
||||
try:
|
||||
# Init repository
|
||||
@ -498,7 +537,7 @@ class SessionInitOperator(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
if self.init_method == 'EMPTY':
|
||||
utils.clean_scene()
|
||||
clean_scene()
|
||||
|
||||
for scene in bpy.data.scenes:
|
||||
porcelain.add(session.repository, scene)
|
||||
@ -982,28 +1021,84 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||
description="Draw users in the scene",
|
||||
default=False,
|
||||
)
|
||||
|
||||
replay: bpy.props.BoolProperty(
|
||||
name="Replay mode",
|
||||
description="Enable replay functions",
|
||||
default=False,
|
||||
)
|
||||
|
||||
user_skin_radius: bpy.props.FloatProperty(
|
||||
name="Wireframe radius",
|
||||
description="Wireframe radius",
|
||||
default=0.005,
|
||||
default=0.01,
|
||||
)
|
||||
user_color_intensity: bpy.props.FloatProperty(
|
||||
name="Shading intensity",
|
||||
description="Shading intensity",
|
||||
default=10.0,
|
||||
default=1.0,
|
||||
)
|
||||
|
||||
files: bpy.props.CollectionProperty(
|
||||
name='File paths',
|
||||
type=bpy.types.OperatorFileListElement
|
||||
)
|
||||
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
from replication.repository import Repository
|
||||
|
||||
|
||||
runtime_settings = context.window_manager.session
|
||||
|
||||
# init the factory with supported types
|
||||
bpy_protocol = bl_types.get_data_translation_protocol()
|
||||
repo = Repository(bpy_protocol)
|
||||
repo.loads(self.filepath)
|
||||
utils.clean_scene()
|
||||
|
||||
try:
|
||||
repo.loads(self.filepath)
|
||||
except TypeError:
|
||||
# Load legacy snapshots
|
||||
db = pickle.load(gzip.open(self.filepath, "rb"))
|
||||
|
||||
nodes = db.get("nodes")
|
||||
|
||||
logging.info(f"Loading legacy {len(nodes)} node")
|
||||
repo.object_store.clear()
|
||||
for node, node_data in nodes:
|
||||
instance = Node(
|
||||
uuid=node,
|
||||
data=node_data.get('data'),
|
||||
owner=node_data.get('owner'),
|
||||
dependencies=node_data.get('dependencies'),
|
||||
state=FETCHED)
|
||||
# Patch data for compatibility
|
||||
type_id = node_data.get('str_type')[2:]
|
||||
if type_id == "File":
|
||||
type_id = "WindowsPath"
|
||||
instance.data['type_id'] = type_id
|
||||
repo.do_commit(instance)
|
||||
instance.state = FETCHED
|
||||
|
||||
# Persitstent collection
|
||||
ignored_datablocks = []
|
||||
|
||||
persistent_collection = bpy.data.collections.get("multiuser_timelapse")
|
||||
if self.replay and \
|
||||
runtime_settings.replay_persistent_collection and \
|
||||
persistent_collection:
|
||||
collection_repo = Repository(
|
||||
rdp=bpy_protocol,
|
||||
username="None")
|
||||
porcelain.add(collection_repo, persistent_collection)
|
||||
porcelain.commit(collection_repo, persistent_collection.uuid)
|
||||
for node in collection_repo.graph.values():
|
||||
ignored_datablocks.append(node.data.get('name'))
|
||||
|
||||
clean_scene(ignored_datablocks=ignored_datablocks)
|
||||
|
||||
nodes = [repo.graph.get(n) for n in repo.index_sorted]
|
||||
|
||||
@ -1018,6 +1113,37 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||
for node in nodes:
|
||||
porcelain.apply(repo, node.uuid)
|
||||
|
||||
if len(self.files) > 1:
|
||||
runtime_settings.replay_files.clear()
|
||||
context.scene.active_replay_file = len(self.files)-1
|
||||
directory = Path(self.filepath).parent
|
||||
file_list = [f['name'] for f in self.files]
|
||||
file_list.sort()
|
||||
for f in file_list:
|
||||
snap = runtime_settings.replay_files.add()
|
||||
snap.name = str(Path(directory, f))
|
||||
print(f)
|
||||
|
||||
if runtime_settings.replay_mode == 'TIMELINE':
|
||||
replay_action = bpy.data.actions.get('replay_action', bpy.data.actions.new('replay_action'))
|
||||
|
||||
bpy.context.scene.animation_data_create()
|
||||
bpy.context.scene.animation_data.action = replay_action
|
||||
if len(replay_action.fcurves) > 0 and replay_action.fcurves[0].data_path == 'active_replay_file':
|
||||
replay_fcurve = replay_action.fcurves[0]
|
||||
else:
|
||||
replay_fcurve = replay_action.fcurves.new('active_replay_file')
|
||||
|
||||
for p in reversed(replay_fcurve.keyframe_points):
|
||||
replay_fcurve.keyframe_points.remove(p, fast=True)
|
||||
|
||||
duration = runtime_settings.replay_duration
|
||||
file_count = len(self.files)-1
|
||||
for index in range(0, file_count):
|
||||
frame = interp(index, [0, file_count], [bpy.context.scene.frame_start, duration])
|
||||
replay_fcurve.keyframe_points.insert(frame, index)
|
||||
|
||||
|
||||
if self.draw_users:
|
||||
f = gzip.open(self.filepath, "rb")
|
||||
db = pickle.load(f)
|
||||
@ -1030,13 +1156,34 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||
if metadata:
|
||||
draw_user(username, metadata, radius=self.user_skin_radius, intensity=self.user_color_intensity)
|
||||
|
||||
# Relink the persistent collection
|
||||
if self.replay and persistent_collection:
|
||||
logging.info(f"Relinking {persistent_collection.name}")
|
||||
bpy.context.scene.collection.children.link(persistent_collection)
|
||||
|
||||
# Reasign scene action
|
||||
if self.replay and \
|
||||
runtime_settings.replay_mode == 'TIMELINE' and \
|
||||
not bpy.context.scene.animation_data :
|
||||
bpy.context.scene.animation_data_create()
|
||||
bpy.context.scene.animation_data.action = bpy.data.actions.get('replay_action')
|
||||
bpy.context.scene.frame_end = runtime_settings.replay_duration
|
||||
|
||||
# Reasign the scene camera
|
||||
if self.replay and \
|
||||
runtime_settings.replay_persistent_collection and \
|
||||
runtime_settings.replay_camera:
|
||||
bpy.context.scene.camera = runtime_settings.replay_camera
|
||||
|
||||
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
class SessionImportUser(bpy.types.Panel):
|
||||
class SESSION_PT_ImportUser(bpy.types.Panel):
|
||||
bl_space_type = 'FILE_BROWSER'
|
||||
bl_region_type = 'TOOL_PROPS'
|
||||
bl_label = "Users"
|
||||
@ -1283,6 +1430,26 @@ def menu_func_import(self, context):
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(SessionSaveBackupOperator.bl_idname, text='Multi-user session snapshot (.db)')
|
||||
|
||||
class SessionRenderReplay(bpy.types.Operator):
|
||||
bl_idname = "session.render_replay"
|
||||
bl_label = "Render Replay"
|
||||
bl_description = "Render Replay"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.window_manager.session.replay_files
|
||||
|
||||
def execute(self, context):
|
||||
base_path = str(context.scene.render.filepath)
|
||||
for frame in range(0,context.scene.frame_end):
|
||||
logging.info(f"Rendering frame {frame} to {base_path}_{frame}.png")
|
||||
context.scene.frame_current = frame
|
||||
filename = Path(bpy.context.window_manager.session.replay_files[context.scene.active_replay_file].name)
|
||||
context.scene.render.filepath = f"{base_path}{frame}_{filename.stem}"
|
||||
bpy.ops.render.render(write_still=True)
|
||||
|
||||
context.scene.render.filepath = base_path
|
||||
return {'FINISHED'}
|
||||
|
||||
classes = (
|
||||
SessionConnectOperator,
|
||||
@ -1300,7 +1467,7 @@ classes = (
|
||||
SessionNotifyOperator,
|
||||
SessionSaveBackupOperator,
|
||||
SessionLoadSaveOperator,
|
||||
SessionImportUser,
|
||||
SESSION_PT_ImportUser,
|
||||
SessionStopAutoSaveOperator,
|
||||
SessionPurgeOperator,
|
||||
SessionPresetServerAdd,
|
||||
@ -1309,6 +1476,7 @@ classes = (
|
||||
RefreshServerStatus,
|
||||
GetDoc,
|
||||
FirstLaunch,
|
||||
SessionRenderReplay,
|
||||
)
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@ import bpy
|
||||
import string
|
||||
import re
|
||||
import os
|
||||
from numpy import interp
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
@ -99,6 +100,77 @@ def update_directory(self, context):
|
||||
def set_log_level(self, value):
|
||||
logging.getLogger().setLevel(value)
|
||||
|
||||
def set_active_replay(self, value):
|
||||
files_count = len(bpy.context.window_manager.session.replay_files)
|
||||
|
||||
if files_count == 0:
|
||||
return
|
||||
|
||||
max_index = files_count-1
|
||||
|
||||
if value > max_index:
|
||||
value = max_index
|
||||
|
||||
if hasattr(self, 'active_replay_file'):
|
||||
self["active_replay_file"] = value
|
||||
else:
|
||||
self.active_replay_file = value
|
||||
|
||||
if bpy.context.window_manager.session.replay_mode == 'MANUAL':
|
||||
bpy.ops.session.load(
|
||||
filepath=bpy.context.window_manager.session.replay_files[value].name,
|
||||
draw_users=True,
|
||||
replay=True)
|
||||
|
||||
def get_active_replay(self):
|
||||
return self.get('active_replay_file', 0)
|
||||
|
||||
|
||||
def set_replay_persistent_collection(self, value):
|
||||
if hasattr(self, 'replay_persistent_collection'):
|
||||
self["replay_persistent_collection"] = value
|
||||
else:
|
||||
self.replay_persistent_collection = value
|
||||
|
||||
collection = bpy.data.collections.get("multiuser_timelapse", None)
|
||||
|
||||
if collection is None and value:
|
||||
collection = bpy.data.collections.new('multiuser_timelapse')
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
elif collection and not value:
|
||||
for o in collection.objects:
|
||||
bpy.data.objects.remove(o)
|
||||
bpy.data.collections.remove(collection)
|
||||
|
||||
def get_replay_persistent_collection(self):
|
||||
return self.get('replay_persistent_collection', False)
|
||||
|
||||
def set_replay_duration(self, value):
|
||||
if hasattr(self, 'replay_duration'):
|
||||
self["replay_duration"] = value
|
||||
else:
|
||||
self.replay_duration = value
|
||||
|
||||
# Update the animation fcurve
|
||||
replay_action = bpy.data.actions.get('replay_action')
|
||||
replay_fcurve = None
|
||||
|
||||
for fcurve in replay_action.fcurves:
|
||||
if fcurve.data_path == 'active_replay_file':
|
||||
replay_fcurve = fcurve
|
||||
|
||||
if replay_fcurve:
|
||||
for p in reversed(replay_fcurve.keyframe_points):
|
||||
replay_fcurve.keyframe_points.remove(p, fast=True)
|
||||
|
||||
bpy.context.scene.frame_end = value
|
||||
files_count = len(bpy.context.window_manager.session.replay_files)-1
|
||||
for index in range(0, files_count):
|
||||
frame = interp(index,[0, files_count],[bpy.context.scene.frame_start, value])
|
||||
replay_fcurve.keyframe_points.insert(frame, index)
|
||||
|
||||
def get_replay_duration(self):
|
||||
return self.get('replay_duration', 10)
|
||||
|
||||
def get_log_level(self):
|
||||
return logging.getLogger().level
|
||||
@ -687,6 +759,37 @@ class SessionProps(bpy.types.PropertyGroup):
|
||||
is_host: bpy.props.BoolProperty(
|
||||
default=False
|
||||
)
|
||||
replay_files: bpy.props.CollectionProperty(
|
||||
name='File paths',
|
||||
type=bpy.types.OperatorFileListElement
|
||||
)
|
||||
replay_persistent_collection: bpy.props.BoolProperty(
|
||||
name="replay_persistent_collection",
|
||||
description='Enable a collection that persist accross frames loading',
|
||||
get=get_replay_persistent_collection,
|
||||
set=set_replay_persistent_collection,
|
||||
)
|
||||
replay_mode: bpy.props.EnumProperty(
|
||||
name='replay method',
|
||||
description='Replay in keyframe (timeline) or manually',
|
||||
items={
|
||||
('TIMELINE', 'TIMELINE', 'Replay from the timeline.'),
|
||||
('MANUAL', 'MANUAL', 'Replay manually, from the replay frame widget.')},
|
||||
default='TIMELINE')
|
||||
replay_duration: bpy.props.IntProperty(
|
||||
name='replay interval',
|
||||
default=250,
|
||||
min=10,
|
||||
set=set_replay_duration,
|
||||
get=get_replay_duration,
|
||||
)
|
||||
replay_frame_current: bpy.props.IntProperty(
|
||||
name='replay_frame_current',
|
||||
)
|
||||
replay_camera: bpy.props.PointerProperty(
|
||||
name='Replay camera',
|
||||
type=bpy.types.Object
|
||||
)
|
||||
|
||||
|
||||
classes = (
|
||||
@ -712,6 +815,16 @@ def register():
|
||||
|
||||
# at launch server presets
|
||||
prefs.generate_default_presets()
|
||||
|
||||
bpy.types.Scene.active_replay_file = bpy.props.IntProperty(
|
||||
name="active_replay_file",
|
||||
default=0,
|
||||
min=0,
|
||||
description='Active snapshot',
|
||||
set=set_active_replay,
|
||||
get=get_active_replay,
|
||||
options={'ANIMATABLE'}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -720,3 +833,5 @@ def unregister():
|
||||
|
||||
for cls in reversed(classes):
|
||||
unregister_class(cls)
|
||||
|
||||
del bpy.types.Scene.active_replay_file
|
||||
|
@ -564,10 +564,42 @@ class SESSION_PT_sync(bpy.types.Panel):
|
||||
row.prop(settings.sync_flags, "sync_active_camera", text="",icon_only=True, icon='VIEW_CAMERA')
|
||||
|
||||
|
||||
class SESSION_PT_replay(bpy.types.Panel):
|
||||
bl_idname = "MULTIUSER_REPLAY_PT_panel"
|
||||
bl_label = "Replay"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel'
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.window_manager.session.replay_files
|
||||
|
||||
def draw_header(self, context):
|
||||
self.layout.label(text="", icon='RECOVER_LAST')
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
settings = context.window_manager.session
|
||||
row= layout.row()
|
||||
row.prop(settings,'replay_mode', toggle=True, expand=True)
|
||||
row= layout.row()
|
||||
if settings.replay_mode == 'MANUAL':
|
||||
row.prop(bpy.context.scene, 'active_replay_file', text="Snapshot index")
|
||||
else:
|
||||
row.prop(settings, 'replay_duration', text="Replay Duration")
|
||||
row= layout.row()
|
||||
row.prop(settings, 'replay_persistent_collection', text="persistent collection", toggle=True, icon='OUTLINER_COLLECTION')
|
||||
|
||||
if settings.replay_persistent_collection:
|
||||
row= layout.row()
|
||||
row.prop(settings, 'replay_camera', text="", icon='VIEW_CAMERA')
|
||||
|
||||
class SESSION_PT_repository(bpy.types.Panel):
|
||||
bl_idname = "MULTIUSER_PROPERTIES_PT_panel"
|
||||
bl_label = "Repository"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel'
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
@ -709,6 +741,7 @@ classes = (
|
||||
SESSION_PT_sync,
|
||||
SESSION_PT_repository,
|
||||
VIEW3D_PT_overlay_session,
|
||||
SESSION_PT_replay,
|
||||
)
|
||||
|
||||
register, unregister = bpy.utils.register_classes_factory(classes)
|
||||
|
@ -38,15 +38,6 @@ from replication.constants import (STATE_ACTIVE, STATE_AUTH,
|
||||
STATE_LOBBY,
|
||||
CONNECTING)
|
||||
|
||||
CLEARED_DATABLOCKS = ['actions', 'armatures', 'cache_files', 'cameras',
|
||||
'collections', 'curves', 'filepath', 'fonts',
|
||||
'grease_pencils', 'images', 'lattices', 'libraries',
|
||||
'lightprobes', 'lights', 'linestyles', 'masks',
|
||||
'materials', 'meshes', 'metaballs', 'movieclips',
|
||||
'node_groups', 'objects', 'paint_curves', 'particles',
|
||||
'scenes', 'shape_keys', 'sounds', 'speakers', 'texts',
|
||||
'textures', 'volumes', 'worlds']
|
||||
|
||||
def find_from_attr(attr_name, attr_value, list):
|
||||
for item in list:
|
||||
if getattr(item, attr_name, None) == attr_value:
|
||||
@ -108,26 +99,6 @@ def get_state_str(state):
|
||||
return state_str
|
||||
|
||||
|
||||
def clean_scene():
|
||||
for type_name in CLEARED_DATABLOCKS:
|
||||
sub_collection_to_avoid = [
|
||||
bpy.data.linestyles.get('LineStyle'),
|
||||
bpy.data.materials.get('Dots Stroke')
|
||||
]
|
||||
|
||||
type_collection = getattr(bpy.data, type_name)
|
||||
items_to_remove = [i for i in type_collection if i not in sub_collection_to_avoid]
|
||||
for item in items_to_remove:
|
||||
try:
|
||||
type_collection.remove(item)
|
||||
logging.info(item.name)
|
||||
except:
|
||||
continue
|
||||
|
||||
# Clear sequencer
|
||||
bpy.context.scene.sequence_editor_clear()
|
||||
|
||||
|
||||
def get_selected_objects(scene, active_view_layer):
|
||||
return [obj.uuid for obj in scene.objects if obj.select_get(view_layer=active_view_layer)]
|
||||
|
||||
|
Reference in New Issue
Block a user