diff options
author | Campbell Barton <ideasman42@gmail.com> | 2017-06-26 08:57:14 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2017-06-26 09:38:04 +0300 |
commit | 28b2f1c30504ce0e437b21f7964282fcd6894421 (patch) | |
tree | 8653eeeb0edb90cf41a93bc2b196ac55739f2a32 /release | |
parent | c9e33b36de25d272a9896ad0a85dcc69ee0692ba (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.py | 29 | ||||
-rw-r--r-- | release/scripts/templates_py/manipulator_operator.py | 224 | ||||
-rw-r--r-- | release/scripts/templates_py/manipulator_operator_target.py | 45 | ||||
-rw-r--r-- | release/scripts/templates_py/manipulator_simple.py | 42 |
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) |