Skip to content

load_image_compositor

LoadImageCompositor

Bases: BlenderLoader

Load media to the compositor.

Source code in client/ayon_blender/plugins/load/load_image_compositor.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
class LoadImageCompositor(plugin.BlenderLoader):
    """Load media to the compositor."""

    product_types = {"render", "image", "plate"}
    representations = {"*"}

    label = "Load in Compositor"
    icon = "code-fork"
    color = "orange"

    def process_asset(
        self, context: dict, name: str, namespace: Optional[str] = None,
        options: Optional[Dict] = None
    ) -> Optional[List]:
        """
        Arguments:
            name: Use pre-defined name
            namespace: Use pre-defined namespace
            context: Full parenthood of representation to load
            options: Additional settings dictionary
        """
        path = self.filepath_from_context(context)

        # Enable nodes to ensure they can be loaded
        if not bpy.context.scene.use_nodes:
            self.log.info("Enabling 'use nodes' for Compositor")
            bpy.context.scene.use_nodes = True

        # Load the image in data
        image = bpy.data.images.load(path, check_existing=True)

        # Get the current scene's compositor node tree
        node_tree = bpy.context.scene.node_tree

        # Create a new image node
        img_comp_node = node_tree.nodes.new(type='CompositorNodeImage')
        img_comp_node.image = image
        self.set_source_and_colorspace(context, img_comp_node)

        data = {
            "schema": "openpype:container-2.0",
            "id": AVALON_CONTAINER_ID,
            "name": name,
            "namespace": namespace or '',
            "loader": str(self.__class__.__name__),
            "representation": context["representation"]["id"],
            "project_name": context["project"]["name"],
        }
        lib.imprint(img_comp_node, data)

        return [img_comp_node]

    def exec_remove(self, container: Dict) -> bool:
        """Remove the image comp node"""
        img_comp_node = container["node"]
        image: Optional[bpy.types.Image] = img_comp_node.image

        # Delete the compositor node
        bpy.context.scene.node_tree.nodes.remove(img_comp_node)

        # Delete the image if it remains unused
        self.remove_image_if_unused(image)

        return True

    def exec_update(self, container: Dict, context: Dict):
        """Update the image comp node to new context version."""
        path = self.filepath_from_context(context)
        img_comp_node = container["node"]

        old_image: Optional[bpy.types.Image] = img_comp_node.image

        new_image = bpy.data.images.load(path, check_existing=True)
        img_comp_node.image = new_image

        self.set_source_and_colorspace(context, img_comp_node)
        self.remove_image_if_unused(old_image)

        # Update representation id
        lib.imprint(img_comp_node, {
            "representation": context["representation"]["id"],
            "project_name": context["project"]["name"],
        })

    def set_source_and_colorspace(
        self,
        context: dict,
        image_comp_node: bpy.types.CompositorNodeImage
    ):
        """
        Set the image source (e.g. SEQUENCE or FILE), set the duration for
        a sequence and set colorspace if representation has colorspace data.
        """

        image = image_comp_node.image
        representation: dict = context["representation"]

        # Set image source
        source = "FILE"  # Single image file
        if representation["context"].get("udim"):
            source = "UDIM"
        elif representation["context"].get("frame"):
            source = "SEQUENCE"
        else:
            ext = os.path.splitext(image.filepath)[-1]
            if ext in VIDEO_EXTENSIONS:
                source = "MOVIE"

        image.source = source

        # Set duration on the compositor node if sequence is used
        if source in {"SEQUENCE", "MOVIE"}:
            version_attrib: dict = context["version"]["attrib"]
            frame_start = version_attrib.get("frameStart", 0)
            frame_end = version_attrib.get("frameEnd", 0)
            handle_start = version_attrib.get("handleStart", 0)
            handle_end = version_attrib.get("handleEnd", 0)
            frame_start_handle = frame_start - handle_start
            frame_end_handle = frame_end + handle_end
            duration: int = frame_end_handle - frame_start_handle + 1
            image_comp_node.frame_duration = duration
            if source == "SEQUENCE":
                image_comp_node.frame_start = frame_start_handle
                image_comp_node.frame_offset = frame_start_handle - 1
            else:
                image_comp_node.frame_start = frame_start_handle
                image_comp_node.frame_offset = 0

        # Set colorspace if representation has colorspace data
        colorspace_data = representation.get("data", {}).get(
            "colorspaceData", {})
        if colorspace_data:
            colorspace: str = colorspace_data["colorspace"]
            if colorspace:
                image.colorspace_settings.name = colorspace

    def remove_image_if_unused(self, image: bpy.types.Image):
        if image and not image.users:
            self.log.debug("Removing unused image: %s", image.name)
            bpy.data.images.remove(image)

exec_remove(container)

Remove the image comp node

