Merge branch '29-differential-revision' into 'develop'

various implementation refactoring

See merge request slumber/multi-user!24
This commit is contained in:
Swann Martinez
2020-03-27 17:50:34 +00:00
31 changed files with 1336 additions and 697 deletions

View File

@ -1,3 +1,21 @@
# ##### 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 #####
bl_info = { bl_info = {
"name": "Multi-User", "name": "Multi-User",
"author": "Swann Martinez", "author": "Swann Martinez",

View File

@ -1,3 +1,21 @@
# ##### 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 #####
__all__ = [ __all__ = [
'bl_object', 'bl_object',
'bl_mesh', 'bl_mesh',

View File

@ -1,11 +1,200 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
import copy import copy
import numpy as np
from enum import Enum
from .. import utils from .. import utils
from .bl_datablock import BlDatablock from .bl_datablock import BlDatablock
# WIP
ENUM_EASING_TYPE = [
'AUTO',
'EAS_IN',
'EASE_OUT',
'EASE_IN_OUT']
ENUM_HANDLE_TYPE = [
'FREE',
'ALIGNED',
'VECTOR',
'AUTO',
'AUTO_CLAMPED']
ENUM_INTERPOLATION_TYPE = [
'CONSTANT',
'LINEAR',
'BEZIER',
'SINE',
'QUAD',
'CUBIC',
'QUART',
'QUINT',
'EXPO',
'CIRC',
'BACK',
'BOUNCE',
'ELASTIC']
ENUM_KEY_TYPE = [
'KEYFRAME',
'BREAKDOWN',
'MOVING_HOLD',
'EXTREME',
'JITTER']
#TODO: Automatic enum and numpy dump and loading
def dump_fcurve(fcurve, use_numpy=True):
""" Dump a sigle curve to a dict
:arg fcurve: fcurve to dump
:type fcurve: bpy.types.FCurve
:arg use_numpy: use numpy to eccelerate dump
:type use_numpy: bool
:return: dict
"""
fcurve_data = {
"data_path": fcurve.data_path,
"dumped_array_index": fcurve.array_index,
"use_numpy": use_numpy
}
if use_numpy:
keyframes_count = len(fcurve.keyframe_points)
k_amplitude = np.empty(keyframes_count, dtype=np.float64)
fcurve.keyframe_points.foreach_get('amplitude', k_amplitude)
k_co = np.empty(keyframes_count*2, dtype=np.float64)
fcurve.keyframe_points.foreach_get('co', k_co)
k_back = np.empty(keyframes_count, dtype=np.float64)
fcurve.keyframe_points.foreach_get('back', k_back)
k_handle_left = np.empty(keyframes_count*2, dtype=np.float64)
fcurve.keyframe_points.foreach_get('handle_left', k_handle_left)
k_handle_right = np.empty(keyframes_count*2, dtype=np.float64)
fcurve.keyframe_points.foreach_get('handle_right', k_handle_right)
fcurve_data['amplitude'] = k_amplitude.tobytes()
fcurve_data['co'] = k_co.tobytes()
fcurve_data['back'] = k_back.tobytes()
fcurve_data['handle_left'] = k_handle_left.tobytes()
fcurve_data['handle_right'] = k_handle_right.tobytes()
fcurve_data['easing'] = [ENUM_EASING_TYPE.index(p.easing) for p in fcurve.keyframe_points]
fcurve_data['handle_left_type'] = [ENUM_HANDLE_TYPE.index(p.handle_left_type) for p in fcurve.keyframe_points]
fcurve_data['handle_right_type'] = [ENUM_HANDLE_TYPE.index(p.handle_right_type) for p in fcurve.keyframe_points]
fcurve_data['type'] = [ENUM_KEY_TYPE.index(p.type) for p in fcurve.keyframe_points]
fcurve_data['interpolation'] = [ENUM_INTERPOLATION_TYPE.index(p.interpolation) for p in fcurve.keyframe_points]
else: # Legacy method
dumper = utils.dump_anything.Dumper()
fcurve_data["keyframe_points"] = []
for k in fcurve.keyframe_points:
fcurve_data["keyframe_points"].append(
dumper.dump(k)
)
return fcurve_data
def load_fcurve(fcurve_data, fcurve):
""" Load a dumped fcurve
:arg fcurve_data: a dumped fcurve
:type fcurve_data: dict
:arg fcurve: fcurve to dump
:type fcurve: bpy.types.FCurve
"""
use_numpy = fcurve_data.get('use_numpy')
keyframe_points = fcurve.keyframe_points
# Remove all keyframe points
for i in range(len(keyframe_points)):
keyframe_points.remove(keyframe_points[0], fast=True)
if use_numpy:
k_amplitude = np.frombuffer(fcurve_data['amplitude'], dtype=np.float64)
keyframe_count = len(k_amplitude)
k_co = np.frombuffer(fcurve_data['co'], dtype=np.float64)
k_back = np.frombuffer(fcurve_data['back'], dtype=np.float64)
k_amplitude = np.frombuffer(fcurve_data['amplitude'], dtype=np.float64)
k_handle_left= np.frombuffer(fcurve_data['handle_left'], dtype=np.float64)
k_handle_right= np.frombuffer(fcurve_data['handle_right'], dtype=np.float64)
keyframe_points.add(keyframe_count)
keyframe_points.foreach_set('co',k_co)
keyframe_points.foreach_set('back',k_back)
keyframe_points.foreach_set('amplitude',k_amplitude)
keyframe_points.foreach_set('handle_left',k_handle_left)
keyframe_points.foreach_set('handle_right',k_handle_right)
for index, point in enumerate(keyframe_points):
point.type = ENUM_KEY_TYPE[fcurve_data['type'][index]]
point.easing = ENUM_EASING_TYPE[fcurve_data['easing'][index]]
point.handle_left_type = ENUM_HANDLE_TYPE[fcurve_data['handle_left_type'][index]]
point.handle_right_type = ENUM_HANDLE_TYPE[fcurve_data['handle_right_type'][index]]
point.interpolation = ENUM_INTERPOLATION_TYPE[fcurve_data['interpolation'][index]]
else:
# paste dumped keyframes
for dumped_keyframe_point in fcurve_data["keyframe_points"]:
if dumped_keyframe_point['type'] == '':
dumped_keyframe_point['type'] = 'KEYFRAME'
new_kf = keyframe_points.insert(
dumped_keyframe_point["co"][0],
dumped_keyframe_point["co"][1],
options={'FAST', 'REPLACE'}
)
keycache = copy.copy(dumped_keyframe_point)
keycache = utils.dump_anything.remove_items_from_dict(
keycache,
["co", "handle_left", "handle_right", 'type']
)
utils.dump_anything.load(new_kf, keycache)
new_kf.type = dumped_keyframe_point['type']
new_kf.handle_left = [
dumped_keyframe_point["handle_left"][0],
dumped_keyframe_point["handle_left"][1]
]
new_kf.handle_right = [
dumped_keyframe_point["handle_right"][0],
dumped_keyframe_point["handle_right"][1]
]
fcurve.update()
class BlAction(BlDatablock): class BlAction(BlDatablock):
bl_id = "actions" bl_id = "actions"
@ -14,30 +203,11 @@ class BlAction(BlDatablock):
bl_delay_apply = 1 bl_delay_apply = 1
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'ACTION_TWEAK' bl_icon = 'ACTION_TWEAK'
def construct(self, data): def _construct(self, data):
return bpy.data.actions.new(data["name"]) return bpy.data.actions.new(data["name"])
def load(self, data, target): def _load(self, data, target):
begin_frame = 100000
end_frame = -100000
for dumped_fcurve in data["fcurves"]:
begin_frame = min(
begin_frame,
min(
[begin_frame] + [dkp["co"][0] for dkp in dumped_fcurve["keyframe_points"]]
)
)
end_frame = max(
end_frame,
max(
[end_frame] + [dkp["co"][0] for dkp in dumped_fcurve["keyframe_points"]]
)
)
begin_frame = 0
loader = utils.dump_anything.Loader()
for dumped_fcurve in data["fcurves"]: for dumped_fcurve in data["fcurves"]:
dumped_data_path = dumped_fcurve["data_path"] dumped_data_path = dumped_fcurve["data_path"]
dumped_array_index = dumped_fcurve["dumped_array_index"] dumped_array_index = dumped_fcurve["dumped_array_index"]
@ -47,53 +217,13 @@ class BlAction(BlDatablock):
if fcurve is None: if fcurve is None:
fcurve = target.fcurves.new(dumped_data_path, index=dumped_array_index) fcurve = target.fcurves.new(dumped_data_path, index=dumped_array_index)
load_fcurve(dumped_fcurve, fcurve)
target.id_root = data['id_root']
# remove keyframes within dumped_action range def _dump(self, pointer=None):
for keyframe in reversed(fcurve.keyframe_points):
if end_frame >= (keyframe.co[0] + begin_frame ) >= begin_frame:
fcurve.keyframe_points.remove(keyframe, fast=True)
# paste dumped keyframes
for dumped_keyframe_point in dumped_fcurve["keyframe_points"]:
if dumped_keyframe_point['type'] == '':
dumped_keyframe_point['type'] = 'KEYFRAME'
new_kf = fcurve.keyframe_points.insert(
dumped_keyframe_point["co"][0] - begin_frame,
dumped_keyframe_point["co"][1],
options={'FAST', 'REPLACE'}
)
keycache = copy.copy(dumped_keyframe_point)
keycache = utils.dump_anything.remove_items_from_dict(
keycache,
["co", "handle_left", "handle_right",'type']
)
loader.load(
new_kf,
keycache
)
new_kf.type = dumped_keyframe_point['type']
new_kf.handle_left = [
dumped_keyframe_point["handle_left"][0] - begin_frame,
dumped_keyframe_point["handle_left"][1]
]
new_kf.handle_right = [
dumped_keyframe_point["handle_right"][0] - begin_frame,
dumped_keyframe_point["handle_right"][1]
]
# clearing (needed for blender to update well)
if len(fcurve.keyframe_points) == 0:
target.fcurves.remove(fcurve)
target.id_root= data['id_root']
def dump(self, pointer=None):
assert(pointer) assert(pointer)
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()
dumper.exclude_filter =[ dumper.exclude_filter = [
'name_full', 'name_full',
'original', 'original',
'use_fake_user', 'use_fake_user',
@ -106,27 +236,13 @@ class BlAction(BlDatablock):
'users' 'users'
] ]
dumper.depth = 1 dumper.depth = 1
data = dumper.dump(pointer) data = dumper.dump(pointer)
data["fcurves"] = [] data["fcurves"] = []
dumper.depth = 2
for fcurve in self.pointer.fcurves: for fcurve in self.pointer.fcurves:
fc = { data["fcurves"].append(dump_fcurve(fcurve, use_numpy=True))
"data_path": fcurve.data_path,
"dumped_array_index": fcurve.array_index,
"keyframe_points": []
}
for k in fcurve.keyframe_points:
fc["keyframe_points"].append(
dumper.dump(k)
)
data["fcurves"].append(fc)
return data return data

View File

@ -1,13 +1,28 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
from ..libs.overrider import Overrider
from .. import utils from .. import utils
from .. import presence, operators from .. import presence, operators
from .bl_datablock import BlDatablock from .bl_datablock import BlDatablock
# WIP
class BlArmature(BlDatablock): class BlArmature(BlDatablock):
bl_id = "armatures" bl_id = "armatures"
@ -17,7 +32,7 @@ class BlArmature(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'ARMATURE_DATA' bl_icon = 'ARMATURE_DATA'
def construct(self, data): def _construct(self, data):
return bpy.data.armatures.new(data["name"]) return bpy.data.armatures.new(data["name"])
def load_implementation(self, data, target): def load_implementation(self, data, target):

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
@ -13,6 +31,10 @@ class BlCamera(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'CAMERA_DATA' bl_icon = 'CAMERA_DATA'
def _construct(self, data):
return bpy.data.cameras.new(data["name"])
def load_implementation(self, data, target): def load_implementation(self, data, target):
utils.dump_anything.load(target, data) utils.dump_anything.load(target, data)
@ -22,12 +44,11 @@ class BlCamera(BlDatablock):
if dof_settings: if dof_settings:
utils.dump_anything.load(target.dof, dof_settings) utils.dump_anything.load(target.dof, dof_settings)
def construct(self, data):
return bpy.data.cameras.new(data["name"])
def dump_implementation(self, data, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
# TODO: background image support
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()
dumper.depth = 2 dumper.depth = 2
dumper.include_filter = [ dumper.include_filter = [
@ -49,6 +70,14 @@ class BlCamera(BlDatablock):
'aperture_blades', 'aperture_blades',
'aperture_rotation', 'aperture_rotation',
'aperture_ratio', 'aperture_ratio',
'display_size',
'show_limits',
'show_mist',
'show_sensor',
'show_name',
'sensor_fit',
'sensor_height',
'sensor_width',
] ]
return dumper.dump(pointer) return dumper.dump(pointer)

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
@ -13,7 +31,7 @@ class BlCollection(BlDatablock):
bl_delay_apply = 1 bl_delay_apply = 1
bl_automatic_push = True bl_automatic_push = True
def construct(self, data): def _construct(self, data):
if self.is_library: if self.is_library:
with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData): with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData):
targetData.collections = [ targetData.collections = [
@ -28,7 +46,7 @@ class BlCollection(BlDatablock):
instance.uuid = self.uuid instance.uuid = self.uuid
return instance return instance
def load(self, data, target): def load_implementation(self, data, target):
# Load other meshes metadata # Load other meshes metadata
# dump_anything.load(target, data) # dump_anything.load(target, data)
target.name = data["name"] target.name = data["name"]

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import bpy.types as T import bpy.types as T
import mathutils import mathutils
@ -15,7 +33,7 @@ class BlCurve(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'CURVE_DATA' bl_icon = 'CURVE_DATA'
def construct(self, data): def _construct(self, data):
return bpy.data.curves.new(data["name"], data["type"]) return bpy.data.curves.new(data["name"], data["type"])
def load_implementation(self, data, target): def load_implementation(self, data, target):

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
@ -61,7 +79,6 @@ class BlDatablock(ReplicatedDatablock):
bl_automatic_push : boolean bl_automatic_push : boolean
bl_icon : type icon (blender icon name) bl_icon : type icon (blender icon name)
""" """
bl_id = "scenes"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -77,23 +94,7 @@ class BlDatablock(ReplicatedDatablock):
self.diff_method = DIFF_BINARY self.diff_method = DIFF_BINARY
def library_apply(self): def _resolve(self):
"""Apply stored data
"""
# UP in case we want to reset our pointer data
self.state = UP
def bl_diff(self):
"""Generic datablock diff"""
return self.pointer.name != self.data['name']
def diff_library(self):
return False
def resolve_deps_library(self):
return [self.pointer.library]
def resolve(self):
datablock_ref = None datablock_ref = None
datablock_root = getattr(bpy.data, self.bl_id) datablock_root = getattr(bpy.data, self.bl_id)
datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root) datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root)
@ -108,7 +109,7 @@ class BlDatablock(ReplicatedDatablock):
self.pointer = datablock_ref self.pointer = datablock_ref
def dump(self, pointer=None): def _dump(self, pointer=None):
data = {} data = {}
# Dump animation data # Dump animation data
if utils.has_action(pointer): if utils.has_action(pointer):
@ -134,7 +135,7 @@ class BlDatablock(ReplicatedDatablock):
def dump_implementation(self, data, target): def dump_implementation(self, data, target):
raise NotImplementedError raise NotImplementedError
def load(self, data, target): def _load(self, data, target):
# Load animation data # Load animation data
if 'animation_data' in data.keys(): if 'animation_data' in data.keys():
if target.animation_data is None: if target.animation_data is None:

View File

@ -1,72 +1,271 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
import numpy as np
from ..libs import dump_anything from ..libs import dump_anything
from .bl_datablock import BlDatablock from .bl_datablock import BlDatablock
# GPencil data api is structured as it follow:
# GP-Object --> GP-Layers --> GP-Frames --> GP-Strokes --> GP-Stroke-Points
def load_gpencil_layer(target=None, data=None, create=False): def dump_stroke(stroke):
""" Dump a grease pencil stroke to a dict
dump_anything.load(target, data) :param stroke: target grease pencil stroke
for k,v in target.frames.items(): :type stroke: bpy.types.GPencilStroke
target.frames.remove(v) :return: dict
"""
for frame in data["frames"]:
tframe = target.frames.new(data["frames"][frame]['frame_number'])
for stroke in data["frames"][frame]["strokes"]: assert(stroke)
try:
tstroke = tframe.strokes[stroke]
except:
tstroke = tframe.strokes.new()
dump_anything.load(
tstroke, data["frames"][frame]["strokes"][stroke])
for point in data["frames"][frame]["strokes"][stroke]["points"]: dumper = dump_anything.Dumper()
p = data["frames"][frame]["strokes"][stroke]["points"][point] dumper.include_filter = [
"aspect",
"display_mode",
"draw_cyclic",
"end_cap_mode",
"hardeness",
"line_width",
"material_index",
"start_cap_mode",
"uv_rotation",
"uv_scale",
"uv_translation",
"vertex_color_fill",
]
dumped_stroke = dumper.dump(stroke)
tstroke.points.add(1) # Stoke points
tpoint = tstroke.points[len(tstroke.points)-1] p_count = len(stroke.points)
dumped_stroke['p_count'] = p_count
p_co = np.empty(p_count*3, dtype=np.float64)
stroke.points.foreach_get('co', p_co)
dumped_stroke['p_co'] = p_co.tobytes()
p_pressure = np.empty(p_count, dtype=np.float64)
stroke.points.foreach_get('pressure', p_pressure)
dumped_stroke['p_pressure'] = p_pressure.tobytes()
p_strength = np.empty(p_count, dtype=np.float64)
stroke.points.foreach_get('strength', p_strength)
dumped_stroke['p_strength'] = p_strength.tobytes()
if bpy.app.version[1] >= 83: # new in blender 2.83
p_vertex_color = np.empty(p_count*4, dtype=np.float64)
stroke.points.foreach_get('vertex_color', p_vertex_color)
dumped_stroke['p_vertex_color'] = p_vertex_color.tobytes()
# TODO: uv_factor, uv_rotation
return dumped_stroke
def load_stroke(stroke_data, stroke):
""" Load a grease pencil stroke from a dict
:param stroke_data: dumped grease pencil stroke
:type stroke_data: dict
:param stroke: target grease pencil stroke
:type stroke: bpy.types.GPencilStroke
"""
assert(stroke and stroke_data)
dump_anything.load(stroke, stroke_data)
p_co = np.frombuffer(stroke_data["p_co"], dtype=np.float64)
p_pressure = np.frombuffer(stroke_data["p_pressure"], dtype=np.float64)
p_strength = np.frombuffer(stroke_data["p_strength"], dtype=np.float64)
stroke.points.add(stroke_data["p_count"])
stroke.points.foreach_set('co', p_co)
stroke.points.foreach_set('pressure', p_pressure)
stroke.points.foreach_set('strength', p_strength)
if "p_vertex_color" in stroke_data:
p_vertex_color = np.frombuffer(stroke_data["p_vertex_color"], dtype=np.float64)
stroke.points.foreach_set('vertex_color', p_vertex_color)
def dump_frame(frame):
""" Dump a grease pencil frame to a dict
:param frame: target grease pencil stroke
:type frame: bpy.types.GPencilFrame
:return: dict
"""
assert(frame)
dumped_frame = dict()
dumped_frame['frame_number'] = frame.frame_number
dumped_frame['strokes'] = []
# TODO: took existing strokes in account
for stroke in frame.strokes:
dumped_frame['strokes'].append(dump_stroke(stroke))
return dumped_frame
def load_frame(frame_data, frame):
""" Load a grease pencil frame from a dict
:param frame_data: source grease pencil frame
:type frame_data: dict
:param frame: target grease pencil stroke
:type frame: bpy.types.GPencilFrame
"""
assert(frame and frame_data)
# frame.frame_number = frame_data['frame_number']
# TODO: took existing stroke in account
for stroke_data in frame_data['strokes']:
target_stroke = frame.strokes.new()
load_stroke(stroke_data, target_stroke)
def dump_layer(layer):
""" Dump a grease pencil layer
:param layer: target grease pencil stroke
:type layer: bpy.types.GPencilFrame
"""
assert(layer)
dumper = dump_anything.Dumper()
dumper.include_filter = [
'info',
'opacity',
'channel_color',
'color',
# 'thickness',
'tint_color',
'tint_factor',
'vertex_paint_opacity',
'line_change',
'use_onion_skinning',
# 'use_annotation_onion_skinning',
# 'annotation_onion_before_range',
# 'annotation_onion_after_range',
# 'annotation_onion_before_color',
# 'annotation_onion_after_color',
'pass_index',
# 'viewlayer_render',
'blend_mode',
'hide',
'annotation_hide',
'lock',
# 'lock_frame',
# 'lock_material',
# 'use_mask_layer',
'use_lights',
'use_solo_mode',
'select',
'show_points',
'show_in_front',
# 'parent',
# 'parent_type',
# 'parent_bone',
# 'matrix_inverse',
]
dumped_layer = dumper.dump(layer)
dumped_layer['frames'] = []
for frame in layer.frames:
dumped_layer['frames'].append(dump_frame(frame))
return dumped_layer
def load_layer(layer_data, layer):
""" Load a grease pencil layer from a dict
:param layer_data: source grease pencil layer data
:type layer_data: dict
:param layer: target grease pencil stroke
:type layer: bpy.types.GPencilFrame
"""
# TODO: take existing data in account
dump_anything.load(layer, layer_data)
for frame_data in layer_data["frames"]:
target_frame = layer.frames.new(frame_data['frame_number'])
load_frame(frame_data, target_frame)
dump_anything.load(tpoint, p)
class BlGpencil(BlDatablock): class BlGpencil(BlDatablock):
bl_id = "grease_pencils" bl_id = "grease_pencils"
bl_class = bpy.types.GreasePencil bl_class = bpy.types.GreasePencil
bl_delay_refresh = 5 bl_delay_refresh = 2
bl_delay_apply = 5 bl_delay_apply = 1
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'GREASEPENCIL' bl_icon = 'GREASEPENCIL'
def construct(self, data): def _construct(self, data):
return bpy.data.grease_pencils.new(data["name"]) return bpy.data.grease_pencils.new(data["name"])
def load_implementation(self, data, target): def load_implementation(self, data, target):
for layer in target.layers:
target.layers.remove(layer)
if "layers" in data.keys():
for layer in data["layers"]:
if layer not in target.layers.keys():
gp_layer = target.layers.new(data["layers"][layer]["info"])
else:
gp_layer = target.layers[layer]
load_gpencil_layer(
target=gp_layer, data=data["layers"][layer], create=True)
dump_anything.load(target, data)
target.materials.clear() target.materials.clear()
if "materials" in data.keys(): if "materials" in data.keys():
for mat in data['materials']: for mat in data['materials']:
target.materials.append(bpy.data.materials[mat]) target.materials.append(bpy.data.materials[mat])
# TODO: reuse existing layer
for layer in target.layers:
target.layers.remove(layer)
if "layers" in data.keys():
for layer in data["layers"]:
layer_data = data["layers"].get(layer)
# if layer not in target.layers.keys():
target_layer = target.layers.new(data["layers"][layer]["info"])
# else:
# target_layer = target.layers[layer]
# target_layer.clear()
load_layer(layer_data, target_layer)
dump_anything.load(target, data)
def dump_implementation(self, data, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
data = dump_anything.dump(pointer, 2) data = dump_anything.dump(pointer, 2)
data['layers'] = dump_anything.dump(pointer.layers, 9)
data['layers'] = {}
for layer in pointer.layers:
data['layers'][layer.info] = dump_layer(layer)
return data return data

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
import os import os
@ -10,8 +28,10 @@ def dump_image(image):
if image.source == "GENERATED": if image.source == "GENERATED":
prefs = utils.get_preferences() prefs = utils.get_preferences()
img_name = "{}.png".format(image.name) img_name = "{}.png".format(image.name)
# Cache the image on the disk
image.filepath_raw = os.path.join(prefs.cache_directory, img_name) image.filepath_raw = os.path.join(prefs.cache_directory, img_name)
os.makedirs(prefs.cache_directory, exist_ok=True)
image.file_format = "PNG" image.file_format = "PNG"
image.save() image.save()
@ -35,14 +55,14 @@ class BlImage(BlDatablock):
bl_automatic_push = False bl_automatic_push = False
bl_icon = 'IMAGE_DATA' bl_icon = 'IMAGE_DATA'
def construct(self, data): def _construct(self, data):
return bpy.data.images.new( return bpy.data.images.new(
name=data['name'], name=data['name'],
width=data['size'][0], width=data['size'][0],
height=data['size'][1] height=data['size'][1]
) )
def load(self, data, target): def _load(self, data, target):
image = target image = target
prefs = utils.get_preferences() prefs = utils.get_preferences()
@ -59,7 +79,7 @@ class BlImage(BlDatablock):
image.colorspace_settings.name = data["colorspace_settings"]["name"] image.colorspace_settings.name = data["colorspace_settings"]["name"]
def dump(self, data, pointer=None): def _dump(self, pointer=None):
assert(pointer) assert(pointer)
data = {} data = {}
data['pixels'] = dump_image(pointer) data['pixels'] = dump_image(pointer)

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
@ -18,7 +36,7 @@ class BlLattice(BlDatablock):
for point in data['points']: for point in data['points']:
utils.dump_anything.load(target.points[point], data["points"][point]) utils.dump_anything.load(target.points[point], data["points"][point])
def construct(self, data): def _construct(self, data):
return bpy.data.lattices.new(data["name"]) return bpy.data.lattices.new(data["name"])
def dump_implementation(self, data, pointer=None): def dump_implementation(self, data, pointer=None):

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
@ -13,11 +31,11 @@ class BlLibrary(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'LIBRARY_DATA_DIRECT' bl_icon = 'LIBRARY_DATA_DIRECT'
def construct(self, data): def _construct(self, data):
with bpy.data.libraries.load(filepath=data["filepath"], link=True) as (sourceData, targetData): with bpy.data.libraries.load(filepath=data["filepath"], link=True) as (sourceData, targetData):
targetData = sourceData targetData = sourceData
return sourceData return sourceData
def load(self, data, target): def _load(self, data, target):
pass pass
def dump(self, pointer=None): def dump(self, pointer=None):

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
@ -13,10 +31,10 @@ class BlLight(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'LIGHT_DATA' bl_icon = 'LIGHT_DATA'
def construct(self, data): def _construct(self, data):
return bpy.data.lights.new(data["name"], data["type"]) return bpy.data.lights.new(data["name"], data["type"])
def load(self, data, target): def load_implementation(self, data, target):
utils.dump_anything.load(target, data) utils.dump_anything.load(target, data)
def dump_implementation(self, data, pointer=None): def dump_implementation(self, data, pointer=None):
@ -41,7 +59,8 @@ class BlLight(BlDatablock):
"contact_shadow_distance", "contact_shadow_distance",
"contact_shadow_soft_size", "contact_shadow_soft_size",
"contact_shadow_bias", "contact_shadow_bias",
"contact_shadow_thickness" "contact_shadow_thickness",
"shape"
] ]
data = dumper.dump(pointer) data = dumper.dump(pointer)
return data return data

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
import logging import logging
@ -15,16 +33,16 @@ class BlLightprobe(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'LIGHTPROBE_GRID' bl_icon = 'LIGHTPROBE_GRID'
def load_implementation(self, data, target): def _construct(self, data):
utils.dump_anything.load(target, data)
def construct(self, data):
type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type'] type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type']
# See https://developer.blender.org/D6396 # See https://developer.blender.org/D6396
if bpy.app.version[1] >= 83: if bpy.app.version[1] >= 83:
return bpy.data.lightprobes.new(data["name"], type) return bpy.data.lightprobes.new(data["name"], type)
else: else:
logger.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396") logger.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")
def load_implementation(self, data, target):
utils.dump_anything.load(target, data)
def dump_implementation(self, data, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
import logging import logging
@ -7,66 +25,62 @@ from ..libs import dump_anything
from .bl_datablock import BlDatablock from .bl_datablock import BlDatablock
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def clean_color_ramp(target_ramp):
# clear existing
try:
for key in target_ramp.elements:
target_ramp.elements.remove(key)
except:
pass
def load_mapping(target_apping, source_mapping):
# clear existing curves
for curve in target_apping.curves:
for point in curve.points:
try:
curve.remove(point)
except:
continue
# Load curves
for curve in source_mapping['curves']:
for point in source_mapping['curves'][curve]['points']:
pos = source_mapping['curves'][curve]['points'][point]['location']
target_apping.curves[curve].points.new(pos[0],pos[1])
def load_node(node_data, node_tree):
""" Load a node into a node_tree from a dict
def load_node(target_node_tree, source): :arg node_data: dumped node data
target_node = target_node_tree.nodes.get(source["name"]) :type node_data: dict
:arg node_tree: target node_tree
:type node_tree: bpy.types.NodeTree
"""
target_node = node_tree.nodes.new(type=node_data["bl_idname"])
if target_node is None: dump_anything.load(target_node, node_data)
node_type = source["bl_idname"]
target_node = target_node_tree.nodes.new(type=node_type)
# Clean color ramp before loading it
if source['type'] == 'VALTORGB':
clean_color_ramp(target_node.color_ramp)
if source['type'] == 'CURVE_RGB':
load_mapping(target_node.mapping, source['mapping'])
dump_anything.load(
target_node,
source)
if source['type'] == 'TEX_IMAGE':
target_node.image = bpy.data.images[source['image']]
for input in source["inputs"]: for input in node_data["inputs"]:
if hasattr(target_node.inputs[input], "default_value"): if hasattr(target_node.inputs[input], "default_value"):
try: try:
target_node.inputs[input].default_value = source["inputs"][input]["default_value"] target_node.inputs[input].default_value = node_data["inputs"][input]["default_value"]
except: except:
logger.error("{} not supported, skipping".format(input)) logger.error("{} not supported, skipping".format(input))
def load_link(target_node_tree, source): def load_links(links_data, node_tree):
input_socket = target_node_tree.nodes[source['to_node'] """ Load node_tree links from a list
['name']].inputs[source['to_socket']['name']]
output_socket = target_node_tree.nodes[source['from_node'] :arg links_data: dumped node links
['name']].outputs[source['from_socket']['name']] :type links_data: list
:arg node_tree: node links collection
:type node_tree: bpy.types.NodeTree
"""
target_node_tree.links.new(input_socket, output_socket) for link in links_data:
input_socket = node_tree.nodes[link['to_node']].inputs[int(link['to_socket'])]
output_socket = node_tree.nodes[link['from_node']].outputs[int(link['from_socket'])]
node_tree.links.new(input_socket, output_socket)
def dump_links(links):
""" Dump node_tree links collection to a list
:arg links: node links collection
:type links: bpy.types.NodeLinks
:retrun: list
"""
links_data = []
for link in links:
links_data.append({
'to_node':link.to_node.name,
'to_socket':link.to_socket.path_from_id()[-2:-1],
'from_node':link.from_node.name,
'from_socket':link.from_socket.path_from_id()[-2:-1],
})
return links_data
class BlMaterial(BlDatablock): class BlMaterial(BlDatablock):
bl_id = "materials" bl_id = "materials"
@ -76,7 +90,7 @@ class BlMaterial(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'MATERIAL_DATA' bl_icon = 'MATERIAL_DATA'
def construct(self, data): def _construct(self, data):
return bpy.data.materials.new(data["name"]) return bpy.data.materials.new(data["name"])
def load_implementation(self, data, target): def load_implementation(self, data, target):
@ -100,19 +114,25 @@ class BlMaterial(BlDatablock):
# Load nodes # Load nodes
for node in data["node_tree"]["nodes"]: for node in data["node_tree"]["nodes"]:
load_node(target.node_tree, data["node_tree"]["nodes"][node]) load_node(data["node_tree"]["nodes"][node], target.node_tree)
# Load nodes links # Load nodes links
target.node_tree.links.clear() target.node_tree.links.clear()
for link in data["node_tree"]["links"]: load_links(data["node_tree"]["links"], target.node_tree)
load_link(target.node_tree, data["node_tree"]["links"][link])
def dump_implementation(self, data, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
mat_dumper = dump_anything.Dumper() mat_dumper = dump_anything.Dumper()
mat_dumper.depth = 2 mat_dumper.depth = 2
mat_dumper.exclude_filter = [ mat_dumper.exclude_filter = [
"is_embed_data",
"is_evaluated",
"name_full",
"bl_description",
"bl_icon",
"bl_idname",
"bl_label",
"preview", "preview",
"original", "original",
"uuid", "uuid",
@ -121,47 +141,44 @@ class BlMaterial(BlDatablock):
"line_color", "line_color",
"view_center", "view_center",
] ]
node_dumper = dump_anything.Dumper()
node_dumper.depth = 1
node_dumper.exclude_filter = [
"dimensions",
"show_expanded"
"select",
"bl_height_min",
"bl_height_max",
"bl_width_min",
"bl_width_max",
"bl_width_default",
"hide",
"show_options",
"show_tetxures",
"show_preview",
"outputs",
"width_hidden"
]
input_dumper = dump_anything.Dumper()
input_dumper.depth = 2
input_dumper.include_filter = ["default_value"]
links_dumper = dump_anything.Dumper()
links_dumper.depth = 3
links_dumper.include_filter = [
"name",
"to_node",
"from_node",
"from_socket",
"to_socket"]
data = mat_dumper.dump(pointer) data = mat_dumper.dump(pointer)
if pointer.use_nodes: if pointer.use_nodes:
nodes = {} nodes = {}
node_dumper = dump_anything.Dumper()
node_dumper.depth = 1
node_dumper.exclude_filter = [
"dimensions",
"show_expanded",
"name_full",
"select",
"bl_height_min",
"bl_height_max",
"bl_width_min",
"bl_width_max",
"type",
"bl_icon",
"bl_width_default",
"bl_static_type",
"show_tetxure",
"hide",
"show_options",
"show_preview",
"outputs",
"width_hidden"
]
for node in pointer.node_tree.nodes: for node in pointer.node_tree.nodes:
nodes[node.name] = node_dumper.dump(node) nodes[node.name] = node_dumper.dump(node)
if hasattr(node, 'inputs'): if hasattr(node, 'inputs'):
nodes[node.name]['inputs'] = {} nodes[node.name]['inputs'] = {}
for i in node.inputs: for i in node.inputs:
input_dumper = dump_anything.Dumper()
input_dumper.depth = 2
input_dumper.include_filter = ["default_value"]
if hasattr(i, 'default_value'): if hasattr(i, 'default_value'):
nodes[node.name]['inputs'][i.name] = input_dumper.dump( nodes[node.name]['inputs'][i.name] = input_dumper.dump(
i) i)
@ -184,11 +201,39 @@ class BlMaterial(BlDatablock):
'location' 'location'
] ]
nodes[node.name]['mapping'] = curve_dumper.dump(node.mapping) nodes[node.name]['mapping'] = curve_dumper.dump(node.mapping)
data["node_tree"]['nodes'] = nodes data["node_tree"]['nodes'] = nodes
data["node_tree"]["links"] = links_dumper.dump(pointer.node_tree.links)
data["node_tree"]["links"] = dump_links(pointer.node_tree.links)
elif pointer.is_grease_pencil: elif pointer.is_grease_pencil:
data['grease_pencil'] = dump_anything.dump(pointer.grease_pencil, 3) gp_mat_dumper = dump_anything.Dumper()
gp_mat_dumper.depth = 3
gp_mat_dumper.include_filter = [
'show_stroke',
'mode',
'stroke_style',
'color',
'use_overlap_strokes',
'show_fill',
'fill_style',
'fill_color',
'pass_index',
'alignment_mode',
# 'fill_image',
'texture_opacity',
'mix_factor',
'texture_offset',
'texture_angle',
'texture_scale',
'texture_clamp',
'gradient_type',
'mix_color',
'flip'
]
data['grease_pencil'] = gp_mat_dumper.dump(pointer.grease_pencil)
return data return data
def resolve_deps_implementation(self): def resolve_deps_implementation(self):

View File

@ -1,163 +1,213 @@
# ##### 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 bpy import bpy
import bmesh import bmesh
import mathutils import mathutils
import logging
import numpy as np
from .. import utils from .. import utils
from ..libs.replication.replication.constants import DIFF_BINARY from ..libs.replication.replication.constants import DIFF_BINARY
from .bl_datablock import BlDatablock from .bl_datablock import BlDatablock
logger = logging.getLogger(__name__)
def dump_mesh(mesh, data={}):
import bmesh
mesh_data = data
mesh_buffer = bmesh.new()
# https://blog.michelanders.nl/2016/02/copying-vertices-to-numpy-arrays-in_4.html
mesh_buffer.from_mesh(mesh)
uv_layer = mesh_buffer.loops.layers.uv.verify()
bevel_layer = mesh_buffer.verts.layers.bevel_weight.verify()
skin_layer = mesh_buffer.verts.layers.skin.verify()
verts = {}
for vert in mesh_buffer.verts:
v = {}
v["co"] = list(vert.co)
# vert metadata
v['bevel'] = vert[bevel_layer]
v['normal'] = list(vert.normal)
# v['skin'] = list(vert[skin_layer])
verts[str(vert.index)] = v
mesh_data["verts"] = verts
edges = {}
for edge in mesh_buffer.edges:
e = {}
e["verts"] = [edge.verts[0].index, edge.verts[1].index]
# Edge metadata
e["smooth"] = edge.smooth
edges[edge.index] = e
mesh_data["edges"] = edges
faces = {}
for face in mesh_buffer.faces:
f = {}
fverts = []
for vert in face.verts:
fverts.append(vert.index)
f["verts"] = fverts
f["material_index"] = face.material_index
f["smooth"] = face.smooth
f["normal"] = list(face.normal)
f["index"] = face.index
uvs = []
# Face metadata
for loop in face.loops:
loop_uv = loop[uv_layer]
uvs.append(list(loop_uv.uv))
f["uv"] = uvs
faces[face.index] = f
mesh_data["faces"] = faces
uv_layers = []
for uv_layer in mesh.uv_layers:
uv_layers.append(uv_layer.name)
mesh_data["uv_layers"] = uv_layers
# return mesh_data
class BlMesh(BlDatablock): class BlMesh(BlDatablock):
bl_id = "meshes" bl_id = "meshes"
bl_class = bpy.types.Mesh bl_class = bpy.types.Mesh
bl_delay_refresh = 10 bl_delay_refresh = 2
bl_delay_apply = 10 bl_delay_apply = 1
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'MESH_DATA' bl_icon = 'MESH_DATA'
def construct(self, data): def _construct(self, data):
instance = bpy.data.meshes.new(data["name"]) instance = bpy.data.meshes.new(data["name"])
instance.uuid = self.uuid instance.uuid = self.uuid
return instance return instance
def load_implementation(self, data, target): def load_implementation(self, data, target):
if not target or not target.is_editmode: if not target or not target.is_editmode:
# 1 - LOAD MATERIAL SLOTS utils.dump_anything.load(target, data)
# SLots
i = 0 # MATERIAL SLOTS
target.materials.clear()
for m in data["material_list"]: for m in data["material_list"]:
target.materials.append(bpy.data.materials[m]) target.materials.append(bpy.data.materials[m])
# 2 - LOAD GEOMETRY # CLEAR GEOMETRY
mesh_buffer = bmesh.new() if target.vertices:
target.clear_geometry()
for i in data["verts"]: # VERTS
v = mesh_buffer.verts.new(data["verts"][i]["co"]) vertices = np.frombuffer(data["verts_co"], dtype=np.float64)
v.normal = data["verts"][i]["normal"] vert_count = int(len(vertices)/3)
mesh_buffer.verts.ensure_lookup_table() target.vertices.add(vert_count)
for i in data["edges"]: # EDGES
verts = mesh_buffer.verts
v1 = data["edges"][i]["verts"][0] egdes_vert = np.frombuffer(data["egdes_vert"], dtype=np.int)
v2 = data["edges"][i]["verts"][1]
edge = mesh_buffer.edges.new([verts[v1], verts[v2]])
edge.smooth = data["edges"][i]["smooth"]
mesh_buffer.edges.ensure_lookup_table() edge_count = data["egdes_count"]
for p in data["faces"]: target.edges.add(edge_count)
verts = []
for v in data["faces"][p]["verts"]:
verts.append(mesh_buffer.verts[v])
if len(verts) > 0: # LOOPS
f = mesh_buffer.faces.new(verts) loops_count = data["loop_count"]
target.loops.add(loops_count)
uv_layer = mesh_buffer.loops.layers.uv.verify() loop_vertex_index = np.frombuffer(
data['loop_vertex_index'], dtype=np.int)
loop_normal = np.frombuffer(data['loop_normal'], dtype=np.float64)
f.smooth = data["faces"][p]["smooth"] # POLY
f.normal = data["faces"][p]["normal"] poly_count = data["poly_count"]
f.index = data["faces"][p]["index"] target.polygons.add(poly_count)
f.material_index = data["faces"][p]['material_index']
# UV loading
for i, loop in enumerate(f.loops):
loop_uv = loop[uv_layer]
loop_uv.uv = data["faces"][p]["uv"][i]
mesh_buffer.faces.ensure_lookup_table()
mesh_buffer.to_mesh(target)
# 3 - LOAD METADATA poly_loop_start = np.frombuffer(
# uv's data["poly_loop_start"], dtype=np.int)
utils.dump_anything.load(target.uv_layers, data['uv_layers']) poly_loop_total = np.frombuffer(
data["poly_loop_total"], dtype=np.int)
poly_smooth = np.frombuffer(data["poly_smooth"], dtype=np.bool)
bevel_layer = mesh_buffer.verts.layers.bevel_weight.verify() poly_mat = np.frombuffer(data["poly_mat"], dtype=np.int)
skin_layer = mesh_buffer.verts.layers.skin.verify()
utils.dump_anything.load(target, data) # LOADING
target.vertices.foreach_set('co', vertices)
target.edges.foreach_set("vertices", egdes_vert)
if data['use_customdata_edge_crease']:
edges_crease = np.frombuffer(data["edges_crease"], dtype=np.float64)
target.edges.foreach_set("crease", edges_crease)
if data['use_customdata_edge_bevel']:
edges_bevel = np.frombuffer(data["edges_bevel"], dtype=np.float64)
target.edges.foreach_set("bevel_weight", edges_bevel)
target.loops.foreach_set("vertex_index", loop_vertex_index)
target.loops.foreach_set("normal", loop_normal)
target.polygons.foreach_set("loop_total", poly_loop_total)
target.polygons.foreach_set("loop_start", poly_loop_start)
target.polygons.foreach_set("use_smooth", poly_smooth)
target.polygons.foreach_set("material_index", poly_mat)
# UV Layers
for layer in data['uv_layers']:
if layer not in target.uv_layers:
target.uv_layers.new(name=layer)
uv_buffer = np.frombuffer(data["uv_layers"][layer]['data'])
target.uv_layers[layer].data.foreach_set('uv', uv_buffer)
target.validate()
target.update()
def dump_implementation(self, data, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
mesh = pointer
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()
dumper.depth = 2 dumper.depth = 1
dumper.include_filter = [ dumper.include_filter = [
'name', 'name',
'use_auto_smooth', 'use_auto_smooth',
'auto_smooth_angle' 'auto_smooth_angle',
'use_customdata_edge_bevel',
'use_customdata_edge_crease'
] ]
data = dumper.dump(pointer)
dump_mesh(pointer, data) data = dumper.dump(mesh)
# TODO: selective dump
# VERTICES
vert_count = len(mesh.vertices)
verts_co = np.empty(vert_count*3, dtype=np.float64)
mesh.vertices.foreach_get('co', verts_co)
data["verts_co"] = verts_co.tobytes()
# EDGES
edge_count = len(mesh.edges)
edges_vert = np.empty(edge_count*2, dtype=np.int)
mesh.edges.foreach_get('vertices', edges_vert)
data["egdes_vert"] = edges_vert.tobytes()
data["egdes_count"] = len(mesh.edges)
if mesh.use_customdata_edge_crease:
edges_crease = np.empty(edge_count, dtype=np.float64)
mesh.edges.foreach_get('crease', edges_crease)
data["edges_crease"] = edges_crease.tobytes()
if mesh.use_customdata_edge_bevel:
edges_bevel = np.empty(edge_count, dtype=np.float64)
mesh.edges.foreach_get('bevel_weight', edges_bevel)
data["edges_bevel"] = edges_bevel.tobytes()
# POLYGONS
poly_count = len(mesh.polygons)
data["poly_count"] = poly_count
poly_mat = np.empty(poly_count, dtype=np.int)
mesh.polygons.foreach_get("material_index", poly_mat)
data["poly_mat"] = poly_mat.tobytes()
poly_loop_start = np.empty(poly_count, dtype=np.int)
mesh.polygons.foreach_get("loop_start", poly_loop_start)
data["poly_loop_start"] = poly_loop_start.tobytes()
poly_loop_total = np.empty(poly_count, dtype=np.int)
mesh.polygons.foreach_get("loop_total", poly_loop_total)
data["poly_loop_total"] = poly_loop_total.tobytes()
poly_smooth = np.empty(poly_count, dtype=np.bool)
mesh.polygons.foreach_get("use_smooth", poly_smooth)
data["poly_smooth"] = poly_smooth.tobytes()
# LOOPS
loop_count = len(mesh.loops)
data["loop_count"] = loop_count
loop_normal = np.empty(loop_count*3, dtype=np.float64)
mesh.loops.foreach_get("normal", loop_normal)
data["loop_normal"] = loop_normal.tobytes()
loop_vertex_index = np.empty(loop_count, dtype=np.int)
mesh.loops.foreach_get("vertex_index", loop_vertex_index)
data["loop_vertex_index"] = loop_vertex_index.tobytes()
# UV Layers
data['uv_layers'] = {}
for layer in mesh.uv_layers:
data['uv_layers'][layer.name] = {}
uv_layer = np.empty(len(layer.data)*2, dtype=np.float64)
layer.data.foreach_get("uv", uv_layer)
data['uv_layers'][layer.name]['data'] = uv_layer.tobytes()
# Fix material index # Fix material index
m_list = [] m_list = []
for material in pointer.materials: for material in pointer.materials:

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
@ -13,7 +31,7 @@ class BlMetaball(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'META_BALL' bl_icon = 'META_BALL'
def construct(self, data): def _construct(self, data):
return bpy.data.metaballs.new(data["name"]) return bpy.data.metaballs.new(data["name"])
def load(self, data, target): def load(self, data, target):

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
import logging import logging
@ -8,22 +26,6 @@ from .bl_datablock import BlDatablock
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def load_constraints(target, data):
for local_constraint in target.constraints:
if local_constraint.name not in data:
target.constraints.remove(local_constraint)
for constraint in data:
target_constraint = target.constraints.get(constraint)
if not target_constraint:
target_constraint = target.constraints.new(
data[constraint]['type'])
utils.dump_anything.load(
target_constraint, data[constraint])
def load_pose(target_bone, data): def load_pose(target_bone, data):
target_bone.rotation_mode = data['rotation_mode'] target_bone.rotation_mode = data['rotation_mode']
@ -38,7 +40,7 @@ class BlObject(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'OBJECT_DATA' bl_icon = 'OBJECT_DATA'
def construct(self, data): def _construct(self, data):
pointer = None pointer = None
if self.is_library: if self.is_library:
@ -50,7 +52,7 @@ class BlObject(BlDatablock):
instance.uuid = self.uuid instance.uuid = self.uuid
return instance return instance
# Object specific constructor... # TODO: refactoring
if "data" not in data: if "data" not in data:
pass pass
elif data["data"] in bpy.data.meshes.keys(): elif data["data"] in bpy.data.meshes.keys():
@ -87,32 +89,7 @@ class BlObject(BlDatablock):
def load_implementation(self, data, target): def load_implementation(self, data, target):
# Load transformation data # Load transformation data
rot_mode = 'rotation_quaternion' if data['rotation_mode'] == 'QUATERNION' else 'rotation_euler' utils.dump_anything.load(target, data)
target.rotation_mode = data['rotation_mode']
target.location = data['location']
setattr(target, rot_mode, data[rot_mode])
target.scale = data['scale']
target.name = data["name"]
# Load modifiers
if hasattr(target, 'modifiers'):
# TODO: smarter selective update
target.modifiers.clear()
for modifier in data['modifiers']:
target_modifier = target.modifiers.get(modifier)
if not target_modifier:
target_modifier = target.modifiers.new(
data['modifiers'][modifier]['name'], data['modifiers'][modifier]['type'])
utils.dump_anything.load(
target_modifier, data['modifiers'][modifier])
# Load constraints
# Object
if hasattr(target, 'constraints') and 'constraints' in data:
load_constraints(target, data['constraints'])
# Pose # Pose
if 'pose' in data: if 'pose' in data:
@ -135,28 +112,14 @@ class BlObject(BlDatablock):
bone_data = data['pose']['bones'].get(bone) bone_data = data['pose']['bones'].get(bone)
if 'constraints' in bone_data.keys(): if 'constraints' in bone_data.keys():
load_constraints( utils.dump_anything.load(target_bone, bone_data['constraints'])
target_bone, bone_data['constraints'])
load_pose(target_bone, bone_data) load_pose(target_bone, bone_data)
if 'bone_index' in bone_data.keys(): if 'bone_index' in bone_data.keys():
target_bone.bone_group = target.pose.bone_group[bone_data['bone_group_index']] target_bone.bone_group = target.pose.bone_group[bone_data['bone_group_index']]
# Load relations
if 'children' in data.keys():
for child in data['children']:
bpy.data.objects[child].parent = self.pointer
# Load empty representation
target.empty_display_size = data['empty_display_size']
target.empty_display_type = data['empty_display_type']
# Instancing
target.instance_type = data['instance_type']
if data['instance_type'] == 'COLLECTION':
target.instance_collection = bpy.data.collections[data['instance_collection']]
# vertex groups # vertex groups
if 'vertex_groups' in data: if 'vertex_groups' in data:
target.vertex_groups.clear() target.vertex_groups.clear()
@ -220,7 +183,6 @@ class BlObject(BlDatablock):
data["modifiers"] = {} data["modifiers"] = {}
for index, modifier in enumerate(pointer.modifiers): for index, modifier in enumerate(pointer.modifiers):
data["modifiers"][modifier.name] = dumper.dump(modifier) data["modifiers"][modifier.name] = dumper.dump(modifier)
data["modifiers"][modifier.name]['m_index'] = index
# CONSTRAINTS # CONSTRAINTS
# OBJECT # OBJECT
@ -299,18 +261,18 @@ class BlObject(BlDatablock):
data['vertex_groups'] = vg_data data['vertex_groups'] = vg_data
# SHAPE KEYS # SHAPE KEYS
pointer_data = pointer.data object_data = pointer.data
if hasattr(pointer_data, 'shape_keys') and pointer_data.shape_keys: if hasattr(object_data, 'shape_keys') and object_data.shape_keys:
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()
dumper.depth = 2 dumper.depth = 2
dumper.include_filter = [ dumper.include_filter = [
'reference_key', 'reference_key',
'use_relative' 'use_relative'
] ]
data['shape_keys'] = dumper.dump(pointer_data.shape_keys) data['shape_keys'] = dumper.dump(object_data.shape_keys)
data['shape_keys']['reference_key'] = pointer_data.shape_keys.reference_key.name data['shape_keys']['reference_key'] = object_data.shape_keys.reference_key.name
key_blocks = {} key_blocks = {}
for key in pointer_data.shape_keys.key_blocks: for key in object_data.shape_keys.key_blocks:
dumper.depth = 3 dumper.depth = 3
dumper.include_filter = [ dumper.include_filter = [
'name', 'name',

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
@ -12,12 +30,12 @@ class BlScene(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'SCENE_DATA' bl_icon = 'SCENE_DATA'
def construct(self, data): def _construct(self, data):
instance = bpy.data.scenes.new(data["name"]) instance = bpy.data.scenes.new(data["name"])
instance.uuid = self.uuid instance.uuid = self.uuid
return instance return instance
def load(self, data, target): def load_implementation(self, data, target):
target = self.pointer target = self.pointer
# Load other meshes metadata # Load other meshes metadata
utils.dump_anything.load(target, data) utils.dump_anything.load(target, data)
@ -55,7 +73,13 @@ class BlScene(BlDatablock):
scene_dumper = utils.dump_anything.Dumper() scene_dumper = utils.dump_anything.Dumper()
scene_dumper.depth = 1 scene_dumper.depth = 1
scene_dumper.include_filter = ['name','world', 'id', 'camera', 'grease_pencil'] scene_dumper.include_filter = [
'name',
'world',
'id',
'camera',
'grease_pencil'
]
data = scene_dumper.dump(pointer) data = scene_dumper.dump(pointer)
scene_dumper.depth = 3 scene_dumper.depth = 3

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
@ -16,7 +34,7 @@ class BlSpeaker(BlDatablock):
def load_implementation(self, data, target): def load_implementation(self, data, target):
utils.dump_anything.load(target, data) utils.dump_anything.load(target, data)
def construct(self, data): def _construct(self, data):
return bpy.data.speakers.new(data["name"]) return bpy.data.speakers.new(data["name"])
def dump_implementation(self, data, pointer=None): def dump_implementation(self, data, pointer=None):

View File

@ -1,9 +1,27 @@
# ##### 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 bpy import bpy
import mathutils import mathutils
from .. import utils from .. import utils
from .bl_datablock import BlDatablock from .bl_datablock import BlDatablock
from .bl_material import load_link, load_node from .bl_material import load_links, load_node, dump_links
class BlWorld(BlDatablock): class BlWorld(BlDatablock):
@ -14,10 +32,10 @@ class BlWorld(BlDatablock):
bl_automatic_push = True bl_automatic_push = True
bl_icon = 'WORLD_DATA' bl_icon = 'WORLD_DATA'
def construct(self, data): def _construct(self, data):
return bpy.data.worlds.new(data["name"]) return bpy.data.worlds.new(data["name"])
def load(self, data, target): def load_implementation(self, data, target):
if data["use_nodes"]: if data["use_nodes"]:
if target.node_tree is None: if target.node_tree is None:
target.use_nodes = True target.use_nodes = True
@ -25,13 +43,13 @@ class BlWorld(BlDatablock):
target.node_tree.nodes.clear() target.node_tree.nodes.clear()
for node in data["node_tree"]["nodes"]: for node in data["node_tree"]["nodes"]:
load_node(target.node_tree, data["node_tree"]["nodes"][node]) load_node(data["node_tree"]["nodes"][node], target.node_tree)
# Load nodes links # Load nodes links
target.node_tree.links.clear() target.node_tree.links.clear()
for link in data["node_tree"]["links"]:
load_link(target.node_tree, data["node_tree"]["links"][link]) load_links(data["node_tree"]["links"], target.node_tree)
def dump_implementation(self, data, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
@ -86,8 +104,9 @@ class BlWorld(BlDatablock):
nodes[node.name]['inputs'][i.name] = input_dumper.dump( nodes[node.name]['inputs'][i.name] = input_dumper.dump(
i) i)
data["node_tree"]['nodes'] = nodes data["node_tree"]['nodes'] = nodes
utils.dump_datablock_attibutes(
pointer.node_tree, ["links"], 3, data['node_tree']) data["node_tree"]['links'] = dump_links(pointer.node_tree.links)
return data return data
def resolve_deps_implementation(self): def resolve_deps_implementation(self):
@ -101,6 +120,3 @@ class BlWorld(BlDatablock):
deps.append(self.pointer.library) deps.append(self.pointer.library)
return deps return deps
def is_valid(self):
return bpy.data.worlds.get(self.data['name'])

View File

@ -1,3 +1,20 @@
# ##### 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 import logging
import bpy import bpy

View File

@ -1,3 +1,21 @@
# ##### 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 collections import collections
import logging import logging
import os import os

View File

@ -1,7 +1,27 @@
# ##### 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
import bpy import bpy
import bpy.types as T import bpy.types as T
import mathutils import mathutils
logger = logging.getLogger(__name__)
def remove_items_from_dict(d, keys, recursive=False): def remove_items_from_dict(d, keys, recursive=False):
copy = dict(d) copy = dict(d)
@ -47,7 +67,7 @@ def _load_filter_type(t, use_bl_rna=True):
if use_bl_rna and x.bl_rna_property: if use_bl_rna and x.bl_rna_property:
return isinstance(x.bl_rna_property, t) return isinstance(x.bl_rna_property, t)
else: else:
isinstance(x.read(), t) return isinstance(x.read(), t)
return filter_function return filter_function
@ -73,8 +93,9 @@ def _load_filter_default(default):
class Dumper: class Dumper:
# TODO: support occlude readonly
def __init__(self): def __init__(self):
self.verbose = False self.verbose = True
self.depth = 1 self.depth = 1
self.keep_compounds_as_leaves = False self.keep_compounds_as_leaves = False
self.accept_read_only = True self.accept_read_only = True
@ -83,7 +104,6 @@ class Dumper:
self.type_subset = self.match_subset_all self.type_subset = self.match_subset_all
self.include_filter = [] self.include_filter = []
self.exclude_filter = [] self.exclude_filter = []
# self._atomic_types = [] # TODO future option?
def dump(self, any): def dump(self, any):
return self._dump_any(any, 0) return self._dump_any(any, 0)
@ -175,7 +195,8 @@ class Dumper:
if (self.include_filter and p not in self.include_filter): if (self.include_filter and p not in self.include_filter):
return False return False
getattr(default, p) getattr(default, p)
except AttributeError: except AttributeError as err:
logger.debug(err)
return False return False
if p.startswith("__"): if p.startswith("__"):
return False return False
@ -238,14 +259,12 @@ class BlenderAPIElement:
def write(self, value): def write(self, value):
# take precaution if property is read-only # take precaution if property is read-only
try: if self.sub_element_name and \
if self.sub_element_name: not self.api_element.is_property_readonly(self.sub_element_name):
setattr(self.api_element, self.sub_element_name, value)
else: setattr(self.api_element, self.sub_element_name, value)
self.api_element = value else:
except AttributeError as err: self.api_element = value
if not self.occlude_read_only:
raise err
def extend(self, element_name): def extend(self, element_name):
return BlenderAPIElement(self.read(), element_name) return BlenderAPIElement(self.read(), element_name)
@ -262,7 +281,7 @@ class BlenderAPIElement:
class Loader: class Loader:
def __init__(self): def __init__(self):
self.type_subset = self.match_subset_all self.type_subset = self.match_subset_all
self.occlude_read_only = True self.occlude_read_only = False
self.order = ['*'] self.order = ['*']
def load(self, dst_data, src_dumped_data): def load(self, dst_data, src_dumped_data):
@ -287,6 +306,7 @@ class Loader:
for i in range(len(dump)): for i in range(len(dump)):
element.read()[i] = dump[i] element.read()[i] = dump[i]
except AttributeError as err: except AttributeError as err:
logger.debug(err)
if not self.occlude_read_only: if not self.occlude_read_only:
raise err raise err
@ -297,29 +317,77 @@ class Loader:
CONSTRUCTOR_NEW = "new" CONSTRUCTOR_NEW = "new"
CONSTRUCTOR_ADD = "add" CONSTRUCTOR_ADD = "add"
DESTRUCTOR_REMOVE = "remove"
DESTRUCTOR_CLEAR = "clear"
constructors = { constructors = {
T.ColorRampElement: (CONSTRUCTOR_NEW, ["position"]), T.ColorRampElement: (CONSTRUCTOR_NEW, ["position"]),
T.ParticleSettingsTextureSlot: (CONSTRUCTOR_ADD, []) T.ParticleSettingsTextureSlot: (CONSTRUCTOR_ADD, []),
T.Modifier: (CONSTRUCTOR_NEW, ["name", "type"]),
T.Constraint: (CONSTRUCTOR_NEW, ["type"]),
# T.VertexGroup: (CONSTRUCTOR_NEW, ["name"], True),
}
destructors = {
T.ColorRampElement:DESTRUCTOR_REMOVE,
T.Modifier: DESTRUCTOR_CLEAR,
T.Constraint: CONSTRUCTOR_NEW,
} }
element_type = element.bl_rna_property.fixed_type element_type = element.bl_rna_property.fixed_type
constructor = constructors.get(type(element_type)) constructor = constructors.get(type(element_type))
if constructor is None: # collection type not supported if constructor is None: # collection type not supported
return return
for dumped_element in dump.values():
try: destructor = destructors.get(type(element_type))
constructor_parameters = [dumped_element[name]
for name in constructor[1]] # Try to clear existing
except KeyError: if destructor:
print("Collection load error, missing parameters.") if destructor == DESTRUCTOR_REMOVE:
continue # TODO handle error collection = element.read()
new_element = getattr(element.read(), constructor[0])( for i in range(len(collection)-1):
*constructor_parameters) collection.remove(collection[0])
else:
getattr(element.read(), DESTRUCTOR_CLEAR)()
for dump_idx, dumped_element in enumerate(dump.values()):
if dump_idx == 0 and len(element.read())>0:
new_element = element.read()[0]
else:
try:
constructor_parameters = [dumped_element[name]
for name in constructor[1]]
except KeyError:
logger.debug("Collection load error, missing parameters.")
continue # TODO handle error
new_element = getattr(element.read(), constructor[0])(
*constructor_parameters)
self._load_any( self._load_any(
BlenderAPIElement( BlenderAPIElement(
new_element, occlude_read_only=self.occlude_read_only), new_element, occlude_read_only=self.occlude_read_only),
dumped_element dumped_element
) )
def _load_curve_mapping(self, element, dump):
mapping = element.read()
# cleanup existing curve
for curve in mapping.curves:
for idx in range(len(curve.points)):
if idx == 0:
break
curve.points.remove(curve.points[1])
for curve_index, curve in dump['curves'].items():
for point_idx, point in curve['points'].items():
pos = point['location']
if len(mapping.curves[curve_index].points) == 1:
mapping.curves[curve_index].points[int(point_idx)].location = pos
else:
mapping.curves[curve_index].points.new(pos[0],pos[1])
def _load_pointer(self, pointer, dump): def _load_pointer(self, pointer, dump):
rna_property_type = pointer.bl_rna_property.fixed_type rna_property_type = pointer.bl_rna_property.fixed_type
if not rna_property_type: if not rna_property_type:
@ -336,6 +404,8 @@ class Loader:
pointer.write(bpy.data.meshes.get(dump)) pointer.write(bpy.data.meshes.get(dump))
elif isinstance(rna_property_type, T.Material): elif isinstance(rna_property_type, T.Material):
pointer.write(bpy.data.materials.get(dump)) pointer.write(bpy.data.materials.get(dump))
elif isinstance(rna_property_type, T.Collection):
pointer.write(bpy.data.collections.get(dump))
def _load_matrix(self, matrix, dump): def _load_matrix(self, matrix, dump):
matrix.write(mathutils.Matrix(dump)) matrix.write(mathutils.Matrix(dump))
@ -365,11 +435,11 @@ class Loader:
for k in self._ordered_keys(dump.keys()): for k in self._ordered_keys(dump.keys()):
v = dump[k] v = dump[k]
if not hasattr(default.read(), k): if not hasattr(default.read(), k):
continue # TODO error handling logger.debug(f"Load default, skipping {default} : {k}")
try: try:
self._load_any(default.extend(k), v) self._load_any(default.extend(k), v)
except: except Exception as err:
pass logger.debug(f"Cannot load {k}: {err}")
@property @property
def match_subset_all(self): def match_subset_all(self):
@ -382,6 +452,7 @@ class Loader:
(_load_filter_type(mathutils.Vector, use_bl_rna=False), self._load_vector), (_load_filter_type(mathutils.Vector, use_bl_rna=False), self._load_vector),
(_load_filter_type(mathutils.Quaternion, use_bl_rna=False), self._load_quaternion), (_load_filter_type(mathutils.Quaternion, use_bl_rna=False), self._load_quaternion),
(_load_filter_type(mathutils.Euler, use_bl_rna=False), self._load_euler), (_load_filter_type(mathutils.Euler, use_bl_rna=False), self._load_euler),
(_load_filter_type(T.CurveMapping, use_bl_rna=False), self._load_curve_mapping),
(_load_filter_type(T.FloatProperty), self._load_identity), (_load_filter_type(T.FloatProperty), self._load_identity),
(_load_filter_type(T.StringProperty), self._load_identity), (_load_filter_type(T.StringProperty), self._load_identity),
(_load_filter_type(T.EnumProperty), self._load_identity), (_load_filter_type(T.EnumProperty), self._load_identity),

View File

@ -1,219 +0,0 @@
"""
Context Manager allowing temporary override of attributes
````python
import bpy
from overrider import Overrider
with Overrider(name='bpy_', parent=bpy) as bpy_:
# set preview render settings
bpy_.context.scene.render.use_file_extension = False
bpy_.context.scene.render.resolution_x = 512
bpy_.context.scene.render.resolution_y = 512
bpy_.context.scene.render.use_file_extension = False
bpy_.context.scene.render.image_settings.file_format = "JPEG"
bpy_.context.scene.layers[10] = False
frame_start = action.frame_range[0]
frame_end = action.frame_range[1]
if begin_frame is not None:
frame_start = begin_frame
if end_frame is not None:
frame_end = end_frame
# render
window = bpy_.data.window_managers[0].windows[0]
screen = bpy_.data.window_managers[0].windows[0].screen
area = next(area for area in screen.areas if area.type == 'VIEW_3D')
space = next(space for space in area.spaces if space.type == 'VIEW_3D')
space.viewport_shade = 'MATERIAL'
space.region_3d.view_perspective = 'CAMERA'
override_context = {
"window": window._real_value_(),
"screen": screen._real_value_()
}
if frame_start == frame_end:
bpy.context.scene.frame_set(int(frame_start))
bpy_.context.scene.render.filepath = os.path.join(directory, "icon.jpg")
bpy.ops.render.opengl(override_context, write_still=True)
else:
for icon_index, frame_number in enumerate(range(int(frame_start), int(frame_end) + 1)):
bpy.context.scene.frame_set(frame_number)
bpy.context.scene.render.filepath = os.path.join(directory, "icon", "{:04d}.jpg".format(icon_index))
bpy.ops.render.opengl(override_context, write_still=True)
````
"""
from collections import OrderedDict
class OverrideIter:
def __init__(self, parent):
self.parent = parent
self.index = -1
def __next__(self):
self.index += 1
try:
return self.parent[self.index]
except IndexError as e:
raise StopIteration
class OverrideBase:
def __init__(self, context_manager, name=None, parent=None):
self._name__ = name
self._context_manager_ = context_manager
self._parent_ = parent
self._changed_attributes_ = OrderedDict()
self._changed_items_ = OrderedDict()
self._children_ = list()
self._original_value_ = self._real_value_()
def __repr__(self):
return "<{}({})>".format(self.__class__.__name__, self._path_)
@property
def _name_(self):
raise NotImplementedError()
@property
def _path_(self):
if isinstance(self._parent_, OverrideBase):
return self._parent_._path_ + self._name_
return self._name_
def _real_value_(self):
raise NotImplementedError()
def _restore_(self):
for attribute, original_value in reversed(self._changed_attributes_.items()):
setattr(self._real_value_(), attribute, original_value)
for item, original_value in reversed(self._changed_items_.items()):
self._real_value_()[item] = original_value
def __getattr__(self, attr):
new_attribute = OverrideAttribute(self._context_manager_, name=attr, parent=self)
self._children_.append(new_attribute)
return new_attribute
def __getitem__(self, item):
new_item = OverrideItem(self._context_manager_, name=item, parent=self)
self._children_.append(new_item)
return new_item
def __iter__(self):
return OverrideIter(self)
def __setattr__(self, attr, value):
if attr in (
'_name__',
'_context_manager_',
'_parent_',
'_children_',
'_original_value_',
'_changed_attributes_',
'_changed_items_'
):
self.__dict__[attr] = value
return
if attr not in self._changed_attributes_.keys():
self._changed_attributes_[attr] = getattr(self._real_value_(), attr)
self._context_manager_.register_as_changed(self)
setattr(self._real_value_(), attr, value)
def __setitem__(self, item, value):
if item not in self._changed_items_.keys():
self._changed_items_[item] = self._real_value_()[item]
self._context_manager_.register_as_changed(self)
self._real_value_()[item] = value
def __eq__(self, other):
return self._real_value_() == other
def __gt__(self, other):
return self._real_value_() > other
def __lt__(self, other):
return self._real_value_() < other
def __ge__(self, other):
return self._real_value_() >= other
def __le__(self, other):
return self._real_value_() <= other
def __call__(self, *args, **kwargs):
# TODO : surround str value with quotes
arguments = list([str(arg) for arg in args]) + ['{}={}'.format(key, value) for key, value in kwargs.items()]
arguments = ', '.join(arguments)
raise RuntimeError('Overrider does not allow call to {}({})'.format(self._path_, arguments))
class OverrideRoot(OverrideBase):
@property
def _name_(self):
return self._name__
def _real_value_(self):
return self._parent_
class OverrideAttribute(OverrideBase):
@property
def _name_(self):
return '.{}'.format(self._name__)
def _real_value_(self):
return getattr(self._parent_._real_value_(), self._name__)
class OverrideItem(OverrideBase):
@property
def _name_(self):
if isinstance(self._name__, str):
return '["{}"]'.format(self._name__)
return '[{}]'.format(self._name__)
def _real_value_(self):
return self._parent_._real_value_()[self._name__]
class Overrider:
def __init__(self, name, parent):
self.name = name
self.parent = parent
self.override = None
self.registered_overrides = list()
def __enter__(self):
self.override = OverrideRoot(
context_manager=self,
parent=self.parent,
name=self.name
)
return self.override
def __exit__(self, exc_type, exc_val, exc_tb):
self.restore()
def register_as_changed(self, override):
self.registered_overrides.append(override)
def restore(self):
for override in reversed(self.registered_overrides):
override._restore_()

View File

@ -1,3 +1,21 @@
# ##### 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 asyncio import asyncio
import logging import logging
import os import os
@ -470,7 +488,7 @@ def sanitize_deps_graph(dummy):
if client and client.state['STATE'] in [STATE_ACTIVE]: if client and client.state['STATE'] in [STATE_ACTIVE]:
for node_key in client.list(): for node_key in client.list():
client.get(node_key).resolve() client.get(node_key)._resolve()
@persistent @persistent

View File

@ -1,3 +1,21 @@
# ##### 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 import logging
import bpy import bpy

View File

@ -1,3 +1,21 @@
# ##### 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 copy import copy
import logging import logging
import math import math

View File

@ -1,3 +1,21 @@
# ##### 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 bpy import bpy
from . import operators, utils from . import operators, utils

View File

@ -1,9 +1,28 @@
# ##### 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 json import json
import logging import logging
import os import os
import random import random
import string import string
import sys import sys
import time
from uuid import uuid4 from uuid import uuid4
from collections.abc import Iterable from collections.abc import Iterable
@ -157,4 +176,7 @@ def resolve_from_id(id, optionnal_type=None):
def get_preferences(): def get_preferences():
return bpy.context.preferences.addons[__package__].preferences return bpy.context.preferences.addons[__package__].preferences
def current_milli_time():
return int(round(time.time() * 1000))