Re-design of submodules used in blender.git
This commit implements described in the #104573. The goal is to fix the confusion of the submodule hashes change, which are not ideal for any of the supported git-module configuration (they are either always visible causing confusion, or silently staged and committed, also causing confusion). This commit replaces submodules with a checkout of addons and addons_contrib, covered by the .gitignore, and locale and developer tools are moved to the main repository. This also changes the paths: - /release/scripts are moved to the /scripts - /source/tools are moved to the /tools - /release/datafiles/locale is moved to /locale This is done to avoid conflicts when using bisect, and also allow buildbot to automatically "recover" wgen building older or newer branches/patches. Running `make update` will initialize the local checkout to the changed repository configuration. Another aspect of the change is that the make update will support Github style of remote organization (origin remote pointing to thy fork, upstream remote pointing to the upstream blender/blender.git). Pull Request #104755
This commit is contained in:
committed by
Sergey Sharybin
parent
3e721195b0
commit
03806d0b67
93
scripts/templates_py/addon_add_object.py
Normal file
93
scripts/templates_py/addon_add_object.py
Normal file
@@ -0,0 +1,93 @@
|
||||
bl_info = {
|
||||
"name": "New Object",
|
||||
"author": "Your Name Here",
|
||||
"version": (1, 0),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "View3D > Add > Mesh > New Object",
|
||||
"description": "Adds a new Mesh Object",
|
||||
"warning": "",
|
||||
"doc_url": "",
|
||||
"category": "Add Mesh",
|
||||
}
|
||||
|
||||
|
||||
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
scripts/templates_py/background_job.py
Normal file
118
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
scripts/templates_py/batch_export.py
Normal file
42
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
scripts/templates_py/bmesh_simple.py
Normal file
22
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
scripts/templates_py/bmesh_simple_editmode.py
Normal file
23
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
scripts/templates_py/builtin_keyingset.py
Normal file
37
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()
|
||||
187
scripts/templates_py/custom_nodes.py
Normal file
187
scripts/templates_py/custom_nodes.py
Normal file
@@ -0,0 +1,187 @@
|
||||
import bpy
|
||||
from bpy.types import NodeTree, Node, NodeSocket
|
||||
|
||||
# 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"
|
||||
|
||||
# Enum items list
|
||||
my_items = (
|
||||
('DOWN', "Down", "Where your feet are"),
|
||||
('UP', "Up", "Where your head should be"),
|
||||
('LEFT', "Left", "Not right"),
|
||||
('RIGHT', "Right", "Not left"),
|
||||
)
|
||||
|
||||
my_enum_prop: bpy.props.EnumProperty(
|
||||
name="Direction",
|
||||
description="Just an example",
|
||||
items=my_items,
|
||||
default='UP',
|
||||
)
|
||||
|
||||
# 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, "my_enum_prop", text=text)
|
||||
|
||||
# Socket color
|
||||
def draw_color(self, context, node):
|
||||
return (1.0, 0.4, 0.216, 0.5)
|
||||
|
||||
|
||||
# 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
|
||||
# http://wiki.blender.org/index.php/Doc:2.6/Manual/Extensions/Python/Properties
|
||||
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', "!")
|
||||
|
||||
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"
|
||||
|
||||
|
||||
### Node Categories ###
|
||||
# Node categories are a python system for automatically
|
||||
# extending the Add menu, toolbar panels and search operator.
|
||||
# For more examples see scripts/startup/nodeitems_builtins.py
|
||||
|
||||
import nodeitems_utils
|
||||
from nodeitems_utils import NodeCategory, NodeItem
|
||||
|
||||
# our own base class with an appropriate poll function,
|
||||
# so the categories only show up in our own tree type
|
||||
|
||||
|
||||
class MyNodeCategory(NodeCategory):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == 'CustomTreeType'
|
||||
|
||||
|
||||
# all categories in a list
|
||||
node_categories = [
|
||||
# identifier, label, items list
|
||||
MyNodeCategory('SOMENODES', "Some Nodes", items=[
|
||||
# our basic node
|
||||
NodeItem("CustomNodeType"),
|
||||
]),
|
||||
MyNodeCategory('OTHERNODES', "Other Nodes", items=[
|
||||
# the node item can have additional settings,
|
||||
# which are applied to new nodes
|
||||
# NOTE: settings values are stored as string expressions,
|
||||
# for this reason they should be converted to strings using repr()
|
||||
NodeItem("CustomNodeType", label="Node A", settings={
|
||||
"my_string_prop": repr("Lorem ipsum dolor sit amet"),
|
||||
"my_float_prop": repr(1.0),
|
||||
}),
|
||||
NodeItem("CustomNodeType", label="Node B", settings={
|
||||
"my_string_prop": repr("consectetur adipisicing elit"),
|
||||
"my_float_prop": repr(2.0),
|
||||
}),
|
||||
]),
|
||||
]
|
||||
|
||||
classes = (
|
||||
MyCustomTree,
|
||||
MyCustomSocket,
|
||||
MyCustomNode,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
nodeitems_utils.register_node_categories('CUSTOM_NODES', node_categories)
|
||||
|
||||
|
||||
def unregister():
|
||||
nodeitems_utils.unregister_node_categories('CUSTOM_NODES')
|
||||
|
||||
from bpy.utils import unregister_class
|
||||
for cls in reversed(classes):
|
||||
unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
35
scripts/templates_py/driver_functions.py
Normal file
35
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
scripts/templates_py/external_script_stub.py
Normal file
13
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)
|
||||
157
scripts/templates_py/gizmo_custom_geometry.py
Normal file
157
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" % 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
scripts/templates_py/gizmo_operator.py
Normal file
235
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
scripts/templates_py/gizmo_operator_target.py
Normal file
50
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)
|
||||
47
scripts/templates_py/gizmo_simple.py
Normal file
47
scripts/templates_py/gizmo_simple.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 it's rear
|
||||
# to change it's 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)
|
||||
35
scripts/templates_py/image_processing.py
Normal file
35
scripts/templates_py/image_processing.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# This sample shows the 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: Inverse 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()
|
||||
76
scripts/templates_py/operator_file_export.py
Normal file
76
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" % 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 mixin 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
scripts/templates_py/operator_file_import.py
Normal file
79
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 mixin 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
scripts/templates_py/operator_mesh_add.py
Normal file
116
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
scripts/templates_py/operator_mesh_uv.py
Normal file
56
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
scripts/templates_py/operator_modal.py
Normal file
58
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')
|
||||
84
scripts/templates_py/operator_modal_draw.py
Normal file
84
scripts/templates_py/operator_modal_draw.py
Normal file
@@ -0,0 +1,84 @@
|
||||
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, 72)
|
||||
blf.draw(font_id, "Hello Word " + str(len(self.mouse_path)))
|
||||
|
||||
# 50% alpha, 2 pixel width line
|
||||
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
|
||||
gpu.state.blend_set('ALPHA')
|
||||
gpu.state.line_width_set(2.0)
|
||||
batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": self.mouse_path})
|
||||
shader.uniform_float("color", (0.0, 0.0, 0.0, 0.5))
|
||||
batch.draw(shader)
|
||||
|
||||
# restore opengl defaults
|
||||
gpu.state.line_width_set(1.0)
|
||||
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 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()
|
||||
54
scripts/templates_py/operator_modal_timer.py
Normal file
54
scripts/templates_py/operator_modal_timer.py
Normal file
@@ -0,0 +1,54 @@
|
||||
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
|
||||
|
||||
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 = context.preferences.themes[0].view_3d.space.gradients.high_gradient
|
||||
color.s = 1.0
|
||||
color.h += 0.01
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def execute(self, context):
|
||||
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)
|
||||
|
||||
|
||||
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
scripts/templates_py/operator_modal_view3d.py
Normal file
77
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" % tuple(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()
|
||||
115
scripts/templates_py/operator_modal_view3d_raycast.py
Normal file
115
scripts/templates_py/operator_modal_view3d_raycast.py
Normal file
@@ -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 viewlayer
|
||||
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 = "RayCast 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 "Raycast 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
scripts/templates_py/operator_node.py
Normal file
66
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
scripts/templates_py/operator_simple.py
Normal file
42
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()
|
||||
47
scripts/templates_py/ui_list.py
Normal file
47
scripts/templates_py/ui_list.py
Normal file
@@ -0,0 +1,47 @@
|
||||
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):
|
||||
# 'DEFAULT' and 'COMPACT' layout types should usually use the same draw code.
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
pass
|
||||
# 'GRID' layout type should be as compact as possible (typically a single icon!).
|
||||
elif self.layout_type == 'GRID':
|
||||
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), and 31 other bits are free for custom needs. Here we use the
|
||||
# first one 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
|
||||
46
scripts/templates_py/ui_list_generic.py
Normal file
46
scripts/templates_py/ui_list_generic.py
Normal file
@@ -0,0 +1,46 @@
|
||||
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_context_path="scene.my_list",
|
||||
active_index_context_path="scene.my_list_active_index"
|
||||
)
|
||||
|
||||
|
||||
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()
|
||||
74
scripts/templates_py/ui_list_simple.py
Normal file
74
scripts/templates_py/ui_list_simple.py
Normal file
@@ -0,0 +1,74 @@
|
||||
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
|
||||
# draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code.
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
# 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)
|
||||
# 'GRID' layout type should be as compact as possible (typically a single icon!).
|
||||
elif self.layout_type == 'GRID':
|
||||
layout.alignment = 'CENTER'
|
||||
layout.label(text="", 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 args.
|
||||
# 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
scripts/templates_py/ui_menu.py
Normal file
50
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
scripts/templates_py/ui_menu_simple.py
Normal file
27
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
scripts/templates_py/ui_panel.py
Normal file
73
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
scripts/templates_py/ui_panel_simple.py
Normal file
38
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
scripts/templates_py/ui_pie_menu.py
Normal file
31
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")
|
||||
76
scripts/templates_py/ui_previews_custom_icon.py
Normal file
76
scripts/templates_py/ui_previews_custom_icon.py
Normal file
@@ -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
scripts/templates_py/ui_previews_dynamic_enum.py
Normal file
141
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: %s" % 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
scripts/templates_py/ui_tool_simple.py
Normal file
91
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.translate"
|
||||
bl_widget = "VIEW3D_GGT_tool_generic_handle_free"
|
||||
bl_widget_properties = [
|
||||
("radius", 75.0),
|
||||
("backdrop_fill_alpha", 0.0),
|
||||
]
|
||||
bl_keymap = (
|
||||
("transform.translate", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
props = tool.operator_properties("transform.translate")
|
||||
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()
|
||||
Reference in New Issue
Block a user