Skip to content

Commit 8109b98

Browse files
committed
initial fix for mzd files with duplicated faces
1 parent 546152d commit 8109b98

File tree

3 files changed

+262
-18
lines changed

3 files changed

+262
-18
lines changed
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from .mzd import readMZD_to_bpymesh,readMZD_to_meshio
1+
from .mzd import readMZD_to_bpymesh, readMZD_to_meshio,readMZD_to_meshio_with_split_norm
22
from .bgeo import readbgeo_to_meshio
33

4-
additional_format_loader ={'.bgeo':readbgeo_to_meshio, '.mzd':readMZD_to_meshio}
5-
6-
__all__ = [readMZD_to_bpymesh,readMZD_to_meshio,readbgeo_to_meshio,additional_format_loader]
4+
additional_format_loader = {'.bgeo': readbgeo_to_meshio, '.mzd': readMZD_to_meshio}
75

6+
__all__ = [
7+
readMZD_to_bpymesh, readMZD_to_meshio, readbgeo_to_meshio, readMZD_to_meshio_with_split_norm, additional_format_loader
8+
]

additional_file_formats/mzd.py

Lines changed: 172 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
## TODO: use dictionary as cells is going to be deprecated, should use list instead
2+
13
head = b" MZD-File-Format \x00" # c string has \x00 as end
24
end = b" >> END OF FILE << \x00" # c string has \x00 as end
5+
import bpy
36
import numpy as np
47
import meshio
58
from .table import table
@@ -85,6 +88,168 @@ def readMZD_to_meshio(filepath):
8588
start_polyVIndices = end_polyVIndices
8689
start_polyVIndicesNum = b
8790

