Add Chromium-only Blender WebEngine parity work
This commit is contained in:
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()
|
||||
Reference in New Issue
Block a user