Skip to content

extract_pointcloud

ExtractPointCloud

Bases: Extractor

Extract PRT format with tyFlow operators.

Notes

Currently only works for the default partition setting

Parameters:

Name Type Description Default
self.export_particle()

sets up all job arguments for attributes to be exported in MAXscript

required
self.get_operators()

get the export_particle operator

required
self.get_custom_attr()

get all custom channel attributes from Openpype setting and sets it as job arguments before exporting

required
self.get_files()

get the files with tyFlow naming convention before publishing

required
self.partition_output_name()

get the naming with partition settings.

required
self.get_partition()

get partition value

required
Source code in client/ayon_max/plugins/publish/extract_pointcloud.py
 10
 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
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
class ExtractPointCloud(publish.Extractor):
    """
    Extract PRT format with tyFlow operators.

    Notes:
        Currently only works for the default partition setting

    Args:
        self.export_particle(): sets up all job arguments for attributes
            to be exported in MAXscript

        self.get_operators(): get the export_particle operator

        self.get_custom_attr(): get all custom channel attributes from Openpype
            setting and sets it as job arguments before exporting

        self.get_files(): get the files with tyFlow naming convention
            before publishing

        self.partition_output_name(): get the naming with partition settings.

        self.get_partition(): get partition value

    """

    order = pyblish.api.ExtractorOrder - 0.2
    label = "Extract Point Cloud"
    hosts = ["max"]
    families = ["pointcloud"]
    settings = []

    def process(self, instance):
        self.settings = self.get_setting(instance)
        start = instance.data["frameStartHandle"]
        end = instance.data["frameEndHandle"]
        self.log.info("Extracting PRT...")

        stagingdir = self.staging_dir(instance)
        filename = "{name}.prt".format(**instance.data)
        path = os.path.join(stagingdir, filename)

        with maintained_selection():
            job_args = self.export_particle(instance.data["members"],
                                            start,
                                            end,
                                            path)

            for job in job_args:
                rt.Execute(job)

        self.log.info("Performing Extraction ...")
        if "representations" not in instance.data:
            instance.data["representations"] = []

        self.log.info("Writing PRT with TyFlow Plugin...")
        filenames = self.get_files(
            instance.data["members"], path, start, end)
        self.log.debug(f"filenames: {filenames}")

        partition = self.partition_output_name(
            instance.data["members"])

        representation = {
            'name': 'prt',
            'ext': 'prt',
            'files': filenames if len(filenames) > 1 else filenames[0],
            "stagingDir": stagingdir,
            "outputName": partition         # partition value
        }
        instance.data["representations"].append(representation)
        self.log.info(f"Extracted instance '{instance.name}' to: {path}")

    def export_particle(self,
                        members,
                        start,
                        end,
                        filepath):
        """Sets up all job arguments for attributes.

        Those attributes are to be exported in MAX Script.

        Args:
            members (list): Member nodes of the instance.
            start (int): Start frame.
            end (int): End frame.
            filepath (str): Path to PRT file.

        Returns:
            list of arguments for MAX Script.

        """
        job_args = []
        opt_list = self.get_operators(members)
        for operator in opt_list:
            start_frame = f"{operator}.frameStart={start}"
            job_args.append(start_frame)
            end_frame = f"{operator}.frameEnd={end}"
            job_args.append(end_frame)
            filepath = filepath.replace("\\", "/")
            prt_filename = f'{operator}.PRTFilename="{filepath}"'
            job_args.append(prt_filename)
            # Partition
            mode = f"{operator}.PRTPartitionsMode=2"
            job_args.append(mode)

            additional_args = self.get_custom_attr(operator)
            job_args.extend(iter(additional_args))
            prt_export = f"{operator}.exportPRT()"
            job_args.append(prt_export)

        return job_args

    @staticmethod
    def get_operators(members):
        """Get Export Particles Operator.

        Args:
            members (list): Instance members.

        Returns:
            list of particle operators

        """
        opt_list = []
        for member in members:
            obj = member.baseobject
            anim_names = rt.GetSubAnimNames(obj)
            for anim_name in anim_names:
                sub_anim = rt.GetSubAnim(obj, anim_name)
                # Isolate only the events
                if not rt.isKindOf(sub_anim, rt.tyEvent):
                    continue

                # Look through all the nodes in the events
                node_names = rt.GetSubAnimNames(sub_anim)
                for node_name in node_names:
                    node_sub_anim = rt.GetSubAnim(sub_anim, node_name)
                    if rt.hasProperty(node_sub_anim, "exportMode"):
                        opt = f"${member.Name}.{sub_anim.Name}.{node_name}"
                        opt_list.append(opt)
        return opt_list

    @staticmethod
    def get_setting(instance):
        project_setting = instance.context.data["project_settings"]
        return project_setting["max"]["PointCloud"]

    def get_custom_attr(self, operator):
        """Get Custom Attributes"""

        custom_attr_list = []
        attr_settings = self.settings["attribute"]
        for attr in attr_settings:
            key = attr["name"]
            value = attr["value"]
            custom_attr = "{0}.PRTChannels_{1}=True".format(operator,
                                                            value)
            self.log.debug(
                "{0} will be added as custom attribute".format(key)
            )
            custom_attr_list.append(custom_attr)

        return custom_attr_list

    def get_files(self,
                  container,
                  path,
                  start_frame,
                  end_frame):
        """Get file names for tyFlow.

        Set the filenames accordingly to the tyFlow file
        naming extension for the publishing purpose

        Actual File Output from tyFlow::
            <SceneFile>__part<PartitionStart>of<PartitionCount>.<frame>.prt

            e.g. tyFlow_cloth_CCCS_blobbyFill_001__part1of1_00004.prt

        Args:
            container: Instance node.
            path (str): Output directory.
            start_frame (int): Start frame.
            end_frame (int): End frame.

        Returns:
            list of filenames

        """
        filenames = []
        filename = os.path.basename(path)
        orig_name, ext = os.path.splitext(filename)
        partition_count, partition_start = self.get_partition(container)
        for frame in range(int(start_frame), int(end_frame) + 1):
            actual_name = "{}__part{:03}of{}_{:05}".format(orig_name,
                                                           partition_start,
                                                           partition_count,
                                                           frame)
            actual_filename = path.replace(orig_name, actual_name)
            filenames.append(os.path.basename(actual_filename))

        return filenames

    def partition_output_name(self, container):
        """Get partition output name.

        Partition output name set for mapping
        the published file output.

        Todo:
            Customizes the setting for the output.

        Args:
            container: Instance node.

        Returns:
            str: Partition name.

        """
        partition_count, partition_start = self.get_partition(container)
        return f"_part{partition_start:03}of{partition_count}"

    def get_partition(self, container):
        """Get Partition value.

        Args:
            container: Instance node.

        """
        opt_list = self.get_operators(container)
        # TODO: This looks strange? Iterating over
        #   the opt_list but returning from inside?
        for operator in opt_list:
            count = rt.Execute(f'{operator}.PRTPartitionsCount')
            start = rt.Execute(f'{operator}.PRTPartitionsFrom')

            return count, start