91+
faces_copy = np.copy(cells['triangle'])
92+
faces_copy.sort(axis=1)
93+
_, indxs, count = np.unique(faces_copy, axis=0, return_index=True, return_counts=True)
94+
faces = cells['triangle'][indxs]
95+
cells['triangle'] = faces
96+
97+
faces_copy = np.copy(cells['quad'])
98+
faces_copy.sort(axis=1)
99+
_, indxs, count = np.unique(faces_copy, axis=0, return_index=True, return_counts=True)
100+
faces = cells['quad'][indxs]
101+
cells['quad'] = faces
102+
103+
elif chunkID == 0xDA7A0001: # vertex normals.
104+
byte = file.read(4)
105+
out_numVerticeAttributes = int.from_bytes(byte, byteorder='little')
106+
if out_numVerticeAttributes != out_numVertices:
107+
return -127
108+
109+
byte = file.read(out_numVerticeAttributes * 6)
110+
out_vertAttribute = np.frombuffer(byte, dtype=np.uint16)
111+
out_vertAttribute = table[out_vertAttribute]
112+
point_data['normal'] = out_vertAttribute.reshape((out_numVerticeAttributes, 3))
113+
114+
elif chunkID == 0xDA7A0002: # vertex motions
115+
byte = file.read(4)
116+
out_numVerticeAttributes = int.from_bytes(byte, byteorder='little')
117+
if out_numVerticeAttributes != out_numVertices:
118+
return -127
119+
120+
byte = file.read(out_numVerticeAttributes * 6)
121+
out_vertAttribute = np.frombuffer(byte, dtype=np.uint16)
122+
out_vertAttribute = table[out_vertAttribute]
123+
point_data['velocity'] = out_vertAttribute.reshape((out_numVerticeAttributes, 3))
124+
125+
elif chunkID == 0xDA7A0003: # vertex colors
126+
byte = file.read(4)
127+
out_numVerticeAttributes = int.from_bytes(byte, byteorder='little')
128+
if out_numVerticeAttributes != out_numVertices:
129+
return -127
130+
131+
byte = file.read(out_numVerticeAttributes * 8)
132+
out_vertAttribute = np.frombuffer(byte, dtype=np.uint16)
133+
out_vertAttribute = table[out_vertAttribute]
134+
point_data['color'] = out_vertAttribute.reshape((out_numVerticeAttributes, 3))
135+
136+
elif chunkID == 0xDA7A0004: # vertex UVWs.
137+
byte = file.read(4)
138+
out_numVerticeAttributes = int.from_bytes(byte, byteorder='little')
139+
if out_numVerticeAttributes != out_numVertices:
140+
return -127
141+
142+
byte = file.read(out_numVerticeAttributes * 12)
143+
out_vertAttribute = np.frombuffer(byte, dtype=np.float32)
144+
point_data['uvw_map'] = out_vertAttribute.reshape((out_numVerticeAttributes, 3))
145+
146+
# For the rest of attributes, because meshio doest not support attributes on nodes, (equivalent to face cornder in blender)
147+
# So the attributes data will be skipped
148+
elif chunkID == 0xDA7A0011: # node normals.
149+
file.seek(size, 1)
150+
pass
151+
elif chunkID == 0xDA7A0013: # node colors.
152+
file.seek(size, 1)
153+
pass
154+
elif chunkID == 0xDA7A0014: # node UVWs.
155+
file.seek(size, 1)
156+
pass
157+
else:
158+
file.seek(size, 1)
159+
pass
160+
return meshio.Mesh(out_vertPositions.reshape((out_numVertices, 3)), cells, point_data)
161+
162+
163+
def readMZD_to_meshio_with_split_norm(filepath):
164+
out_numVertices = None
165+
out_numPolygons = None
166+
out_vertPositions = None
167+
out_numNodes = None # number of loops
168+
out_polyVIndicesNum = None # faces_loop_total
169+
out_polyVIndices = None #loops_vert_idx
170+
cells = {}
171+
point_data = {}
172+
173+
with open(filepath, 'rb') as file:
174+
byte = file.read(24)
175+
if byte != head:
176+
return -4
177+
while 1:
178+
# check if it reach the end
179+
byte = file.read(24)
180+
if byte == end:
181+
break
182+
else:
183+
# if not reach the end, rewind the pointer back 24 bytes
184+
file.seek(-24, 1)
185+
186+
byte = file.read(4)
187+
chunkID = int.from_bytes(byte, byteorder='little')
188+
189+
byte = file.read(24)
190+
name = byte
191+
192+
byte = file.read(4)
193+
size = int.from_bytes(byte, byteorder='little')
194+
195+
if chunkID == 0x0ABC0001: # vertices and polygons.
196+
197+
byte = file.read(4)
198+
out_numVertices = int.from_bytes(byte, byteorder='little')
199+
if out_numVertices < 0:
200+
return -127
201+
if out_numVertices == 0:
202+
break
203+
204+
byte = file.read(12 * out_numVertices)
205+
out_vertPositions = np.frombuffer(byte, dtype=np.float32)
206+
207+
byte = file.read(4)
208+
out_numPolygons = int.from_bytes(byte, byteorder='little')
209+
210+
byte = file.read(out_numPolygons)
211+
out_polyVIndicesNum = np.frombuffer(byte, dtype=np.uint8)
212+
out_numNodes = out_polyVIndicesNum.sum(dtype=np.int32)
213+
214+
byte = file.read(4)
215+
numBytesPerPolyVInd = int.from_bytes(byte, byteorder='little')
216+
217+
if numBytesPerPolyVInd == 4:
218+
# int
219+
byte = file.read(out_numNodes * numBytesPerPolyVInd)
220+
out_polyVIndices = np.frombuffer(byte, dtype=np.int32)
221+
elif numBytesPerPolyVInd == 2:
222+
# unsigned short
223+
byte = file.read(out_numNodes * numBytesPerPolyVInd)
224+
# WARNING: not sure if it's correct
225+
# uncovered branch from test data
226+
out_polyVIndices = np.frombuffer(byte, dtype=np.uint16)
227+
else:
228+
return -127
229+
start_polyVIndicesNum = 0
230+
start_polyVIndices = 0
231+
breaks = np.where(out_polyVIndicesNum[:-1] != out_polyVIndicesNum[1:])[0] + 1
232+
breaks = np.append(breaks, len(out_polyVIndicesNum))
233+
for b in breaks:
234+
poly_nodes_num = out_polyVIndicesNum[start_polyVIndices] # 3(triangle) or 4 (quad)
235+
end_polyVIndices = start_polyVIndices + poly_nodes_num * (b - start_polyVIndicesNum)
236+
cells[num_nodes_to_name[poly_nodes_num]] = out_polyVIndices[start_polyVIndices:end_polyVIndices].reshape(
237+
((b - start_polyVIndicesNum), poly_nodes_num))
238+
start_polyVIndices = end_polyVIndices
239+
start_polyVIndicesNum = b
240+
241+
faces_copy = np.copy(cells['triangle'])
242+
faces_copy.sort(axis=1)
243+
_, indxs = np.unique(faces_copy, axis=0, return_index=True)
244+
faces = cells['triangle'][indxs]
245+
cells['triangle'] = faces
246+
247+
faces_copy = np.copy(cells['quad'])
248+
faces_copy.sort(axis=1)
249+
_, indxs = np.unique(faces_copy, axis=0, return_index=True)
250+
faces = cells['quad'][indxs]
251+
cells['quad'] = faces
252+
88253
elif chunkID == 0xDA7A0001: # vertex normals.
89254
byte = file.read(4)
90255
out_numVerticeAttributes = int.from_bytes(byte, byteorder='little')
@@ -129,7 +294,7 @@ def readMZD_to_meshio(filepath):
129294
point_data['uvw_map'] = out_vertAttribute.reshape((out_numVerticeAttributes, 3))
130295

