diff --git a/multi_user/__init__.py b/multi_user/__init__.py
index f27d541..1015e79 100644
--- a/multi_user/__init__.py
+++ b/multi_user/__init__.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
bl_info = {
"name": "Multi-User",
"author": "Swann Martinez",
diff --git a/multi_user/bl_types/__init__.py b/multi_user/bl_types/__init__.py
index c3e9605..b5427c2 100644
--- a/multi_user/bl_types/__init__.py
+++ b/multi_user/bl_types/__init__.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
__all__ = [
'bl_object',
'bl_mesh',
diff --git a/multi_user/bl_types/bl_action.py b/multi_user/bl_types/bl_action.py
index 9d4c28f..2ca2cf3 100644
--- a/multi_user/bl_types/bl_action.py
+++ b/multi_user/bl_types/bl_action.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
import copy
+import numpy as np
+from enum import Enum
from .. import utils
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):
bl_id = "actions"
@@ -14,30 +203,11 @@ class BlAction(BlDatablock):
bl_delay_apply = 1
bl_automatic_push = True
bl_icon = 'ACTION_TWEAK'
-
- def construct(self, data):
+
+ def _construct(self, data):
return bpy.data.actions.new(data["name"])
- 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()
+ def _load(self, data, target):
for dumped_fcurve in data["fcurves"]:
dumped_data_path = dumped_fcurve["data_path"]
dumped_array_index = dumped_fcurve["dumped_array_index"]
@@ -47,53 +217,13 @@ class BlAction(BlDatablock):
if fcurve is None:
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
- 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):
+ def _dump(self, pointer=None):
assert(pointer)
dumper = utils.dump_anything.Dumper()
- dumper.exclude_filter =[
+ dumper.exclude_filter = [
'name_full',
'original',
'use_fake_user',
@@ -106,27 +236,13 @@ class BlAction(BlDatablock):
'users'
]
dumper.depth = 1
- data = dumper.dump(pointer)
+ data = dumper.dump(pointer)
-
data["fcurves"] = []
- dumper.depth = 2
+
for fcurve in self.pointer.fcurves:
- fc = {
- "data_path": fcurve.data_path,
- "dumped_array_index": fcurve.array_index,
- "keyframe_points": []
- }
+ data["fcurves"].append(dump_fcurve(fcurve, use_numpy=True))
- for k in fcurve.keyframe_points:
- fc["keyframe_points"].append(
- dumper.dump(k)
- )
-
- data["fcurves"].append(fc)
return data
-
-
-
diff --git a/multi_user/bl_types/bl_armature.py b/multi_user/bl_types/bl_armature.py
index 8aca65c..81d464f 100644
--- a/multi_user/bl_types/bl_armature.py
+++ b/multi_user/bl_types/bl_armature.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
-from ..libs.overrider import Overrider
from .. import utils
from .. import presence, operators
from .bl_datablock import BlDatablock
-# WIP
-
class BlArmature(BlDatablock):
bl_id = "armatures"
@@ -17,7 +32,7 @@ class BlArmature(BlDatablock):
bl_automatic_push = True
bl_icon = 'ARMATURE_DATA'
- def construct(self, data):
+ def _construct(self, data):
return bpy.data.armatures.new(data["name"])
def load_implementation(self, data, target):
diff --git a/multi_user/bl_types/bl_camera.py b/multi_user/bl_types/bl_camera.py
index 72700a2..4876010 100644
--- a/multi_user/bl_types/bl_camera.py
+++ b/multi_user/bl_types/bl_camera.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
@@ -13,6 +31,10 @@ class BlCamera(BlDatablock):
bl_automatic_push = True
bl_icon = 'CAMERA_DATA'
+ def _construct(self, data):
+ return bpy.data.cameras.new(data["name"])
+
+
def load_implementation(self, data, target):
utils.dump_anything.load(target, data)
@@ -22,12 +44,11 @@ class BlCamera(BlDatablock):
if 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):
assert(pointer)
+ # TODO: background image support
+
dumper = utils.dump_anything.Dumper()
dumper.depth = 2
dumper.include_filter = [
@@ -49,6 +70,14 @@ class BlCamera(BlDatablock):
'aperture_blades',
'aperture_rotation',
'aperture_ratio',
+ 'display_size',
+ 'show_limits',
+ 'show_mist',
+ 'show_sensor',
+ 'show_name',
+ 'sensor_fit',
+ 'sensor_height',
+ 'sensor_width',
]
return dumper.dump(pointer)
diff --git a/multi_user/bl_types/bl_collection.py b/multi_user/bl_types/bl_collection.py
index 38ac0b1..3d3f9f7 100644
--- a/multi_user/bl_types/bl_collection.py
+++ b/multi_user/bl_types/bl_collection.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
@@ -13,7 +31,7 @@ class BlCollection(BlDatablock):
bl_delay_apply = 1
bl_automatic_push = True
- def construct(self, data):
+ def _construct(self, data):
if self.is_library:
with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData):
targetData.collections = [
@@ -28,7 +46,7 @@ class BlCollection(BlDatablock):
instance.uuid = self.uuid
return instance
- def load(self, data, target):
+ def load_implementation(self, data, target):
# Load other meshes metadata
# dump_anything.load(target, data)
target.name = data["name"]
diff --git a/multi_user/bl_types/bl_curve.py b/multi_user/bl_types/bl_curve.py
index 4a498e9..ac6fbdc 100644
--- a/multi_user/bl_types/bl_curve.py
+++ b/multi_user/bl_types/bl_curve.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import bpy.types as T
import mathutils
@@ -15,7 +33,7 @@ class BlCurve(BlDatablock):
bl_automatic_push = True
bl_icon = 'CURVE_DATA'
- def construct(self, data):
+ def _construct(self, data):
return bpy.data.curves.new(data["name"], data["type"])
def load_implementation(self, data, target):
diff --git a/multi_user/bl_types/bl_datablock.py b/multi_user/bl_types/bl_datablock.py
index 60c0a86..3a5d8b3 100644
--- a/multi_user/bl_types/bl_datablock.py
+++ b/multi_user/bl_types/bl_datablock.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
@@ -61,7 +79,6 @@ class BlDatablock(ReplicatedDatablock):
bl_automatic_push : boolean
bl_icon : type icon (blender icon name)
"""
- bl_id = "scenes"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -77,23 +94,7 @@ class BlDatablock(ReplicatedDatablock):
self.diff_method = DIFF_BINARY
- def library_apply(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):
+ def _resolve(self):
datablock_ref = None
datablock_root = getattr(bpy.data, self.bl_id)
datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root)
@@ -108,7 +109,7 @@ class BlDatablock(ReplicatedDatablock):
self.pointer = datablock_ref
- def dump(self, pointer=None):
+ def _dump(self, pointer=None):
data = {}
# Dump animation data
if utils.has_action(pointer):
@@ -134,7 +135,7 @@ class BlDatablock(ReplicatedDatablock):
def dump_implementation(self, data, target):
raise NotImplementedError
- def load(self, data, target):
+ def _load(self, data, target):
# Load animation data
if 'animation_data' in data.keys():
if target.animation_data is None:
diff --git a/multi_user/bl_types/bl_gpencil.py b/multi_user/bl_types/bl_gpencil.py
index d593e6d..79ada32 100644
--- a/multi_user/bl_types/bl_gpencil.py
+++ b/multi_user/bl_types/bl_gpencil.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
+import numpy as np
from ..libs import dump_anything
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)
- for k,v in target.frames.items():
- target.frames.remove(v)
-
- for frame in data["frames"]:
-
- tframe = target.frames.new(data["frames"][frame]['frame_number'])
+ :param stroke: target grease pencil stroke
+ :type stroke: bpy.types.GPencilStroke
+ :return: dict
+ """
- for stroke in data["frames"][frame]["strokes"]:
- try:
- tstroke = tframe.strokes[stroke]
- except:
- tstroke = tframe.strokes.new()
- dump_anything.load(
- tstroke, data["frames"][frame]["strokes"][stroke])
+ assert(stroke)
- for point in data["frames"][frame]["strokes"][stroke]["points"]:
- p = data["frames"][frame]["strokes"][stroke]["points"][point]
+ dumper = dump_anything.Dumper()
+ 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)
- tpoint = tstroke.points[len(tstroke.points)-1]
+ # Stoke points
+ 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):
bl_id = "grease_pencils"
bl_class = bpy.types.GreasePencil
- bl_delay_refresh = 5
- bl_delay_apply = 5
+ bl_delay_refresh = 2
+ bl_delay_apply = 1
bl_automatic_push = True
bl_icon = 'GREASEPENCIL'
- def construct(self, data):
+ def _construct(self, data):
return bpy.data.grease_pencils.new(data["name"])
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()
if "materials" in data.keys():
for mat in data['materials']:
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):
assert(pointer)
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
diff --git a/multi_user/bl_types/bl_image.py b/multi_user/bl_types/bl_image.py
index 8b965f1..8497725 100644
--- a/multi_user/bl_types/bl_image.py
+++ b/multi_user/bl_types/bl_image.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
import os
@@ -10,8 +28,10 @@ def dump_image(image):
if image.source == "GENERATED":
prefs = utils.get_preferences()
img_name = "{}.png".format(image.name)
-
+
+ # Cache the image on the disk
image.filepath_raw = os.path.join(prefs.cache_directory, img_name)
+ os.makedirs(prefs.cache_directory, exist_ok=True)
image.file_format = "PNG"
image.save()
@@ -35,14 +55,14 @@ class BlImage(BlDatablock):
bl_automatic_push = False
bl_icon = 'IMAGE_DATA'
- def construct(self, data):
+ def _construct(self, data):
return bpy.data.images.new(
name=data['name'],
width=data['size'][0],
height=data['size'][1]
)
- def load(self, data, target):
+ def _load(self, data, target):
image = target
prefs = utils.get_preferences()
@@ -59,7 +79,7 @@ class BlImage(BlDatablock):
image.colorspace_settings.name = data["colorspace_settings"]["name"]
- def dump(self, data, pointer=None):
+ def _dump(self, pointer=None):
assert(pointer)
data = {}
data['pixels'] = dump_image(pointer)
diff --git a/multi_user/bl_types/bl_lattice.py b/multi_user/bl_types/bl_lattice.py
index 5a7ac28..ca6de99 100644
--- a/multi_user/bl_types/bl_lattice.py
+++ b/multi_user/bl_types/bl_lattice.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
@@ -18,7 +36,7 @@ class BlLattice(BlDatablock):
for point in data['points']:
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"])
def dump_implementation(self, data, pointer=None):
diff --git a/multi_user/bl_types/bl_library.py b/multi_user/bl_types/bl_library.py
index 8e909ac..f53e6de 100644
--- a/multi_user/bl_types/bl_library.py
+++ b/multi_user/bl_types/bl_library.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
@@ -13,11 +31,11 @@ class BlLibrary(BlDatablock):
bl_automatic_push = True
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):
targetData = sourceData
return sourceData
- def load(self, data, target):
+ def _load(self, data, target):
pass
def dump(self, pointer=None):
diff --git a/multi_user/bl_types/bl_light.py b/multi_user/bl_types/bl_light.py
index 6dfdd80..67f8af9 100644
--- a/multi_user/bl_types/bl_light.py
+++ b/multi_user/bl_types/bl_light.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
@@ -13,10 +31,10 @@ class BlLight(BlDatablock):
bl_automatic_push = True
bl_icon = 'LIGHT_DATA'
- def construct(self, data):
+ def _construct(self, data):
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)
def dump_implementation(self, data, pointer=None):
@@ -41,7 +59,8 @@ class BlLight(BlDatablock):
"contact_shadow_distance",
"contact_shadow_soft_size",
"contact_shadow_bias",
- "contact_shadow_thickness"
+ "contact_shadow_thickness",
+ "shape"
]
data = dumper.dump(pointer)
return data
diff --git a/multi_user/bl_types/bl_lightprobe.py b/multi_user/bl_types/bl_lightprobe.py
index bc8edfd..abd20e9 100644
--- a/multi_user/bl_types/bl_lightprobe.py
+++ b/multi_user/bl_types/bl_lightprobe.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
import logging
@@ -15,16 +33,16 @@ class BlLightprobe(BlDatablock):
bl_automatic_push = True
bl_icon = 'LIGHTPROBE_GRID'
- def load_implementation(self, data, target):
- utils.dump_anything.load(target, data)
-
- def construct(self, data):
+ def _construct(self, data):
type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type']
# See https://developer.blender.org/D6396
if bpy.app.version[1] >= 83:
return bpy.data.lightprobes.new(data["name"], type)
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):
assert(pointer)
diff --git a/multi_user/bl_types/bl_material.py b/multi_user/bl_types/bl_material.py
index fa33f40..228283d 100644
--- a/multi_user/bl_types/bl_material.py
+++ b/multi_user/bl_types/bl_material.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
import logging
@@ -7,66 +25,62 @@ from ..libs import dump_anything
from .bl_datablock import BlDatablock
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):
- target_node = target_node_tree.nodes.get(source["name"])
+ :arg node_data: dumped node data
+ :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:
- node_type = source["bl_idname"]
+ dump_anything.load(target_node, node_data)
- 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"):
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:
logger.error("{} not supported, skipping".format(input))
-def load_link(target_node_tree, source):
- input_socket = target_node_tree.nodes[source['to_node']
- ['name']].inputs[source['to_socket']['name']]
- output_socket = target_node_tree.nodes[source['from_node']
- ['name']].outputs[source['from_socket']['name']]
+def load_links(links_data, node_tree):
+ """ Load node_tree links from a list
+
+ :arg links_data: dumped node links
+ :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):
bl_id = "materials"
@@ -76,7 +90,7 @@ class BlMaterial(BlDatablock):
bl_automatic_push = True
bl_icon = 'MATERIAL_DATA'
- def construct(self, data):
+ def _construct(self, data):
return bpy.data.materials.new(data["name"])
def load_implementation(self, data, target):
@@ -100,19 +114,25 @@ class BlMaterial(BlDatablock):
# Load 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
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):
assert(pointer)
mat_dumper = dump_anything.Dumper()
mat_dumper.depth = 2
mat_dumper.exclude_filter = [
+ "is_embed_data",
+ "is_evaluated",
+ "name_full",
+ "bl_description",
+ "bl_icon",
+ "bl_idname",
+ "bl_label",
"preview",
"original",
"uuid",
@@ -121,47 +141,44 @@ class BlMaterial(BlDatablock):
"line_color",
"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)
if pointer.use_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:
+
nodes[node.name] = node_dumper.dump(node)
if hasattr(node, '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'):
nodes[node.name]['inputs'][i.name] = input_dumper.dump(
i)
@@ -184,11 +201,39 @@ class BlMaterial(BlDatablock):
'location'
]
nodes[node.name]['mapping'] = curve_dumper.dump(node.mapping)
+
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:
- 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
def resolve_deps_implementation(self):
diff --git a/multi_user/bl_types/bl_mesh.py b/multi_user/bl_types/bl_mesh.py
index f502cae..51ca774 100644
--- a/multi_user/bl_types/bl_mesh.py
+++ b/multi_user/bl_types/bl_mesh.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import bmesh
import mathutils
+import logging
+import numpy as np
from .. import utils
from ..libs.replication.replication.constants import DIFF_BINARY
from .bl_datablock import BlDatablock
-
-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
+logger = logging.getLogger(__name__)
class BlMesh(BlDatablock):
bl_id = "meshes"
bl_class = bpy.types.Mesh
- bl_delay_refresh = 10
- bl_delay_apply = 10
+ bl_delay_refresh = 2
+ bl_delay_apply = 1
bl_automatic_push = True
bl_icon = 'MESH_DATA'
- def construct(self, data):
+ def _construct(self, data):
instance = bpy.data.meshes.new(data["name"])
instance.uuid = self.uuid
return instance
def load_implementation(self, data, target):
if not target or not target.is_editmode:
- # 1 - LOAD MATERIAL SLOTS
- # SLots
- i = 0
+ utils.dump_anything.load(target, data)
+
+ # MATERIAL SLOTS
+ target.materials.clear()
for m in data["material_list"]:
target.materials.append(bpy.data.materials[m])
- # 2 - LOAD GEOMETRY
- mesh_buffer = bmesh.new()
+ # CLEAR GEOMETRY
+ if target.vertices:
+ target.clear_geometry()
- for i in data["verts"]:
- v = mesh_buffer.verts.new(data["verts"][i]["co"])
- v.normal = data["verts"][i]["normal"]
- mesh_buffer.verts.ensure_lookup_table()
+ # VERTS
+ vertices = np.frombuffer(data["verts_co"], dtype=np.float64)
+ vert_count = int(len(vertices)/3)
+ target.vertices.add(vert_count)
- for i in data["edges"]:
- verts = mesh_buffer.verts
- v1 = data["edges"][i]["verts"][0]
- v2 = data["edges"][i]["verts"][1]
- edge = mesh_buffer.edges.new([verts[v1], verts[v2]])
- edge.smooth = data["edges"][i]["smooth"]
+ # EDGES
+
+ egdes_vert = np.frombuffer(data["egdes_vert"], dtype=np.int)
- mesh_buffer.edges.ensure_lookup_table()
- for p in data["faces"]:
- verts = []
- for v in data["faces"][p]["verts"]:
- verts.append(mesh_buffer.verts[v])
+ edge_count = data["egdes_count"]
+ target.edges.add(edge_count)
+
+
- if len(verts) > 0:
- f = mesh_buffer.faces.new(verts)
+ # LOOPS
+ 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"]
- f.normal = data["faces"][p]["normal"]
- f.index = data["faces"][p]["index"]
- 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)
+ # POLY
+ poly_count = data["poly_count"]
+ target.polygons.add(poly_count)
- # 3 - LOAD METADATA
- # uv's
- utils.dump_anything.load(target.uv_layers, data['uv_layers'])
+ poly_loop_start = np.frombuffer(
+ data["poly_loop_start"], dtype=np.int)
+ 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()
- skin_layer = mesh_buffer.verts.layers.skin.verify()
+ poly_mat = np.frombuffer(data["poly_mat"], dtype=np.int)
- 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):
assert(pointer)
+ mesh = pointer
+
dumper = utils.dump_anything.Dumper()
- dumper.depth = 2
+ dumper.depth = 1
dumper.include_filter = [
'name',
'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
m_list = []
for material in pointer.materials:
diff --git a/multi_user/bl_types/bl_metaball.py b/multi_user/bl_types/bl_metaball.py
index 7dec312..36c998f 100644
--- a/multi_user/bl_types/bl_metaball.py
+++ b/multi_user/bl_types/bl_metaball.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
@@ -13,7 +31,7 @@ class BlMetaball(BlDatablock):
bl_automatic_push = True
bl_icon = 'META_BALL'
- def construct(self, data):
+ def _construct(self, data):
return bpy.data.metaballs.new(data["name"])
def load(self, data, target):
diff --git a/multi_user/bl_types/bl_object.py b/multi_user/bl_types/bl_object.py
index 26b5469..2abb6ad 100644
--- a/multi_user/bl_types/bl_object.py
+++ b/multi_user/bl_types/bl_object.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
import logging
@@ -8,22 +26,6 @@ from .bl_datablock import BlDatablock
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):
target_bone.rotation_mode = data['rotation_mode']
@@ -38,7 +40,7 @@ class BlObject(BlDatablock):
bl_automatic_push = True
bl_icon = 'OBJECT_DATA'
- def construct(self, data):
+ def _construct(self, data):
pointer = None
if self.is_library:
@@ -50,7 +52,7 @@ class BlObject(BlDatablock):
instance.uuid = self.uuid
return instance
- # Object specific constructor...
+ # TODO: refactoring
if "data" not in data:
pass
elif data["data"] in bpy.data.meshes.keys():
@@ -87,32 +89,7 @@ class BlObject(BlDatablock):
def load_implementation(self, data, target):
# Load transformation data
- rot_mode = 'rotation_quaternion' if data['rotation_mode'] == 'QUATERNION' else 'rotation_euler'
- 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'])
+ utils.dump_anything.load(target, data)
# Pose
if 'pose' in data:
@@ -135,28 +112,14 @@ class BlObject(BlDatablock):
bone_data = data['pose']['bones'].get(bone)
if 'constraints' in bone_data.keys():
- load_constraints(
- target_bone, bone_data['constraints'])
+ utils.dump_anything.load(target_bone, bone_data['constraints'])
+
load_pose(target_bone, bone_data)
if 'bone_index' in bone_data.keys():
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
if 'vertex_groups' in data:
target.vertex_groups.clear()
@@ -220,7 +183,6 @@ class BlObject(BlDatablock):
data["modifiers"] = {}
for index, modifier in enumerate(pointer.modifiers):
data["modifiers"][modifier.name] = dumper.dump(modifier)
- data["modifiers"][modifier.name]['m_index'] = index
# CONSTRAINTS
# OBJECT
@@ -299,18 +261,18 @@ class BlObject(BlDatablock):
data['vertex_groups'] = vg_data
# SHAPE KEYS
- pointer_data = pointer.data
- if hasattr(pointer_data, 'shape_keys') and pointer_data.shape_keys:
+ object_data = pointer.data
+ if hasattr(object_data, 'shape_keys') and object_data.shape_keys:
dumper = utils.dump_anything.Dumper()
dumper.depth = 2
dumper.include_filter = [
'reference_key',
'use_relative'
]
- data['shape_keys'] = dumper.dump(pointer_data.shape_keys)
- data['shape_keys']['reference_key'] = pointer_data.shape_keys.reference_key.name
+ data['shape_keys'] = dumper.dump(object_data.shape_keys)
+ data['shape_keys']['reference_key'] = object_data.shape_keys.reference_key.name
key_blocks = {}
- for key in pointer_data.shape_keys.key_blocks:
+ for key in object_data.shape_keys.key_blocks:
dumper.depth = 3
dumper.include_filter = [
'name',
diff --git a/multi_user/bl_types/bl_scene.py b/multi_user/bl_types/bl_scene.py
index bac83f8..5b7b9e5 100644
--- a/multi_user/bl_types/bl_scene.py
+++ b/multi_user/bl_types/bl_scene.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
@@ -12,12 +30,12 @@ class BlScene(BlDatablock):
bl_automatic_push = True
bl_icon = 'SCENE_DATA'
- def construct(self, data):
+ def _construct(self, data):
instance = bpy.data.scenes.new(data["name"])
instance.uuid = self.uuid
return instance
- def load(self, data, target):
+ def load_implementation(self, data, target):
target = self.pointer
# Load other meshes metadata
utils.dump_anything.load(target, data)
@@ -55,7 +73,13 @@ class BlScene(BlDatablock):
scene_dumper = utils.dump_anything.Dumper()
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)
scene_dumper.depth = 3
diff --git a/multi_user/bl_types/bl_speaker.py b/multi_user/bl_types/bl_speaker.py
index d675ee2..9d2f87c 100644
--- a/multi_user/bl_types/bl_speaker.py
+++ b/multi_user/bl_types/bl_speaker.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
@@ -16,7 +34,7 @@ class BlSpeaker(BlDatablock):
def load_implementation(self, data, target):
utils.dump_anything.load(target, data)
- def construct(self, data):
+ def _construct(self, data):
return bpy.data.speakers.new(data["name"])
def dump_implementation(self, data, pointer=None):
diff --git a/multi_user/bl_types/bl_world.py b/multi_user/bl_types/bl_world.py
index 588ef64..8f79dc1 100644
--- a/multi_user/bl_types/bl_world.py
+++ b/multi_user/bl_types/bl_world.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
import mathutils
from .. import utils
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):
@@ -14,10 +32,10 @@ class BlWorld(BlDatablock):
bl_automatic_push = True
bl_icon = 'WORLD_DATA'
- def construct(self, data):
+ def _construct(self, data):
return bpy.data.worlds.new(data["name"])
- def load(self, data, target):
+ def load_implementation(self, data, target):
if data["use_nodes"]:
if target.node_tree is None:
target.use_nodes = True
@@ -25,13 +43,13 @@ class BlWorld(BlDatablock):
target.node_tree.nodes.clear()
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
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):
assert(pointer)
@@ -86,8 +104,9 @@ class BlWorld(BlDatablock):
nodes[node.name]['inputs'][i.name] = input_dumper.dump(
i)
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
def resolve_deps_implementation(self):
@@ -101,6 +120,3 @@ class BlWorld(BlDatablock):
deps.append(self.pointer.library)
return deps
- def is_valid(self):
- return bpy.data.worlds.get(self.data['name'])
-
diff --git a/multi_user/delayable.py b/multi_user/delayable.py
index 6c100ab..78598d4 100644
--- a/multi_user/delayable.py
+++ b/multi_user/delayable.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
import logging
import bpy
diff --git a/multi_user/environment.py b/multi_user/environment.py
index 077f451..716fcf8 100644
--- a/multi_user/environment.py
+++ b/multi_user/environment.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import collections
import logging
import os
diff --git a/multi_user/libs/dump_anything.py b/multi_user/libs/dump_anything.py
index 9c4f3fc..d686085 100644
--- a/multi_user/libs/dump_anything.py
+++ b/multi_user/libs/dump_anything.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import logging
+
import bpy
import bpy.types as T
import mathutils
+logger = logging.getLogger(__name__)
def remove_items_from_dict(d, keys, recursive=False):
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:
return isinstance(x.bl_rna_property, t)
else:
- isinstance(x.read(), t)
+ return isinstance(x.read(), t)
return filter_function
@@ -73,8 +93,9 @@ def _load_filter_default(default):
class Dumper:
+ # TODO: support occlude readonly
def __init__(self):
- self.verbose = False
+ self.verbose = True
self.depth = 1
self.keep_compounds_as_leaves = False
self.accept_read_only = True
@@ -83,7 +104,6 @@ class Dumper:
self.type_subset = self.match_subset_all
self.include_filter = []
self.exclude_filter = []
- # self._atomic_types = [] # TODO future option?
def dump(self, any):
return self._dump_any(any, 0)
@@ -175,7 +195,8 @@ class Dumper:
if (self.include_filter and p not in self.include_filter):
return False
getattr(default, p)
- except AttributeError:
+ except AttributeError as err:
+ logger.debug(err)
return False
if p.startswith("__"):
return False
@@ -238,14 +259,12 @@ class BlenderAPIElement:
def write(self, value):
# take precaution if property is read-only
- try:
- if self.sub_element_name:
- setattr(self.api_element, self.sub_element_name, value)
- else:
- self.api_element = value
- except AttributeError as err:
- if not self.occlude_read_only:
- raise err
+ if self.sub_element_name and \
+ not self.api_element.is_property_readonly(self.sub_element_name):
+
+ setattr(self.api_element, self.sub_element_name, value)
+ else:
+ self.api_element = value
def extend(self, element_name):
return BlenderAPIElement(self.read(), element_name)
@@ -262,7 +281,7 @@ class BlenderAPIElement:
class Loader:
def __init__(self):
self.type_subset = self.match_subset_all
- self.occlude_read_only = True
+ self.occlude_read_only = False
self.order = ['*']
def load(self, dst_data, src_dumped_data):
@@ -287,6 +306,7 @@ class Loader:
for i in range(len(dump)):
element.read()[i] = dump[i]
except AttributeError as err:
+ logger.debug(err)
if not self.occlude_read_only:
raise err
@@ -297,29 +317,77 @@ class Loader:
CONSTRUCTOR_NEW = "new"
CONSTRUCTOR_ADD = "add"
+ DESTRUCTOR_REMOVE = "remove"
+ DESTRUCTOR_CLEAR = "clear"
+
constructors = {
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
+
constructor = constructors.get(type(element_type))
+
if constructor is None: # collection type not supported
return
- for dumped_element in dump.values():
- try:
- constructor_parameters = [dumped_element[name]
- for name in constructor[1]]
- except KeyError:
- print("Collection load error, missing parameters.")
- continue # TODO handle error
- new_element = getattr(element.read(), constructor[0])(
- *constructor_parameters)
+
+ destructor = destructors.get(type(element_type))
+
+ # Try to clear existing
+ if destructor:
+ if destructor == DESTRUCTOR_REMOVE:
+ collection = element.read()
+ for i in range(len(collection)-1):
+ 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(
BlenderAPIElement(
new_element, occlude_read_only=self.occlude_read_only),
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):
rna_property_type = pointer.bl_rna_property.fixed_type
if not rna_property_type:
@@ -336,6 +404,8 @@ class Loader:
pointer.write(bpy.data.meshes.get(dump))
elif isinstance(rna_property_type, T.Material):
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):
matrix.write(mathutils.Matrix(dump))
@@ -365,11 +435,11 @@ class Loader:
for k in self._ordered_keys(dump.keys()):
v = dump[k]
if not hasattr(default.read(), k):
- continue # TODO error handling
+ logger.debug(f"Load default, skipping {default} : {k}")
try:
self._load_any(default.extend(k), v)
- except:
- pass
+ except Exception as err:
+ logger.debug(f"Cannot load {k}: {err}")
@property
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.Quaternion, use_bl_rna=False), self._load_quaternion),
(_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.StringProperty), self._load_identity),
(_load_filter_type(T.EnumProperty), self._load_identity),
diff --git a/multi_user/libs/overrider.py b/multi_user/libs/overrider.py
deleted file mode 100644
index 964833d..0000000
--- a/multi_user/libs/overrider.py
+++ /dev/null
@@ -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_()
diff --git a/multi_user/libs/replication b/multi_user/libs/replication
index 114b2eb..70b2d24 160000
--- a/multi_user/libs/replication
+++ b/multi_user/libs/replication
@@ -1 +1 @@
-Subproject commit 114b2ebe37ccd10b36544924d23443df0867581b
+Subproject commit 70b2d24d15690540c0e0bee43bd82bf338f986c9
diff --git a/multi_user/operators.py b/multi_user/operators.py
index 424418f..1d302c1 100644
--- a/multi_user/operators.py
+++ b/multi_user/operators.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import asyncio
import logging
import os
@@ -470,7 +488,7 @@ def sanitize_deps_graph(dummy):
if client and client.state['STATE'] in [STATE_ACTIVE]:
for node_key in client.list():
- client.get(node_key).resolve()
+ client.get(node_key)._resolve()
@persistent
diff --git a/multi_user/preferences.py b/multi_user/preferences.py
index e329e1d..29f892c 100644
--- a/multi_user/preferences.py
+++ b/multi_user/preferences.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import logging
import bpy
diff --git a/multi_user/presence.py b/multi_user/presence.py
index 5494e19..975193d 100644
--- a/multi_user/presence.py
+++ b/multi_user/presence.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import copy
import logging
import math
diff --git a/multi_user/ui.py b/multi_user/ui.py
index 21f4d48..26f5c8d 100644
--- a/multi_user/ui.py
+++ b/multi_user/ui.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import bpy
from . import operators, utils
diff --git a/multi_user/utils.py b/multi_user/utils.py
index db96e92..cd69456 100644
--- a/multi_user/utils.py
+++ b/multi_user/utils.py
@@ -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 .
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
import json
import logging
import os
import random
import string
import sys
+import time
from uuid import uuid4
from collections.abc import Iterable
@@ -157,4 +176,7 @@ def resolve_from_id(id, optionnal_type=None):
def get_preferences():
- return bpy.context.preferences.addons[__package__].preferences
\ No newline at end of file
+ return bpy.context.preferences.addons[__package__].preferences
+
+def current_milli_time():
+ return int(round(time.time() * 1000))
\ No newline at end of file