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