Skip to content

load_image

ImageLoader

Bases: HoudiniLoader

Load images into COP2

Source code in client/ayon_houdini/plugins/load/load_image.py
 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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
class ImageLoader(plugin.HoudiniLoader):
    """Load images into COP2"""

    product_types = {
        "imagesequence",
        "review",
        "render",
        "plate",
        "image",
        "online",
    }
    label = "Load Image (COP2)"
    representations = {"*"}
    order = -10

    icon = "code-fork"
    color = "orange"

    def load(self, context, name=None, namespace=None, data=None):
        # Format file name, Houdini only wants forward slashes
        path = self.filepath_from_context(context)
        path = self.format_path(path, representation=context["representation"])

        # Get the root node
        parent = get_image_avalon_container()

        # Define node name
        namespace = namespace if namespace else context["folder"]["name"]
        node_name = "{}_{}".format(namespace, name) if namespace else name

        node = parent.createNode("file", node_name=node_name)
        node.moveToGoodPosition()

        parms = {"filename1": path}
        parms.update(self.get_colorspace_parms(context["representation"]))

        node.setParms(parms)

        # Imprint it manually
        data = {
            "schema": "openpype:container-2.0",
            "id": AVALON_CONTAINER_ID,
            "name": node_name,
            "namespace": namespace,
            "loader": str(self.__class__.__name__),
            "representation": context["representation"]["id"],
        }

        # todo: add folder="Avalon"
        lib.imprint(node, data)

        return node

    def update(self, container, context):
        repre_entity = context["representation"]
        node = container["node"]

        # Update the file path
        file_path = self.filepath_from_context(context)
        file_path = self.format_path(file_path, repre_entity)

        parms = {
            "filename1": file_path,
            "representation": repre_entity["id"],
        }

        parms.update(self.get_colorspace_parms(repre_entity))

        # Update attributes
        node.setParms(parms)

    def remove(self, container):
        node = container["node"]

        # Let's clean up the IMAGES COP2 network
        # if it ends up being empty and we deleted
        # the last file node. Store the parent
        # before we delete the node.
        parent = node.parent()

        node.destroy()

        if not parent.children():
            parent.destroy()

    @staticmethod
    def format_path(path, representation):
        """Format file path correctly for single image or sequence."""
        ext = os.path.splitext(path)[-1]

        # The path is either a single file or sequence in a folder.
        is_sequence = bool(representation["context"].get("frame"))
        if is_sequence:
            folder, filename = os.path.split(path)
            filename = re.sub(r"(.*)\.(\d+){}$".format(re.escape(ext)),
                              "\\1.$F4{}".format(ext),
                              filename)
            path = os.path.join(folder, filename)

        path = os.path.normpath(path)
        path = path.replace("\\", "/")
        return path

    def get_colorspace_parms(self, representation: dict) -> dict:
        """Return the color space parameters.

        Returns the values for the colorspace parameters on the node if there
        is colorspace data on the representation.

        Arguments:
            representation (dict): The representation entity.

        Returns:
            dict: Parm to value mapping if colorspace data is defined.

        """
        # Using OCIO colorspace on COP2 File node is only supported in Hou 20+
        major, _, _ = hou.applicationVersion()
        if major < 20:
            return {}

        data = representation.get("data", {}).get("colorspaceData", {})
        if not data:
            return {}

        colorspace = data["colorspace"]
        if colorspace:
            return {
                "colorspace": 3,  # Use OpenColorIO
                "ocio_space": colorspace
            }

        return {}

    def switch(self, container, representation):
        self.update(container, representation)

format_path(path, representation) staticmethod

Format file path correctly for single image or sequence.

Source code in client/ayon_houdini/plugins/load/load_image.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
@staticmethod
def format_path(path, representation):
    """Format file path correctly for single image or sequence."""
    ext = os.path.splitext(path)[-1]

    # The path is either a single file or sequence in a folder.
    is_sequence = bool(representation["context"].get("frame"))
    if is_sequence:
        folder, filename = os.path.split(path)
        filename = re.sub(r"(.*)\.(\d+){}$".format(re.escape(ext)),
                          "\\1.$F4{}".format(ext),
                          filename)
        path = os.path.join(folder, filename)

    path = os.path.normpath(path)
    path = path.replace("\\", "/")
    return path

get_colorspace_parms(representation)

Return the color space parameters.

Returns the values for the colorspace parameters on the node if there is colorspace data on the representation.

Parameters:

Name Type Description Default
representation dict

The representation entity.

required

Returns:

Name Type Description
dict dict

Parm to value mapping if colorspace data is defined.

Source code in client/ayon_houdini/plugins/load/load_image.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def get_colorspace_parms(self, representation: dict) -> dict:
    """Return the color space parameters.

    Returns the values for the colorspace parameters on the node if there
    is colorspace data on the representation.

    Arguments:
        representation (dict): The representation entity.

    Returns:
        dict: Parm to value mapping if colorspace data is defined.

    """
    # Using OCIO colorspace on COP2 File node is only supported in Hou 20+
    major, _, _ = hou.applicationVersion()
    if major < 20:
        return {}

    data = representation.get("data", {}).get("colorspaceData", {})
    if not data:
        return {}

    colorspace = data["colorspace"]
    if colorspace:
        return {
            "colorspace": 3,  # Use OpenColorIO
            "ocio_space": colorspace
        }

    return {}

get_image_avalon_container()

The COP2 files must be in a COP2 network.

So we maintain a single entry point within AVALON_CONTAINERS, just for ease of use.

Source code in client/ayon_houdini/plugins/load/load_image.py
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
def get_image_avalon_container():
    """The COP2 files must be in a COP2 network.

    So we maintain a single entry point within AVALON_CONTAINERS,
    just for ease of use.

    """

    path = pipeline.AVALON_CONTAINERS
    avalon_container = hou.node(path)
    if not avalon_container:
        # Let's create avalon container secretly
        # but make sure the pipeline still is built the
        # way we anticipate it was built, asserting it.
        assert path == "/obj/AVALON_CONTAINERS"

        parent = hou.node("/obj")
        avalon_container = parent.createNode(
            "subnet", node_name="AVALON_CONTAINERS"
        )

    image_container = hou.node(path + "/IMAGES")
    if not image_container:
        image_container = avalon_container.createNode(
            "cop2net", node_name="IMAGES"
        )
        image_container.moveToGoodPosition()

    return image_container