Files
multi-user/multi_user/io_bpy/bl_scene.py

570 lines
18 KiB
Python
Raw Normal View History

2020-03-20 14:56:50 +01:00
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####
import logging
2021-01-28 23:59:19 +01:00
from pathlib import Path
import bpy
import mathutils
2020-09-25 11:23:36 +02:00
from deepdiff import DeepDiff
from replication.constants import DIFF_JSON, MODIFIED
from replication.protocol import ReplicatedDatablock
from replication.objects import Node
2020-04-02 18:42:41 +02:00
2021-01-29 11:39:00 +01:00
from ..utils import flush_history
from .bl_collection import (dump_collection_children, dump_collection_objects,
load_collection_childrens, load_collection_objects,
resolve_collection_dependencies)
from .bl_action import (load_animation_data,
dump_animation_data,
2021-03-26 16:14:27 +01:00
resolve_animation_dependencies)
from .bl_datablock import stamp_uuid
2021-01-28 23:59:19 +01:00
from .bl_file import get_filepath
from .dump_anything import Dumper, Loader
from ..preferences import get_preferences
2020-10-14 19:11:32 +02:00
RENDER_SETTINGS = [
'dither_intensity',
'engine',
'film_transparent',
'filter_size',
'fps',
'fps_base',
'frame_map_new',
'frame_map_old',
'hair_subdiv',
'hair_type',
'line_thickness',
'line_thickness_mode',
'metadata_input',
'motion_blur_shutter',
'pixel_aspect_x',
'pixel_aspect_y',
'preview_pixel_size',
'preview_start_resolution',
'resolution_percentage',
'resolution_x',
'resolution_y',
'sequencer_gl_preview',
'use_bake_clear',
'use_bake_lores_mesh',
'use_bake_multires',
'use_bake_selected_to_active',
'use_bake_user_scale',
'use_border',
'use_compositing',
'use_crop_to_border',
'use_file_extension',
'use_freestyle',
'use_full_sample',
'use_high_quality_normals',
'use_lock_interface',
'use_motion_blur',
'use_multiview',
'use_sequencer',
'use_sequencer_override_scene_strip',
'use_single_layer',
'views_format',
]
EVEE_SETTINGS = [
'gi_diffuse_bounces',
'gi_cubemap_resolution',
'gi_visibility_resolution',
'gi_irradiance_smoothing',
'gi_glossy_clamp',
'gi_filter_quality',
'gi_show_irradiance',
'gi_show_cubemaps',
'gi_irradiance_display_size',
'gi_cubemap_display_size',
'gi_auto_bake',
'taa_samples',
'taa_render_samples',
'use_taa_reprojection',
'sss_samples',
'sss_jitter_threshold',
'use_ssr',
'use_ssr_refraction',
'use_ssr_halfres',
'ssr_quality',
'ssr_max_roughness',
'ssr_thickness',
'ssr_border_fade',
'ssr_firefly_fac',
'volumetric_start',
'volumetric_end',
'volumetric_tile_size',
'volumetric_samples',
'volumetric_sample_distribution',
'use_volumetric_lights',
'volumetric_light_clamp',
'use_volumetric_shadows',
'volumetric_shadow_samples',
'use_gtao',
'use_gtao_bent_normals',
'use_gtao_bounce',
'gtao_factor',
'gtao_quality',
'gtao_distance',
'bokeh_max_size',
'bokeh_threshold',
'use_bloom',
'bloom_threshold',
'bloom_color',
'bloom_knee',
'bloom_radius',
'bloom_clamp',
'bloom_intensity',
'use_motion_blur',
'motion_blur_shutter',
'motion_blur_depth_scale',
'motion_blur_max',
'motion_blur_steps',
'shadow_cube_size',
'shadow_cascade_size',
'use_shadow_high_bitdepth',
'gi_diffuse_bounces',
'gi_cubemap_resolution',
'gi_visibility_resolution',
'gi_irradiance_smoothing',
'gi_glossy_clamp',
'gi_filter_quality',
'gi_show_irradiance',
'gi_show_cubemaps',
'gi_irradiance_display_size',
'gi_cubemap_display_size',
'gi_auto_bake',
'taa_samples',
'taa_render_samples',
'use_taa_reprojection',
'sss_samples',
'sss_jitter_threshold',
'use_ssr',
'use_ssr_refraction',
'use_ssr_halfres',
'ssr_quality',
'ssr_max_roughness',
'ssr_thickness',
'ssr_border_fade',
'ssr_firefly_fac',
'volumetric_start',
'volumetric_end',
'volumetric_tile_size',
'volumetric_samples',
'volumetric_sample_distribution',
'use_volumetric_lights',
'volumetric_light_clamp',
'use_volumetric_shadows',
'volumetric_shadow_samples',
'use_gtao',
'use_gtao_bent_normals',
'use_gtao_bounce',
'gtao_factor',
'gtao_quality',
'gtao_distance',
'bokeh_max_size',
'bokeh_threshold',
'use_bloom',
'bloom_threshold',
'bloom_color',
'bloom_knee',
'bloom_radius',
'bloom_clamp',
'bloom_intensity',
'use_motion_blur',
'motion_blur_shutter',
'motion_blur_depth_scale',
'motion_blur_max',
'motion_blur_steps',
'shadow_cube_size',
'shadow_cascade_size',
'use_shadow_high_bitdepth',
]
CYCLES_SETTINGS = [
'shading_system',
'progressive',
'use_denoising',
'denoiser',
'use_square_samples',
'samples',
'aa_samples',
'diffuse_samples',
'glossy_samples',
'transmission_samples',
'ao_samples',
'mesh_light_samples',
'subsurface_samples',
'volume_samples',
'sampling_pattern',
'use_layer_samples',
'sample_all_lights_direct',
'sample_all_lights_indirect',
'light_sampling_threshold',
'use_adaptive_sampling',
'adaptive_threshold',
'adaptive_min_samples',
'min_light_bounces',
'min_transparent_bounces',
'caustics_reflective',
'caustics_refractive',
'blur_glossy',
'max_bounces',
'diffuse_bounces',
'glossy_bounces',
'transmission_bounces',
'volume_bounces',
'transparent_max_bounces',
'volume_step_rate',
'volume_max_steps',
'dicing_rate',
'max_subdivisions',
'dicing_camera',
'offscreen_dicing_scale',
'film_exposure',
'film_transparent_glass',
'film_transparent_roughness',
'filter_type',
'pixel_filter_type',
'filter_width',
'seed',
'use_animated_seed',
'sample_clamp_direct',
'sample_clamp_indirect',
'tile_order',
'use_progressive_refine',
'bake_type',
'use_camera_cull',
'camera_cull_margin',
'use_distance_cull',
'distance_cull_margin',
'motion_blur_position',
'rolling_shutter_type',
'rolling_shutter_duration',
'texture_limit',
'texture_limit_render',
'ao_bounces',
'ao_bounces_render',
]
VIEW_SETTINGS = [
'look',
'view_transform',
'exposure',
'gamma',
'use_curve_mapping',
'white_level',
'black_level'
]
2021-01-28 23:59:19 +01:00
def dump_sequence(sequence: bpy.types.Sequence) -> dict:
""" Dump a sequence to a dict
:arg sequence: sequence to dump
:type sequence: bpy.types.Sequence
:return dict:
"""
dumper = Dumper()
dumper.exclude_filter = [
'lock',
'select',
'select_left_handle',
'select_right_handle',
'strobe'
]
dumper.depth = 1
data = dumper.dump(sequence)
# TODO: Support multiple images
if sequence.type == 'IMAGE':
data['filenames'] = [e.filename for e in sequence.elements]
# Effect strip inputs
input_count = getattr(sequence, 'input_count', None)
if input_count:
for n in range(input_count):
input_name = f"input_{n+1}"
data[input_name] = getattr(sequence, input_name).name
return data
def load_sequence(sequence_data: dict, sequence_editor: bpy.types.SequenceEditor):
""" Load sequence from dumped data
:arg sequence_data: sequence to dump
:type sequence_data:dict
:arg sequence_editor: root sequence editor
:type sequence_editor: bpy.types.SequenceEditor
"""
strip_type = sequence_data.get('type')
strip_name = sequence_data.get('name')
strip_channel = sequence_data.get('channel')
strip_frame_start = sequence_data.get('frame_start')
sequence = sequence_editor.sequences_all.get(strip_name, None)
if sequence is None:
if strip_type == 'SCENE':
strip_scene = bpy.data.scenes.get(sequence_data.get('scene'))
sequence = sequence_editor.sequences.new_scene(strip_name,
strip_scene,
strip_channel,
strip_frame_start)
2021-01-28 23:59:19 +01:00
elif strip_type == 'MOVIE':
filepath = get_filepath(Path(sequence_data['filepath']).name)
sequence = sequence_editor.sequences.new_movie(strip_name,
filepath,
strip_channel,
strip_frame_start)
2021-01-28 23:59:19 +01:00
elif strip_type == 'SOUND':
filepath = bpy.data.sounds[sequence_data['sound']].filepath
sequence = sequence_editor.sequences.new_sound(strip_name,
filepath,
strip_channel,
strip_frame_start)
2021-01-28 23:59:19 +01:00
elif strip_type == 'IMAGE':
images_name = sequence_data.get('filenames')
filepath = get_filepath(images_name[0])
sequence = sequence_editor.sequences.new_image(strip_name,
filepath,
strip_channel,
strip_frame_start)
2021-01-28 23:59:19 +01:00
# load other images
if len(images_name) > 1:
for img_idx in range(1, len(images_name)):
2021-01-28 23:59:19 +01:00
sequence.elements.append((images_name[img_idx]))
else:
seq = {}
2021-01-28 23:59:19 +01:00
for i in range(sequence_data['input_count']):
seq[f"seq{i+1}"] = sequence_editor.sequences_all.get(
sequence_data.get(f"input_{i+1}", None))
2021-01-28 23:59:19 +01:00
sequence = sequence_editor.sequences.new_effect(name=strip_name,
type=strip_type,
channel=strip_channel,
frame_start=strip_frame_start,
frame_end=sequence_data['frame_final_end'],
**seq)
2021-01-28 23:59:19 +01:00
loader = Loader()
# TODO: Support filepath updates
loader.exclure_filter = ['filepath', 'sound', 'filenames', 'fps']
2021-01-28 23:59:19 +01:00
loader.load(sequence, sequence_data)
sequence.select = False
class BlScene(ReplicatedDatablock):
2021-03-14 20:58:25 +01:00
is_root = True
bl_id = "scenes"
bl_class = bpy.types.Scene
2020-09-17 22:47:11 +02:00
bl_check_common = True
bl_icon = 'SCENE_DATA'
bl_reload_parent = False
@staticmethod
def construct(data: dict) -> object:
datablock = bpy.data.scenes.new(data["name"])
datablock.uuid = data.get("uuid")
2021-01-28 23:59:19 +01:00
return datablock
@staticmethod
def load(data: dict, datablock: object):
# Load other meshes metadata
2020-04-01 11:34:24 +02:00
loader = Loader()
loader.load(datablock, data)
# Load master collection
load_collection_objects(
data['collection']['objects'], datablock.collection)
load_collection_childrens(
data['collection']['children'], datablock.collection)
2019-09-27 16:55:26 +02:00
if 'world' in data.keys():
datablock.world = bpy.data.worlds[data['world']]
2019-10-14 14:42:23 +02:00
# Annotation
if 'grease_pencil' in data.keys():
datablock.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']]
2020-09-25 11:33:35 +02:00
if get_preferences().sync_flags.sync_render_settings:
2020-09-25 11:23:36 +02:00
if 'eevee' in data.keys():
loader.load(datablock.eevee, data['eevee'])
2019-09-27 16:55:26 +02:00
2020-09-25 11:23:36 +02:00
if 'cycles' in data.keys():
loader.load(datablock.cycles, data['cycles'])
2020-09-25 11:23:36 +02:00
if 'render' in data.keys():
loader.load(datablock.render, data['render'])
2020-09-25 11:23:36 +02:00
if 'view_settings' in data.keys():
loader.load(datablock.view_settings, data['view_settings'])
if datablock.view_settings.use_curve_mapping and \
'curve_mapping' in data['view_settings']:
# TODO: change this ugly fix
datablock.view_settings.curve_mapping.white_level = data[
2020-10-14 19:11:32 +02:00
'view_settings']['curve_mapping']['white_level']
datablock.view_settings.curve_mapping.black_level = data[
2020-10-14 19:11:32 +02:00
'view_settings']['curve_mapping']['black_level']
datablock.view_settings.curve_mapping.update()
2021-01-28 23:59:19 +01:00
# Sequencer
sequences = data.get('sequences')
2021-01-28 23:59:19 +01:00
if sequences:
# Create sequencer data
datablock.sequence_editor_create()
vse = datablock.sequence_editor
2021-01-29 11:39:00 +01:00
# Clear removed sequences
2021-01-28 23:59:19 +01:00
for seq in vse.sequences_all:
if seq.name not in sequences:
vse.sequences.remove(seq)
2021-01-29 11:39:00 +01:00
# Load existing sequences
2021-01-28 23:59:19 +01:00
for seq_name, seq_data in sequences.items():
load_sequence(seq_data, vse)
2021-01-29 11:39:00 +01:00
# If the sequence is no longer used, clear it
elif datablock.sequence_editor and not sequences:
datablock.sequence_editor_clear()
2021-01-28 23:59:19 +01:00
# FIXME: Find a better way after the replication big refacotoring
# Keep other user from deleting collection object by flushing their history
flush_history()
@staticmethod
def dump(datablock: object) -> dict:
stamp_uuid(datablock)
2019-09-27 16:55:26 +02:00
# Metadata
2020-04-01 11:34:24 +02:00
scene_dumper = Dumper()
2019-09-27 16:55:26 +02:00
scene_dumper.depth = 1
2020-03-26 17:13:59 +01:00
scene_dumper.include_filter = [
'name',
'world',
'id',
'grease_pencil',
'frame_start',
'frame_end',
'frame_step',
'uuid'
2020-03-26 17:13:59 +01:00
]
if get_preferences().sync_flags.sync_active_camera:
2020-09-25 11:33:35 +02:00
scene_dumper.include_filter.append('camera')
data = scene_dumper.dump(datablock)
dump_animation_data(datablock, data)
2019-09-27 16:55:26 +02:00
# Master collection
data['collection'] = {}
data['collection']['children'] = dump_collection_children(
datablock.collection)
data['collection']['objects'] = dump_collection_objects(
datablock.collection)
scene_dumper.depth = 1
scene_dumper.include_filter = None
2020-04-02 18:42:41 +02:00
# Render settings
if get_preferences().sync_flags.sync_render_settings:
2020-10-14 19:11:32 +02:00
scene_dumper.include_filter = RENDER_SETTINGS
data['render'] = scene_dumper.dump(datablock.render)
if datablock.render.engine == 'BLENDER_EEVEE':
2020-10-14 19:11:32 +02:00
scene_dumper.include_filter = EVEE_SETTINGS
data['eevee'] = scene_dumper.dump(datablock.eevee)
elif datablock.render.engine == 'CYCLES':
2020-10-14 19:11:32 +02:00
scene_dumper.include_filter = CYCLES_SETTINGS
data['cycles'] = scene_dumper.dump(datablock.cycles)
2020-10-14 19:11:32 +02:00
scene_dumper.include_filter = VIEW_SETTINGS
data['view_settings'] = scene_dumper.dump(datablock.view_settings)
if datablock.view_settings.use_curve_mapping:
data['view_settings']['curve_mapping'] = scene_dumper.dump(
datablock.view_settings.curve_mapping)
2020-04-02 18:42:41 +02:00
scene_dumper.depth = 5
scene_dumper.include_filter = [
'curves',
'points',
2020-10-14 19:11:32 +02:00
'location',
2020-04-02 18:42:41 +02:00
]
data['view_settings']['curve_mapping']['curves'] = scene_dumper.dump(
datablock.view_settings.curve_mapping.curves)
2021-01-28 23:59:19 +01:00
# Sequence
vse = datablock.sequence_editor
2021-01-28 23:59:19 +01:00
if vse:
dumped_sequences = {}
for seq in vse.sequences_all:
dumped_sequences[seq.name] = dump_sequence(seq)
data['sequences'] = dumped_sequences
return data
@staticmethod
def resolve_deps(datablock: object) -> [object]:
deps = []
2019-09-23 22:13:06 +02:00
2020-10-30 16:58:18 +01:00
# Master Collection
deps.extend(resolve_collection_dependencies(datablock.collection))
2019-09-23 22:13:06 +02:00
# world
if datablock.world:
deps.append(datablock.world)
2019-10-14 14:42:23 +02:00
# annotations
if datablock.grease_pencil:
deps.append(datablock.grease_pencil)
2019-10-14 14:42:23 +02:00
2020-10-30 16:58:18 +01:00
# Sequences
vse = datablock.sequence_editor
2021-01-28 23:59:19 +01:00
if vse:
for sequence in vse.sequences_all:
if sequence.type == 'MOVIE' and sequence.filepath:
deps.append(Path(bpy.path.abspath(sequence.filepath)))
elif sequence.type == 'SOUND' and sequence.sound:
deps.append(sequence.sound)
elif sequence.type == 'IMAGE':
for elem in sequence.elements:
sequence.append(
Path(bpy.path.abspath(sequence.directory),
elem.filename))
return deps
2020-09-25 11:23:36 +02:00
def diff(self):
exclude_path = []
if not get_preferences().sync_flags.sync_render_settings:
2020-09-25 11:23:36 +02:00
exclude_path.append("root['eevee']")
exclude_path.append("root['cycles']")
exclude_path.append("root['view_settings']")
exclude_path.append("root['render']")
if not get_preferences().sync_flags.sync_active_camera:
2020-09-25 11:33:35 +02:00
exclude_path.append("root['camera']")
return DeepDiff(self.data, self._dump(datablock=self.datablock), exclude_paths=exclude_path)