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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2017-06-26 08:57:14 +0300
committerCampbell Barton <ideasman42@gmail.com>2017-06-26 09:38:04 +0300
commit28b2f1c30504ce0e437b21f7964282fcd6894421 (patch)
tree8653eeeb0edb90cf41a93bc2b196ac55739f2a32 /release
parentc9e33b36de25d272a9896ad0a85dcc69ee0692ba (diff)
Manipulator: Python API
Initial support for Python/Manipulator integration from 'custom-manipulators' branch. Supports: - Registering custom manipulators & manipulator-groups. - Modifying RNA properties, custom values via get/set callbacks, or invoking an operator. - Drawing shape presets for Python defined manipulators (arrow, circle, face-maps) Limitations: - Only float properties supported. - Drawing only supported via shape presets. (we'll likely want a way to define custom geometry or draw directly). - When to refresh, recalculate manipulators will likely need integration with notifier system. Development will be continued in the 2.8 branch
Diffstat (limited to 'release')
-rw-r--r--release/scripts/modules/bpy_types.py29
-rw-r--r--release/scripts/templates_py/manipulator_operator.py224
-rw-r--r--release/scripts/templates_py/manipulator_operator_target.py45
-rw-r--r--release/scripts/templates_py/manipulator_simple.py42
4 files changed, 340 insertions, 0 deletions
diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py
index 600b29a6b2b..89d8e0746a6 100644
--- a/release/scripts/modules/bpy_types.py
+++ b/release/scripts/modules/bpy_types.py
@@ -592,6 +592,35 @@ class OrderedMeta(RNAMeta):
return OrderedDictMini() # collections.OrderedDict()
+# Same as 'Operator'
+# only without 'as_keywords'
+class Manipulator(StructRNA, metaclass=OrderedMeta):
+ __slots__ = ()
+
+ def __getattribute__(self, attr):
+ properties = StructRNA.path_resolve(self, "properties")
+ bl_rna = getattr(properties, "bl_rna", None)
+ if (bl_rna is not None) and (attr in bl_rna.properties):
+ return getattr(properties, attr)
+ return super().__getattribute__(attr)
+
+ def __setattr__(self, attr, value):
+ properties = StructRNA.path_resolve(self, "properties")
+ bl_rna = getattr(properties, "bl_rna", None)
+ if (bl_rna is not None) and (attr in bl_rna.properties):
+ return setattr(properties, attr, value)
+ return super().__setattr__(attr, value)
+
+ def __delattr__(self, attr):
+ properties = StructRNA.path_resolve(self, "properties")
+ bl_rna = getattr(properties, "bl_rna", None)
+ if (bl_rna is not None) and (attr in bl_rna.properties):
+ return delattr(properties, attr)
+ return super().__delattr__(attr)
+
+ target_set_handler = _bpy._rna_manipulator_target_set_handler
+
+
# Only defined so operators members can be used by accessing self.order
# with doc generation 'self.properties.bl_rna.properties' can fail
class Operator(StructRNA, metaclass=OrderedMeta):
diff --git a/release/scripts/templates_py/manipulator_operator.py b/release/scripts/templates_py/manipulator_operator.py
new file mode 100644
index 00000000000..0ba871bea84
--- /dev/null
+++ b/release/scripts/templates_py/manipulator_operator.py
@@ -0,0 +1,224 @@
+# Example of an operator which uses manipulators to control its properties.
+#
+# Usage: Run this script, then in mesh edit-mode press Spacebar
+# to activate the operator "Select Side of Plane"
+# The manipulators can then be used to adjust the plane in the 3D view.
+#
+import bpy
+import bmesh
+
+from bpy.types import (
+ Operator,
+ ManipulatorGroup,
+)
+
+from bpy.props import (
+ FloatVectorProperty,
+)
+
+def main(context, plane_co, plane_no):
+ obj = context.active_object
+ matrix = obj.matrix_world.copy()
+ me = obj.data
+ bm = bmesh.from_edit_mesh(me)
+
+ plane_dot = plane_no.dot(plane_co)
+
+ for v in bm.verts:
+ co = matrix * v.co
+ v.select = (plane_no.dot(co) > plane_dot)
+ bm.select_flush_mode()
+
+ bmesh.update_edit_mesh(me)
+
+
+class SelectSideOfPlane(Operator):
+ """UV Operator description"""
+ bl_idname = "mesh.select_side_of_plane"
+ bl_label = "Select Side of Plane"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ plane_co = FloatVectorProperty(
+ size=3,
+ default=(0, 0, 0),
+ )
+ plane_no = FloatVectorProperty(
+ size=3,
+ default=(0, 0, 1),
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return (context.mode == 'EDIT_MESH')
+
+ def invoke(self, context, event):
+
+ if not self.properties.is_property_set("plane_co"):
+ self.plane_co = context.scene.cursor_location
+
+ if not self.properties.is_property_set("plane_no"):
+ if context.space_data.type == 'VIEW_3D':
+ rv3d = context.space_data.region_3d
+ view_inv = rv3d.view_matrix.to_3x3()
+ # view y axis
+ self.plane_no = view_inv[1].normalized()
+
+ self.execute(context)
+
+ if context.space_data.type == 'VIEW_3D':
+ wm = context.window_manager
+ wm.manipulator_group_type_add(SelectSideOfPlaneManipulatorGroup.bl_idname)
+
+ return {'FINISHED'}
+
+ def execute(self, context):
+ from mathutils import Vector
+ main(context, Vector(self.plane_co), Vector(self.plane_no))
+ return {'FINISHED'}
+
+
+# Manipulators for plane_co, plane_no
+class SelectSideOfPlaneManipulatorGroup(ManipulatorGroup):
+ bl_idname = "MESH_WGT_select_side_of_plane"
+ bl_label = "Side of Plane Manipulator"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'WINDOW'
+ bl_options = {'3D'}
+
+ # Helper functions
+ @staticmethod
+ def my_target_operator(context):
+ wm = context.window_manager
+ op = wm.operators[-1] if wm.operators else None
+ if isinstance(op, SelectSideOfPlane):
+ return op
+ return None
+
+ @staticmethod
+ def my_view_orientation(context):
+ rv3d = context.space_data.region_3d
+ view_inv = rv3d.view_matrix.to_3x3()
+ return view_inv.normalized()
+
+ @classmethod
+ def poll(cls, context):
+ op = cls.my_target_operator(context)
+ if op is None:
+ wm = context.window_manager
+ wm.manipulator_group_type_remove(SelectSideOfPlaneManipulatorGroup.bl_idname)
+ return False
+ return True
+
+ def setup(self, context):
+ from mathutils import Matrix, Vector
+
+ # ----
+ # Grab
+
+ def grab_get_cb():
+ op = SelectSideOfPlaneManipulatorGroup.my_target_operator(context)
+ return op.plane_co
+
+ def grab_set_cb(value):
+ op = SelectSideOfPlaneManipulatorGroup.my_target_operator(context)
+ op.plane_co = value
+ # XXX, this may change!
+ op.execute(context)
+
+ mpr = self.manipulators.new("MANIPULATOR_WT_grab_3d")
+ mpr.target_set_handler("offset", get=grab_get_cb, set=grab_set_cb)
+
+ mpr.use_draw_value = True
+
+ mpr.color = 0.8, 0.8, 0.8, 0.5
+ mpr.color_highlight = 1.0, 1.0, 1.0, 1.0
+ mpr.scale_basis = 0.2
+
+ self.widget_grab = mpr
+
+ # ----
+ # Dial
+
+ def direction_get_cb():
+ op = SelectSideOfPlaneManipulatorGroup.my_target_operator(context)
+
+ no_a = self.widget_dial.matrix_basis.col[1].xyz
+ no_b = Vector(op.plane_no)
+
+ no_a = (no_a * self.view_inv).xy.normalized()
+ no_b = (no_b * self.view_inv).xy.normalized()
+ return no_a.angle_signed(no_b)
+
+ def direction_set_cb(value):
+ op = SelectSideOfPlaneManipulatorGroup.my_target_operator(context)
+ matrix_rotate = Matrix.Rotation(-value, 3, self.rotate_axis)
+ no = matrix_rotate * self.widget_dial.matrix_basis.col[1].xyz
+ op.plane_no = no
+ op.execute(context)
+
+ mpr = self.manipulators.new("MANIPULATOR_WT_dial_3d")
+ mpr.target_set_handler("offset", get=direction_get_cb, set=direction_set_cb)
+ mpr.draw_options = {'ANGLE_START_Y'}
+
+ mpr.use_draw_value = True
+
+ mpr.color = 0.8, 0.8, 0.8, 0.5
+ mpr.color_highlight = 1.0, 1.0, 1.0, 1.0
+
+ self.widget_dial = mpr
+
+ def draw_prepare(self, context):
+ from mathutils import Vector
+
+ view_inv = self.my_view_orientation(context)
+
+ self.view_inv = view_inv
+ self.rotate_axis = view_inv[2].xyz
+ self.rotate_up = view_inv[1].xyz
+
+ op = self.my_target_operator(context)
+
+ co = Vector(op.plane_co)
+ no = Vector(op.plane_no).normalized()
+
+ # Grab
+ no_z = no
+ no_y = no_z.orthogonal()
+ no_x = no_z.cross(no_y)
+
+ matrix = self.widget_grab.matrix_basis
+ matrix.identity()
+ matrix.col[0].xyz = no_x
+ matrix.col[1].xyz = no_y
+ matrix.col[2].xyz = no_z
+ matrix.col[3].xyz = co
+
+ # Dial
+ no_z = self.rotate_axis
+ no_y = (no - (no.project(no_z))).normalized()
+ no_x = self.rotate_axis.cross(no_y)
+
+ matrix = self.widget_dial.matrix_basis
+ matrix.identity()
+ matrix.col[0].xyz = no_x
+ matrix.col[1].xyz = no_y
+ matrix.col[2].xyz = no_z
+ matrix.col[3].xyz = co
+
+
+classes = (
+ SelectSideOfPlane,
+ SelectSideOfPlaneManipulatorGroup,
+)
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+
+def unregister():
+ for cls in reversed(classes):
+ bpy.utils.unregister_class(cls)
+
+if __name__ == "__main__":
+ register()
diff --git a/release/scripts/templates_py/manipulator_operator_target.py b/release/scripts/templates_py/manipulator_operator_target.py
new file mode 100644
index 00000000000..eafe8b1a863
--- /dev/null
+++ b/release/scripts/templates_py/manipulator_operator_target.py
@@ -0,0 +1,45 @@
+# Example of a manipulator that activates an operator
+# using the predefined dial manipulator to change the camera roll.
+#
+# Usage: Run this script and select a camera in the 3D view.
+#
+import bpy
+from bpy.types import (
+ ManipulatorGroup,
+)
+
+class MyCameraWidgetGroup(ManipulatorGroup):
+ bl_idname = "OBJECT_WGT_test_camera"
+ bl_label = "Object Camera Test Widget"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'WINDOW'
+ bl_options = {'3D', 'PERSISTENT'}
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.object
+ return (ob and ob.type == 'CAMERA')
+
+ def setup(self, context):
+ # Run an operator using the dial manipulator
+ ob = context.object
+ mpr = self.manipulators.new("MANIPULATOR_WT_dial_3d")
+ props = mpr.target_set_operator("transform.rotate")
+ props.constraint_axis = False, False, True
+ props.constraint_orientation = 'LOCAL'
+ props.release_confirm = True
+
+ mpr.matrix_basis = ob.matrix_world.normalized()
+ mpr.line_width = 3
+
+ mpr.color = 0.8, 0.8, 0.8, 0.5
+ mpr.color_highlight = 1.0, 1.0, 1.0, 1.0
+
+ self.roll_widget = mpr
+
+ def refresh(self, context):
+ ob = context.object
+ mpr = self.roll_widget
+ mpr.matrix_basis = ob.matrix_world.normalized()
+
+bpy.utils.register_class(MyCameraWidgetGroup)
diff --git a/release/scripts/templates_py/manipulator_simple.py b/release/scripts/templates_py/manipulator_simple.py
new file mode 100644
index 00000000000..6cb232312e8
--- /dev/null
+++ b/release/scripts/templates_py/manipulator_simple.py
@@ -0,0 +1,42 @@
+# Example of a group that edits a single property
+# using the predefined manipulator arrow.
+#
+# Usage: Select a lamp in the 3D view and drag the arrow at it's rear
+# to change it's energy value.
+#
+import bpy
+from bpy.types import (
+ ManipulatorGroup,
+)
+
+class MyLampWidgetGroup(ManipulatorGroup):
+ bl_idname = "OBJECT_WGT_lamp_test"
+ bl_label = "Test Lamp Widget"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'WINDOW'
+ bl_options = {'3D', 'PERSISTENT'}
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.object
+ return (ob and ob.type == 'LAMP')
+
+ def setup(self, context):
+ # Arrow manipulator has one 'offset' property we can assign to the lamp energy.
+ ob = context.object
+ mpr = self.manipulators.new("MANIPULATOR_WT_arrow_3d")
+ mpr.target_set_prop("offset", ob.data, "energy")
+ mpr.matrix_basis = ob.matrix_world.normalized()
+ mpr.draw_style = 'BOX'
+
+ mpr.color = 1, 0.5, 0, 0.5
+ mpr.color_highlight = 1, 0.5, 1, 0.5
+
+ self.energy_widget = mpr
+
+ def refresh(self, context):
+ ob = context.object
+ mpr = self.energy_widget
+ mpr.matrix_basis = ob.matrix_world.normalized()
+
+bpy.utils.register_class(MyLampWidgetGroup)