Welcome to mirror list, hosted at ThFree Co, Russian Federation.

obj_trimmer.py « scripts - github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: ac8e331f38889809b86e9aa937bb05ef7abf5603 (plain)
1
2
3
4
5
6
7
8
9
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
# Copyright (c) 2022 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import argparse
import os
from typing import Optional, List

"""
    Used to reduce the size of obj files used for printer platform models.
    
    Trims trailing 0 from coordinates
    Removes duplicate vertex texture coordinates
    Removes any rows that are not a face, vertex or vertex texture
"""

def process_obj(input_file: str, output_file: str) -> None:
    with open(input_file, "r") as in_obj, open("temp", "w") as temp:
        trim_lines(in_obj, temp)

    with open("temp", "r") as temp, open(output_file, "w") as out_obj:
        merge_duplicate_vt(temp, out_obj)

    os.remove("temp")


def trim_lines(in_obj: TextIO, out_obj: TextIO) -> None:
    for line in in_obj:
        line = trim_line(line)
        if line:
            out_obj.write(line + "\n")


def trim_line(line: str) -> Optional[str]:
    # Discards all rows that are not a vertex ("v"), face ("f") or vertex texture ("vt")
    values = line.split()

    if values[0] == "vt":
        return trim_vertex_texture(values)
    elif values[0] == "f":
        return trim_face(values)
    elif values[0] == "v":
        return trim_vertex(values)

    return


def trim_face(values: List[str]) -> str:
    # Removes face normals (vn)
    # f 15/15/17 15/15/17 14/14/17 -> f 15/15 15/15 14/14
    for i, coordinates in enumerate(values[1:]):
        v, vt = coordinates.split("/")[:2]
        values[i + 1] = v + "/" + vt

    return " ".join(values)


def trim_vertex(values: List[str]) -> str:
    # Removes trailing zeros from vertex coordinates
    # v 0.044000 0.137000 0.123000 -> v 0.044 0.137 0.123
    for i, coordinate in enumerate(values[1:]):
        values[i + 1] = str(float(coordinate))
    return " ".join(values)


def trim_vertex_texture(values: List[str]) -> str:
    # Removes trailing zeros from vertex texture coordinates
    # vt 0.137000 0.123000 -> v 0.137 0.123
    for i, coordinate in enumerate(values[1:]):
        values[i + 1] = str(float(coordinate))
    return " ".join(values)

def merge_duplicate_vt(in_obj, out_obj):
    # Removes duplicate vertex texture ("vt")
    # Points references to all deleted copies in face ("f") to a single vertex texture

    # Maps index of all copies of a vt line to the same index
    vt_index_mapping = {}
    # Maps vt line to index ("vt 0.043 0.137" -> 23)
    vt_to_index = {}

    # .obj file indexes start at 1
    vt_index = 1
    skipped_count = 0

    # First write everything except faces
    for line in in_obj.readlines():
        if line[0] == "f":
            continue

        if line[:2] == "vt":
            if line in vt_to_index.keys():
                # vt with same value has already been written
                # this points the current vt index to the one that has been written
                vt_index_mapping[vt_index] = vt_to_index[line]
                skipped_count += 1
            else:
                # vt has not been seen, point vt line to index
                vt_to_index[line] = vt_index - skipped_count
                vt_index_mapping[vt_index] = vt_index - skipped_count
                out_obj.write(line)

            vt_index += 1
        else:
            out_obj.write(line)

    # Second pass remaps face vt index
    in_obj.seek(0)
    for line in in_obj.readlines():
        if line[0] != "f":
            continue

        values = line.split()

        for i, coordinates in enumerate(values[1:]):
            v, vt = coordinates.split("/")[:2]
            vt = int(vt)

            if vt in vt_index_mapping.keys():
                vt = vt_index_mapping[vt]

            values[i + 1] = v + "/" + str(vt)

        out_obj.write(" ".join(values) + "\n")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description = "Reduce the size of a .obj file")
    parser.add_argument("input_file", type = str, help = "Input .obj file name")
    parser.add_argument("--output_file", default = "output.obj", type = str, help = "Output .obj file name")
    args = parser.parse_args()
    process_obj(args.input_file, args.output_file)