131296
# For the rest of attributes, because meshio doest not support attributes on nodes, (equivalent to face cornder in blender)
132-
# So the attributes data will be skipped
297+
# So the attributes data will be skipped
133298
elif chunkID == 0xDA7A0011: # node normals.
134299
file.seek(size, 1)
135300
pass
@@ -144,12 +309,13 @@ def readMZD_to_meshio(filepath):
144309
pass
145310
return meshio.Mesh(out_vertPositions.reshape((out_numVertices, 3)), cells, point_data)
146311

312+
147313
def readMZD_to_bpymesh(filepath, mesh):
148314
shade_scheme = False
149315
if mesh.polygons:
150316
shade_scheme = mesh.polygons[0].use_smooth
151317

152-
if len(mesh.vertices)>0:
318+
if len(mesh.vertices) > 0:
153319
mesh.clear_geometry()
154320

155321
out_numVertices = None
@@ -217,20 +383,20 @@ def readMZD_to_bpymesh(filepath, mesh):
217383
return -127
218384

219385
mesh.vertices.add(out_numVertices)
220-
mesh.vertices.foreach_set('co',out_vertPositions)
386+
mesh.vertices.foreach_set('co', out_vertPositions)
221387

222388
mesh.loops.add(out_numNodes)
223-
mesh.loops.foreach_set('vertex_index',out_polyVIndices)
389+
mesh.loops.foreach_set('vertex_index', out_polyVIndices)
224390

225391
mesh.polygons.add(out_numPolygons)
226-
mesh.polygons.foreach_set('loop_total',out_polyVIndicesNum)
392+
mesh.polygons.foreach_set('loop_total', out_polyVIndicesNum)
227393
faces_loop_start = None
228394

229395
if out_polyVIndicesNum.size > 0:
230396
faces_loop_start = np.cumsum(out_polyVIndicesNum)
231397
faces_loop_start = np.roll(faces_loop_start, 1)
232398
faces_loop_start[0] = 0
233-
mesh.polygons.foreach_set('loop_start',faces_loop_start)
399+
mesh.polygons.foreach_set('loop_start', faces_loop_start)
234400
mesh.polygons.foreach_set("use_smooth", [shade_scheme] * out_numPolygons)
235401
mesh.update()
236402
mesh.validate()

template/mzd_template.py

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import fileseq
88
import bpy
99
import additional_file_formats
10+
import numpy as np
1011

1112

1213
# In general we suggest to directly use process for performance and compatibility reason,
@@ -15,11 +16,87 @@
1516

