Add Chromium-only Blender WebEngine parity work
This commit is contained in:
123
blender-5.2.0/doc/python_api/check_stubs.py
Normal file
123
blender-5.2.0/doc/python_api/check_stubs.py
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Type-check Blender Python examples and templates against generated stubs.
|
||||
Each file is checked in a separate MYPY process, running in parallel.
|
||||
|
||||
NOTE(@ideasman42): we are nowhere near close to having Blender scripts type check without any type warnings.
|
||||
This is mainly as a way to check:
|
||||
|
||||
- The stubs are valid can be loaded into MYPY.
|
||||
- The stubs are working as expected,
|
||||
since errors in the stubs *do* point to errors in the RST documentation.
|
||||
|
||||
However, it is not as a way to ensure we have zero typing errors,
|
||||
as there are too many false positives.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import multiprocessing
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Project root derived from this file's location (doc/python_api/).
|
||||
SOURCE_DIR = Path(__file__).resolve().parents[2]
|
||||
|
||||
STUB_DIR = SOURCE_DIR / "doc" / "python_api" / "stubs"
|
||||
|
||||
SKIP = {
|
||||
"doc/python_api/examples/aud.0.py",
|
||||
"doc/python_api/examples/bpy.types.HydraRenderEngine.py",
|
||||
"scripts/templates_py/ui_list_generic.py",
|
||||
}
|
||||
|
||||
|
||||
def check_file(filepath: str) -> tuple[str, str]:
|
||||
"""Run mypy on a single file, return (filepath, error_output)."""
|
||||
env = os.environ.copy()
|
||||
env["MYPYPATH"] = str(STUB_DIR)
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable, "-m", "mypy", filepath,
|
||||
"--no-error-summary",
|
||||
"--explicit-package-bases",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env,
|
||||
)
|
||||
prefix = filepath + ":"
|
||||
lines = [
|
||||
line for line in result.stdout.splitlines()
|
||||
if line.startswith(prefix)
|
||||
# Mix-in classes that narrow `bl_*` Literal attributes cause diamond
|
||||
# inheritance conflicts - a `mypy` limitation, not a stub bug.
|
||||
and "incompatible with definition in base class" not in line
|
||||
]
|
||||
return filepath, "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"-j", "--jobs", type=int, default=0,
|
||||
help=(
|
||||
"Parallel jobs (default 0 uses CPU count; 1 runs synchronously, "
|
||||
"streaming each mypy invocation's output directly to the terminal)."
|
||||
),
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
os.chdir(SOURCE_DIR)
|
||||
|
||||
# `Path.as_posix()` normalizes backslashes for WIN32 so SKIP paths match.
|
||||
all_files = [
|
||||
path.as_posix() for pattern in (
|
||||
"doc/python_api/examples/*.py",
|
||||
"scripts/templates_py/*.py",
|
||||
"tests/python/*.py",
|
||||
"scripts/modules/**/*.py",
|
||||
"scripts/startup/**/*.py",
|
||||
"scripts/addons_core/**/*.py",
|
||||
) for path in Path().glob(pattern)
|
||||
]
|
||||
files = sorted(f for f in all_files if f not in SKIP)
|
||||
|
||||
jobs = args.jobs
|
||||
if jobs <= 0:
|
||||
jobs = multiprocessing.cpu_count()
|
||||
|
||||
errors = 0
|
||||
|
||||
if jobs == 1:
|
||||
# Synchronous: print each file's result as soon as it's ready.
|
||||
for filepath in files:
|
||||
_, output = check_file(filepath)
|
||||
if output:
|
||||
errors += 1
|
||||
print(output)
|
||||
print()
|
||||
else:
|
||||
results: dict[str, str] = {}
|
||||
with multiprocessing.Pool(jobs) as pool:
|
||||
for filepath, output in pool.imap_unordered(check_file, files):
|
||||
results[filepath] = output
|
||||
|
||||
# Print results in file order.
|
||||
for filepath in files:
|
||||
output = results[filepath]
|
||||
if output:
|
||||
errors += 1
|
||||
print(output)
|
||||
print()
|
||||
|
||||
print("Checked {:d} files, {:d} with errors.".format(len(files), errors))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
185
blender-5.2.0/doc/python_api/conf.py
Normal file
185
blender-5.2.0/doc/python_api/conf.py
Normal file
@@ -0,0 +1,185 @@
|
||||
# SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
def has_module(module_name):
|
||||
found = False
|
||||
try:
|
||||
__import__(module_name)
|
||||
found = True
|
||||
except ModuleNotFoundError as ex:
|
||||
if ex.name != module_name:
|
||||
raise ex
|
||||
return found
|
||||
|
||||
|
||||
# These are substituted when this file is copied to the build directory.
|
||||
BLENDER_VERSION_STRING = "${BLENDER_VERSION_STRING}"
|
||||
BLENDER_VERSION_DOTS = "${BLENDER_VERSION_DOTS}"
|
||||
BLENDER_REVISION = "${BLENDER_REVISION}"
|
||||
BLENDER_REVISION_TIMESTAMP = "${BLENDER_REVISION_TIMESTAMP}"
|
||||
BLENDER_VERSION_DATE = time.strftime(
|
||||
"%d/%m/%Y",
|
||||
time.localtime(int(BLENDER_REVISION_TIMESTAMP) if BLENDER_REVISION_TIMESTAMP != "0" else None),
|
||||
)
|
||||
|
||||
if BLENDER_REVISION != "Unknown":
|
||||
# SHA1 GIT hash.
|
||||
BLENDER_VERSION_HASH = BLENDER_REVISION
|
||||
BLENDER_VERSION_HASH_HTML_LINK = (
|
||||
"<a href=https://projects.blender.org/blender/blender/commit/{:s}>{:s}</a>".format(
|
||||
BLENDER_VERSION_HASH, BLENDER_VERSION_HASH,
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Fallback: Should not be used.
|
||||
BLENDER_VERSION_HASH = "Hash Unknown"
|
||||
BLENDER_VERSION_HASH_HTML_LINK = BLENDER_VERSION_HASH
|
||||
|
||||
extensions = []
|
||||
|
||||
# Downloading can be slow and get in the way of development,
|
||||
# support "offline" builds.
|
||||
if not os.environ.get("BLENDER_DOC_OFFLINE", "").strip("0"):
|
||||
extensions.append("sphinx.ext.intersphinx")
|
||||
intersphinx_mapping = {"blender_manual": ("https://docs.blender.org/manual/en/dev/", None)}
|
||||
|
||||
# Provides copy button next to code-blocks (nice to have but not essential).
|
||||
if has_module("sphinx_copybutton"):
|
||||
extensions.append("sphinx_copybutton")
|
||||
|
||||
# Exclude line numbers, prompts, and console text.
|
||||
copybutton_exclude = ".linenos, .gp, .go"
|
||||
|
||||
|
||||
project = "Blender {:s} Python API".format(BLENDER_VERSION_STRING)
|
||||
root_doc = "index"
|
||||
copyright = "Blender Authors"
|
||||
version = BLENDER_VERSION_DOTS
|
||||
release = BLENDER_VERSION_DOTS
|
||||
|
||||
# Set this as the default is a super-set of Python3.
|
||||
highlight_language = "python3"
|
||||
# No need to detect encoding.
|
||||
highlight_options = {"default": {"encoding": "utf-8"}}
|
||||
|
||||
# Quiet file not in table-of-contents warnings.
|
||||
exclude_patterns = [
|
||||
"include__bmesh.rst",
|
||||
]
|
||||
|
||||
html_title = "Blender Python API"
|
||||
|
||||
# The fallback to a built-in theme when `furo` is not found.
|
||||
html_theme = "default"
|
||||
|
||||
if has_module("furo"):
|
||||
html_theme = "furo"
|
||||
html_theme_options = {
|
||||
"light_css_variables": {
|
||||
"color-brand-primary": "#265787",
|
||||
"color-brand-content": "#265787",
|
||||
},
|
||||
}
|
||||
|
||||
html_sidebars = {
|
||||
"**": [
|
||||
"sidebar/brand.html",
|
||||
"sidebar/search.html",
|
||||
"sidebar/scroll-start.html",
|
||||
"sidebar/navigation.html",
|
||||
"sidebar/scroll-end.html",
|
||||
"sidebar/variant-selector.html",
|
||||
]
|
||||
}
|
||||
|
||||
# Not helpful since the source is generated, adds to upload size.
|
||||
html_copy_source = False
|
||||
html_show_sphinx = False
|
||||
html_baseurl = "https://docs.blender.org/api/current/"
|
||||
html_use_opensearch = "https://docs.blender.org/api/current"
|
||||
html_show_search_summary = True
|
||||
html_split_index = True
|
||||
html_static_path = ["static"]
|
||||
templates_path = ["templates"]
|
||||
html_context = {
|
||||
"commit": "{:s} - {:s}".format(BLENDER_VERSION_HASH_HTML_LINK, BLENDER_VERSION_DATE),
|
||||
}
|
||||
html_extra_path = ["static"]
|
||||
html_favicon = "static/favicon.png"
|
||||
html_logo = "static/blender_logo.svg"
|
||||
# Disable default `last_updated` value, since this is the date of doc generation, not the one of the source commit.
|
||||
html_last_updated_fmt = None
|
||||
if html_theme == "furo":
|
||||
html_css_files = ["css/theme_overrides.css", "css/version_switch.css"]
|
||||
html_js_files = ["js/version_switch.js"]
|
||||
|
||||
# Needed for latex, PDF generation.
|
||||
latex_elements = {
|
||||
"papersize": "a4paper",
|
||||
}
|
||||
|
||||
latex_documents = [
|
||||
("contents", "contents.tex", "Blender Index", "Blender Foundation", "manual"),
|
||||
]
|
||||
|
||||
# Workaround for useless links leading to compile errors
|
||||
# See https://github.com/sphinx-doc/sphinx/issues/3866
|
||||
from sphinx.domains.python import PythonDomain
|
||||
|
||||
|
||||
class PatchedPythonDomain(PythonDomain):
|
||||
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
|
||||
if "refspecific" in node:
|
||||
del node["refspecific"]
|
||||
return super(PatchedPythonDomain, self).resolve_xref(
|
||||
env, fromdocname, builder, typ, target, node, contnode)
|
||||
|
||||
|
||||
def register_details_directive(app):
|
||||
"""
|
||||
Register a `.. details:: Title` directive.
|
||||
|
||||
Wraps content in an HTML ``<details>`` widget so verbose-but-uninteresting
|
||||
sections (e.g. dunder methods) are foldable. Implemented via ``nodes.raw``
|
||||
around the parsed content - this avoids needing a custom node class
|
||||
(Sphinx pickles the doctree for parallel/incremental builds and cannot
|
||||
reach classes defined in ``conf.py``, which is exec-loaded). Non-HTML
|
||||
builders ignore ``raw`` nodes and render the content inline.
|
||||
"""
|
||||
from html import escape
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive
|
||||
|
||||
class DetailsDirective(Directive):
|
||||
required_arguments = 0
|
||||
optional_arguments = 1
|
||||
final_argument_whitespace = True
|
||||
has_content = True
|
||||
|
||||
def run(self):
|
||||
summary = self.arguments[0] if self.arguments else "Details"
|
||||
container = nodes.Element()
|
||||
self.state.nested_parse(self.content, self.content_offset, container)
|
||||
children = list(container.children)
|
||||
container.children = []
|
||||
for child in children:
|
||||
child.parent = None
|
||||
open_tag = nodes.raw(
|
||||
"",
|
||||
"<details><summary>{:s}</summary>".format(escape(summary)),
|
||||
format="html",
|
||||
)
|
||||
close_tag = nodes.raw("", "</details>", format="html")
|
||||
return [open_tag, *children, close_tag]
|
||||
|
||||
app.add_directive("details", DetailsDirective)
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_domain(PatchedPythonDomain, override=True)
|
||||
register_details_directive(app)
|
||||
22
blender-5.2.0/doc/python_api/examples/aud.0.py
Normal file
22
blender-5.2.0/doc/python_api/examples/aud.0.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Basic Sound Playback
|
||||
++++++++++++++++++++
|
||||
|
||||
This script shows how to use the classes: :class:`Device`, :class:`Sound` and
|
||||
:class:`Handle`.
|
||||
"""
|
||||
import aud
|
||||
|
||||
device = aud.Device()
|
||||
# Load sound file (it can be a video file with audio).
|
||||
sound = aud.Sound('music.ogg')
|
||||
|
||||
# Play the audio, this return a handle to control play/pause.
|
||||
handle = device.play(sound)
|
||||
# If the audio is not too big and will be used often you can buffer it.
|
||||
sound_buffered = aud.Sound.cache(sound)
|
||||
handle_buffered = device.play(sound_buffered)
|
||||
|
||||
# Stop the sounds (otherwise they play until their ends).
|
||||
handle.stop()
|
||||
handle_buffered.stop()
|
||||
45
blender-5.2.0/doc/python_api/examples/blf.0.py
Normal file
45
blender-5.2.0/doc/python_api/examples/blf.0.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Hello World Text Example
|
||||
++++++++++++++++++++++++
|
||||
|
||||
Example of using the blf module. For this module to work we
|
||||
need to use the GPU module :mod:`gpu` as well.
|
||||
"""
|
||||
# Import stand alone modules.
|
||||
import blf
|
||||
import bpy
|
||||
|
||||
font_info = {
|
||||
"font_id": 0,
|
||||
"handler": None,
|
||||
}
|
||||
|
||||
|
||||
def init():
|
||||
"""init function - runs once"""
|
||||
import os
|
||||
# Create a new font object, use external TTF file.
|
||||
font_path = bpy.path.abspath('//Zeyada.ttf')
|
||||
# Store the font index - to use later.
|
||||
if os.path.exists(font_path):
|
||||
font_info["font_id"] = blf.load(font_path)
|
||||
else:
|
||||
# Default font.
|
||||
font_info["font_id"] = 0
|
||||
|
||||
# Set the font drawing routine to run every frame.
|
||||
font_info["handler"] = bpy.types.SpaceView3D.draw_handler_add(
|
||||
draw_callback_px, (None, None), 'WINDOW', 'POST_PIXEL')
|
||||
|
||||
|
||||
def draw_callback_px(self, context):
|
||||
"""Draw on the viewports"""
|
||||
# BLF drawing routine.
|
||||
font_id = font_info["font_id"]
|
||||
blf.position(font_id, 2, 80, 0)
|
||||
blf.size(font_id, 50.0)
|
||||
blf.draw(font_id, "Hello World")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
init()
|
||||
29
blender-5.2.0/doc/python_api/examples/blf.1.py
Normal file
29
blender-5.2.0/doc/python_api/examples/blf.1.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Drawing Text to an Image
|
||||
++++++++++++++++++++++++
|
||||
|
||||
Example showing how text can be drawn into an image.
|
||||
This can be done by binding an image buffer (:mod:`imbuf`) to the font's ID.
|
||||
"""
|
||||
|
||||
import blf
|
||||
import imbuf
|
||||
|
||||
image_size = 512, 512
|
||||
font_size = 20
|
||||
|
||||
ibuf = imbuf.new(image_size)
|
||||
|
||||
font_id = blf.load("/path/to/font.ttf")
|
||||
|
||||
blf.color(font_id, 1.0, 1.0, 1.0, 1.0)
|
||||
blf.size(font_id, font_size)
|
||||
blf.position(font_id, 0, image_size[1] - font_size, 0)
|
||||
|
||||
blf.enable(font_id, blf.WORD_WRAP)
|
||||
blf.word_wrap(font_id, image_size[0])
|
||||
|
||||
with blf.bind_imbuf(font_id, ibuf, display_name="sRGB"):
|
||||
blf.draw_buffer(font_id, "Lots of wrapped text. " * 50)
|
||||
|
||||
imbuf.write(ibuf, filepath="/path/to/image.png")
|
||||
107
blender-5.2.0/doc/python_api/examples/bmesh.ops.1.py
Normal file
107
blender-5.2.0/doc/python_api/examples/bmesh.ops.1.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# This script uses bmesh operators to make 2 links of a chain.
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import math
|
||||
import mathutils
|
||||
|
||||
# Make a new BMesh
|
||||
bm = bmesh.new()
|
||||
|
||||
# Add a circle XXX, should return all geometry created, not just verts.
|
||||
bmesh.ops.create_circle(
|
||||
bm,
|
||||
cap_ends=False,
|
||||
radius=0.2,
|
||||
segments=8)
|
||||
|
||||
|
||||
# Spin and deal with geometry on side 'a'
|
||||
edges_start_a = bm.edges[:]
|
||||
geom_start_a = bm.verts[:] + edges_start_a
|
||||
ret = bmesh.ops.spin(
|
||||
bm,
|
||||
geom=geom_start_a,
|
||||
angle=math.radians(180.0),
|
||||
steps=8,
|
||||
axis=(1.0, 0.0, 0.0),
|
||||
cent=(0.0, 1.0, 0.0))
|
||||
edges_end_a = [ele for ele in ret["geom_last"]
|
||||
if isinstance(ele, bmesh.types.BMEdge)]
|
||||
del ret
|
||||
|
||||
|
||||
# Extrude and create geometry on side 'b'
|
||||
ret = bmesh.ops.extrude_edge_only(
|
||||
bm,
|
||||
edges=edges_start_a)
|
||||
geom_extrude_mid = ret["geom"]
|
||||
del ret
|
||||
|
||||
|
||||
# Collect the edges to spin XXX, 'extrude_edge_only' could return this.
|
||||
verts_extrude_b = [ele for ele in geom_extrude_mid
|
||||
if isinstance(ele, bmesh.types.BMVert)]
|
||||
edges_extrude_b = [ele for ele in geom_extrude_mid
|
||||
if isinstance(ele, bmesh.types.BMEdge) and ele.is_boundary]
|
||||
bmesh.ops.translate(
|
||||
bm,
|
||||
verts=verts_extrude_b,
|
||||
vec=(0.0, 0.0, 1.0))
|
||||
|
||||
|
||||
# Create the circle on side 'b'
|
||||
ret = bmesh.ops.spin(
|
||||
bm,
|
||||
geom=verts_extrude_b + edges_extrude_b,
|
||||
angle=-math.radians(180.0),
|
||||
steps=8,
|
||||
axis=(1.0, 0.0, 0.0),
|
||||
cent=(0.0, 1.0, 1.0))
|
||||
edges_end_b = [ele for ele in ret["geom_last"]
|
||||
if isinstance(ele, bmesh.types.BMEdge)]
|
||||
del ret
|
||||
|
||||
|
||||
# Bridge the resulting edge loops of both spins 'a & b'
|
||||
bmesh.ops.bridge_loops(
|
||||
bm,
|
||||
edges=edges_end_a + edges_end_b)
|
||||
|
||||
|
||||
# Now we have made a links of the chain, make a copy and rotate it
|
||||
# (so this looks something like a chain)
|
||||
|
||||
ret = bmesh.ops.duplicate(
|
||||
bm,
|
||||
geom=bm.verts[:] + bm.edges[:] + bm.faces[:])
|
||||
geom_dupe = ret["geom"]
|
||||
verts_dupe = [ele for ele in geom_dupe if isinstance(ele, bmesh.types.BMVert)]
|
||||
del ret
|
||||
|
||||
# position the new link
|
||||
bmesh.ops.translate(
|
||||
bm,
|
||||
verts=verts_dupe,
|
||||
vec=(0.0, 0.0, 2.0))
|
||||
bmesh.ops.rotate(
|
||||
bm,
|
||||
verts=verts_dupe,
|
||||
cent=(0.0, 1.0, 0.0),
|
||||
matrix=mathutils.Matrix.Rotation(math.radians(90.0), 3, 'Z'))
|
||||
|
||||
# Done with creating the mesh, simply link it into the scene so we can see it
|
||||
|
||||
# Finish up, write the bmesh into a new mesh
|
||||
me = bpy.data.meshes.new("Mesh")
|
||||
bm.to_mesh(me)
|
||||
bm.free()
|
||||
|
||||
|
||||
# Add the mesh to the scene
|
||||
obj = bpy.data.objects.new("Object", me)
|
||||
bpy.context.collection.objects.link(obj)
|
||||
|
||||
# Select and make active
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
File Loading & Order of Initialization
|
||||
Since drivers may be evaluated immediately after loading a blend-file it is necessary
|
||||
to ensure the driver name-space is initialized beforehand.
|
||||
|
||||
This can be done by registering text data-blocks to execute on startup,
|
||||
which executes the scripts before drivers are evaluated.
|
||||
See *Text -> Register* from Blender's text editor.
|
||||
|
||||
.. hint::
|
||||
|
||||
You may prefer to use external files instead of Blender's text-blocks.
|
||||
This can be done using a text-block which executes an external file.
|
||||
|
||||
This example runs ``driver_namespace.py`` located in the same directory as the text-blocks blend-file:
|
||||
|
||||
.. code-block::
|
||||
|
||||
import os
|
||||
import bpy
|
||||
blend_dir = os.path.normpath(os.path.join(__file__, "..", ".."))
|
||||
bpy.utils.execfile(os.path.join(blend_dir, "driver_namespace.py"))
|
||||
|
||||
Using ``__file__`` ensures the text resolves to the expected path even when library-linked from another file.
|
||||
|
||||
Other methods of populating the drivers name-space can be made to work but tend to be error prone:
|
||||
|
||||
Using The ``--python`` command line argument to populate name-space often fails to achieve the desired goal
|
||||
because the initial evaluation will lookup a function that doesn't exist yet,
|
||||
marking the driver as invalid - preventing further evaluation.
|
||||
|
||||
Populating the driver name-space before the blend-file loads also doesn't work
|
||||
since opening a file clears the name-space.
|
||||
|
||||
It is possible to run a script via the ``--python`` command line argument, before the blend file.
|
||||
This can register a load-post handler (:mod:`bpy.app.handlers.load_post`) that initializes the name-space.
|
||||
While this works for background tasks it has the downside that opening the file from the file selector
|
||||
won't setup the name-space.
|
||||
"""
|
||||
15
blender-5.2.0/doc/python_api/examples/bpy.app.handlers.0.py
Normal file
15
blender-5.2.0/doc/python_api/examples/bpy.app.handlers.0.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Basic Handler Example
|
||||
+++++++++++++++++++++
|
||||
|
||||
This script shows the most simple example of adding a handler.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def my_handler(scene):
|
||||
print("Frame Change", scene.frame_current)
|
||||
|
||||
|
||||
bpy.app.handlers.frame_change_pre.append(my_handler)
|
||||
21
blender-5.2.0/doc/python_api/examples/bpy.app.handlers.1.py
Normal file
21
blender-5.2.0/doc/python_api/examples/bpy.app.handlers.1.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Persistent Handler Example
|
||||
++++++++++++++++++++++++++
|
||||
|
||||
By default handlers are freed when loading new files, in some cases you may
|
||||
want the handler stay running across multiple files (when the handler is
|
||||
part of an add-on for example).
|
||||
|
||||
For this the :data:`bpy.app.handlers.persistent` decorator needs to be used.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
|
||||
@persistent
|
||||
def load_handler(dummy):
|
||||
print("Load Handler:", bpy.data.filepath)
|
||||
|
||||
|
||||
bpy.app.handlers.load_post.append(load_handler)
|
||||
24
blender-5.2.0/doc/python_api/examples/bpy.app.handlers.2.py
Normal file
24
blender-5.2.0/doc/python_api/examples/bpy.app.handlers.2.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
Note on Altering Data
|
||||
+++++++++++++++++++++
|
||||
|
||||
Altering data from handlers should be done carefully. While rendering the
|
||||
``frame_change_pre`` and ``frame_change_post`` handlers are called from one
|
||||
thread and the viewport updates from a different thread. If the handler changes
|
||||
data that is accessed by the viewport, this can cause a crash of Blender. In
|
||||
such cases, lock the interface (Render → Lock Interface or
|
||||
:data:`bpy.types.RenderSettings.use_lock_interface`) before starting a render.
|
||||
|
||||
Below is an example of a mesh that is altered from a handler:
|
||||
"""
|
||||
|
||||
|
||||
def frame_change_pre(scene):
|
||||
# A triangle that shifts in the z direction.
|
||||
zshift = scene.frame_current * 0.1
|
||||
vertices = [(-1, -1, zshift), (1, -1, zshift), (0, 1, zshift)]
|
||||
triangles = [(0, 1, 2)]
|
||||
|
||||
object = bpy.data.objects["The Object"]
|
||||
object.data.clear_geometry()
|
||||
object.data.from_pydata(vertices, [], triangles)
|
||||
12
blender-5.2.0/doc/python_api/examples/bpy.app.timers.1.py
Normal file
12
blender-5.2.0/doc/python_api/examples/bpy.app.timers.1.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Run a Function in x Seconds
|
||||
---------------------------
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
def in_5_seconds():
|
||||
print("Hello World")
|
||||
|
||||
|
||||
bpy.app.timers.register(in_5_seconds, first_interval=5)
|
||||
13
blender-5.2.0/doc/python_api/examples/bpy.app.timers.2.py
Normal file
13
blender-5.2.0/doc/python_api/examples/bpy.app.timers.2.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Run a Function every x Seconds
|
||||
------------------------------
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
def every_2_seconds():
|
||||
print("Hello World")
|
||||
return 2.0
|
||||
|
||||
|
||||
bpy.app.timers.register(every_2_seconds)
|
||||
19
blender-5.2.0/doc/python_api/examples/bpy.app.timers.3.py
Normal file
19
blender-5.2.0/doc/python_api/examples/bpy.app.timers.3.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Run a Function n times every x seconds
|
||||
--------------------------------------
|
||||
"""
|
||||
import bpy
|
||||
|
||||
counter = 0
|
||||
|
||||
|
||||
def run_10_times():
|
||||
global counter
|
||||
counter += 1
|
||||
print(counter)
|
||||
if counter == 10:
|
||||
return None
|
||||
return 0.1
|
||||
|
||||
|
||||
bpy.app.timers.register(run_10_times)
|
||||
14
blender-5.2.0/doc/python_api/examples/bpy.app.timers.4.py
Normal file
14
blender-5.2.0/doc/python_api/examples/bpy.app.timers.4.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Assign parameters to functions
|
||||
------------------------------
|
||||
"""
|
||||
import bpy
|
||||
import functools
|
||||
|
||||
|
||||
def print_message(message):
|
||||
print("Message:", message)
|
||||
|
||||
|
||||
bpy.app.timers.register(functools.partial(print_message, "Hello"), first_interval=2.0)
|
||||
bpy.app.timers.register(functools.partial(print_message, "World"), first_interval=3.0)
|
||||
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Introduction
|
||||
------------
|
||||
|
||||
.. warning::
|
||||
|
||||
Most of this object should only be useful if you actually manipulate i18n stuff from Python.
|
||||
If you are a regular add-on, you should only bother about :const:`contexts` member,
|
||||
and the :func:`register`/:func:`unregister` functions! The :func:`pgettext` family of functions
|
||||
should only be used in rare, specific cases (like e.g. complex "composited" UI strings...).
|
||||
|
||||
To add translations to your Python script, you must define a dictionary formatted like that:
|
||||
``{locale: {msg_key: msg_translation, ...}, ...}`` where:
|
||||
|
||||
- locale is either a lang ISO code (e.g. ``fr``), a lang+country code (e.g. ``pt_BR``),
|
||||
a lang+variant code (e.g. ``sr@latin``), or a full code (e.g. ``uz_UZ@cyrilic``).
|
||||
- msg_key is a tuple (context, org message) - use, as much as possible, the predefined :const:`contexts`.
|
||||
- msg_translation is the translated message in given language!
|
||||
|
||||
Then, call ``bpy.app.translations.register(__name__, your_dict)`` in your ``register()`` function, and
|
||||
``bpy.app.translations.unregister(__name__)`` in your ``unregister()`` one.
|
||||
|
||||
The ``Manage UI translations`` add-on has several functions to help you collect strings to translate, and
|
||||
generate the needed Python code (the translation dictionary), as well as optional intermediary po files
|
||||
if you want some... See
|
||||
`How to Translate Blender <https://developer.blender.org/docs/handbook/translating/translator_guide/>`_ and
|
||||
`Using i18n in Blender Code <https://developer.blender.org/docs/handbook/translating/developer_guide/>`_
|
||||
for more info.
|
||||
|
||||
Module References
|
||||
-----------------
|
||||
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
# This block can be automatically generated by UI translations addon, which also handles conversion with PO format.
|
||||
# See also https://developer.blender.org/docs/handbook/translating/translator_guide/#translating-non-official-add-ons
|
||||
# It can (should) also be put in a different, specific py file.
|
||||
|
||||
# ##### BEGIN AUTOGENERATED I18N SECTION #####
|
||||
# NOTE: You can safely move around this auto-generated block (with the begin/end markers!),
|
||||
# and edit the translations by hand.
|
||||
# Just carefully respect the format of the tuple!
|
||||
|
||||
# Tuple of tuples ((msgctxt, msgid), (sources, gen_comments), (lang, translation, (is_fuzzy, comments)), ...)
|
||||
translations_tuple = (
|
||||
(("*", ""),
|
||||
((), ()),
|
||||
("fr_FR", "Project-Id-Version: Copy Settings 0.1.5 (r0)\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2013-04-18 15:27:45.563524\nPO-Revision-Date: 2013-04-18 15:38+0100\nLast-Translator: Bastien Montagne <montagne29@wanadoo.fr>\nLanguage-Team: LANGUAGE <LL@li.org>\nLanguage: __POT__\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n",
|
||||
(False,
|
||||
("Blender's translation file (po format).",
|
||||
"Copyright (C) 2013 The Blender Foundation.",
|
||||
"This file is distributed under the same license as the Blender package.",
|
||||
"FIRST AUTHOR <EMAIL@ADDRESS>, YEAR."))),
|
||||
),
|
||||
(("Operator", "Render: Copy Settings"),
|
||||
(("bpy.types.SCENE_OT_render_copy_settings",),
|
||||
()),
|
||||
("fr_FR", "Rendu: copier réglages",
|
||||
(False, ())),
|
||||
),
|
||||
(("*", "Copy render settings from current scene to others"),
|
||||
(("bpy.types.SCENE_OT_render_copy_settings",),
|
||||
()),
|
||||
("fr_FR", "Copier les réglages de rendu depuis la scène courante vers d’autres",
|
||||
(False, ())),
|
||||
),
|
||||
# ... etc, all messages from your addon.
|
||||
)
|
||||
|
||||
translations_dict = {}
|
||||
for msg in translations_tuple:
|
||||
key = msg[0]
|
||||
for lang, trans, (is_fuzzy, comments) in msg[2:]:
|
||||
if trans and not is_fuzzy:
|
||||
translations_dict.setdefault(lang, {})[key] = trans
|
||||
|
||||
# ##### END AUTOGENERATED I18N SECTION #####
|
||||
|
||||
# Define remaining addon (operators, UI...) here.
|
||||
|
||||
|
||||
def register():
|
||||
# Usual operator/UI/etc. registration...
|
||||
|
||||
bpy.app.translations.register(__name__, translations_dict)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.app.translations.unregister(__name__)
|
||||
|
||||
# Usual operator/UI/etc. unregistration...
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Get the property associated with a hovered button.
|
||||
Returns a tuple of the data-block, data path to the property, and array index.
|
||||
|
||||
.. note::
|
||||
|
||||
When the property doesn't have an associated :class:`bpy.types.ID` non-ID data may be returned.
|
||||
This may occur when accessing windowing data, for example, operator properties.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
# Example inserting keyframe for the hovered property.
|
||||
active_property = bpy.context.property
|
||||
if active_property:
|
||||
datablock, data_path, index = active_property
|
||||
datablock.keyframe_insert(data_path=data_path, index=index, frame=1)
|
||||
24
blender-5.2.0/doc/python_api/examples/bpy.data.0.py
Normal file
24
blender-5.2.0/doc/python_api/examples/bpy.data.0.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import bpy
|
||||
|
||||
|
||||
# Print all objects.
|
||||
for obj in bpy.data.objects:
|
||||
print(obj.name)
|
||||
|
||||
|
||||
# Print all scene names in a list.
|
||||
print(bpy.data.scenes.keys())
|
||||
|
||||
|
||||
# Remove mesh Cube.
|
||||
if "Cube" in bpy.data.meshes:
|
||||
mesh = bpy.data.meshes["Cube"]
|
||||
print("removing mesh", mesh)
|
||||
bpy.data.meshes.remove(mesh)
|
||||
|
||||
|
||||
# Write images into a file next to the blend.
|
||||
import os
|
||||
with open(os.path.splitext(bpy.data.filepath)[0] + ".txt", 'w') as fs:
|
||||
for image in bpy.data.images:
|
||||
fs.write("{:s} {:d} x {:d}\n".format(image.filepath, image.size[0], image.size[1]))
|
||||
53
blender-5.2.0/doc/python_api/examples/bpy.msgbus.1.py
Normal file
53
blender-5.2.0/doc/python_api/examples/bpy.msgbus.1.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
The message bus system can be used to receive notifications when properties of
|
||||
Blender data-blocks are changed via the data API.
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
The message bus system is triggered by updates via the RNA system. This means
|
||||
that the following updates will result in a notification on the message bus:
|
||||
|
||||
- Changes via the Python API, for example ``some_object.location.x += 3``.
|
||||
- Changes via the sliders, fields, and buttons in the user interface.
|
||||
|
||||
The following updates do **not** trigger message bus notifications:
|
||||
|
||||
- Moving objects in the 3D Viewport.
|
||||
- Changes performed by the animation system.
|
||||
|
||||
Changes done from ``msgbus`` callbacks are not included in related undo steps,
|
||||
so users can easily skip their effects by using Undo followed by Redo.
|
||||
|
||||
Unlike properties ``update`` callbacks, message bus update callbacks are postponed
|
||||
until all operators have finished executing.
|
||||
Additionally, for each property the callback is only triggered once per update cycle,
|
||||
even if the property was changed multiple times during that period.
|
||||
|
||||
Example Use
|
||||
-----------
|
||||
|
||||
Below is an example of subscription to changes in the active object's location.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
# Any Python object can act as the subscription's owner.
|
||||
owner = object()
|
||||
|
||||
subscribe_to = bpy.context.object.location
|
||||
|
||||
|
||||
def msgbus_callback(*args):
|
||||
# This will print:
|
||||
# Something changed! (1, 2, 3)
|
||||
print("Something changed!", args)
|
||||
|
||||
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_to,
|
||||
owner=owner,
|
||||
args=(1, 2, 3),
|
||||
notify=msgbus_callback,
|
||||
)
|
||||
8
blender-5.2.0/doc/python_api/examples/bpy.msgbus.2.py
Normal file
8
blender-5.2.0/doc/python_api/examples/bpy.msgbus.2.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Some properties are converted to Python objects when you retrieve them. This
|
||||
needs to be avoided in order to create the subscription, by using
|
||||
``datablock.path_resolve("property_name", False)``:
|
||||
"""
|
||||
import bpy
|
||||
|
||||
subscribe_to = bpy.context.object.path_resolve("name", False)
|
||||
7
blender-5.2.0/doc/python_api/examples/bpy.msgbus.3.py
Normal file
7
blender-5.2.0/doc/python_api/examples/bpy.msgbus.3.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
It is also possible to create subscriptions on a property of all instances of a
|
||||
certain type:
|
||||
"""
|
||||
import bpy
|
||||
|
||||
subscribe_to = (bpy.types.Object, "location")
|
||||
56
blender-5.2.0/doc/python_api/examples/bpy.ops.0.py
Normal file
56
blender-5.2.0/doc/python_api/examples/bpy.ops.0.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Calling Operators
|
||||
-----------------
|
||||
|
||||
Provides Python access to calling operators, this includes operators written in
|
||||
C++, Python or macros.
|
||||
|
||||
Only keyword arguments can be used to pass operator properties.
|
||||
|
||||
Operators don't have return values as you might expect,
|
||||
instead they return a set() which is made up of:
|
||||
``{'RUNNING_MODAL', 'CANCELLED', 'FINISHED', 'PASS_THROUGH'}``.
|
||||
Common return values are ``{'FINISHED'}`` and ``{'CANCELLED'}``, the latter
|
||||
meaning that the operator execution was aborted without making any changes or
|
||||
saving an undo history entry.
|
||||
|
||||
If operator was cancelled but there wasn't any reports from it with ``{'ERROR'}`` type,
|
||||
it will just return ``{'CANCELLED'}`` without raising any exceptions.
|
||||
However, if there are error reports, a ``RuntimeError`` will be raised
|
||||
after the operator finishes execution, including all error report messages,
|
||||
regardless of the return status (even if it was ``{'FINISHED'}``).
|
||||
|
||||
Calling an operator in the wrong context will raise a ``RuntimeError``,
|
||||
there is a poll() method to avoid this problem.
|
||||
|
||||
Note that the operator ID (bl_idname) in this example is ``mesh.subdivide``,
|
||||
``bpy.ops`` is just the access path for Python.
|
||||
|
||||
|
||||
Keywords and Positional Arguments
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For calling operators keywords are used for operator properties and
|
||||
positional arguments are used to define how the operator is called.
|
||||
|
||||
There are 2 optional positional arguments (documented in detail below).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bpy.ops.test.operator(execution_context, undo)
|
||||
|
||||
- execution_context - ``str`` (enum).
|
||||
- undo - ``bool`` type.
|
||||
|
||||
|
||||
Each of these arguments is optional, but must be given in the order above.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
# Calling an operator.
|
||||
bpy.ops.mesh.subdivide(number_cuts=3, smoothness=0.5)
|
||||
|
||||
|
||||
# Check poll() to avoid exception.
|
||||
if bpy.ops.object.mode_set.poll():
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
31
blender-5.2.0/doc/python_api/examples/bpy.ops.1.py
Normal file
31
blender-5.2.0/doc/python_api/examples/bpy.ops.1.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Overriding Context
|
||||
------------------
|
||||
|
||||
It is possible to override context members that the operator sees, so that they
|
||||
act on specified rather than the selected or active data, or to execute an
|
||||
operator in the different part of the user interface.
|
||||
|
||||
The context overrides are passed in as keyword arguments,
|
||||
with keywords matching the context member names in ``bpy.context``.
|
||||
For example to override ``bpy.context.active_object``,
|
||||
you would pass ``active_object=object`` to :class:`bpy.types.Context.temp_override`.
|
||||
|
||||
.. note::
|
||||
|
||||
You will nearly always want to use a copy of the actual current context as basis
|
||||
(otherwise, you'll have to find and gather all needed data yourself).
|
||||
|
||||
.. note::
|
||||
|
||||
Context members are names which Blender uses for data access,
|
||||
overrides do not extend to overriding methods or any Python specific functionality.
|
||||
"""
|
||||
|
||||
# Remove all objects in scene rather than the selected ones.
|
||||
import bpy
|
||||
from bpy import context
|
||||
context_override = context.copy()
|
||||
context_override["selected_objects"] = list(context.scene.objects)
|
||||
with context.temp_override(**context_override):
|
||||
bpy.ops.object.delete()
|
||||
34
blender-5.2.0/doc/python_api/examples/bpy.ops.2.py
Normal file
34
blender-5.2.0/doc/python_api/examples/bpy.ops.2.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
.. _operator-execution_context:
|
||||
|
||||
Execution Context
|
||||
-----------------
|
||||
|
||||
When calling an operator you may want to pass the execution context.
|
||||
|
||||
This determines the context that is given for the operator to run in, and whether
|
||||
invoke() is called or only execute().
|
||||
|
||||
``EXEC_DEFAULT`` is used by default, running only the ``execute()`` method, but you may
|
||||
want the operator to take user interaction with ``INVOKE_DEFAULT`` which will also
|
||||
call invoke() if existing.
|
||||
|
||||
The execution context is one of:
|
||||
|
||||
- ``INVOKE_DEFAULT``
|
||||
- ``INVOKE_REGION_WIN``
|
||||
- ``INVOKE_REGION_CHANNELS``
|
||||
- ``INVOKE_REGION_PREVIEW``
|
||||
- ``INVOKE_AREA``
|
||||
- ``INVOKE_SCREEN``
|
||||
- ``EXEC_DEFAULT``
|
||||
- ``EXEC_REGION_WIN``
|
||||
- ``EXEC_REGION_CHANNELS``
|
||||
- ``EXEC_REGION_PREVIEW``
|
||||
- ``EXEC_AREA``
|
||||
- ``EXEC_SCREEN``
|
||||
"""
|
||||
|
||||
# Collection add popup.
|
||||
import bpy
|
||||
bpy.ops.object.collection_instance_add('INVOKE_DEFAULT')
|
||||
16
blender-5.2.0/doc/python_api/examples/bpy.ops.3.py
Normal file
16
blender-5.2.0/doc/python_api/examples/bpy.ops.3.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
It is also possible to run an operator in a particular part of the user
|
||||
interface. For this we need to pass the window, area and sometimes a region.
|
||||
"""
|
||||
|
||||
# Maximize 3d view in all windows.
|
||||
import bpy
|
||||
from bpy import context
|
||||
|
||||
for window in context.window_manager.windows:
|
||||
screen = window.screen
|
||||
for area in screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
with context.temp_override(window=window, area=area):
|
||||
bpy.ops.screen.screen_full_area()
|
||||
break
|
||||
27
blender-5.2.0/doc/python_api/examples/bpy.props.0.py
Normal file
27
blender-5.2.0/doc/python_api/examples/bpy.props.0.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
Assigning to Existing Classes
|
||||
+++++++++++++++++++++++++++++
|
||||
|
||||
Custom properties can be added to any subclass of an :class:`ID`,
|
||||
:class:`Bone` and :class:`PoseBone`.
|
||||
|
||||
These properties can be animated, accessed by the user interface and Python
|
||||
like Blender's existing properties.
|
||||
|
||||
.. warning::
|
||||
|
||||
Access to these properties might happen in threaded context, on a per-data-block level.
|
||||
This has to be carefully considered when using accessors or update callbacks.
|
||||
|
||||
Typically, these callbacks should not affect any other data that the one owned by their data-block.
|
||||
When accessing external non-Blender data, thread safety mechanisms should be considered.
|
||||
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
# Assign a custom property to an existing type.
|
||||
bpy.types.Material.custom_float = bpy.props.FloatProperty(name="Test Property")
|
||||
|
||||
# Test the property is there.
|
||||
bpy.data.materials[0].custom_float = 5.0
|
||||
64
blender-5.2.0/doc/python_api/examples/bpy.props.1.py
Normal file
64
blender-5.2.0/doc/python_api/examples/bpy.props.1.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Operator Example
|
||||
++++++++++++++++
|
||||
|
||||
A common use of custom properties is for Python based :class:`Operator`
|
||||
classes. Test this code by running it in the text editor, or by clicking the
|
||||
button in the 3D Viewport's Tools panel. The latter will show the properties
|
||||
in the Redo panel and allow you to change them.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class OBJECT_OT_property_example(bpy.types.Operator):
|
||||
bl_idname = "object.property_example"
|
||||
bl_label = "Property Example"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
my_float: bpy.props.FloatProperty(name="Some Floating Point")
|
||||
my_bool: bpy.props.BoolProperty(name="Toggle Option")
|
||||
my_string: bpy.props.StringProperty(name="String Value")
|
||||
|
||||
def execute(self, context):
|
||||
self.report(
|
||||
{'INFO'}, "F: {:.2f} B: {!s} S: {!r}".format(
|
||||
self.my_float, self.my_bool, self.my_string,
|
||||
)
|
||||
)
|
||||
print('My float:', self.my_float)
|
||||
print('My bool:', self.my_bool)
|
||||
print('My string:', self.my_string)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_PT_property_example(bpy.types.Panel):
|
||||
bl_idname = "object_PT_property_example"
|
||||
bl_label = "Property Example"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Tool"
|
||||
|
||||
def draw(self, context):
|
||||
# You can set the property values that should be used when the user
|
||||
# presses the button in the UI.
|
||||
props = self.layout.operator('object.property_example')
|
||||
props.my_bool = True
|
||||
props.my_string = "Shouldn't that be 47?"
|
||||
|
||||
# You can set properties dynamically:
|
||||
if context.object:
|
||||
props.my_float = context.object.location.x
|
||||
else:
|
||||
props.my_float = 327
|
||||
|
||||
|
||||
bpy.utils.register_class(OBJECT_OT_property_example)
|
||||
bpy.utils.register_class(OBJECT_PT_property_example)
|
||||
|
||||
# Demo call. Be sure to also test in the 3D Viewport.
|
||||
bpy.ops.object.property_example(
|
||||
my_float=47,
|
||||
my_bool=True,
|
||||
my_string="Shouldn't that be 327?",
|
||||
)
|
||||
27
blender-5.2.0/doc/python_api/examples/bpy.props.2.py
Normal file
27
blender-5.2.0/doc/python_api/examples/bpy.props.2.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
PropertyGroup Example
|
||||
+++++++++++++++++++++
|
||||
|
||||
PropertyGroups can be used for collecting custom settings into one value
|
||||
to avoid many individual settings mixed in together.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class MaterialSettings(bpy.types.PropertyGroup):
|
||||
my_int: bpy.props.IntProperty()
|
||||
my_float: bpy.props.FloatProperty()
|
||||
my_string: bpy.props.StringProperty()
|
||||
|
||||
|
||||
bpy.utils.register_class(MaterialSettings)
|
||||
|
||||
bpy.types.Material.my_settings = bpy.props.PointerProperty(type=MaterialSettings)
|
||||
|
||||
# Test the new settings work.
|
||||
material = bpy.data.materials[0]
|
||||
|
||||
material.my_settings.my_int = 5
|
||||
material.my_settings.my_float = 3.0
|
||||
material.my_settings.my_string = "Foo"
|
||||
34
blender-5.2.0/doc/python_api/examples/bpy.props.3.py
Normal file
34
blender-5.2.0/doc/python_api/examples/bpy.props.3.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Collection Example
|
||||
++++++++++++++++++
|
||||
|
||||
Custom properties can be added to any subclass of an :class:`ID`,
|
||||
:class:`Bone` and :class:`PoseBone`.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
# Assign a collection.
|
||||
class SceneSettingItem(bpy.types.PropertyGroup):
|
||||
name: bpy.props.StringProperty(name="Test Property", default="Unknown")
|
||||
value: bpy.props.IntProperty(name="Test Property", default=22)
|
||||
|
||||
|
||||
bpy.utils.register_class(SceneSettingItem)
|
||||
|
||||
bpy.types.Scene.my_settings = bpy.props.CollectionProperty(type=SceneSettingItem)
|
||||
|
||||
# Assume an armature object selected.
|
||||
print("Adding 2 values!")
|
||||
|
||||
my_item = bpy.context.scene.my_settings.add()
|
||||
my_item.name = "Spam"
|
||||
my_item.value = 1000
|
||||
|
||||
my_item = bpy.context.scene.my_settings.add()
|
||||
my_item.name = "Eggs"
|
||||
my_item.value = 30
|
||||
|
||||
for my_item in bpy.context.scene.my_settings:
|
||||
print(my_item.name, my_item.value)
|
||||
34
blender-5.2.0/doc/python_api/examples/bpy.props.4.py
Normal file
34
blender-5.2.0/doc/python_api/examples/bpy.props.4.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Update Example
|
||||
++++++++++++++
|
||||
|
||||
It can be useful to perform an action when a property is changed and can be
|
||||
used to update other properties or synchronize with external data.
|
||||
|
||||
All properties define update functions except for CollectionProperty.
|
||||
|
||||
.. warning::
|
||||
|
||||
Remember that these callbacks may be executed in threaded context.
|
||||
|
||||
.. warning::
|
||||
|
||||
If the property belongs to an Operator, the update callback's first
|
||||
parameter will be an OperatorProperties instance, rather than an instance
|
||||
of the operator itself. This means you can't access other internal functions
|
||||
of the operator, only its other properties.
|
||||
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def update_func(self, context):
|
||||
print("my test function", self)
|
||||
|
||||
|
||||
bpy.types.Scene.testprop = bpy.props.FloatProperty(update=update_func)
|
||||
|
||||
bpy.context.scene.testprop = 11.0
|
||||
|
||||
# >>> my test function <bpy_struct, Scene("Scene")>
|
||||
205
blender-5.2.0/doc/python_api/examples/bpy.props.5.py
Normal file
205
blender-5.2.0/doc/python_api/examples/bpy.props.5.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
Getter/Setter Example
|
||||
+++++++++++++++++++++
|
||||
|
||||
Accessor functions can be used for boolean, int, float, string and enum properties.
|
||||
|
||||
If ``get`` or ``set`` callbacks are defined, the property will not be stored in the ID properties
|
||||
automatically. Instead, the ``get`` and ``set`` functions will be called when the property
|
||||
is respectively read or written from the API, and are responsible to handle the data storage.
|
||||
|
||||
Note that:
|
||||
|
||||
- It is illegal to define a ``set`` callback without a matching ``get`` one.
|
||||
- When a ``get`` callback is defined but no ``set`` one, the property is read-only.
|
||||
|
||||
``get_transform`` and ``set_transform`` can be used when the returned value needs to be modified,
|
||||
but the default internal storage is still used. They can only transform the value before it is
|
||||
set or returned, but do not control how/where that data is stored.
|
||||
|
||||
.. note::
|
||||
|
||||
It is possible to define both ``get``/``set`` and ``get_transform``/``set_transform`` callbacks
|
||||
for the same property. In practice however, this should rarely be needed, as most 'transform'
|
||||
operation can also happen within a ``get``/``set`` callback.
|
||||
|
||||
.. warning::
|
||||
|
||||
Remember that these callbacks may be executed in threaded context.
|
||||
|
||||
.. warning::
|
||||
|
||||
Take care when accessing other properties in these callbacks, as it can easily trigger
|
||||
complex issues, such as infinite loops (if e.g. two properties try to also set the other
|
||||
property's value in their own ``set`` callback), or unexpected side effects due to changes
|
||||
in data, caused e.g. by an ``update`` callback.
|
||||
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
scene = bpy.context.scene
|
||||
|
||||
|
||||
# Simple property reading/writing from 'custom' IDProperties.
|
||||
# This is similar to what the RNA would do internally, albeit using it own separate,
|
||||
# internal 'system' IDProperty storage, since Blender 5.0.
|
||||
def get_float(self):
|
||||
return self.get("testprop", 0.0)
|
||||
|
||||
|
||||
def set_float(self, value):
|
||||
self["testprop"] = value
|
||||
|
||||
|
||||
bpy.types.Scene.test_float = bpy.props.FloatProperty(get=get_float, set=set_float)
|
||||
|
||||
# Testing the property:
|
||||
print("test_float:", scene.test_float)
|
||||
scene.test_float = 7.5
|
||||
print("test_float:", scene.test_float)
|
||||
|
||||
# The above outputs:
|
||||
# test_float: 0.0
|
||||
# test_float: 7.5
|
||||
|
||||
|
||||
# Read-only string property, returns the current date.
|
||||
def get_date(self):
|
||||
import datetime
|
||||
return str(datetime.datetime.now())
|
||||
|
||||
|
||||
bpy.types.Scene.test_date = bpy.props.StringProperty(get=get_date)
|
||||
|
||||
# Testing the property:
|
||||
# scene.test_date = "blah" # This would fail, property is read-only.
|
||||
print("test_date:", scene.test_date)
|
||||
|
||||
# The above outputs something like:
|
||||
# test_date: 2018-03-14 11:36:53.158653
|
||||
|
||||
|
||||
# Boolean array.
|
||||
# - Set function stores a single boolean value, returned as the second component.
|
||||
# - Array getters must return a list or tuple.
|
||||
# - Array size must match the property vector size exactly.
|
||||
def get_array(self):
|
||||
return (True, self.get("somebool", True))
|
||||
|
||||
|
||||
def set_array(self, values):
|
||||
self["somebool"] = values[0] and values[1]
|
||||
|
||||
|
||||
bpy.types.Scene.test_array = bpy.props.BoolVectorProperty(size=2, get=get_array, set=set_array)
|
||||
|
||||
# Testing the property:
|
||||
print("test_array:", tuple(scene.test_array))
|
||||
scene.test_array = (True, False)
|
||||
print("test_array:", tuple(scene.test_array))
|
||||
|
||||
# The above outputs:
|
||||
# test_array: (True, True)
|
||||
# test_array: (True, False)
|
||||
|
||||
|
||||
# Boolean array, using 'transform' accessors.
|
||||
# Note how the same result is achieved as with previous get/set example, but using default RNA storage.
|
||||
# Transform accessors also have access to more information.
|
||||
# Also note how the stored data _is_ a two-items array.
|
||||
# - Set function stores a single boolean value, returned as the second component.
|
||||
# - Array getters must return a list or tuple.
|
||||
# - Array size must match the property vector size exactly.
|
||||
def get_array_transform(self, curr_value, is_set):
|
||||
print("Stored data:", curr_value, "(is set:", is_set, ")")
|
||||
return (True, curr_value[1])
|
||||
|
||||
|
||||
def set_array_transform(self, new_value, curr_value, is_set):
|
||||
print("New data:", new_value, "; Stored data:", curr_value, "(is set:", is_set, ")")
|
||||
return True, new_value[0] and new_value[1]
|
||||
|
||||
|
||||
bpy.types.Scene.test_array_transform = bpy.props.BoolVectorProperty(
|
||||
size=2, get_transform=get_array_transform, set_transform=set_array_transform)
|
||||
|
||||
# Testing the property:
|
||||
print("test_array_transform:", tuple(scene.test_array_transform))
|
||||
scene.test_array_transform = (True, False)
|
||||
print("test_array_transform:", tuple(scene.test_array_transform))
|
||||
|
||||
# The above outputs:
|
||||
# Stored data: (False, False) (is set: False )
|
||||
# test_array_transform: (True, False)
|
||||
# New data: (True, False) ; Stored data: (False, False) (is set: False )
|
||||
# Stored data: (True, False) (is set: True )
|
||||
# test_array_transform: (True, False)
|
||||
|
||||
|
||||
# Enum property.
|
||||
# Note: the getter/setter callback must use integer identifiers!
|
||||
test_items = [
|
||||
("RED", "Red", "", 1),
|
||||
("GREEN", "Green", "", 2),
|
||||
("BLUE", "Blue", "", 3),
|
||||
("YELLOW", "Yellow", "", 4),
|
||||
]
|
||||
|
||||
|
||||
def get_enum(self):
|
||||
import random
|
||||
return random.randint(1, 4)
|
||||
|
||||
|
||||
def set_enum(self, value):
|
||||
print("setting value", value)
|
||||
|
||||
|
||||
bpy.types.Scene.test_enum = bpy.props.EnumProperty(items=test_items, get=get_enum, set=set_enum)
|
||||
|
||||
# Testing the property:
|
||||
print("test_enum:", scene.test_enum)
|
||||
scene.test_enum = 'BLUE'
|
||||
print("test_enum:", scene.test_enum)
|
||||
|
||||
# The above outputs something like:
|
||||
# test_enum: YELLOW
|
||||
# setting value 3
|
||||
# test_enum: GREEN
|
||||
|
||||
|
||||
# String, using 'transform' accessors to validate data before setting/returning it.
|
||||
def get_string_transform(self, curr_value, is_set):
|
||||
import os
|
||||
is_valid_path = os.path.exists(curr_value)
|
||||
print("Stored data:", curr_value, "(is set:", is_set, ", is valid path:", is_valid_path, ")")
|
||||
return curr_value if is_valid_path else ""
|
||||
|
||||
|
||||
def set_string_transform(self, new_value, curr_value, is_set):
|
||||
import os
|
||||
is_valid_path = os.path.exists(new_value)
|
||||
print("New data:", new_value, "(is_valid_path:", is_valid_path, ");",
|
||||
"Stored data:", curr_value, "(is set:", is_set, ")")
|
||||
return new_value if is_valid_path else curr_value
|
||||
|
||||
|
||||
bpy.types.Scene.test_string_transform = bpy.props.StringProperty(
|
||||
subtype='DIR_PATH',
|
||||
default="an/invalid/path",
|
||||
get_transform=get_string_transform,
|
||||
set_transform=set_string_transform,
|
||||
)
|
||||
|
||||
# Testing the property:
|
||||
print("test_string_transform:", scene.test_string_transform)
|
||||
scene.test_string_transform = "try\\to\\find\\me"
|
||||
print("test_string_transform:", scene.test_string_transform)
|
||||
|
||||
# The above outputs something like:
|
||||
# Stored data: an/invalid/path (is set: False , is valid path: False )
|
||||
# test_string_transform:
|
||||
# New data: try\to\find\me (is_valid_path: False ) ; Stored data: an/invalid/path (is set: False )
|
||||
# Stored data: an/invalid/path (is set: True , is valid path: False )
|
||||
# test_string_transform:
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Action Slots organize animation data within an action. Each action has slots with specific animation
|
||||
data. An animated data-block specifies an action and a slot, determining the animation data it uses.
|
||||
See the `Blender Manual <https://docs.blender.org/manual/en/5.1/animation/actions.html#action-slots>`_
|
||||
for how Action Slots are used, or the
|
||||
`technical documentation <https://developer.blender.org/docs/features/animation/>`_
|
||||
for details on the animation system's architecture.
|
||||
|
||||
Create & Access an Action Slot
|
||||
++++++++++++++++++++++++++++++
|
||||
|
||||
To get started with Action Slots, you can easily create them by inserting a keyframe on an object. When you do this,
|
||||
Blender automatically creates an Action & Slot for that data-block.
|
||||
|
||||
"""
|
||||
import bpy
|
||||
|
||||
# Assume Suzanne mesh is present in the scene.
|
||||
suzanne = bpy.data.objects["Suzanne"]
|
||||
|
||||
# Create animation data and an action for Suzanne:
|
||||
# Slot will be automatically created.
|
||||
suzanne.keyframe_insert("location", index=0)
|
||||
|
||||
# Action slots can be accessed like this:
|
||||
action = suzanne.animation_data.action
|
||||
for slot in action.slots:
|
||||
print(f"Slot Identifier {slot.identifier!r} "
|
||||
f"with name {slot.name_display!r} "
|
||||
f"targets ID type {slot.target_id_type!r}")
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Manually Create an Action Slot
|
||||
++++++++++++++++++++++++++++++
|
||||
If required you can also manually create Action Slots on an Action. Note the ``target_id_type``
|
||||
that matches the data-block type. Identifiers start with a prefix based on the ID type,
|
||||
e.g. "OB" for objects, followed by the name. There can be identifiers like ``OBSuzanne``
|
||||
and ``MESuzanne`` and the name (``Suzanne``) can be shared between them. This is intentional,
|
||||
so that the slots and the datablocks can have the same name.
|
||||
|
||||
"""
|
||||
import bpy
|
||||
|
||||
# Actions creation.
|
||||
action = bpy.data.actions.new("SuzanneAction")
|
||||
|
||||
# Creation of slots requires an ID type and a name.
|
||||
slot = action.slots.new(id_type='OBJECT', name="Suzanne")
|
||||
print(f"slot type={slot.target_id_type!r} "
|
||||
f"name={slot.name_display!r} "
|
||||
f"identifier={slot.identifier!r}")
|
||||
|
||||
# Output:
|
||||
# slot type=OBJECT name=Suzanne identifier=OBSuzanne
|
||||
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
Explicitly Assigning Action Slots
|
||||
+++++++++++++++++++++++++++++++++
|
||||
An action slot is compatible with a data-block if the slot's ``target_id_type`` matches the data-block's type.
|
||||
If there are multiple slots on the Action, and you want to just pick the first one that's
|
||||
compatible, use the following code. ``anim_data.action_suitable_slots`` can be used `after` the
|
||||
Action has been assigned; it is a list of action slots of that Action, but only the ones that
|
||||
are actually compatible with the owner of anim_data (in this case, Suzanne).
|
||||
|
||||
"""
|
||||
import bpy
|
||||
|
||||
# Assume Suzanne mesh is present in the scene.
|
||||
suzanne = bpy.data.objects["Suzanne"]
|
||||
|
||||
# Create an action with an object slot.
|
||||
action = bpy.data.actions.new("SuzanneAction")
|
||||
action.slots.new(id_type='OBJECT', name="Suzanne")
|
||||
|
||||
# If there are multiple slots on the Action, pick the first one that's compatible.
|
||||
anim_data = suzanne.animation_data_create()
|
||||
anim_data.action = action
|
||||
assert anim_data.action_suitable_slots, "expecting at least one suitable slot"
|
||||
anim_data.action_slot = anim_data.action_suitable_slots[0]
|
||||
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Finding Action Slot Users
|
||||
+++++++++++++++++++++++++
|
||||
|
||||
To return a list of the data-blocks that are animated by a specific slot of an Action,
|
||||
use the ``users()`` method of the ActionSlot.
|
||||
|
||||
"""
|
||||
import bpy
|
||||
|
||||
# Iterate through all actions in the Blender data.
|
||||
print("Action & slot users:")
|
||||
for action in bpy.data.actions:
|
||||
for slot in action.slots:
|
||||
# Return the data-blocks that are animated by this slot of this action
|
||||
users = slot.users()
|
||||
print(f"{action.name:20} slot={slot.identifier:12s} users: {users}")
|
||||
@@ -0,0 +1,73 @@
|
||||
bl_info = {
|
||||
"name": "Example Add-on Preferences",
|
||||
"author": "Your Name Here",
|
||||
"version": (1, 0),
|
||||
"blender": (2, 65, 0),
|
||||
"location": "SpaceBar Search -> Add-on Preferences Example",
|
||||
"description": "Example Add-on",
|
||||
"warning": "",
|
||||
"doc_url": "",
|
||||
"tracker_url": "",
|
||||
"category": "Object",
|
||||
}
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator, AddonPreferences
|
||||
from bpy.props import StringProperty, IntProperty, BoolProperty
|
||||
|
||||
|
||||
class ExampleAddonPreferences(AddonPreferences):
|
||||
# This must match the add-on name, use `__package__`
|
||||
# when defining this for add-on extensions or a sub-module of a Python package.
|
||||
bl_idname = __name__
|
||||
|
||||
filepath: StringProperty(
|
||||
name="Example File Path",
|
||||
subtype='FILE_PATH',
|
||||
)
|
||||
number: IntProperty(
|
||||
name="Example Number",
|
||||
default=4,
|
||||
)
|
||||
boolean: BoolProperty(
|
||||
name="Example Boolean",
|
||||
default=False,
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="This is a preferences view for our add-on")
|
||||
layout.prop(self, "filepath")
|
||||
layout.prop(self, "number")
|
||||
layout.prop(self, "boolean")
|
||||
|
||||
|
||||
class OBJECT_OT_addon_prefs_example(Operator):
|
||||
"""Display example preferences"""
|
||||
bl_idname = "object.addon_prefs_example"
|
||||
bl_label = "Add-on Preferences Example"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
preferences = context.preferences
|
||||
addon_prefs = preferences.addons[__name__].preferences
|
||||
|
||||
info = "Path: {:s}, Number: {:d}, Boolean {!r}".format(
|
||||
addon_prefs.filepath, addon_prefs.number, addon_prefs.boolean,
|
||||
)
|
||||
self.report({'INFO'}, info)
|
||||
print(info)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Registration
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_addon_prefs_example)
|
||||
bpy.utils.register_class(ExampleAddonPreferences)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_addon_prefs_example)
|
||||
bpy.utils.unregister_class(ExampleAddonPreferences)
|
||||
114
blender-5.2.0/doc/python_api/examples/bpy.types.Attribute.0.py
Normal file
114
blender-5.2.0/doc/python_api/examples/bpy.types.Attribute.0.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
Attributes are used to store data that corresponds to geometry elements.
|
||||
Geometry elements are items in one of the geometry domains like points, curves, or faces.
|
||||
|
||||
An attribute has a ``name``, a ``type``, and is stored on a ``domain``.
|
||||
|
||||
``name``
|
||||
The name of this attribute. Names have to be unique within the same geometry.
|
||||
If the name starts with a ``.``, the attribute is hidden from the UI.
|
||||
``type``
|
||||
The type of data that this attribute stores, e.g. a float, integer, color, etc.
|
||||
See `Attribute Type Items <bpy_types_enum_items/attribute_type_items.html>`__.
|
||||
``domain``
|
||||
The geometry domain that the attribute is stored on.
|
||||
See `Attribute Domain Items <bpy_types_enum_items/attribute_domain_items.html>`__.
|
||||
|
||||
|
||||
Using Attributes
|
||||
++++++++++++++++
|
||||
|
||||
Attributes can be stored on geometries like :class:`Mesh`, :class:`Curves`, :class:`PointCloud`, etc.
|
||||
These geometries have attribute groups (usually called ``attributes``).
|
||||
Using the groups, attributes can then be accessed by their name:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
radii = curves.attributes["radius"]
|
||||
|
||||
Creating and storing custom attributes is done using the ``attributes.new`` function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Add a new attribute named `my_attribute_name` of type `float` on the point domain of the geometry.
|
||||
my_attribute = curves.attributes.new("my_attribute_name", 'FLOAT', 'POINT')
|
||||
|
||||
Removing attributes can be done like so:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
attribute = drawing.attributes["some_attribute"]
|
||||
drawing.attributes.remove(attribute)
|
||||
|
||||
.. note::
|
||||
|
||||
Some attributes are required and cannot be removed, like ``"position"``.
|
||||
|
||||
Attribute values are read by accessing their ``attribute.data`` collection property.
|
||||
However, in cases where multiple values should be read at once,
|
||||
it is better to use the :class:`bpy_prop_collection.foreach_get` function and read the values into a ``numpy`` buffer.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import numpy as np
|
||||
|
||||
# Get the radius attribute.
|
||||
radii = curves.attributes["radius"]
|
||||
# Print the radius of the first point.
|
||||
print(radii.data[0].value)
|
||||
# Output: 0.005
|
||||
|
||||
# Get the total number of points.
|
||||
num_points = attributes.domain_size('POINT')
|
||||
# Create an empty buffer to read all the radii into.
|
||||
radii_data = np.zeros(num_points, dtype=np.float32)
|
||||
# Read all the radii of the curves into `radii_data` at once.
|
||||
radii.data.foreach_get('value', radii_data)
|
||||
# Print all the radii.
|
||||
print(radii_data)
|
||||
# Output: [0.1, 0.2, 0.3, 0.4, ... ]
|
||||
|
||||
.. note::
|
||||
|
||||
Some attribute types use different named properties to access their value.
|
||||
Instead of ``value``, vectors use ``vector``, and colors use ``color``.
|
||||
|
||||
Writing to different attribute types is very similar. You can simply assign to a value directly.
|
||||
Again, when writing to multiple values, it is recommended to use the :class:`bpy_prop_collection.foreach_set` function
|
||||
to write the values from a ``numpy`` buffer.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import numpy as np
|
||||
|
||||
radii = curves.attributes["radius"]
|
||||
# Write a radius with a value of 0.5 to the first point.
|
||||
radii.data[0].value = 0.5
|
||||
print(radii.data[0].value)
|
||||
# Output: 0.5
|
||||
|
||||
num_points = attributes.domain_size('POINT')
|
||||
# Generate random radii with values between 0.001 and 0.05 using numpy.
|
||||
new_radii = np.random.uniform(0.001, 0.05, num_points)
|
||||
# Write the new radii to the radius attribute.
|
||||
radii.data.foreach_set('value', new_radii)
|
||||
|
||||
|
||||
The :class:`bpy_prop_collection.foreach_get` / :class:`bpy_prop_collection.foreach_set` methods require a flat array.
|
||||
This is sometimes not desirable, e.g. when reading/writing positions, which are 3D vectors.
|
||||
In these cases, it's possible to use ``np.ravel`` to pass the data as a flat array:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
num_points = attributes.domain_size('POINT')
|
||||
positions = curves.attributes['position']
|
||||
# Here, we're using a numpy array with shape (num_points, 3) so that each
|
||||
# element is a 3d vector.
|
||||
positions_data = np.zeros((num_points, 3), dtype=np.float32)
|
||||
# The `np.ravel` function will pass the `positions_data` as a flat array
|
||||
# without changing the original shape.
|
||||
positions.data.foreach_get('vector', np.ravel(positions_data))
|
||||
print(positions_data)
|
||||
# Output: [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], ...]
|
||||
|
||||
"""
|
||||
@@ -0,0 +1,34 @@
|
||||
import bpy
|
||||
|
||||
filepath = "//link_library.blend"
|
||||
|
||||
# Load a single scene we know the name of.
|
||||
with bpy.data.libraries.load(filepath) as (data_src, data_dst):
|
||||
data_dst.scenes = ["Scene"]
|
||||
|
||||
|
||||
# Load all meshes.
|
||||
with bpy.data.libraries.load(filepath) as (data_src, data_dst):
|
||||
data_dst.meshes = data_src.meshes
|
||||
|
||||
|
||||
# Link all objects starting with "A".
|
||||
with bpy.data.libraries.load(filepath, link=True) as (data_src, data_dst):
|
||||
data_dst.objects = [name for name in data_src.objects if name.startswith("A")]
|
||||
|
||||
|
||||
# Append everything.
|
||||
with bpy.data.libraries.load(filepath) as (data_src, data_dst):
|
||||
for attr in dir(data_dst):
|
||||
setattr(data_dst, attr, getattr(data_src, attr))
|
||||
|
||||
|
||||
# The loaded objects can be accessed from `data_dst` outside of the context
|
||||
# since loading the data replaces the strings for the data-blocks or None
|
||||
# if the data-block could not be loaded.
|
||||
with bpy.data.libraries.load(filepath) as (data_src, data_dst):
|
||||
data_dst.meshes = data_src.meshes
|
||||
# Now operate directly on the loaded data.
|
||||
for mesh in data_dst.meshes:
|
||||
if mesh is not None:
|
||||
print(mesh.name)
|
||||
@@ -0,0 +1,18 @@
|
||||
import bpy
|
||||
|
||||
filepath = "//new_library.blend"
|
||||
|
||||
# Write selected objects and their data to a blend file.
|
||||
data_blocks = set(bpy.context.selected_objects)
|
||||
bpy.data.libraries.write(filepath, data_blocks)
|
||||
|
||||
|
||||
# Write all meshes starting with a capital letter and
|
||||
# set them with fake-user enabled so they aren't lost on re-saving.
|
||||
data_blocks = {mesh for mesh in bpy.data.meshes if mesh.name[:1].isupper()}
|
||||
bpy.data.libraries.write(filepath, data_blocks, fake_user=True)
|
||||
|
||||
|
||||
# Write all materials, textures and node groups to a library.
|
||||
data_blocks = {*bpy.data.materials, *bpy.data.textures, *bpy.data.node_groups}
|
||||
bpy.data.libraries.write(filepath, data_blocks)
|
||||
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
This method enables conversions between Local and Pose space for bones in
|
||||
the middle of updating the armature without having to update dependencies
|
||||
after each change, by manually carrying updated matrices in a recursive walk.
|
||||
"""
|
||||
|
||||
|
||||
def set_pose_matrices(obj, matrix_map):
|
||||
"Assign pose space matrices of all bones at once, ignoring constraints."
|
||||
|
||||
def rec(pbone, parent_matrix):
|
||||
if pbone.name in matrix_map:
|
||||
matrix = matrix_map[pbone.name]
|
||||
|
||||
# # Instead of:
|
||||
# pbone.matrix = matrix
|
||||
# bpy.context.view_layer.update()
|
||||
|
||||
# Compute and assign local matrix, using the new parent matrix.
|
||||
if pbone.parent:
|
||||
pbone.matrix_basis = pbone.bone.convert_local_to_pose(
|
||||
matrix,
|
||||
pbone.bone.matrix_local,
|
||||
parent_matrix=parent_matrix,
|
||||
parent_matrix_local=pbone.parent.bone.matrix_local,
|
||||
invert=True
|
||||
)
|
||||
else:
|
||||
pbone.matrix_basis = pbone.bone.convert_local_to_pose(
|
||||
matrix,
|
||||
pbone.bone.matrix_local,
|
||||
invert=True
|
||||
)
|
||||
else:
|
||||
# Compute the updated pose matrix from local and new parent matrix.
|
||||
if pbone.parent:
|
||||
matrix = pbone.bone.convert_local_to_pose(
|
||||
pbone.matrix_basis,
|
||||
pbone.bone.matrix_local,
|
||||
parent_matrix=parent_matrix,
|
||||
parent_matrix_local=pbone.parent.bone.matrix_local,
|
||||
)
|
||||
else:
|
||||
matrix = pbone.bone.convert_local_to_pose(
|
||||
pbone.matrix_basis,
|
||||
pbone.bone.matrix_local,
|
||||
)
|
||||
|
||||
# Recursively process children, passing the new matrix through.
|
||||
for child in pbone.children:
|
||||
rec(child, matrix)
|
||||
|
||||
# Scan all bone trees from their roots.
|
||||
for pbone in obj.pose.bones:
|
||||
if not pbone.parent:
|
||||
rec(pbone, None)
|
||||
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Overriding the context can be used to temporarily activate another ``window`` / ``area`` & ``region``,
|
||||
as well as other members such as the ``active_object`` or ``bone``.
|
||||
|
||||
Notes:
|
||||
|
||||
- When overriding window, area and regions: the arguments must be consistent,
|
||||
so any region argument that's passed in must be contained by the current area or the area passed in.
|
||||
The same goes for the area needing to be contained in the current window.
|
||||
|
||||
- Temporary context overrides may be nested, when this is done, members will be added to the existing overrides.
|
||||
|
||||
- Context members are restored outside the scope of the context-manager.
|
||||
The only exception to this is when the data is no longer available.
|
||||
|
||||
In the event windowing data was removed (for example), the state of the context is left as-is.
|
||||
While this isn't likely to happen, explicit window operation such as closing windows or loading a new file
|
||||
remove the windowing data that was set before the temporary context was created.
|
||||
"""
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Overriding the context can be useful to set the context after loading files
|
||||
(which would otherwise be None). For example:
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from bpy import context
|
||||
|
||||
# Reload the current file and select all.
|
||||
bpy.ops.wm.open_mainfile(filepath=bpy.data.filepath)
|
||||
window = context.window_manager.windows[0]
|
||||
with context.temp_override(window=window):
|
||||
bpy.ops.mesh.primitive_uv_sphere_add()
|
||||
# The context override is needed so it's possible to set edit-mode.
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
This example shows how it's possible to add an object to the scene in another window.
|
||||
"""
|
||||
import bpy
|
||||
from bpy import context
|
||||
|
||||
win_active = context.window
|
||||
win_other = None
|
||||
for win_iter in context.window_manager.windows:
|
||||
if win_iter != win_active:
|
||||
win_other = win_iter
|
||||
break
|
||||
|
||||
# Add cube in the other window.
|
||||
with context.temp_override(window=win_other):
|
||||
bpy.ops.mesh.primitive_cube_add()
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
**Logging Context Member Access**
|
||||
|
||||
Context members can be logged by calling ``logging_set(True)`` on the "with" target of a temporary override.
|
||||
This will log the members that are being accessed during the operation and may
|
||||
assist in debugging when it is unclear which members need to be overridden.
|
||||
|
||||
In the event an operator fails to execute because of a missing context member, logging may help
|
||||
identify which member is required.
|
||||
|
||||
This example shows how to log which context members are being accessed.
|
||||
Log statements are printed to your system's console.
|
||||
|
||||
.. important::
|
||||
|
||||
Not all operators rely on Context Members and therefore will not be affected by
|
||||
:class:`bpy.types.Context.temp_override`, use logging to what members if any are accessed.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from bpy import context
|
||||
|
||||
my_objects = [context.scene.camera]
|
||||
|
||||
with context.temp_override(selected_objects=my_objects) as override:
|
||||
override.logging_set(
|
||||
True, # Enable logging.
|
||||
hide_missing=True, # Don't show failed attempts.
|
||||
)
|
||||
bpy.ops.object.delete()
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Dependency graph: Evaluated ID example
|
||||
++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
This example demonstrates access to the evaluated ID (such as object, material, etc.) state from
|
||||
an original ID.
|
||||
This is needed every time one needs to access state with animation, constraints, and modifiers
|
||||
taken into account.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class OBJECT_OT_evaluated_example(bpy.types.Operator):
|
||||
"""Access evaluated object state and do something with it"""
|
||||
bl_label = "DEG Access Evaluated Object"
|
||||
bl_idname = "object.evaluated_example"
|
||||
|
||||
def execute(self, context):
|
||||
# This is an original object. Its data does not have any modifiers applied.
|
||||
obj = context.object
|
||||
if obj is None or obj.type != 'MESH':
|
||||
self.report({'INFO'}, "No active mesh object to get info from")
|
||||
return {'CANCELLED'}
|
||||
# Evaluated object exists within a specific dependency graph.
|
||||
# We will request evaluated object from the dependency graph which corresponds to the
|
||||
# current scene and view layer.
|
||||
#
|
||||
# NOTE: This call ensure the dependency graph is fully evaluated. This might be expensive
|
||||
# if changes were made to the scene, but is needed to ensure no dangling or incorrect
|
||||
# pointers are exposed.
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
# Actually request evaluated object.
|
||||
#
|
||||
# This object has animation and drivers applied on it, together with constraints and
|
||||
# modifiers.
|
||||
#
|
||||
# For mesh objects the object.data will be a mesh with all modifiers applied.
|
||||
# This means that in access to vertices or faces after modifier stack happens via fields of
|
||||
# object_eval.object.
|
||||
#
|
||||
# For other types of objects the object_eval.data does not have modifiers applied on it,
|
||||
# but has animation applied.
|
||||
#
|
||||
# NOTE: All ID types have `evaluated_get()`, including materials, node trees, worlds.
|
||||
object_eval = obj.evaluated_get(depsgraph)
|
||||
mesh_eval = object_eval.data
|
||||
self.report({'INFO'}, f"Number of evaluated vertices: {len(mesh_eval.vertices)}")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_evaluated_example)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_evaluated_example)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Dependency graph: Original object example
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
This example demonstrates access to the original ID.
|
||||
Such access is needed to check whether object is selected, or to compare pointers.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class OBJECT_OT_original_example(bpy.types.Operator):
|
||||
"""Access original object and do something with it"""
|
||||
bl_label = "DEG Access Original Object"
|
||||
bl_idname = "object.original_example"
|
||||
|
||||
def check_object_selected(self, object_eval):
|
||||
# Selection depends on a context and is only valid for original objects. This means we need
|
||||
# to request the original object from the known evaluated one.
|
||||
#
|
||||
# NOTE: All ID types have an `original` field.
|
||||
obj = object_eval.original
|
||||
return obj.select_get()
|
||||
|
||||
def execute(self, context):
|
||||
# NOTE: It seems redundant to iterate over original objects to request evaluated ones
|
||||
# just to get original back. But we want to keep example as short as possible, but in real
|
||||
# world there are cases when evaluated object is coming from a more meaningful source.
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
for obj in context.editable_objects:
|
||||
object_eval = obj.evaluated_get(depsgraph)
|
||||
if self.check_object_selected(object_eval):
|
||||
self.report({'INFO'}, f"Object is selected: {object_eval.name}")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_original_example)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_original_example)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Dependency graph: Iterate over all object instances
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Sometimes it is needed to know all the instances with their matrices (for example, when writing an
|
||||
exporter or a custom render engine).
|
||||
This example shows how to access all objects and instances in the scene.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class OBJECT_OT_object_instances(bpy.types.Operator):
|
||||
"""Access original object and do something with it"""
|
||||
bl_label = "DEG Iterate Object Instances"
|
||||
bl_idname = "object.object_instances"
|
||||
|
||||
def execute(self, context):
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
for object_instance in depsgraph.object_instances:
|
||||
# This is an object which is being instanced.
|
||||
obj = object_instance.object
|
||||
# `is_instance` denotes whether the object is coming from instances (as an opposite of
|
||||
# being an emitting object. )
|
||||
if not object_instance.is_instance:
|
||||
print(f"Object {obj.name} at {object_instance.matrix_world}")
|
||||
else:
|
||||
# Instanced will additionally have fields like uv, random_id and others which are
|
||||
# specific for instances. See Python API for DepsgraphObjectInstance for details,
|
||||
print(f"Instance of {obj.name} at {object_instance.matrix_world}")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_object_instances)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_object_instances)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Dependency graph: Object.to_mesh()
|
||||
+++++++++++++++++++++++++++++++++++
|
||||
|
||||
Function to get a mesh from any object with geometry. It is typically used by exporters, render
|
||||
engines and tools that need to access the evaluated mesh as displayed in the viewport.
|
||||
|
||||
Object.to_mesh() is closely interacting with dependency graph: its behavior depends on whether it
|
||||
is used on original or evaluated object.
|
||||
|
||||
When is used on original object, the result mesh is calculated from the object without taking
|
||||
animation or modifiers into account:
|
||||
|
||||
- For meshes this is similar to duplicating the source mesh.
|
||||
- For curves this disables own modifiers, and modifiers of objects used as bevel and taper.
|
||||
- For meta-balls this produces an empty mesh since polygonization is done as a modifier evaluation.
|
||||
|
||||
When is used on evaluated object all modifiers are taken into account.
|
||||
|
||||
.. note:: The result mesh is owned by the object. It can be freed by calling :meth:`~Object.to_mesh_clear`.
|
||||
.. note::
|
||||
The result mesh must be treated as temporary, and cannot be referenced from objects in the main
|
||||
database. If the mesh intended to be used in a persistent manner use :meth:`~BlendDataMeshes.new_from_object`
|
||||
instead.
|
||||
.. note:: If object does not have geometry (i.e. camera) the functions returns None.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class OBJECT_OT_object_to_mesh(bpy.types.Operator):
|
||||
"""Convert selected object to mesh and show number of vertices"""
|
||||
bl_label = "DEG Object to Mesh"
|
||||
bl_idname = "object.object_to_mesh"
|
||||
|
||||
def execute(self, context):
|
||||
# Access input original object.
|
||||
obj = context.object
|
||||
if obj is None:
|
||||
self.report({'INFO'}, "No active mesh object to convert to mesh")
|
||||
return {'CANCELLED'}
|
||||
# Avoid annoying None checks later on.
|
||||
if obj.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}:
|
||||
self.report({'INFO'}, "Object cannot be converted to mesh")
|
||||
return {'CANCELLED'}
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
# Invoke to_mesh() for original object.
|
||||
mesh_from_orig = obj.to_mesh()
|
||||
self.report({'INFO'}, f"{len(mesh_from_orig.vertices)} in new mesh without modifiers.")
|
||||
# Remove temporary mesh.
|
||||
obj.to_mesh_clear()
|
||||
# Invoke to_mesh() for evaluated object.
|
||||
object_eval = obj.evaluated_get(depsgraph)
|
||||
mesh_from_eval = object_eval.to_mesh()
|
||||
self.report({'INFO'}, f"{len(mesh_from_eval.vertices)} in new mesh with modifiers.")
|
||||
# Remove temporary mesh.
|
||||
object_eval.to_mesh_clear()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_object_to_mesh)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_object_to_mesh)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
Dependency graph: bpy.data.meshes.new_from_object()
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Function to copy a new mesh from any object with geometry. The mesh is added to the main
|
||||
database and can be referenced by objects. Typically used by tools that create new objects
|
||||
or apply modifiers.
|
||||
|
||||
When is used on original object, the result mesh is calculated from the object without taking
|
||||
animation or modifiers into account:
|
||||
|
||||
- For meshes this is similar to duplicating the source mesh.
|
||||
- For curves this disables own modifiers, and modifiers of objects used as bevel and taper.
|
||||
- For meta-balls this produces an empty mesh since polygonization is done as a modifier evaluation.
|
||||
|
||||
When is used on evaluated object all modifiers are taken into account.
|
||||
|
||||
All the references (such as materials) are re-mapped to original. This ensures validity and
|
||||
consistency of the main database.
|
||||
|
||||
.. note:: If object does not have geometry (i.e. camera) the functions returns None.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class OBJECT_OT_mesh_from_object(bpy.types.Operator):
|
||||
"""Convert selected object to mesh and show number of vertices"""
|
||||
bl_label = "DEG Mesh From Object"
|
||||
bl_idname = "object.mesh_from_object"
|
||||
|
||||
def execute(self, context):
|
||||
# Access input original object.
|
||||
obj = context.object
|
||||
if obj is None:
|
||||
self.report({'INFO'}, "No active mesh object to convert to mesh")
|
||||
return {'CANCELLED'}
|
||||
# Avoid annoying None checks later on.
|
||||
if obj.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}:
|
||||
self.report({'INFO'}, "Object cannot be converted to mesh")
|
||||
return {'CANCELLED'}
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
object_eval = obj.evaluated_get(depsgraph)
|
||||
mesh_from_eval = bpy.data.meshes.new_from_object(object_eval)
|
||||
self.report({'INFO'}, f"{len(mesh_from_eval.vertices)} in new mesh, and is ready for use!")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_mesh_from_object)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_mesh_from_object)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Dependency graph: Simple exporter
|
||||
+++++++++++++++++++++++++++++++++
|
||||
|
||||
This example is a combination of all previous ones, and shows how to write a simple exporter
|
||||
script.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class OBJECT_OT_simple_exporter(bpy.types.Operator):
|
||||
"""Simple (fake) exporter of selected objects"""
|
||||
bl_label = "DEG Export Selected"
|
||||
bl_idname = "object.simple_exporter"
|
||||
|
||||
apply_modifiers: bpy.props.BoolProperty(name="Apply Modifiers")
|
||||
|
||||
def execute(self, context):
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
for object_instance in depsgraph.object_instances:
|
||||
if not self.is_object_instance_from_selected(object_instance):
|
||||
# We only export selected objects.
|
||||
continue
|
||||
# NOTE: This will create a mesh for every instance, which is not ideal at all. In
|
||||
# reality destination format will support some sort of instancing mechanism, so the
|
||||
# code here will simply say "instance this object at object_instance.matrix_world".
|
||||
mesh = self.create_mesh_for_object_instance(object_instance)
|
||||
if mesh is None:
|
||||
# Happens for non-geometry objects.
|
||||
continue
|
||||
print(f"Exporting mesh with {len(mesh.vertices)} vertices "
|
||||
f"at {object_instance.matrix_world}")
|
||||
|
||||
self.clear_mesh_for_object_instance(object_instance)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def is_object_instance_from_selected(self, object_instance):
|
||||
# For instanced objects we check selection of their instancer (more accurately: check
|
||||
# selection status of the original object corresponding to the instancer).
|
||||
if object_instance.parent:
|
||||
return object_instance.parent.original.select_get()
|
||||
# For non-instanced objects we check selection state of the original object.
|
||||
return object_instance.object.original.select_get()
|
||||
|
||||
def create_mesh_for_object_instance(self, object_instance):
|
||||
if self.apply_modifiers:
|
||||
return object_instance.object.to_mesh()
|
||||
else:
|
||||
return object_instance.object.original.to_mesh()
|
||||
|
||||
def clear_mesh_for_object_instance(self, object_instance):
|
||||
if self.apply_modifiers:
|
||||
return object_instance.object.to_mesh_clear()
|
||||
else:
|
||||
return object_instance.object.original.to_mesh_clear()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_simple_exporter)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_simple_exporter)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Dependency graph: Object.to_curve()
|
||||
+++++++++++++++++++++++++++++++++++
|
||||
|
||||
Function to get a curve from text and curve objects. It is typically used by exporters, render
|
||||
engines, and tools that need to access the curve representing the object.
|
||||
|
||||
The function takes the evaluated dependency graph as a required parameter and optionally a boolean
|
||||
apply_modifiers which defaults to false. If apply_modifiers is true and the object is a curve object,
|
||||
the spline deform modifiers are applied on the control points. Note that constructive modifiers and
|
||||
modifiers that are not spline-enabled will not be applied. So modifiers like Array will not be applied
|
||||
and deform modifiers that have Apply On Spline disabled will not be applied.
|
||||
|
||||
If the object is a text object. The text will be converted into a 3D curve and returned. Modifiers are
|
||||
never applied on text objects and apply_modifiers will be ignored. If the object is neither a curve nor
|
||||
a text object, an error will be reported.
|
||||
|
||||
.. note:: The resulting curve is owned by the object. It can be freed by calling :meth:`~Object.to_curve_clear`.
|
||||
.. note::
|
||||
The resulting curve must be treated as temporary, and cannot be referenced from objects in the main
|
||||
database.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class OBJECT_OT_object_to_curve(bpy.types.Operator):
|
||||
"""Convert selected object to curve and show number of splines"""
|
||||
bl_label = "DEG Object to Curve"
|
||||
bl_idname = "object.object_to_curve"
|
||||
|
||||
def execute(self, context):
|
||||
# Access input original object.
|
||||
obj = context.object
|
||||
if obj is None:
|
||||
self.report({'INFO'}, "No active object to convert to curve")
|
||||
return {'CANCELLED'}
|
||||
if obj.type not in {'CURVE', 'FONT'}:
|
||||
self.report({'INFO'}, "Object cannot be converted to curve")
|
||||
return {'CANCELLED'}
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
# Invoke to_curve() without applying modifiers.
|
||||
curve_without_modifiers = obj.to_curve(depsgraph)
|
||||
self.report({'INFO'}, f"{len(curve_without_modifiers.splines)} splines in a new curve without modifiers.")
|
||||
# Remove temporary curve.
|
||||
obj.to_curve_clear()
|
||||
# Invoke to_curve() with applying modifiers.
|
||||
curve_with_modifiers = obj.to_curve(depsgraph, apply_modifiers=True)
|
||||
self.report({'INFO'}, f"{len(curve_with_modifiers.splines)} splines in new curve with modifiers.")
|
||||
# Remove temporary curve.
|
||||
obj.to_curve_clear()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_object_to_curve)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_object_to_curve)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
Basic FileHandler for importing a single file
|
||||
---------------------------------------------
|
||||
|
||||
A file handler allows custom drag-and-drop behavior to be associated with a given ``Operator``
|
||||
(:class:`FileHandler.bl_import_operator`) and set of file extensions
|
||||
(:class:`FileHandler.bl_file_extensions`). Control over which area of the UI accepts the
|
||||
drag-in-drop action is specified using the :class:`FileHandler.poll_drop` method.
|
||||
|
||||
Similar to operators that use a file select window, operators participating in drag-and-drop, and
|
||||
only accepting a single file, must define the following property:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
filepath: bpy.props.StringProperty(subtype='FILE_PATH', options={'SKIP_SAVE'})
|
||||
|
||||
This ``filepath`` property will be set to the full path of the file dropped by the user.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class CurveTextImport(bpy.types.Operator):
|
||||
"""
|
||||
Creates a text object from a text file.
|
||||
"""
|
||||
bl_idname = "curve.text_import"
|
||||
bl_label = "Import a text file as text object"
|
||||
|
||||
# This Operator supports processing one `.txt` file at a time. The following file-path
|
||||
# property must be defined.
|
||||
filepath: bpy.props.StringProperty(subtype='FILE_PATH', options={'SKIP_SAVE'})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (context.area and context.area.type == "VIEW_3D")
|
||||
|
||||
def execute(self, context):
|
||||
# Direct calls to this Operator may use unsupported file-paths. Ensure the incoming
|
||||
# file-path is one that is supported.
|
||||
if not self.filepath or not self.filepath.endswith(".txt"):
|
||||
return {'CANCELLED'}
|
||||
|
||||
# Create a Blender Text object from the contents of the provided file.
|
||||
with open(self.filepath) as file:
|
||||
text_curve = bpy.data.curves.new(type="FONT", name="Text")
|
||||
text_curve.body = ''.join(file.readlines())
|
||||
text_object = bpy.data.objects.new(name="Text", object_data=text_curve)
|
||||
bpy.context.scene.collection.objects.link(text_object)
|
||||
return {'FINISHED'}
|
||||
|
||||
# By default the file handler invokes the operator with the file-path property set. If the
|
||||
# operator also supports being invoked with no file-path set, and allows the user to pick from a
|
||||
# file select window instead, the following logic can be used.
|
||||
#
|
||||
# Note: It is important to use `options={'SKIP_SAVE'}` when defining the file-path property to
|
||||
# avoid prior values from being reused on subsequent calls.
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.filepath:
|
||||
return self.execute(context)
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
# Define a file handler that supports the following set of conditions:
|
||||
# - Execute the `curve.text_import` operator
|
||||
# - When `.txt` files are dropped in the 3D Viewport
|
||||
class CURVE_FH_text_import(bpy.types.FileHandler):
|
||||
bl_idname = "CURVE_FH_text_import"
|
||||
bl_label = "File handler for curve text object import"
|
||||
bl_import_operator = "curve.text_import"
|
||||
bl_file_extensions = ".txt"
|
||||
|
||||
@classmethod
|
||||
def poll_drop(cls, context):
|
||||
return (context.area and context.area.type == 'VIEW_3D')
|
||||
|
||||
|
||||
bpy.utils.register_class(CurveTextImport)
|
||||
bpy.utils.register_class(CURVE_FH_text_import)
|
||||
109
blender-5.2.0/doc/python_api/examples/bpy.types.FileHandler.2.py
Normal file
109
blender-5.2.0/doc/python_api/examples/bpy.types.FileHandler.2.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
FileHandler for Importing multiple files and exposing Operator options
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Operators which support being executed with multiple files from drag-and-drop require the
|
||||
following properties be defined:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
directory: StringProperty(subtype='DIR_PATH', options={'SKIP_SAVE', 'HIDDEN'})
|
||||
files: CollectionProperty(type=OperatorFileListElement, options={'SKIP_SAVE', 'HIDDEN'})
|
||||
|
||||
These ``directory`` and ``files`` properties will be set with the necessary data from the
|
||||
drag-and-drop operation.
|
||||
|
||||
Additionally, if the operator provides operator properties that need to be accessible to the user,
|
||||
the :class:`ImportHelper.invoke_popup` method can be used to show a dialog leveraging the standard
|
||||
:class:`Operator.draw` method for layout and display.
|
||||
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
class ShaderScriptImport(bpy.types.Operator, ImportHelper):
|
||||
"""
|
||||
Creates one or more Shader Script nodes from text files.
|
||||
"""
|
||||
bl_idname = "shader.script_import"
|
||||
bl_label = "Import a text file as a script node"
|
||||
|
||||
# This Operator supports processing multiple `.txt` files at a time. The following properties
|
||||
# must be defined.
|
||||
directory: bpy.props.StringProperty(subtype='DIR_PATH', options={'SKIP_SAVE', 'HIDDEN'})
|
||||
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={'SKIP_SAVE', 'HIDDEN'})
|
||||
|
||||
# Allow the user to choose whether the node's label is set or not
|
||||
set_label: bpy.props.BoolProperty(name="Set Label", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (
|
||||
context.region and context.region.type == 'WINDOW' and
|
||||
context.area and context.area.ui_type == 'ShaderNodeTree' and
|
||||
context.object and context.object.type == 'MESH' and
|
||||
context.material
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
# The directory property must be set.
|
||||
if not self.directory:
|
||||
return {'CANCELLED'}
|
||||
|
||||
x = 0.0
|
||||
y = 0.0
|
||||
for file in self.files:
|
||||
# Direct calls to this Operator may use unsupported file-paths. Ensure the incoming
|
||||
# files are ones that are supported.
|
||||
if file.name.endswith(".txt"):
|
||||
import os
|
||||
filepath = os.path.join(self.directory, file.name)
|
||||
|
||||
node_tree = context.material.node_tree
|
||||
text_node = node_tree.nodes.new(type="ShaderNodeScript")
|
||||
text_node.mode = 'EXTERNAL'
|
||||
text_node.filepath = filepath
|
||||
text_node.location = Vector((x, y))
|
||||
|
||||
# Set the node's title to the file name.
|
||||
if self.set_label:
|
||||
text_node.label = file.name
|
||||
|
||||
x += 20.0
|
||||
y -= 20.0
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
# Use ImportHelper's invoke_popup() to handle the invocation so that this operator's properties
|
||||
# are shown in a popup. This allows the user to configure additional settings on the operator
|
||||
# like the `set_label` property. Consider having a draw() method on the operator in order to
|
||||
# layout the properties in the UI appropriately.
|
||||
#
|
||||
# If filepath information is not provided the file select window will be invoked instead.
|
||||
|
||||
def invoke(self, context, event):
|
||||
return self.invoke_popup(context)
|
||||
|
||||
|
||||
# Define a file handler that supports the following set of conditions:
|
||||
# - Execute the `shader.script_import` operator
|
||||
# - When `.txt` files are dropped in the Shader Editor
|
||||
class SHADER_FH_script_import(bpy.types.FileHandler):
|
||||
bl_idname = "SHADER_FH_script_import"
|
||||
bl_label = "File handler for shader script node import"
|
||||
bl_import_operator = "shader.script_import"
|
||||
bl_file_extensions = ".txt"
|
||||
|
||||
@classmethod
|
||||
def poll_drop(cls, context):
|
||||
return (
|
||||
context.region and context.region.type == 'WINDOW' and
|
||||
context.area and context.area.ui_type == 'ShaderNodeTree'
|
||||
)
|
||||
|
||||
|
||||
bpy.utils.register_class(ShaderScriptImport)
|
||||
bpy.utils.register_class(SHADER_FH_script_import)
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Accessing Evaluated Geometry
|
||||
++++++++++++++++++++++++++++
|
||||
"""
|
||||
import bpy
|
||||
|
||||
# The GeometrySet can only be retrieved from an evaluated object. So one always
|
||||
# needs a depsgraph that has evaluated the object.
|
||||
depsgraph = bpy.context.view_layer.depsgraph
|
||||
ob = bpy.context.active_object
|
||||
ob_eval = depsgraph.id_eval_get(ob)
|
||||
|
||||
# Get the final evaluated geometry of an object.
|
||||
geometry = ob_eval.evaluated_geometry()
|
||||
|
||||
# Print basic information like the number of elements.
|
||||
print(geometry)
|
||||
|
||||
# A geometry set may have a name. It can be set with the Set Geometry Name node.
|
||||
print(geometry.name)
|
||||
|
||||
# Access "realized" geometry components.
|
||||
print(geometry.mesh)
|
||||
print(geometry.pointcloud)
|
||||
print(geometry.curves)
|
||||
print(geometry.volume)
|
||||
print(geometry.grease_pencil)
|
||||
|
||||
# Access the mesh without final subdivision applied.
|
||||
print(geometry.mesh_base)
|
||||
|
||||
# Accessing instances is a bit more tricky, because there is no specific
|
||||
# mechanism to expose instances. Instead, two accessors are provided which
|
||||
# are easy to keep working in the future even if we get a proper Instances type.
|
||||
|
||||
# This is a pointcloud that provides access to all the instance attributes.
|
||||
# There is a point per instances. May return None if there is no instances data.
|
||||
instances_pointcloud = geometry.instances_pointcloud()
|
||||
|
||||
if instances_pointcloud is not None:
|
||||
# This is a list containing the data that is instanced. The list may contain
|
||||
# None, objects, collections or other GeometrySets. If the geometry does not
|
||||
# have instances, the list is empty.
|
||||
references = geometry.instance_references()
|
||||
|
||||
# Besides normal generic attributes, there are also two important
|
||||
# instance-specific attributes. "instance_transform" is a 4x4 matrix attribute
|
||||
# containing the transforms of each instance.
|
||||
instance_transforms = instances_pointcloud.attributes["instance_transform"]
|
||||
|
||||
# ".reference_index" contains indices into the `references` list above and
|
||||
# determines what geometry each instance uses.
|
||||
reference_indices = instances_pointcloud.attributes[".reference_index"]
|
||||
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
Base class for integrating USD Hydra based renderers.
|
||||
|
||||
USD Hydra Based Renderer
|
||||
++++++++++++++++++++++++
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class CustomHydraRenderEngine(bpy.types.HydraRenderEngine):
|
||||
# Identifier and name in the user interface.
|
||||
bl_idname = "CUSTOM_HYDRA_RENDERER"
|
||||
bl_label = "Custom Hydra Renderer"
|
||||
|
||||
# Name of the render plugin.
|
||||
bl_delegate_id = "HdCustomRendererPlugin"
|
||||
|
||||
# Use MaterialX instead of `UsdPreviewSurface` for materials.
|
||||
bl_use_materialx = True
|
||||
|
||||
# Register path to plugin.
|
||||
@classmethod
|
||||
def register(cls):
|
||||
# Make `pxr` module available, for running as `bpy` PIP package.
|
||||
bpy.utils.expose_bundled_modules()
|
||||
|
||||
import pxr.Plug
|
||||
pxr.Plug.Registry().RegisterPlugins(['/path/to/plugin'])
|
||||
|
||||
# Render settings that will be passed to the delegate.
|
||||
def get_render_settings(self, engine_type):
|
||||
return {
|
||||
'myBoolean': True,
|
||||
'myValue': 8,
|
||||
'aovToken:Depth': "depth",
|
||||
}
|
||||
|
||||
# RenderEngine methods for update, render and draw are implemented in
|
||||
# HydraRenderEngine. Optionally extra work can be done before or after
|
||||
# by implementing the methods like this.
|
||||
def update(self, data, depsgraph):
|
||||
super().update(data, depsgraph)
|
||||
# Do extra work here.
|
||||
|
||||
def update_render_passes(self, scene, render_layer):
|
||||
if render_layer.use_pass_z:
|
||||
self.register_pass(scene, render_layer, 'Depth', 1, 'Z', 'VALUE')
|
||||
|
||||
|
||||
# Registration.
|
||||
def register():
|
||||
bpy.utils.register_class(CustomHydraRenderEngine)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(CustomHydraRenderEngine)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
This function is for advanced use only, misuse can crash Blender since the user
|
||||
count is used to prevent data being removed when it is used.
|
||||
"""
|
||||
|
||||
# This example shows what _not_ to do, and will crash Blender.
|
||||
import bpy
|
||||
|
||||
# Object which is in the scene.
|
||||
obj = bpy.data.objects["Cube"]
|
||||
|
||||
# Without this, removal would raise an error.
|
||||
obj.user_clear()
|
||||
|
||||
# Runs without an exception but will crash on redraw.
|
||||
bpy.data.objects.remove(obj)
|
||||
46
blender-5.2.0/doc/python_api/examples/bpy.types.Image.0.py
Normal file
46
blender-5.2.0/doc/python_api/examples/bpy.types.Image.0.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Image Data
|
||||
++++++++++
|
||||
|
||||
The Image data-block is a shallow wrapper around image or video file(s)
|
||||
(on disk, as packed data, or generated).
|
||||
|
||||
All actual data like the pixel buffer, size, resolution etc. is
|
||||
cached in an :class:`imbuf.types.ImBuf` image buffer (or several buffers
|
||||
in some cases, like UDIM textures, multi-views, animations...).
|
||||
|
||||
Several properties and functions of the Image data-block are then actually
|
||||
using/modifying its image buffer, and not the Image data-block itself.
|
||||
|
||||
.. warning::
|
||||
|
||||
One key limitation is that image buffers are not shared between different
|
||||
Image data-blocks, and they are not duplicated when copying an image.
|
||||
|
||||
So until a modified image buffer is saved on disk, duplicating its Image
|
||||
data-block will not propagate the underlying buffer changes to the new Image.
|
||||
|
||||
|
||||
This example script generates an Image data-block with a given size,
|
||||
change its first pixel, rescale it, and duplicates the image.
|
||||
|
||||
The duplicated image still has the same size and colors as the original image
|
||||
at its creation, all editing in the original image's buffer is 'lost' in its copy.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
image_src = bpy.data.images.new('src', 1024, 102)
|
||||
print(image_src.size)
|
||||
print(image_src.pixels[0:4])
|
||||
|
||||
image_src.scale(1024, 720)
|
||||
image_src.pixels[0:4] = (0.5, 0.5, 0.5, 0.5)
|
||||
image_src.update()
|
||||
print(image_src.size)
|
||||
print(image_src.pixels[0:4])
|
||||
|
||||
image_dest = image_src.copy()
|
||||
image_dest.update()
|
||||
print(image_dest.size)
|
||||
print(image_dest.pixels[0:4])
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Inline Shader Nodes
|
||||
+++++++++++++++++++
|
||||
"""
|
||||
import bpy
|
||||
|
||||
# The materials should be retrieved from the evaluated object to make sure that
|
||||
# e.g. edits of Geometry Nodes are applied.
|
||||
depsgraph = bpy.context.view_layer.depsgraph
|
||||
ob = bpy.context.active_object
|
||||
ob_eval = depsgraph.id_eval_get(ob)
|
||||
material_eval = ob_eval.material_slots[0].material
|
||||
|
||||
# Compute the inlined shader nodes.
|
||||
# Important: Do not loose the reference to this object while accessing the inlined
|
||||
# node tree. Otherwise there will be a crash due to a dangling pointer.
|
||||
inline_shader_nodes = material_eval.inline_shader_nodes()
|
||||
|
||||
# Get the actual inlined `bpy.types.NodeTree`.
|
||||
tree = inline_shader_nodes.node_tree
|
||||
|
||||
for node in tree.nodes:
|
||||
print(node.name)
|
||||
73
blender-5.2.0/doc/python_api/examples/bpy.types.KeyMaps.1.py
Normal file
73
blender-5.2.0/doc/python_api/examples/bpy.types.KeyMaps.1.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Add-on Keymap Registration
|
||||
++++++++++++++++++++++++++
|
||||
|
||||
This example shows how an add-on can register custom keyboard shortcuts.
|
||||
Keymaps are added to ``keyconfigs.addon`` and removed when unregistered.
|
||||
|
||||
Store ``(keymap, keymap_item)`` tuples for safe cleanup, as multiple add-ons may use the same keymap.
|
||||
|
||||
.. note::
|
||||
|
||||
Users can customize add-on shortcuts in the Keymap Preferences.
|
||||
Add-on keymaps appear under their respective editors and can be
|
||||
modified or disabled without editing the add-on code.
|
||||
|
||||
Add-ons should only manipulate keymaps in ``keyconfigs.addon`` and not manipulate the user's keymaps
|
||||
because add-on keymaps serve as a default which users may customize.
|
||||
Modifying user keymaps directly interferes with users' own preferences.
|
||||
|
||||
.. warning::
|
||||
|
||||
Add-ons can add items to existing modal keymaps but cannot create
|
||||
new modal keymaps via Python. Use ``modal=True`` when targeting
|
||||
an existing modal keymap such as "Knife Tool Modal Map".
|
||||
"""
|
||||
|
||||
# In this example keymap registration functions are only split out for clarity,
|
||||
# so skipping keymap registration in background mode doesn't interfere with other registration logic.
|
||||
|
||||
import bpy
|
||||
|
||||
# Store (keymap, keymap_item) for cleanup on unregister.
|
||||
addon_keymaps = []
|
||||
|
||||
|
||||
def register_keymaps():
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
if kc is None:
|
||||
return # Can be None in background mode.
|
||||
|
||||
# Target the 3D View; name must match Blender's built-in keymap exactly.
|
||||
km = kc.keymaps.new(name="3D View", space_type='VIEW_3D')
|
||||
|
||||
# Bind Shift+Alt+K to frame selected objects.
|
||||
kmi = km.keymap_items.new(
|
||||
idname="view3d.view_selected",
|
||||
type='K',
|
||||
value='PRESS',
|
||||
shift=True,
|
||||
alt=True,
|
||||
)
|
||||
kmi.properties.use_all_regions = True
|
||||
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
|
||||
def unregister_keymaps():
|
||||
for km, kmi in addon_keymaps:
|
||||
km.keymap_items.remove(kmi)
|
||||
addon_keymaps.clear()
|
||||
|
||||
|
||||
def register():
|
||||
register_keymaps()
|
||||
|
||||
|
||||
def unregister():
|
||||
unregister_keymaps()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
50
blender-5.2.0/doc/python_api/examples/bpy.types.Macro.0.py
Normal file
50
blender-5.2.0/doc/python_api/examples/bpy.types.Macro.0.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Example Macro
|
||||
+++++++++++++
|
||||
|
||||
This example creates a simple macro operator that
|
||||
moves the active object and then rotates it.
|
||||
It demonstrates:
|
||||
|
||||
- Defining a macro operator class.
|
||||
- Registering it and defining sub-operators.
|
||||
- Setting property values for each step.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class OBJECT_OT_simple_macro(bpy.types.Macro):
|
||||
bl_idname = "object.simple_macro"
|
||||
bl_label = "Simple Transform Macro"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object is not None
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(OBJECT_OT_simple_macro)
|
||||
|
||||
# Define steps after registration and set operator values via .properties
|
||||
step = OBJECT_OT_simple_macro.define("transform.translate")
|
||||
props = step.properties
|
||||
props.value = (1.0, 0.0, 0.0)
|
||||
props.constraint_axis = (True, False, False)
|
||||
|
||||
step = OBJECT_OT_simple_macro.define("transform.rotate")
|
||||
props = step.properties
|
||||
props.value = 0.785398 # 45 degrees in radians
|
||||
props.orient_axis = 'Z'
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(OBJECT_OT_simple_macro)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
# To run the macro:
|
||||
bpy.ops.object.simple_macro()
|
||||
42
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.0.py
Normal file
42
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.0.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Basic Menu Example
|
||||
++++++++++++++++++
|
||||
|
||||
Here is an example of a simple menu. Menus differ from panels in that they must
|
||||
reference from a header, panel or another menu.
|
||||
|
||||
Notice the 'CATEGORY_MT_name' in :class:`Menu.bl_idname`, this is a naming
|
||||
convention for menus.
|
||||
|
||||
.. note::
|
||||
|
||||
Menu subclasses must be registered before referencing them from Blender.
|
||||
|
||||
.. note::
|
||||
|
||||
Menus have their :class:`UILayout.operator_context` initialized as
|
||||
'EXEC_REGION_WIN' rather than 'INVOKE_REGION_WIN' (see :ref:`Execution Context <rna_enum_operator_context_items>`).
|
||||
If the operator context needs to initialize inputs from the
|
||||
:class:`Operator.invoke` function, then this needs to be explicitly set.
|
||||
When a menu is added to UI elements such as a panel or header,
|
||||
the operator execution context will be inherited from them.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class BasicMenu(bpy.types.Menu):
|
||||
bl_idname = "OBJECT_MT_select_test"
|
||||
bl_label = "Select"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator("object.select_all", text="Select/Deselect All").action = 'TOGGLE'
|
||||
layout.operator("object.select_all", text="Inverse").action = 'INVERT'
|
||||
layout.operator("object.select_random", text="Random")
|
||||
|
||||
|
||||
bpy.utils.register_class(BasicMenu)
|
||||
|
||||
# Test call to display immediately.
|
||||
bpy.ops.wm.call_menu(name="OBJECT_MT_select_test")
|
||||
38
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.1.py
Normal file
38
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.1.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Submenus
|
||||
++++++++
|
||||
|
||||
This menu demonstrates some different functions.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class SubMenu(bpy.types.Menu):
|
||||
bl_idname = "OBJECT_MT_select_submenu"
|
||||
bl_label = "Select"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator("object.select_all", text="Select/Deselect All").action = 'TOGGLE'
|
||||
layout.operator("object.select_all", text="Inverse").action = 'INVERT'
|
||||
layout.operator("object.select_random", text="Random")
|
||||
|
||||
# Access this operator as a sub-menu.
|
||||
layout.operator_menu_enum("object.select_by_type", "type", text="Select All by Type")
|
||||
|
||||
layout.separator()
|
||||
|
||||
# Expand each operator option into this menu.
|
||||
layout.operator_enum("object.light_add", "type")
|
||||
|
||||
layout.separator()
|
||||
|
||||
# Use existing menu.
|
||||
layout.menu("VIEW3D_MT_transform")
|
||||
|
||||
|
||||
bpy.utils.register_class(SubMenu)
|
||||
|
||||
# Test call to display immediately.
|
||||
bpy.ops.wm.call_menu(name="OBJECT_MT_select_submenu")
|
||||
18
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.2.py
Normal file
18
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.2.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Extending Menus
|
||||
+++++++++++++++
|
||||
|
||||
When creating menus for add-ons you can't reference menus
|
||||
in Blender's default scripts.
|
||||
Instead, the add-on can add menu items to existing menus.
|
||||
|
||||
The function menu_draw acts like :class:`Menu.draw`.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
def menu_draw(self, context):
|
||||
self.layout.operator("wm.save_homefile")
|
||||
|
||||
|
||||
bpy.types.TOPBAR_MT_file.append(menu_draw)
|
||||
80
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.3.py
Normal file
80
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.3.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Preset Menus
|
||||
++++++++++++
|
||||
|
||||
Preset menus are simply a convention that uses a menu sub-class
|
||||
to perform the common task of managing presets.
|
||||
|
||||
This example shows how you can add a preset menu.
|
||||
|
||||
This example uses the object display options,
|
||||
however you can use properties defined by your own scripts too.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator, Menu
|
||||
from bl_operators.presets import AddPresetBase
|
||||
|
||||
|
||||
class OBJECT_MT_display_presets(Menu):
|
||||
bl_label = "Object Display Presets"
|
||||
preset_subdir = "object/display"
|
||||
preset_operator = "script.execute_preset"
|
||||
draw = Menu.draw_preset
|
||||
|
||||
|
||||
class AddPresetObjectDisplay(AddPresetBase, Operator):
|
||||
'''Add a Object Display Preset'''
|
||||
bl_idname = "camera.object_display_preset_add"
|
||||
bl_label = "Add Object Display Preset"
|
||||
preset_menu = "OBJECT_MT_display_presets"
|
||||
|
||||
# Variable used for all preset values.
|
||||
preset_defines = [
|
||||
"obj = bpy.context.object"
|
||||
]
|
||||
|
||||
# Properties to store in the preset.
|
||||
preset_values = [
|
||||
"obj.display_type",
|
||||
"obj.show_bounds",
|
||||
"obj.display_bounds_type",
|
||||
"obj.show_name",
|
||||
"obj.show_axis",
|
||||
"obj.show_wire",
|
||||
]
|
||||
|
||||
# Where to store the preset.
|
||||
preset_subdir = "object/display"
|
||||
|
||||
|
||||
# Display into an existing panel.
|
||||
def panel_func(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.menu(OBJECT_MT_display_presets.__name__, text=OBJECT_MT_display_presets.bl_label)
|
||||
row.operator(AddPresetObjectDisplay.bl_idname, text="", icon='ZOOM_IN')
|
||||
row.operator(AddPresetObjectDisplay.bl_idname, text="", icon='ZOOM_OUT').remove_active = True
|
||||
|
||||
|
||||
classes = (
|
||||
OBJECT_MT_display_presets,
|
||||
AddPresetObjectDisplay,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
bpy.types.OBJECT_PT_display.prepend(panel_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
bpy.types.OBJECT_PT_display.remove(panel_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
66
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.4.py
Normal file
66
blender-5.2.0/doc/python_api/examples/bpy.types.Menu.4.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Extending the Button Context Menu
|
||||
+++++++++++++++++++++++++++++++++
|
||||
|
||||
This example enables you to insert your own menu entry into the common
|
||||
right click menu that you get while hovering over a UI button (e.g. operator,
|
||||
value field, color, string, etc.)
|
||||
|
||||
To make the example work, you have to first select an object
|
||||
then right click on an user interface element (maybe a color in the
|
||||
material properties) and choose *Execute Custom Action*.
|
||||
|
||||
Executing the operator will then print all values.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def dump(obj, text):
|
||||
for attr in dir(obj):
|
||||
print("{!r}.{:s} = {!s}".format(obj, attr, getattr(obj, attr)))
|
||||
|
||||
|
||||
class WM_OT_button_context_test(bpy.types.Operator):
|
||||
"""Right click entry test"""
|
||||
bl_idname = "wm.button_context_test"
|
||||
bl_label = "Run Context Test"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object is not None
|
||||
|
||||
def execute(self, context):
|
||||
value = getattr(context, "button_pointer", None)
|
||||
if value is not None:
|
||||
dump(value, "button_pointer")
|
||||
|
||||
value = getattr(context, "button_prop", None)
|
||||
if value is not None:
|
||||
dump(value, "button_prop")
|
||||
|
||||
value = getattr(context, "button_operator", None)
|
||||
if value is not None:
|
||||
dump(value, "button_operator")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def draw_menu(self, context):
|
||||
layout = self.layout
|
||||
layout.separator()
|
||||
layout.operator(WM_OT_button_context_test.bl_idname)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(WM_OT_button_context_test)
|
||||
bpy.types.UI_MT_button_context_menu.append(draw_menu)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.UI_MT_button_context_menu.remove(draw_menu)
|
||||
bpy.utils.unregister_class(WM_OT_button_context_test)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
41
blender-5.2.0/doc/python_api/examples/bpy.types.Mesh.0.py
Normal file
41
blender-5.2.0/doc/python_api/examples/bpy.types.Mesh.0.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
Mesh Data
|
||||
+++++++++
|
||||
|
||||
The mesh data is accessed in object mode and intended for compact storage,
|
||||
for more flexible mesh editing from Python see :mod:`bmesh`.
|
||||
|
||||
Blender stores 4 main arrays to define mesh geometry.
|
||||
|
||||
- :class:`Mesh.vertices` (3 points in space)
|
||||
- :class:`Mesh.edges` (reference 2 vertices)
|
||||
- :class:`Mesh.loops` (reference a single vertex and edge)
|
||||
- :class:`Mesh.polygons`: (reference a range of loops)
|
||||
|
||||
|
||||
Each polygon references a slice in the loop array, this way,
|
||||
polygons do not store vertices or corner data such as UVs directly,
|
||||
only a reference to loops that the polygon uses.
|
||||
|
||||
:class:`Mesh.loops`, :class:`Mesh.uv_layers` :class:`Mesh.vertex_colors` are all aligned so the same polygon loop
|
||||
indices can be used to find the UVs and vertex colors as with as the vertices.
|
||||
|
||||
To compare mesh API options see: :ref:`NGons and Tessellation Faces <info_gotcha_mesh_faces>`
|
||||
|
||||
|
||||
This example script prints the vertices and UVs for each polygon, assumes the active object is a mesh with UVs.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
me = bpy.context.object.data
|
||||
uv_layer = me.uv_layers.active.data
|
||||
|
||||
for poly in me.polygons:
|
||||
print("Polygon index: {:d}, length: {:d}".format(poly.index, poly.loop_total))
|
||||
|
||||
# Range is used here to show how the polygons reference loops,
|
||||
# for convenience 'poly.loop_indices' can be used instead.
|
||||
for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
|
||||
print(" Vertex: {:d}".format(me.loops[loop_index].vertex_index))
|
||||
print(" UV: {!r}".format(uv_layer[loop_index].uv))
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Poll Function
|
||||
+++++++++++++++
|
||||
|
||||
The :class:`NodeTree.poll` function determines if a node tree is visible
|
||||
in the given context (similar to how :class:`Panel.poll`
|
||||
and :class:`Menu.poll` define visibility). If it returns False,
|
||||
the node tree type will not be selectable in the node editor.
|
||||
|
||||
A typical condition for shader nodes would be to check the active render engine
|
||||
of the scene and only show nodes of the renderer they are designed for.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class CyclesNodeTree(bpy.types.NodeTree):
|
||||
""" This operator is only visible when Cycles is the selected render engine"""
|
||||
bl_label = "Cycles Node Tree"
|
||||
bl_icon = 'NONE'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene.render.engine == 'CYCLES'
|
||||
|
||||
|
||||
bpy.utils.register_class(CyclesNodeTree)
|
||||
28
blender-5.2.0/doc/python_api/examples/bpy.types.Object.0.py
Normal file
28
blender-5.2.0/doc/python_api/examples/bpy.types.Object.0.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Basic Object Operations Example
|
||||
+++++++++++++++++++++++++++++++
|
||||
|
||||
This script demonstrates basic operations on object like creating new
|
||||
object, placing it into a view layer, selecting it and making it active.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
view_layer = bpy.context.view_layer
|
||||
|
||||
# Create new light data-block.
|
||||
light_data = bpy.data.lights.new(name="New Light", type='POINT')
|
||||
|
||||
# Create new object with our light data-block.
|
||||
light_object = bpy.data.objects.new(name="New Light", object_data=light_data)
|
||||
|
||||
# Link light object to the active collection of current view layer,
|
||||
# so that it'll appear in the current scene.
|
||||
view_layer.active_layer_collection.collection.objects.link(light_object)
|
||||
|
||||
# Place light to a specified location.
|
||||
light_object.location = (5.0, 5.0, 5.0)
|
||||
|
||||
# And finally select it and make it active.
|
||||
light_object.select_set(True)
|
||||
view_layer.objects.active = light_object
|
||||
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
Basic Operator Example
|
||||
++++++++++++++++++++++
|
||||
|
||||
This script shows simple operator which prints a message.
|
||||
|
||||
Since the operator only has an :class:`Operator.execute` function it takes no
|
||||
user input.
|
||||
|
||||
The function should return ``{'FINISHED'}`` or ``{'CANCELLED'}``, the latter
|
||||
meaning that operator execution was aborted without making any changes, and
|
||||
that no undo step will created (see next example for more info about undo).
|
||||
|
||||
.. note::
|
||||
|
||||
Operator subclasses must be registered before accessing them from Blender.
|
||||
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class HelloWorldOperator(bpy.types.Operator):
|
||||
bl_idname = "wm.hello_world"
|
||||
bl_label = "Minimal Operator"
|
||||
|
||||
def execute(self, context):
|
||||
print("Hello World")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu.
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(HelloWorldOperator.bl_idname, text="Hello World Operator")
|
||||
|
||||
|
||||
# Register and add to the view menu (required to also use F3 search "Hello World Operator" for quick access).
|
||||
bpy.utils.register_class(HelloWorldOperator)
|
||||
bpy.types.VIEW3D_MT_view.append(menu_func)
|
||||
|
||||
# Test call to the newly defined operator.
|
||||
bpy.ops.wm.hello_world()
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
.. _operator_modifying_blender_data_undo:
|
||||
|
||||
Modifying Blender Data & Undo
|
||||
+++++++++++++++++++++++++++++
|
||||
|
||||
Any operator modifying Blender data should enable the ``'UNDO'`` option.
|
||||
This will make Blender automatically create an undo step when the operator
|
||||
finishes its ``execute`` (or ``invoke``, see below) functions, and returns
|
||||
``{'FINISHED'}``.
|
||||
|
||||
Otherwise, no undo step will be created, which will at best corrupt the
|
||||
undo stack and confuse the user (since modifications done by the operator
|
||||
may either not be undoable, or be undone together with other edits done
|
||||
before). In many cases, this can even lead to data corruption and crashes.
|
||||
|
||||
Note that when an operator returns ``{'CANCELLED'}``, no undo step will be
|
||||
created. This means that if an error occurs *after* modifying some data
|
||||
already, it is better to return ``{'FINISHED'}``, unless it is possible to
|
||||
fully undo the changes before returning.
|
||||
|
||||
.. note::
|
||||
|
||||
Most examples in this page do not do any edit to Blender data, which is
|
||||
why it is safe to keep the default ``bl_options`` value for these operators.
|
||||
|
||||
.. note::
|
||||
|
||||
In some complex cases, the automatic undo step created on operator exit may
|
||||
not be enough. For example, if the operator does mode switching, or calls
|
||||
other operators that should create an extra undo step, etc.
|
||||
|
||||
Such manual undo push is possible using the :class:`bpy.ops.ed.undo_push`
|
||||
function. Be careful though, this is considered an advanced feature and
|
||||
requires some understanding of the actual undo system in Blender code.
|
||||
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class DataEditOperator(bpy.types.Operator):
|
||||
bl_idname = "object.data_edit"
|
||||
bl_label = "Data Editing Operator"
|
||||
# The default value is only 'REGISTER', 'UNDO' is mandatory when Blender data is modified
|
||||
# (and does require 'REGISTER' as well).
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
context.object.location.x += 1.0
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu.
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(DataEditOperator.bl_idname, text="Blender Data Editing Operator")
|
||||
|
||||
|
||||
# Register.
|
||||
bpy.utils.register_class(DataEditOperator)
|
||||
bpy.types.VIEW3D_MT_view.append(menu_func)
|
||||
|
||||
# Test call to the newly defined operator.
|
||||
bpy.ops.object.data_edit()
|
||||
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Invoke Function
|
||||
+++++++++++++++
|
||||
|
||||
:class:`Operator.invoke` is used to initialize the operator from the context
|
||||
at the moment the operator is called.
|
||||
invoke() is typically used to assign properties which are then used by
|
||||
execute().
|
||||
Some operators don't have an execute() function, removing the ability to be
|
||||
repeated from a script or macro.
|
||||
|
||||
When an operator is called via :mod:`bpy.ops`, the execution context depends
|
||||
on the argument provided to :mod:`bpy.ops`. By default, it uses execute().
|
||||
When an operator is activated from a button or menu item, it follows
|
||||
the setting in :class:`UILayout.operator_context`. In most cases, invoke() is used.
|
||||
Running an operator via a key shortcut always uses invoke(),
|
||||
and this behavior cannot be changed.
|
||||
|
||||
This example shows how to define an operator which gets mouse input to
|
||||
execute a function and that this operator can be invoked or executed from
|
||||
the Python API.
|
||||
|
||||
Also notice this operator defines its own properties, these are different
|
||||
to typical class properties because Blender registers them with the
|
||||
operator, to use as arguments when called, saved for operator undo/redo and
|
||||
automatically added into the user interface.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class SimpleMouseOperator(bpy.types.Operator):
|
||||
""" This operator shows the mouse location,
|
||||
this string is used for the tooltip and API docs
|
||||
"""
|
||||
bl_idname = "wm.mouse_position"
|
||||
bl_label = "Invoke Mouse Operator"
|
||||
|
||||
x: bpy.props.IntProperty()
|
||||
y: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
# Rather than printing, use the report function,
|
||||
# this way the message appears in the header.
|
||||
self.report({'INFO'}, "Mouse coords are {:d} {:d}".format(self.x, self.y))
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.x = event.mouse_x
|
||||
self.y = event.mouse_y
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu.
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(SimpleMouseOperator.bl_idname, text="Simple Mouse Operator")
|
||||
|
||||
|
||||
# Register and add to the view menu (required to also use F3 search "Simple Mouse Operator" for quick access).
|
||||
bpy.utils.register_class(SimpleMouseOperator)
|
||||
bpy.types.VIEW3D_MT_view.append(menu_func)
|
||||
|
||||
# Test call to the newly defined operator.
|
||||
# Here we call the operator and invoke it,
|
||||
# meaning that the settings are taken from the mouse.
|
||||
bpy.ops.wm.mouse_position('INVOKE_DEFAULT')
|
||||
|
||||
# Another test call, this time call execute() directly with pre-defined settings.
|
||||
bpy.ops.wm.mouse_position('EXEC_DEFAULT', x=20, y=66)
|
||||
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Calling a File Selector
|
||||
+++++++++++++++++++++++
|
||||
This example shows how an operator can use the file selector.
|
||||
|
||||
Notice the invoke function calls a window manager method and returns
|
||||
``{'RUNNING_MODAL'}``, this means the file selector stays open and the operator does not
|
||||
exit immediately after invoke finishes.
|
||||
|
||||
The file selector runs the operator, calling :class:`Operator.execute` when the
|
||||
user confirms.
|
||||
|
||||
The :class:`Operator.poll` function is optional, used to check if the operator
|
||||
can run.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class ExportSomeData(bpy.types.Operator):
|
||||
"""Test exporter which just writes hello world"""
|
||||
bl_idname = "export.some_data"
|
||||
bl_label = "Export Some Data"
|
||||
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object is not None
|
||||
|
||||
def execute(self, context):
|
||||
file = open(self.filepath, 'w')
|
||||
file.write("Hello World " + context.object.name)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu.
|
||||
def menu_func(self, context):
|
||||
self.layout.operator_context = 'INVOKE_DEFAULT'
|
||||
self.layout.operator(ExportSomeData.bl_idname, text="Text Export Operator")
|
||||
|
||||
|
||||
# Register and add to the file selector (required to also use F3 search "Text Export Operator" for quick access).
|
||||
bpy.utils.register_class(ExportSomeData)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func)
|
||||
|
||||
|
||||
# Test call.
|
||||
bpy.ops.export.some_data('INVOKE_DEFAULT')
|
||||
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
Dialog Box
|
||||
++++++++++
|
||||
|
||||
This operator uses its :class:`Operator.invoke` function to call a popup.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class DialogOperator(bpy.types.Operator):
|
||||
bl_idname = "object.dialog_operator"
|
||||
bl_label = "Simple Dialog Operator"
|
||||
|
||||
my_float: bpy.props.FloatProperty(name="Some Floating Point")
|
||||
my_bool: bpy.props.BoolProperty(name="Toggle Option")
|
||||
my_string: bpy.props.StringProperty(name="String Value")
|
||||
|
||||
def execute(self, context):
|
||||
message = "Popup Values: {:f}, {:d}, '{:s}'".format(
|
||||
self.my_float, self.my_bool, self.my_string,
|
||||
)
|
||||
self.report({'INFO'}, message)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu.
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(DialogOperator.bl_idname, text="Dialog Operator")
|
||||
|
||||
|
||||
# Register and add to the object menu (required to also use F3 search "Dialog Operator" for quick access).
|
||||
bpy.utils.register_class(DialogOperator)
|
||||
bpy.types.VIEW3D_MT_object.append(menu_func)
|
||||
|
||||
# Test call.
|
||||
bpy.ops.object.dialog_operator('INVOKE_DEFAULT')
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Custom Drawing
|
||||
++++++++++++++
|
||||
|
||||
By default operator properties use an automatic user interface layout.
|
||||
If you need more control you can create your own layout with a
|
||||
:class:`Operator.draw` function.
|
||||
|
||||
This works like the :class:`Panel` and :class:`Menu` draw functions, its used
|
||||
for dialogs and file selectors.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class CustomDrawOperator(bpy.types.Operator):
|
||||
bl_idname = "object.custom_draw"
|
||||
bl_label = "Simple Modal Operator"
|
||||
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
my_float: bpy.props.FloatProperty(name="Float")
|
||||
my_bool: bpy.props.BoolProperty(name="Toggle Option")
|
||||
my_string: bpy.props.StringProperty(name="String Value")
|
||||
|
||||
def execute(self, context):
|
||||
print("Test", self)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.label(text="Custom Interface!")
|
||||
|
||||
row = col.row()
|
||||
row.prop(self, "my_float")
|
||||
row.prop(self, "my_bool")
|
||||
|
||||
col.prop(self, "my_string")
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu.
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(CustomDrawOperator.bl_idname, text="Custom Draw Operator")
|
||||
|
||||
|
||||
# Register and add to the object menu (required to also use F3 search "Custom Draw Operator" for quick access).
|
||||
bpy.utils.register_class(CustomDrawOperator)
|
||||
bpy.types.VIEW3D_MT_object.append(menu_func)
|
||||
|
||||
# Test call.
|
||||
bpy.ops.object.custom_draw('INVOKE_DEFAULT')
|
||||
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
.. _modal_operator:
|
||||
|
||||
Modal Execution
|
||||
+++++++++++++++
|
||||
|
||||
This operator defines a :class:`Operator.modal` function that will keep being
|
||||
run to handle events until it returns ``{'FINISHED'}`` or ``{'CANCELLED'}``.
|
||||
|
||||
Modal operators run every time a new event is detected, such as a mouse click
|
||||
or key press. Conversely, when no new events are detected, the modal operator
|
||||
will not run. Modal operators are especially useful for interactive tools, an
|
||||
operator can have its own state where keys toggle options as the operator runs.
|
||||
Grab, Rotate, Scale, and Fly-Mode are examples of modal operators.
|
||||
|
||||
:class:`Operator.invoke` is used to initialize the operator as being active
|
||||
by returning ``{'RUNNING_MODAL'}``, initializing the modal loop.
|
||||
|
||||
Notice ``__init__()`` and ``__del__()`` are declared.
|
||||
For other operator types they are not useful but for modal operators they will
|
||||
be called before the :class:`Operator.invoke` and after the operator finishes.
|
||||
Also see the
|
||||
:ref:`class construction and destruction section <info_overview_class_construction_destruction>`.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class ModalOperator(bpy.types.Operator):
|
||||
bl_idname = "object.modal_operator"
|
||||
bl_label = "Simple Modal Operator"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
print("Start")
|
||||
|
||||
def __del__(self):
|
||||
print("End")
|
||||
super().__del__()
|
||||
|
||||
def execute(self, context):
|
||||
context.object.location.x = self.value / 100.0
|
||||
return {'FINISHED'}
|
||||
|
||||
def modal(self, context, event):
|
||||
if event.type == 'MOUSEMOVE': # Apply.
|
||||
self.value = event.mouse_x
|
||||
self.execute(context)
|
||||
elif event.type == 'LEFTMOUSE': # Confirm.
|
||||
return {'FINISHED'}
|
||||
elif event.type in {'RIGHTMOUSE', 'ESC'}: # Cancel.
|
||||
# Revert all changes that have been made
|
||||
context.object.location.x = self.init_loc_x
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.init_loc_x = context.object.location.x
|
||||
self.value = event.mouse_x
|
||||
self.execute(context)
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu.
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(ModalOperator.bl_idname, text="Modal Operator")
|
||||
|
||||
|
||||
# Register and add to the object menu (required to also use F3 search "Modal Operator" for quick access).
|
||||
bpy.utils.register_class(ModalOperator)
|
||||
bpy.types.VIEW3D_MT_object.append(menu_func)
|
||||
|
||||
# Test call.
|
||||
bpy.ops.object.modal_operator('INVOKE_DEFAULT')
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Enum Search Popup
|
||||
+++++++++++++++++
|
||||
|
||||
You may want to have an operator prompt the user to select an item
|
||||
from a search field, this can be done using :class:`bpy.types.Operator.invoke_search_popup`.
|
||||
"""
|
||||
import bpy
|
||||
from bpy.props import EnumProperty
|
||||
|
||||
|
||||
class SearchEnumOperator(bpy.types.Operator):
|
||||
bl_idname = "object.search_enum_operator"
|
||||
bl_label = "Search Enum Operator"
|
||||
bl_property = "my_search"
|
||||
|
||||
my_search: EnumProperty(
|
||||
name="My Search",
|
||||
items=(
|
||||
('FOO', "Foo", ""),
|
||||
('BAR', "Bar", ""),
|
||||
('BAZ', "Baz", ""),
|
||||
),
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
self.report({'INFO'}, "Selected:" + self.my_search)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.invoke_search_popup(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu.
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(SearchEnumOperator.bl_idname, text="Search Enum Operator")
|
||||
|
||||
|
||||
# Register and add to the object menu (required to also use F3 search "Search Enum Operator" for quick access).
|
||||
bpy.utils.register_class(SearchEnumOperator)
|
||||
bpy.types.VIEW3D_MT_object.append(menu_func)
|
||||
|
||||
# Test call.
|
||||
bpy.ops.object.search_enum_operator('INVOKE_DEFAULT')
|
||||
29
blender-5.2.0/doc/python_api/examples/bpy.types.Panel.0.py
Normal file
29
blender-5.2.0/doc/python_api/examples/bpy.types.Panel.0.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Basic Panel Example
|
||||
+++++++++++++++++++
|
||||
|
||||
This script is a simple panel which will draw into the object properties
|
||||
section.
|
||||
|
||||
Notice the 'CATEGORY_PT_name' :class:`Panel.bl_idname`, this is a naming
|
||||
convention for panels.
|
||||
|
||||
.. note::
|
||||
|
||||
Panel subclasses must be registered for Blender to use them.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class HelloWorldPanel(bpy.types.Panel):
|
||||
bl_idname = "OBJECT_PT_hello_world"
|
||||
bl_label = "Hello World"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Hello World")
|
||||
|
||||
|
||||
bpy.utils.register_class(HelloWorldPanel)
|
||||
38
blender-5.2.0/doc/python_api/examples/bpy.types.Panel.1.py
Normal file
38
blender-5.2.0/doc/python_api/examples/bpy.types.Panel.1.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Simple Object Panel
|
||||
+++++++++++++++++++
|
||||
|
||||
This panel has a :class:`Panel.poll` and :class:`Panel.draw_header` function,
|
||||
even though the contents is basic this closely resembles blenders panels.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class ObjectSelectPanel(bpy.types.Panel):
|
||||
bl_idname = "OBJECT_PT_select"
|
||||
bl_label = "Select"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (context.object is not None)
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="My Select Panel")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
box = layout.box()
|
||||
box.label(text="Selection Tools")
|
||||
box.operator("object.select_all").action = 'TOGGLE'
|
||||
row = box.row()
|
||||
row.operator("object.select_all").action = 'INVERT'
|
||||
row.operator("object.select_random")
|
||||
|
||||
|
||||
bpy.utils.register_class(ObjectSelectPanel)
|
||||
37
blender-5.2.0/doc/python_api/examples/bpy.types.Panel.2.py
Normal file
37
blender-5.2.0/doc/python_api/examples/bpy.types.Panel.2.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
Mix-in Classes
|
||||
++++++++++++++
|
||||
A mix-in parent class can be used to share common properties and
|
||||
:class:`Menu.poll` function.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class View3DPanel:
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Tool"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (context.object is not None)
|
||||
|
||||
|
||||
class PanelOne(View3DPanel, bpy.types.Panel):
|
||||
bl_idname = "VIEW3D_PT_test_1"
|
||||
bl_label = "Panel One"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Small Class")
|
||||
|
||||
|
||||
class PanelTwo(View3DPanel, bpy.types.Panel):
|
||||
bl_idname = "VIEW3D_PT_test_2"
|
||||
bl_label = "Panel Two"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Also Small Class")
|
||||
|
||||
|
||||
bpy.utils.register_class(PanelOne)
|
||||
bpy.utils.register_class(PanelTwo)
|
||||
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
This example shows how to use B-Bone segment matrices to emulate deformation
|
||||
produced by the Armature modifier or constraint when assigned to the given bone
|
||||
(without Preserve Volume). The coordinates are processed in armature Pose space:
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
def bbone_deform_matrix(pose_bone, point):
|
||||
index, blend_next = pose_bone.bbone_segment_index(point)
|
||||
|
||||
rest1 = pose_bone.bbone_segment_matrix(index, rest=True)
|
||||
pose1 = pose_bone.bbone_segment_matrix(index, rest=False)
|
||||
deform1 = pose1 @ rest1.inverted()
|
||||
|
||||
# `bbone_segment_index` ensures that index + 1 is always valid
|
||||
rest2 = pose_bone.bbone_segment_matrix(index + 1, rest=True)
|
||||
pose2 = pose_bone.bbone_segment_matrix(index + 1, rest=False)
|
||||
deform2 = pose2 @ rest2.inverted()
|
||||
|
||||
deform = deform1 * (1 - blend_next) + deform2 * blend_next
|
||||
|
||||
return pose_bone.matrix @ deform @ pose_bone.bone.matrix_local.inverted()
|
||||
|
||||
|
||||
# Armature modifier deforming vertices:
|
||||
mesh = bpy.data.objects["Mesh"]
|
||||
pose_bone = bpy.data.objects["Armature"].pose.bones["Bone"]
|
||||
|
||||
for vertex in mesh.data.vertices:
|
||||
vertex.co = bbone_deform_matrix(pose_bone, vertex.co) @ vertex.co
|
||||
|
||||
# Armature constraint modifying an object transform:
|
||||
empty = bpy.data.objects["Empty"]
|
||||
matrix = empty.matrix_world
|
||||
|
||||
empty.matrix_world = bbone_deform_matrix(pose_bone, matrix.translation) @ matrix
|
||||
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
Custom Properties
|
||||
+++++++++++++++++
|
||||
|
||||
PropertyGroups are the base class for dynamically defined sets of properties.
|
||||
|
||||
They can be used to extend existing Blender data with your own types which can
|
||||
be animated, accessed from the user interface and from Python.
|
||||
|
||||
.. note::
|
||||
|
||||
The values assigned to Blender data are saved to disk but the class
|
||||
definitions are not, this means whenever you load Blender the class needs
|
||||
to be registered too.
|
||||
|
||||
This is best done by creating an add-on which loads on startup and registers
|
||||
your properties.
|
||||
|
||||
.. note::
|
||||
|
||||
PropertyGroups must be registered before assigning them to Blender data.
|
||||
|
||||
.. seealso::
|
||||
|
||||
Property types used in class declarations are all in :mod:`bpy.props`
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class MyPropertyGroup(bpy.types.PropertyGroup):
|
||||
custom_1: bpy.props.FloatProperty(name="My Float")
|
||||
custom_2: bpy.props.IntProperty(name="My Int")
|
||||
|
||||
|
||||
bpy.utils.register_class(MyPropertyGroup)
|
||||
|
||||
bpy.types.Object.my_prop_grp = bpy.props.PointerProperty(type=MyPropertyGroup)
|
||||
|
||||
|
||||
# Test this worked.
|
||||
bpy.data.objects[0].my_prop_grp.custom_1 = 22.0
|
||||
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Simple Render Engine
|
||||
++++++++++++++++++++
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import array
|
||||
|
||||
|
||||
class CustomRenderEngine(bpy.types.RenderEngine):
|
||||
# These three members are used by Blender to set up the
|
||||
# RenderEngine; define its internal name, visible name and capabilities.
|
||||
bl_idname = "CUSTOM"
|
||||
bl_label = "Custom"
|
||||
bl_use_preview = True
|
||||
|
||||
# Init is called whenever a new render engine instance is created. Multiple
|
||||
# instances may exist at the same time, for example for a viewport and final
|
||||
# render.
|
||||
# Note the generic arguments signature, and the call to the parent class
|
||||
# `__init__` methods, which are required for Blender to create the underlying
|
||||
# `RenderEngine` data.
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.scene_data = None
|
||||
self.draw_data = None
|
||||
|
||||
# When the render engine instance is destroy, this is called. Clean up any
|
||||
# render engine data here, for example stopping running render threads.
|
||||
def __del__(self):
|
||||
# Own delete code...
|
||||
super().__del__()
|
||||
|
||||
# This is the method called by Blender for both final renders (F12) and
|
||||
# small preview for materials, world and lights.
|
||||
def render(self, depsgraph):
|
||||
scene = depsgraph.scene
|
||||
scale = scene.render.resolution_percentage / 100.0
|
||||
self.size_x = int(scene.render.resolution_x * scale)
|
||||
self.size_y = int(scene.render.resolution_y * scale)
|
||||
|
||||
# Fill the render result with a flat color. The frame-buffer is
|
||||
# defined as a list of pixels, each pixel itself being a list of
|
||||
# R,G,B,A values.
|
||||
if self.is_preview:
|
||||
color = [0.1, 0.2, 0.1, 1.0]
|
||||
else:
|
||||
color = [0.2, 0.1, 0.1, 1.0]
|
||||
|
||||
pixel_count = self.size_x * self.size_y
|
||||
rect = [color] * pixel_count
|
||||
|
||||
# Here we write the pixel values to the RenderResult
|
||||
result = self.begin_result(0, 0, self.size_x, self.size_y)
|
||||
layer = result.layers[0].passes["Combined"]
|
||||
layer.rect = rect
|
||||
self.end_result(result)
|
||||
|
||||
# For viewport renders, this method gets called once at the start and
|
||||
# whenever the scene or 3D viewport changes. This method is where data
|
||||
# should be read from Blender in the same thread. Typically a render
|
||||
# thread will be started to do the work while keeping Blender responsive.
|
||||
def view_update(self, context, depsgraph):
|
||||
region = context.region
|
||||
view3d = context.space_data
|
||||
scene = depsgraph.scene
|
||||
|
||||
# Get viewport dimensions
|
||||
dimensions = region.width, region.height
|
||||
|
||||
if not self.scene_data:
|
||||
# First time initialization
|
||||
self.scene_data = []
|
||||
first_time = True
|
||||
|
||||
# Loop over all datablocks used in the scene.
|
||||
for datablock in depsgraph.ids:
|
||||
pass
|
||||
else:
|
||||
first_time = False
|
||||
|
||||
# Test which datablocks changed
|
||||
for update in depsgraph.updates:
|
||||
print("Datablock updated: ", update.id.name)
|
||||
|
||||
# Test if any material was added, removed or changed.
|
||||
if depsgraph.id_type_updated('MATERIAL'):
|
||||
print("Materials updated")
|
||||
|
||||
# Loop over all object instances in the scene.
|
||||
if first_time or depsgraph.id_type_updated('OBJECT'):
|
||||
for instance in depsgraph.object_instances:
|
||||
pass
|
||||
|
||||
# For viewport renders, this method is called whenever Blender redraws
|
||||
# the 3D viewport. The renderer is expected to quickly draw the render
|
||||
# with OpenGL, and not perform other expensive work.
|
||||
# Blender will draw overlays for selection and editing on top of the
|
||||
# rendered image automatically.
|
||||
def view_draw(self, context, depsgraph):
|
||||
# Lazily import GPU module, so that the render engine works in
|
||||
# background mode where the GPU module can't be imported by default.
|
||||
import gpu
|
||||
|
||||
region = context.region
|
||||
scene = depsgraph.scene
|
||||
|
||||
# Get viewport dimensions
|
||||
dimensions = region.width, region.height
|
||||
|
||||
# Bind shader that converts from scene linear to display space,
|
||||
gpu.state.blend_set('ALPHA_PREMULT')
|
||||
self.bind_display_space_shader(scene)
|
||||
|
||||
if not self.draw_data or self.draw_data.dimensions != dimensions:
|
||||
self.draw_data = CustomDrawData(dimensions)
|
||||
|
||||
self.draw_data.draw()
|
||||
|
||||
self.unbind_display_space_shader()
|
||||
gpu.state.blend_set('NONE')
|
||||
|
||||
|
||||
class CustomDrawData:
|
||||
def __init__(self, dimensions):
|
||||
import gpu
|
||||
|
||||
# Generate dummy float image buffer.
|
||||
self.dimensions = dimensions
|
||||
width, height = dimensions
|
||||
|
||||
pixels = width * height * array.array('f', [0.1, 0.2, 0.1, 1.0])
|
||||
pixels = gpu.types.Buffer('FLOAT', width * height * 4, pixels)
|
||||
|
||||
# Generate texture.
|
||||
self.texture = gpu.types.GPUTexture((width, height), format='RGBA16F', data=pixels)
|
||||
|
||||
# Note: This is just a didactic example.
|
||||
# In this case it would be more convenient to fill the texture with:
|
||||
# self.texture.clear('FLOAT', value=[0.1, 0.2, 0.1, 1.0])
|
||||
|
||||
def __del__(self):
|
||||
del self.texture
|
||||
|
||||
def draw(self):
|
||||
from gpu_extras.presets import draw_texture_2d
|
||||
draw_texture_2d(self.texture, (0, 0), self.texture.width, self.texture.height)
|
||||
|
||||
|
||||
# RenderEngines also need to tell UI Panels that they are compatible with.
|
||||
# We recommend to enable all panels marked as BLENDER_RENDER, and then
|
||||
# exclude any panels that are replaced by custom panels registered by the
|
||||
# render engine, or that are not supported.
|
||||
def get_panels():
|
||||
exclude_panels = {
|
||||
'VIEWLAYER_PT_filter',
|
||||
'VIEWLAYER_PT_layer_passes',
|
||||
}
|
||||
|
||||
panels = []
|
||||
for panel in bpy.types.Panel.__subclasses__():
|
||||
if hasattr(panel, 'COMPAT_ENGINES') and 'BLENDER_RENDER' in panel.COMPAT_ENGINES:
|
||||
if panel.__name__ not in exclude_panels:
|
||||
panels.append(panel)
|
||||
|
||||
return panels
|
||||
|
||||
|
||||
def register():
|
||||
# Register the RenderEngine.
|
||||
bpy.utils.register_class(CustomRenderEngine)
|
||||
|
||||
for panel in get_panels():
|
||||
panel.COMPAT_ENGINES.add('CUSTOM')
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(CustomRenderEngine)
|
||||
|
||||
for panel in get_panels():
|
||||
if 'CUSTOM' in panel.COMPAT_ENGINES:
|
||||
panel.COMPAT_ENGINES.remove('CUSTOM')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
GPU Render Engine
|
||||
+++++++++++++++++
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class CustomGPURenderEngine(bpy.types.RenderEngine):
|
||||
bl_idname = "CUSTOM_GPU"
|
||||
bl_label = "Custom GPU"
|
||||
|
||||
# Request a GPU context to be created and activated for the render method.
|
||||
# This may be used either to perform the rendering itself, or to allocate
|
||||
# and fill a texture for more efficient drawing.
|
||||
bl_use_gpu_context = True
|
||||
|
||||
def render(self, depsgraph):
|
||||
# Lazily import GPU module, since GPU context is only created on demand
|
||||
# for rendering and does not exist on register.
|
||||
import gpu
|
||||
|
||||
# Perform rendering task.
|
||||
pass
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(CustomGPURenderEngine)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(CustomGPURenderEngine)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
79
blender-5.2.0/doc/python_api/examples/bpy.types.UIList.1.py
Normal file
79
blender-5.2.0/doc/python_api/examples/bpy.types.UIList.1.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Basic UIList Example
|
||||
++++++++++++++++++++
|
||||
|
||||
This script is the UIList subclass used to show material slots, with a bunch of additional commentaries.
|
||||
|
||||
Notice the name of the class, this naming convention is similar as the one for panels or menus.
|
||||
|
||||
.. note::
|
||||
|
||||
UIList subclasses must be registered for Blender to use them.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class MATERIAL_UL_matslots_example(bpy.types.UIList):
|
||||
# The draw_item function is called for each item of the collection that is visible in the list.
|
||||
# data is the RNA object containing the collection,
|
||||
# item is the current drawn item of the collection,
|
||||
# icon is the "computed" icon for the item (as an integer, because some objects like materials or textures
|
||||
# have custom icons ID, which are not available as enum items).
|
||||
# active_data is the RNA object containing the active property for the collection (i.e. integer pointing to the
|
||||
# active item of the collection).
|
||||
# active_propname is the name of the active property (use 'getattr(active_data, active_propname)').
|
||||
# index is index of the current item in the collection.
|
||||
# flt_flag is the result of the filtering process for this item.
|
||||
# Note: as index and flt_flag are optional arguments, you do not have to use/declare them here if you don't
|
||||
# need them.
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
slot = item
|
||||
ma = slot.material
|
||||
# You should always start your row layout by a label (icon + text), or a non-embossed text field,
|
||||
# this will also make the row easily selectable in the list! The later also enables ctrl-click rename.
|
||||
# We use icon_value of label, as our given icon is an integer value, not an enum ID.
|
||||
# Note "data" names should never be translated!
|
||||
if ma:
|
||||
layout.prop(ma, "name", text="", emboss=False, icon_value=icon)
|
||||
else:
|
||||
layout.label(text="", translate=False, icon_value=icon)
|
||||
|
||||
|
||||
# And now we can use this list everywhere in Blender. Here is a small example panel.
|
||||
class UIListPanelExample1(bpy.types.Panel):
|
||||
"""Creates a Panel in the Object properties window"""
|
||||
bl_label = "UIList Example 1 Panel"
|
||||
bl_idname = "OBJECT_PT_ui_list_example_1"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
obj = context.object
|
||||
|
||||
# `template_list` now takes two new arguments.
|
||||
# The first one is the identifier of the registered UIList to use (if you want only the default list,
|
||||
# with no custom draw code, use "UI_UL_list").
|
||||
layout.template_list("MATERIAL_UL_matslots_example", "", obj, "material_slots", obj, "active_material_index")
|
||||
|
||||
# The second one can usually be left as an empty string.
|
||||
# It's an additional ID used to distinguish lists in case you use the same list several times in a given area.
|
||||
layout.template_list("MATERIAL_UL_matslots_example", "compact", obj, "material_slots",
|
||||
obj, "active_material_index", type='COMPACT')
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(MATERIAL_UL_matslots_example)
|
||||
bpy.utils.register_class(UIListPanelExample1)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(UIListPanelExample1)
|
||||
bpy.utils.unregister_class(MATERIAL_UL_matslots_example)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
216
blender-5.2.0/doc/python_api/examples/bpy.types.UIList.2.py
Normal file
216
blender-5.2.0/doc/python_api/examples/bpy.types.UIList.2.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
Advanced UIList Example - Filtering and Reordering
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
This script is an extended version of the ``UIList`` subclass used to show vertex groups. It is not used 'as is',
|
||||
because iterating over all vertices in a 'draw' function is a very bad idea for UI performance! However, it's a good
|
||||
example of how to create/use filtering/reordering callbacks.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
|
||||
class MESH_UL_vgroups_slow(bpy.types.UIList):
|
||||
# Constants (flags).
|
||||
# Be careful not to shadow FILTER_ITEM!
|
||||
VGROUP_EMPTY = 1 << 0
|
||||
|
||||
# Custom properties, saved with `.blend` file.
|
||||
use_filter_empty: bpy.props.BoolProperty(
|
||||
name="Filter Empty",
|
||||
default=False,
|
||||
options=set(),
|
||||
description="Whether to filter empty vertex groups",
|
||||
)
|
||||
use_filter_empty_reverse: bpy.props.BoolProperty(
|
||||
name="Reverse Empty",
|
||||
default=False,
|
||||
options=set(),
|
||||
description="Reverse empty filtering",
|
||||
)
|
||||
use_filter_name_reverse: bpy.props.BoolProperty(
|
||||
name="Reverse Name",
|
||||
default=False,
|
||||
options=set(),
|
||||
description="Reverse name filtering",
|
||||
)
|
||||
use_filter_orderby_invert: bpy.props.BoolProperty(
|
||||
name="Reverse Order",
|
||||
default=False,
|
||||
options=set(),
|
||||
description="Reverse order filtering",
|
||||
)
|
||||
|
||||
# This allows us to have mutually exclusive options, which are also all disable-able!
|
||||
def _gen_order_update(name1, name2):
|
||||
def _u(self, ctxt):
|
||||
if (getattr(self, name1)):
|
||||
setattr(self, name2, False)
|
||||
return _u
|
||||
use_order_name: bpy.props.BoolProperty(
|
||||
name="Name", default=False, options=set(),
|
||||
description="Sort groups by their name (case-insensitive)",
|
||||
update=_gen_order_update("use_order_name", "use_order_importance"),
|
||||
)
|
||||
use_order_importance: bpy.props.BoolProperty(
|
||||
name="Importance",
|
||||
default=False,
|
||||
options=set(),
|
||||
description="Sort groups by their average weight in the mesh",
|
||||
update=_gen_order_update("use_order_importance", "use_order_name"),
|
||||
)
|
||||
|
||||
# Usual draw item function.
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
|
||||
# Just in case, we do not use it here!
|
||||
self.use_filter_invert = False
|
||||
|
||||
# assert(isinstance(item, bpy.types.VertexGroup)
|
||||
vgroup = item
|
||||
# Here we use one feature of new filtering feature: it can pass data to draw_item, through flt_flag
|
||||
# parameter, which contains exactly what filter_items set in its filter list for this item!
|
||||
# In this case, we show empty groups grayed out.
|
||||
if flt_flag & self.VGROUP_EMPTY:
|
||||
col = layout.column()
|
||||
col.enabled = False
|
||||
col.alignment = 'LEFT'
|
||||
col.prop(vgroup, "name", text="", emboss=False, icon_value=icon)
|
||||
else:
|
||||
layout.prop(vgroup, "name", text="", emboss=False, icon_value=icon)
|
||||
icon = 'LOCKED' if vgroup.lock_weight else 'UNLOCKED'
|
||||
layout.prop(vgroup, "lock_weight", text="", icon=icon, emboss=False)
|
||||
|
||||
def draw_filter(self, context, layout):
|
||||
# Nothing much to say here, it's usual UI code...
|
||||
row = layout.row()
|
||||
|
||||
subrow = row.row(align=True)
|
||||
subrow.prop(self, "filter_name", text="")
|
||||
icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN'
|
||||
subrow.prop(self, "use_filter_name_reverse", text="", icon=icon)
|
||||
|
||||
subrow = row.row(align=True)
|
||||
subrow.prop(self, "use_filter_empty", toggle=True)
|
||||
icon = 'ZOOM_OUT' if self.use_filter_empty_reverse else 'ZOOM_IN'
|
||||
subrow.prop(self, "use_filter_empty_reverse", text="", icon=icon)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Order by:")
|
||||
row.prop(self, "use_order_name", toggle=True)
|
||||
row.prop(self, "use_order_importance", toggle=True)
|
||||
icon = 'TRIA_UP' if self.use_filter_orderby_invert else 'TRIA_DOWN'
|
||||
row.prop(self, "use_filter_orderby_invert", text="", icon=icon)
|
||||
|
||||
def filter_items_empty_vgroups(self, context, vgroups):
|
||||
# This helper function checks vgroups to find out whether they are empty, and what's their average weights.
|
||||
# TODO: This should be RNA helper actually (a vgroup prop like `"raw_data: ((vidx, vweight), etc.)"`).
|
||||
# Too slow for Python!
|
||||
obj_data = context.active_object.data
|
||||
ret = {vg.index: [True, 0.0] for vg in vgroups}
|
||||
if hasattr(obj_data, "vertices"): # Mesh data
|
||||
if obj_data.is_editmode:
|
||||
import bmesh
|
||||
bm = bmesh.from_edit_mesh(obj_data)
|
||||
# only ever one deform weight layer
|
||||
dvert_lay = bm.verts.layers.deform.active
|
||||
fact = 1 / len(bm.verts)
|
||||
if dvert_lay:
|
||||
for v in bm.verts:
|
||||
for vg_idx, vg_weight in v[dvert_lay].items():
|
||||
ret[vg_idx][0] = False
|
||||
ret[vg_idx][1] += vg_weight * fact
|
||||
else:
|
||||
fact = 1 / len(obj_data.vertices)
|
||||
for v in obj_data.vertices:
|
||||
for vg in v.groups:
|
||||
ret[vg.group][0] = False
|
||||
ret[vg.group][1] += vg.weight * fact
|
||||
elif hasattr(obj_data, "points"): # Lattice data
|
||||
# XXX: no access to lattice edit-data?
|
||||
fact = 1 / len(obj_data.points)
|
||||
for v in obj_data.points:
|
||||
for vg in v.groups:
|
||||
ret[vg.group][0] = False
|
||||
ret[vg.group][1] += vg.weight * fact
|
||||
return ret
|
||||
|
||||
def filter_items(self, context, data, propname):
|
||||
# This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
|
||||
# * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
|
||||
# matching item as filtered (i.e. to be shown). The upper 16 bits (including `self.bitflag_filter_item`) are
|
||||
# reserved for internal use, the lower 16 bits are free for custom use. Here we use the first bit to mark
|
||||
# VGROUP_EMPTY.
|
||||
# * The second one is for reordering, it must return a list containing the new indices of the items (which
|
||||
# gives us a mapping `org_idx -> new_idx`).
|
||||
# Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).
|
||||
# If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than
|
||||
# returning full lists doing nothing!).
|
||||
vgroups = getattr(data, propname)
|
||||
helper_funcs = bpy.types.UI_UL_list
|
||||
|
||||
# Default return values.
|
||||
flt_flags = []
|
||||
flt_neworder = []
|
||||
|
||||
# Pre-compute of vertex-groups data, unfortunately this is CPU-intensive.
|
||||
vgroups_empty = self.filter_items_empty_vgroups(context, vgroups)
|
||||
|
||||
# Filtering by name.
|
||||
if self.filter_name:
|
||||
flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, vgroups, "name",
|
||||
reverse=self.use_filter_name_reverse)
|
||||
if not flt_flags:
|
||||
flt_flags = [self.bitflag_filter_item] * len(vgroups)
|
||||
|
||||
# Filter by emptiness.
|
||||
for idx, vg in enumerate(vgroups):
|
||||
if vgroups_empty[vg.index][0]:
|
||||
flt_flags[idx] |= self.VGROUP_EMPTY
|
||||
if self.use_filter_empty and self.use_filter_empty_reverse:
|
||||
flt_flags[idx] &= ~self.bitflag_filter_item
|
||||
elif self.use_filter_empty and not self.use_filter_empty_reverse:
|
||||
flt_flags[idx] &= ~self.bitflag_filter_item
|
||||
|
||||
# Reorder by name or average weight.
|
||||
if self.use_order_name:
|
||||
flt_neworder = helper_funcs.sort_items_by_name(vgroups, "name")
|
||||
if self.use_filter_orderby_invert:
|
||||
flt_neworder.reverse()
|
||||
elif self.use_order_importance:
|
||||
_sort = [(idx, vgroups_empty[vg.index][1]) for idx, vg in enumerate(vgroups)]
|
||||
highest_first = not self.use_filter_orderby_invert
|
||||
flt_neworder = helper_funcs.sort_items_helper(_sort, lambda e: e[1], highest_first)
|
||||
|
||||
return flt_flags, flt_neworder
|
||||
|
||||
|
||||
# Minimal code to use above UIList...
|
||||
class UIListPanelExample2(bpy.types.Panel):
|
||||
"""Creates a Panel in the Object properties window"""
|
||||
bl_label = "UIList Example 2 Panel"
|
||||
bl_idname = "OBJECT_PT_ui_list_example_2"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
obj = context.object
|
||||
|
||||
# `template_list` now takes two new arguments.
|
||||
# The first one is the identifier of the registered UIList to use (if you want only the default list,
|
||||
# with no custom draw code, use "UI_UL_list").
|
||||
layout.template_list("MESH_UL_vgroups_slow", "", obj, "vertex_groups", obj.vertex_groups, "active_index")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(MESH_UL_vgroups_slow)
|
||||
bpy.utils.register_class(UIListPanelExample2)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(UIListPanelExample2)
|
||||
bpy.utils.unregister_class(MESH_UL_vgroups_slow)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
373
blender-5.2.0/doc/python_api/examples/bpy.types.USDHook.0.py
Normal file
373
blender-5.2.0/doc/python_api/examples/bpy.types.USDHook.0.py
Normal file
@@ -0,0 +1,373 @@
|
||||
"""
|
||||
USD Hook Example
|
||||
++++++++++++++++
|
||||
|
||||
This example shows an implementation of ``USDHook`` to extend USD
|
||||
export and import functionality.
|
||||
|
||||
Callback Function API
|
||||
---------------------
|
||||
|
||||
One may optionally define any or all of the following callback functions
|
||||
in the ``USDHook`` subclass.
|
||||
|
||||
on_export
|
||||
^^^^^^^^^
|
||||
|
||||
Called before the USD export finalizes, allowing modifications to the USD
|
||||
stage immediately before it is saved.
|
||||
|
||||
Args:
|
||||
|
||||
- ``export_context`` (`USDSceneExportContext`_): Provides access to the stage and dependency graph
|
||||
|
||||
Returns:
|
||||
|
||||
- ``True`` on success or ``False`` if the operation was bypassed or otherwise failed to complete
|
||||
|
||||
on_material_export
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Called for each material that is exported, allowing modifications to the USD material,
|
||||
such as shader generation.
|
||||
|
||||
Args:
|
||||
|
||||
- ``export_context`` (`USDMaterialExportContext`_): Provides access to the stage and a texture export utility function
|
||||
- ``bl_material`` (``bpy.types.Material``): The source Blender material
|
||||
- ``usd_material`` (``pxr.UsdShade.Material``): The target USD material to be exported
|
||||
|
||||
Returns:
|
||||
|
||||
- ``True`` on success or ``False`` if the operation was bypassed or otherwise failed to complete
|
||||
|
||||
Note that the target USD material might already have connected shaders created by the USD exporter or
|
||||
by other material export hooks.
|
||||
|
||||
on_import
|
||||
^^^^^^^^^
|
||||
|
||||
Called after the USD import finalizes.
|
||||
|
||||
Args:
|
||||
|
||||
- ``import_context`` (`USDSceneImportContext`_):
|
||||
Provides access to the stage and a map associating USD prim paths and Blender IDs
|
||||
|
||||
Returns:
|
||||
|
||||
- ``True`` on success or ``False`` if the operation was bypassed or otherwise failed to complete
|
||||
|
||||
|
||||
material_import_poll
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Called to determine if the ``USDHook`` implementation can convert a given USD material.
|
||||
|
||||
Args:
|
||||
|
||||
- ``import_context`` (`USDMaterialImportContext`_): Provides access to the stage and a texture import utility function
|
||||
- ``usd_material`` (``pxr.UsdShade.Material``): The source USD material to be exported
|
||||
|
||||
Returns:
|
||||
|
||||
- ``True`` if the hook can convert the material or ``False`` otherwise
|
||||
|
||||
If any hook returns ``True`` from ``material_import_poll``, the USD importer will skip standard ``USD Preview Surface``
|
||||
or ``MaterialX`` import and invoke the hook's `on_material_import`_ method to convert the material instead.
|
||||
|
||||
on_material_import
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Called for each material that is imported, to allow converting the USD material to nodes on the Blender material.
|
||||
To ensure that this function gets called, the hook must also implement the ``material_import_poll()``
|
||||
callback to return ``True`` for the given USD material.
|
||||
|
||||
Args:
|
||||
|
||||
- ``import_context`` (`USDMaterialImportContext`_): Provides access to the stage and a texture import utility function
|
||||
- ``bl_material`` (``bpy.types.Material``): The target Blender material with an empty node tree
|
||||
- ``usd_material`` (``pxr.UsdShade.Material``): The source USD material to be imported
|
||||
|
||||
Returns:
|
||||
|
||||
- ``True`` on success or ``False`` if the conversion failed or otherwise did not complete
|
||||
|
||||
|
||||
Context Classes
|
||||
---------------
|
||||
|
||||
Instances of the following built-in classes are provided as arguments to the callbacks.
|
||||
|
||||
USDSceneExportContext
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Argument for `on_export`_.
|
||||
|
||||
Methods:
|
||||
|
||||
- ``get_stage()``: returns the USD stage to be saved
|
||||
- ``get_depsgraph()``: returns the Blender scene dependency graph
|
||||
- ``get_prim_map()`` returns a ``dict`` where the key is an exported USD Prim path and the value a ``list``
|
||||
of the IDs associated with that prim.
|
||||
|
||||
|
||||
USDMaterialExportContext
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Argument for `on_material_export`_.
|
||||
|
||||
Methods:
|
||||
|
||||
- ``get_stage()``: returns the USD stage to be saved
|
||||
- ``export_texture(image: bpy.types.Image)``: Returns the USD asset path for the given texture image
|
||||
|
||||
The ``export_texture`` function will save in-memory images and may copy texture assets,
|
||||
depending on the current USD export options.
|
||||
For example, by default calling ``export_texture(/foo/bar.png)`` will copy the file to a ``textures``
|
||||
directory next to the exported USD and will return the relative path ``./textures/bar.png``.
|
||||
|
||||
|
||||
USDSceneImportContext
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Argument for `on_import`_.
|
||||
|
||||
Methods:
|
||||
|
||||
- ``get_prim_map()`` returns a ``dict`` where the key is an imported USD Prim path and the value a ``list``
|
||||
of the IDs created by the imported prim.
|
||||
- ``get_stage()`` returns the USD stage which was imported.
|
||||
|
||||
|
||||
USDMaterialImportContext
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Argument for `material_import_poll`_ and `on_material_import`_.
|
||||
|
||||
Methods:
|
||||
|
||||
- ``get_stage()``:
|
||||
returns the USD stage to be saved.
|
||||
- ``import_texture(asset_path: str)``:
|
||||
for the given USD texture asset path, returns a ``tuple[str, bool]``,
|
||||
containing the asset's local path and a bool indicating whether the path references a temporary file.
|
||||
|
||||
The ``import_texture`` function may copy the texture to the local file system if the given asset path is a
|
||||
package-relative path for a USDZ archive, depending on the current USD ``Import Textures`` options.
|
||||
When the ``Import Textures`` mode is ``Packed``, the texture is saved to a temporary location and the
|
||||
second element of the returned tuple is ``True``, indicating that the file is temporary, in which
|
||||
case it may be necessary to pack the image. The original asset path will be returned unchanged if it's
|
||||
already a local file or if it could not be copied to a local destination.
|
||||
|
||||
|
||||
Errors
|
||||
------
|
||||
|
||||
Exceptions raised by these functions will be reported in Blender with the exception details printed to the console.
|
||||
|
||||
|
||||
Example Code
|
||||
------------
|
||||
|
||||
The ``USDHookExample`` class in the example below implements the following functions:
|
||||
|
||||
- ``on_export()`` function to add custom data to the stage's root layer.
|
||||
- ``on_material_export()`` function to create a simple ``MaterialX`` shader on the given USD material.
|
||||
- ``on_import()`` function to create a text object to display the stage's custom layer data.
|
||||
- ``material_import_poll()`` returns ``True`` if the given USD material has an ``mtlx`` context.
|
||||
- ``on_material_import()`` function to convert a simple ``MaterialX`` shader with a ``base_color`` input.
|
||||
|
||||
"""
|
||||
|
||||
bl_info = {
|
||||
"name": "USD Hook Example",
|
||||
"blender": (4, 4, 0),
|
||||
}
|
||||
|
||||
import bpy
|
||||
import bpy.types
|
||||
import textwrap
|
||||
|
||||
# Make `pxr` module available, for running as `bpy` PIP package.
|
||||
bpy.utils.expose_bundled_modules()
|
||||
|
||||
import pxr.Gf as Gf
|
||||
import pxr.Sdf as Sdf
|
||||
import pxr.Usd as Usd
|
||||
import pxr.UsdShade as UsdShade
|
||||
|
||||
|
||||
class USDHookExample(bpy.types.USDHook):
|
||||
"""Example implementation of USD IO hooks"""
|
||||
bl_idname = "usd_hook_example"
|
||||
bl_label = "Example"
|
||||
|
||||
@staticmethod
|
||||
def on_export(export_context):
|
||||
""" Include the Blender filepath in the root layer custom data.
|
||||
"""
|
||||
|
||||
stage = export_context.get_stage()
|
||||
|
||||
if stage is None:
|
||||
return False
|
||||
data = bpy.data
|
||||
if data is None:
|
||||
return False
|
||||
|
||||
# Set the custom data.
|
||||
rootLayer = stage.GetRootLayer()
|
||||
customData = rootLayer.customLayerData
|
||||
customData["blenderFilepath"] = data.filepath
|
||||
rootLayer.customLayerData = customData
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def on_material_export(export_context, bl_material, usd_material):
|
||||
""" Create a simple MaterialX shader on the exported material.
|
||||
"""
|
||||
|
||||
stage = export_context.get_stage()
|
||||
|
||||
# Create a MaterialX standard surface shader
|
||||
mtl_path = usd_material.GetPrim().GetPath()
|
||||
shader = UsdShade.Shader.Define(stage, mtl_path.AppendPath("mtlxstandard_surface"))
|
||||
shader.CreateIdAttr("ND_standard_surface_surfaceshader")
|
||||
|
||||
# Connect the shader. MaterialX materials use "mtlx" renderContext
|
||||
usd_material.CreateSurfaceOutput("mtlx").ConnectToSource(shader.ConnectableAPI(), "out")
|
||||
|
||||
# Set the color to the Blender material's viewport display color.
|
||||
col = bl_material.diffuse_color
|
||||
shader.CreateInput("base_color", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(col[0], col[1], col[2]))
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def on_import(import_context):
|
||||
"""Inspect the imported stage & objects to set some custom data
|
||||
"""
|
||||
|
||||
###########################################################
|
||||
# Store some USD metadata on each imported data-block.
|
||||
###########################################################
|
||||
prim_map = import_context.get_prim_map()
|
||||
|
||||
# Store prim path as a string on each data-block created.
|
||||
for prim_path, data_blocks in prim_map.items():
|
||||
|
||||
# Type hints for prim map.
|
||||
prim_path: Sdf.Path
|
||||
data_blocks: list[bpy.types.ID]
|
||||
|
||||
# Loop over mapped data-blocks to store some metadata.
|
||||
for data_block in data_blocks:
|
||||
data_block["prim_path"] = str(prim_path)
|
||||
|
||||
###########################################################
|
||||
# Create a text object to display the stage's custom data.
|
||||
###########################################################
|
||||
stage = import_context.get_stage()
|
||||
|
||||
if stage is None:
|
||||
return False
|
||||
|
||||
# Get the custom data.
|
||||
rootLayer = stage.GetRootLayer()
|
||||
customData = rootLayer.customLayerData
|
||||
|
||||
# Create a text object to display the stage path
|
||||
# and custom data dictionary entries.
|
||||
|
||||
bpy.ops.object.text_add()
|
||||
ob = bpy.context.view_layer.objects.active
|
||||
|
||||
if (ob is None) or (ob.data is None):
|
||||
return False
|
||||
|
||||
ob.name = "layer_data"
|
||||
ob.data.name = "layer_data"
|
||||
|
||||
# The stage root path is the first line.
|
||||
text = rootLayer.realPath
|
||||
|
||||
# Append key/value strings, enforcing text wrapping.
|
||||
for item in customData.items():
|
||||
print(item)
|
||||
text += '\n'
|
||||
line = str(item[0]) + ': ' + str(item[1])
|
||||
text += textwrap.fill(line, width=80)
|
||||
|
||||
ob.data.body = text
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def material_import_poll(import_context, usd_material):
|
||||
"""
|
||||
Return True if the given USD material can be converted.
|
||||
Return False otherwise.
|
||||
"""
|
||||
# We can convert MaterialX.
|
||||
surf_output = usd_material.GetSurfaceOutput("mtlx")
|
||||
return bool(surf_output)
|
||||
|
||||
@staticmethod
|
||||
def on_material_import(import_context, bl_material, usd_material):
|
||||
"""
|
||||
Import a simple mtlx material. Just handle the base_color input
|
||||
of a ND_standard_surface_surfaceshader.
|
||||
"""
|
||||
|
||||
# We must confirm that we can handle this material.
|
||||
surf_output = usd_material.GetSurfaceOutput("mtlx")
|
||||
if not surf_output:
|
||||
return False
|
||||
|
||||
if not surf_output.HasConnectedSource():
|
||||
return False
|
||||
|
||||
# Get the connected surface output source.
|
||||
source = surf_output.GetConnectedSource()
|
||||
# Get the shader prim from the source
|
||||
shader = UsdShade.Shader(source[0])
|
||||
shader_id = shader.GetShaderId()
|
||||
if shader_id != "ND_standard_surface_surfaceshader":
|
||||
return False
|
||||
|
||||
color_attr = shader.GetInput("base_color")
|
||||
if color_attr is None:
|
||||
return False
|
||||
|
||||
# Create the node tree
|
||||
nodes = bl_material.node_tree.nodes
|
||||
output = nodes.new(type="ShaderNodeOutputMaterial")
|
||||
bsdf = nodes.new(type="ShaderNodeBsdfPrincipled")
|
||||
bsdf.location[0] -= 1.5 * bsdf.width
|
||||
bl_material.node_tree.links.new(output.inputs["Surface"], bsdf.outputs["BSDF"])
|
||||
bsdf_base_color_input = bsdf.inputs['Base Color']
|
||||
|
||||
# Try to set the default color value.
|
||||
# Get the authored default value
|
||||
color = color_attr.Get()
|
||||
|
||||
if color is None:
|
||||
return False
|
||||
|
||||
bsdf_base_color_input.default_value = (color[0], color[1], color[2], 1)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(USDHookExample)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(USDHookExample)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
**Save 3D Viewport to a PNG**
|
||||
|
||||
Capture the 3D viewport's main region from the current window
|
||||
and write it to a PNG file using :mod:`imbuf`.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import imbuf
|
||||
|
||||
window = bpy.context.window
|
||||
|
||||
# Locate the 3D viewport (if any).
|
||||
region = None
|
||||
for area in window.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
for region_iter in area.regions:
|
||||
if region_iter.type == 'WINDOW':
|
||||
region = region_iter
|
||||
break
|
||||
break
|
||||
|
||||
if region is not None:
|
||||
# The end coordinate is not inclusive, like Python slicing.
|
||||
region_rect = (
|
||||
(region.x, region.y),
|
||||
(region.x + region.width, region.y + region.height),
|
||||
)
|
||||
pixels = window.screenshot(region=region_rect)
|
||||
height, width = pixels.shape[0], pixels.shape[1]
|
||||
|
||||
ibuf = imbuf.new((width, height))
|
||||
ibuf.file_type = 'PNG'
|
||||
with ibuf.with_buffer(write=True) as buf:
|
||||
# The cast produces a zero-copy 1-D view of the same bytes.
|
||||
# Currently only 1-D copies are supported by Python.
|
||||
buf.cast('B')[:] = pixels.cast('B')
|
||||
|
||||
imbuf.write(ibuf, filepath="/tmp/viewport.png")
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
This method is used from the operators ``invoke`` callback
|
||||
which must then return ``{'RUNNING_MODAL'}``.
|
||||
|
||||
Accepting the file selector will run the operators ``execute`` callback.
|
||||
|
||||
The following properties are supported:
|
||||
|
||||
``filepath``: ``bpy.props.StringProperty(subtype='FILE_PATH')``
|
||||
Represents the absolute path to the file.
|
||||
``dirpath``: ``bpy.props.StringProperty(subtype='DIR_PATH')``
|
||||
Represents the absolute path to the directory.
|
||||
``filename``: ``bpy.props.StringProperty(subtype='FILE_NAME')``
|
||||
Represents the filename without the leading directory.
|
||||
``files``: ``bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement)``
|
||||
When present in the operator this collection includes all selected files.
|
||||
``filter_glob``: ``bpy.props.StringProperty(default="*.ext")``
|
||||
When present in the operator and it's not empty,
|
||||
it will be used as a file filter (example value: ``*.zip;*.py;*.exe``).
|
||||
``check_existing``: ``bpy.props.BoolProperty()``
|
||||
If this property is present and set to ``True``,
|
||||
the operator will warn if the provided file-path already exists
|
||||
by highlighting the filename input field in red.
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
After opening the file-browser the user may continue to use Blender,
|
||||
this means it is possible for the user to change the context in ways
|
||||
that would cause the operators ``poll`` function to fail.
|
||||
|
||||
Unless the operator reads all necessary data from the context before the file-selector is opened,
|
||||
it is recommended for operators to check the ``poll`` function from ``execute``
|
||||
to ensure the context is still valid.
|
||||
|
||||
Example from the body of an operators ``execute`` function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if self.options.is_invoke:
|
||||
# The context may have changed since invoking the file selector.
|
||||
if not self.poll(context):
|
||||
self.report({'ERROR'}, "Invalid context")
|
||||
return {'CANCELLED'}
|
||||
"""
|
||||
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Popup menus can be useful for creating menus without having to register menu classes.
|
||||
|
||||
Note that they will not block the scripts execution, so the caller can't wait for user input.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Hello World")
|
||||
|
||||
|
||||
bpy.context.window_manager.popup_menu(draw, title="Greeting", icon='INFO')
|
||||
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Only works for 'basic type' properties (bool, int and float)!
|
||||
Multi-dimensional arrays (like array of vectors) will be flattened into seq.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
mesh = bpy.context.object.data
|
||||
collection = mesh.vertices
|
||||
|
||||
# Allocate a flat list for the `co` property (X, Y, Z per vertex).
|
||||
coords = [0.0] * len(collection) * 3
|
||||
|
||||
# Fast access.
|
||||
collection.foreach_get("co", coords)
|
||||
|
||||
# Python equivalent (per-element iteration is much slower).
|
||||
for i, vert in enumerate(collection):
|
||||
coords[i * 3], coords[i * 3 + 1], coords[i * 3 + 2] = vert.co
|
||||
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Only works for 'basic type' properties (bool, int and float)!
|
||||
seq must be uni-dimensional, multi-dimensional arrays (like array of vectors) will be re-created from it.
|
||||
"""
|
||||
import bpy
|
||||
|
||||
mesh = bpy.context.object.data
|
||||
collection = mesh.vertices
|
||||
|
||||
# Flatten all Z coordinates to zero (X, Y, Z per vertex).
|
||||
coords = [0.0] * len(collection) * 3
|
||||
collection.foreach_get("co", coords)
|
||||
for i in range(2, len(coords), 3):
|
||||
coords[i] = 0.0
|
||||
|
||||
# Fast assignment.
|
||||
collection.foreach_set("co", coords)
|
||||
|
||||
# Python equivalent (per-element iteration is much slower).
|
||||
for i, vert in enumerate(collection):
|
||||
vert.co = (coords[i * 3], coords[i * 3 + 1], coords[i * 3 + 2])
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
.. note::
|
||||
|
||||
Properties defined at run-time store the values of the properties as custom-properties.
|
||||
|
||||
This method checks if the underlying data exists, causing the property to be considered *set*.
|
||||
|
||||
A common pattern for operators is to calculate a value for the properties
|
||||
that have not had their values explicitly set by the caller
|
||||
(where the caller could be a key-binding, menu-items or Python script for example).
|
||||
|
||||
In the case of executing operators multiple times, values are re-used from the previous execution.
|
||||
|
||||
For example: subdividing a mesh with a smooth value of 1.0 will keep using
|
||||
that value on subsequent calls to subdivision, unless the operator is called with
|
||||
that property set to a different value.
|
||||
|
||||
This behavior can be disabled using the ``SKIP_SAVE`` option when the property is declared (see: :mod:`bpy.props`).
|
||||
|
||||
The ``ghost`` argument allows detecting how a value from a previous execution is handled.
|
||||
|
||||
- When true: The property is considered unset even if the value from a previous call is used.
|
||||
- When false: The existence of any values causes ``is_property_set`` to return true.
|
||||
|
||||
While this argument should typically be omitted, there are times when
|
||||
it's important to know if a value is anything besides the default.
|
||||
|
||||
For example, the previous value may have been scaled by the scene's unit scale.
|
||||
In this case scaling the value multiple times would cause problems, so the ``ghost`` argument should be false.
|
||||
"""
|
||||
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
This is the most simple example of inserting a keyframe from Python.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
obj = bpy.context.object
|
||||
|
||||
# Set the keyframe at frame 1.
|
||||
obj.location = (3.0, 4.0, 10.0)
|
||||
obj.keyframe_insert(data_path="location", frame=1)
|
||||
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Note that when keying data paths which contain nested properties this must be
|
||||
done from the :class:`ID` subclass, in this case the :class:`Armature` rather
|
||||
than the bone.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
FloatProperty,
|
||||
PointerProperty,
|
||||
)
|
||||
|
||||
|
||||
# Define a nested property.
|
||||
class MyPropGroup(bpy.types.PropertyGroup):
|
||||
nested: FloatProperty(name="Nested", default=0.0)
|
||||
|
||||
|
||||
# Register it so its available for all bones.
|
||||
bpy.utils.register_class(MyPropGroup)
|
||||
bpy.types.Bone.my_prop = PointerProperty(
|
||||
type=MyPropGroup,
|
||||
name="MyProp",
|
||||
)
|
||||
|
||||
# Get a bone.
|
||||
obj = bpy.data.objects["Armature"]
|
||||
arm = obj.data
|
||||
|
||||
# Set the keyframe at frame 1.
|
||||
arm.bones["Bone"].my_prop.nested = 10
|
||||
arm.keyframe_insert(
|
||||
data_path='bones["Bone"].my_prop.nested',
|
||||
frame=1,
|
||||
group="Nested Group",
|
||||
)
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
**Custom Commands**
|
||||
|
||||
Registering commands makes it possible to conveniently expose command line
|
||||
functionality via commands passed to (``-c`` / ``--command``).
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def sysinfo_print():
|
||||
"""
|
||||
Report basic system information.
|
||||
"""
|
||||
|
||||
import pprint
|
||||
import platform
|
||||
import textwrap
|
||||
|
||||
width = 80
|
||||
indent = 2
|
||||
|
||||
print("Blender {:s}".format(bpy.app.version_string))
|
||||
print("Running on: {:s}-{:s}".format(platform.platform(), platform.machine()))
|
||||
print("Processors: {!r}".format(os.cpu_count()))
|
||||
print()
|
||||
|
||||
# Dump `bpy.app`.
|
||||
for attr in dir(bpy.app):
|
||||
if attr.startswith("_"):
|
||||
continue
|
||||
# Overly verbose.
|
||||
if attr in {"handlers", "build_cflags", "build_cxxflags"}:
|
||||
continue
|
||||
|
||||
value = getattr(bpy.app, attr)
|
||||
if attr.startswith("build_"):
|
||||
pass
|
||||
elif isinstance(value, tuple):
|
||||
pass
|
||||
else:
|
||||
# Otherwise ignore.
|
||||
continue
|
||||
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode("utf-8", errors="ignore")
|
||||
|
||||
if isinstance(value, str):
|
||||
pass
|
||||
elif isinstance(value, tuple) and hasattr(value, "__dir__"):
|
||||
value = {
|
||||
attr_sub: value_sub
|
||||
for attr_sub in dir(value)
|
||||
# Exclude built-ins.
|
||||
if not attr_sub.startswith(("_", "n_"))
|
||||
# Exclude methods.
|
||||
if not callable(value_sub := getattr(value, attr_sub))
|
||||
}
|
||||
value = pprint.pformat(value, indent=0, width=width)
|
||||
else:
|
||||
value = pprint.pformat(value, indent=0, width=width)
|
||||
|
||||
print("{:s}:\n{:s}\n".format(attr, textwrap.indent(value, " " * indent)))
|
||||
|
||||
|
||||
def sysinfo_command(argv):
|
||||
if argv and argv[0] == "--help":
|
||||
print("Print system information & exit!")
|
||||
return 0
|
||||
|
||||
sysinfo_print()
|
||||
return 0
|
||||
|
||||
|
||||
cli_commands = []
|
||||
|
||||
|
||||
def register():
|
||||
cli_commands.append(bpy.utils.register_cli_command("sysinfo", sysinfo_command))
|
||||
|
||||
|
||||
def unregister():
|
||||
for cmd in cli_commands:
|
||||
bpy.utils.unregister_cli_command(cmd)
|
||||
cli_commands.clear()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
**Using Python Argument Parsing**
|
||||
|
||||
This example shows how the Python ``argparse`` module can be used with a custom command.
|
||||
|
||||
Using ``argparse`` is generally recommended as it has many useful utilities and
|
||||
generates a ``--help`` message for your command.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def argparse_create():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=os.path.basename(sys.argv[0]) + " --command keyconfig_export",
|
||||
description="Write key-configuration to a file.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-o", "--output",
|
||||
dest="output",
|
||||
metavar='OUTPUT',
|
||||
type=str,
|
||||
help="The path to write the keymap to.",
|
||||
required=True,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-a", "--all",
|
||||
dest="all",
|
||||
action="store_true",
|
||||
help="Write all key-maps (not only customized key-maps).",
|
||||
required=False,
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def keyconfig_export(argv):
|
||||
parser = argparse_create()
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
# Ensure the key configuration is loaded in background mode.
|
||||
bpy.utils.keyconfig_init()
|
||||
|
||||
bpy.ops.preferences.keyconfig_export(
|
||||
filepath=args.output,
|
||||
all=args.all,
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
cli_commands = []
|
||||
|
||||
|
||||
def register():
|
||||
cli_commands.append(bpy.utils.register_cli_command("keyconfig_export", keyconfig_export))
|
||||
|
||||
|
||||
def unregister():
|
||||
for cmd in cli_commands:
|
||||
bpy.utils.unregister_cli_command(cmd)
|
||||
cli_commands.clear()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
187
blender-5.2.0/doc/python_api/examples/gpu.1.py
Normal file
187
blender-5.2.0/doc/python_api/examples/gpu.1.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Geometry Batches
|
||||
++++++++++++++++
|
||||
|
||||
Geometry is drawn in batches.
|
||||
A batch contains the necessary data to perform the drawing.
|
||||
That includes an obligatory *Vertex Buffer* and an optional *Index Buffer*,
|
||||
each of which is described in more detail in the following sections.
|
||||
A batch also defines a draw type.
|
||||
Typical draw types are ``POINTS``, ``LINES`` and ``TRIS``.
|
||||
The draw type determines how the data will be interpreted and drawn.
|
||||
|
||||
Vertex Buffers
|
||||
++++++++++++++
|
||||
|
||||
A *Vertex Buffer Object* (VBO) (:class:`gpu.types.GPUVertBuf`)
|
||||
is an array that contains the vertex attributes needed for drawing using a specific shader.
|
||||
Typical vertex attributes are *location*, *normal*, *color*, and *uv*.
|
||||
Every vertex buffer has a *Vertex Format* (:class:`gpu.types.GPUVertFormat`)
|
||||
and a length corresponding to the number of vertices in the buffer.
|
||||
A vertex format describes the attributes stored per vertex and their types.
|
||||
|
||||
The following code demonstrates the creation of a vertex buffer that contains 6 vertices.
|
||||
For each vertex 2 attributes will be stored: The position and the normal.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import gpu
|
||||
vertex_positions = [(0, 0, 0), ...]
|
||||
vertex_normals = [(0, 0, 1), ...]
|
||||
|
||||
fmt = gpu.types.GPUVertFormat()
|
||||
fmt.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
|
||||
fmt.attr_add(id="normal", comp_type='F32', len=3, fetch_mode='FLOAT')
|
||||
|
||||
vbo = gpu.types.GPUVertBuf(len=6, format=fmt)
|
||||
vbo.attr_fill(id="pos", data=vertex_positions)
|
||||
vbo.attr_fill(id="normal", data=vertex_normals)
|
||||
|
||||
This vertex buffer could be used to draw 6 points, 3 separate lines, 5 consecutive lines, 2 separate triangles, ...
|
||||
E.g. in the case of lines, each two consecutive vertices define a line.
|
||||
The type that will actually be drawn is determined when the batch is created later.
|
||||
|
||||
Index Buffers
|
||||
+++++++++++++
|
||||
|
||||
Often triangles and lines share one or more vertices.
|
||||
With only a vertex buffer one would have to store all attributes for the these vertices multiple times.
|
||||
This is very inefficient because in a connected triangle mesh every vertex is used 6 times on average.
|
||||
A more efficient approach would be to use an *Index Buffer* (IBO) (:class:`gpu.types.GPUIndexBuf`),
|
||||
sometimes referred to as *Element Buffer*.
|
||||
An *Index Buffer* is an array that references vertices based on their index in the vertex buffer.
|
||||
|
||||
For instance, to draw a rectangle composed of two triangles, one could use an index buffer.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
positions = (
|
||||
(-1, 1), (1, 1),
|
||||
(-1, -1), (1, -1))
|
||||
|
||||
indices = ((0, 1, 2), (2, 1, 3))
|
||||
|
||||
ibo = gpu.types.GPUIndexBuf(type='TRIS', seq=indices)
|
||||
|
||||
Here the first tuple in ``indices`` describes which vertices should be used for the first triangle
|
||||
(same for the second tuple).
|
||||
Note how the diagonal vertices 1 and 2 are shared between both triangles.
|
||||
|
||||
Shaders
|
||||
+++++++
|
||||
|
||||
A shader is a program that runs on the GPU (written in GLSL in our case).
|
||||
There are multiple types of shaders.
|
||||
The most important ones are *Vertex Shaders* and *Fragment Shaders*.
|
||||
Typically multiple shaders are linked together into a *Program*.
|
||||
However, in the Blender Python API the term *Shader* refers to an OpenGL Program.
|
||||
Every :class:`gpu.types.GPUShader` consists of a vertex shader, a fragment shader and an optional geometry shader.
|
||||
For common drawing tasks there are some built-in shaders accessible from :class:`gpu.shader.from_builtin`
|
||||
with an identifier such as ``UNIFORM_COLOR`` or ``FLAT_COLOR``. There are specific builtin shaders for
|
||||
drawing triangles, lines and points.
|
||||
|
||||
Every shader defines a set of attributes and uniforms that have to be set in order to use the shader.
|
||||
Attributes are properties that are set using a vertex buffer and can be different for individual vertices.
|
||||
Uniforms are properties that are constant per draw call.
|
||||
They can be set using the ``shader.uniform_*`` functions after the shader has been bound.
|
||||
|
||||
.. note::
|
||||
|
||||
It is important to note that GLSL sources are reinterpreted to MSL (Metal Shading Language)
|
||||
on Apple operating systems.
|
||||
This uses a small compatibility layer that does not cover the whole GLSL language specification.
|
||||
Here is a list of differences to keep in mind when targeting compatibility with Apple platforms:
|
||||
|
||||
- The only matrix constructors available are:
|
||||
|
||||
- diagonal scalar (example: ``mat2(1)``)
|
||||
- all scalars (example: ``mat2(1, 0, 0, 1)``)
|
||||
- column vector (example: ``mat2(vec2(1,0), vec2(0,1))``)
|
||||
- reshape constructors work only for square matrices (example: ``mat3(mat4(1))``)
|
||||
|
||||
- ``vertex``, ``fragment`` and ``kernel`` are reserved keywords.
|
||||
- all types and keywords defined by the
|
||||
`MSL specification <https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf>`__
|
||||
are reserved keywords and should not be used.
|
||||
|
||||
|
||||
Batch Creation
|
||||
++++++++++++++
|
||||
|
||||
Batches can be created by first manually creating VBOs and IBOs.
|
||||
However, it is recommended to use the :class:`gpu_extras.batch.batch_for_shader` function.
|
||||
It makes sure that all the vertex attributes necessary for a specific shader are provided.
|
||||
Consequently, the shader has to be passed to the function as well.
|
||||
When using this function one rarely has to care about the vertex format, VBOs and IBOs created in the background.
|
||||
This is still something one should know when drawing stuff though.
|
||||
|
||||
Since batches can be drawn multiple times, they should be cached and reused whenever possible.
|
||||
|
||||
Offscreen Rendering
|
||||
+++++++++++++++++++
|
||||
|
||||
What one can see on the screen after rendering is called the *Front Buffer*.
|
||||
When draw calls are issued, batches are drawn on a *Back Buffer* that will only be displayed
|
||||
when all drawing is done and the current back buffer will become the new front buffer.
|
||||
Sometimes, one might want to draw the batches into a distinct buffer that could be used as
|
||||
texture to display on another object or to be saved as image on disk.
|
||||
This is called Offscreen Rendering.
|
||||
In Blender Offscreen Rendering is done using the :class:`gpu.types.GPUOffScreen` type.
|
||||
|
||||
.. warning::
|
||||
|
||||
:class:`gpu.types.GPUOffScreen` objects are bound to the OpenGL context they have been created in.
|
||||
This means that once Blender discards this context (i.e. the window is closed),
|
||||
the offscreen instance will be freed.
|
||||
|
||||
Examples
|
||||
++++++++
|
||||
|
||||
To try these examples, just copy them into Blender's text editor and execute them.
|
||||
To keep the examples relatively small, they just register a draw function that can't easily be removed anymore.
|
||||
Blender has to be restarted in order to delete the draw handlers.
|
||||
|
||||
3D Points with Single Color
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
coords = [(1, 1, 1), (-2, 0, 0), (-2, -1, 3), (0, 1, 1)]
|
||||
shader = gpu.shader.from_builtin('POINT_UNIFORM_COLOR')
|
||||
batch = batch_for_shader(shader, 'POINTS', {"pos": coords})
|
||||
|
||||
|
||||
def draw():
|
||||
shader.uniform_float("color", (1, 1, 0, 1))
|
||||
gpu.state.point_size_set(4.5)
|
||||
batch.draw(shader)
|
||||
|
||||
|
||||
bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW')
|
||||
|
||||
|
||||
"""
|
||||
|
||||
3D Lines with Single Color
|
||||
--------------------------
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
coords = [(1, 1, 1), (-2, 0, 0), (-2, -1, 3), (0, 1, 1)]
|
||||
shader = gpu.shader.from_builtin('POLYLINE_UNIFORM_COLOR')
|
||||
batch = batch_for_shader(shader, 'LINES', {"pos": coords})
|
||||
|
||||
|
||||
def draw():
|
||||
shader.uniform_float("viewportSize", gpu.state.viewport_get()[2:])
|
||||
shader.uniform_float("lineWidth", 4.5)
|
||||
shader.uniform_float("color", (1, 1, 0, 1))
|
||||
batch.draw(shader)
|
||||
|
||||
|
||||
bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW')
|
||||
67
blender-5.2.0/doc/python_api/examples/gpu.10.py
Normal file
67
blender-5.2.0/doc/python_api/examples/gpu.10.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
Custom Shader for dotted 3D Line
|
||||
--------------------------------
|
||||
|
||||
In this example the arc length (distance to the first point on the line) is calculated in every vertex.
|
||||
Between the vertex and fragment shader that value is automatically interpolated
|
||||
for all points that will be visible on the screen.
|
||||
In the fragment shader the ``sin`` of the arc length is calculated.
|
||||
Based on the result a decision is made on whether the fragment should be drawn or not.
|
||||
"""
|
||||
import bpy
|
||||
import gpu
|
||||
from random import random
|
||||
from mathutils import Vector
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||
vert_out.smooth('FLOAT', "v_ArcLength")
|
||||
|
||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
shader_info.push_constant('MAT4', "u_ViewProjectionMatrix")
|
||||
shader_info.push_constant('FLOAT', "u_Scale")
|
||||
shader_info.vertex_in(0, 'VEC3', "position")
|
||||
shader_info.vertex_in(1, 'FLOAT', "arcLength")
|
||||
shader_info.vertex_out(vert_out)
|
||||
shader_info.fragment_out(0, 'VEC4', "FragColor")
|
||||
|
||||
shader_info.vertex_source(
|
||||
"void main()"
|
||||
"{"
|
||||
" v_ArcLength = arcLength;"
|
||||
" gl_Position = u_ViewProjectionMatrix * vec4(position, 1.0f);"
|
||||
"}"
|
||||
)
|
||||
|
||||
shader_info.fragment_source(
|
||||
"void main()"
|
||||
"{"
|
||||
" if (step(sin(v_ArcLength * u_Scale), 0.5) == 1) discard;"
|
||||
" FragColor = vec4(1.0);"
|
||||
"}"
|
||||
)
|
||||
|
||||
shader = gpu.shader.create_from_info(shader_info)
|
||||
del vert_out
|
||||
del shader_info
|
||||
|
||||
coords = [Vector((random(), random(), random())) * 5 for _ in range(5)]
|
||||
|
||||
arc_lengths = [0.0]
|
||||
for a, b in zip(coords[:-1], coords[1:]):
|
||||
arc_lengths.append(arc_lengths[-1] + (a - b).length)
|
||||
|
||||
batch = batch_for_shader(
|
||||
shader, 'LINE_STRIP',
|
||||
{"position": coords, "arcLength": arc_lengths},
|
||||
)
|
||||
|
||||
|
||||
def draw():
|
||||
matrix = bpy.context.region_data.perspective_matrix
|
||||
shader.uniform_float("u_ViewProjectionMatrix", matrix)
|
||||
shader.uniform_float("u_Scale", 10)
|
||||
batch.draw(shader)
|
||||
|
||||
|
||||
bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW')
|
||||
96
blender-5.2.0/doc/python_api/examples/gpu.11.py
Normal file
96
blender-5.2.0/doc/python_api/examples/gpu.11.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Custom compute shader (using image store) and vertex/fragment shader
|
||||
--------------------------------------------------------------------
|
||||
|
||||
This is an example of how to use a custom compute shader
|
||||
to write to a texture and then use that texture in a vertex/fragment shader.
|
||||
The expected result is a 2x2 plane (size of the default cube),
|
||||
which changes color from a green-black gradient to a green-red gradient,
|
||||
based on current time.
|
||||
"""
|
||||
import bpy
|
||||
import gpu
|
||||
from mathutils import Matrix
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
import time
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
size = 128
|
||||
texture = gpu.types.GPUTexture((size, size), format='RGBA32F')
|
||||
|
||||
# Create the compute shader to write to the texture.
|
||||
compute_shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
compute_shader_info.image(0, 'RGBA32F', "FLOAT_2D", "img_output", qualifiers={"WRITE"})
|
||||
compute_shader_info.compute_source('''
|
||||
void main()
|
||||
{
|
||||
vec4 pixel = vec4(
|
||||
sin(time / 1.0),
|
||||
gl_GlobalInvocationID.y/128.0,
|
||||
0.0,
|
||||
1.0
|
||||
);
|
||||
imageStore(img_output, ivec2(gl_GlobalInvocationID.xy), pixel);
|
||||
}''')
|
||||
compute_shader_info.push_constant('FLOAT', "time")
|
||||
compute_shader_info.local_group_size(1, 1)
|
||||
compute_shader = gpu.shader.create_from_info(compute_shader_info)
|
||||
|
||||
# Create the shader to draw the texture.
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||
vert_out.smooth('VEC2', "uvInterp")
|
||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
shader_info.push_constant('MAT4', "viewProjectionMatrix")
|
||||
shader_info.push_constant('MAT4', "modelMatrix")
|
||||
shader_info.sampler(0, 'FLOAT_2D', "img_input")
|
||||
shader_info.vertex_in(0, 'VEC2', "position")
|
||||
shader_info.vertex_in(1, 'VEC2', "uv")
|
||||
shader_info.vertex_out(vert_out)
|
||||
shader_info.fragment_out(0, 'VEC4', "FragColor")
|
||||
|
||||
shader_info.vertex_source(
|
||||
"void main()"
|
||||
"{"
|
||||
" uvInterp = uv;"
|
||||
" gl_Position = viewProjectionMatrix * modelMatrix * vec4(position, 0.0, 1.0);"
|
||||
"}"
|
||||
)
|
||||
|
||||
shader_info.fragment_source(
|
||||
"void main()"
|
||||
"{"
|
||||
" FragColor = texture(img_input, uvInterp);"
|
||||
"}"
|
||||
)
|
||||
|
||||
shader = gpu.shader.create_from_info(shader_info)
|
||||
|
||||
batch = batch_for_shader(
|
||||
shader, 'TRI_STRIP',
|
||||
{
|
||||
"position": ((-1, -1), (1, -1), (-1, 1), (1, 1)),
|
||||
"uv": ((0, 0), (1, 0), (0, 1), (1, 1)),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def draw():
|
||||
shader.uniform_float("modelMatrix", Matrix.Translation((0, 0, 0)) @ Matrix.Scale(1, 4))
|
||||
shader.uniform_float("viewProjectionMatrix", bpy.context.region_data.perspective_matrix)
|
||||
shader.uniform_sampler("img_input", texture)
|
||||
batch.draw(shader)
|
||||
compute_shader.image('img_output', texture)
|
||||
compute_shader.uniform_float("time", time.time() - start_time)
|
||||
gpu.compute.dispatch(compute_shader, 128, 128, 1)
|
||||
|
||||
|
||||
def drawTimer():
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
area.tag_redraw()
|
||||
return 1.0 / 60.0
|
||||
|
||||
|
||||
bpy.app.timers.register(drawTimer)
|
||||
bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW')
|
||||
50
blender-5.2.0/doc/python_api/examples/gpu.2.py
Normal file
50
blender-5.2.0/doc/python_api/examples/gpu.2.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Triangle with Custom Shader
|
||||
---------------------------
|
||||
"""
|
||||
import bpy
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||
vert_out.smooth('VEC3', "pos")
|
||||
|
||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
shader_info.push_constant('MAT4', "viewProjectionMatrix")
|
||||
shader_info.push_constant('FLOAT', "brightness")
|
||||
shader_info.vertex_in(0, 'VEC3', "position")
|
||||
shader_info.vertex_out(vert_out)
|
||||
shader_info.fragment_out(0, 'VEC4', "FragColor")
|
||||
|
||||
shader_info.vertex_source(
|
||||
"void main()"
|
||||
"{"
|
||||
" pos = position;"
|
||||
" gl_Position = viewProjectionMatrix * vec4(position, 1.0f);"
|
||||
"}"
|
||||
)
|
||||
|
||||
shader_info.fragment_source(
|
||||
"void main()"
|
||||
"{"
|
||||
" FragColor = vec4(pos * brightness, 1.0);"
|
||||
"}"
|
||||
)
|
||||
|
||||
shader = gpu.shader.create_from_info(shader_info)
|
||||
del vert_out
|
||||
del shader_info
|
||||
|
||||
coords = [(1, 1, 1), (2, 0, 0), (-2, -1, 3)]
|
||||
batch = batch_for_shader(shader, 'TRIS', {"position": coords})
|
||||
|
||||
|
||||
def draw():
|
||||
matrix = bpy.context.region_data.perspective_matrix
|
||||
shader.uniform_float("viewProjectionMatrix", matrix)
|
||||
shader.uniform_float("brightness", 0.5)
|
||||
batch.draw(shader)
|
||||
|
||||
|
||||
bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW')
|
||||
31
blender-5.2.0/doc/python_api/examples/gpu.3.py
Normal file
31
blender-5.2.0/doc/python_api/examples/gpu.3.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Wireframe Cube using Index Buffer
|
||||
---------------------------------
|
||||
"""
|
||||
import bpy
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
coords = (
|
||||
(-1, -1, -1), (+1, -1, -1),
|
||||
(-1, +1, -1), (+1, +1, -1),
|
||||
(-1, -1, +1), (+1, -1, +1),
|
||||
(-1, +1, +1), (+1, +1, +1))
|
||||
|
||||
indices = (
|
||||
(0, 1), (0, 2), (1, 3), (2, 3),
|
||||
(4, 5), (4, 6), (5, 7), (6, 7),
|
||||
(0, 4), (1, 5), (2, 6), (3, 7))
|
||||
|
||||
shader = gpu.shader.from_builtin('POLYLINE_UNIFORM_COLOR')
|
||||
batch = batch_for_shader(shader, 'LINES', {"pos": coords}, indices=indices)
|
||||
|
||||
|
||||
def draw():
|
||||
shader.uniform_float("viewportSize", gpu.state.viewport_get()[2:])
|
||||
shader.uniform_float("lineWidth", 4.5)
|
||||
shader.uniform_float("color", (1, 0, 0, 1))
|
||||
batch.draw(shader)
|
||||
|
||||
|
||||
bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW')
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user