gizmo_operator.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. # Example of an operator which uses gizmos to control its properties.
  2. #
  3. # Usage: Run this script, then in mesh edit-mode press F3
  4. # to activate the operator "Select Side of Plane"
  5. # The gizmos can then be used to adjust the plane in the 3D view.
  6. #
  7. import bpy
  8. import bmesh
  9. from bpy.types import (
  10. Operator,
  11. GizmoGroup,
  12. )
  13. from bpy.props import (
  14. FloatVectorProperty,
  15. )
  16. def main(context, plane_co, plane_no):
  17. obj = context.active_object
  18. matrix = obj.matrix_world.copy()
  19. me = obj.data
  20. bm = bmesh.from_edit_mesh(me)
  21. plane_dot = plane_no.dot(plane_co)
  22. for v in bm.verts:
  23. co = matrix @ v.co
  24. v.select = (plane_no.dot(co) > plane_dot)
  25. bm.select_flush_mode()
  26. bmesh.update_edit_mesh(me)
  27. class SelectSideOfPlane(Operator):
  28. """UV Operator description"""
  29. bl_idname = "mesh.select_side_of_plane"
  30. bl_label = "Select Side of Plane"
  31. bl_options = {'REGISTER', 'UNDO'}
  32. plane_co: FloatVectorProperty(
  33. size=3,
  34. default=(0, 0, 0),
  35. )
  36. plane_no: FloatVectorProperty(
  37. size=3,
  38. default=(0, 0, 1),
  39. )
  40. @classmethod
  41. def poll(cls, context):
  42. return (context.mode == 'EDIT_MESH')
  43. def invoke(self, context, event):
  44. if not self.properties.is_property_set("plane_co"):
  45. self.plane_co = context.scene.cursor.location
  46. if not self.properties.is_property_set("plane_no"):
  47. if context.space_data.type == 'VIEW_3D':
  48. rv3d = context.space_data.region_3d
  49. view_inv = rv3d.view_matrix.to_3x3()
  50. # view y axis
  51. self.plane_no = view_inv[1].normalized()
  52. self.execute(context)
  53. if context.space_data.type == 'VIEW_3D':
  54. wm = context.window_manager
  55. wm.gizmo_group_type_ensure(SelectSideOfPlaneGizmoGroup.bl_idname)
  56. return {'FINISHED'}
  57. def execute(self, context):
  58. from mathutils import Vector
  59. main(context, Vector(self.plane_co), Vector(self.plane_no))
  60. return {'FINISHED'}
  61. # Gizmos for plane_co, plane_no
  62. class SelectSideOfPlaneGizmoGroup(GizmoGroup):
  63. bl_idname = "MESH_GGT_select_side_of_plane"
  64. bl_label = "Side of Plane Gizmo"
  65. bl_space_type = 'VIEW_3D'
  66. bl_region_type = 'WINDOW'
  67. bl_options = {'3D'}
  68. # Helper functions
  69. @staticmethod
  70. def my_target_operator(context):
  71. wm = context.window_manager
  72. op = wm.operators[-1] if wm.operators else None
  73. if isinstance(op, SelectSideOfPlane):
  74. return op
  75. return None
  76. @staticmethod
  77. def my_view_orientation(context):
  78. rv3d = context.space_data.region_3d
  79. view_inv = rv3d.view_matrix.to_3x3()
  80. return view_inv.normalized()
  81. @classmethod
  82. def poll(cls, context):
  83. op = cls.my_target_operator(context)
  84. if op is None:
  85. wm = context.window_manager
  86. wm.gizmo_group_type_unlink_delayed(SelectSideOfPlaneGizmoGroup.bl_idname)
  87. return False
  88. return True
  89. def setup(self, context):
  90. from mathutils import Matrix, Vector
  91. # ----
  92. # Move
  93. def move_get_cb():
  94. op = SelectSideOfPlaneGizmoGroup.my_target_operator(context)
  95. return op.plane_co
  96. def move_set_cb(value):
  97. op = SelectSideOfPlaneGizmoGroup.my_target_operator(context)
  98. op.plane_co = value
  99. # XXX, this may change!
  100. op.execute(context)
  101. mpr = self.gizmos.new("GIZMO_GT_move_3d")
  102. mpr.target_set_handler("offset", get=move_get_cb, set=move_set_cb)
  103. mpr.use_draw_value = True
  104. mpr.color = 0.8, 0.8, 0.8
  105. mpr.alpha = 0.5
  106. mpr.color_highlight = 1.0, 1.0, 1.0
  107. mpr.alpha_highlight = 1.0
  108. mpr.scale_basis = 0.2
  109. self.widget_move = mpr
  110. # ----
  111. # Dial
  112. def direction_get_cb():
  113. op = SelectSideOfPlaneGizmoGroup.my_target_operator(context)
  114. no_a = self.widget_dial.matrix_basis.col[1].xyz
  115. no_b = Vector(op.plane_no)
  116. no_a = (no_a @ self.view_inv).xy.normalized()
  117. no_b = (no_b @ self.view_inv).xy.normalized()
  118. return no_a.angle_signed(no_b)
  119. def direction_set_cb(value):
  120. op = SelectSideOfPlaneGizmoGroup.my_target_operator(context)
  121. matrix_rotate = Matrix.Rotation(-value, 3, self.rotate_axis)
  122. no = matrix_rotate @ self.widget_dial.matrix_basis.col[1].xyz
  123. op.plane_no = no
  124. op.execute(context)
  125. mpr = self.gizmos.new("GIZMO_GT_dial_3d")
  126. mpr.target_set_handler("offset", get=direction_get_cb, set=direction_set_cb)
  127. mpr.draw_options = {'ANGLE_START_Y'}
  128. mpr.use_draw_value = True
  129. mpr.color = 0.8, 0.8, 0.8
  130. mpr.alpha = 0.5
  131. mpr.color_highlight = 1.0, 1.0, 1.0
  132. mpr.alpha_highlight = 1.0
  133. self.widget_dial = mpr
  134. def draw_prepare(self, context):
  135. from mathutils import Vector
  136. view_inv = self.my_view_orientation(context)
  137. self.view_inv = view_inv
  138. self.rotate_axis = view_inv[2].xyz
  139. self.rotate_up = view_inv[1].xyz
  140. op = self.my_target_operator(context)
  141. co = Vector(op.plane_co)
  142. no = Vector(op.plane_no).normalized()
  143. # Move
  144. no_z = no
  145. no_y = no_z.orthogonal()
  146. no_x = no_z.cross(no_y)
  147. matrix = self.widget_move.matrix_basis
  148. matrix.identity()
  149. matrix.col[0].xyz = no_x
  150. matrix.col[1].xyz = no_y
  151. matrix.col[2].xyz = no_z
  152. matrix.col[3].xyz = co
  153. # Dial
  154. no_z = self.rotate_axis
  155. no_y = (no - (no.project(no_z))).normalized()
  156. no_x = self.rotate_axis.cross(no_y)
  157. matrix = self.widget_dial.matrix_basis
  158. matrix.identity()
  159. matrix.col[0].xyz = no_x
  160. matrix.col[1].xyz = no_y
  161. matrix.col[2].xyz = no_z
  162. matrix.col[3].xyz = co
  163. classes = (
  164. SelectSideOfPlane,
  165. SelectSideOfPlaneGizmoGroup,
  166. )
  167. def register():
  168. for cls in classes:
  169. bpy.utils.register_class(cls)
  170. def unregister():
  171. for cls in reversed(classes):
  172. bpy.utils.unregister_class(cls)
  173. if __name__ == "__main__":
  174. register()