Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'viewport_vr_preview/action_map_io.py')
-rw-r--r--viewport_vr_preview/action_map_io.py348
1 files changed, 348 insertions, 0 deletions
diff --git a/viewport_vr_preview/action_map_io.py b/viewport_vr_preview/action_map_io.py
new file mode 100644
index 00000000..c57543a0
--- /dev/null
+++ b/viewport_vr_preview/action_map_io.py
@@ -0,0 +1,348 @@
+# ##### 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 2
+# 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, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+# -----------------------------------------------------------------------------
+# Export Functions
+
+__all__ = (
+ "actionconfig_export_as_data",
+ "actionconfig_import_from_data",
+ "actionconfig_init_from_data",
+ "actionmap_init_from_data",
+ "actionmap_item_init_from_data",
+)
+
+
+def indent(levels):
+ return levels * " "
+
+
+def round_float_32(f):
+ from struct import pack, unpack
+ return unpack("f", pack("f", f))[0]
+
+
+def repr_f32(f):
+ f_round = round_float_32(f)
+ f_str = repr(f)
+ f_str_frac = f_str.partition(".")[2]
+ if not f_str_frac:
+ return f_str
+ for i in range(1, len(f_str_frac)):
+ f_test = round(f, i)
+ f_test_round = round_float_32(f_test)
+ if f_test_round == f_round:
+ return "%.*f" % (i, f_test)
+ return f_str
+
+def ami_args_as_data(ami):
+ s = [
+ f"\"type\": '{ami.type}'",
+ f"\"user_path0\": '{ami.user_path0}'",
+ f"\"user_path1\": '{ami.user_path1}'",
+ ]
+
+ if ami.type == 'FLOAT' or ami.type == 'VECTOR2D':
+ s.append(f"\"op\": '{ami.op}'")
+ s.append(f"\"op_mode\": '{ami.op_mode}'")
+ s.append(f"\"bimanual\": '{ami.bimanual}'")
+ s.append(f"\"haptic_name\": '{ami.haptic_name}'")
+ s.append(f"\"haptic_match_user_paths\": '{ami.haptic_match_user_paths}'")
+ s.append(f"\"haptic_duration\": '{ami.haptic_duration}'")
+ s.append(f"\"haptic_frequency\": '{ami.haptic_frequency}'")
+ s.append(f"\"haptic_amplitude\": '{ami.haptic_amplitude}'")
+ s.append(f"\"haptic_mode\": '{ami.haptic_mode}'")
+ elif ami.type == 'POSE':
+ s.append(f"\"pose_is_controller_grip\": '{ami.pose_is_controller_grip}'")
+ s.append(f"\"pose_is_controller_aim\": '{ami.pose_is_controller_aim}'")
+
+
+ return "{" + ", ".join(s) + "}"
+
+
+def ami_data_from_args(ami, args):
+ ami.type = args["type"]
+ ami.user_path0 = args["user_path0"]
+ ami.user_path1 = args["user_path1"]
+
+ if ami.type == 'FLOAT' or ami.type == 'VECTOR2D':
+ ami.op = args["op"]
+ ami.op_mode = args["op_mode"]
+ ami.bimanual = True if (args["bimanual"] == 'True') else False
+ ami.haptic_name = args["haptic_name"]
+ ami.haptic_match_user_paths = True if (args["haptic_match_user_paths"] == 'True') else False
+ ami.haptic_duration = float(args["haptic_duration"])
+ ami.haptic_frequency = float(args["haptic_frequency"])
+ ami.haptic_amplitude = float(args["haptic_amplitude"])
+ ami.haptic_mode = args["haptic_mode"]
+ elif ami.type == 'POSE':
+ ami.pose_is_controller_grip = True if (args["pose_is_controller_grip"] == 'True') else False
+ ami.pose_is_controller_aim = True if (args["pose_is_controller_aim"] == 'True') else False
+
+
+def _ami_properties_to_lines_recursive(level, properties, lines):
+ from bpy.types import OperatorProperties
+
+ def string_value(value):
+ if isinstance(value, (str, bool, int, set)):
+ return repr(value)
+ elif isinstance(value, float):
+ return repr_f32(value)
+ elif getattr(value, '__len__', False):
+ return repr(tuple(value))
+ raise Exception(f"Export action configuration: can't write {value!r}")
+
+ for pname in properties.bl_rna.properties.keys():
+ if pname != "rna_type":
+ value = getattr(properties, pname)
+ if isinstance(value, OperatorProperties):
+ lines_test = []
+ _ami_properties_to_lines_recursive(level + 2, value, lines_test)
+ if lines_test:
+ lines.append(f"(")
+ lines.append(f"\"{pname}\",\n")
+ lines.append(f"{indent(level + 3)}" "[")
+ lines.extend(lines_test)
+ lines.append("],\n")
+ lines.append(f"{indent(level + 3)}" "),\n" f"{indent(level + 2)}")
+ del lines_test
+ elif properties.is_property_set(pname):
+ value = string_value(value)
+ lines.append((f"(\"{pname}\", {value:s}),\n" f"{indent(level + 2)}"))
+
+
+def _ami_properties_to_lines(level, ami_props, lines):
+ if ami_props is None:
+ return
+
+ lines_test = [f"\"op_properties\":\n" f"{indent(level + 1)}" "["]
+ _ami_properties_to_lines_recursive(level, ami_props, lines_test)
+ if len(lines_test) > 1:
+ lines_test.append("],\n")
+ lines.extend(lines_test)
+
+
+def _ami_attrs_or_none(level, ami):
+ lines = []
+ _ami_properties_to_lines(level + 1, ami.op_properties, lines)
+ if not lines:
+ return None
+ return "".join(lines)
+
+
+def amb_args_as_data(amb, type):
+ s = [
+ f"\"profile\": '{amb.profile}'",
+ f"\"component_path0\": '{amb.component_path0}'",
+ f"\"component_path1\": '{amb.component_path1}'",
+ ]
+
+ if type == 'FLOAT' or type == 'VECTOR2D':
+ s.append(f"\"threshold\": '{amb.threshold}'")
+ if type == 'FLOAT':
+ s.append(f"\"axis_region\": '{amb.axis0_region}'")
+ else: # type == 'VECTOR2D':
+ s.append(f"\"axis0_region\": '{amb.axis0_region}'")
+ s.append(f"\"axis1_region\": '{amb.axis1_region}'")
+ elif type == 'POSE':
+ s.append(f"\"pose_location\": '{amb.pose_location.x, amb.pose_location.y, amb.pose_location.z}'")
+ s.append(f"\"pose_rotation\": '{amb.pose_rotation.x, amb.pose_rotation.y, amb.pose_rotation.z}'")
+
+ return "{" + ", ".join(s) + "}"
+
+
+def amb_data_from_args(amb, args, type):
+ amb.profile = args["profile"]
+ amb.component_path0 = args["component_path0"]
+ amb.component_path1 = args["component_path1"]
+
+ if type == 'FLOAT' or type == 'VECTOR2D':
+ amb.threshold = float(args["threshold"])
+ if type == 'FLOAT':
+ amb.axis0_region = args["axis_region"]
+ else: # type == 'VECTOR2D':
+ amb.axis0_region = args["axis0_region"]
+ amb.axis1_region = args["axis1_region"]
+ elif type == 'POSE':
+ l = args["pose_location"].strip(')(').split(', ')
+ amb.pose_location.x = float(l[0])
+ amb.pose_location.y = float(l[1])
+ amb.pose_location.z = float(l[2])
+ l = args["pose_rotation"].strip(')(').split(', ')
+ amb.pose_rotation.x = float(l[0])
+ amb.pose_rotation.y = float(l[1])
+ amb.pose_rotation.z = float(l[2])
+
+
+def actionconfig_export_as_data(session_state, filepath, *, sort=False):
+ export_actionmaps = []
+
+ for am in session_state.actionmaps:
+ export_actionmaps.append(am)
+
+ if sort:
+ export_actionmaps.sort(key=lambda k: k.name)
+
+ with open(filepath, "w", encoding="utf-8") as fh:
+ fw = fh.write
+
+ # Use the file version since it includes the sub-version
+ # which we can bump multiple times between releases.
+ from bpy.app import version_file
+ fw(f"actionconfig_version = {version_file!r}\n")
+ del version_file
+
+ fw("actionconfig_data = \\\n[")
+
+ for am in export_actionmaps:
+ fw("(")
+ fw(f"\"{am.name:s}\",\n")
+
+ fw(f"{indent(2)}" "{")
+ fw(f"\"items\":\n")
+ fw(f"{indent(3)}[")
+ for ami in am.actionmap_items:
+ fw(f"(")
+ fw(f"\"{ami.name:s}\"")
+ ami_args = ami_args_as_data(ami)
+ ami_data = _ami_attrs_or_none(4, ami)
+ if ami_data is None:
+ fw(f", ")
+ else:
+ fw(",\n" f"{indent(5)}")
+
+ fw(ami_args)
+ if ami_data is None:
+ fw(", None,\n")
+ else:
+ fw(",\n")
+ fw(f"{indent(5)}" "{")
+ fw(ami_data)
+ fw(f"{indent(6)}")
+ fw("}," f"{indent(5)}")
+ fw("\n")
+
+ fw(f"{indent(5)}" "{")
+ fw(f"\"bindings\":\n")
+ fw(f"{indent(6)}[")
+ for amb in ami.bindings:
+ fw(f"(")
+ fw(f"\"{amb.name:s}\"")
+ fw(f", ")
+ amb_args = amb_args_as_data(amb, ami.type)
+ fw(amb_args)
+ fw("),\n" f"{indent(7)}")
+ fw("],\n" f"{indent(6)}")
+ fw("},\n" f"{indent(5)}")
+ fw("),\n" f"{indent(4)}")
+
+ fw("],\n" f"{indent(3)}")
+ fw("},\n" f"{indent(2)}")
+ fw("),\n" f"{indent(1)}")
+
+ fw("]\n")
+ fw("\n\n")
+ fw("if __name__ == \"__main__\":\n")
+
+ # We could remove this in the future, as loading new action-maps in older Blender versions
+ # makes less and less sense as Blender changes.
+ fw(" # Only add keywords that are supported.\n")
+ fw(" from bpy.app import version as blender_version\n")
+ fw(" keywords = {}\n")
+ fw(" if blender_version >= (3, 0, 0):\n")
+ fw(" keywords[\"actionconfig_version\"] = actionconfig_version\n")
+
+ fw(" import os\n")
+ fw(" from viewport_vr_preview.io import actionconfig_import_from_data\n")
+ fw(" actionconfig_import_from_data(\n")
+ fw(" os.path.splitext(os.path.basename(__file__))[0],\n")
+ fw(" actionconfig_data,\n")
+ fw(" **keywords,\n")
+ fw(" )\n")
+
+
+# -----------------------------------------------------------------------------
+# Import Functions
+
+def _ami_props_setattr(ami_name, ami_props, attr, value):
+ if type(value) is list:
+ ami_subprop = getattr(ami_props, attr)
+ for subattr, subvalue in value:
+ _ami_props_setattr(ami_subprop, subattr, subvalue)
+ return
+
+ try:
+ setattr(ami_props, attr, value)
+ except AttributeError:
+ print(f"Warning: property '{attr}' not found in action map item '{ami_name}'")
+ except Exception as ex:
+ print(f"Warning: {ex!r}")
+
+
+def actionmap_item_init_from_data(ami, ami_bindings):
+ new_fn = getattr(ami.bindings, "new")
+ for (amb_name, amb_args) in ami_bindings:
+ amb = new_fn(amb_name, True)
+ amb_data_from_args(amb, amb_args, ami.type)
+
+
+def actionmap_init_from_data(am, am_items):
+ new_fn = getattr(am.actionmap_items, "new")
+ for (ami_name, ami_args, ami_data, ami_content) in am_items:
+ ami = new_fn(ami_name, True)
+ ami_data_from_args(ami, ami_args)
+ if ami_data is not None:
+ ami_props_data = ami_data.get("op_properties", None)
+ if ami_props_data is not None:
+ ami_props = ami.op_properties
+ assert type(ami_props_data) is list
+ for attr, value in ami_props_data:
+ _ami_props_setattr(ami_name, ami_props, attr, value)
+ ami_bindings = ami_content["bindings"]
+ assert type(ami_bindings) is list
+ actionmap_item_init_from_data(ami, ami_bindings)
+
+
+def actionconfig_init_from_data(session_state, actionconfig_data, actionconfig_version):
+ # Load data in the format defined above.
+ #
+ # Runs at load time, keep this fast!
+ if actionconfig_version is not None:
+ from .versioning import actionconfig_update
+ actionconfig_data = actionconfig_update(actionconfig_data, actionconfig_version)
+
+ for (am_name, am_content) in actionconfig_data:
+ am = session_state.actionmaps.new(session_state, am_name, True)
+ am_items = am_content["items"]
+ # Check here instead of inside 'actionmap_init_from_data'
+ # because we want to allow both tuple & list types in that case.
+ #
+ # For full action maps, ensure these are always lists to allow for extending them
+ # in a generic way that doesn't have to check for the type each time.
+ assert type(am_items) is list
+ actionmap_init_from_data(am, am_items)
+
+
+def actionconfig_import_from_data(session_state, actionconfig_data, *, actionconfig_version=(0, 0, 0)):
+ # Load data in the format defined above.
+ #
+ # Runs at load time, keep this fast!
+ import bpy
+ actionconfig_init_from_data(session_state, actionconfig_data, actionconfig_version)