1617
def process(fileseq: fileseq.FileSequence, frame_number: int, mesh:bpy.types.Mesh):
1718
frame_number = frame_number % len(fileseq)
18-
additional_file_formats.readMZD_to_bpymesh(fileseq[frame_number],mesh)
19-
20-
# this will be ignored
21-
def preprocess(fileseq: fileseq.FileSequence, frame_number: int) -> meshio.Mesh:
22-
# this is current implementation, will be slightly different from `process`
23-
frame_number = frame_number % len(fileseq)
24-
mesh = additional_file_formats.readMZD_to_meshio(fileseq[frame_number])
25-
return mesh
19+
meshio_mesh = additional_file_formats.readMZD_to_meshio_with_split_norm(fileseq[frame_number])
20+
21+
mesh_vertices = meshio_mesh.points
22+
23+
n_poly = 0
24+
n_loop = 0
25+
n_verts = len(mesh_vertices)
26+
27+
faces_loop_start = np.array([], dtype=np.uint64)
28+
faces_loop_total = np.array([], dtype=np.uint64)
29+
loops_vert_idx = np.array([], dtype=np.uint64)
30+
shade_scheme = False
31+
if mesh.polygons:
32+
shade_scheme = mesh.polygons[0].use_smooth
33+
for cell in meshio_mesh.cells:
34+
data = extract_faces(cell)
35+
# np array can't be simply written as `if not data:`,
36+
if not data.any():
37+
continue
38+
n_poly += len(data)
39+
n_loop += data.shape[0] * data.shape[1]
40+
loops_vert_idx = np.append(loops_vert_idx, data.ravel())
41+
faces_loop_total = np.append(faces_loop_total, np.ones((len(data)), dtype=np.uint64) * data.shape[1])
42+
if faces_loop_total.size > 0:
43+
faces_loop_start = np.cumsum(faces_loop_total)
44+
# Add a zero as first entry
45+
faces_loop_start = np.roll(faces_loop_start, 1)
46+
faces_loop_start[0] = 0
47+
48+
if len(mesh.vertices) == n_verts and len(mesh.polygons) == n_poly and len(mesh.loops) == n_loop:
49+
pass
50+
else:
51+
mesh.clear_geometry()
52+
mesh.vertices.add(n_verts)
53+
mesh.loops.add(n_loop)
54+
mesh.polygons.add(n_poly)
55+
56+
mesh.vertices.foreach_set("co", mesh_vertices.ravel())
57+
mesh.loops.foreach_set("vertex_index", loops_vert_idx)
58+
mesh.polygons.foreach_set("loop_start", faces_loop_start)
59+
mesh.polygons.foreach_set("loop_total", faces_loop_total)
60+
mesh.polygons.foreach_set("use_smooth", [shade_scheme] * len(faces_loop_total))
61+
62+
mesh.update()
63+
mesh.validate()
64+
65+
# copy attributes
66+
attributes = mesh.attributes
67+
for k, v in meshio_mesh.point_data.items():
68+
if k == 'normal':
69+
# mesh.vertices.foreach_set("normal", v.ravel())
70+
mesh.use_auto_smooth = True
71+
mesh.normals_split_custom_set_from_vertices(v)
72+
73+
continue
74+
k = reserved_word_check(k)
75+
attribute = None
76+
if k not in attributes:
77+
if len(v.shape) == 1:
78+
# one dimensional attribute
79+
attribute = mesh.attributes.new(k, "FLOAT", "POINT")
80+
if len(v.shape) == 2:
81+
dim = v.shape[1]
82+
if dim > 3:
83+
show_message_box('higher than 3 dimensional attribue, ignored')
84+
continue
85+
if dim == 1:
86+
attribute = mesh.attributes.new(k, "FLOAT", "POINT")
87+
if dim == 2:
88+
attribute = mesh.attributes.new(k, "FLOAT2", "POINT")
89+
if dim == 3:
90+
attribute = mesh.attributes.new(k, "FLOAT_VECTOR", "POINT")
91+
if len(v.shape) > 2:
92+
show_message_box('more than 2 dimensional tensor, ignored')
93+
continue
94+
else:
95+
attribute = attributes[k]
96+
name_string = None
97+
if attribute.data_type == "FLOAT":
98+
name_string = "value"
99+
else:
100+
name_string = 'vector'
101+
102+
attribute.data.foreach_set(name_string, v.ravel())

0 commit comments

Comments
 (0)