Source code in client/ayon_blender/plugins/load/load_image_compositor.py
63
64
65
66
67
68
69
70
71
72
73
74
def exec_remove(self, container: Dict) -> bool:
    """Remove the image comp node"""
    img_comp_node = container["node"]
    image: Optional[bpy.types.Image] = img_comp_node.image

    # Delete the compositor node
    bpy.context.scene.node_tree.nodes.remove(img_comp_node)

    # Delete the image if it remains unused
    self.remove_image_if_unused(image)

    return True

exec_update(container, context)

Update the image comp node to new context version.

Source code in client/ayon_blender/plugins/load/load_image_compositor.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def exec_update(self, container: Dict, context: Dict):
    """Update the image comp node to new context version."""
    path = self.filepath_from_context(context)
    img_comp_node = container["node"]

    old_image: Optional[bpy.types.Image] = img_comp_node.image

    new_image = bpy.data.images.load(path, check_existing=True)
    img_comp_node.image = new_image

    self.set_source_and_colorspace(context, img_comp_node)
    self.remove_image_if_unused(old_image)

    # Update representation id
    lib.imprint(img_comp_node, {
        "representation": context["representation"]["id"],
        "project_name": context["project"]["name"],
    })

process_asset(context, name, namespace=None, options=None)

Parameters:

Name Type Description Default
name str

Use pre-defined name

required
namespace Optional[str]

Use pre-defined namespace

None
context dict

Full parenthood of representation to load

required
options Optional[Dict]

Additional settings dictionary

None
Source code in client/ayon_blender/plugins/load/load_image_compositor.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def process_asset(
    self, context: dict, name: str, namespace: Optional[str] = None,
    options: Optional[Dict] = None
) -> Optional[List]:
    """
    Arguments:
        name: Use pre-defined name
        namespace: Use pre-defined namespace
        context: Full parenthood of representation to load
        options: Additional settings dictionary
    """
    path = self.filepath_from_context(context)

    # Enable nodes to ensure they can be loaded
    if not bpy.context.scene.use_nodes:
        self.log.info("Enabling 'use nodes' for Compositor")
        bpy.context.scene.use_nodes = True

    # Load the image in data
    image = bpy.data.images.load(path, check_existing=True)

    # Get the current scene's compositor node tree
    node_tree = bpy.context.scene.node_tree

    # Create a new image node
    img_comp_node = node_tree.nodes.new(type='CompositorNodeImage')
    img_comp_node.image = image
    self.set_source_and_colorspace(context, img_comp_node)

    data = {
        "schema": "openpype:container-2.0",
        "id": AVALON_CONTAINER_ID,
        "name": name,
        "namespace": namespace or '',
        "loader": str(self.__class__.__name__),
        "representation": context["representation"]["id"],
        "project_name": context["project"]["name"],
    }
    lib.imprint(img_comp_node, data)

    return [img_comp_node]

set_source_and_colorspace(context, image_comp_node)

Set the image source (e.g. SEQUENCE or FILE), set the duration for a sequence and set colorspace if representation has colorspace data.

Source code in client/ayon_blender/plugins/load/load_image_compositor.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def set_source_and_colorspace(
    self,
    context: dict,
    image_comp_node: bpy.types.CompositorNodeImage
):
    """
    Set the image source (e.g. SEQUENCE or FILE), set the duration for
    a sequence and set colorspace if representation has colorspace data.
    """

    image = image_comp_node.image
    representation: dict = context["representation"]

    # Set image source
    source = "FILE"  # Single image file
    if representation["context"].get("udim"):
        source = "UDIM"
    elif representation["context"].get("frame"):
        source = "SEQUENCE"
    else:
        ext = os.path.splitext(image.filepath)[-1]
        if ext in VIDEO_EXTENSIONS:
            source = "MOVIE"

    image.source = source

    # Set duration on the compositor node if sequence is used
    if source in {"SEQUENCE", "MOVIE"}:
        version_attrib: dict = context["version"]["attrib"]
        frame_start = version_attrib.get("frameStart", 0)
        frame_end = version_attrib.get("frameEnd", 0)
        handle_start = version_attrib.get("handleStart", 0)
        handle_end = version_attrib.get("handleEnd", 0)
        frame_start_handle = frame_start - handle_start
        frame_end_handle = frame_end + handle_end
        duration: int = frame_end_handle - frame_start_handle + 1
        image_comp_node.frame_duration = duration
        if source == "SEQUENCE":
            image_comp_node.frame_start = frame_start_handle
            image_comp_node.frame_offset = frame_start_handle - 1
        else:
            image_comp_node.frame_start = frame_start_handle
            image_comp_node.frame_offset = 0

    # Set colorspace if representation has colorspace data
    colorspace_data = representation.get("data", {}).get(
        "colorspaceData", {})
    if colorspace_data:
        colorspace: str = colorspace_data["colorspace"]
        if colorspace:
            image.colorspace_settings.name = colorspace