export_particle(members, start, end, filepath)

Sets up all job arguments for attributes.

Those attributes are to be exported in MAX Script.

Parameters:

Name Type Description Default
members list

Member nodes of the instance.

required
start int

Start frame.

required
end int

End frame.

required
filepath str

Path to PRT file.

required

Returns:

Type Description

list of arguments for MAX Script.

Source code in client/ayon_max/plugins/publish/extract_pointcloud.py
 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
def export_particle(self,
                    members,
                    start,
                    end,
                    filepath):
    """Sets up all job arguments for attributes.

    Those attributes are to be exported in MAX Script.

    Args:
        members (list): Member nodes of the instance.
        start (int): Start frame.
        end (int): End frame.
        filepath (str): Path to PRT file.

    Returns:
        list of arguments for MAX Script.

    """
    job_args = []
    opt_list = self.get_operators(members)
    for operator in opt_list:
        start_frame = f"{operator}.frameStart={start}"
        job_args.append(start_frame)
        end_frame = f"{operator}.frameEnd={end}"
        job_args.append(end_frame)
        filepath = filepath.replace("\\", "/")
        prt_filename = f'{operator}.PRTFilename="{filepath}"'
        job_args.append(prt_filename)
        # Partition
        mode = f"{operator}.PRTPartitionsMode=2"
        job_args.append(mode)

        additional_args = self.get_custom_attr(operator)
        job_args.extend(iter(additional_args))
        prt_export = f"{operator}.exportPRT()"
        job_args.append(prt_export)

    return job_args

get_custom_attr(operator)

Get Custom Attributes

