blender/release/scripts/templates_py/gizmo_operator.py
Campbell Barton f560dc6892 Cleanup: rename variables for gizmo templates
The abbreviation was from 'manipulator',
which was changed to gizmo during development.

Also correct operator description.
2021-01-29 11:33:54 +11:00

235 lines
6.1 KiB
Python

# Example of an operator which uses gizmos to control its properties.
#
# Usage: Run this script, then in mesh edit-mode press F3
# to activate the operator "Select Side of Plane"
# The gizmos can then be used to adjust the plane in the 3D view.
#
import bpy
import bmesh
from bpy.types import (
Operator,
GizmoGroup,
)
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):
"""Select all vertices on one side of a plane defined by a location and a direction"""
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.gizmo_group_type_ensure(SelectSideOfPlaneGizmoGroup.bl_idname)
return {'FINISHED'}
def execute(self, context):
from mathutils import Vector
main(context, Vector(self.plane_co), Vector(self.plane_no))
return {'FINISHED'}
# Gizmos for plane_co, plane_no
class SelectSideOfPlaneGizmoGroup(GizmoGroup):
bl_idname = "MESH_GGT_select_side_of_plane"
bl_label = "Side of Plane Gizmo"
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.gizmo_group_type_unlink_delayed(SelectSideOfPlaneGizmoGroup.bl_idname)
return False
return True
def setup(self, context):
from mathutils import Matrix, Vector
# ----
# Move
def move_get_cb():
op = SelectSideOfPlaneGizmoGroup.my_target_operator(context)
return op.plane_co
def move_set_cb(value):
op = SelectSideOfPlaneGizmoGroup.my_target_operator(context)
op.plane_co = value
# XXX, this may change!
op.execute(context)
gz = self.gizmos.new("GIZMO_GT_move_3d")
gz.target_set_handler("offset", get=move_get_cb, set=move_set_cb)
gz.use_draw_value = True
gz.color = 0.8, 0.8, 0.8
gz.alpha = 0.5
gz.color_highlight = 1.0, 1.0, 1.0
gz.alpha_highlight = 1.0
gz.scale_basis = 0.2
self.gizmo_move = gz
# ----
# Dial
def direction_get_cb():
op = SelectSideOfPlaneGizmoGroup.my_target_operator(context)
no_a = self.gizmo_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 = SelectSideOfPlaneGizmoGroup.my_target_operator(context)
matrix_rotate = Matrix.Rotation(-value, 3, self.rotate_axis)
no = matrix_rotate @ self.gizmo_dial.matrix_basis.col[1].xyz
op.plane_no = no
op.execute(context)
gz = self.gizmos.new("GIZMO_GT_dial_3d")
gz.target_set_handler("offset", get=direction_get_cb, set=direction_set_cb)
gz.draw_options = {'ANGLE_START_Y'}
gz.use_draw_value = True
gz.color = 0.8, 0.8, 0.8
gz.alpha = 0.5
gz.color_highlight = 1.0, 1.0, 1.0
gz.alpha_highlight = 1.0
self.gizmo_dial = gz
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()
# Move
no_z = no
no_y = no_z.orthogonal()
no_x = no_z.cross(no_y)
matrix = self.gizmo_move.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.gizmo_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,
SelectSideOfPlaneGizmoGroup,
)
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()