forked from Godlander/objmc
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.py
More file actions
213 lines (175 loc) · 7.15 KB
/
models.py
File metadata and controls
213 lines (175 loc) · 7.15 KB
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
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
from dataclasses import dataclass
from pathlib import Path
from typing import Literal, NamedTuple
@dataclass
class Args:
"""
Represents the arguments required to generate a Minecraft model.
Args:
objs (List[Path]): A list of paths to the model files.
texs (List[Path]): A list of paths to the texture files.
out (List[str]): A list of output file names for the generated model.
offset (Tuple[float, float, float]): A tuple representing the position offset of the model.
scale (float): A float representing the scaling factor of the model.
duration (int): An integer representing the duration of the animation in ticks.
easing (Literal[0, 1, 2, 3]): An integer literal representing the type of animation easing.
interpolation (Literal[0, 1]): An integer literal representing the type of texture interpolation.
colorbehavior (str): A string representing the behavior of the RGB color values.
autorotate (Literal[0, 1, 2, 3]): An integer literal representing the type of auto-rotation.
autoplay (bool): A boolean representing whether the animation should auto-play.
flipuv (bool): A boolean representing whether the UV coordinates should be flipped.
noshadow (bool): A boolean representing whether the model should have shadows.
visibility (int): An integer representing the visibility of the model.
nopow (bool): A boolean representing whether the texture files should have power-of-two dimensions.
join (List[str]): A list of model names to join together.
"""
objs: list[Path] = [""]
# texture animations not supported yet
texs: list[Path] = [""]
# Output json & png
out: list[str] = ["potion.json", "block/out.png"]
# Position & Scaling
# just adds & multiplies vertex positions before encoding, so you dont have to re export the model
offset: tuple[float, float, float] = (0.0, 0.0, 0.0)
scale: float = 1.0
# Duration of the animation in ticks
duration: int = 0
# Animation Easing
# 0: none, 1: linear, 2: in-out cubic, 3: 4-point bezier
easing: Literal[0, 1, 2, 3] = 3
# Texture Interpolation
# 0: none, 1: fade
interpolation: Literal[0, 1] = 1
# Color Behavior
# defines the behavior of 3 bytes of rgb to rotation and animation frames,
# any 3 chars of 'x', 'y', 'z', 't', 'o' is valid
# 'xyz' = rotate, 't' = animation, 'o' = overlay hue
# multiple rotation bytes increase accuracy on that axis
# for 'ttt', autoplay is automatically on. numbers past 8388608 define paused frame to display (suso's idea)
# auto-play color can be calculated by: ((([time query gametime] % 24000) - starting frame) % total duration)
colorbehavior: str = "xyz"
# Auto Rotate
# attempt to estimate rotation with Normals, added to colorbehavior rotation.
# one axis is ok but both is jittery. For display purposes color defined rotation is better.
# 0: none, 1: yaw, 2: pitch, 3: both
autorotate: Literal[0, 1, 2, 3] = 1
# Auto Play
# always interpolate frames, colorbehavior='aaa' overrides this.
autoplay: bool = False
# Flip uv
# if your model renders but textures are not right try toggling this
# i find that blockbench ends up flipping uv, but blender does not. dont trust me too much on this tho i have no idea what causes it.
flipuv: bool = False
# No Shadow
# disable face normal shading (lightmap color still applies)
# can be used for models with lighting baked into the texture
noshadow: bool = False
# Visibility
# determins where the model is visible
# 3 bits for: world, hand, gui
visibility: int = 7
# No power of two textures
# i guess saves a bit of space maybe
# makes it not optifine compatible
nopow: bool = True
# Joining multiple models
join: list[str] = []
class Position(NamedTuple):
x: float
y: float
z: float
class UV(NamedTuple):
u: float
v: float
class Vertex(NamedTuple):
pos: Position
uv: UV
class VertexIndices(NamedTuple):
pos: int
uv: int
@dataclass
class ObjFormat:
vertices: list[Position]
uvs: list[UV]
faces: list[list[VertexIndices]]
@classmethod
def parse(cls, content: str):
vertices = []
uvs = []
faces = []
# TODO: throw errors for invalid OBJ files, like missing vertices
# ex: ValueError -> float("tacos")
for line in content.splitlines():
if line.startswith("v "):
cleaned = line[2:].split() # remove leading + double
vertices.append(Vertex(*(float(num) for num in cleaned)))
elif line.startswith("vt "):
cleaned = line[3:].split()
uvs.append(UV(*(float(num) for num in cleaned)))
elif line.startswith("f "):
face_vertices = line[1:].split(" ")
vertex_indicies = []
for vertex in face_vertices:
splitted = vertex.split("/")
assert len(splitted) in (2, 3) # TODO
pos = int(splitted[0]) - 1
uv = int(splitted[1]) - 1
vertex_indicies.append(VertexIndices(pos, uv))
faces.append(vertex_indicies)
# TODO: handle error
# if nfaces > 0 and len(d["faces"]) != nfaces:
# print(col.err+"mismatched obj face count"+col.end)
return cls(vertices, uvs, faces)
count = [0,0]
mem = {"positions":{},"uvs":{}}
data = {"positions":[],"uvs":[],"vertices":[]}
def readobj(name, nfaces):
obj = open(name, "r", encoding="utf-8")
d = {"positions":[],"uvs":[],"faces":[]}
for line in obj:
if line.startswith("v "):
d["positions"].append(tuple([float(i) for i in " ".join(line.split()).split(" ")[1:]]))
if line.startswith("vt "):
d["uvs"].append(tuple([float(i) for i in " ".join(line.split()).split(" ")[1:]]))
if line.startswith("f "):
d["faces"].append(tuple([[int(i)-1 for i in vert.split("/")] for vert in " ".join(line.split()).split(" ")[1:]]))
obj.close()
if nfaces > 0 and len(d["faces"]) != nfaces:
print(col.err+"mismatched obj face count"+col.end)
exit()
return d
#index vertices
def indexvert(o, vert):
global count
global mem
global data
v = []
pos = o["positions"][vert[0]]
uv = o["uvs"][vert[1]]
posh = ','.join([str(i) for i in pos])
uvh =','.join([str(i) for i in uv])
try:
v.append(mem["positions"][posh])
except:
mem["positions"][posh] = count[0]
data["positions"].append(pos)
v.append(count[0])
count[0] += 1
try:
v.append(mem["uvs"][uvh])
except:
mem["uvs"][uvh] = count[1]
data["uvs"].append(uv)
v.append(count[1])
count[1] += 1
data["vertices"].append(v)
#index obj
def indexobj(o, frame, nframes, nfaces):
for face in range(0, len(o["faces"])):
if face % 1000 == 0:
print("\Reading obj ", frame+1, " of ", nframes, "...", "{:>15.2f}".format((frame*nfaces+face)*100/(nframes*nfaces)), "%\033[K", sep="", end="\r")
face = o["faces"][face]
for vert in face:
indexvert(o, vert)
if len(face) == 3:
indexvert(o, face[1]) indexvert(o, face[1])