Source code in client/ayon_max/plugins/publish/extract_pointcloud.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def get_custom_attr(self, operator):
    """Get Custom Attributes"""

    custom_attr_list = []
    attr_settings = self.settings["attribute"]
    for attr in attr_settings:
        key = attr["name"]
        value = attr["value"]
        custom_attr = "{0}.PRTChannels_{1}=True".format(operator,
                                                        value)
        self.log.debug(
            "{0} will be added as custom attribute".format(key)
        )
        custom_attr_list.append(custom_attr)

    return custom_attr_list

get_files(container, path, start_frame, end_frame)

Get file names for tyFlow.

Set the filenames accordingly to the tyFlow file naming extension for the publishing purpose

Actual File Output from tyFlow:: __partof..prt

e.g. tyFlow_cloth_CCCS_blobbyFill_001__part1of1_00004.prt

Parameters:

Name Type Description Default
container

Instance node.

required
path str

Output directory.

required
start_frame int

Start frame.

required
end_frame int

End frame.

required

Returns:

Type Description

list of filenames

Source code in client/ayon_max/plugins/publish/extract_pointcloud.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def get_files(self,
              container,
              path,
              start_frame,
              end_frame):
    """Get file names for tyFlow.

    Set the filenames accordingly to the tyFlow file
    naming extension for the publishing purpose

    Actual File Output from tyFlow::
        <SceneFile>__part<PartitionStart>of<PartitionCount>.<frame>.prt

        e.g. tyFlow_cloth_CCCS_blobbyFill_001__part1of1_00004.prt

    Args:
        container: Instance node.
        path (str): Output directory.
        start_frame (int): Start frame.
        end_frame (int): End frame.

    Returns:
        list of filenames

    """
    filenames = []
    filename = os.path.basename(path)
    orig_name, ext = os.path.splitext(filename)
    partition_count, partition_start = self.get_partition(container)
    for frame in range(int(start_frame), int(end_frame) + 1):
        actual_name = "{}__part{:03}of{}_{:05}".format(orig_name,
                                                       partition_start,
                                                       partition_count,
                                                       frame)
        actual_filename = path.replace(orig_name, actual_name)
        filenames.append(os.path.basename(actual_filename))

    return filenames

get_operators(members) staticmethod

Get Export Particles Operator.

Parameters:

Name Type Description Default
members list

Instance members.

required

Returns:

Type Description

list of particle operators

Source code in client/ayon_max/plugins/publish/extract_pointcloud.py
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
@staticmethod
def get_operators(members):
    """Get Export Particles Operator.

    Args:
        members (list): Instance members.

    Returns:
        list of particle operators

    """
    opt_list = []
    for member in members:
        obj = member.baseobject
        anim_names = rt.GetSubAnimNames(obj)
        for anim_name in anim_names:
            sub_anim = rt.GetSubAnim(obj, anim_name)
            # Isolate only the events
            if not rt.isKindOf(sub_anim, rt.tyEvent):
                continue

            # Look through all the nodes in the events
            node_names = rt.GetSubAnimNames(sub_anim)
            for node_name in node_names:
                node_sub_anim = rt.GetSubAnim(sub_anim, node_name)
                if rt.hasProperty(node_sub_anim, "exportMode"):
                    opt = f"${member.Name}.{sub_anim.Name}.{node_name}"
                    opt_list.append(opt)
    return opt_list

get_partition(container)

Get Partition value.

Parameters:

Name Type Description Default
container

Instance node.

required
Source code in client/ayon_max/plugins/publish/extract_pointcloud.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def get_partition(self, container):
    """Get Partition value.

    Args:
        container: Instance node.

    """
    opt_list = self.get_operators(container)
    # TODO: This looks strange? Iterating over
    #   the opt_list but returning from inside?
    for operator in opt_list:
        count = rt.Execute(f'{operator}.PRTPartitionsCount')
        start = rt.Execute(f'{operator}.PRTPartitionsFrom')

        return count, start

partition_output_name(container)

Get partition output name.

Partition output name set for mapping the published file output.

Todo

Customizes the setting for the output.

Parameters:

Name Type Description Default
container

Instance node.

required

Returns:

Name Type Description
str

Partition name.

Source code in client/ayon_max/plugins/publish/extract_pointcloud.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def partition_output_name(self, container):
    """Get partition output name.

    Partition output name set for mapping
    the published file output.

    Todo:
        Customizes the setting for the output.

    Args:
        container: Instance node.

    Returns:
        str: Partition name.

    """
    partition_count, partition_start = self.get_partition(container)
    return f"_part{partition_start:03}of{partition_count}"