1# ##### BEGIN GPL LICENSE BLOCK ##### 2# 3# This program is free software; you can redistribute it and/or 4# modify it under the terms of the GNU General Public License 5# as published by the Free Software Foundation; either version 2 6# of the License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software Foundation, 15# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16# 17# ##### END GPL LICENSE BLOCK ##### 18 19# <pep8-80 compliant> 20 21from _bpy import types as bpy_types 22import _bpy 23 24StructRNA = bpy_types.bpy_struct 25StructMetaPropGroup = bpy_types.bpy_struct_meta_idprop 26# StructRNA = bpy_types.Struct 27 28# Note that methods extended in C are defined in: 'bpy_rna_types_capi.c' 29 30 31class Context(StructRNA): 32 __slots__ = () 33 34 def copy(self): 35 from types import BuiltinMethodType 36 new_context = {} 37 generic_attrs = ( 38 *StructRNA.__dict__.keys(), 39 "bl_rna", "rna_type", "copy", 40 ) 41 for attr in dir(self): 42 if not (attr.startswith("_") or attr in generic_attrs): 43 value = getattr(self, attr) 44 if type(value) != BuiltinMethodType: 45 new_context[attr] = value 46 47 return new_context 48 49 50class Library(bpy_types.ID): 51 __slots__ = () 52 53 @property 54 def users_id(self): 55 """ID data blocks which use this library""" 56 import bpy 57 58 # See: readblenentry.c, IDTYPE_FLAGS_ISLINKABLE, 59 # we could make this an attribute in rna. 60 attr_links = ( 61 "actions", "armatures", "brushes", "cameras", 62 "curves", "grease_pencils", "collections", "images", 63 "lights", "lattices", "materials", "metaballs", 64 "meshes", "node_groups", "objects", "scenes", 65 "sounds", "speakers", "textures", "texts", 66 "fonts", "worlds", 67 ) 68 69 return tuple(id_block 70 for attr in attr_links 71 for id_block in getattr(bpy.data, attr) 72 if id_block.library == self) 73 74 75class Texture(bpy_types.ID): 76 __slots__ = () 77 78 @property 79 def users_material(self): 80 """Materials that use this texture""" 81 import bpy 82 return tuple(mat for mat in bpy.data.materials 83 if self in [slot.texture 84 for slot in mat.texture_slots 85 if slot] 86 ) 87 88 @property 89 def users_object_modifier(self): 90 """Object modifiers that use this texture""" 91 import bpy 92 return tuple( 93 obj for obj in bpy.data.objects if 94 self in [ 95 mod.texture 96 for mod in obj.modifiers 97 if mod.type == 'DISPLACE'] 98 ) 99 100 101class Collection(bpy_types.ID): 102 __slots__ = () 103 104 @property 105 def users_dupli_group(self): 106 """The collection instance objects this collection is used in""" 107 import bpy 108 return tuple(obj for obj in bpy.data.objects 109 if self == obj.instance_collection) 110 111 112class Object(bpy_types.ID): 113 __slots__ = () 114 115 @property 116 def children(self): 117 """All the children of this object. 118 119 .. note:: Takes ``O(len(bpy.data.objects))`` time.""" 120 import bpy 121 return tuple(child for child in bpy.data.objects 122 if child.parent == self) 123 124 @property 125 def users_collection(self): 126 """ 127 The collections this object is in. 128 129 .. note:: Takes ``O(len(bpy.data.collections) + len(bpy.data.scenes))`` time.""" 130 import bpy 131 return ( 132 tuple( 133 collection for collection in bpy.data.collections 134 if self in collection.objects[:] 135 ) + tuple( 136 scene.collection for scene in bpy.data.scenes 137 if self in scene.collection.objects[:] 138 ) 139 ) 140 141 @property 142 def users_scene(self): 143 """The scenes this object is in. 144 145 .. note:: Takes ``O(len(bpy.data.scenes) * len(bpy.data.objects))`` time.""" 146 import bpy 147 return tuple(scene for scene in bpy.data.scenes 148 if self in scene.objects[:]) 149 150 151class WindowManager(bpy_types.ID): 152 __slots__ = () 153 154 def popup_menu(self, draw_func, title="", icon='NONE'): 155 import bpy 156 popup = self.popmenu_begin__internal(title, icon=icon) 157 158 try: 159 draw_func(popup, bpy.context) 160 finally: 161 self.popmenu_end__internal(popup) 162 163 def popover( 164 self, draw_func, *, 165 ui_units_x=0, 166 keymap=None, 167 from_active_button=False, 168 ): 169 import bpy 170 popup = self.popover_begin__internal( 171 ui_units_x=ui_units_x, 172 from_active_button=from_active_button, 173 ) 174 175 try: 176 draw_func(popup, bpy.context) 177 finally: 178 self.popover_end__internal(popup, keymap=keymap) 179 180 def popup_menu_pie(self, event, draw_func, title="", icon='NONE'): 181 import bpy 182 pie = self.piemenu_begin__internal(title, icon=icon, event=event) 183 184 if pie: 185 try: 186 draw_func(pie, bpy.context) 187 finally: 188 self.piemenu_end__internal(pie) 189 190 191class WorkSpace(bpy_types.ID): 192 __slots__ = () 193 194 def status_text_set(self, text): 195 """ 196 Set the status text or None to clear, 197 When text is a function, this will be called with the (header, context) arguments. 198 """ 199 from bl_ui.space_statusbar import STATUSBAR_HT_header 200 draw_fn = getattr(STATUSBAR_HT_header, "_draw_orig", None) 201 if draw_fn is None: 202 draw_fn = STATUSBAR_HT_header._draw_orig = STATUSBAR_HT_header.draw 203 204 if not (text is None or isinstance(text, str)): 205 draw_fn = text 206 text = None 207 208 self.status_text_set_internal(text) 209 STATUSBAR_HT_header.draw = draw_fn 210 211 212class _GenericBone: 213 """ 214 functions for bones, common between Armature/Pose/Edit bones. 215 internal subclassing use only. 216 """ 217 __slots__ = () 218 219 def translate(self, vec): 220 """Utility function to add *vec* to the head and tail of this bone""" 221 self.head += vec 222 self.tail += vec 223 224 def parent_index(self, parent_test): 225 """ 226 The same as 'bone in other_bone.parent_recursive' 227 but saved generating a list. 228 """ 229 # use the name so different types can be tested. 230 name = parent_test.name 231 232 parent = self.parent 233 i = 1 234 while parent: 235 if parent.name == name: 236 return i 237 parent = parent.parent 238 i += 1 239 240 return 0 241 242 @property 243 def x_axis(self): 244 """ Vector pointing down the x-axis of the bone. 245 """ 246 from mathutils import Vector 247 return self.matrix.to_3x3() @ Vector((1.0, 0.0, 0.0)) 248 249 @property 250 def y_axis(self): 251 """ Vector pointing down the y-axis of the bone. 252 """ 253 from mathutils import Vector 254 return self.matrix.to_3x3() @ Vector((0.0, 1.0, 0.0)) 255 256 @property 257 def z_axis(self): 258 """ Vector pointing down the z-axis of the bone. 259 """ 260 from mathutils import Vector 261 return self.matrix.to_3x3() @ Vector((0.0, 0.0, 1.0)) 262 263 @property 264 def basename(self): 265 """The name of this bone before any '.' character""" 266 # return self.name.rsplit(".", 1)[0] 267 return self.name.split(".")[0] 268 269 @property 270 def parent_recursive(self): 271 """A list of parents, starting with the immediate parent""" 272 parent_list = [] 273 parent = self.parent 274 275 while parent: 276 if parent: 277 parent_list.append(parent) 278 279 parent = parent.parent 280 281 return parent_list 282 283 @property 284 def center(self): 285 """The midpoint between the head and the tail.""" 286 return (self.head + self.tail) * 0.5 287 288 @property 289 def vector(self): 290 """ 291 The direction this bone is pointing. 292 Utility function for (tail - head) 293 """ 294 return (self.tail - self.head) 295 296 @property 297 def children(self): 298 """A list of all the bones children. 299 300 .. note:: Takes ``O(len(bones))`` time.""" 301 return [child for child in self._other_bones if child.parent == self] 302 303 @property 304 def children_recursive(self): 305 """A list of all children from this bone. 306 307 .. note:: Takes ``O(len(bones)**2)`` time.""" 308 bones_children = [] 309 for bone in self._other_bones: 310 index = bone.parent_index(self) 311 if index: 312 bones_children.append((index, bone)) 313 314 # sort by distance to parent 315 bones_children.sort(key=lambda bone_pair: bone_pair[0]) 316 return [bone for index, bone in bones_children] 317 318 @property 319 def children_recursive_basename(self): 320 """ 321 Returns a chain of children with the same base name as this bone. 322 Only direct chains are supported, forks caused by multiple children 323 with matching base names will terminate the function 324 and not be returned. 325 326 .. note:: Takes ``O(len(bones)**2)`` time. 327 """ 328 basename = self.basename 329 chain = [] 330 331 child = self 332 while True: 333 children = child.children 334 children_basename = [] 335 336 for child in children: 337 if basename == child.basename: 338 children_basename.append(child) 339 340 if len(children_basename) == 1: 341 child = children_basename[0] 342 chain.append(child) 343 else: 344 if children_basename: 345 print("multiple basenames found, " 346 "this is probably not what you want!", 347 self.name, children_basename) 348 349 break 350 351 return chain 352 353 @property 354 def _other_bones(self): 355 id_data = self.id_data 356 357 # `id_data` is an 'Object' for `PosePone`, otherwise it's an `Armature`. 358 if isinstance(self, PoseBone): 359 return id_data.pose.bones 360 if isinstance(self, EditBone): 361 return id_data.edit_bones 362 if isinstance(self, Bone): 363 return id_data.bones 364 raise RuntimeError("Invalid type %r" % self) 365 366 367class PoseBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup): 368 __slots__ = () 369 370 @property 371 def children(self): 372 obj = self.id_data 373 pbones = obj.pose.bones 374 self_bone = self.bone 375 376 return tuple(pbones[bone.name] for bone in obj.data.bones 377 if bone.parent == self_bone) 378 379 380class Bone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup): 381 __slots__ = () 382 383 384class EditBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup): 385 __slots__ = () 386 387 def align_orientation(self, other): 388 """ 389 Align this bone to another by moving its tail and settings its roll 390 the length of the other bone is not used. 391 """ 392 vec = other.vector.normalized() * self.length 393 self.tail = self.head + vec 394 self.roll = other.roll 395 396 def transform(self, matrix, scale=True, roll=True): 397 """ 398 Transform the the bones head, tail, roll and envelope 399 (when the matrix has a scale component). 400 401 :arg matrix: 3x3 or 4x4 transformation matrix. 402 :type matrix: :class:`mathutils.Matrix` 403 :arg scale: Scale the bone envelope by the matrix. 404 :type scale: bool 405 :arg roll: 406 407 Correct the roll to point in the same relative 408 direction to the head and tail. 409 410 :type roll: bool 411 """ 412 from mathutils import Vector 413 z_vec = self.matrix.to_3x3() @ Vector((0.0, 0.0, 1.0)) 414 self.tail = matrix @ self.tail 415 self.head = matrix @ self.head 416 417 if scale: 418 scalar = matrix.median_scale 419 self.head_radius *= scalar 420 self.tail_radius *= scalar 421 422 if roll: 423 self.align_roll(matrix @ z_vec) 424 425 426def ord_ind(i1, i2): 427 if i1 < i2: 428 return i1, i2 429 return i2, i1 430 431 432class Mesh(bpy_types.ID): 433 __slots__ = () 434 435 def from_pydata(self, vertices, edges, faces): 436 """ 437 Make a mesh from a list of vertices/edges/faces 438 Until we have a nicer way to make geometry, use this. 439 440 :arg vertices: 441 442 float triplets each representing (X, Y, Z) 443 eg: [(0.0, 1.0, 0.5), ...]. 444 445 :type vertices: iterable object 446 :arg edges: 447 448 int pairs, each pair contains two indices to the 449 *vertices* argument. eg: [(1, 2), ...] 450 451 When an empty iterable is passed in, the edges are inferred from the polygons. 452 453 :type edges: iterable object 454 :arg faces: 455 456 iterator of faces, each faces contains three or more indices to 457 the *vertices* argument. eg: [(5, 6, 8, 9), (1, 2, 3), ...] 458 459 :type faces: iterable object 460 461 .. warning:: 462 463 Invalid mesh data 464 *(out of range indices, edges with matching indices, 465 2 sided faces... etc)* are **not** prevented. 466 If the data used for mesh creation isn't known to be valid, 467 run :class:`Mesh.validate` after this function. 468 """ 469 from itertools import chain, islice, accumulate 470 471 face_lengths = tuple(map(len, faces)) 472 473 self.vertices.add(len(vertices)) 474 self.edges.add(len(edges)) 475 self.loops.add(sum(face_lengths)) 476 self.polygons.add(len(faces)) 477 478 self.vertices.foreach_set("co", tuple(chain.from_iterable(vertices))) 479 self.edges.foreach_set("vertices", tuple(chain.from_iterable(edges))) 480 481 vertex_indices = tuple(chain.from_iterable(faces)) 482 loop_starts = tuple(islice(chain([0], accumulate(face_lengths)), len(faces))) 483 484 self.polygons.foreach_set("loop_total", face_lengths) 485 self.polygons.foreach_set("loop_start", loop_starts) 486 self.polygons.foreach_set("vertices", vertex_indices) 487 488 if edges or faces: 489 self.update( 490 # Needed to either: 491 # - Calculate edges that don't exist for polygons. 492 # - Assign edges to polygon loops. 493 calc_edges=bool(faces), 494 # Flag loose edges. 495 calc_edges_loose=bool(edges), 496 ) 497 498 @property 499 def edge_keys(self): 500 return [ed.key for ed in self.edges] 501 502 503class MeshEdge(StructRNA): 504 __slots__ = () 505 506 @property 507 def key(self): 508 return ord_ind(*tuple(self.vertices)) 509 510 511class MeshLoopTriangle(StructRNA): 512 __slots__ = () 513 514 @property 515 def center(self): 516 """The midpoint of the face.""" 517 face_verts = self.vertices[:] 518 mesh_verts = self.id_data.vertices 519 return ( 520 mesh_verts[face_verts[0]].co + 521 mesh_verts[face_verts[1]].co + 522 mesh_verts[face_verts[2]].co 523 ) / 3.0 524 525 @property 526 def edge_keys(self): 527 verts = self.vertices[:] 528 return ( 529 ord_ind(verts[0], verts[1]), 530 ord_ind(verts[1], verts[2]), 531 ord_ind(verts[2], verts[0]), 532 ) 533 534 535class MeshPolygon(StructRNA): 536 __slots__ = () 537 538 @property 539 def edge_keys(self): 540 verts = self.vertices[:] 541 vlen = len(self.vertices) 542 return [ord_ind(verts[i], verts[(i + 1) % vlen]) for i in range(vlen)] 543 544 @property 545 def loop_indices(self): 546 start = self.loop_start 547 end = start + self.loop_total 548 return range(start, end) 549 550 551class Text(bpy_types.ID): 552 __slots__ = () 553 554 def as_string(self): 555 """Return the text as a string.""" 556 return "\n".join(line.body for line in self.lines) 557 558 def from_string(self, string): 559 """Replace text with this string.""" 560 self.clear() 561 self.write(string) 562 563 def as_module(self): 564 from os.path import splitext 565 from types import ModuleType 566 mod = ModuleType(splitext(self.name)[0]) 567 # TODO: We could use Text.compiled (C struct member) 568 # if this is called often it will be much faster. 569 exec(self.as_string(), mod.__dict__) 570 return mod 571 572 573class Sound(bpy_types.ID): 574 __slots__ = () 575 576 @property 577 def factory(self): 578 """The aud.Factory object of the sound.""" 579 import aud 580 return aud._sound_from_pointer(self.as_pointer()) 581 582 583class RNAMeta(type): 584 # TODO(campbell): move to C-API 585 @property 586 def is_registered(cls): 587 return "bl_rna" in cls.__dict__ 588 589 590class RNAMetaPropGroup(StructMetaPropGroup, RNAMeta): 591 pass 592 593 594# Same as 'Operator' 595# only without 'as_keywords' 596class Gizmo(StructRNA): 597 __slots__ = () 598 599 def __getattribute__(self, attr): 600 properties = StructRNA.path_resolve(self, "properties") 601 bl_rna = getattr(properties, "bl_rna", None) 602 if (bl_rna is not None) and (attr in bl_rna.properties): 603 return getattr(properties, attr) 604 return super().__getattribute__(attr) 605 606 def __setattr__(self, attr, value): 607 properties = StructRNA.path_resolve(self, "properties") 608 bl_rna = getattr(properties, "bl_rna", None) 609 if (bl_rna is not None) and (attr in bl_rna.properties): 610 return setattr(properties, attr, value) 611 return super().__setattr__(attr, value) 612 613 def __delattr__(self, attr): 614 properties = StructRNA.path_resolve(self, "properties") 615 bl_rna = getattr(properties, "bl_rna", None) 616 if (bl_rna is not None) and (attr in bl_rna.properties): 617 return delattr(properties, attr) 618 return super().__delattr__(attr) 619 620 from _bpy import ( 621 _rna_gizmo_target_set_handler as target_set_handler, 622 _rna_gizmo_target_get_value as target_get_value, 623 _rna_gizmo_target_set_value as target_set_value, 624 _rna_gizmo_target_get_range as target_get_range, 625 ) 626 627 # Convenience wrappers around private `_gpu` module. 628 def draw_custom_shape(self, shape, *, matrix=None, select_id=None): 629 """ 630 Draw a shape created form :class:`bpy.types.Gizmo.draw_custom_shape`. 631 632 :arg shape: The cached shape to draw. 633 :type shape: Undefined. 634 :arg matrix: 4x4 matrix, when not given 635 :class:`bpy.types.Gizmo.matrix_world` is used. 636 :type matrix: :class:`mathutils.Matrix` 637 :arg select_id: The selection id. 638 Only use when drawing within :class:`bpy.types.Gizmo.draw_select`. 639 :type select_it: int 640 """ 641 import gpu 642 643 if matrix is None: 644 matrix = self.matrix_world 645 646 batch, shader = shape 647 shader.bind() 648 649 if select_id is not None: 650 gpu.select.load_id(select_id) 651 use_blend = False 652 else: 653 if self.is_highlight: 654 color = (*self.color_highlight, self.alpha_highlight) 655 else: 656 color = (*self.color, self.alpha) 657 shader.uniform_float("color", color) 658 use_blend = color[3] < 1.0 659 660 if use_blend: 661 # TODO: wrap GPU_blend from GPU state. 662 from bgl import glEnable, glDisable, GL_BLEND 663 glEnable(GL_BLEND) 664 665 with gpu.matrix.push_pop(): 666 gpu.matrix.multiply_matrix(matrix) 667 batch.draw() 668 669 if use_blend: 670 glDisable(GL_BLEND) 671 672 @staticmethod 673 def new_custom_shape(type, verts): 674 """ 675 Create a new shape that can be passed to :class:`bpy.types.Gizmo.draw_custom_shape`. 676 677 :arg type: The type of shape to create in (POINTS, LINES, TRIS, LINE_STRIP). 678 :type type: string 679 :arg verts: Coordinates. 680 :type verts: sequence of of 2D or 3D coordinates. 681 :arg display_name: Optional callback that takes the full path, returns the name to display. 682 :type display_name: Callable that takes a string and returns a string. 683 :return: The newly created shape. 684 :rtype: Undefined (it may change). 685 """ 686 import gpu 687 from gpu.types import ( 688 GPUBatch, 689 GPUVertBuf, 690 GPUVertFormat, 691 ) 692 dims = len(verts[0]) 693 if dims not in {2, 3}: 694 raise ValueError("Expected 2D or 3D vertex") 695 fmt = GPUVertFormat() 696 pos_id = fmt.attr_add(id="pos", comp_type='F32', len=dims, fetch_mode='FLOAT') 697 vbo = GPUVertBuf(len=len(verts), format=fmt) 698 vbo.attr_fill(id=pos_id, data=verts) 699 batch = GPUBatch(type=type, buf=vbo) 700 shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR' if dims == 3 else '2D_UNIFORM_COLOR') 701 batch.program_set(shader) 702 return (batch, shader) 703 704 705# Dummy class to keep the reference in `bpy_types_dict` and avoid 706# erros like: "TypeError: expected GizmoGroup subclass of class ..." 707class GizmoGroup(StructRNA): 708 __slots__ = () 709 710 711# Only defined so operators members can be used by accessing self.order 712# with doc generation 'self.properties.bl_rna.properties' can fail 713class Operator(StructRNA, metaclass=RNAMeta): 714 __slots__ = () 715 716 def __getattribute__(self, attr): 717 properties = StructRNA.path_resolve(self, "properties") 718 bl_rna = getattr(properties, "bl_rna", None) 719 if (bl_rna is not None) and (attr in bl_rna.properties): 720 return getattr(properties, attr) 721 return super().__getattribute__(attr) 722 723 def __setattr__(self, attr, value): 724 properties = StructRNA.path_resolve(self, "properties") 725 bl_rna = getattr(properties, "bl_rna", None) 726 if (bl_rna is not None) and (attr in bl_rna.properties): 727 return setattr(properties, attr, value) 728 return super().__setattr__(attr, value) 729 730 def __delattr__(self, attr): 731 properties = StructRNA.path_resolve(self, "properties") 732 bl_rna = getattr(properties, "bl_rna", None) 733 if (bl_rna is not None) and (attr in bl_rna.properties): 734 return delattr(properties, attr) 735 return super().__delattr__(attr) 736 737 def as_keywords(self, ignore=()): 738 """Return a copy of the properties as a dictionary""" 739 ignore = ignore + ("rna_type",) 740 return {attr: getattr(self, attr) 741 for attr in self.properties.rna_type.properties.keys() 742 if attr not in ignore} 743 744 745class Macro(StructRNA): 746 # bpy_types is imported before ops is defined 747 # so we have to do a local import on each run 748 __slots__ = () 749 750 @classmethod 751 def define(self, opname): 752 from _bpy import ops 753 return ops.macro_define(self, opname) 754 755 756class PropertyGroup(StructRNA, metaclass=RNAMetaPropGroup): 757 __slots__ = () 758 759 760class RenderEngine(StructRNA, metaclass=RNAMeta): 761 __slots__ = () 762 763 764class KeyingSetInfo(StructRNA, metaclass=RNAMeta): 765 __slots__ = () 766 767 768class AddonPreferences(StructRNA, metaclass=RNAMeta): 769 __slots__ = () 770 771 772class _GenericUI: 773 __slots__ = () 774 775 @classmethod 776 def _dyn_ui_initialize(cls): 777 draw_funcs = getattr(cls.draw, "_draw_funcs", None) 778 779 if draw_funcs is None: 780 781 def draw_ls(self, context): 782 # ensure menus always get default context 783 operator_context_default = self.layout.operator_context 784 785 # Support filtering out by owner 786 workspace = context.workspace 787 if workspace.use_filter_by_owner: 788 owner_names = {owner_id.name for owner_id in workspace.owner_ids} 789 else: 790 owner_names = None 791 792 for func in draw_ls._draw_funcs: 793 794 # Begin 'owner_id' filter. 795 # Exclude Import/Export menus from this filtering (io addons should always show there) 796 if not getattr(self, "bl_owner_use_filter", True): 797 pass 798 elif owner_names is not None: 799 owner_id = getattr(func, "_owner", None) 800 if owner_id is not None: 801 if func._owner not in owner_names: 802 continue 803 # End 'owner_id' filter. 804 805 # so bad menu functions don't stop 806 # the entire menu from drawing 807 try: 808 func(self, context) 809 except: 810 import traceback 811 traceback.print_exc() 812 813 self.layout.operator_context = operator_context_default 814 815 draw_funcs = draw_ls._draw_funcs = [cls.draw] 816 cls.draw = draw_ls 817 818 return draw_funcs 819 820 @staticmethod 821 def _dyn_owner_apply(draw_func): 822 from _bpy import _bl_owner_id_get 823 owner_id = _bl_owner_id_get() 824 if owner_id is not None: 825 draw_func._owner = owner_id 826 827 @classmethod 828 def is_extended(cls): 829 return bool(getattr(cls.draw, "_draw_funcs", None)) 830 831 @classmethod 832 def append(cls, draw_func): 833 """ 834 Append a draw function to this menu, 835 takes the same arguments as the menus draw function 836 """ 837 draw_funcs = cls._dyn_ui_initialize() 838 cls._dyn_owner_apply(draw_func) 839 draw_funcs.append(draw_func) 840 841 @classmethod 842 def prepend(cls, draw_func): 843 """ 844 Prepend a draw function to this menu, takes the same arguments as 845 the menus draw function 846 """ 847 draw_funcs = cls._dyn_ui_initialize() 848 cls._dyn_owner_apply(draw_func) 849 draw_funcs.insert(0, draw_func) 850 851 @classmethod 852 def remove(cls, draw_func): 853 """Remove a draw function that has been added to this menu""" 854 draw_funcs = cls._dyn_ui_initialize() 855 try: 856 draw_funcs.remove(draw_func) 857 except ValueError: 858 pass 859 860 861class Panel(StructRNA, _GenericUI, metaclass=RNAMeta): 862 __slots__ = () 863 864 865class UIList(StructRNA, _GenericUI, metaclass=RNAMeta): 866 __slots__ = () 867 868 869class Header(StructRNA, _GenericUI, metaclass=RNAMeta): 870 __slots__ = () 871 872 873class Menu(StructRNA, _GenericUI, metaclass=RNAMeta): 874 __slots__ = () 875 876 def path_menu(self, searchpaths, operator, *, 877 props_default=None, prop_filepath="filepath", 878 filter_ext=None, filter_path=None, display_name=None, 879 add_operator=None): 880 """ 881 Populate a menu from a list of paths. 882 883 :arg searchpaths: Paths to scan. 884 :type searchpaths: sequence of strings. 885 :arg operator: The operator id to use with each file. 886 :type operator: string 887 :arg prop_filepath: Optional operator filepath property (defaults to "filepath"). 888 :type prop_filepath: string 889 :arg props_default: Properties to assign to each operator. 890 :type props_default: dict 891 :arg filter_ext: Optional callback that takes the file extensions. 892 893 Returning false excludes the file from the list. 894 895 :type filter_ext: Callable that takes a string and returns a bool. 896 :arg display_name: Optional callback that takes the full path, returns the name to display. 897 :type display_name: Callable that takes a string and returns a string. 898 """ 899 900 layout = self.layout 901 902 import os 903 import bpy.utils 904 905 layout = self.layout 906 907 if not searchpaths: 908 layout.label(text="* Missing Paths *") 909 910 # collect paths 911 files = [] 912 for directory in searchpaths: 913 files.extend([ 914 (f, os.path.join(directory, f)) 915 for f in os.listdir(directory) 916 if (not f.startswith(".")) 917 if ((filter_ext is None) or 918 (filter_ext(os.path.splitext(f)[1]))) 919 if ((filter_path is None) or 920 (filter_path(f))) 921 ]) 922 923 files.sort() 924 925 col = layout.column(align=True) 926 927 for f, filepath in files: 928 # Intentionally pass the full path to 'display_name' callback, 929 # since the callback may want to use part a directory in the name. 930 row = col.row(align=True) 931 name = display_name(filepath) if display_name else bpy.path.display_name(f) 932 props = row.operator( 933 operator, 934 text=name, 935 translate=False, 936 ) 937 938 if props_default is not None: 939 for attr, value in props_default.items(): 940 setattr(props, attr, value) 941 942 setattr(props, prop_filepath, filepath) 943 if operator == "script.execute_preset": 944 props.menu_idname = self.bl_idname 945 946 if add_operator: 947 props = row.operator(add_operator, text="", icon='REMOVE') 948 props.name = name 949 props.remove_name = True 950 951 if add_operator: 952 wm = bpy.data.window_managers[0] 953 954 layout.separator() 955 row = layout.row() 956 957 sub = row.row() 958 sub.emboss = 'NORMAL' 959 sub.prop(wm, "preset_name", text="") 960 961 props = row.operator(add_operator, text="", icon='ADD') 962 props.name = wm.preset_name 963 964 def draw_preset(self, _context): 965 """ 966 Define these on the subclass: 967 - preset_operator (string) 968 - preset_subdir (string) 969 970 Optionally: 971 - preset_add_operator (string) 972 - preset_extensions (set of strings) 973 - preset_operator_defaults (dict of keyword args) 974 """ 975 import bpy 976 ext_valid = getattr(self, "preset_extensions", {".py", ".xml"}) 977 props_default = getattr(self, "preset_operator_defaults", None) 978 add_operator = getattr(self, "preset_add_operator", None) 979 self.path_menu( 980 bpy.utils.preset_paths(self.preset_subdir), 981 self.preset_operator, 982 props_default=props_default, 983 filter_ext=lambda ext: ext.lower() in ext_valid, 984 add_operator=add_operator, 985 ) 986 987 @classmethod 988 def draw_collapsible(cls, context, layout): 989 # helper function for (optionally) collapsed header menus 990 # only usable within headers 991 if context.area.show_menus: 992 # Align menus to space them closely. 993 layout.row(align=True).menu_contents(cls.__name__) 994 else: 995 layout.menu(cls.__name__, icon='COLLAPSEMENU') 996 997 998class NodeTree(bpy_types.ID, metaclass=RNAMetaPropGroup): 999 __slots__ = () 1000 1001 1002class Node(StructRNA, metaclass=RNAMetaPropGroup): 1003 __slots__ = () 1004 1005 @classmethod 1006 def poll(cls, _ntree): 1007 return True 1008 1009 1010class NodeInternal(Node): 1011 __slots__ = () 1012 1013 1014class NodeSocket(StructRNA, metaclass=RNAMetaPropGroup): 1015 __slots__ = () 1016 1017 @property 1018 def links(self): 1019 """ 1020 List of node links from or to this socket. 1021 1022 .. note:: Takes ``O(len(nodetree.links))`` time.""" 1023 return tuple( 1024 link for link in self.id_data.links 1025 if (link.from_socket == self or 1026 link.to_socket == self)) 1027 1028 1029class NodeSocketInterface(StructRNA, metaclass=RNAMetaPropGroup): 1030 __slots__ = () 1031 1032 1033# These are intermediate subclasses, need a bpy type too 1034class CompositorNode(NodeInternal): 1035 __slots__ = () 1036 1037 @classmethod 1038 def poll(cls, ntree): 1039 return ntree.bl_idname == 'CompositorNodeTree' 1040 1041 def update(self): 1042 self.tag_need_exec() 1043 1044 1045class ShaderNode(NodeInternal): 1046 __slots__ = () 1047 1048 @classmethod 1049 def poll(cls, ntree): 1050 return ntree.bl_idname == 'ShaderNodeTree' 1051 1052 1053class TextureNode(NodeInternal): 1054 __slots__ = () 1055 1056 @classmethod 1057 def poll(cls, ntree): 1058 return ntree.bl_idname == 'TextureNodeTree' 1059