Add Chromium-only Blender WebEngine parity work
This commit is contained in:
157
blender-5.2.0/scripts/templates_py/Gizmo/custom_geometry.py
Normal file
157
blender-5.2.0/scripts/templates_py/Gizmo/custom_geometry.py
Normal file
@@ -0,0 +1,157 @@
|
||||
# Example of a custom widget that defines its own geometry.
|
||||
#
|
||||
# Usage: Select a light in the 3D view and drag the arrow at it's rear
|
||||
# to change it's energy value.
|
||||
#
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
Gizmo,
|
||||
GizmoGroup,
|
||||
)
|
||||
|
||||
# Coordinates (each one is a triangle).
|
||||
custom_shape_verts = (
|
||||
(3.0, 1.0, -1.0), (2.0, 2.0, -1.0), (3.0, 3.0, -1.0),
|
||||
(1.0, 3.0, 1.0), (3.0, 3.0, -1.0), (1.0, 3.0, -1.0),
|
||||
(3.0, 3.0, 1.0), (3.0, 1.0, -1.0), (3.0, 3.0, -1.0),
|
||||
(2.0, 0.0, 1.0), (3.0, 1.0, -1.0), (3.0, 1.0, 1.0),
|
||||
(2.0, 0.0, -1.0), (2.0, 2.0, 1.0), (2.0, 2.0, -1.0),
|
||||
(2.0, 2.0, -1.0), (0.0, 2.0, 1.0), (0.0, 2.0, -1.0),
|
||||
(1.0, 3.0, 1.0), (2.0, 2.0, 1.0), (3.0, 3.0, 1.0),
|
||||
(0.0, 2.0, -1.0), (1.0, 3.0, 1.0), (1.0, 3.0, -1.0),
|
||||
(2.0, 2.0, 1.0), (3.0, 1.0, 1.0), (3.0, 3.0, 1.0),
|
||||
(2.0, 2.0, -1.0), (1.0, 3.0, -1.0), (3.0, 3.0, -1.0),
|
||||
(-3.0, -1.0, -1.0), (-2.0, -2.0, -1.0), (-3.0, -3.0, -1.0),
|
||||
(-1.0, -3.0, 1.0), (-3.0, -3.0, -1.0), (-1.0, -3.0, -1.0),
|
||||
(-3.0, -3.0, 1.0), (-3.0, -1.0, -1.0), (-3.0, -3.0, -1.0),
|
||||
(-2.0, 0.0, 1.0), (-3.0, -1.0, -1.0), (-3.0, -1.0, 1.0),
|
||||
(-2.0, 0.0, -1.0), (-2.0, -2.0, 1.0), (-2.0, -2.0, -1.0),
|
||||
(-2.0, -2.0, -1.0), (0.0, -2.0, 1.0), (0.0, -2.0, -1.0),
|
||||
(-1.0, -3.0, 1.0), (-2.0, -2.0, 1.0), (-3.0, -3.0, 1.0),
|
||||
(0.0, -2.0, -1.0), (-1.0, -3.0, 1.0), (-1.0, -3.0, -1.0),
|
||||
(-2.0, -2.0, 1.0), (-3.0, -1.0, 1.0), (-3.0, -3.0, 1.0),
|
||||
(-2.0, -2.0, -1.0), (-1.0, -3.0, -1.0), (-3.0, -3.0, -1.0),
|
||||
(1.0, -1.0, 0.0), (-1.0, -1.0, 0.0), (0.0, 0.0, -5.0),
|
||||
(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (0.0, 0.0, 5.0),
|
||||
(1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (0.0, 0.0, 5.0),
|
||||
(1.0, 1.0, 0.0), (-1.0, 1.0, 0.0), (0.0, 0.0, 5.0),
|
||||
(-1.0, 1.0, 0.0), (-1.0, -1.0, 0.0), (0.0, 0.0, 5.0),
|
||||
(-1.0, -1.0, 0.0), (-1.0, 1.0, 0.0), (0.0, 0.0, -5.0),
|
||||
(-1.0, 1.0, 0.0), (1.0, 1.0, 0.0), (0.0, 0.0, -5.0),
|
||||
(1.0, 1.0, 0.0), (1.0, -1.0, 0.0), (0.0, 0.0, -5.0),
|
||||
(3.0, 1.0, -1.0), (2.0, 0.0, -1.0), (2.0, 2.0, -1.0),
|
||||
(1.0, 3.0, 1.0), (3.0, 3.0, 1.0), (3.0, 3.0, -1.0),
|
||||
(3.0, 3.0, 1.0), (3.0, 1.0, 1.0), (3.0, 1.0, -1.0),
|
||||
(2.0, 0.0, 1.0), (2.0, 0.0, -1.0), (3.0, 1.0, -1.0),
|
||||
(2.0, 0.0, -1.0), (2.0, 0.0, 1.0), (2.0, 2.0, 1.0),
|
||||
(2.0, 2.0, -1.0), (2.0, 2.0, 1.0), (0.0, 2.0, 1.0),
|
||||
(1.0, 3.0, 1.0), (0.0, 2.0, 1.0), (2.0, 2.0, 1.0),
|
||||
(0.0, 2.0, -1.0), (0.0, 2.0, 1.0), (1.0, 3.0, 1.0),
|
||||
(2.0, 2.0, 1.0), (2.0, 0.0, 1.0), (3.0, 1.0, 1.0),
|
||||
(2.0, 2.0, -1.0), (0.0, 2.0, -1.0), (1.0, 3.0, -1.0),
|
||||
(-3.0, -1.0, -1.0), (-2.0, 0.0, -1.0), (-2.0, -2.0, -1.0),
|
||||
(-1.0, -3.0, 1.0), (-3.0, -3.0, 1.0), (-3.0, -3.0, -1.0),
|
||||
(-3.0, -3.0, 1.0), (-3.0, -1.0, 1.0), (-3.0, -1.0, -1.0),
|
||||
(-2.0, 0.0, 1.0), (-2.0, 0.0, -1.0), (-3.0, -1.0, -1.0),
|
||||
(-2.0, 0.0, -1.0), (-2.0, 0.0, 1.0), (-2.0, -2.0, 1.0),
|
||||
(-2.0, -2.0, -1.0), (-2.0, -2.0, 1.0), (0.0, -2.0, 1.0),
|
||||
(-1.0, -3.0, 1.0), (0.0, -2.0, 1.0), (-2.0, -2.0, 1.0),
|
||||
(0.0, -2.0, -1.0), (0.0, -2.0, 1.0), (-1.0, -3.0, 1.0),
|
||||
(-2.0, -2.0, 1.0), (-2.0, 0.0, 1.0), (-3.0, -1.0, 1.0),
|
||||
(-2.0, -2.0, -1.0), (0.0, -2.0, -1.0), (-1.0, -3.0, -1.0),
|
||||
)
|
||||
|
||||
|
||||
class MyCustomShapeWidget(Gizmo):
|
||||
bl_idname = "VIEW3D_GT_custom_shape_widget"
|
||||
bl_target_properties = (
|
||||
{"id": "offset", "type": 'FLOAT', "array_length": 1},
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
"custom_shape",
|
||||
"init_mouse_y",
|
||||
"init_value",
|
||||
)
|
||||
|
||||
def _update_offset_matrix(self):
|
||||
# offset behind the light
|
||||
self.matrix_offset.col[3][2] = self.target_get_value("offset") / -10.0
|
||||
|
||||
def draw(self, context):
|
||||
self._update_offset_matrix()
|
||||
self.draw_custom_shape(self.custom_shape)
|
||||
|
||||
def draw_select(self, context, select_id):
|
||||
self._update_offset_matrix()
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
|
||||
def setup(self):
|
||||
if not hasattr(self, "custom_shape"):
|
||||
self.custom_shape = self.new_custom_shape('TRIS', custom_shape_verts)
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.init_mouse_y = event.mouse_y
|
||||
self.init_value = self.target_get_value("offset")
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def exit(self, context, cancel):
|
||||
context.area.header_text_set(None)
|
||||
if cancel:
|
||||
self.target_set_value("offset", self.init_value)
|
||||
|
||||
def modal(self, context, event, tweak):
|
||||
delta = (event.mouse_y - self.init_mouse_y) / 10.0
|
||||
if 'SNAP' in tweak:
|
||||
delta = round(delta)
|
||||
if 'PRECISE' in tweak:
|
||||
delta /= 10.0
|
||||
value = self.init_value - delta
|
||||
self.target_set_value("offset", value)
|
||||
context.area.header_text_set("My Gizmo: {:.4f}".format(value))
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
class MyCustomShapeWidgetGroup(GizmoGroup):
|
||||
bl_idname = "OBJECT_GGT_light_test"
|
||||
bl_label = "Test Light 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 == 'LIGHT')
|
||||
|
||||
def setup(self, context):
|
||||
# Assign the `offset` target property to the light energy.
|
||||
ob = context.object
|
||||
gz = self.gizmos.new(MyCustomShapeWidget.bl_idname)
|
||||
gz.target_set_prop("offset", ob.data, "energy")
|
||||
|
||||
gz.color = 1.0, 0.5, 1.0
|
||||
gz.alpha = 0.5
|
||||
|
||||
gz.color_highlight = 1.0, 1.0, 1.0
|
||||
gz.alpha_highlight = 0.5
|
||||
|
||||
# units are large, so shrink to something more reasonable.
|
||||
gz.scale_basis = 0.1
|
||||
gz.use_draw_modal = True
|
||||
|
||||
self.energy_gizmo = gz
|
||||
|
||||
def refresh(self, context):
|
||||
ob = context.object
|
||||
gz = self.energy_gizmo
|
||||
gz.matrix_basis = ob.matrix_world.normalized()
|
||||
|
||||
|
||||
classes = (
|
||||
MyCustomShapeWidget,
|
||||
MyCustomShapeWidgetGroup,
|
||||
)
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
235
blender-5.2.0/scripts/templates_py/Gizmo/operator.py
Normal file
235
blender-5.2.0/scripts/templates_py/Gizmo/operator.py
Normal file
@@ -0,0 +1,235 @@
|
||||
# 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', 'EXCLUDE_MODAL'}
|
||||
|
||||
# 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
|
||||
# The location callback handles the location.
|
||||
# `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()
|
||||
50
blender-5.2.0/scripts/templates_py/Gizmo/operator_target.py
Normal file
50
blender-5.2.0/scripts/templates_py/Gizmo/operator_target.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# Example of a gizmo that activates an operator
|
||||
# using the predefined dial gizmo to change the camera roll.
|
||||
#
|
||||
# Usage: Run this script and select a camera in the 3D view.
|
||||
#
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
GizmoGroup,
|
||||
)
|
||||
|
||||
|
||||
class MyCameraWidgetGroup(GizmoGroup):
|
||||
bl_idname = "OBJECT_GGT_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 gizmo
|
||||
ob = context.object
|
||||
gz = self.gizmos.new("GIZMO_GT_dial_3d")
|
||||
props = gz.target_set_operator("transform.rotate")
|
||||
props.constraint_axis = False, False, True
|
||||
props.orient_type = 'LOCAL'
|
||||
props.release_confirm = True
|
||||
|
||||
gz.matrix_basis = ob.matrix_world.normalized()
|
||||
gz.line_width = 3
|
||||
|
||||
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.roll_gizmo = gz
|
||||
|
||||
def refresh(self, context):
|
||||
ob = context.object
|
||||
gz = self.roll_gizmo
|
||||
gz.matrix_basis = ob.matrix_world.normalized()
|
||||
|
||||
|
||||
bpy.utils.register_class(MyCameraWidgetGroup)
|
||||
59
blender-5.2.0/scripts/templates_py/Gizmo/simple_2d.py
Normal file
59
blender-5.2.0/scripts/templates_py/Gizmo/simple_2d.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# Example of a group that lets you move an object in the 3D view
|
||||
# using the default translate transform operator.
|
||||
#
|
||||
# Usage: Select and object and drag the Gizmo to
|
||||
# move it in the 3D Viewport.
|
||||
#
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
GizmoGroup,
|
||||
)
|
||||
|
||||
|
||||
class MyTranslateWidgetGroup(GizmoGroup):
|
||||
bl_idname = "OBJECT_GGT_translate_test"
|
||||
bl_label = "Test Translate Widget"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_options = {'PERSISTENT', 'SCALE'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ob = context.object
|
||||
return ob is not None
|
||||
|
||||
def draw_prepare(self, context):
|
||||
region = context.region
|
||||
|
||||
# Place the Gizmo in the lower center of the 3D Viewport.
|
||||
self.translate_gizmo.matrix_basis[0][3] = region.width / 2
|
||||
self.translate_gizmo.matrix_basis[1][3] = region.height / 16
|
||||
|
||||
def setup(self, context):
|
||||
gz = self.gizmos.new("GIZMO_GT_button_2d")
|
||||
gz.target_set_operator("transform.translate")
|
||||
gz.draw_options = {'BACKDROP', 'OUTLINE'}
|
||||
|
||||
gz.color = 0.0, 0.5, 1.0
|
||||
gz.alpha = 0.2
|
||||
gz.backdrop_fill_alpha = 0.1
|
||||
|
||||
gz.color_highlight = 1.0, 0.5, 0.0
|
||||
gz.alpha_highlight = 0.8
|
||||
|
||||
gz.use_tooltip = True
|
||||
gz.line_width = 1.5
|
||||
|
||||
# Same as buttons defined in C++ code.
|
||||
gz.scale_basis = (80 * 0.35) / 2
|
||||
|
||||
# Show a dragging mouse cursor when hovering the gizmo.
|
||||
gz.show_drag = True
|
||||
|
||||
# Can also use gz.icon_value to use a custom/generated preview icon.
|
||||
gz.icon = 'EMPTY_ARROWS'
|
||||
|
||||
self.translate_gizmo = gz
|
||||
|
||||
|
||||
bpy.utils.register_class(MyTranslateWidgetGroup)
|
||||
47
blender-5.2.0/scripts/templates_py/Gizmo/simple_3d.py
Normal file
47
blender-5.2.0/scripts/templates_py/Gizmo/simple_3d.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# Example of a group that edits a single property
|
||||
# using the predefined gizmo arrow.
|
||||
#
|
||||
# Usage: Select a light in the 3D view and drag the arrow at its rear
|
||||
# to change its energy value.
|
||||
#
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
GizmoGroup,
|
||||
)
|
||||
|
||||
|
||||
class MyLightWidgetGroup(GizmoGroup):
|
||||
bl_idname = "OBJECT_GGT_light_test"
|
||||
bl_label = "Test Light 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 == 'LIGHT')
|
||||
|
||||
def setup(self, context):
|
||||
# Arrow gizmo has one `offset` property we can assign to the light energy.
|
||||
ob = context.object
|
||||
gz = self.gizmos.new("GIZMO_GT_arrow_3d")
|
||||
gz.target_set_prop("offset", ob.data, "energy")
|
||||
gz.matrix_basis = ob.matrix_world.normalized()
|
||||
gz.draw_style = 'BOX'
|
||||
|
||||
gz.color = 1.0, 0.5, 0.0
|
||||
gz.alpha = 0.5
|
||||
|
||||
gz.color_highlight = 1.0, 0.5, 1.0
|
||||
gz.alpha_highlight = 0.5
|
||||
|
||||
self.energy_gizmo = gz
|
||||
|
||||
def refresh(self, context):
|
||||
ob = context.object
|
||||
gz = self.energy_gizmo
|
||||
gz.matrix_basis = ob.matrix_world.normalized()
|
||||
|
||||
|
||||
bpy.utils.register_class(MyLightWidgetGroup)
|
||||
76
blender-5.2.0/scripts/templates_py/Operator/file_export.py
Normal file
76
blender-5.2.0/scripts/templates_py/Operator/file_export.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import bpy
|
||||
|
||||
|
||||
def write_some_data(context, filepath, use_some_setting):
|
||||
print("running write_some_data...")
|
||||
f = open(filepath, "w", encoding='utf-8')
|
||||
f.write("Hello World {:s}".format(use_some_setting))
|
||||
f.close()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ExportHelper is a helper class, defines filename and
|
||||
# invoke() function which calls the file selector.
|
||||
from bpy_extras.io_utils import ExportHelper
|
||||
from bpy.props import StringProperty, BoolProperty, EnumProperty
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
class ExportSomeData(Operator, ExportHelper):
|
||||
"""This appears in the tooltip of the operator and in the generated docs"""
|
||||
bl_idname = "export_test.some_data" # Important since its how bpy.ops.import_test.some_data is constructed.
|
||||
bl_label = "Export Some Data"
|
||||
|
||||
# ExportHelper mix-in class uses this.
|
||||
filename_ext = ".txt"
|
||||
|
||||
filter_glob: StringProperty(
|
||||
default="*.txt",
|
||||
options={'HIDDEN'},
|
||||
maxlen=255, # Max internal buffer length, longer would be clamped.
|
||||
)
|
||||
|
||||
# List of operator properties, the attributes will be assigned
|
||||
# to the class instance from the operator settings before calling.
|
||||
use_setting: BoolProperty(
|
||||
name="Example Boolean",
|
||||
description="Example Tooltip",
|
||||
default=True,
|
||||
)
|
||||
|
||||
type: EnumProperty(
|
||||
name="Example Enum",
|
||||
description="Choose between two items",
|
||||
items=(
|
||||
('OPT_A', "First Option", "Description one"),
|
||||
('OPT_B', "Second Option", "Description two"),
|
||||
),
|
||||
default='OPT_A',
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
return write_some_data(context, self.filepath, self.use_setting)
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(ExportSomeData.bl_idname, text="Text Export Operator")
|
||||
|
||||
|
||||
# Register and add to the "file selector" menu (required to use F3 search "Text Export Operator" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(ExportSomeData)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(ExportSomeData)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# Test call.
|
||||
bpy.ops.export_test.some_data('INVOKE_DEFAULT')
|
||||
79
blender-5.2.0/scripts/templates_py/Operator/file_import.py
Normal file
79
blender-5.2.0/scripts/templates_py/Operator/file_import.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import bpy
|
||||
|
||||
|
||||
def read_some_data(context, filepath, use_some_setting):
|
||||
print("running read_some_data...")
|
||||
f = open(filepath, "r", encoding="utf-8")
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
# Would normally load the data here.
|
||||
print(data)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# ImportHelper is a helper class, defines filename and
|
||||
# invoke() function which calls the file selector.
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from bpy.props import StringProperty, BoolProperty, EnumProperty
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
class ImportSomeData(Operator, ImportHelper):
|
||||
"""This appears in the tooltip of the operator and in the generated docs"""
|
||||
bl_idname = "import_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed
|
||||
bl_label = "Import Some Data"
|
||||
|
||||
# ImportHelper mix-in class uses this.
|
||||
filename_ext = ".txt"
|
||||
|
||||
filter_glob: StringProperty(
|
||||
default="*.txt",
|
||||
options={'HIDDEN'},
|
||||
maxlen=255, # Max internal buffer length, longer would be clamped.
|
||||
)
|
||||
|
||||
# List of operator properties, the attributes will be assigned
|
||||
# to the class instance from the operator settings before calling.
|
||||
use_setting: BoolProperty(
|
||||
name="Example Boolean",
|
||||
description="Example Tooltip",
|
||||
default=True,
|
||||
)
|
||||
|
||||
type: EnumProperty(
|
||||
name="Example Enum",
|
||||
description="Choose between two items",
|
||||
items=(
|
||||
('OPT_A', "First Option", "Description one"),
|
||||
('OPT_B', "Second Option", "Description two"),
|
||||
),
|
||||
default='OPT_A',
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
return read_some_data(context, self.filepath, self.use_setting)
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu.
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(ImportSomeData.bl_idname, text="Text Import Operator")
|
||||
|
||||
|
||||
# Register and add to the "file selector" menu (required to use F3 search "Text Import Operator" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(ImportSomeData)
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(ImportSomeData)
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# Test call.
|
||||
bpy.ops.import_test.some_data('INVOKE_DEFAULT')
|
||||
116
blender-5.2.0/scripts/templates_py/Operator/mesh_add.py
Normal file
116
blender-5.2.0/scripts/templates_py/Operator/mesh_add.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
from bpy_extras.object_utils import AddObjectHelper
|
||||
|
||||
from bpy.props import (
|
||||
FloatProperty,
|
||||
)
|
||||
|
||||
|
||||
def add_box(width, height, depth):
|
||||
"""
|
||||
This function takes inputs and returns vertex and face arrays.
|
||||
no actual mesh data creation is done here.
|
||||
"""
|
||||
|
||||
verts = [
|
||||
(+1.0, +1.0, -1.0),
|
||||
(+1.0, -1.0, -1.0),
|
||||
(-1.0, -1.0, -1.0),
|
||||
(-1.0, +1.0, -1.0),
|
||||
(+1.0, +1.0, +1.0),
|
||||
(+1.0, -1.0, +1.0),
|
||||
(-1.0, -1.0, +1.0),
|
||||
(-1.0, +1.0, +1.0),
|
||||
]
|
||||
|
||||
faces = [
|
||||
(0, 1, 2, 3),
|
||||
(4, 7, 6, 5),
|
||||
(0, 4, 5, 1),
|
||||
(1, 5, 6, 2),
|
||||
(2, 6, 7, 3),
|
||||
(4, 0, 3, 7),
|
||||
]
|
||||
|
||||
# Apply size.
|
||||
for i, v in enumerate(verts):
|
||||
verts[i] = v[0] * width, v[1] * depth, v[2] * height
|
||||
|
||||
return verts, faces
|
||||
|
||||
|
||||
class AddBox(bpy.types.Operator, AddObjectHelper):
|
||||
"""Add a simple box mesh"""
|
||||
bl_idname = "mesh.primitive_box_add"
|
||||
bl_label = "Add Box"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
width: FloatProperty(
|
||||
name="Width",
|
||||
description="Box Width",
|
||||
min=0.01, max=100.0,
|
||||
default=1.0,
|
||||
)
|
||||
height: FloatProperty(
|
||||
name="Height",
|
||||
description="Box Height",
|
||||
min=0.01, max=100.0,
|
||||
default=1.0,
|
||||
)
|
||||
depth: FloatProperty(
|
||||
name="Depth",
|
||||
description="Box Depth",
|
||||
min=0.01, max=100.0,
|
||||
default=1.0,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
verts_loc, faces = add_box(
|
||||
self.width,
|
||||
self.height,
|
||||
self.depth,
|
||||
)
|
||||
|
||||
mesh = bpy.data.meshes.new("Box")
|
||||
|
||||
bm = bmesh.new()
|
||||
|
||||
for v_co in verts_loc:
|
||||
bm.verts.new(v_co)
|
||||
|
||||
bm.verts.ensure_lookup_table()
|
||||
for f_idx in faces:
|
||||
bm.faces.new([bm.verts[i] for i in f_idx])
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
|
||||
# Add the mesh as an object into the scene with this utility module.
|
||||
from bpy_extras import object_utils
|
||||
object_utils.object_data_add(context, mesh, operator=self)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(AddBox.bl_idname, icon='MESH_CUBE')
|
||||
|
||||
|
||||
# Register and add to the "add mesh" menu (required to use F3 search "Add Box" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(AddBox)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(AddBox)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# Test call.
|
||||
bpy.ops.mesh.primitive_box_add()
|
||||
56
blender-5.2.0/scripts/templates_py/Operator/mesh_uv.py
Normal file
56
blender-5.2.0/scripts/templates_py/Operator/mesh_uv.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
|
||||
|
||||
def main(context):
|
||||
obj = context.active_object
|
||||
me = obj.data
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
# Adjust UV coordinates.
|
||||
for face in bm.faces:
|
||||
for loop in face.loops:
|
||||
loop_uv = loop[uv_layer]
|
||||
# Use XY position of the vertex as a uv coordinate.
|
||||
loop_uv.uv = loop.vert.co.xy
|
||||
|
||||
bmesh.update_edit_mesh(me)
|
||||
|
||||
|
||||
class UvOperator(bpy.types.Operator):
|
||||
"""UV Operator description"""
|
||||
bl_idname = "uv.simple_operator"
|
||||
bl_label = "Simple UV Operator"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj and obj.type == 'MESH' and obj.mode == 'EDIT'
|
||||
|
||||
def execute(self, context):
|
||||
main(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(UvOperator.bl_idname, text="Simple UV Operator")
|
||||
|
||||
|
||||
# Register and add to the "UV" menu (required to also use F3 search "Simple UV Operator" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(UvOperator)
|
||||
bpy.types.IMAGE_MT_uvs.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(UvOperator)
|
||||
bpy.types.IMAGE_MT_uvs.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# Test call.
|
||||
bpy.ops.uv.simple_operator()
|
||||
58
blender-5.2.0/scripts/templates_py/Operator/modal.py
Normal file
58
blender-5.2.0/scripts/templates_py/Operator/modal.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import bpy
|
||||
from bpy.props import IntProperty, FloatProperty
|
||||
|
||||
|
||||
class ModalOperator(bpy.types.Operator):
|
||||
"""Move an object with the mouse, example"""
|
||||
bl_idname = "object.modal_operator"
|
||||
bl_label = "Simple Modal Operator"
|
||||
|
||||
first_mouse_x: IntProperty()
|
||||
first_value: FloatProperty()
|
||||
|
||||
def modal(self, context, event):
|
||||
if event.type == 'MOUSEMOVE':
|
||||
delta = self.first_mouse_x - event.mouse_x
|
||||
context.object.location.x = self.first_value + delta * 0.01
|
||||
|
||||
elif event.type == 'LEFTMOUSE':
|
||||
return {'FINISHED'}
|
||||
|
||||
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||
context.object.location.x = self.first_value
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if context.object:
|
||||
self.first_mouse_x = event.mouse_x
|
||||
self.first_value = context.object.location.x
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
else:
|
||||
self.report({'WARNING'}, "No active object, could not finish")
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(ModalOperator.bl_idname, text=ModalOperator.bl_label)
|
||||
|
||||
|
||||
# Register and add to the "view" menu (required to also use F3 search "Simple Modal Operator" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(ModalOperator)
|
||||
bpy.types.VIEW3D_MT_object.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(ModalOperator)
|
||||
bpy.types.VIEW3D_MT_object.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# Test call.
|
||||
bpy.ops.object.modal_operator('INVOKE_DEFAULT')
|
||||
83
blender-5.2.0/scripts/templates_py/Operator/modal_draw.py
Normal file
83
blender-5.2.0/scripts/templates_py/Operator/modal_draw.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import bpy
|
||||
import blf
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
def draw_callback_px(self, context):
|
||||
print("mouse points", len(self.mouse_path))
|
||||
|
||||
font_id = 0 # XXX, need to find out how best to get this.
|
||||
|
||||
# draw some text
|
||||
blf.position(font_id, 15, 30, 0)
|
||||
blf.size(font_id, 20.0)
|
||||
blf.draw(font_id, "Hello Word " + str(len(self.mouse_path)))
|
||||
|
||||
# 50% alpha, 2 pixel width line
|
||||
gpu.state.blend_set('ALPHA')
|
||||
shader = gpu.shader.from_builtin('POLYLINE_UNIFORM_COLOR')
|
||||
shader.uniform_float("color", (0.0, 0.0, 0.0, 0.5))
|
||||
shader.uniform_float("viewportSize", (context.area.width, context.area.height))
|
||||
shader.uniform_float('lineWidth', 2.0)
|
||||
batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": self.mouse_path})
|
||||
batch.draw(shader)
|
||||
|
||||
# restore gpu defaults
|
||||
gpu.state.blend_set('NONE')
|
||||
|
||||
|
||||
class ModalDrawOperator(bpy.types.Operator):
|
||||
"""Draw a line with the mouse"""
|
||||
bl_idname = "view3d.modal_draw_operator"
|
||||
bl_label = "Simple Modal View3D Operator"
|
||||
|
||||
def modal(self, context, event):
|
||||
context.area.tag_redraw()
|
||||
|
||||
if event.type == 'MOUSEMOVE':
|
||||
self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
|
||||
|
||||
elif event.type == 'LEFTMOUSE':
|
||||
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
||||
return {'FINISHED'}
|
||||
|
||||
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if context.area.type == 'VIEW_3D':
|
||||
# The arguments we pass the callback.
|
||||
args = (self, context)
|
||||
# Add the region OpenGL drawing callback draw in view space with `POST_VIEW` and `PRE_VIEW`.
|
||||
self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
|
||||
|
||||
self.mouse_path = []
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
else:
|
||||
self.report({'WARNING'}, "View3D not found, cannot run operator")
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(ModalDrawOperator.bl_idname, text="Modal Draw Operator")
|
||||
|
||||
|
||||
# Register and add to the "view" menu (required to also use F3 search "Modal Draw Operator" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(ModalDrawOperator)
|
||||
bpy.types.VIEW3D_MT_view.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(ModalDrawOperator)
|
||||
bpy.types.VIEW3D_MT_view.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
61
blender-5.2.0/scripts/templates_py/Operator/modal_timer.py
Normal file
61
blender-5.2.0/scripts/templates_py/Operator/modal_timer.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class ModalTimerOperator(bpy.types.Operator):
|
||||
"""Operator which runs itself from a timer"""
|
||||
bl_idname = "wm.modal_timer_operator"
|
||||
bl_label = "Modal Timer Operator"
|
||||
|
||||
_timer = None
|
||||
_theme = None
|
||||
_original_color = None
|
||||
|
||||
def modal(self, context, event):
|
||||
if event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||
self.cancel(context)
|
||||
return {'CANCELLED'}
|
||||
|
||||
if event.type == 'TIMER':
|
||||
# Change theme color, silly!
|
||||
color = self._theme.view_3d.space.gradients.high_gradient
|
||||
color.s = 1.0
|
||||
color.h += 0.01
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def execute(self, context):
|
||||
# Store the original color to avoid altering user settings
|
||||
self._theme = context.preferences.themes[0]
|
||||
color = self._theme.view_3d.space.gradients.high_gradient
|
||||
self._original_color = color[:]
|
||||
wm = context.window_manager
|
||||
self._timer = wm.event_timer_add(0.1, window=context.window)
|
||||
wm.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def cancel(self, context):
|
||||
wm = context.window_manager
|
||||
wm.event_timer_remove(self._timer)
|
||||
self._theme.view_3d.space.gradients.high_gradient = self._original_color
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(ModalTimerOperator.bl_idname, text=ModalTimerOperator.bl_label)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(ModalTimerOperator)
|
||||
bpy.types.VIEW3D_MT_view.append(menu_func)
|
||||
|
||||
|
||||
# Register and add to the "view" menu (required to also use F3 search "Modal Timer Operator" for quick access).
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(ModalTimerOperator)
|
||||
bpy.types.VIEW3D_MT_view.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# Test call.
|
||||
bpy.ops.wm.modal_timer_operator()
|
||||
77
blender-5.2.0/scripts/templates_py/Operator/modal_view3d.py
Normal file
77
blender-5.2.0/scripts/templates_py/Operator/modal_view3d.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
from bpy.props import FloatVectorProperty
|
||||
|
||||
|
||||
class ViewOperator(bpy.types.Operator):
|
||||
"""Translate the view using mouse events"""
|
||||
bl_idname = "view3d.modal_operator"
|
||||
bl_label = "Simple View Operator"
|
||||
|
||||
offset: FloatVectorProperty(
|
||||
name="Offset",
|
||||
size=3,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
v3d = context.space_data
|
||||
rv3d = v3d.region_3d
|
||||
|
||||
rv3d.view_location = self._initial_location + Vector(self.offset)
|
||||
|
||||
def modal(self, context, event):
|
||||
v3d = context.space_data
|
||||
rv3d = v3d.region_3d
|
||||
|
||||
if event.type == 'MOUSEMOVE':
|
||||
self.offset = (self._initial_mouse - Vector((event.mouse_x, event.mouse_y, 0.0))) * 0.02
|
||||
self.execute(context)
|
||||
context.area.header_text_set("Offset {:.4f} {:.4f} {:.4f}".format(*self.offset))
|
||||
|
||||
elif event.type == 'LEFTMOUSE':
|
||||
context.area.header_text_set(None)
|
||||
return {'FINISHED'}
|
||||
|
||||
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||
rv3d.view_location = self._initial_location
|
||||
context.area.header_text_set(None)
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
if context.space_data.type == 'VIEW_3D':
|
||||
v3d = context.space_data
|
||||
rv3d = v3d.region_3d
|
||||
|
||||
if rv3d.view_perspective == 'CAMERA':
|
||||
rv3d.view_perspective = 'PERSP'
|
||||
|
||||
self._initial_mouse = Vector((event.mouse_x, event.mouse_y, 0.0))
|
||||
self._initial_location = rv3d.view_location.copy()
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
else:
|
||||
self.report({'WARNING'}, "Active space must be a View3d")
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(ViewOperator.bl_idname, text="Simple View Modal Operator")
|
||||
|
||||
|
||||
# Register and add to the "view" menu (required to also use F3 search "Simple View Modal Operator" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(ViewOperator)
|
||||
bpy.types.VIEW3D_MT_view.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(ViewOperator)
|
||||
bpy.types.VIEW3D_MT_view.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,115 @@
|
||||
import bpy
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
|
||||
def main(context, event):
|
||||
"""Run this function on left mouse, execute the ray cast"""
|
||||
# get the context arguments
|
||||
scene = context.scene
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
coord = event.mouse_region_x, event.mouse_region_y
|
||||
|
||||
# get the ray from the viewport and mouse
|
||||
view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
|
||||
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
|
||||
|
||||
ray_target = ray_origin + view_vector
|
||||
|
||||
def visible_objects_and_duplis():
|
||||
"""Loop over (object, matrix) pairs (mesh only)"""
|
||||
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
for dup in depsgraph.object_instances:
|
||||
if dup.is_instance: # Real dupli instance
|
||||
obj = dup.instance_object
|
||||
yield (obj, dup.matrix_world.copy())
|
||||
else: # Usual object
|
||||
obj = dup.object
|
||||
yield (obj, obj.matrix_world.copy())
|
||||
|
||||
def obj_ray_cast(obj, matrix):
|
||||
"""Wrapper for ray casting that moves the ray into object space"""
|
||||
|
||||
# get the ray relative to the object
|
||||
matrix_inv = matrix.inverted()
|
||||
ray_origin_obj = matrix_inv @ ray_origin
|
||||
ray_target_obj = matrix_inv @ ray_target
|
||||
ray_direction_obj = ray_target_obj - ray_origin_obj
|
||||
|
||||
# cast the ray
|
||||
success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
|
||||
|
||||
if success:
|
||||
return location, normal, face_index
|
||||
else:
|
||||
return None, None, None
|
||||
|
||||
# cast rays and find the closest object
|
||||
best_length_squared = -1.0
|
||||
best_obj = None
|
||||
|
||||
for obj, matrix in visible_objects_and_duplis():
|
||||
if obj.type == 'MESH':
|
||||
hit, normal, face_index = obj_ray_cast(obj, matrix)
|
||||
if hit is not None:
|
||||
hit_world = matrix @ hit
|
||||
scene.cursor.location = hit_world
|
||||
length_squared = (hit_world - ray_origin).length_squared
|
||||
if best_obj is None or length_squared < best_length_squared:
|
||||
best_length_squared = length_squared
|
||||
best_obj = obj
|
||||
|
||||
# now we have the object under the mouse cursor,
|
||||
# we could do lots of stuff but for the example just select.
|
||||
if best_obj is not None:
|
||||
# for selection etc. we need the original object,
|
||||
# evaluated objects are not in view-layer.
|
||||
best_original = best_obj.original
|
||||
best_original.select_set(True)
|
||||
context.view_layer.objects.active = best_original
|
||||
|
||||
|
||||
class ViewOperatorRayCast(bpy.types.Operator):
|
||||
"""Modal object selection with a ray cast"""
|
||||
bl_idname = "view3d.modal_operator_raycast"
|
||||
bl_label = "Ray-cast View Operator"
|
||||
|
||||
def modal(self, context, event):
|
||||
if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
|
||||
# allow navigation
|
||||
return {'PASS_THROUGH'}
|
||||
elif event.type == 'LEFTMOUSE':
|
||||
main(context, event)
|
||||
return {'RUNNING_MODAL'}
|
||||
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if context.space_data.type == 'VIEW_3D':
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
else:
|
||||
self.report({'WARNING'}, "Active space must be a View3d")
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(ViewOperatorRayCast.bl_idname, text="Raycast View Modal Operator")
|
||||
|
||||
|
||||
# Register and add to the "view" menu (required to also use F3 search "Ray-cast View Modal Operator" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(ViewOperatorRayCast)
|
||||
bpy.types.VIEW3D_MT_view.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(ViewOperatorRayCast)
|
||||
bpy.types.VIEW3D_MT_view.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
66
blender-5.2.0/scripts/templates_py/Operator/node.py
Normal file
66
blender-5.2.0/scripts/templates_py/Operator/node.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import bpy
|
||||
|
||||
|
||||
def main(operator, context):
|
||||
space = context.space_data
|
||||
node_tree = space.node_tree
|
||||
node_active = context.active_node
|
||||
node_selected = context.selected_nodes
|
||||
|
||||
# now we have the context, perform a simple operation
|
||||
if node_active in node_selected:
|
||||
node_selected.remove(node_active)
|
||||
if len(node_selected) != 1:
|
||||
operator.report({'ERROR'}, "2 nodes must be selected")
|
||||
return
|
||||
|
||||
node_other, = node_selected
|
||||
|
||||
# now we have 2 nodes to operate on
|
||||
if not node_active.inputs:
|
||||
operator.report({'ERROR'}, "Active node has no inputs")
|
||||
return
|
||||
|
||||
if not node_other.outputs:
|
||||
operator.report({'ERROR'}, "Selected node has no outputs")
|
||||
return
|
||||
|
||||
socket_in = node_active.inputs[0]
|
||||
socket_out = node_other.outputs[0]
|
||||
|
||||
# add a link between the two nodes
|
||||
node_link = node_tree.links.new(socket_in, socket_out)
|
||||
|
||||
|
||||
class NodeOperator(bpy.types.Operator):
|
||||
"""Tooltip"""
|
||||
bl_idname = "node.simple_operator"
|
||||
bl_label = "Simple Node Operator"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
space = context.space_data
|
||||
return space.type == 'NODE_EDITOR'
|
||||
|
||||
def execute(self, context):
|
||||
main(self, context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(NodeOperator.bl_idname, text=NodeOperator.bl_label)
|
||||
|
||||
|
||||
# Register and add to the "Node" menu (required to also use F3 search "Simple Node Operator" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(NodeOperator)
|
||||
bpy.types.NODE_MT_node.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(NodeOperator)
|
||||
bpy.types.NODE_MT_node.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
42
blender-5.2.0/scripts/templates_py/Operator/simple.py
Normal file
42
blender-5.2.0/scripts/templates_py/Operator/simple.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import bpy
|
||||
|
||||
|
||||
def main(context):
|
||||
for ob in context.scene.objects:
|
||||
print(ob)
|
||||
|
||||
|
||||
class SimpleOperator(bpy.types.Operator):
|
||||
"""Tooltip"""
|
||||
bl_idname = "object.simple_operator"
|
||||
bl_label = "Simple Object Operator"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object is not None
|
||||
|
||||
def execute(self, context):
|
||||
main(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(SimpleOperator.bl_idname, text=SimpleOperator.bl_label)
|
||||
|
||||
|
||||
# Register and add to the "object" menu (required to also use F3 search "Simple Object Operator" for quick access).
|
||||
def register():
|
||||
bpy.utils.register_class(SimpleOperator)
|
||||
bpy.types.VIEW3D_MT_object.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(SimpleOperator)
|
||||
bpy.types.VIEW3D_MT_object.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# Test call.
|
||||
bpy.ops.object.simple_operator()
|
||||
29
blender-5.2.0/scripts/templates_py/UI/asset_shelf.py
Normal file
29
blender-5.2.0/scripts/templates_py/UI/asset_shelf.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class MyAssetShelf(bpy.types.AssetShelf):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_idname = "VIEW3D_AST_my_asset_shelf"
|
||||
|
||||
# Tell the shelf which data-block types to show. It is highly recommended
|
||||
# to use these filters, as they avoid slowdowns when there are many assets
|
||||
# of irrelevant data-block types.
|
||||
# If no filter is set, all data-block types will show.
|
||||
filter_material = True
|
||||
filter_object = True
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mode == 'OBJECT'
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(MyAssetShelf)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(MyAssetShelf)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
44
blender-5.2.0/scripts/templates_py/UI/list.py
Normal file
44
blender-5.2.0/scripts/templates_py/UI/list.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class MESH_UL_mylist(bpy.types.UIList):
|
||||
# Constants (flags)
|
||||
# Be careful not to shadow FILTER_ITEM (i.e. UIList().bitflag_filter_item)!
|
||||
# E.g. VGROUP_EMPTY = 1 << 0
|
||||
|
||||
# Custom properties, saved with .blend file. E.g.
|
||||
# use_filter_empty: bpy.props.BoolProperty(
|
||||
# name="Filter Empty", default=False, options=set(),
|
||||
# description="Whether to filter empty vertex groups",
|
||||
# )
|
||||
|
||||
# Called for each drawn item.
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
|
||||
pass
|
||||
|
||||
# Called once to draw filtering/reordering options.
|
||||
def draw_filter(self, context, layout):
|
||||
# Nothing much to say here, it's usual UI code...
|
||||
pass
|
||||
|
||||
# Called once to filter/reorder items.
|
||||
def filter_items(self, context, data, propname):
|
||||
# This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
|
||||
# * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
|
||||
# matching item as filtered (i.e. to be shown). The upper 16 bits (including self.bitflag_filter_item) are
|
||||
# reserved for internal use, the lower 16 bits are free for custom use. Here we use the first bit to mark
|
||||
# VGROUP_EMPTY.
|
||||
# * The second one is for reordering, it must return a list containing the new indices of the items (which
|
||||
# gives us a mapping org_idx -> new_idx).
|
||||
# Please note that the default `UI_UL_list` defines helper functions for common tasks
|
||||
# (see its doc for more info).
|
||||
# If you do not make filtering and/or ordering, return empty list(s)
|
||||
# (this will be more efficient than returning full lists doing nothing!).
|
||||
|
||||
# Default return values.
|
||||
flt_flags = []
|
||||
flt_neworder = []
|
||||
|
||||
# Do filtering/reordering here...
|
||||
|
||||
return flt_flags, flt_neworder
|
||||
47
blender-5.2.0/scripts/templates_py/UI/list_generic.py
Normal file
47
blender-5.2.0/scripts/templates_py/UI/list_generic.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import bpy
|
||||
from bl_ui.generic_ui_list import draw_ui_list
|
||||
|
||||
|
||||
class MyPropGroup(bpy.types.PropertyGroup):
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
|
||||
class MyPanel(bpy.types.Panel):
|
||||
bl_label = "My Label"
|
||||
bl_idname = "SCENE_PT_list_demo"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "My Category"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
draw_ui_list(
|
||||
layout,
|
||||
context,
|
||||
list_path="scene.my_list",
|
||||
active_index_path="scene.my_list_active_index",
|
||||
unique_id="my_list_id",
|
||||
)
|
||||
|
||||
|
||||
classes = [
|
||||
MyPropGroup,
|
||||
MyPanel,
|
||||
]
|
||||
|
||||
class_register, class_unregister = bpy.utils.register_classes_factory(classes)
|
||||
|
||||
|
||||
def register():
|
||||
class_register()
|
||||
bpy.types.Scene.my_list = bpy.props.CollectionProperty(type=MyPropGroup)
|
||||
bpy.types.Scene.my_list_active_index = bpy.props.IntProperty()
|
||||
|
||||
|
||||
def unregister():
|
||||
class_unregister()
|
||||
del bpy.types.Scene.my_list
|
||||
del bpy.types.Scene.my_list_active_index
|
||||
|
||||
|
||||
register()
|
||||
70
blender-5.2.0/scripts/templates_py/UI/list_simple.py
Normal file
70
blender-5.2.0/scripts/templates_py/UI/list_simple.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class MATERIAL_UL_matslots_example(bpy.types.UIList):
|
||||
# The draw_item function is called for each item of the collection that is visible in the list.
|
||||
# data is the RNA object containing the collection,
|
||||
# item is the current drawn item of the collection,
|
||||
# icon is the "computed" icon for the item (as an integer, because some objects like materials or textures
|
||||
# have custom icons ID, which are not available as enum items).
|
||||
# active_data is the RNA object containing the active property for the collection (i.e. integer pointing to the
|
||||
# active item of the collection).
|
||||
# active_propname is the name of the active property (use 'getattr(active_data, active_propname)').
|
||||
# index is index of the current item in the collection.
|
||||
# flt_flag is the result of the filtering process for this item.
|
||||
# Note: as index and flt_flag are optional arguments, you do not have to use/declare them here if you don't
|
||||
# need them.
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
slot = item
|
||||
ma = slot.material
|
||||
# You should always start your row layout by a label (icon + text), or a non-embossed text field,
|
||||
# this will also make the row easily selectable in the list! The later also enables ctrl-click rename.
|
||||
# We use icon_value of label, as our given icon is an integer value, not an enum ID.
|
||||
# Note "data" names should never be translated!
|
||||
if ma:
|
||||
layout.prop(ma, "name", text="", emboss=False, icon_value=icon)
|
||||
else:
|
||||
layout.label(text="", translate=False, icon_value=icon)
|
||||
|
||||
|
||||
# And now we can use this list everywhere in Blender. Here is a small example panel.
|
||||
class UIListPanelExample(bpy.types.Panel):
|
||||
"""Creates a Panel in the Object properties window"""
|
||||
bl_label = "UIList Panel"
|
||||
bl_idname = "OBJECT_PT_ui_list_example"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
obj = context.object
|
||||
|
||||
# `template_list` now takes two new arguments.
|
||||
# The first one is the identifier of the registered UIList to use (if you want only the default list,
|
||||
# with no custom draw code, use "UI_UL_list").
|
||||
layout.template_list("MATERIAL_UL_matslots_example", "", obj, "material_slots", obj, "active_material_index")
|
||||
|
||||
# The second one can usually be left as an empty string.
|
||||
# It's an additional ID used to distinguish lists in case you
|
||||
# use the same list several times in a given area.
|
||||
layout.template_list(
|
||||
"MATERIAL_UL_matslots_example", "compact", obj, "material_slots",
|
||||
obj, "active_material_index", type='COMPACT',
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(MATERIAL_UL_matslots_example)
|
||||
bpy.utils.register_class(UIListPanelExample)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(MATERIAL_UL_matslots_example)
|
||||
bpy.utils.unregister_class(UIListPanelExample)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
50
blender-5.2.0/scripts/templates_py/UI/menu.py
Normal file
50
blender-5.2.0/scripts/templates_py/UI/menu.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class CustomMenu(bpy.types.Menu):
|
||||
bl_label = "Custom Menu"
|
||||
bl_idname = "OBJECT_MT_custom_menu"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator("wm.open_mainfile")
|
||||
layout.operator("wm.save_as_mainfile").copy = True
|
||||
|
||||
layout.operator("object.shade_smooth")
|
||||
|
||||
layout.label(text="Hello world!", icon='WORLD_DATA')
|
||||
|
||||
# use an operator enum property to populate a sub-menu
|
||||
layout.operator_menu_enum(
|
||||
"object.select_by_type",
|
||||
property="type",
|
||||
text="Select All by Type",
|
||||
)
|
||||
# call another menu
|
||||
layout.operator("wm.call_menu", text="Unwrap").name = "VIEW3D_MT_uv_map"
|
||||
|
||||
|
||||
def draw_item(self, context):
|
||||
layout = self.layout
|
||||
layout.menu(CustomMenu.bl_idname)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(CustomMenu)
|
||||
|
||||
# lets add ourselves to the main header
|
||||
bpy.types.INFO_HT_header.append(draw_item)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(CustomMenu)
|
||||
|
||||
bpy.types.INFO_HT_header.remove(draw_item)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# The menu can also be called from scripts
|
||||
bpy.ops.wm.call_menu(name=CustomMenu.bl_idname)
|
||||
27
blender-5.2.0/scripts/templates_py/UI/menu_simple.py
Normal file
27
blender-5.2.0/scripts/templates_py/UI/menu_simple.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class SimpleCustomMenu(bpy.types.Menu):
|
||||
bl_label = "Simple Custom Menu"
|
||||
bl_idname = "OBJECT_MT_simple_custom_menu"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator("wm.open_mainfile")
|
||||
layout.operator("wm.save_as_mainfile")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(SimpleCustomMenu)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(SimpleCustomMenu)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# The menu can also be called from scripts
|
||||
bpy.ops.wm.call_menu(name=SimpleCustomMenu.bl_idname)
|
||||
73
blender-5.2.0/scripts/templates_py/UI/panel.py
Normal file
73
blender-5.2.0/scripts/templates_py/UI/panel.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class LayoutDemoPanel(bpy.types.Panel):
|
||||
"""Creates a Panel in the scene context of the properties editor"""
|
||||
bl_label = "Layout Demo"
|
||||
bl_idname = "SCENE_PT_layout"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "scene"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
scene = context.scene
|
||||
|
||||
# Create a simple row.
|
||||
layout.label(text=" Simple Row:")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(scene, "frame_start")
|
||||
row.prop(scene, "frame_end")
|
||||
|
||||
# Create an row where the buttons are aligned to each other.
|
||||
layout.label(text=" Aligned Row:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(scene, "frame_start")
|
||||
row.prop(scene, "frame_end")
|
||||
|
||||
# Create two columns, by using a split layout.
|
||||
split = layout.split()
|
||||
|
||||
# First column
|
||||
col = split.column()
|
||||
col.label(text="Column One:")
|
||||
col.prop(scene, "frame_end")
|
||||
col.prop(scene, "frame_start")
|
||||
|
||||
# Second column, aligned
|
||||
col = split.column(align=True)
|
||||
col.label(text="Column Two:")
|
||||
col.prop(scene, "frame_start")
|
||||
col.prop(scene, "frame_end")
|
||||
|
||||
# Big render button
|
||||
layout.label(text="Big Button:")
|
||||
row = layout.row()
|
||||
row.scale_y = 3.0
|
||||
row.operator("render.render")
|
||||
|
||||
# Different sizes in a row
|
||||
layout.label(text="Different button sizes:")
|
||||
row = layout.row(align=True)
|
||||
row.operator("render.render")
|
||||
|
||||
sub = row.row()
|
||||
sub.scale_x = 2.0
|
||||
sub.operator("render.render")
|
||||
|
||||
row.operator("render.render")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(LayoutDemoPanel)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(LayoutDemoPanel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
38
blender-5.2.0/scripts/templates_py/UI/panel_simple.py
Normal file
38
blender-5.2.0/scripts/templates_py/UI/panel_simple.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class HelloWorldPanel(bpy.types.Panel):
|
||||
"""Creates a Panel in the Object properties window"""
|
||||
bl_label = "Hello World Panel"
|
||||
bl_idname = "OBJECT_PT_hello"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
obj = context.object
|
||||
|
||||
row = layout.row()
|
||||
row.label(text="Hello world!", icon='WORLD_DATA')
|
||||
|
||||
row = layout.row()
|
||||
row.label(text="Active object is: " + obj.name)
|
||||
row = layout.row()
|
||||
row.prop(obj, "name")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("mesh.primitive_cube_add")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(HelloWorldPanel)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(HelloWorldPanel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
31
blender-5.2.0/scripts/templates_py/UI/pie_menu.py
Normal file
31
blender-5.2.0/scripts/templates_py/UI/pie_menu.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import bpy
|
||||
from bpy.types import Menu
|
||||
|
||||
# spawn an edit mode selection pie (run while object is in edit mode to get a valid output)
|
||||
|
||||
|
||||
class VIEW3D_MT_PIE_template(Menu):
|
||||
# label is displayed at the center of the pie menu.
|
||||
bl_label = "Select Mode"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
pie = layout.menu_pie()
|
||||
# operator_enum will just spread all available options
|
||||
# for the type enum of the operator on the pie
|
||||
pie.operator_enum("mesh.select_mode", "type")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(VIEW3D_MT_PIE_template)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(VIEW3D_MT_PIE_template)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_template")
|
||||
@@ -0,0 +1,76 @@
|
||||
# This sample script demonstrates how to place a custom icon on a button or
|
||||
# menu entry.
|
||||
#
|
||||
# IMPORTANT NOTE: if you run this sample, there will be no icon in the button
|
||||
# You need to replace the image path with a real existing one.
|
||||
# For distributable scripts, it is recommended to place the icons inside the
|
||||
# addon folder and access it relative to the py script file for portability
|
||||
#
|
||||
#
|
||||
# Other use cases for UI-previews:
|
||||
# - provide a fixed list of previews to select from
|
||||
# - provide a dynamic list of preview (eg. calculated from reading a directory)
|
||||
#
|
||||
# For the above use cases, see the template `ui_previews_dynamic_enum.py`.
|
||||
|
||||
|
||||
import os
|
||||
import bpy
|
||||
|
||||
|
||||
class PreviewsExamplePanel(bpy.types.Panel):
|
||||
"""Creates a Panel in the Object properties window"""
|
||||
bl_label = "Previews Example Panel"
|
||||
bl_idname = "OBJECT_PT_previews"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pcoll = preview_collections["main"]
|
||||
|
||||
row = layout.row()
|
||||
my_icon = pcoll["my_icon"]
|
||||
row.operator("render.render", icon_value=my_icon.icon_id)
|
||||
|
||||
# my_icon.icon_id can be used in any UI function that accepts
|
||||
# icon_value # try also setting text=""
|
||||
# to get an icon only operator button
|
||||
|
||||
|
||||
# We can store multiple preview collections here,
|
||||
# however in this example we only store "main"
|
||||
preview_collections = {}
|
||||
|
||||
|
||||
def register():
|
||||
|
||||
# Note that preview collections returned by bpy.utils.previews
|
||||
# are regular py objects - you can use them to store custom data.
|
||||
import bpy.utils.previews
|
||||
pcoll = bpy.utils.previews.new()
|
||||
|
||||
# path to the folder where the icon is
|
||||
# the path is calculated relative to this py file inside the addon folder
|
||||
my_icons_dir = os.path.join(os.path.dirname(__file__), "icons")
|
||||
|
||||
# load a preview thumbnail of a file and store in the previews collection
|
||||
pcoll.load("my_icon", os.path.join(my_icons_dir, "icon-image.png"), 'IMAGE')
|
||||
|
||||
preview_collections["main"] = pcoll
|
||||
|
||||
bpy.utils.register_class(PreviewsExamplePanel)
|
||||
|
||||
|
||||
def unregister():
|
||||
|
||||
for pcoll in preview_collections.values():
|
||||
bpy.utils.previews.remove(pcoll)
|
||||
preview_collections.clear()
|
||||
|
||||
bpy.utils.unregister_class(PreviewsExamplePanel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
141
blender-5.2.0/scripts/templates_py/UI/previews_dynamic_enum.py
Normal file
141
blender-5.2.0/scripts/templates_py/UI/previews_dynamic_enum.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# This sample script demonstrates a dynamic EnumProperty with custom icons.
|
||||
# The EnumProperty is populated dynamically with thumbnails of the contents of
|
||||
# a chosen directory in 'enum_previews_from_directory_items'.
|
||||
# Then, the same enum is displayed with different interfaces. Note that the
|
||||
# generated icon previews do not have Blender IDs, which means that they can
|
||||
# not be used with UILayout templates that require IDs,
|
||||
# such as template_list and template_ID_preview.
|
||||
#
|
||||
# Other use cases:
|
||||
# - make a fixed list of enum_items instead of calculating them in a function
|
||||
# - generate isolated thumbnails to use as custom icons in buttons
|
||||
# and menu items
|
||||
#
|
||||
# For custom icons, see the template "ui_previews_custom_icon.py".
|
||||
#
|
||||
# For distributable scripts, it is recommended to place the icons inside the
|
||||
# script directory and access it relative to the py script file for portability:
|
||||
#
|
||||
# os.path.join(os.path.dirname(__file__), "images")
|
||||
|
||||
|
||||
import os
|
||||
import bpy
|
||||
|
||||
|
||||
def enum_previews_from_directory_items(self, context):
|
||||
"""EnumProperty callback"""
|
||||
enum_items = []
|
||||
|
||||
if context is None:
|
||||
return enum_items
|
||||
|
||||
wm = context.window_manager
|
||||
directory = wm.my_previews_dir
|
||||
|
||||
# Get the preview collection (defined in register func).
|
||||
pcoll = preview_collections["main"]
|
||||
|
||||
if directory == pcoll.my_previews_dir:
|
||||
return pcoll.my_previews
|
||||
|
||||
print("Scanning directory:", directory)
|
||||
|
||||
if directory and os.path.exists(directory):
|
||||
# Scan the directory for `*.png` files
|
||||
image_paths = []
|
||||
for fn in os.listdir(directory):
|
||||
if fn.lower().endswith(".png"):
|
||||
image_paths.append(fn)
|
||||
|
||||
for i, name in enumerate(image_paths):
|
||||
# generates a thumbnail preview for a file.
|
||||
filepath = os.path.join(directory, name)
|
||||
icon = pcoll.get(name)
|
||||
if not icon:
|
||||
thumb = pcoll.load(name, filepath, 'IMAGE')
|
||||
else:
|
||||
thumb = pcoll[name]
|
||||
enum_items.append((name, name, "", thumb.icon_id, i))
|
||||
|
||||
pcoll.my_previews = enum_items
|
||||
pcoll.my_previews_dir = directory
|
||||
return pcoll.my_previews
|
||||
|
||||
|
||||
class PreviewsExamplePanel(bpy.types.Panel):
|
||||
"""Creates a Panel in the Object properties window"""
|
||||
bl_label = "Previews Example Panel"
|
||||
bl_idname = "OBJECT_PT_previews"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
wm = context.window_manager
|
||||
|
||||
row = layout.row()
|
||||
row.prop(wm, "my_previews_dir")
|
||||
|
||||
row = layout.row()
|
||||
row.template_icon_view(wm, "my_previews")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(wm, "my_previews")
|
||||
|
||||
|
||||
# We can store multiple preview collections here,
|
||||
# however in this example we only store "main"
|
||||
preview_collections = {}
|
||||
|
||||
|
||||
def register():
|
||||
from bpy.types import WindowManager
|
||||
from bpy.props import (
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
|
||||
WindowManager.my_previews_dir = StringProperty(
|
||||
name="Folder Path",
|
||||
subtype='DIR_PATH',
|
||||
default=""
|
||||
)
|
||||
|
||||
WindowManager.my_previews = EnumProperty(
|
||||
items=enum_previews_from_directory_items,
|
||||
)
|
||||
|
||||
# Note that preview collections returned by bpy.utils.previews
|
||||
# are regular Python objects - you can use them to store custom data.
|
||||
#
|
||||
# This is especially useful here, since:
|
||||
# - It avoids us regenerating the whole enum over and over.
|
||||
# - It can store enum_items' strings
|
||||
# (remember you have to keep those strings somewhere in py,
|
||||
# else they get freed and Blender references invalid memory!).
|
||||
import bpy.utils.previews
|
||||
pcoll = bpy.utils.previews.new()
|
||||
pcoll.my_previews_dir = ""
|
||||
pcoll.my_previews = ()
|
||||
|
||||
preview_collections["main"] = pcoll
|
||||
|
||||
bpy.utils.register_class(PreviewsExamplePanel)
|
||||
|
||||
|
||||
def unregister():
|
||||
from bpy.types import WindowManager
|
||||
|
||||
del WindowManager.my_previews
|
||||
|
||||
for pcoll in preview_collections.values():
|
||||
bpy.utils.previews.remove(pcoll)
|
||||
preview_collections.clear()
|
||||
|
||||
bpy.utils.unregister_class(PreviewsExamplePanel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
91
blender-5.2.0/scripts/templates_py/UI/tool_simple.py
Normal file
91
blender-5.2.0/scripts/templates_py/UI/tool_simple.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# This example adds an object mode tool to the toolbar.
|
||||
# This is just the circle-select and lasso tools tool.
|
||||
import bpy
|
||||
from bpy.types import WorkSpaceTool
|
||||
|
||||
|
||||
class MyTool(WorkSpaceTool):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_context_mode = 'OBJECT'
|
||||
|
||||
# The prefix of the idname should be your add-on name.
|
||||
bl_idname = "my_template.my_circle_select"
|
||||
bl_label = "My Circle Select"
|
||||
bl_description = (
|
||||
"This is a tooltip\n"
|
||||
"with multiple lines"
|
||||
)
|
||||
bl_icon = "ops.generic.select_circle"
|
||||
bl_widget = None
|
||||
bl_keymap = (
|
||||
("view3d.select_circle", {"type": 'LEFTMOUSE', "value": 'PRESS'},
|
||||
{"properties": [("wait_for_input", False)]}),
|
||||
("view3d.select_circle", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
|
||||
{"properties": [("mode", 'SUB'), ("wait_for_input", False)]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
props = tool.operator_properties("view3d.select_circle")
|
||||
layout.prop(props, "mode")
|
||||
layout.prop(props, "radius")
|
||||
|
||||
|
||||
class MyOtherTool(WorkSpaceTool):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_context_mode = 'OBJECT'
|
||||
|
||||
bl_idname = "my_template.my_other_select"
|
||||
bl_label = "My Lasso Tool Select"
|
||||
bl_description = (
|
||||
"This is a tooltip\n"
|
||||
"with multiple lines"
|
||||
)
|
||||
bl_icon = "ops.generic.select_lasso"
|
||||
bl_widget = None
|
||||
bl_keymap = (
|
||||
("view3d.select_lasso", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
|
||||
("view3d.select_lasso", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
|
||||
{"properties": [("mode", 'SUB')]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
props = tool.operator_properties("view3d.select_lasso")
|
||||
layout.prop(props, "mode")
|
||||
|
||||
|
||||
class MyWidgetTool(WorkSpaceTool):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_context_mode = 'OBJECT'
|
||||
|
||||
bl_idname = "my_template.my_gizmo_translate"
|
||||
bl_label = "My Gizmo Tool"
|
||||
bl_description = "Short description"
|
||||
bl_icon = "ops.transform.transform"
|
||||
bl_widget = "VIEW3D_GGT_tool_generic_handle_free"
|
||||
bl_widget_properties = [
|
||||
("radius", 75.0),
|
||||
("backdrop_fill_alpha", 0.0),
|
||||
]
|
||||
bl_keymap = (
|
||||
("transform.transform", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
props = tool.operator_properties("transform.transform")
|
||||
layout.prop(props, "mode")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_tool(MyTool, after={"builtin.scale_cage"}, separator=True, group=True)
|
||||
bpy.utils.register_tool(MyOtherTool, after={MyTool.bl_idname})
|
||||
bpy.utils.register_tool(MyWidgetTool, after={MyTool.bl_idname})
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_tool(MyTool)
|
||||
bpy.utils.unregister_tool(MyOtherTool)
|
||||
bpy.utils.unregister_tool(MyWidgetTool)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
84
blender-5.2.0/scripts/templates_py/addon_add_object.py
Normal file
84
blender-5.2.0/scripts/templates_py/addon_add_object.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# To make this add-on installable, create an extension with it:
|
||||
# https://docs.blender.org/manual/en/latest/advanced/extensions/getting_started.html
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatVectorProperty
|
||||
from bpy_extras.object_utils import AddObjectHelper, object_data_add
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
def add_object(self, context):
|
||||
scale_x = self.scale.x
|
||||
scale_y = self.scale.y
|
||||
|
||||
verts = [
|
||||
Vector((-1 * scale_x, 1 * scale_y, 0)),
|
||||
Vector((1 * scale_x, 1 * scale_y, 0)),
|
||||
Vector((1 * scale_x, -1 * scale_y, 0)),
|
||||
Vector((-1 * scale_x, -1 * scale_y, 0)),
|
||||
]
|
||||
|
||||
edges = []
|
||||
faces = [[0, 1, 2, 3]]
|
||||
|
||||
mesh = bpy.data.meshes.new(name="New Object Mesh")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
# useful for development when the mesh may be invalid.
|
||||
# mesh.validate(verbose=True)
|
||||
object_data_add(context, mesh, operator=self)
|
||||
|
||||
|
||||
class OBJECT_OT_add_object(Operator, AddObjectHelper):
|
||||
"""Create a new Mesh Object"""
|
||||
bl_idname = "mesh.add_object"
|
||||
bl_label = "Add Mesh Object"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
scale: FloatVectorProperty(
|
||||
name="scale",
|
||||
default=(1.0, 1.0, 1.0),
|
||||
subtype='TRANSLATION',
|
||||
description="scaling",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
add_object(self, context)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Registration
|
||||
|
||||
def add_object_button(self, context):
|
||||
self.layout.operator(
|
||||
OBJECT_OT_add_object.bl_idname,
|
||||
text="Add Object",
|
||||
icon='PLUGIN',
|
||||
)
|
||||
|
||||
|
||||
# This allows you to right click on a button and link to documentation
|
||||
def add_object_manual_map():
|
||||
url_manual_prefix = "https://docs.blender.org/manual/en/latest/"
|
||||
url_manual_mapping = (
|
||||
("bpy.ops.mesh.add_object", "scene_layout/object/types.html"),
|
||||
)
|
||||
return url_manual_prefix, url_manual_mapping
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_add_object)
|
||||
bpy.utils.register_manual_map(add_object_manual_map)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(add_object_button)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_add_object)
|
||||
bpy.utils.unregister_manual_map(add_object_manual_map)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(add_object_button)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
118
blender-5.2.0/scripts/templates_py/background_job.py
Normal file
118
blender-5.2.0/scripts/templates_py/background_job.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# This script is an example of how you can run blender from the command line
|
||||
# (in background mode with no interface) to automate tasks, in this example it
|
||||
# creates a text object, camera and light, then renders and/or saves it.
|
||||
# This example also shows how you can parse command line options to scripts.
|
||||
#
|
||||
# Example usage for this test.
|
||||
# blender --background --factory-startup --python $HOME/background_job.py -- \
|
||||
# --text="Hello World" \
|
||||
# --render="/tmp/hello" \
|
||||
# --save="/tmp/hello.blend"
|
||||
#
|
||||
# Notice:
|
||||
# `--factory-startup` is used to avoid the user default settings from
|
||||
# interfering with automated scene generation.
|
||||
#
|
||||
# `--` causes blender to ignore all following arguments so python can use them.
|
||||
#
|
||||
# See blender --help for details.
|
||||
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def example_function(text, save_path, render_path):
|
||||
# Clear existing objects.
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
|
||||
scene = bpy.context.scene
|
||||
|
||||
txt_data = bpy.data.curves.new(name="MyText", type='FONT')
|
||||
|
||||
# Text Object
|
||||
txt_ob = bpy.data.objects.new(name="MyText", object_data=txt_data)
|
||||
scene.collection.objects.link(txt_ob) # add the data to the scene as an object
|
||||
txt_data.body = text # the body text to the command line arg given
|
||||
txt_data.align_x = 'CENTER' # center text
|
||||
|
||||
# Camera
|
||||
cam_data = bpy.data.cameras.new("MyCam")
|
||||
cam_ob = bpy.data.objects.new(name="MyCam", object_data=cam_data)
|
||||
scene.collection.objects.link(cam_ob) # instance the camera object in the scene
|
||||
scene.camera = cam_ob # set the active camera
|
||||
cam_ob.location = 0.0, 0.0, 10.0
|
||||
|
||||
# Light
|
||||
light_data = bpy.data.lights.new("MyLight", 'POINT')
|
||||
light_ob = bpy.data.objects.new(name="MyLight", object_data=light_data)
|
||||
scene.collection.objects.link(light_ob)
|
||||
light_ob.location = 2.0, 2.0, 5.0
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
if save_path:
|
||||
bpy.ops.wm.save_as_mainfile(filepath=save_path)
|
||||
|
||||
if render_path:
|
||||
render = scene.render
|
||||
render.use_file_extension = True
|
||||
render.filepath = render_path
|
||||
bpy.ops.render.render(write_still=True)
|
||||
|
||||
|
||||
def main():
|
||||
import sys # to get command line args
|
||||
import argparse # to parse options for us and print a nice help message
|
||||
|
||||
# get the args passed to blender after "--", all of which are ignored by
|
||||
# blender so scripts may receive their own arguments
|
||||
argv = sys.argv
|
||||
|
||||
if "--" not in argv:
|
||||
argv = [] # as if no args are passed
|
||||
else:
|
||||
argv = argv[argv.index("--") + 1:] # get all args after "--"
|
||||
|
||||
# When --help or no args are given, print this help
|
||||
usage_text = (
|
||||
"Run blender in background mode with this script:"
|
||||
" blender --background --python " + __file__ + " -- [options]"
|
||||
)
|
||||
|
||||
parser = argparse.ArgumentParser(description=usage_text)
|
||||
|
||||
# Example utility, add some text and renders or saves it (with options)
|
||||
# Possible types are: string, int, long, choice, float and complex.
|
||||
parser.add_argument(
|
||||
"-t", "--text", dest="text", type=str, required=True,
|
||||
help="This text will be used to render an image",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-s", "--save", dest="save_path", metavar='FILE',
|
||||
help="Save the generated file to the specified path",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r", "--render", dest="render_path", metavar='FILE',
|
||||
help="Render an image to the specified path",
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv) # In this example we won't use the args
|
||||
|
||||
if not argv:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
if not args.text:
|
||||
print("Error: --text=\"some string\" argument not given, aborting.")
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
# Run the example function
|
||||
example_function(args.text, args.save_path, args.render_path)
|
||||
|
||||
print("batch job finished, exiting")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
42
blender-5.2.0/scripts/templates_py/batch_export.py
Normal file
42
blender-5.2.0/scripts/templates_py/batch_export.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# exports each selected object into its own file
|
||||
|
||||
import bpy
|
||||
import os
|
||||
|
||||
# export to blend file location
|
||||
basedir = os.path.dirname(bpy.data.filepath)
|
||||
|
||||
if not basedir:
|
||||
raise Exception("Blend file is not saved")
|
||||
|
||||
view_layer = bpy.context.view_layer
|
||||
|
||||
obj_active = view_layer.objects.active
|
||||
selection = bpy.context.selected_objects
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
for obj in selection:
|
||||
|
||||
obj.select_set(True)
|
||||
|
||||
# some exporters only use the active object
|
||||
view_layer.objects.active = obj
|
||||
|
||||
name = bpy.path.clean_name(obj.name)
|
||||
fn = os.path.join(basedir, name)
|
||||
|
||||
bpy.ops.export_scene.fbx(filepath=fn + ".fbx", use_selection=True)
|
||||
|
||||
# Can be used for multiple formats
|
||||
# bpy.ops.export_scene.x3d(filepath=fn + ".x3d", use_selection=True)
|
||||
|
||||
obj.select_set(False)
|
||||
|
||||
print("written:", fn)
|
||||
|
||||
|
||||
view_layer.objects.active = obj_active
|
||||
|
||||
for obj in selection:
|
||||
obj.select_set(True)
|
||||
22
blender-5.2.0/scripts/templates_py/bmesh_simple.py
Normal file
22
blender-5.2.0/scripts/templates_py/bmesh_simple.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# This example assumes we have a mesh object selected
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
|
||||
# Get the active mesh
|
||||
me = bpy.context.object.data
|
||||
|
||||
|
||||
# Get a BMesh representation
|
||||
bm = bmesh.new() # create an empty BMesh
|
||||
bm.from_mesh(me) # fill it in from a Mesh
|
||||
|
||||
|
||||
# Modify the BMesh, can do anything here...
|
||||
for v in bm.verts:
|
||||
v.co.x += 1.0
|
||||
|
||||
|
||||
# Finish up, write the bmesh back to the mesh
|
||||
bm.to_mesh(me)
|
||||
bm.free() # free and prevent further access
|
||||
23
blender-5.2.0/scripts/templates_py/bmesh_simple_editmode.py
Normal file
23
blender-5.2.0/scripts/templates_py/bmesh_simple_editmode.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# This example assumes we have a mesh object in edit-mode
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
|
||||
# Get the active mesh
|
||||
obj = bpy.context.edit_object
|
||||
me = obj.data
|
||||
|
||||
|
||||
# Get a BMesh representation
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
|
||||
bm.faces.active = None
|
||||
|
||||
# Modify the BMesh, can do anything here...
|
||||
for v in bm.verts:
|
||||
v.co.x += 1.0
|
||||
|
||||
|
||||
# Show the updates in the viewport
|
||||
# and recalculate n-gon tessellation.
|
||||
bmesh.update_edit_mesh(me, loop_triangles=True)
|
||||
37
blender-5.2.0/scripts/templates_py/builtin_keyingset.py
Normal file
37
blender-5.2.0/scripts/templates_py/builtin_keyingset.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import bpy
|
||||
|
||||
|
||||
class BUILTIN_KSI_hello(bpy.types.KeyingSetInfo):
|
||||
bl_label = "Hello World KeyingSet"
|
||||
|
||||
# poll - test for whether Keying Set can be used at all
|
||||
def poll(ksi, context):
|
||||
return context.active_object or context.selected_objects
|
||||
|
||||
# iterator - go over all relevant data, calling generate()
|
||||
def iterator(ksi, context, ks):
|
||||
for ob in context.selected_objects:
|
||||
ksi.generate(context, ks, ob)
|
||||
|
||||
# generator - populate Keying Set with property paths to use
|
||||
def generate(ksi, context, ks, data):
|
||||
id_block = data.id_data
|
||||
|
||||
ks.paths.add(id_block, "location")
|
||||
|
||||
for i in range(5):
|
||||
ks.paths.add(id_block, "layers", i, group_method='NAMED', group_name="5x Hello Layers")
|
||||
|
||||
ks.paths.add(id_block, "show_in_front", group_method='NONE')
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(BUILTIN_KSI_hello)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(BUILTIN_KSI_hello)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
register()
|
||||
172
blender-5.2.0/scripts/templates_py/custom_nodes.py
Normal file
172
blender-5.2.0/scripts/templates_py/custom_nodes.py
Normal file
@@ -0,0 +1,172 @@
|
||||
import bpy
|
||||
from bpy.types import NodeTree, Node, NodeSocket, NodeTreeInterfaceSocket
|
||||
from bl_ui import node_add_menu
|
||||
|
||||
# Implementation of custom nodes from Python
|
||||
|
||||
|
||||
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
|
||||
class MyCustomTree(NodeTree):
|
||||
# Description string
|
||||
'''A custom node tree type that will show up in the editor type list'''
|
||||
# Optional identifier string. If not explicitly defined, the python class name is used.
|
||||
bl_idname = 'CustomTreeType'
|
||||
# Label for nice name display
|
||||
bl_label = "Custom Node Tree"
|
||||
# Icon identifier
|
||||
bl_icon = 'NODETREE'
|
||||
|
||||
|
||||
# Custom socket type
|
||||
class MyCustomSocket(NodeSocket):
|
||||
# Description string
|
||||
"""Custom node socket type"""
|
||||
# Optional identifier string. If not explicitly defined, the python class name is used.
|
||||
bl_idname = 'CustomSocketType'
|
||||
# Label for nice name display
|
||||
bl_label = "Custom Node Socket"
|
||||
|
||||
input_value: bpy.props.FloatProperty(
|
||||
name="Value",
|
||||
description="Value when the socket is not connected",
|
||||
)
|
||||
|
||||
# Optional function for drawing the socket input value
|
||||
def draw(self, context, layout, node, text):
|
||||
if self.is_output or self.is_linked:
|
||||
layout.label(text=text)
|
||||
else:
|
||||
layout.prop(self, "input_value", text=text)
|
||||
|
||||
# Socket color
|
||||
@classmethod
|
||||
def draw_color_simple(cls):
|
||||
return (1.0, 0.4, 0.216, 0.5)
|
||||
|
||||
|
||||
# Customizable interface properties to generate a socket from.
|
||||
class MyCustomInterfaceSocket(NodeTreeInterfaceSocket):
|
||||
# The type of socket that is generated.
|
||||
bl_socket_idname = 'CustomSocketType'
|
||||
|
||||
default_value: bpy.props.FloatProperty(default=1.0, description="Default input value for new sockets",)
|
||||
|
||||
def draw(self, context, layout):
|
||||
# Display properties of the interface.
|
||||
layout.prop(self, "default_value")
|
||||
|
||||
# Set properties of newly created sockets
|
||||
def init_socket(self, node, socket, data_path):
|
||||
socket.input_value = self.default_value
|
||||
|
||||
# Use an existing socket to initialize the group interface
|
||||
def from_socket(self, node, socket):
|
||||
# Current value of the socket becomes the default
|
||||
self.default_value = socket.input_value
|
||||
|
||||
|
||||
# Mix-in class for all custom nodes in this tree type.
|
||||
# Defines a poll function to enable instantiation.
|
||||
class MyCustomTreeNode:
|
||||
@classmethod
|
||||
def poll(cls, ntree):
|
||||
return ntree.bl_idname == 'CustomTreeType'
|
||||
|
||||
|
||||
# Derived from the Node base type.
|
||||
class MyCustomNode(MyCustomTreeNode, Node):
|
||||
# === Basics ===
|
||||
# Description string
|
||||
'''A custom node'''
|
||||
# Optional identifier string. If not explicitly defined, the python class name is used.
|
||||
bl_idname = 'CustomNodeType'
|
||||
# Label for nice name display
|
||||
bl_label = "Custom Node"
|
||||
# Icon identifier
|
||||
bl_icon = 'SOUND'
|
||||
|
||||
# === Custom Properties ===
|
||||
# These work just like custom properties in ID data blocks
|
||||
# Extensive information can be found under
|
||||
# https://docs.blender.org/api/current/bpy.props.html
|
||||
my_string_prop: bpy.props.StringProperty()
|
||||
my_float_prop: bpy.props.FloatProperty(default=3.1415926)
|
||||
|
||||
# === Optional Functions ===
|
||||
# Initialization function, called when a new node is created.
|
||||
# This is the most common place to create the sockets for a node, as shown below.
|
||||
# NOTE: this is not the same as the standard __init__ function in Python, which is
|
||||
# a purely internal Python method and unknown to the node system!
|
||||
def init(self, context):
|
||||
self.inputs.new('CustomSocketType', "Hello")
|
||||
self.inputs.new('NodeSocketFloat', "World")
|
||||
self.inputs.new('NodeSocketVector', "!", use_multi_input=True)
|
||||
|
||||
self.outputs.new('NodeSocketColor', "How")
|
||||
self.outputs.new('NodeSocketColor', "are")
|
||||
self.outputs.new('NodeSocketFloat', "you")
|
||||
|
||||
# Copy function to initialize a copied node from an existing one.
|
||||
def copy(self, node):
|
||||
print("Copying from node ", node)
|
||||
|
||||
# Free function to clean up on removal.
|
||||
def free(self):
|
||||
print("Removing node ", self, ", Goodbye!")
|
||||
|
||||
# Additional buttons displayed on the node.
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.label(text="Node settings")
|
||||
layout.prop(self, "my_float_prop")
|
||||
|
||||
# Detail buttons in the sidebar.
|
||||
# If this function is not defined, the draw_buttons function is used instead
|
||||
def draw_buttons_ext(self, context, layout):
|
||||
layout.prop(self, "my_float_prop")
|
||||
# my_string_prop button will only be visible in the sidebar
|
||||
layout.prop(self, "my_string_prop")
|
||||
|
||||
# Optional: custom label
|
||||
# Explicit user label overrides this, but here we can define a label dynamically
|
||||
def draw_label(self):
|
||||
return "I am a custom node"
|
||||
|
||||
|
||||
# Add custom nodes to the Add & Swap menu.
|
||||
def draw_add_menu(self, context):
|
||||
layout = self.layout
|
||||
if context.space_data.tree_type != MyCustomTree.bl_idname:
|
||||
# Avoid adding nodes to built-in node tree
|
||||
return
|
||||
# Add nodes to the layout. Can use submenus, separators, etc. as in any other menu.
|
||||
self.node_operator(layout, "CustomNodeType")
|
||||
|
||||
|
||||
classes = (
|
||||
MyCustomTree,
|
||||
MyCustomSocket,
|
||||
MyCustomInterfaceSocket,
|
||||
MyCustomNode,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
bpy.types.NODE_MT_add.append(draw_add_menu)
|
||||
bpy.types.NODE_MT_swap.append(draw_add_menu)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.NODE_MT_add.remove(draw_add_menu)
|
||||
bpy.types.NODE_MT_swap.remove(draw_add_menu)
|
||||
|
||||
from bpy.utils import unregister_class
|
||||
for cls in reversed(classes):
|
||||
unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
35
blender-5.2.0/scripts/templates_py/driver_functions.py
Normal file
35
blender-5.2.0/scripts/templates_py/driver_functions.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# This script defines functions to be used directly in driver expressions to
|
||||
# extend the built-in set of python functions.
|
||||
#
|
||||
# This can be executed on manually or set to "Register" to
|
||||
# initialize the functions on file load.
|
||||
|
||||
|
||||
# two sample functions
|
||||
def invert(f):
|
||||
""" Simple function call:
|
||||
|
||||
invert(val)
|
||||
"""
|
||||
return 1.0 - f
|
||||
|
||||
|
||||
uuid_store = {}
|
||||
|
||||
|
||||
def slow_value(value, fac, uuid):
|
||||
""" Delay the value by a factor, use a unique string to allow
|
||||
use in multiple drivers without conflict:
|
||||
|
||||
slow_value(val, 0.5, "my_value")
|
||||
"""
|
||||
value_prev = uuid_store.get(uuid, value)
|
||||
uuid_store[uuid] = value_new = (value_prev * fac) + (value * (1.0 - fac))
|
||||
return value_new
|
||||
|
||||
|
||||
import bpy
|
||||
|
||||
# Add functions defined in this script into the drivers namespace.
|
||||
bpy.app.driver_namespace["invert"] = invert
|
||||
bpy.app.driver_namespace["slow_value"] = slow_value
|
||||
13
blender-5.2.0/scripts/templates_py/external_script_stub.py
Normal file
13
blender-5.2.0/scripts/templates_py/external_script_stub.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# This stub runs a python script relative to the currently open
|
||||
# blend file, useful when editing scripts externally.
|
||||
|
||||
import bpy
|
||||
import os
|
||||
|
||||
# Use your own script name here:
|
||||
filename = "my_script.py"
|
||||
|
||||
filepath = os.path.join(os.path.dirname(bpy.data.filepath), filename)
|
||||
global_namespace = {"__file__": filepath, "__name__": "__main__"}
|
||||
with open(filepath, "rb") as file:
|
||||
exec(compile(file.read(), filepath, 'exec'), global_namespace)
|
||||
35
blender-5.2.0/scripts/templates_py/image_processing.py
Normal file
35
blender-5.2.0/scripts/templates_py/image_processing.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# This sample shows an efficient way of doing image processing
|
||||
# over Blender's images using Python.
|
||||
|
||||
import bpy
|
||||
import numpy as np
|
||||
|
||||
|
||||
input_image_name = "Image"
|
||||
output_image_name = "NewImage"
|
||||
|
||||
# Retrieve input image.
|
||||
input_image = bpy.data.images[input_image_name]
|
||||
w, h = input_image.size
|
||||
|
||||
# Allocate a numpy array to manipulate pixel data.
|
||||
pixel_data = np.zeros((w, h, 4), 'f')
|
||||
|
||||
# Fast copy of pixel data from bpy.data to numpy array.
|
||||
input_image.pixels.foreach_get(pixel_data.ravel())
|
||||
|
||||
# Do whatever image processing you want using numpy here:
|
||||
# Example 1: Invert red green and blue channels.
|
||||
pixel_data[:, :, :3] = 1.0 - pixel_data[:, :, :3]
|
||||
# Example 2: Change gamma on the red channel.
|
||||
pixel_data[:, :, 0] = np.power(pixel_data[:, :, 0], 1.5)
|
||||
|
||||
# Create output image.
|
||||
if output_image_name in bpy.data.images:
|
||||
output_image = bpy.data.images[output_image_name]
|
||||
else:
|
||||
output_image = bpy.data.images.new(output_image_name, width=w, height=h)
|
||||
|
||||
# Copy of pixel data from numpy array back to the output image.
|
||||
output_image.pixels.foreach_set(pixel_data.ravel())
|
||||
output_image.update()
|
||||
Reference in New Issue
Block a user