Skip to content

extract_obj

ExtractObj

Bases: MayaExtractorPlugin, OptionalPyblishPluginMixin

Extract OBJ from Maya.

This extracts reproducible OBJ exports ignoring any of the settings set on the local machine in the OBJ export options window.

Source code in client/ayon_maya/plugins/publish/extract_obj.py
 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
class ExtractObj(plugin.MayaExtractorPlugin,
                 OptionalPyblishPluginMixin):
    """Extract OBJ from Maya.

    This extracts reproducible OBJ exports ignoring any of the settings
    set on the local machine in the OBJ export options window.

    """
    order = pyblish.api.ExtractorOrder
    label = "Extract OBJ"
    families = ["model"]

    # Default OBJ export options.
    obj_options = {
        "groups": 1,
        "ptgroups": 1,
        "materials": 0,
        "smoothing": 1,
        "normals": 1,
    }

    def process(self, instance):
        if not self.is_active(instance.data):
            return

        # Define output path

        staging_dir = self.staging_dir(instance)
        filename = "{0}.obj".format(instance.name)
        path = os.path.join(staging_dir, filename)

        # The export requires forward slashes because we need to
        # format it into a string in a mel expression

        self.log.debug("Extracting OBJ to: {0}".format(path))

        members = instance.data("setMembers")
        members = cmds.ls(members,
                          dag=True,
                          shapes=True,
                          type=("mesh", "nurbsCurve"),
                          noIntermediate=True,
                          long=True)
        self.log.debug("Members: {0}".format(members))
        self.log.debug("Instance: {0}".format(instance[:]))

        if not cmds.pluginInfo('objExport', query=True, loaded=True):
            cmds.loadPlugin('objExport')

        # Check if shaders should be included as part of the model export. If
        # False, the default shader is assigned to the geometry.
        include_shaders = instance.data.get("include_shaders", False)
        options = self.obj_options.copy()
        if include_shaders:
            options["materials"] = 1

        # Format options for the OBJexport command.
        options_str = ';'.join(
            f"{key}={val}" for key, val in options.items()
        )

        # Export    
        with lib.no_display_layers(instance):
            with lib.displaySmoothness(members,
                                       divisionsU=0,
                                       divisionsV=0,
                                       pointsWire=4,
                                       pointsShaded=1,
                                       polygonObject=1):
                with lib.maintained_selection():
                    cmds.select(members, noExpand=True)
                    cmds.file(path,
                              exportSelected=True,
                              type='OBJexport',
                              op=options_str,
                              preserveReferences=True,
                              force=True)

        if include_shaders:
            # Materials for `.obj` files are exported to a `.mtl` file that
            # usually lives next to the `.obj` and is referenced to by filename
            # from the `.obj` file itself, like:
            # mtllib modelMain.mtl
            # We want to copy the file over and preserve the filename for
            # the materials to load correctly for the obj file, so we add it
            # as explicit file transfer
            mtl_source = path[:-len(".obj")] + ".mtl"
            mtl_filename = os.path.basename(mtl_source)
            publish_dir = instance.data["publishDir"]
            mtl_destination = os.path.join(publish_dir, mtl_filename)
            transfers = instance.data.setdefault("transfers", [])
            transfers.append((mtl_source, mtl_destination))
            self.log.debug(f"Including .mtl file: {mtl_filename}")

            # Include any images from the obj export
            textures = get_textures_from_mtl(mtl_source)
            for texture_src in textures:
                texture_dest = os.path.join(publish_dir,
                                            os.path.basename(texture_src))
                self.log.debug(f"Including texture: {texture_src}")
                transfers.append((texture_src, texture_dest))

        if "representation" not in instance.data:
            instance.data["representation"] = []

        representation = {
            'name': 'obj',
            'ext': 'obj',
            'files': filename,
            "stagingDir": staging_dir,
        }
        instance.data["representations"].append(representation)

        self.log.debug("Extract OBJ successful to: {0}".format(path))

get_textures_from_mtl(mtl_filepath)

Return all textures from a OBJ .mtl sidecar file.

Each line has a separate entry, like map_Ka, where the filename is the last argument on that line.

Notes

Filenames with spaces in them are saved along with the .obj but with spaces replaced to underscores in the .mtl file so they can be detected as the single argument.

Also see

https://paulbourke.net/dataformats/mtl/

Parameters:

Name Type Description Default
mtl_filepath str

Full path to .mtl file to parse.

required

Returns:

Type Description
set[str]

set[str]: Set of files referenced in the MTL file.

Source code in client/ayon_maya/plugins/publish/extract_obj.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
def get_textures_from_mtl(mtl_filepath: str) -> "set[str]":
    """Return all textures from a OBJ `.mtl` sidecar file.

    Each line has a separate entry, like `map_Ka`, where the filename is the
    last argument on that line.

    Notes:
        Filenames with spaces in them are saved along with the `.obj` but with
        spaces replaced to underscores in the `.mtl` file so they can be
        detected as the single argument.

    Also see:
        https://paulbourke.net/dataformats/mtl/

    Arguments:
        mtl_filepath (str): Full path to `.mtl` file to parse.

    Returns:
        set[str]: Set of files referenced in the MTL file.
    """

    map_prefixes = (
        "map_Ka ", 
        "map_Kd ", 
        "map_Ks ", 
        "map_Ns ", 
        "map_d ", 
        "disp ", 
        "decal ",
        "bump ", 
        "refl "
    )

    folder = os.path.dirname(mtl_filepath)
    filepaths = set()
    with open(mtl_filepath, "r", encoding='utf-8') as f:
        for line in f.readlines():
            if line.startswith(map_prefixes):
                line = line.strip()  # strip of end of line
                filename = line.rsplit(" ", 1)[-1]
                filepath = os.path.join(folder, filename)
                filepaths.add(filepath)

    return filepaths